@fmsim/machine 2.0.0-beta.99 → 2.0.1
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 +9 -1
- package/dist/carrier-manager.js.map +1 -1
- package/dist/carrier.js +69 -52
- package/dist/carrier.js.map +1 -1
- package/dist/crane.js +15 -6
- package/dist/crane.js.map +1 -1
- package/dist/factories/agv-3d.js +9 -27
- package/dist/factories/agv-3d.js.map +1 -1
- package/dist/factories/conveyor-3d.js +11 -19
- package/dist/factories/conveyor-3d.js.map +1 -1
- package/dist/factories/crane-3d.js +8 -17
- package/dist/factories/crane-3d.js.map +1 -1
- package/dist/factories/crane-rail-3d.js +8 -12
- package/dist/factories/crane-rail-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +100 -63
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/factories/oht-3d.js +9 -27
- package/dist/factories/oht-3d.js.map +1 -1
- package/dist/factories/shuttle-3d.js +11 -27
- package/dist/factories/shuttle-3d.js.map +1 -1
- package/dist/features/capacity-gauge-mixin.js +520 -0
- package/dist/features/capacity-gauge-mixin.js.map +1 -0
- package/dist/features/mcs-status-default.js +13 -0
- package/dist/features/mcs-status-default.js.map +1 -1
- package/dist/features/parent-object-mixin.js +5 -1
- package/dist/features/parent-object-mixin.js.map +1 -1
- package/dist/mcs-machine.js +4 -23
- package/dist/mcs-machine.js.map +1 -1
- package/dist/mcs-unit.js +2 -5
- package/dist/mcs-unit.js.map +1 -1
- package/dist/mcs-vehicle.js +3 -1
- package/dist/mcs-vehicle.js.map +1 -1
- package/dist/scene/root-container-override.js +5 -2
- package/dist/scene/root-container-override.js.map +1 -1
- package/dist/stocker-capacity-bar.js +11 -372
- package/dist/stocker-capacity-bar.js.map +1 -1
- package/dist/stocker.js +29 -1
- package/dist/stocker.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/crane-position.js +23 -14
- package/dist/utils/crane-position.js.map +1 -1
- package/dist/utils/stocker-gauge.js +48 -10
- package/dist/utils/stocker-gauge.js.map +1 -1
- package/dist/zone-capacity-bar.js +13 -213
- package/dist/zone-capacity-bar.js.map +1 -1
- package/package.json +4 -4
- package/src/carrier-manager.ts +9 -1
- package/src/carrier.ts +73 -52
- package/src/crane.ts +14 -5
- package/src/factories/agv-3d.ts +10 -26
- package/src/factories/conveyor-3d.ts +12 -19
- package/src/factories/crane-3d.ts +9 -17
- package/src/factories/crane-rail-3d.ts +9 -13
- package/src/factories/machine-3d.ts +125 -66
- package/src/factories/oht-3d.ts +10 -26
- package/src/factories/shuttle-3d.ts +12 -26
- package/src/features/capacity-gauge-mixin.ts +642 -0
- package/src/features/mcs-status-default.ts +13 -0
- package/src/features/parent-object-mixin.ts +5 -1
- package/src/mcs-machine.ts +4 -23
- package/src/mcs-unit.ts +2 -5
- package/src/mcs-vehicle.ts +3 -1
- package/src/scene/root-container-override.ts +5 -2
- package/src/stocker-capacity-bar.ts +15 -476
- package/src/stocker.ts +29 -1
- package/src/utils/crane-position.ts +46 -14
- package/src/utils/stocker-gauge.ts +63 -10
- package/src/zone-capacity-bar.ts +17 -282
- package/translations/en.json +4 -1
- package/translations/ja.json +4 -1
- package/translations/ko.json +4 -1
- package/translations/ms.json +4 -1
- package/translations/zh.json +4 -1
package/src/mcs-machine.ts
CHANGED
|
@@ -84,32 +84,13 @@ export default class MCSMachine extends MCSStatusMixin(ContainerAbstract) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* const fade = (alpha || 1) - this._delta.fade
|
|
91
|
-
* if (fade) context.globalAlpha = fade
|
|
92
|
-
*
|
|
93
|
-
* It *overwrites* `globalAlpha` (not multiplies), so the inherited
|
|
94
|
-
* parent alpha is also lost — that's a base-layer quirk we match the
|
|
95
|
-
* recovery for. Then at line 48 it applies editMode+hidden dimming
|
|
96
|
-
* with a *= 0.5.
|
|
97
|
-
*
|
|
98
|
-
* Our recovery:
|
|
99
|
-
* 1. Capture the inherited `globalAlpha` *before* super runs.
|
|
100
|
-
* 2. Let super do its work (transforms / shadow / dim flags).
|
|
101
|
-
* 3. Restore globalAlpha to inherited × editMode-hidden dim factor —
|
|
102
|
-
* i.e., what super would have produced if our state.alpha were 1.
|
|
103
|
-
*
|
|
104
|
-
* 3D path is unaffected: 3D factories read `state.alpha` directly via
|
|
105
|
-
* their `updateAlpha()` implementations and continue to honor it (with
|
|
106
|
-
* per-config opacity fallback).
|
|
87
|
+
* 2D 에서는 `state.alpha`(3D material 전용)를 무시한다. hidden/모델링 dim 흐림은 **코어(draw)가
|
|
88
|
+
* 일괄 처리**하므로 여기서 손대지 않는다 (component-drawing.ts draw 가 prerender 직후 faint 적용).
|
|
89
|
+
* fade 애니메이션은 보존. 3D 는 factory `updateAlpha()` 가 state.alpha 를 별도로 존중.
|
|
107
90
|
*/
|
|
108
91
|
prerender(ctx: any) {
|
|
109
|
-
const inherited = ctx.globalAlpha
|
|
110
92
|
super.prerender(ctx)
|
|
111
|
-
|
|
112
|
-
ctx.globalAlpha = inherited * dim
|
|
93
|
+
ctx.globalAlpha = 1 - this._delta.fade
|
|
113
94
|
}
|
|
114
95
|
|
|
115
96
|
render(ctx) {
|
package/src/mcs-unit.ts
CHANGED
|
@@ -62,14 +62,11 @@ export default class MCSUnit extends MCSStatusMixin(ParentObjectMixin(ContainerA
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
66
|
-
* See MCSMachine.prerender for the full rationale.
|
|
65
|
+
* 2D 에서는 state.alpha 무시. hidden/모델링 dim 흐림은 코어(draw)가 일괄 처리 (MCSMachine 동일).
|
|
67
66
|
*/
|
|
68
67
|
prerender(ctx: any) {
|
|
69
|
-
const inherited = ctx.globalAlpha
|
|
70
68
|
super.prerender(ctx)
|
|
71
|
-
|
|
72
|
-
ctx.globalAlpha = inherited * dim
|
|
69
|
+
ctx.globalAlpha = 1 - this._delta.fade
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
contains(x, y) {
|
package/src/mcs-vehicle.ts
CHANGED
|
@@ -110,7 +110,9 @@ export default class MCSVehicle extends MCSCarrierHolder {
|
|
|
110
110
|
|
|
111
111
|
const afterNode = findNode(this, NodeId, NodeMachine)
|
|
112
112
|
if (!afterNode) {
|
|
113
|
-
|
|
113
|
+
// 보드는 전체 노드의 부분 뷰 — 모든 노드를 그리지 않는다. 위치 NODEID 가 이 보드에
|
|
114
|
+
// 없는 노드를 가리키는 건 오류가 아니라 정상(그 노드는 이 보드에 미표시)이므로,
|
|
115
|
+
// 경고 없이 조용히 배치를 생략한다. (데이터 갱신마다 도는 hot path — 거짓 경보 방지.)
|
|
114
116
|
return
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -217,6 +217,7 @@ Object.defineProperty(ModelLayer.prototype, 'nature', {
|
|
|
217
217
|
type: 'legend',
|
|
218
218
|
label: '',
|
|
219
219
|
name: 'MOOD',
|
|
220
|
+
requires3d: true,
|
|
220
221
|
property: {
|
|
221
222
|
label: 'MOOD'
|
|
222
223
|
}
|
|
@@ -225,6 +226,7 @@ Object.defineProperty(ModelLayer.prototype, 'nature', {
|
|
|
225
226
|
type: 'select',
|
|
226
227
|
label: 'light-preset',
|
|
227
228
|
name: 'lightPreset',
|
|
229
|
+
requires3d: true,
|
|
228
230
|
property: {
|
|
229
231
|
options: ['dark', 'medium', 'bright']
|
|
230
232
|
}
|
|
@@ -367,8 +369,9 @@ RootContainer.prototype._refreshStockerGauges = function () {
|
|
|
367
369
|
if (type === 'STOCKER') {
|
|
368
370
|
// 3D 게이지는 스토커 자신의 real-object 가 그린다 → 재빌드 (2D 모드면 _realObject 없음)
|
|
369
371
|
c._realObject?.update?.()
|
|
370
|
-
} else if (type === 'StockerCapacityBar' || type === 'MCSGaugeCapacityBar') {
|
|
371
|
-
// 2D
|
|
372
|
+
} else if (type === 'StockerCapacityBar' || type === 'MCSGaugeCapacityBar' || type === 'ZoneCapacityBar') {
|
|
373
|
+
// 2D 게이지(스토커·존)는 자식 컴포넌트가 그린다 → 리드로우.
|
|
374
|
+
// 존 게이지도 스토커 게이지와 같은 stockerGaugeMode 스위치로 함께 전환된다.
|
|
372
375
|
c.invalidate?.()
|
|
373
376
|
}
|
|
374
377
|
walk(c)
|
|
@@ -1,138 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ComponentNature,
|
|
4
|
-
POSITION,
|
|
5
|
-
Properties,
|
|
6
|
-
RectPath,
|
|
7
|
-
Shape,
|
|
8
|
-
sceneComponent
|
|
9
|
-
} from '@hatiolab/things-scene'
|
|
10
|
-
import { themesColorRange } from '@fmsim/api'
|
|
1
|
+
import { ComponentNature, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'
|
|
11
2
|
import { MCSStatusMixin } from './features/mcs-status-mixin'
|
|
12
3
|
import { LEGEND_CAPACITY, Legend } from './features/mcs-status-default'
|
|
13
|
-
import { safeRound } from './utils/safe-round'
|
|
14
|
-
import { getValueOnRanges } from './utils/get-value-on-ranges'
|
|
15
4
|
import { ParentObjectMixin, ParentObjectMixinProperties } from './features/parent-object-mixin'
|
|
16
|
-
import {
|
|
5
|
+
import { CapacityGaugeMixin, CapacityGaugeMixinProperties } from './features/capacity-gauge-mixin'
|
|
17
6
|
|
|
18
7
|
/**
|
|
19
8
|
* 스토커 게이지 (통합).
|
|
20
9
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* - 'abnormal' : 캐리어 정상/비정상 비율 세그먼트 (Normal / Abnormal)
|
|
10
|
+
* mode 에 따라 capacity / carrier / abnormal 3가지 게이지를 표현한다. 그리는 규칙은
|
|
11
|
+
* CapacityGaugeMixin 에 있으며 ZoneCapacityBar 와 완전히 공유한다(단일 소스). 스토커는
|
|
12
|
+
* STOCKER 내부에 놓이는 Shape 로, 부모 STOCKER 가 데이터를 push 한다(isIdentifiable=false).
|
|
25
13
|
*
|
|
26
|
-
* mode 소스: root.stockerGaugeMode (
|
|
27
|
-
* RootContainer 의 **런타임 전용 transient 속성** — 모델에 저장하지 않고 항상 'capacity'
|
|
28
|
-
* 로 시작하며 실행 중 동적으로만 변경된다(root-container-override.ts 정의). 3D 표현
|
|
29
|
-
* (machine-3d.ts buildGauge)과 동일 소스라 2D/3D 게이지가 항상 일치한다.
|
|
30
|
-
*
|
|
31
|
-
* 데이터 필드(2D 기준, canonical):
|
|
32
|
-
* - capacity : CURRENTCAPACITY / MAXCAPACITY / HIGHWATERMARK / LOWWATERMARK / FULLUP
|
|
33
|
-
* - carrier : CARRIER_FULL / CARRIER_EMPTY / CARRIER_EMPTY_EMPTY
|
|
34
|
-
* - abnormal : CARRIER_NORMAL / CARRIER_ABNORMAL
|
|
14
|
+
* mode 소스: root.stockerGaugeMode. 3D(machine-3d buildGauge)와 동일 소스라 2D/3D 가 일치한다.
|
|
35
15
|
*/
|
|
36
16
|
|
|
37
17
|
const NATURE: ComponentNature = {
|
|
38
18
|
mutable: false,
|
|
39
19
|
resizable: true,
|
|
40
20
|
rotatable: true,
|
|
41
|
-
properties: [
|
|
42
|
-
...ParentObjectMixinProperties,
|
|
43
|
-
{
|
|
44
|
-
type: 'select',
|
|
45
|
-
label: 'legend-name',
|
|
46
|
-
name: 'legendName',
|
|
47
|
-
property: {
|
|
48
|
-
options: themesColorRange
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
type: 'number',
|
|
53
|
-
name: 'currentUsage',
|
|
54
|
-
label: 'current-usage'
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
type: 'number',
|
|
58
|
-
name: 'highWatermark',
|
|
59
|
-
label: 'high-watermark'
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
type: 'number',
|
|
63
|
-
name: 'lowWatermark',
|
|
64
|
-
label: 'low-watermark'
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
type: 'number',
|
|
68
|
-
name: 'maxCapacity',
|
|
69
|
-
label: 'max-capacity'
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
type: 'boolean',
|
|
73
|
-
name: 'fullup',
|
|
74
|
-
label: 'fullup'
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
type: 'number',
|
|
78
|
-
name: 'carrierFull',
|
|
79
|
-
label: 'carrier-full'
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
type: 'number',
|
|
83
|
-
name: 'carrierEmpty',
|
|
84
|
-
label: 'carrier-empty'
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
type: 'number',
|
|
88
|
-
name: 'carrierEmptyEmpty',
|
|
89
|
-
label: 'carrier-empty-empty'
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
type: 'number',
|
|
93
|
-
name: 'carrierNormal',
|
|
94
|
-
label: 'carrier-normal'
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
type: 'number',
|
|
98
|
-
name: 'carrierAbnormal',
|
|
99
|
-
label: 'carrier-abnormal'
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
type: 'number',
|
|
103
|
-
label: 'round',
|
|
104
|
-
name: 'round',
|
|
105
|
-
property: {
|
|
106
|
-
min: 0
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
type: 'hidden',
|
|
111
|
-
name: 'usage',
|
|
112
|
-
label: 'usage'
|
|
113
|
-
}
|
|
114
|
-
],
|
|
21
|
+
properties: [...ParentObjectMixinProperties, ...CapacityGaugeMixinProperties],
|
|
115
22
|
'value-property': 'usage'
|
|
116
23
|
}
|
|
117
24
|
|
|
118
|
-
var controlHandler = {
|
|
119
|
-
ondragmove: function (point: POSITION, index: number, component: Component) {
|
|
120
|
-
var { left, top, width, height } = component.model
|
|
121
|
-
|
|
122
|
-
var transcoorded = component.transcoordP2S(point.x, point.y)
|
|
123
|
-
var round = ((transcoorded.x - left) / (width / 2)) * 100
|
|
124
|
-
|
|
125
|
-
round = safeRound(round, width, height)
|
|
126
|
-
|
|
127
|
-
component.set({
|
|
128
|
-
round
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
25
|
@sceneComponent('StockerCapacityBar')
|
|
134
26
|
@sceneComponent('MCSGaugeCapacityBar')
|
|
135
|
-
export default class StockerCapacityBar extends
|
|
27
|
+
export default class StockerCapacityBar extends CapacityGaugeMixin(
|
|
28
|
+
ParentObjectMixin(MCSStatusMixin(RectPath(Shape)))
|
|
29
|
+
) {
|
|
136
30
|
static get nature() {
|
|
137
31
|
return NATURE
|
|
138
32
|
}
|
|
@@ -142,377 +36,22 @@ export default class StockerCapacityBar extends ParentObjectMixin(MCSStatusMixin
|
|
|
142
36
|
return ['STOCKER']
|
|
143
37
|
}
|
|
144
38
|
|
|
145
|
-
/**
|
|
146
|
-
get gaugeMode(): string {
|
|
147
|
-
return (this.root as any)?.stockerGaugeMode || 'capacity'
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
get controls() {
|
|
151
|
-
var { left, top, width, round, height } = this.state
|
|
152
|
-
round = round == undefined ? 0 : safeRound(round, width, height)
|
|
153
|
-
|
|
154
|
-
return [
|
|
155
|
-
{
|
|
156
|
-
x: left + (width / 2) * (round / 100),
|
|
157
|
-
y: top,
|
|
158
|
-
handler: controlHandler
|
|
159
|
-
}
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
|
|
39
|
+
/** 게이지는 자체 id 로 매핑하지 않는다 — 부모 STOCKER 가 데이터를 push. */
|
|
163
40
|
isIdentifiable() {
|
|
164
41
|
return false
|
|
165
42
|
}
|
|
166
43
|
|
|
167
|
-
|
|
168
|
-
const { legendName } = this.state
|
|
169
|
-
|
|
170
|
-
if (legendName) {
|
|
171
|
-
return (this.root as any)?.style?.[legendName]
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
get legend(): Legend {
|
|
176
|
-
return this.getTheme() || this.getLegendFallback()
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
getLegendFallback() {
|
|
44
|
+
getLegendFallback(): Legend {
|
|
180
45
|
return (this.root as any).stockerCapacityLegendTheme || LEGEND_CAPACITY
|
|
181
46
|
}
|
|
182
47
|
|
|
183
|
-
get statusColor() {
|
|
184
|
-
const { fullup } = this.state
|
|
185
|
-
return fullup ? 'red' : getValueOnRanges(this.usage, this.legend)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
get text() {
|
|
189
|
-
// usage% 값은 capacity 모드에서만 표시. carrier/abnormal 은 세그먼트별 자체 라벨을 그리므로
|
|
190
|
-
// base 의 value 텍스트(value-property=usage)를 억제한다.
|
|
191
|
-
if (this.gaugeMode !== 'capacity') {
|
|
192
|
-
return ''
|
|
193
|
-
}
|
|
194
|
-
return this.usage + '%'
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
render(context: CanvasRenderingContext2D) {
|
|
198
|
-
const mode = this.gaugeMode
|
|
199
|
-
|
|
200
|
-
if (mode === 'carrier' || mode === 'abnormal') {
|
|
201
|
-
this.renderSegmentGauge(context, this.segments(mode))
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// capacity (기본)
|
|
206
|
-
const { width, height } = this.bounds
|
|
207
|
-
if (width >= height) {
|
|
208
|
-
this.renderLandscape(context)
|
|
209
|
-
} else {
|
|
210
|
-
this.renderPortrait(context)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
48
|
/**
|
|
215
49
|
* Shape.postrender 의 drawFill/drawStroke 를 건너뛴다.
|
|
216
|
-
*
|
|
217
|
-
* fillStyle 로 게이지 전체를 다시
|
|
50
|
+
* render 가 자체 배경(fillStyle)·외곽선(strokeStyle)을 이미 그리므로, base 가
|
|
51
|
+
* fillStyle 로 게이지 전체를 다시 덮는(각 모드 표현이 사라지는) 것을 방지한다.
|
|
218
52
|
* 값 텍스트(drawText)는 Component.postrender 와 동일하게 유지한다.
|
|
219
53
|
*/
|
|
220
54
|
postrender(context: CanvasRenderingContext2D) {
|
|
221
|
-
this.drawText(context)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* carrier/abnormal mode 의 세그먼트 목록 (공유 유틸 = 3D 와 동일 계산).
|
|
226
|
-
* 배열 순서 = portrait 아래→위 / landscape 왼→오.
|
|
227
|
-
*/
|
|
228
|
-
segments(mode: string): GaugeSegment[] {
|
|
229
|
-
if (mode === 'abnormal') {
|
|
230
|
-
const { carrierNormal = 0, carrierAbnormal = 0 } = this.state
|
|
231
|
-
return abnormalSegments(carrierNormal, carrierAbnormal)
|
|
232
|
-
}
|
|
233
|
-
const { carrierFull = 0, carrierEmpty = 0, carrierEmptyEmpty = 0 } = this.state
|
|
234
|
-
return carrierSegments(carrierFull, carrierEmpty, carrierEmptyEmpty)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** 세그먼트 게이지 렌더 (carrier/abnormal 공통). bounds 방향으로 landscape/portrait 자동. */
|
|
238
|
-
renderSegmentGauge(context: CanvasRenderingContext2D, segments: GaugeSegment[]) {
|
|
239
|
-
const { left, top, width, height } = this.bounds
|
|
240
|
-
const { lineWidth, strokeStyle, round, fillStyle = 'white' } = this.state
|
|
241
|
-
const landscape = width >= height
|
|
242
|
-
|
|
243
|
-
context.save()
|
|
244
|
-
context.translate(left, top)
|
|
245
|
-
|
|
246
|
-
// background
|
|
247
|
-
context.beginPath()
|
|
248
|
-
context.fillStyle = fillStyle
|
|
249
|
-
context.roundRect(0, 0, width, height, round)
|
|
250
|
-
context.fill()
|
|
251
|
-
context.clip()
|
|
252
|
-
|
|
253
|
-
if (landscape) {
|
|
254
|
-
// 왼→오
|
|
255
|
-
let x = 0
|
|
256
|
-
for (const seg of segments) {
|
|
257
|
-
const segWidth = width * seg.ratio
|
|
258
|
-
if (segWidth <= 0) continue
|
|
259
|
-
|
|
260
|
-
context.beginPath()
|
|
261
|
-
context.fillStyle = seg.color
|
|
262
|
-
context.rect(x, 0, segWidth, height)
|
|
263
|
-
context.fill()
|
|
264
|
-
|
|
265
|
-
this._drawSegmentLabel(context, seg, x, 0, segWidth, height)
|
|
266
|
-
x += segWidth
|
|
267
|
-
}
|
|
268
|
-
} else {
|
|
269
|
-
// 아래→위 (배열 첫 세그먼트가 바닥)
|
|
270
|
-
let y = height
|
|
271
|
-
for (const seg of segments) {
|
|
272
|
-
const segHeight = height * seg.ratio
|
|
273
|
-
if (segHeight <= 0) continue
|
|
274
|
-
|
|
275
|
-
y -= segHeight
|
|
276
|
-
|
|
277
|
-
context.beginPath()
|
|
278
|
-
context.fillStyle = seg.color
|
|
279
|
-
context.rect(0, y, width, segHeight)
|
|
280
|
-
context.fill()
|
|
281
|
-
|
|
282
|
-
this._drawSegmentLabel(context, seg, 0, y, width, segHeight)
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// outline
|
|
287
|
-
context.beginPath()
|
|
288
|
-
context.setLineDash([])
|
|
289
|
-
context.strokeStyle = strokeStyle
|
|
290
|
-
context.lineWidth = lineWidth
|
|
291
|
-
context.roundRect(0, 0, width, height, round)
|
|
292
|
-
context.stroke()
|
|
293
|
-
|
|
294
|
-
context.translate(-left, -top)
|
|
295
|
-
context.restore()
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private _drawSegmentLabel(
|
|
299
|
-
context: CanvasRenderingContext2D,
|
|
300
|
-
seg: GaugeSegment,
|
|
301
|
-
x: number,
|
|
302
|
-
y: number,
|
|
303
|
-
w: number,
|
|
304
|
-
h: number
|
|
305
|
-
) {
|
|
306
|
-
// 이름 라벨(Full/Empty/Abnormal…)은 그리지 않는다. 각 세그먼트 영역 중심에 숫자(%)만 표시.
|
|
307
|
-
// value 텍스트와 동일하게 state.fontSize 를 따른다 (기본 15). 속성 하나로 함께 조절.
|
|
308
|
-
const fontSize = this.state.fontSize || 15
|
|
309
|
-
context.font = `bold ${fontSize}px sans-serif`
|
|
310
|
-
|
|
311
|
-
// 공간에 따라 단계적 축소: 넉넉하면 '값%', 좁으면 '%' 떼고 값만, 더 좁으면 생략.
|
|
312
|
-
const value = `${Math.round(seg.ratio * 100)}`
|
|
313
|
-
const fits = (t: string) => fontSize <= h && context.measureText(t).width <= w
|
|
314
|
-
|
|
315
|
-
let text: string | null = null
|
|
316
|
-
if (fits(`${value}%`)) {
|
|
317
|
-
text = `${value}%`
|
|
318
|
-
} else if (fits(value)) {
|
|
319
|
-
text = value
|
|
320
|
-
}
|
|
321
|
-
if (text === null) {
|
|
322
|
-
return
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
context.fillStyle = '#333'
|
|
326
|
-
context.textAlign = 'center'
|
|
327
|
-
context.textBaseline = 'middle'
|
|
328
|
-
context.fillText(text, x + w / 2, y + h / 2)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
renderLandscape(context: CanvasRenderingContext2D) {
|
|
332
|
-
const { left, top, width, height } = this.bounds
|
|
333
|
-
const { highWatermark, lowWatermark, lineWidth, strokeStyle, round, fillStyle = 'white' } = this.state
|
|
334
|
-
|
|
335
|
-
context.save()
|
|
336
|
-
context.translate(left, top)
|
|
337
|
-
|
|
338
|
-
// gauge background
|
|
339
|
-
context.beginPath()
|
|
340
|
-
|
|
341
|
-
context.fillStyle = fillStyle
|
|
342
|
-
context.roundRect(0, 0, width, height, round)
|
|
343
|
-
|
|
344
|
-
context.fill()
|
|
345
|
-
|
|
346
|
-
context.clip()
|
|
347
|
-
|
|
348
|
-
// gauge foreground
|
|
349
|
-
context.beginPath()
|
|
350
|
-
|
|
351
|
-
context.fillStyle = this.statusColor!
|
|
352
|
-
|
|
353
|
-
context.rect(0, 0, (width / 100) * this.usage, height)
|
|
354
|
-
context.fill()
|
|
355
|
-
|
|
356
|
-
// high-watermark level
|
|
357
|
-
|
|
358
|
-
if (highWatermark && highWatermark > 0 && highWatermark < 100) {
|
|
359
|
-
context.beginPath()
|
|
360
|
-
|
|
361
|
-
context.strokeStyle = 'black'
|
|
362
|
-
context.lineWidth = 1
|
|
363
|
-
context.setLineDash([3])
|
|
364
|
-
|
|
365
|
-
const x = (width / 100) * highWatermark
|
|
366
|
-
context.moveTo(x, 0)
|
|
367
|
-
context.lineTo(x, height)
|
|
368
|
-
|
|
369
|
-
context.stroke()
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// low-watermark level
|
|
373
|
-
|
|
374
|
-
if (lowWatermark && lowWatermark > 0 && lowWatermark < 100) {
|
|
375
|
-
context.beginPath()
|
|
376
|
-
|
|
377
|
-
context.strokeStyle = 'black'
|
|
378
|
-
context.lineWidth = 1
|
|
379
|
-
context.setLineDash([3])
|
|
380
|
-
|
|
381
|
-
const x = (width / 100) * lowWatermark
|
|
382
|
-
context.moveTo(x, 0)
|
|
383
|
-
context.lineTo(x, height)
|
|
384
|
-
|
|
385
|
-
context.stroke()
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// gauge outline
|
|
389
|
-
context.beginPath()
|
|
390
|
-
|
|
391
|
-
context.setLineDash([])
|
|
392
|
-
context.strokeStyle = strokeStyle
|
|
393
|
-
context.lineWidth = lineWidth
|
|
394
|
-
context.roundRect(0, 0, width, height, round)
|
|
395
|
-
context.stroke()
|
|
396
|
-
|
|
397
|
-
context.beginPath()
|
|
398
|
-
|
|
399
|
-
context.translate(-left, -top)
|
|
400
|
-
context.restore()
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
renderPortrait(context: CanvasRenderingContext2D) {
|
|
404
|
-
const { left, top, width, height } = this.bounds
|
|
405
|
-
const { highWatermark, lowWatermark, lineWidth, strokeStyle, round, fillStyle = 'white' } = this.state
|
|
406
|
-
|
|
407
|
-
context.save()
|
|
408
|
-
context.translate(left, top)
|
|
409
|
-
|
|
410
|
-
// gauge background
|
|
411
|
-
context.beginPath()
|
|
412
|
-
|
|
413
|
-
context.fillStyle = fillStyle
|
|
414
|
-
context.roundRect(0, 0, width, height, round)
|
|
415
|
-
|
|
416
|
-
context.fill()
|
|
417
|
-
|
|
418
|
-
context.clip()
|
|
419
|
-
|
|
420
|
-
// gauge foreground
|
|
421
|
-
context.beginPath()
|
|
422
|
-
|
|
423
|
-
context.fillStyle = this.statusColor!
|
|
424
|
-
|
|
425
|
-
context.rect(0, height - (height / 100) * this.usage, width, (height / 100) * this.usage)
|
|
426
|
-
context.fill()
|
|
427
|
-
|
|
428
|
-
// high-watermark level
|
|
429
|
-
|
|
430
|
-
if (highWatermark && highWatermark > 0 && highWatermark < 100) {
|
|
431
|
-
context.beginPath()
|
|
432
|
-
|
|
433
|
-
context.strokeStyle = 'black'
|
|
434
|
-
context.lineWidth = 1
|
|
435
|
-
context.setLineDash([3])
|
|
436
|
-
|
|
437
|
-
const y = height - (height / 100) * highWatermark
|
|
438
|
-
context.moveTo(0, y)
|
|
439
|
-
context.lineTo(width, y)
|
|
440
|
-
|
|
441
|
-
context.stroke()
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// low-watermark level
|
|
445
|
-
|
|
446
|
-
if (lowWatermark && lowWatermark > 0 && lowWatermark < 100) {
|
|
447
|
-
context.beginPath()
|
|
448
|
-
|
|
449
|
-
context.strokeStyle = 'black'
|
|
450
|
-
context.lineWidth = 1
|
|
451
|
-
context.setLineDash([3])
|
|
452
|
-
|
|
453
|
-
const y = height - (height / 100) * lowWatermark
|
|
454
|
-
context.moveTo(0, y)
|
|
455
|
-
context.lineTo(width, y)
|
|
456
|
-
|
|
457
|
-
context.stroke()
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// gauge outline
|
|
461
|
-
context.beginPath()
|
|
462
|
-
|
|
463
|
-
context.setLineDash([])
|
|
464
|
-
context.strokeStyle = strokeStyle
|
|
465
|
-
context.lineWidth = lineWidth
|
|
466
|
-
context.roundRect(0, 0, width, height, round)
|
|
467
|
-
context.stroke()
|
|
468
|
-
|
|
469
|
-
context.beginPath()
|
|
470
|
-
|
|
471
|
-
context.translate(-left, -top)
|
|
472
|
-
context.restore()
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
get usage() {
|
|
476
|
-
const { currentUsage = 0, maxCapacity = 100 } = this.state
|
|
477
|
-
return Math.round((currentUsage / maxCapacity) * 100)
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
set usage(usage: number) {
|
|
481
|
-
// intentionally have done nothing
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
onchangeData(after: Record<string, any>, before: Record<string, any>): void {
|
|
485
|
-
const data = this.data && typeof this.data == 'object' ? this.data : {}
|
|
486
|
-
const {
|
|
487
|
-
CURRENTCAPACITY = 0,
|
|
488
|
-
MAXCAPACITY = 100,
|
|
489
|
-
HIGHWATERMARK = 100,
|
|
490
|
-
LOWWATERMARK = 0,
|
|
491
|
-
FULLUP = 'F',
|
|
492
|
-
CARRIER_FULL = 0,
|
|
493
|
-
CARRIER_EMPTY = 0,
|
|
494
|
-
CARRIER_EMPTY_EMPTY = 0,
|
|
495
|
-
CARRIER_NORMAL = 0,
|
|
496
|
-
CARRIER_ABNORMAL = 0
|
|
497
|
-
} = data
|
|
498
|
-
|
|
499
|
-
const currentUsage = Number(CURRENTCAPACITY) || 0
|
|
500
|
-
const maxCapacity = Number(MAXCAPACITY) || 100
|
|
501
|
-
const highWatermark = Number(HIGHWATERMARK) || 100
|
|
502
|
-
const lowWatermark = Number(LOWWATERMARK) || 0
|
|
503
|
-
|
|
504
|
-
this.setState({
|
|
505
|
-
currentUsage,
|
|
506
|
-
maxCapacity,
|
|
507
|
-
highWatermark,
|
|
508
|
-
lowWatermark,
|
|
509
|
-
fullup: FULLUP == 'T',
|
|
510
|
-
usage: Math.round((currentUsage / maxCapacity) * 100),
|
|
511
|
-
carrierFull: Number(CARRIER_FULL) || 0,
|
|
512
|
-
carrierEmpty: Number(CARRIER_EMPTY) || 0,
|
|
513
|
-
carrierEmptyEmpty: Number(CARRIER_EMPTY_EMPTY) || 0,
|
|
514
|
-
carrierNormal: Number(CARRIER_NORMAL) || 0,
|
|
515
|
-
carrierAbnormal: Number(CARRIER_ABNORMAL) || 0
|
|
516
|
-
})
|
|
55
|
+
;(this as any).drawText(context)
|
|
517
56
|
}
|
|
518
57
|
}
|
package/src/stocker.ts
CHANGED
|
@@ -5,7 +5,35 @@ const NATURE: ComponentNature = {
|
|
|
5
5
|
mutable: false,
|
|
6
6
|
resizable: true,
|
|
7
7
|
rotatable: true,
|
|
8
|
-
properties: [
|
|
8
|
+
properties: [
|
|
9
|
+
...MCSTransport.properties,
|
|
10
|
+
/* 이 스토커의 *내부 좌표계* 규약 — 외부 시스템이 보내는 X/Y 를 footprint 의 어느
|
|
11
|
+
* 축·어느 방향에 대응시킬지 정한다(크레인 위치 데이터 CRANEPOSITION 등이 이를 따른다).
|
|
12
|
+
* 미설정이면 종전 폴백(긴 변 = X축, 원점 = 좌상단). 모두 *로컬 프레임* 기준이라
|
|
13
|
+
* 스토커 회전(rotation)과는 무관하다. Z(높이)는 바닥 기준이 물리적으로 고정이라 설정 없음. */
|
|
14
|
+
{
|
|
15
|
+
type: 'select',
|
|
16
|
+
label: 'coord-axis-x',
|
|
17
|
+
name: 'coordAxisX',
|
|
18
|
+
property: {
|
|
19
|
+
options: [
|
|
20
|
+
{ display: 'auto (longer side)', value: '' },
|
|
21
|
+
{ display: 'width', value: 'width' },
|
|
22
|
+
{ display: 'height', value: 'height' }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'checkbox',
|
|
28
|
+
label: 'coord-invert-x',
|
|
29
|
+
name: 'coordInvertX'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'checkbox',
|
|
33
|
+
label: 'coord-invert-y',
|
|
34
|
+
name: 'coordInvertY'
|
|
35
|
+
}
|
|
36
|
+
]
|
|
9
37
|
}
|
|
10
38
|
|
|
11
39
|
// const capacity = { ko: 'Capacity', en: 'Capacity', zh_CN: '容量' }
|