@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
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.5",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"things-scene": true,
|
|
@@ -27,10 +27,11 @@
|
|
|
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.5",
|
|
31
31
|
"@hatiolab/figure-model": "^0.12.0",
|
|
32
32
|
"@hatiolab/things-scene": "^10.1.90",
|
|
33
|
-
"@operato/slot-layout-editor": "^10.29.33"
|
|
33
|
+
"@operato/slot-layout-editor": "^10.29.33",
|
|
34
|
+
"tinycolor2": "^1.6.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@hatiolab/prettier-config": "^1.0.0",
|
|
@@ -58,5 +59,5 @@
|
|
|
58
59
|
"prettier --write"
|
|
59
60
|
]
|
|
60
61
|
},
|
|
61
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "8a40b51a4db5f5b73a2f18dd4f7455eb5f9518fe"
|
|
62
63
|
}
|
package/src/agv-line.ts
CHANGED
|
@@ -22,8 +22,8 @@ export default class AGVLine extends MCSTransport {
|
|
|
22
22
|
return '#333'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
return
|
|
25
|
+
get statusLegendRow(): string {
|
|
26
|
+
return 'agvline'
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
render(context: CanvasRenderingContext2D) {
|
package/src/agv.ts
CHANGED
|
@@ -37,8 +37,13 @@ export default class AGV extends MCSVehicle {
|
|
|
37
37
|
return ['AGVLine']
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
return
|
|
40
|
+
get statusLegendRow(): string {
|
|
41
|
+
return 'agv'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Its STATUS row and its battery's (the battery is the AGV's own: only a board with AGVs shows the row) */
|
|
45
|
+
get legendRowsUsed(): string[] {
|
|
46
|
+
return [...super.legendRowsUsed, 'battery-rate']
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
getBatteryLegendFallback() {
|
package/src/buffer.ts
CHANGED
|
@@ -14,8 +14,8 @@ export default class Buffer extends MCSMachine {
|
|
|
14
14
|
return NATURE
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
return
|
|
17
|
+
get statusLegendRow(): string {
|
|
18
|
+
return 'buffer'
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
containable(component: Component) {
|
package/src/conveyor-join.ts
CHANGED
|
@@ -98,8 +98,8 @@ export default class ConveyorJoin extends ConveyorMixin(MCSTransport) {
|
|
|
98
98
|
return NATURE
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
return
|
|
101
|
+
get statusLegendRow(): string {
|
|
102
|
+
return 'conveyor'
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
containable(component: Component) {
|
package/src/conveyor.ts
CHANGED
|
@@ -36,8 +36,8 @@ export default class Conveyor extends MCSTransport {
|
|
|
36
36
|
return '#777'
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
return
|
|
39
|
+
get statusLegendRow(): string {
|
|
40
|
+
return 'conveyor'
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
containable(component: Component) {
|
package/src/crane.ts
CHANGED
|
@@ -92,8 +92,8 @@ export default class Crane extends LoadLayoutHolder(MCSVehicle) {
|
|
|
92
92
|
return ['STOCKER']
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
return
|
|
95
|
+
get statusLegendRow(): string {
|
|
96
|
+
return 'crane'
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
private _posAnimator?: CranePositionAnimator
|
package/src/equipment.ts
CHANGED
|
@@ -14,8 +14,8 @@ export default class Equipment extends MCSMachine {
|
|
|
14
14
|
return NATURE
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
return
|
|
17
|
+
get statusLegendRow(): string {
|
|
18
|
+
return 'equipment'
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
containable(component: Component) {
|
|
@@ -15,7 +15,7 @@ import * as THREE from 'three'
|
|
|
15
15
|
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
16
16
|
import { RealObjectGroup, registerRealObjectFactory, MaterialBank } from '@hatiolab/things-scene'
|
|
17
17
|
import type { Component } from '@hatiolab/things-scene'
|
|
18
|
-
import {
|
|
18
|
+
import { MACHINE_HEIGHTS } from '../features/lv-heights.js'
|
|
19
19
|
import { Z_FMSIM } from './z-priority'
|
|
20
20
|
|
|
21
21
|
const FRAME_COLOR = 0x888899
|
|
@@ -27,7 +27,7 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
27
27
|
|
|
28
28
|
get effectiveDepth(): number {
|
|
29
29
|
const { depth } = this.component.state
|
|
30
|
-
return depth ||
|
|
30
|
+
return depth || MACHINE_HEIGHTS.conveyor
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
private resolveColor(): number {
|
|
@@ -14,6 +14,8 @@ import { RealObjectGroup, registerRealObjectFactory, disposeObject3D, MaterialBa
|
|
|
14
14
|
import type { Component } from '@hatiolab/things-scene'
|
|
15
15
|
import { LEGEND_CAPACITY, legendColor, legendRangeEntryOf, resolveCarrierLegend } from '../features/mcs-status-default'
|
|
16
16
|
import { fitGaugeScale } from '../utils/fit-gauge-scale'
|
|
17
|
+
import { railTopOf } from '../features/oht-rail'
|
|
18
|
+
import { MACHINE_HEIGHTS, MACHINE_OPACITY, TRANSPORT_SURFACE_HEIGHT } from '../features/lv-heights'
|
|
17
19
|
import {
|
|
18
20
|
carrierSegmentsFromData,
|
|
19
21
|
abnormalSegmentsFromData,
|
|
@@ -59,12 +61,8 @@ interface MCS3DConfig {
|
|
|
59
61
|
zPriority?: number
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* 모든 이송장치의 상단 높이 = 이 값.
|
|
65
|
-
* 캐리어가 이송장치 간 높이 차 없이 이동할 수 있는 기준 높이.
|
|
66
|
-
*/
|
|
67
|
-
export const TRANSPORT_SURFACE_HEIGHT = 30
|
|
64
|
+
/* 포트 기본 높이와 설비 타입별 높이는 한 곳(features/lv-heights) — 피규어 fmsim.Machine 도 같은 표를 읽는다 */
|
|
65
|
+
export { TRANSPORT_SURFACE_HEIGHT } from '../features/lv-heights'
|
|
68
66
|
|
|
69
67
|
const DEFAULTS: MCS3DConfig = {
|
|
70
68
|
depthRatio: 0.5,
|
|
@@ -502,8 +500,8 @@ function register(type: string, overrides: Partial<MCS3DConfig> = {}) {
|
|
|
502
500
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
503
501
|
|
|
504
502
|
register('Equipment', {
|
|
505
|
-
defaultDepth:
|
|
506
|
-
opacity:
|
|
503
|
+
defaultDepth: MACHINE_HEIGHTS.equipment,
|
|
504
|
+
opacity: MACHINE_OPACITY.equipment,
|
|
507
505
|
metalness: 0.4,
|
|
508
506
|
roughness: 0.6,
|
|
509
507
|
defaultColor: 0x8899aa,
|
|
@@ -511,8 +509,8 @@ register('Equipment', {
|
|
|
511
509
|
})
|
|
512
510
|
|
|
513
511
|
register('Buffer', {
|
|
514
|
-
defaultDepth:
|
|
515
|
-
opacity:
|
|
512
|
+
defaultDepth: MACHINE_HEIGHTS.buffer,
|
|
513
|
+
opacity: MACHINE_OPACITY.buffer,
|
|
516
514
|
metalness: 0.3,
|
|
517
515
|
roughness: 0.7,
|
|
518
516
|
defaultColor: 0x99aa88,
|
|
@@ -534,14 +532,14 @@ register('AGVLine', {
|
|
|
534
532
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
535
533
|
|
|
536
534
|
const OHT_LINE_RAIL_COLOR = 0xcccccc
|
|
537
|
-
const OHT_LINE_HEIGHT = 7 * TRANSPORT_SURFACE_HEIGHT // OHT 상단과 일치
|
|
538
535
|
|
|
539
536
|
class OHTLineRealObject3D extends RealObjectGroup {
|
|
540
537
|
private _mesh?: THREE.Mesh
|
|
541
538
|
|
|
542
539
|
get zPriority(): number { return Z_FMSIM.OHT_LINE }
|
|
543
540
|
get effectiveDepth(): number { return 0 }
|
|
544
|
-
|
|
541
|
+
/** 레일 윗면 = 작업환경 천장 − 라인의 매달림 길이(railHang). OHT 상단이 여기에 매달린다 */
|
|
542
|
+
protected get syncZPosOffset(): number { return railTopOf(this.component) }
|
|
545
543
|
|
|
546
544
|
build() {
|
|
547
545
|
super.build()
|
|
@@ -616,6 +614,7 @@ class OHTLineRealObject3D extends RealObjectGroup {
|
|
|
616
614
|
this.update()
|
|
617
615
|
return
|
|
618
616
|
}
|
|
617
|
+
if ('railHang' in after) this.updateTransform()
|
|
619
618
|
super.onchange(after, before)
|
|
620
619
|
}
|
|
621
620
|
|
package/src/factories/oht-3d.ts
CHANGED
|
@@ -15,14 +15,12 @@ import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeom
|
|
|
15
15
|
import { RealObjectGroup, registerRealObjectFactory, disposeObject3D, MaterialBank } from '@hatiolab/things-scene'
|
|
16
16
|
import type { Component } from '@hatiolab/things-scene'
|
|
17
17
|
import { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js'
|
|
18
|
+
import { ohtRailTop } from '../features/oht-rail.js'
|
|
18
19
|
|
|
19
20
|
const TROLLEY_COLOR = 0x222222
|
|
20
21
|
const FRAME_COLOR = 0x444444
|
|
21
22
|
const DEFAULT_BODY_COLOR = 0x6688cc
|
|
22
23
|
|
|
23
|
-
/** OHT 레일 높이: 스토커(5x) 위 */
|
|
24
|
-
const OHT_RAIL_HEIGHT = 6 * TRANSPORT_SURFACE_HEIGHT
|
|
25
|
-
|
|
26
24
|
export class OHT3D extends RealObjectGroup {
|
|
27
25
|
private _ownMeshes: THREE.Mesh[] = []
|
|
28
26
|
get effectiveDepth(): number {
|
|
@@ -30,9 +28,9 @@ export class OHT3D extends RealObjectGroup {
|
|
|
30
28
|
return depth || TRANSPORT_SURFACE_HEIGHT
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
/**
|
|
31
|
+
/** 자기 라인의 레일에 상단을 매단다 (center origin 기준: 레일 윗면 − 볼륨 중심 반깊이) */
|
|
34
32
|
protected get syncZPosOffset(): number {
|
|
35
|
-
return
|
|
33
|
+
return ohtRailTop(this.component) - this.effectiveDepth / 2
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
private resolveColor(): number {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { themesColorMap, themesColorRange, themesAnimationMap } from '@fmsim/api'
|
|
2
|
+
import { legendRows, type LegendRow } from '@fmsim/figure-capabilities'
|
|
3
|
+
import { legendRowsOnBoard } from './legend-rows'
|
|
4
|
+
|
|
5
|
+
/** The options a row's legend select offers: the board's legends of its kind. */
|
|
6
|
+
const LEGEND_OPTIONS: Record<LegendRow['kind'], unknown> = {
|
|
7
|
+
'color-map': themesColorMap,
|
|
8
|
+
'color-ranges': themesColorRange,
|
|
9
|
+
'animation-map': themesAnimationMap
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const legendSelect = (row: LegendRow) => ({ type: 'select', label: row.label, name: row.property, property: { options: LEGEND_OPTIONS[row.kind] } })
|
|
13
|
+
const legendHeader = (name: string, label: string) => ({ type: 'legend', label: '', name, property: { label } })
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The board's LEGENDS panel, drawn from the registered rows (ADR-0167 decision 20): the rows the board's components
|
|
17
|
+
* follow first, then the others; the stocker's rows apart, as they were.
|
|
18
|
+
*/
|
|
19
|
+
export function legendPanel(board: any): any[] {
|
|
20
|
+
const used = legendRowsOnBoard(board)
|
|
21
|
+
const rows = legendRows().filter(row => row.section !== 'stocker')
|
|
22
|
+
const here = rows.filter(row => used.has(row.name))
|
|
23
|
+
const others = rows.filter(row => !used.has(row.name))
|
|
24
|
+
const stocker = legendRows().filter(row => row.section === 'stocker')
|
|
25
|
+
return [
|
|
26
|
+
legendHeader('LEGENDS', 'LEGENDS'),
|
|
27
|
+
...here.map(legendSelect),
|
|
28
|
+
...(others.length ? [legendHeader('LEGENDS-OTHERS', 'LEGENDS · NOT ON THIS BOARD'), ...others.map(legendSelect)] : []),
|
|
29
|
+
...(stocker.length ? [legendHeader('LEGENDS-STOCKER', 'LEGENDS · STOCKER'), ...stocker.map(legendSelect)] : [])
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import JSON5 from 'json5'
|
|
2
|
+
import tinycolor from 'tinycolor2'
|
|
3
|
+
import { legendRowOf } from '@fmsim/figure-capabilities'
|
|
4
|
+
import type { Legend } from './mcs-status-default'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* A board's legend rows (ADR-0167 decision 20): the legend a row chose is read here, by the row's board property, for
|
|
8
|
+
* fmsim's own components and its figures alike. Kept in the board properties they have always had.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// rootModel.state.style[name] 에 저장된 legend/animation 값은 보드 직렬화 형태에 따라 (a) 정상 object 또는 (b) JSON 문자열
|
|
12
|
+
// 둘 다로 나타난다 (2026-06-09 진단). WeakMap<state, Map<rawString, parsed>> 으로 *raw string 자체를 key* — 보드 편집으로 raw 가
|
|
13
|
+
// 바뀌면 자연 무효화, 같은 raw 의 반복 lookup 은 O(1). root 가 GC 되면 cache 도 같이 GC.
|
|
14
|
+
const _legendParseCache = new WeakMap<object, Map<string, any>>()
|
|
15
|
+
|
|
16
|
+
export function parseLegendIfString(state: any, raw: any): any {
|
|
17
|
+
if (typeof raw !== 'string') return raw
|
|
18
|
+
let cache = _legendParseCache.get(state)
|
|
19
|
+
if (!cache) {
|
|
20
|
+
cache = new Map()
|
|
21
|
+
_legendParseCache.set(state, cache)
|
|
22
|
+
}
|
|
23
|
+
if (cache.has(raw)) return cache.get(raw)
|
|
24
|
+
let parsed: any
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(raw)
|
|
27
|
+
} catch {
|
|
28
|
+
parsed = raw // parse 실패 시 raw 보존
|
|
29
|
+
}
|
|
30
|
+
cache.set(raw, parsed)
|
|
31
|
+
return parsed
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The legend a board property names, read from the board's style: none when the board chose none. */
|
|
35
|
+
export function boardLegendOf(root: any, property: string): Legend | undefined {
|
|
36
|
+
const name = root?.rootModel?.state?.[property]
|
|
37
|
+
if (!name || !root?.state?.style) return undefined
|
|
38
|
+
return parseLegendIfString(root.state, root.state.style[name]) || undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The legend the board chose for a row, for a component on it: none when the row is not registered or not chosen. */
|
|
42
|
+
export function boardLegendOfRow(component: any, row: unknown): Legend | undefined {
|
|
43
|
+
const registered = legendRowOf(row)
|
|
44
|
+
return registered ? boardLegendOf(component?.root, registered.property) : undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A legend colour as a figure takes it: `#RRGGBB`. fmsim's own components draw any CSS colour a legend gives (`navy`,
|
|
49
|
+
* `rgb(33, 148, 33)`, `#f00`); a figure's table takes `#RRGGBB` only (ADR-0166), so an opaque colour is written so. A
|
|
50
|
+
* colour that is not opaque (`transparent`) has no `#RRGGBB` and stays as it is -- the figure says it did not take it.
|
|
51
|
+
*/
|
|
52
|
+
export function figureColourOf(value: unknown): unknown {
|
|
53
|
+
if (typeof value !== 'string' || /^#[0-9a-f]{6}$/i.test(value)) return value
|
|
54
|
+
const colour = tinycolor(value.trim())
|
|
55
|
+
return colour.isValid() && colour.getAlpha() === 1 ? colour.toHexString() : value
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A legend as a figure takes it from fmsim: each entry's colour, written `#RRGGBB`, and effect; an entry's JSON5 text read. */
|
|
59
|
+
export function colourAndEffect(legend: Record<string, unknown>): Record<string, unknown> {
|
|
60
|
+
return Object.fromEntries(
|
|
61
|
+
Object.entries(legend).map(([code, raw]) => {
|
|
62
|
+
let entry = raw
|
|
63
|
+
if (typeof entry === 'string' && entry.trim().startsWith('{')) {
|
|
64
|
+
try {
|
|
65
|
+
entry = JSON5.parse(entry)
|
|
66
|
+
} catch {
|
|
67
|
+
return [code, raw]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!entry || typeof entry !== 'object') return [code, figureColourOf(entry)]
|
|
71
|
+
const { color, effect } = entry as { color?: unknown; effect?: unknown }
|
|
72
|
+
return [code, { ...(color !== undefined ? { color: figureColourOf(color) } : {}), ...(effect !== undefined ? { effect } : {}) }]
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The rows the components of a board follow, each once: what its LEGENDS panel shows first. */
|
|
78
|
+
export function legendRowsOnBoard(container: any): Set<string> {
|
|
79
|
+
const used = new Set<string>()
|
|
80
|
+
const walk = (c: any) => {
|
|
81
|
+
for (const row of c?.legendRowsUsed ?? []) if (row) used.add(row)
|
|
82
|
+
for (const child of c?.components ?? []) walk(child)
|
|
83
|
+
}
|
|
84
|
+
walk(container)
|
|
85
|
+
return used
|
|
86
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { figureLegendSourcesChanged, registerFigureCapability, registerFigureLegendResolver } from '@hatiolab/things-scene'
|
|
2
|
+
import { CARRIER_HOLDER, MACHINE, STATUS, STATUS_LEGEND_ROLE, UNIT, VEHICLE, carrierHolderProperties, statusProperties } from '@fmsim/figure-capabilities'
|
|
3
|
+
import { themesAnimationMap, themesColorMap } from '@fmsim/api'
|
|
4
|
+
import { boardLegendOfRow, colourAndEffect, parseLegendIfString } from './legend-rows'
|
|
5
|
+
import { MACHINE_HEIGHTS, TRANSPORT_SURFACE_HEIGHT } from './lv-heights'
|
|
6
|
+
import { VehicleTravel } from './vehicle-travel'
|
|
7
|
+
import { CarrierHolder } from '../mcs-carrier-holder'
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* fmsim's figure capabilities (ADR-0167 decision 22): what a figure is in LV -- fmsim.Machine, fmsim.Unit or
|
|
11
|
+
* fmsim.Vehicle, one at most -- and the features worn with any of them, fmsim.Status and fmsim.CarrierHolder. A kind
|
|
12
|
+
* says its type (`lvType`), the board row a status follows; the features are the mixins fmsim's own wear.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* STATUS as the figure takes it: a code, so a number (MCS sends 4001) is given as its text ('4001'). The status table reads
|
|
17
|
+
* STATUS as a text and a figure refuses data whose value is not of the declared kind; the component's own data is not changed.
|
|
18
|
+
*/
|
|
19
|
+
export function statusAsCode(data: any): any {
|
|
20
|
+
return data && typeof data === 'object' && typeof data.STATUS === 'number' ? { ...data, STATUS: String(data.STATUS) } : data
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `fmsim.Status`: the figure follows the board row of its kind's type, a change of its legend has its tables chosen
|
|
25
|
+
* again, and its STATUS reaches the figure as a code. The colour is its capability's table, drawn by things-scene.
|
|
26
|
+
*/
|
|
27
|
+
export function StatusFigure<T extends new (...args: any[]) => any>(Base: T) {
|
|
28
|
+
return class extends Base {
|
|
29
|
+
_statusInputs?: unknown
|
|
30
|
+
|
|
31
|
+
/** The figure's inputs, given the data's STATUS as a code when they are made (a new release starts them again) */
|
|
32
|
+
get figureInputs(): any {
|
|
33
|
+
const inputs = super.figureInputs
|
|
34
|
+
if (inputs && inputs !== this._statusInputs) {
|
|
35
|
+
this._statusInputs = inputs
|
|
36
|
+
if (typeof (this as any).state?.data?.STATUS === 'number') inputs.setData(statusAsCode((this as any).state.data))
|
|
37
|
+
}
|
|
38
|
+
return inputs
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The board row its status follows: its kind's type, none without a kind */
|
|
42
|
+
get statusLegendRow(): string | undefined {
|
|
43
|
+
return (this as any).lvType
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get legendRowsUsed(): string[] {
|
|
47
|
+
const row = this.statusLegendRow
|
|
48
|
+
return [...((super.legendRowsUsed as string[] | undefined) ?? []), ...(row ? [row] : [])]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onchange(after: Record<string, any>, before: Record<string, any>) {
|
|
52
|
+
super.onchange?.(after && 'data' in after ? { ...after, data: statusAsCode(after.data) } : after, before)
|
|
53
|
+
if (after && 'legendName' in after) figureLegendSourcesChanged(this)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** An LV kind: its type, chosen or its default, is `lvType`; another type has the figure's legends chosen again. */
|
|
59
|
+
function Kind<T extends new (...args: any[]) => any>(Base: T, property: string, defaultOf: (figure: any) => string) {
|
|
60
|
+
return class extends Base {
|
|
61
|
+
get lvType(): string {
|
|
62
|
+
const chosen = (this as any).state?.[property]
|
|
63
|
+
return typeof chosen === 'string' && chosen ? chosen : defaultOf(this)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onchange(after: Record<string, any>, before: Record<string, any>) {
|
|
67
|
+
super.onchange?.(after, before)
|
|
68
|
+
if (after && property in after) figureLegendSourcesChanged(this)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `fmsim.Machine`: an LV machine of a type, equipment unless chosen -- it stands as tall as fmsim's own machine of that
|
|
75
|
+
* type when the board writes no height (things-scene asks the component, `effectiveDepth`).
|
|
76
|
+
*/
|
|
77
|
+
export function LVMachine<T extends new (...args: any[]) => any>(Base: T) {
|
|
78
|
+
return class extends Kind(Base, 'machineType', () => 'equipment') {
|
|
79
|
+
get effectiveDepth(): number | undefined {
|
|
80
|
+
return MACHINE_HEIGHTS[this.lvType]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onchange(after: Record<string, any>, before: Record<string, any>) {
|
|
84
|
+
super.onchange(after, before)
|
|
85
|
+
/* Another type stands at another height: the 3D object takes it */
|
|
86
|
+
if (after && 'machineType' in after) {
|
|
87
|
+
const real = (this as any)._realObject
|
|
88
|
+
real?.updateDimension?.()
|
|
89
|
+
real?.updateTransform?.()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** `fmsim.Unit`: an LV unit of a type, a port unless chosen -- a tag LV handles it by */
|
|
96
|
+
export function LVUnit<T extends new (...args: any[]) => any>(Base: T) {
|
|
97
|
+
return Kind(Base, 'unitType', () => 'port')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** `fmsim.Vehicle`: an LV vehicle -- it travels between nodes; its type, unless chosen, an AGV on the floor and an OHT on the ceiling */
|
|
101
|
+
export function LVVehicle<T extends new (...args: any[]) => any>(Base: T) {
|
|
102
|
+
return Kind(VehicleTravel(Base), 'vehicleType', figure => (figure?.constructor?.placement === 'ceiling' ? 'oht' : 'agv'))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* `fmsim.CarrierHolder` on a figure: carriers change hands at the transport surface, so a holder that takes them on its
|
|
107
|
+
* top stands that tall when nothing else says -- as fmsim's own AGV and shuttle do. A machine's height wins (whichever is
|
|
108
|
+
* worn over the other); a holder that hangs them or hangs itself from the ceiling (an OHT) keeps its own.
|
|
109
|
+
*/
|
|
110
|
+
export function FigureCarrierHolder<T extends new (...args: any[]) => any>(Base: T) {
|
|
111
|
+
return class extends CarrierHolder(Base) {
|
|
112
|
+
get effectiveDepth(): number | undefined {
|
|
113
|
+
const given = super.effectiveDepth
|
|
114
|
+
if (given !== undefined) return given
|
|
115
|
+
const hung = (this as any).state?.carrierMount === 'HANG' || (this as any).constructor?.placement === 'ceiling'
|
|
116
|
+
return hung ? undefined : TRANSPORT_SURFACE_HEIGHT
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The legend a status figure shows its STATUS by: its own choice (`legendName`, as fmsim's own have), then the board row
|
|
123
|
+
* of its kind's type; none chosen, no answer (its capability's table, fmsim's default legends).
|
|
124
|
+
*/
|
|
125
|
+
export function statusLegend({ component, role }: { component: any; role: string }): unknown {
|
|
126
|
+
if (role !== STATUS_LEGEND_ROLE) return undefined
|
|
127
|
+
const own = component?.state?.legendName
|
|
128
|
+
const root = component?.root
|
|
129
|
+
const row = component?.statusLegendRow
|
|
130
|
+
const legend =
|
|
131
|
+
(typeof own === 'string' && own && root?.state?.style ? parseLegendIfString(root.state, root.state.style[own]) : undefined) ||
|
|
132
|
+
(row ? boardLegendOfRow(component, row) : undefined)
|
|
133
|
+
return legend && typeof legend === 'object' ? colourAndEffect(legend) : undefined
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
registerFigureLegendResolver(statusLegend)
|
|
137
|
+
registerFigureCapability(MACHINE, base => LVMachine(base) as never)
|
|
138
|
+
registerFigureCapability(UNIT, base => LVUnit(base) as never)
|
|
139
|
+
registerFigureCapability(VEHICLE, base => LVVehicle(base) as never)
|
|
140
|
+
registerFigureCapability({ ...STATUS, properties: statusProperties({ legends: themesColorMap }) as never }, base => StatusFigure(base) as never)
|
|
141
|
+
registerFigureCapability(
|
|
142
|
+
{ ...CARRIER_HOLDER, properties: carrierHolderProperties({ legends: themesColorMap, animations: themesAnimationMap, mount: true }) as never },
|
|
143
|
+
base => FigureCarrierHolder(base) as never
|
|
144
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LV's heights (ADR-0167 decision 21): fmsim gives no height on the board -- a machine stands as tall as its type, in
|
|
3
|
+
* transport surfaces, the height every port and transport stands at so carriers move between them level. fmsim's own
|
|
4
|
+
* machines and a figure wearing fmsim.Machine read the one table.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** 포트 기본 높이 (절대값). 모든 이송장치의 상단 높이 = 이 값 — 캐리어가 이송장치 간 높이 차 없이 이동하는 기준 */
|
|
8
|
+
export const TRANSPORT_SURFACE_HEIGHT = 30
|
|
9
|
+
|
|
10
|
+
/** The height a machine of each type stands at, by its legend row name */
|
|
11
|
+
export const MACHINE_HEIGHTS: Record<string, number> = {
|
|
12
|
+
equipment: 3 * TRANSPORT_SURFACE_HEIGHT,
|
|
13
|
+
buffer: TRANSPORT_SURFACE_HEIGHT,
|
|
14
|
+
conveyor: TRANSPORT_SURFACE_HEIGHT
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** How opaque fmsim's own machine of each type is drawn: equipment is seen through, so the ports and carriers in it show */
|
|
18
|
+
export const MACHINE_OPACITY: Record<string, number> = {
|
|
19
|
+
equipment: 0.75,
|
|
20
|
+
buffer: 1,
|
|
21
|
+
conveyor: 1
|
|
22
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { resolveLegend, resolveLegendEntries, type LegendEntry as RangeEntry } from '@hatiolab/things-scene'
|
|
2
|
-
import { LEGEND_CARRIER, legendColor, type Legend, type LegendEntry } from '@fmsim/figure-capabilities'
|
|
2
|
+
import { LEGEND_CARRIER, LEGEND_CRANE, LEGEND_MACHINE, LEGEND_UNIT, LEGEND_VEHICLE, legendColor, type Legend, type LegendEntry } from '@fmsim/figure-capabilities'
|
|
3
3
|
|
|
4
|
-
/* 범례 항목 모양과 캐리어 기본 범례는 피규어 능력과 한 곳(@fmsim/figure-capabilities, ADR-0167) */
|
|
5
|
-
export { LEGEND_CARRIER, legendColor, type Legend, type LegendEntry }
|
|
4
|
+
/* 범례 항목 모양과 캐리어 · 상태 기본 범례는 피규어 능력과 한 곳(@fmsim/figure-capabilities, ADR-0167) */
|
|
5
|
+
export { LEGEND_CARRIER, LEGEND_CRANE, LEGEND_MACHINE, LEGEND_UNIT, LEGEND_VEHICLE, legendColor, type Legend, type LegendEntry }
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 코드의 범례 항목: 그 코드의 항목, 없으면 `default` (ADR-0166 판정 6). things-scene 의 한 조회(resolveLegend)로 읽는다 — 피규어의 표와
|
|
@@ -30,56 +30,6 @@ export function legendRangeEntryOf(value: unknown, legend: Legend | null | undef
|
|
|
30
30
|
|
|
31
31
|
export const LEGEND_NULL: Legend = { default: '#F0F0F0' }
|
|
32
32
|
|
|
33
|
-
export const LEGEND_MACHINE: Legend = {
|
|
34
|
-
'1000': '#707070', // DISCONNECTED
|
|
35
|
-
'1001': '#8BDA5B', // UP
|
|
36
|
-
'1002': '#EB3245', // OUTOFSERVICE
|
|
37
|
-
'1003': '#5630D0', // PM
|
|
38
|
-
'1004': '#FFFFFF', // NOTONLINE
|
|
39
|
-
'1005': '#404040', // DOWN
|
|
40
|
-
'1006': '#F4F226', // NOTAUTO
|
|
41
|
-
'1007': '#FD9B00', // ALARM
|
|
42
|
-
'1008': '#4AA7FE', // FULL
|
|
43
|
-
default: '#F0F0F0'
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const LEGEND_VEHICLE: Legend = {
|
|
47
|
-
'4000': '#EB3245', // OUTOFSERVICE
|
|
48
|
-
'4001': '#8BDA5B', // ASSIGNED
|
|
49
|
-
// '4002': '#cbf266', // ASSIGNED_ACQUIRING
|
|
50
|
-
// '4003': '#cbf266', // ASSIGNED_DEPOSITING
|
|
51
|
-
// '4004': '#cbf266', // ASSIGNED_ENROUTE
|
|
52
|
-
// '4005': '#cbf266', // ASSIGNED_PARKED
|
|
53
|
-
'4006': '#FD9B00', // NOTASSIGNED
|
|
54
|
-
'4007': '#32D1C4', // INSTALLED
|
|
55
|
-
'4008': '#9790EC', // CHARGING
|
|
56
|
-
'4009': '#4AA7FE', // CHARGED
|
|
57
|
-
'4010': '#404040', // REMOVED
|
|
58
|
-
default: '#F0F0F0'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const LEGEND_UNIT: Legend = {
|
|
62
|
-
'2000': '#FD9B00', // BANNED
|
|
63
|
-
'2001': '#404040', // DOWN
|
|
64
|
-
'2002': '#8BDA5B', // EMPTY
|
|
65
|
-
'2003': '#FFFFFF', // EMPTY_RESERVED
|
|
66
|
-
'2004': '#32D1C4', // FULL_RESERVED
|
|
67
|
-
'2005': '#4AA7FE', // FULL
|
|
68
|
-
'2006': '#EB3245', // NOTINSERVICE
|
|
69
|
-
default: '#F0F0F0'
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export const LEGEND_CRANE: Legend = {
|
|
73
|
-
'3000': '#EB3245', // OUTOFSERVICE
|
|
74
|
-
// '3001': '#404040', // DOWN
|
|
75
|
-
// '3002': '#8BDA5B', // EMPTY
|
|
76
|
-
'3003': '#FFFFFF', // IDLE
|
|
77
|
-
'3004': '#32D1C4', // ACTIVE
|
|
78
|
-
// '3005': '#4AA7FE', // FULL
|
|
79
|
-
// '3006': '#EB3245', // NOTINSERVICE
|
|
80
|
-
default: '#F0F0F0'
|
|
81
|
-
}
|
|
82
|
-
|
|
83
33
|
/**
|
|
84
34
|
* carrier legend(테마) 해석 — carrier 컴포넌트(carrier.ts get legend)와 동일 로직 단일 소스.
|
|
85
35
|
* parent 에 carrierLegendName 이 지정되면 그 style 을, 아니면 board 전역 carrierLegendTheme,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { drawingScaleFor, sceneHeightsOf } from '@hatiolab/things-scene'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Where an OHT runs: on its line's rail, hung from the work environment's ceiling. The rail's top is the ceiling less the
|
|
5
|
+
* line's hang (`railHang`, mm); an OHT -- fmsim's own or a figure wearing fmsim.Vehicle -- hangs its top from the rail of
|
|
6
|
+
* the line it runs on. Nothing here is a height of its own: each follows the ceiling of the room the board draws.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** The top of an OHT line's rail, in drawing units: the scene's ceiling less the line's hang. */
|
|
10
|
+
export function railTopOf(line: any): number {
|
|
11
|
+
const ceiling = sceneHeightsOf(line).ceiling
|
|
12
|
+
const hang = Number(line?.state?.railHang) || 0
|
|
13
|
+
return ceiling - hang / drawingScaleFor(line).mmPerUnit
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The OHT line a vehicle runs on: the one its data names (NODEMACHINE), its parent object, or the line holding it. */
|
|
17
|
+
export function ohtLineOf(vehicle: any): any {
|
|
18
|
+
const isLine = (c: any) => c?.state?.type === 'OHTLine'
|
|
19
|
+
for (const id of [vehicle?.data?.NODEMACHINE, vehicle?.state?.parentObjectId]) {
|
|
20
|
+
if (!id) continue
|
|
21
|
+
const line = (vehicle.root?.findAllById?.(id) || []).find(isLine)
|
|
22
|
+
if (line) return line
|
|
23
|
+
}
|
|
24
|
+
return isLine(vehicle?.parent) ? vehicle.parent : undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The height an OHT hangs its top from, in drawing units: its line's rail, or the ceiling when it runs on none. */
|
|
28
|
+
export function ohtRailTop(vehicle: any): number {
|
|
29
|
+
const line = ohtLineOf(vehicle)
|
|
30
|
+
return line ? railTopOf(line) : sceneHeightsOf(vehicle).ceiling
|
|
31
|
+
}
|