@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.
Files changed (73) hide show
  1. package/dist/carrier-manager.js +9 -1
  2. package/dist/carrier-manager.js.map +1 -1
  3. package/dist/carrier.js +69 -52
  4. package/dist/carrier.js.map +1 -1
  5. package/dist/crane.js +15 -6
  6. package/dist/crane.js.map +1 -1
  7. package/dist/factories/agv-3d.js +9 -27
  8. package/dist/factories/agv-3d.js.map +1 -1
  9. package/dist/factories/conveyor-3d.js +11 -19
  10. package/dist/factories/conveyor-3d.js.map +1 -1
  11. package/dist/factories/crane-3d.js +8 -17
  12. package/dist/factories/crane-3d.js.map +1 -1
  13. package/dist/factories/crane-rail-3d.js +8 -12
  14. package/dist/factories/crane-rail-3d.js.map +1 -1
  15. package/dist/factories/machine-3d.js +100 -63
  16. package/dist/factories/machine-3d.js.map +1 -1
  17. package/dist/factories/oht-3d.js +9 -27
  18. package/dist/factories/oht-3d.js.map +1 -1
  19. package/dist/factories/shuttle-3d.js +11 -27
  20. package/dist/factories/shuttle-3d.js.map +1 -1
  21. package/dist/features/capacity-gauge-mixin.js +520 -0
  22. package/dist/features/capacity-gauge-mixin.js.map +1 -0
  23. package/dist/features/mcs-status-default.js +13 -0
  24. package/dist/features/mcs-status-default.js.map +1 -1
  25. package/dist/features/parent-object-mixin.js +5 -1
  26. package/dist/features/parent-object-mixin.js.map +1 -1
  27. package/dist/mcs-machine.js +4 -23
  28. package/dist/mcs-machine.js.map +1 -1
  29. package/dist/mcs-unit.js +2 -5
  30. package/dist/mcs-unit.js.map +1 -1
  31. package/dist/mcs-vehicle.js +3 -1
  32. package/dist/mcs-vehicle.js.map +1 -1
  33. package/dist/scene/root-container-override.js +5 -2
  34. package/dist/scene/root-container-override.js.map +1 -1
  35. package/dist/stocker-capacity-bar.js +11 -372
  36. package/dist/stocker-capacity-bar.js.map +1 -1
  37. package/dist/stocker.js +29 -1
  38. package/dist/stocker.js.map +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/dist/utils/crane-position.js +23 -14
  41. package/dist/utils/crane-position.js.map +1 -1
  42. package/dist/utils/stocker-gauge.js +48 -10
  43. package/dist/utils/stocker-gauge.js.map +1 -1
  44. package/dist/zone-capacity-bar.js +13 -213
  45. package/dist/zone-capacity-bar.js.map +1 -1
  46. package/package.json +4 -4
  47. package/src/carrier-manager.ts +9 -1
  48. package/src/carrier.ts +73 -52
  49. package/src/crane.ts +14 -5
  50. package/src/factories/agv-3d.ts +10 -26
  51. package/src/factories/conveyor-3d.ts +12 -19
  52. package/src/factories/crane-3d.ts +9 -17
  53. package/src/factories/crane-rail-3d.ts +9 -13
  54. package/src/factories/machine-3d.ts +125 -66
  55. package/src/factories/oht-3d.ts +10 -26
  56. package/src/factories/shuttle-3d.ts +12 -26
  57. package/src/features/capacity-gauge-mixin.ts +642 -0
  58. package/src/features/mcs-status-default.ts +13 -0
  59. package/src/features/parent-object-mixin.ts +5 -1
  60. package/src/mcs-machine.ts +4 -23
  61. package/src/mcs-unit.ts +2 -5
  62. package/src/mcs-vehicle.ts +3 -1
  63. package/src/scene/root-container-override.ts +5 -2
  64. package/src/stocker-capacity-bar.ts +15 -476
  65. package/src/stocker.ts +29 -1
  66. package/src/utils/crane-position.ts +46 -14
  67. package/src/utils/stocker-gauge.ts +63 -10
  68. package/src/zone-capacity-bar.ts +17 -282
  69. package/translations/en.json +4 -1
  70. package/translations/ja.json +4 -1
  71. package/translations/ko.json +4 -1
  72. package/translations/ms.json +4 -1
  73. package/translations/zh.json +4 -1
@@ -84,32 +84,13 @@ export default class MCSMachine extends MCSStatusMixin(ContainerAbstract) {
84
84
  }
85
85
 
86
86
  /**
87
- * In 2D, ignore `state.alpha`; preserve everything else.
88
- *
89
- * things-scene's base prerender (component-drawing.ts:31, 44) does:
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
- const dim = this.app.isEditMode && this.hidden ? 0.5 : 1
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
- * In 2D, ignore `state.alpha`; preserve inherited alpha + editMode-hidden dim.
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
- const dim = this.app.isEditMode && this.hidden ? 0.5 : 1
72
- ctx.globalAlpha = inherited * dim
69
+ ctx.globalAlpha = 1 - this._delta.fade
73
70
  }
74
71
 
75
72
  contains(x, y) {
@@ -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
- console.warn(`Node not found. NodeId: ${NodeId}, NodeMachine: ${NodeMachine}`)
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 { carrierSegments, abnormalSegments, GaugeSegment } from './utils/stocker-gauge'
5
+ import { CapacityGaugeMixin, CapacityGaugeMixinProperties } from './features/capacity-gauge-mixin'
17
6
 
18
7
  /**
19
8
  * 스토커 게이지 (통합).
20
9
  *
21
- * 하나의 컴포넌트가 mode 에 따라 3가지 게이지를 표현한다:
22
- * - 'capacity' : 용량 사용률(usage%) 채움 게이지 + high/low watermark (기본)
23
- * - 'carrier' : 캐리어 상태비율 세그먼트 (Full / Empty / EmptyEmpty)
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 (기본 'capacity').
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 ParentObjectMixin(MCSStatusMixin(RectPath(Shape))) {
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
- /** 게이지 모드: RootContainer 런타임 transient 속성. 3D(machine-3d) 와 동일 소스. */
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
- getTheme() {
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
- * 각 모드 render 가 자체 배경(fillStyle)·외곽선(strokeStyle)을 이미 그리므로, base 가
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: [...MCSTransport.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: '容量' }