@fmsim/machine 3.0.0-beta.2 → 3.0.0-beta.4
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/features/vehicle-travel.js +73 -0
- package/dist/features/vehicle-travel.js.map +1 -0
- package/dist/mcs-carrier-holder.js +4 -3
- package/dist/mcs-carrier-holder.js.map +1 -1
- package/dist/mcs-vehicle.js +6 -60
- package/dist/mcs-vehicle.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jest.config.js +1 -1
- package/package.json +3 -3
- package/src/features/vehicle-travel.ts +82 -0
- package/src/mcs-carrier-holder.ts +6 -3
- package/src/mcs-vehicle.ts +7 -71
package/jest.config.js
CHANGED
|
@@ -4,7 +4,7 @@ module.exports = {
|
|
|
4
4
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
|
5
5
|
transform: {
|
|
6
6
|
'^.+\\.tsx?$': 'babel-jest',
|
|
7
|
-
// 공통 계산(@hatiolab/figure-model)과 things-scene 의 node
|
|
7
|
+
// 공통 계산(@hatiolab/figure-model)과 things-scene 의 node 버전(공통 아이템 믹스인)은 ESM 이라 babel 로 바꿔 읽는다
|
|
8
8
|
'^.+\\.m?js$': 'babel-jest'
|
|
9
9
|
},
|
|
10
10
|
transformIgnorePatterns: ['/node_modules/(?!@hatiolab/(figure-model|things-scene)/)'],
|
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": "3.0.0-beta.
|
|
5
|
+
"version": "3.0.0-beta.4",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"things-scene": true,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@fmsim/api": "^3.0.0-beta.2",
|
|
30
|
-
"@fmsim/figure-capabilities": "^3.0.0-beta.
|
|
30
|
+
"@fmsim/figure-capabilities": "^3.0.0-beta.4",
|
|
31
31
|
"@hatiolab/figure-model": "^0.12.0",
|
|
32
32
|
"@hatiolab/things-scene": "^10.1.90",
|
|
33
33
|
"@operato/slot-layout-editor": "^10.29.33"
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"prettier --write"
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "95b704a73e326b7be3bbd1ef3db5ee31f7ef7184"
|
|
62
62
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type Node from '../node'
|
|
2
|
+
|
|
3
|
+
/** The node a vehicle's data names: a `Node` of the board with that id and node machine. */
|
|
4
|
+
function findNode(vehicle: any, nodeId: string, nodeMachine: string): Node | null {
|
|
5
|
+
if (!nodeId || !nodeMachine) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const nodes = (vehicle.root.findAllById?.(nodeId) || []).filter((node: any) => node.state.type == 'Node') as Node[]
|
|
10
|
+
|
|
11
|
+
return nodes.find((node: Node) => node.nodeMachine === nodeMachine) ?? null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A vehicle's travel between the nodes of the board (`fmsim.Vehicle`, ADR-0167 decision 18): it goes to the centre of
|
|
16
|
+
* the Node its NODEID and NODEMACHINE name, turned the way it came, or -- the board's vehicle animation on -- it is drawn
|
|
17
|
+
* on the way (waypoint). One mixin worn by fmsim's own AGV and OHT and by a figure alike. A figure's front need not be
|
|
18
|
+
* its travel's: `vehicleHeading` (degrees) turns it, 0 for fmsim's own.
|
|
19
|
+
*/
|
|
20
|
+
export function VehicleTravel<T extends new (...args: any[]) => any>(Base: T) {
|
|
21
|
+
return class extends Base {
|
|
22
|
+
_lastWaypoint: { NodeId: string; NodeMachine: string } | null = null
|
|
23
|
+
|
|
24
|
+
/** The angle, in radians, the vehicle's front makes with the way it travels. */
|
|
25
|
+
get travelHeading(): number {
|
|
26
|
+
return ((Number((this as any).state?.vehicleHeading) || 0) * Math.PI) / 180
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onchangeData(after: Record<string, any>, before: Record<string, any>): void {
|
|
30
|
+
super.onchangeData(after, before)
|
|
31
|
+
|
|
32
|
+
const { NODEID: beforeNodeId, NODEMACHINE: beforeNodeMachine } = before.data || {}
|
|
33
|
+
const { NODEID: NodeId, NODEMACHINE: NodeMachine } = after.data || {}
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
NodeId === undefined ||
|
|
37
|
+
NodeMachine === undefined ||
|
|
38
|
+
(beforeNodeId === NodeId && beforeNodeMachine === NodeMachine) ||
|
|
39
|
+
(this._lastWaypoint && this._lastWaypoint.NodeId === NodeId && this._lastWaypoint.NodeMachine === NodeMachine)
|
|
40
|
+
) {
|
|
41
|
+
return // not changed
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const afterNode = findNode(this, NodeId, NodeMachine)
|
|
45
|
+
if (!afterNode) {
|
|
46
|
+
// 보드는 전체 노드의 부분 뷰 — 모든 노드를 그리지 않는다. 위치 NODEID 가 이 보드에
|
|
47
|
+
// 없는 노드를 가리키는 건 오류가 아니라 정상(그 노드는 이 보드에 미표시)이므로,
|
|
48
|
+
// 경고 없이 조용히 배치를 생략한다. (데이터 갱신마다 도는 hot path — 거짓 경보 방지.)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const beforeNode = this._lastWaypoint ? findNode(this, this._lastWaypoint.NodeId, this._lastWaypoint.NodeMachine) : afterNode
|
|
53
|
+
|
|
54
|
+
// 실제 존재하는 노드 중 가장 최근에 이동한 노드
|
|
55
|
+
this._lastWaypoint = { NodeId, NodeMachine }
|
|
56
|
+
|
|
57
|
+
if ((this as any).root.vehicleAnimation) {
|
|
58
|
+
;(this as any).trigger('waypoint', {
|
|
59
|
+
from: beforeNode || afterNode,
|
|
60
|
+
to: afterNode,
|
|
61
|
+
duration: 5000
|
|
62
|
+
})
|
|
63
|
+
} else if (beforeNode) {
|
|
64
|
+
const fromCenter = beforeNode.center
|
|
65
|
+
const toCenter = afterNode.center
|
|
66
|
+
|
|
67
|
+
const { x: startX, y: startY } = beforeNode.transcoordS2T(fromCenter.x, fromCenter.y)
|
|
68
|
+
const { x: endX, y: endY } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
69
|
+
|
|
70
|
+
const theta = Math.atan2(endY - startY, endX - startX) + Math.PI / 2
|
|
71
|
+
|
|
72
|
+
;(this as any).location = { x: endX, y: endY }
|
|
73
|
+
;(this as any).setState('rotation', theta + this.travelHeading)
|
|
74
|
+
} else {
|
|
75
|
+
// beforeNode 없음: afterNode 위치로 즉시 이동
|
|
76
|
+
const toCenter = afterNode.center
|
|
77
|
+
const { x, y } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
78
|
+
;(this as any).location = { x, y }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -227,10 +227,10 @@ export function CarrierHolder<T extends new (...args: any[]) => any>(Base: T) {
|
|
|
227
227
|
/**
|
|
228
228
|
* Where a holder's carriers rest, in coordinates above the holder's bottom, and whether they stand on it or hang from
|
|
229
229
|
* it: on the transport surface by default (fmsim v2). A Port, Shelf or Shuttle holds them at its bottom; an OHT hangs
|
|
230
|
-
* them below.
|
|
230
|
+
* them below. A figure holder says it by its `carrierMount` (ADR-0167 decision 18).
|
|
231
231
|
*/
|
|
232
232
|
get carrierRest(): { height: number; mount: 'ON' | 'HANG' } {
|
|
233
|
-
return { height: 30, mount: 'ON' }
|
|
233
|
+
return (this as any).state?.carrierMount === 'HANG' ? { height: 0, mount: 'HANG' } : { height: 30, mount: 'ON' }
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
/** The pile, and the exception area beside the holder's +X edge. Frames in mm on the holder's own anchor (its bottom) */
|
|
@@ -343,4 +343,7 @@ function colourAndEffect(legend: Record<string, unknown>): Record<string, unknow
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
registerFigureLegendResolver(carrierHolderLegend)
|
|
346
|
-
registerFigureCapability(
|
|
346
|
+
registerFigureCapability(
|
|
347
|
+
{ ...CARRIER_HOLDER, properties: carrierHolderProperties({ legends: themesColorMap, animations: themesAnimationMap, mount: true }) as never },
|
|
348
|
+
base => CarrierHolder(base) as never
|
|
349
|
+
)
|
package/src/mcs-vehicle.ts
CHANGED
|
@@ -1,25 +1,15 @@
|
|
|
1
|
+
import { registerFigureCapability } from '@hatiolab/things-scene'
|
|
2
|
+
import { VEHICLE } from '@fmsim/figure-capabilities'
|
|
1
3
|
import { LEGEND_VEHICLE, Legend } from './features/mcs-status-default'
|
|
4
|
+
import { VehicleTravel } from './features/vehicle-travel'
|
|
2
5
|
import MCSCarrierHolder from './mcs-carrier-holder'
|
|
3
|
-
import Node from './node'
|
|
4
6
|
|
|
5
7
|
const DIRECTION_GAP = 3
|
|
6
8
|
|
|
7
|
-
function findNode(vehicle: MCSVehicle, nodeId: string, nodeMachine: string) {
|
|
8
|
-
if (!nodeId || !nodeMachine) {
|
|
9
|
-
return null
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const nodes = (vehicle.root.findAllById?.(nodeId) || []).filter(node => node.state.type == 'Node') as Node[]
|
|
13
|
-
|
|
14
|
-
return nodes.find((node: Node) => {
|
|
15
|
-
return node.nodeMachine === nodeMachine
|
|
16
|
-
})
|
|
17
|
-
}
|
|
18
|
-
|
|
19
9
|
/**
|
|
20
10
|
* MCS용 Unit > Transport들의 공통 속성을 정의한 오브젝트
|
|
21
11
|
*/
|
|
22
|
-
export default class MCSVehicle extends MCSCarrierHolder {
|
|
12
|
+
export default class MCSVehicle extends VehicleTravel(MCSCarrierHolder) {
|
|
23
13
|
static get properties(): any {
|
|
24
14
|
return [
|
|
25
15
|
...MCSCarrierHolder.properties,
|
|
@@ -34,8 +24,6 @@ export default class MCSVehicle extends MCSCarrierHolder {
|
|
|
34
24
|
]
|
|
35
25
|
}
|
|
36
26
|
|
|
37
|
-
private lastWaypoint: { NodeId: string; NodeMachine: string } | null = null
|
|
38
|
-
|
|
39
27
|
getLegendFallback(): Legend {
|
|
40
28
|
return LEGEND_VEHICLE
|
|
41
29
|
}
|
|
@@ -92,59 +80,7 @@ export default class MCSVehicle extends MCSCarrierHolder {
|
|
|
92
80
|
super.postrender(ctx)
|
|
93
81
|
// this.renderDirection(ctx)
|
|
94
82
|
}
|
|
95
|
-
|
|
96
|
-
onchangeData(after: Record<string, any>, before: Record<string, any>): void {
|
|
97
|
-
super.onchangeData(after, before)
|
|
98
|
-
|
|
99
|
-
const { NODEID: beforeNodeId, NODEMACHINE: beforeNodeMachine } = before.data || {}
|
|
100
|
-
const { NODEID: NodeId, NODEMACHINE: NodeMachine } = after.data || {}
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
NodeId === undefined ||
|
|
104
|
-
NodeMachine === undefined ||
|
|
105
|
-
(beforeNodeId === NodeId && beforeNodeMachine === NodeMachine) ||
|
|
106
|
-
(this.lastWaypoint && this.lastWaypoint.NodeId === NodeId && this.lastWaypoint.NodeMachine === NodeMachine)
|
|
107
|
-
) {
|
|
108
|
-
return // not changed
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const afterNode = findNode(this, NodeId, NodeMachine)
|
|
112
|
-
if (!afterNode) {
|
|
113
|
-
// 보드는 전체 노드의 부분 뷰 — 모든 노드를 그리지 않는다. 위치 NODEID 가 이 보드에
|
|
114
|
-
// 없는 노드를 가리키는 건 오류가 아니라 정상(그 노드는 이 보드에 미표시)이므로,
|
|
115
|
-
// 경고 없이 조용히 배치를 생략한다. (데이터 갱신마다 도는 hot path — 거짓 경보 방지.)
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const beforeNode = this.lastWaypoint
|
|
120
|
-
? findNode(this, this.lastWaypoint.NodeId, this.lastWaypoint.NodeMachine)
|
|
121
|
-
: afterNode
|
|
122
|
-
|
|
123
|
-
// 실제 존재하는 노드 중 가장 최근에 이동한 노드
|
|
124
|
-
this.lastWaypoint = { NodeId, NodeMachine }
|
|
125
|
-
|
|
126
|
-
if ((this.root as any).vehicleAnimation) {
|
|
127
|
-
this.trigger('waypoint', {
|
|
128
|
-
from: beforeNode || afterNode,
|
|
129
|
-
to: afterNode,
|
|
130
|
-
duration: 5000
|
|
131
|
-
})
|
|
132
|
-
} else if (beforeNode) {
|
|
133
|
-
const fromCenter = beforeNode.center
|
|
134
|
-
const toCenter = afterNode.center
|
|
135
|
-
|
|
136
|
-
const { x: startX, y: startY } = beforeNode.transcoordS2T(fromCenter.x, fromCenter.y)
|
|
137
|
-
const { x: endX, y: endY } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
138
|
-
|
|
139
|
-
const theta = Math.atan2(endY - startY, endX - startX) + Math.PI / 2
|
|
140
|
-
|
|
141
|
-
this.location = { x: endX, y: endY }
|
|
142
|
-
this.setState('rotation', theta)
|
|
143
|
-
} else {
|
|
144
|
-
// beforeNode 없음: afterNode 위치로 즉시 이동
|
|
145
|
-
const toCenter = afterNode.center
|
|
146
|
-
const { x, y } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
147
|
-
this.location = { x, y }
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
83
|
}
|
|
84
|
+
|
|
85
|
+
/* fmsim.Vehicle (ADR-0167 18): a figure travels between nodes by the same mixin as fmsim's own AGV and OHT */
|
|
86
|
+
registerFigureCapability(VEHICLE, base => VehicleTravel(base) as never)
|