@fmsim/machine 3.0.0-beta.3 → 3.0.0-beta.5
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-line.js +2 -2
- package/dist/agv-line.js.map +1 -1
- package/dist/agv.js +6 -2
- package/dist/agv.js.map +1 -1
- package/dist/buffer.js +2 -2
- package/dist/buffer.js.map +1 -1
- package/dist/conveyor-join.js +2 -2
- package/dist/conveyor-join.js.map +1 -1
- package/dist/conveyor.js +2 -2
- package/dist/conveyor.js.map +1 -1
- package/dist/crane.js +2 -2
- package/dist/crane.js.map +1 -1
- package/dist/equipment.js +2 -2
- package/dist/equipment.js.map +1 -1
- package/dist/factories/conveyor-3d.js +2 -2
- package/dist/factories/conveyor-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +12 -12
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/factories/oht-3d.js +3 -4
- package/dist/factories/oht-3d.js.map +1 -1
- package/dist/features/legend-panel.js +29 -0
- package/dist/features/legend-panel.js.map +1 -0
- package/dist/features/legend-rows.js +88 -0
- package/dist/features/legend-rows.js.map +1 -0
- package/dist/features/lv-figures.js +137 -0
- package/dist/features/lv-figures.js.map +1 -0
- package/dist/features/lv-heights.js +20 -0
- package/dist/features/lv-heights.js.map +1 -0
- package/dist/features/mcs-status-default.js +3 -49
- package/dist/features/mcs-status-default.js.map +1 -1
- package/dist/features/oht-rail.js +32 -0
- package/dist/features/oht-rail.js.map +1 -0
- package/dist/features/vehicle-travel.js +90 -0
- package/dist/features/vehicle-travel.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcs-carrier-holder.js +19 -25
- package/dist/mcs-carrier-holder.js.map +1 -1
- package/dist/mcs-machine.js +10 -1
- package/dist/mcs-machine.js.map +1 -1
- package/dist/mcs-unit.js +10 -1
- package/dist/mcs-unit.js.map +1 -1
- package/dist/mcs-vehicle.js +2 -60
- package/dist/mcs-vehicle.js.map +1 -1
- package/dist/oht-line.js +7 -3
- package/dist/oht-line.js.map +1 -1
- package/dist/oht.js +2 -2
- package/dist/oht.js.map +1 -1
- package/dist/port.js +2 -2
- package/dist/port.js.map +1 -1
- package/dist/scene/root-container-override.js +15 -210
- package/dist/scene/root-container-override.js.map +1 -1
- package/dist/shelf.js +2 -2
- package/dist/shelf.js.map +1 -1
- package/dist/shuttle.js +2 -2
- package/dist/shuttle.js.map +1 -1
- package/dist/stocker.js +2 -2
- package/dist/stocker.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/agv-line.ts +2 -2
- package/src/agv.ts +7 -2
- package/src/buffer.ts +2 -2
- package/src/conveyor-join.ts +2 -2
- package/src/conveyor.ts +2 -2
- package/src/crane.ts +2 -2
- package/src/equipment.ts +2 -2
- package/src/factories/conveyor-3d.ts +2 -2
- package/src/factories/machine-3d.ts +11 -12
- package/src/factories/oht-3d.ts +3 -5
- package/src/features/legend-panel.ts +31 -0
- package/src/features/legend-rows.ts +86 -0
- package/src/features/lv-figures.ts +144 -0
- package/src/features/lv-heights.ts +22 -0
- package/src/features/mcs-status-default.ts +3 -53
- package/src/features/oht-rail.ts +31 -0
- package/src/features/vehicle-travel.ts +97 -0
- package/src/index.ts +4 -0
- package/src/mcs-carrier-holder.ts +17 -26
- package/src/mcs-machine.ts +12 -1
- package/src/mcs-unit.ts +12 -1
- package/src/mcs-vehicle.ts +3 -71
- package/src/oht-line.ts +7 -3
- package/src/oht.ts +2 -2
- package/src/port.ts +2 -2
- package/src/scene/root-container-override.ts +15 -207
- package/src/shelf.ts +2 -2
- package/src/shuttle.ts +2 -2
- package/src/stocker.ts +2 -2
- package/translations/en.json +7 -1
- package/translations/ja.json +7 -1
- package/translations/ko.json +7 -1
- package/translations/ms.json +7 -1
- package/translations/zh.json +7 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ceilingZPos } from '@hatiolab/things-scene'
|
|
2
|
+
import type Node from '../node'
|
|
3
|
+
import { ohtRailTop } from './oht-rail'
|
|
4
|
+
|
|
5
|
+
/** The node a vehicle's data names: a `Node` of the board with that id and node machine. */
|
|
6
|
+
function findNode(vehicle: any, nodeId: string, nodeMachine: string): Node | null {
|
|
7
|
+
if (!nodeId || !nodeMachine) {
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const nodes = (vehicle.root.findAllById?.(nodeId) || []).filter((node: any) => node.state.type == 'Node') as Node[]
|
|
12
|
+
|
|
13
|
+
return nodes.find((node: Node) => node.nodeMachine === nodeMachine) ?? null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A vehicle's travel between the nodes of the board (`fmsim.Vehicle`, ADR-0167 decision 18): it goes to the centre of
|
|
18
|
+
* the Node its NODEID and NODEMACHINE name, turned the way it came, or -- the board's vehicle animation on -- it is drawn
|
|
19
|
+
* 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
|
|
20
|
+
* its travel's: `vehicleHeading` (degrees) turns it, 0 for fmsim's own.
|
|
21
|
+
*/
|
|
22
|
+
export function VehicleTravel<T extends new (...args: any[]) => any>(Base: T) {
|
|
23
|
+
return class extends Base {
|
|
24
|
+
_lastWaypoint: { NodeId: string; NodeMachine: string } | null = null
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Where a figure hung from the ceiling (an OHT) stands: its top at the rail of the line it runs on, as fmsim's own
|
|
28
|
+
* OHT -- not the ceiling. Things-scene takes it before the ceiling (a written zPos still wins); others say nothing.
|
|
29
|
+
*/
|
|
30
|
+
get effectiveZPos(): number | undefined {
|
|
31
|
+
const own = super.effectiveZPos
|
|
32
|
+
if ((this as any).constructor?.placement !== 'ceiling') return own
|
|
33
|
+
const ro = (this as any)._realObject
|
|
34
|
+
if (!ro || !Number.isFinite(ro.effectiveDepth)) return own
|
|
35
|
+
return ceilingZPos(ro.placementMode, ohtRailTop(this), ro.effectiveDepth)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The angle, in radians, the vehicle's front makes with the way it travels. */
|
|
39
|
+
get travelHeading(): number {
|
|
40
|
+
return ((Number((this as any).state?.vehicleHeading) || 0) * Math.PI) / 180
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onchangeData(after: Record<string, any>, before: Record<string, any>): void {
|
|
44
|
+
super.onchangeData(after, before)
|
|
45
|
+
|
|
46
|
+
const { NODEID: beforeNodeId, NODEMACHINE: beforeNodeMachine } = before.data || {}
|
|
47
|
+
const { NODEID: NodeId, NODEMACHINE: NodeMachine } = after.data || {}
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
NodeId === undefined ||
|
|
51
|
+
NodeMachine === undefined ||
|
|
52
|
+
(beforeNodeId === NodeId && beforeNodeMachine === NodeMachine) ||
|
|
53
|
+
(this._lastWaypoint && this._lastWaypoint.NodeId === NodeId && this._lastWaypoint.NodeMachine === NodeMachine)
|
|
54
|
+
) {
|
|
55
|
+
return // not changed
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const afterNode = findNode(this, NodeId, NodeMachine)
|
|
59
|
+
if (!afterNode) {
|
|
60
|
+
// 보드는 전체 노드의 부분 뷰 — 모든 노드를 그리지 않는다. 위치 NODEID 가 이 보드에
|
|
61
|
+
// 없는 노드를 가리키는 건 오류가 아니라 정상(그 노드는 이 보드에 미표시)이므로,
|
|
62
|
+
// 경고 없이 조용히 배치를 생략한다. (데이터 갱신마다 도는 hot path — 거짓 경보 방지.)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const beforeNode = this._lastWaypoint ? findNode(this, this._lastWaypoint.NodeId, this._lastWaypoint.NodeMachine) : afterNode
|
|
67
|
+
|
|
68
|
+
// 실제 존재하는 노드 중 가장 최근에 이동한 노드
|
|
69
|
+
this._lastWaypoint = { NodeId, NodeMachine }
|
|
70
|
+
|
|
71
|
+
if ((this as any).root.vehicleAnimation) {
|
|
72
|
+
;(this as any).trigger('waypoint', {
|
|
73
|
+
from: beforeNode || afterNode,
|
|
74
|
+
to: afterNode,
|
|
75
|
+
duration: 5000,
|
|
76
|
+
yawOffset: Math.PI / 2 + this.travelHeading
|
|
77
|
+
})
|
|
78
|
+
} else if (beforeNode) {
|
|
79
|
+
const fromCenter = beforeNode.center
|
|
80
|
+
const toCenter = afterNode.center
|
|
81
|
+
|
|
82
|
+
const { x: startX, y: startY } = beforeNode.transcoordS2T(fromCenter.x, fromCenter.y)
|
|
83
|
+
const { x: endX, y: endY } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
84
|
+
|
|
85
|
+
const theta = Math.atan2(endY - startY, endX - startX) + Math.PI / 2
|
|
86
|
+
|
|
87
|
+
;(this as any).location = { x: endX, y: endY }
|
|
88
|
+
;(this as any).setState('rotation', theta + this.travelHeading)
|
|
89
|
+
} else {
|
|
90
|
+
// beforeNode 없음: afterNode 위치로 즉시 이동
|
|
91
|
+
const toCenter = afterNode.center
|
|
92
|
+
const { x, y } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
93
|
+
;(this as any).location = { x, y }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -93,6 +93,10 @@ export { default as Crane } from './crane'
|
|
|
93
93
|
export { default as Shelf } from './shelf'
|
|
94
94
|
export { default as Carrier } from './carrier'
|
|
95
95
|
export { CARRIER_HOLDER_PROPERTIES, CarrierHolder, carrierHolderLegend } from './mcs-carrier-holder'
|
|
96
|
+
export { LVMachine, LVUnit, LVVehicle, StatusFigure, statusAsCode, statusLegend } from './features/lv-figures'
|
|
97
|
+
export { MACHINE_HEIGHTS, TRANSPORT_SURFACE_HEIGHT } from './features/lv-heights'
|
|
98
|
+
export { boardLegendOf, boardLegendOfRow, legendRowsOnBoard } from './features/legend-rows'
|
|
99
|
+
export { legendPanel } from './features/legend-panel'
|
|
96
100
|
|
|
97
101
|
// ── topology ──
|
|
98
102
|
export { default as Node } from './node'
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
figureLegendSourcesChanged,
|
|
10
10
|
itemTypeEntryOf,
|
|
11
11
|
itemTypeTableOf,
|
|
12
|
-
registerFigureCapability,
|
|
13
12
|
registerFigureLegendResolver,
|
|
14
13
|
type ItemType
|
|
15
14
|
} from '@hatiolab/things-scene'
|
|
15
|
+
import { colourAndEffect } from './features/legend-rows'
|
|
16
16
|
import { HOLDER_ANCHOR, type ItemDims, type ResolvedSlot } from '@hatiolab/figure-model'
|
|
17
|
-
import {
|
|
17
|
+
import { carrierHolderProperties } from '@fmsim/figure-capabilities'
|
|
18
18
|
import MCSUnit from './mcs-unit.js'
|
|
19
19
|
import { incomingFromV2 } from './utils/load-address'
|
|
20
20
|
|
|
@@ -56,6 +56,11 @@ export function CarrierHolder<T extends new (...args: any[]) => any>(Base: T) {
|
|
|
56
56
|
return true
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** The board legend rows its carriers follow, with whatever else it follows (ADR-0167 decision 20) */
|
|
60
|
+
get legendRowsUsed(): string[] {
|
|
61
|
+
return [...((super.legendRowsUsed as string[] | undefined) ?? []), 'carrier', 'carrier-animation']
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
/** The records the reconcile took last, as JSON: the same again is not reconciled */
|
|
60
65
|
_carrierRecordsTaken?: string
|
|
61
66
|
|
|
@@ -225,12 +230,13 @@ export function CarrierHolder<T extends new (...args: any[]) => any>(Base: T) {
|
|
|
225
230
|
// ── where carriers stand ──────────────────────────────────────────────────
|
|
226
231
|
|
|
227
232
|
/**
|
|
228
|
-
* Where a holder's carriers rest, in coordinates above the holder's bottom, and whether they stand on it or
|
|
229
|
-
*
|
|
230
|
-
*
|
|
233
|
+
* Where a figure holder's carriers rest, in coordinates above the holder's bottom, and whether they stand on it or
|
|
234
|
+
* hang from it, as its `carrierMount` says (ADR-0167 decision 18): on top of its body, or below it. fmsim's own
|
|
235
|
+
* holders say their own (MCSCarrierHolder and those over it).
|
|
231
236
|
*/
|
|
232
237
|
get carrierRest(): { height: number; mount: 'ON' | 'HANG' } {
|
|
233
|
-
return { height:
|
|
238
|
+
if ((this as any).state?.carrierMount === 'HANG') return { height: 0, mount: 'HANG' }
|
|
239
|
+
return { height: Number((this as any)._realObject?.effectiveDepth) || 0, mount: 'ON' }
|
|
234
240
|
}
|
|
235
241
|
|
|
236
242
|
/** The pile, and the exception area beside the holder's +X edge. Frames in mm on the holder's own anchor (its bottom) */
|
|
@@ -290,6 +296,11 @@ class MCSCarrierHolder extends CarrierHolder(ItemsDataBinding(Holdable(Capacity(
|
|
|
290
296
|
static get properties(): any {
|
|
291
297
|
return [...MCSUnit.properties, ...CARRIER_HOLDER_PROPERTIES]
|
|
292
298
|
}
|
|
299
|
+
|
|
300
|
+
/** On the transport surface by default (fmsim v2). A Port, Shelf or Shuttle holds them at its bottom; an OHT hangs them below. */
|
|
301
|
+
get carrierRest(): { height: number; mount: 'ON' | 'HANG' } {
|
|
302
|
+
return { height: 30, mount: 'ON' }
|
|
303
|
+
}
|
|
293
304
|
}
|
|
294
305
|
|
|
295
306
|
export default MCSCarrierHolder
|
|
@@ -323,24 +334,4 @@ export function carrierHolderLegend({ component, role }: { component: any; role:
|
|
|
323
334
|
return legend && typeof legend === 'object' ? colourAndEffect(legend) : undefined
|
|
324
335
|
}
|
|
325
336
|
|
|
326
|
-
/** A legend as a figure takes it from fmsim: each entry's colour and effect; a colour alone stays the colour; an entry's JSON5 text read. */
|
|
327
|
-
function colourAndEffect(legend: Record<string, unknown>): Record<string, unknown> {
|
|
328
|
-
return Object.fromEntries(
|
|
329
|
-
Object.entries(legend).map(([code, raw]) => {
|
|
330
|
-
let entry = raw
|
|
331
|
-
if (typeof entry === 'string' && entry.trim().startsWith('{')) {
|
|
332
|
-
try {
|
|
333
|
-
entry = JSON5.parse(entry)
|
|
334
|
-
} catch {
|
|
335
|
-
return [code, raw]
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
if (!entry || typeof entry !== 'object') return [code, entry]
|
|
339
|
-
const { color, effect } = entry as { color?: unknown; effect?: unknown }
|
|
340
|
-
return [code, { ...(color !== undefined ? { color } : {}), ...(effect !== undefined ? { effect } : {}) }]
|
|
341
|
-
})
|
|
342
|
-
)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
337
|
registerFigureLegendResolver(carrierHolderLegend)
|
|
346
|
-
registerFigureCapability({ ...CARRIER_HOLDER, properties: CARRIER_HOLDER_PROPERTIES as never }, base => CarrierHolder(base) as never)
|
package/src/mcs-machine.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { getPopupData } from '@fmsim/api'
|
|
|
4
4
|
import { MCSStatusMixin, MCSStatusMixinProperties } from './features/mcs-status-mixin'
|
|
5
5
|
|
|
6
6
|
import { LEGEND_MACHINE, Legend } from './features/mcs-status-default'
|
|
7
|
+
import { boardLegendOfRow } from './features/legend-rows'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* MCS Machine의 공통 속성을 정의한 최상위 오브젝트
|
|
@@ -68,7 +69,17 @@ export default class MCSMachine extends MCSStatusMixin(ContainerAbstract) {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
get legend(): Legend {
|
|
71
|
-
return this.getTheme() || this.getLegendFallback()
|
|
72
|
+
return this.getTheme() || boardLegendOfRow(this, this.statusLegendRow) || this.getLegendFallback()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The board legend row its STATUS colour follows when it chose no legend of its own (ADR-0167 decision 20); none here */
|
|
76
|
+
get statusLegendRow(): string | undefined {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The board legend rows it follows: what the board's LEGENDS panel shows first */
|
|
81
|
+
get legendRowsUsed(): string[] {
|
|
82
|
+
return this.statusLegendRow ? [this.statusLegendRow] : []
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
get auxLegend() {
|
package/src/mcs-unit.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { getPopupData } from '@fmsim/api'
|
|
|
3
3
|
|
|
4
4
|
import { MCSStatusMixin, MCSStatusMixinProperties } from './features/mcs-status-mixin'
|
|
5
5
|
import { LEGEND_UNIT, Legend } from './features/mcs-status-default'
|
|
6
|
+
import { boardLegendOfRow } from './features/legend-rows'
|
|
6
7
|
import { ParentObjectMixin, ParentObjectMixinProperties } from './features/parent-object-mixin'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -50,7 +51,17 @@ export default class MCSUnit extends MCSStatusMixin(ParentObjectMixin(ContainerA
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
get legend(): Legend {
|
|
53
|
-
return this.getTheme() || this.getLegendFallback()
|
|
54
|
+
return this.getTheme() || boardLegendOfRow(this, this.statusLegendRow) || this.getLegendFallback()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The board legend row its STATUS colour follows when it chose no legend of its own (ADR-0167 decision 20); none here */
|
|
58
|
+
get statusLegendRow(): string | undefined {
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The board legend rows it follows: what the board's LEGENDS panel shows first */
|
|
63
|
+
get legendRowsUsed(): string[] {
|
|
64
|
+
return this.statusLegendRow ? [this.statusLegendRow] : []
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
get auxLegend() {
|
package/src/mcs-vehicle.ts
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import { LEGEND_VEHICLE, Legend } from './features/mcs-status-default'
|
|
2
|
+
import { VehicleTravel } from './features/vehicle-travel'
|
|
2
3
|
import MCSCarrierHolder from './mcs-carrier-holder'
|
|
3
|
-
import Node from './node'
|
|
4
4
|
|
|
5
5
|
const DIRECTION_GAP = 3
|
|
6
6
|
|
|
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
7
|
/**
|
|
20
8
|
* MCS용 Unit > Transport들의 공통 속성을 정의한 오브젝트
|
|
21
9
|
*/
|
|
22
|
-
export default class MCSVehicle extends MCSCarrierHolder {
|
|
10
|
+
export default class MCSVehicle extends VehicleTravel(MCSCarrierHolder) {
|
|
23
11
|
static get properties(): any {
|
|
24
12
|
return [
|
|
25
13
|
...MCSCarrierHolder.properties,
|
|
@@ -34,8 +22,6 @@ export default class MCSVehicle extends MCSCarrierHolder {
|
|
|
34
22
|
]
|
|
35
23
|
}
|
|
36
24
|
|
|
37
|
-
private lastWaypoint: { NodeId: string; NodeMachine: string } | null = null
|
|
38
|
-
|
|
39
25
|
getLegendFallback(): Legend {
|
|
40
26
|
return LEGEND_VEHICLE
|
|
41
27
|
}
|
|
@@ -92,59 +78,5 @@ export default class MCSVehicle extends MCSCarrierHolder {
|
|
|
92
78
|
super.postrender(ctx)
|
|
93
79
|
// this.renderDirection(ctx)
|
|
94
80
|
}
|
|
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
81
|
}
|
|
82
|
+
|
package/src/oht-line.ts
CHANGED
|
@@ -5,7 +5,11 @@ const NATURE: ComponentNature = {
|
|
|
5
5
|
mutable: false,
|
|
6
6
|
resizable: true,
|
|
7
7
|
rotatable: true,
|
|
8
|
-
properties: [
|
|
8
|
+
properties: [
|
|
9
|
+
...MCSTransport.properties,
|
|
10
|
+
/* 레일 윗면이 작업환경 천장에서 내려온 길이(mm). 레일과 그 위를 다니는 OHT 가 천장을 따라 움직인다 */
|
|
11
|
+
{ type: 'number', label: 'rail-hang', name: 'railHang', property: { min: 0, step: 100 } }
|
|
12
|
+
]
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
@sceneComponent('OHTLine')
|
|
@@ -18,8 +22,8 @@ export default class OHTLine extends MCSTransport {
|
|
|
18
22
|
return '#333'
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
return
|
|
25
|
+
get statusLegendRow(): string {
|
|
26
|
+
return 'ohtline'
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
containable(component: Component) {
|
package/src/oht.ts
CHANGED
|
@@ -30,8 +30,8 @@ export default class OHT extends MCSVehicle {
|
|
|
30
30
|
return ['OHTLine']
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
return
|
|
33
|
+
get statusLegendRow(): string {
|
|
34
|
+
return 'oht'
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
render(ctx: CanvasRenderingContext2D) {
|
package/src/port.ts
CHANGED
|
@@ -25,8 +25,8 @@ export default class Port extends LoadLayoutHolder(MCSZoneMixin(MCSCarrierHolder
|
|
|
25
25
|
return 'black'
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
return
|
|
28
|
+
get statusLegendRow(): string {
|
|
29
|
+
return 'port'
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** Port 는 후보가 매우 많아 부모 머신 없이는 id 리스트를 조회하지 않는다 (machine 필수). */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component, RootContainer, figureLegendSourcesChanged } from '@hatiolab/things-scene'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { FMSIM_LEGEND_ROWS, legendRows } from '@fmsim/figure-capabilities'
|
|
3
|
+
import { boardLegendOf } from '../features/legend-rows'
|
|
4
|
+
import { legendPanel } from '../features/legend-panel'
|
|
4
5
|
|
|
5
6
|
// RootContainer 클래스 타입 확장
|
|
6
7
|
declare module '@hatiolab/things-scene' {
|
|
@@ -58,158 +59,7 @@ Object.defineProperty(ModelLayer.prototype, 'nature', {
|
|
|
58
59
|
label: 'vehicle-animation',
|
|
59
60
|
name: 'vehicleAnimation'
|
|
60
61
|
},
|
|
61
|
-
|
|
62
|
-
type: 'legend',
|
|
63
|
-
label: '',
|
|
64
|
-
name: 'LEGENDS',
|
|
65
|
-
property: {
|
|
66
|
-
label: 'LEGENDS'
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
type: 'select',
|
|
71
|
-
label: 'equipment-legend-name',
|
|
72
|
-
name: 'equipmentLegendName',
|
|
73
|
-
property: {
|
|
74
|
-
options: themesColorMap
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
type: 'select',
|
|
79
|
-
label: 'buffer-legend-name',
|
|
80
|
-
name: 'bufferLegendName',
|
|
81
|
-
property: {
|
|
82
|
-
options: themesColorMap
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
type: 'select',
|
|
87
|
-
label: 'agvline-legend-name',
|
|
88
|
-
name: 'agvlineLegendName',
|
|
89
|
-
property: {
|
|
90
|
-
options: themesColorMap
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
type: 'select',
|
|
95
|
-
label: 'ohtline-legend-name',
|
|
96
|
-
name: 'ohtlineLegendName',
|
|
97
|
-
property: {
|
|
98
|
-
options: themesColorMap
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
type: 'select',
|
|
103
|
-
label: 'conveyor-legend-name',
|
|
104
|
-
name: 'conveyorLegendName',
|
|
105
|
-
property: {
|
|
106
|
-
options: themesColorMap
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
type: 'select',
|
|
111
|
-
label: 'stoker-legend-name',
|
|
112
|
-
name: 'stokerLegendName',
|
|
113
|
-
property: {
|
|
114
|
-
options: themesColorMap
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
type: 'select',
|
|
119
|
-
label: 'port-legend-name',
|
|
120
|
-
name: 'portLegendName',
|
|
121
|
-
property: {
|
|
122
|
-
options: themesColorMap
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
type: 'select',
|
|
127
|
-
label: 'agv-legend-name',
|
|
128
|
-
name: 'agvLegendName',
|
|
129
|
-
property: {
|
|
130
|
-
options: themesColorMap
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
type: 'select',
|
|
135
|
-
label: 'oht-legend-name',
|
|
136
|
-
name: 'ohtLegendName',
|
|
137
|
-
property: {
|
|
138
|
-
options: themesColorMap
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
type: 'select',
|
|
143
|
-
label: 'shuttle-legend-name',
|
|
144
|
-
name: 'shuttleLegendName',
|
|
145
|
-
property: {
|
|
146
|
-
options: themesColorMap
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
type: 'select',
|
|
151
|
-
label: 'crane-legend-name',
|
|
152
|
-
name: 'craneLegendName',
|
|
153
|
-
property: {
|
|
154
|
-
options: themesColorMap
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
type: 'select',
|
|
159
|
-
label: 'port-legend-name',
|
|
160
|
-
name: 'portLegendName',
|
|
161
|
-
property: {
|
|
162
|
-
options: themesColorMap
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
type: 'select',
|
|
167
|
-
label: 'shelf-legend-name',
|
|
168
|
-
name: 'shelfLegendName',
|
|
169
|
-
property: {
|
|
170
|
-
options: themesColorMap
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
type: 'select',
|
|
175
|
-
label: 'carrier-legend-name',
|
|
176
|
-
name: 'carrierLegendName',
|
|
177
|
-
property: {
|
|
178
|
-
options: themesColorMap
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
type: 'select',
|
|
183
|
-
label: 'carrier-animation-name',
|
|
184
|
-
name: 'carrierAnimationName',
|
|
185
|
-
property: {
|
|
186
|
-
options: themesAnimationMap
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
type: 'select',
|
|
191
|
-
label: 'stocker-capacity-legend-name',
|
|
192
|
-
name: 'stockerCapacityLegendName',
|
|
193
|
-
property: {
|
|
194
|
-
options: themesColorRange
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
type: 'select',
|
|
199
|
-
label: 'zone-capacity-legend-name',
|
|
200
|
-
name: 'zoneCapacityLegendName',
|
|
201
|
-
property: {
|
|
202
|
-
options: themesColorRange
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
type: 'select',
|
|
207
|
-
label: 'battery-rate-legend-name',
|
|
208
|
-
name: 'batteryRateLegendName',
|
|
209
|
-
property: {
|
|
210
|
-
options: themesColorRange
|
|
211
|
-
}
|
|
212
|
-
},
|
|
62
|
+
...legendPanel(this),
|
|
213
63
|
{
|
|
214
64
|
type: 'legend',
|
|
215
65
|
label: '',
|
|
@@ -260,65 +110,23 @@ ModelLayer.prototype.onchange = function (after: any) {
|
|
|
260
110
|
})
|
|
261
111
|
}
|
|
262
112
|
}
|
|
263
|
-
/* 보드의
|
|
264
|
-
if (after &&
|
|
113
|
+
/* 보드의 범례 줄이 바뀌었다: 피규어들이 범례를 다시 고른다 (ADR-0166 결정 12 · ADR-0167 결정 20) */
|
|
114
|
+
if (after && legendRows().some(row => Object.prototype.hasOwnProperty.call(after, row.property))) figureLegendSourcesChanged()
|
|
265
115
|
return _origModelLayerOnchange.call(this, after)
|
|
266
116
|
}
|
|
267
117
|
|
|
268
|
-
// ===
|
|
269
|
-
//
|
|
270
|
-
// rootModel.state.style[name] 에 저장된 legend/animation 값은 보드 직렬화
|
|
271
|
-
// 형태에 따라 (a) 정상 object 또는 (b) JSON 문자열 둘 다로 나타난다 (2026-06-09
|
|
272
|
-
// 진단 — consumer 인 carrier.ts:animationPreset 이 같은 이유로 typeof string
|
|
273
|
-
// 분기 + JSON5.parse 처리하는 선례 존재). LegendTheme getter 들이 이 분기를
|
|
274
|
-
// 두지 않아 string 통과 → 구간 조회 매칭 실패 → 3D gauge 의 색상이
|
|
275
|
-
// fallback 으로만 결정되던 증상.
|
|
118
|
+
// === 범례 줄의 보드 속성 → 범례 (ADR-0167 결정 20) ===
|
|
276
119
|
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!cache) {
|
|
286
|
-
cache = new Map()
|
|
287
|
-
_legendParseCache.set(state, cache)
|
|
288
|
-
}
|
|
289
|
-
if (cache.has(raw)) return cache.get(raw)
|
|
290
|
-
let parsed: any
|
|
291
|
-
try { parsed = JSON.parse(raw) } catch { parsed = raw } // parse 실패 시 raw 보존
|
|
292
|
-
cache.set(raw, parsed)
|
|
293
|
-
return parsed
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function legendThemeGetter(stateKey: string) {
|
|
297
|
-
return function (this: any) {
|
|
298
|
-
const name = this.rootModel.state[stateKey]
|
|
299
|
-
const raw = name && this.state.style[name]
|
|
300
|
-
return parseLegendIfString(this.state, raw)
|
|
301
|
-
}
|
|
120
|
+
// 줄마다 손으로 적던 getter 대신 등록된 줄에서 만든다: 보드 속성 `<x>Name` 의 범례를 `<x>Theme` 로 읽는다
|
|
121
|
+
// (equipmentLegendName → equipmentLegendTheme, carrierAnimationName → carrierAnimationTheme). 문자열 범례는 legend-rows 가 한 번 읽는다.
|
|
122
|
+
for (const row of FMSIM_LEGEND_ROWS) {
|
|
123
|
+
Object.defineProperty(RootContainer.prototype, row.property.replace(/Name$/, 'Theme'), {
|
|
124
|
+
get: function (this: any) {
|
|
125
|
+
return boardLegendOf(this, row.property)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
302
128
|
}
|
|
303
129
|
|
|
304
|
-
Object.defineProperty(RootContainer.prototype, 'equipmentLegendTheme', { get: legendThemeGetter('equipmentLegendName') })
|
|
305
|
-
Object.defineProperty(RootContainer.prototype, 'bufferLegendTheme', { get: legendThemeGetter('bufferLegendName') })
|
|
306
|
-
Object.defineProperty(RootContainer.prototype, 'agvlineLegendTheme', { get: legendThemeGetter('agvlineLegendName') })
|
|
307
|
-
Object.defineProperty(RootContainer.prototype, 'ohtlineLegendTheme', { get: legendThemeGetter('ohtlineLegendName') })
|
|
308
|
-
Object.defineProperty(RootContainer.prototype, 'conveyorLegendTheme', { get: legendThemeGetter('conveyorLegendName') })
|
|
309
|
-
Object.defineProperty(RootContainer.prototype, 'stokerLegendTheme', { get: legendThemeGetter('stokerLegendName') })
|
|
310
|
-
Object.defineProperty(RootContainer.prototype, 'agvLegendTheme', { get: legendThemeGetter('agvLegendName') })
|
|
311
|
-
Object.defineProperty(RootContainer.prototype, 'ohtLegendTheme', { get: legendThemeGetter('ohtLegendName') })
|
|
312
|
-
Object.defineProperty(RootContainer.prototype, 'shuttleLegendTheme', { get: legendThemeGetter('shuttleLegendName') })
|
|
313
|
-
Object.defineProperty(RootContainer.prototype, 'craneLegendTheme', { get: legendThemeGetter('craneLegendName') })
|
|
314
|
-
Object.defineProperty(RootContainer.prototype, 'shelfLegendTheme', { get: legendThemeGetter('shelfLegendName') })
|
|
315
|
-
Object.defineProperty(RootContainer.prototype, 'portLegendTheme', { get: legendThemeGetter('portLegendName') })
|
|
316
|
-
Object.defineProperty(RootContainer.prototype, 'carrierLegendTheme', { get: legendThemeGetter('carrierLegendName') })
|
|
317
|
-
Object.defineProperty(RootContainer.prototype, 'carrierAnimationTheme', { get: legendThemeGetter('carrierAnimationName') })
|
|
318
|
-
Object.defineProperty(RootContainer.prototype, 'stockerCapacityLegendTheme', { get: legendThemeGetter('stockerCapacityLegendName') })
|
|
319
|
-
Object.defineProperty(RootContainer.prototype, 'zoneCapacityLegendTheme', { get: legendThemeGetter('zoneCapacityLegendName') })
|
|
320
|
-
Object.defineProperty(RootContainer.prototype, 'batteryRateLegendTheme', { get: legendThemeGetter('batteryRateLegendName') })
|
|
321
|
-
|
|
322
130
|
Object.defineProperty(RootContainer.prototype, 'vehicleAnimation', {
|
|
323
131
|
get: function () {
|
|
324
132
|
const { vehicleAnimation } = this.rootModel.state
|
package/src/shelf.ts
CHANGED
|
@@ -29,8 +29,8 @@ export default class Shelf extends MCSZoneMixin(MCSCarrierHolder) {
|
|
|
29
29
|
return ['STOCKER']
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
return
|
|
32
|
+
get statusLegendRow(): string {
|
|
33
|
+
return 'shelf'
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
render(ctx: CanvasRenderingContext2D) {
|
package/src/shuttle.ts
CHANGED
|
@@ -24,8 +24,8 @@ export default class Shuttle extends MCSVehicle {
|
|
|
24
24
|
return 'black'
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
return
|
|
27
|
+
get statusLegendRow(): string {
|
|
28
|
+
return 'shuttle'
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
render(ctx: CanvasRenderingContext2D) {
|
package/src/stocker.ts
CHANGED
|
@@ -61,8 +61,8 @@ export default class Stocker extends MCSTransport {
|
|
|
61
61
|
return NATURE
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
return
|
|
64
|
+
get statusLegendRow(): string {
|
|
65
|
+
return 'stocker'
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
containable(component: Component) {
|