@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/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 판(공통 아이템 믹스인)은 ESM 이라 babel 로 바꿔 읽는다
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.2",
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.2",
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": "23b7fedf8bb2b70b1150b963bd0f75d77a26052d"
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({ ...CARRIER_HOLDER, properties: CARRIER_HOLDER_PROPERTIES as never }, base => CarrierHolder(base) as never)
346
+ registerFigureCapability(
347
+ { ...CARRIER_HOLDER, properties: carrierHolderProperties({ legends: themesColorMap, animations: themesAnimationMap, mount: true }) as never },
348
+ base => CarrierHolder(base) as never
349
+ )
@@ -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)