@fmsim/machine 2.0.1 → 3.0.0-beta.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/agv.js +2 -3
- package/dist/agv.js.map +1 -1
- package/dist/carrier.js +53 -43
- package/dist/carrier.js.map +1 -1
- package/dist/crane.js +37 -2
- package/dist/crane.js.map +1 -1
- package/dist/editors/index.js +2 -0
- package/dist/editors/index.js.map +1 -1
- package/dist/editors/load-layout-host.js +96 -0
- package/dist/editors/load-layout-host.js.map +1 -0
- package/dist/editors/ox-load-layout-panel.js +124 -0
- package/dist/editors/ox-load-layout-panel.js.map +1 -0
- package/dist/factories/crane-3d.js +77 -18
- package/dist/factories/crane-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +29 -52
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/features/capacity-gauge-mixin.js +2 -3
- package/dist/features/capacity-gauge-mixin.js.map +1 -1
- package/dist/features/load-layout-holder.js +77 -0
- package/dist/features/load-layout-holder.js.map +1 -0
- package/dist/features/mcs-status-default.js +30 -7
- package/dist/features/mcs-status-default.js.map +1 -1
- package/dist/features/mcs-status-mixin.js +3 -3
- package/dist/features/mcs-status-mixin.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/mcs-carrier-holder.js +287 -82
- package/dist/mcs-carrier-holder.js.map +1 -1
- package/dist/oht.js +4 -0
- package/dist/oht.js.map +1 -1
- package/dist/port.js +6 -1
- package/dist/port.js.map +1 -1
- package/dist/scene/root-container-override.js +5 -11
- package/dist/scene/root-container-override.js.map +1 -1
- package/dist/shelf.js +4 -0
- package/dist/shelf.js.map +1 -1
- package/dist/shuttle.js +4 -0
- package/dist/shuttle.js.map +1 -1
- package/dist/stocker-carrier-bar.js +4 -4
- package/dist/stocker-carrier-bar.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/carrier-type-code.js +10 -0
- package/dist/utils/carrier-type-code.js.map +1 -0
- package/dist/utils/crane-forks.js +5 -0
- package/dist/utils/crane-forks.js.map +1 -0
- package/dist/utils/layout-issue-text.js +16 -0
- package/dist/utils/layout-issue-text.js.map +1 -0
- package/dist/utils/layout-issue.js +16 -0
- package/dist/utils/layout-issue.js.map +1 -0
- package/dist/utils/load-address.js +217 -0
- package/dist/utils/load-address.js.map +1 -0
- package/dist/utils/stocker-gauge.js +3 -2
- package/dist/utils/stocker-gauge.js.map +1 -1
- package/jest.config.js +9 -1
- package/package.json +9 -6
- package/src/agv.ts +2 -3
- package/src/carrier.ts +50 -41
- package/src/crane.ts +39 -2
- package/src/editors/index.ts +2 -0
- package/src/editors/load-layout-host.ts +121 -0
- package/src/editors/ox-load-layout-panel.ts +129 -0
- package/src/factories/crane-3d.ts +74 -20
- package/src/factories/machine-3d.ts +26 -49
- package/src/features/capacity-gauge-mixin.ts +2 -3
- package/src/features/load-layout-holder.ts +83 -0
- package/src/features/mcs-status-default.ts +29 -10
- package/src/features/mcs-status-mixin.ts +3 -7
- package/src/index.ts +7 -0
- package/src/mcs-carrier-holder.ts +319 -78
- package/src/oht.ts +5 -0
- package/src/port.ts +7 -1
- package/src/scene/root-container-override.ts +4 -14
- package/src/shelf.ts +5 -0
- package/src/shuttle.ts +5 -0
- package/src/stocker-carrier-bar.ts +4 -4
- package/src/utils/carrier-type-code.ts +9 -0
- package/src/utils/crane-forks.ts +4 -0
- package/src/utils/layout-issue-text.ts +18 -0
- package/src/utils/layout-issue.ts +41 -0
- package/src/utils/load-address.ts +270 -0
- package/src/utils/stocker-gauge.ts +3 -3
- package/translations/en.json +42 -1
- package/translations/ja.json +42 -1
- package/translations/ko.json +59 -18
- package/translations/ms.json +42 -1
- package/translations/zh.json +42 -1
- package/dist/carrier-manager.js +0 -191
- package/dist/carrier-manager.js.map +0 -1
- package/dist/features/animation-default.js +0 -6
- package/dist/features/animation-default.js.map +0 -1
- package/dist/utils/get-value-on-ranges.js +0 -21
- package/dist/utils/get-value-on-ranges.js.map +0 -1
- package/src/carrier-manager.ts +0 -231
- package/src/features/animation-default.ts +0 -13
- package/src/utils/get-value-on-ranges.ts +0 -23
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 실시간 데이터의 적재 주소 해석과 점유 배정 (설계 02번 3.6절, 3.6.1절)
|
|
3
|
+
* Carrier 는 자리 번호에 고정된다. 주소를 못 읽으면 빈 자리에 채우지 않고 예외로 둔다.
|
|
4
|
+
* 레이아웃(층·자리)과 자리 계산은 figure-model 의 것이다(ADR-0051 4.1절 결정 8). 여기 남는 것은 MCS 의 몫이다:
|
|
5
|
+
* 실시간 레코드에서 주소를 읽는 규칙(FIELDS·PATTERN)과 점유 배정·예외.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolveSlot, type SlotAddress, type SlotLayout } from '@hatiolab/figure-model'
|
|
9
|
+
import { AddressPatternError, LayoutIssue, issue } from './layout-issue'
|
|
10
|
+
import { parseIfJsonString } from './data-value'
|
|
11
|
+
|
|
12
|
+
export type LoadAddress = SlotAddress
|
|
13
|
+
|
|
14
|
+
/** 실시간 데이터에서 주소를 읽는 규칙 (3.6절) */
|
|
15
|
+
export type LoadAddressing =
|
|
16
|
+
| { by: 'FIELDS'; tierField?: string; slotField: string }
|
|
17
|
+
| { by: 'PATTERN'; field: string; pattern: string }
|
|
18
|
+
|
|
19
|
+
/** 홀더의 적재 레이아웃: figure-model 의 층·자리에 주소 읽기 규칙을 더한 배치 값 */
|
|
20
|
+
export interface LoadLayout extends SlotLayout {
|
|
21
|
+
addressing?: LoadAddressing
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type AddressRead =
|
|
25
|
+
| { kind: 'OK'; address: LoadAddress }
|
|
26
|
+
| { kind: 'UNKNOWN'; raw: unknown }
|
|
27
|
+
| { kind: 'ERROR'; raw: unknown; reason: AddressErrorReason }
|
|
28
|
+
|
|
29
|
+
export type AddressErrorReason = 'NOT_POSITIVE_INTEGER' | 'OUT_OF_RANGE' | 'STACK_LIMIT_EXCEEDED' | 'DUPLICATE_CONFLICT'
|
|
30
|
+
|
|
31
|
+
/** 양의 정수 또는 양의 정수 문자열만 받는다. 그 밖의 값은 null */
|
|
32
|
+
function positiveInt(v: unknown): number | null {
|
|
33
|
+
if (typeof v === 'number') return Number.isSafeInteger(v) && v > 0 ? v : null
|
|
34
|
+
if (typeof v === 'string' && /^\d+$/.test(v.trim())) {
|
|
35
|
+
const n = Number(v.trim())
|
|
36
|
+
return Number.isSafeInteger(n) && n > 0 ? n : null
|
|
37
|
+
}
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const missing = (v: unknown) => v === undefined || v === null
|
|
42
|
+
|
|
43
|
+
// ── PATTERN 문법 ────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
const TOKEN = /\{([A-Za-z_][A-Za-z0-9_]*)(?::(0\d+))?\}/g
|
|
46
|
+
|
|
47
|
+
export interface CompiledPattern {
|
|
48
|
+
regex: RegExp
|
|
49
|
+
tierGroup?: number
|
|
50
|
+
slotGroup?: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 위치 문자열 패턴을 정규식으로 바꾼다. 문법이 틀리면 AddressPatternError(모든 문제 목록)를 던진다 */
|
|
54
|
+
export function compileAddressPattern(pattern: string): CompiledPattern {
|
|
55
|
+
if (typeof pattern !== 'string' || !pattern)
|
|
56
|
+
throw new AddressPatternError([issue('ADDRESS_PATTERN_EMPTY', 'pattern')])
|
|
57
|
+
const errors: LayoutIssue[] = []
|
|
58
|
+
let source = '^'
|
|
59
|
+
let last = 0
|
|
60
|
+
let group = 0
|
|
61
|
+
let tierGroup: number | undefined
|
|
62
|
+
let slotGroup: number | undefined
|
|
63
|
+
const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
64
|
+
|
|
65
|
+
for (const m of pattern.matchAll(TOKEN)) {
|
|
66
|
+
const literal = pattern.slice(last, m.index)
|
|
67
|
+
if (/[{}]/.test(literal)) errors.push(issue('ADDRESS_PATTERN_SYNTAX', 'pattern', { text: literal }))
|
|
68
|
+
source += escape(literal)
|
|
69
|
+
const [, name, width] = m
|
|
70
|
+
group++
|
|
71
|
+
if (name === 'tier' || name === 'slot') {
|
|
72
|
+
if ((name === 'tier' ? tierGroup : slotGroup) !== undefined)
|
|
73
|
+
errors.push(issue('ADDRESS_PATTERN_TOKEN_REPEATED', 'pattern', { token: name }))
|
|
74
|
+
if (name === 'tier') tierGroup = group
|
|
75
|
+
else slotGroup = group
|
|
76
|
+
source += width ? `(\\d{${width.length}})` : '(\\d+)'
|
|
77
|
+
} else {
|
|
78
|
+
if (width) errors.push(issue('ADDRESS_PATTERN_WIDTH_NOT_ALLOWED', 'pattern', { token: name }))
|
|
79
|
+
source += '(.+?)'
|
|
80
|
+
}
|
|
81
|
+
last = m.index! + m[0].length
|
|
82
|
+
}
|
|
83
|
+
const tail = pattern.slice(last)
|
|
84
|
+
if (/[{}]/.test(tail)) errors.push(issue('ADDRESS_PATTERN_SYNTAX', 'pattern', { text: tail }))
|
|
85
|
+
source += escape(tail) + '$'
|
|
86
|
+
if (slotGroup === undefined) errors.push(issue('ADDRESS_PATTERN_SLOT_REQUIRED', 'pattern'))
|
|
87
|
+
if (errors.length) throw new AddressPatternError(errors)
|
|
88
|
+
return { regex: new RegExp(source), tierGroup, slotGroup }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 레이아웃의 addressing 설정 오류 목록. 경로는 'addressing.' 으로 시작한다. 빈 배열이면 올바르다 */
|
|
92
|
+
export function validateAddressing(addressing: LoadAddressing | undefined): LayoutIssue[] {
|
|
93
|
+
if (!addressing) return []
|
|
94
|
+
if (addressing.by === 'FIELDS') return addressing.slotField ? [] : [issue('FIELD_REQUIRED', 'addressing.slotField')]
|
|
95
|
+
if (addressing.by === 'PATTERN') {
|
|
96
|
+
const errors = addressing.field ? [] : [issue('FIELD_REQUIRED', 'addressing.field')]
|
|
97
|
+
try {
|
|
98
|
+
compileAddressPattern(addressing.pattern)
|
|
99
|
+
} catch (e) {
|
|
100
|
+
if (!(e instanceof AddressPatternError)) throw e
|
|
101
|
+
errors.push(...e.issues.map(i => ({ ...i, path: `addressing.${i.path}` })))
|
|
102
|
+
}
|
|
103
|
+
return errors
|
|
104
|
+
}
|
|
105
|
+
return [issue('ADDRESSING_BY_INVALID', 'addressing.by')]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── 주소 읽기 ───────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 레코드에서 주소를 읽는다. 범위 검사는 하지 않는다 (resolveOccupancy 가 한다).
|
|
112
|
+
* 필드 누락·패턴 불일치는 UNKNOWN, 0·음수·소수·빈 문자열은 ERROR.
|
|
113
|
+
* {tier} 가 없는 패턴과 tierField 를 생략한 FIELDS 는 층 1로 읽는다.
|
|
114
|
+
*/
|
|
115
|
+
export function readAddress(record: Record<string, unknown>, addressing: LoadAddressing | undefined): AddressRead {
|
|
116
|
+
if (!addressing) return { kind: 'UNKNOWN', raw: undefined }
|
|
117
|
+
|
|
118
|
+
if (addressing.by === 'FIELDS') {
|
|
119
|
+
const rawSlot = record?.[addressing.slotField]
|
|
120
|
+
const rawTier = addressing.tierField ? record?.[addressing.tierField] : 1
|
|
121
|
+
const raw = addressing.tierField ? { tier: rawTier, slot: rawSlot } : { slot: rawSlot }
|
|
122
|
+
if (missing(rawSlot) || missing(rawTier)) return { kind: 'UNKNOWN', raw }
|
|
123
|
+
const tier = positiveInt(rawTier)
|
|
124
|
+
const slot = positiveInt(rawSlot)
|
|
125
|
+
if (tier === null || slot === null) return { kind: 'ERROR', raw, reason: 'NOT_POSITIVE_INTEGER' }
|
|
126
|
+
return { kind: 'OK', address: { tier, slot } }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const raw = record?.[addressing.field]
|
|
130
|
+
if (typeof raw !== 'string') return { kind: 'UNKNOWN', raw }
|
|
131
|
+
const { regex, tierGroup, slotGroup } = compileAddressPattern(addressing.pattern)
|
|
132
|
+
const m = regex.exec(raw)
|
|
133
|
+
if (!m) return { kind: 'UNKNOWN', raw }
|
|
134
|
+
const tier = tierGroup ? positiveInt(m[tierGroup]) : 1
|
|
135
|
+
const slot = positiveInt(m[slotGroup!])
|
|
136
|
+
if (tier === null || slot === null) return { kind: 'ERROR', raw, reason: 'NOT_POSITIVE_INTEGER' }
|
|
137
|
+
return { kind: 'OK', address: { tier, slot } }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── 점유 배정 ───────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
export interface IncomingCarrier {
|
|
143
|
+
name: string
|
|
144
|
+
type?: string
|
|
145
|
+
/** 주소를 읽을 원본 레코드 */
|
|
146
|
+
record: Record<string, unknown>
|
|
147
|
+
/** 메시지가 주는 적층 순서 (아래가 작다). 없으면 생략 */
|
|
148
|
+
stackOrder?: number
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface PlacedCarrier {
|
|
152
|
+
name: string
|
|
153
|
+
type?: string
|
|
154
|
+
address: LoadAddress
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface ExceptionCarrier {
|
|
158
|
+
name: string
|
|
159
|
+
type?: string
|
|
160
|
+
raw: unknown
|
|
161
|
+
reason?: AddressErrorReason
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface OccupancyResult {
|
|
165
|
+
/** 'tier:slot' → 아래에서 위로 정상 점유 */
|
|
166
|
+
slots: Map<string, PlacedCarrier[]>
|
|
167
|
+
/** 주소를 읽지 못해 예외 표시 영역에 두는 Carrier */
|
|
168
|
+
addressUnknown: ExceptionCarrier[]
|
|
169
|
+
/** 범위 밖·적층 한도 초과·중복 충돌로 예외 표시 영역에 두는 Carrier */
|
|
170
|
+
addressError: ExceptionCarrier[]
|
|
171
|
+
/** 데이터 오류 기록 (같은 이름이 두 번 온 경우 등) */
|
|
172
|
+
dataErrors: LayoutIssue[]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const slotKey = (a: LoadAddress) => `${a.tier}:${a.slot}`
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* v2 홀더 데이터 → 아래에서 위로 놓을 Carrier 목록 (3.6.1절, CR-11).
|
|
179
|
+
* MULTICARRIERS[] 를 배열 순서대로 아래부터, 대표 CARRIERNAME 을 맨 위에 둔다.
|
|
180
|
+
* MULTICARRIERS 는 JSON 문자열로 올 수 있다. CARRIERNAME 이 없는 항목은 건너뛴다.
|
|
181
|
+
*/
|
|
182
|
+
export function incomingFromV2(data: Record<string, any> | null | undefined): IncomingCarrier[] {
|
|
183
|
+
if (!data) return []
|
|
184
|
+
const out: IncomingCarrier[] = []
|
|
185
|
+
const list = parseIfJsonString(data.MULTICARRIERS)
|
|
186
|
+
if (Array.isArray(list))
|
|
187
|
+
for (const item of list)
|
|
188
|
+
if (item && typeof item === 'object' && item.CARRIERNAME)
|
|
189
|
+
out.push({ name: String(item.CARRIERNAME), type: item.CARRIERTYPE, record: item, stackOrder: out.length })
|
|
190
|
+
if (data.CARRIERNAME)
|
|
191
|
+
out.push({ name: String(data.CARRIERNAME), type: data.CARRIERTYPE, record: data, stackOrder: out.length })
|
|
192
|
+
return out
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 홀더 점유 스냅샷 하나를 한 번에 배정한다.
|
|
197
|
+
* 순서: 주소 해석 → 정상 자리 배정 → 자리별 적층 한도 검사 → 예외 목록 작성.
|
|
198
|
+
* previous 는 직전 결과이며, 메시지에 적층 순서가 없을 때 기존 순서를 지키는 데 쓴다.
|
|
199
|
+
* 각 Carrier 는 slots·addressUnknown·addressError 셋 중 한 곳에만 들어간다.
|
|
200
|
+
*/
|
|
201
|
+
export function resolveOccupancy(layout: LoadLayout, carriers: IncomingCarrier[], previous?: OccupancyResult): OccupancyResult {
|
|
202
|
+
const result: OccupancyResult = { slots: new Map(), addressUnknown: [], addressError: [], dataErrors: [] }
|
|
203
|
+
|
|
204
|
+
// 같은 이름: 한 번만 그린다. 주소가 다르면 주소 오류
|
|
205
|
+
const byName = new Map<string, { carrier: IncomingCarrier; read: AddressRead; arrival: number }>()
|
|
206
|
+
const conflicted = new Set<string>()
|
|
207
|
+
carriers.forEach((carrier, arrival) => {
|
|
208
|
+
const read = readAddress(carrier.record, layout.addressing)
|
|
209
|
+
const seen = byName.get(carrier.name)
|
|
210
|
+
if (!seen) {
|
|
211
|
+
byName.set(carrier.name, { carrier, read, arrival })
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
result.dataErrors.push(issue('DUPLICATE_CARRIER', '', { name: carrier.name }))
|
|
215
|
+
const same =
|
|
216
|
+
seen.read.kind === read.kind &&
|
|
217
|
+
(read.kind !== 'OK' || slotKey(read.address) === slotKey((seen.read as { address: LoadAddress }).address))
|
|
218
|
+
if (!same) conflicted.add(carrier.name)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const previousOrder = new Map<string, number>()
|
|
222
|
+
previous?.slots.forEach(list => list.forEach((c, i) => previousOrder.set(c.name, i)))
|
|
223
|
+
|
|
224
|
+
const pending = new Map<string, { carrier: IncomingCarrier; arrival: number }[]>()
|
|
225
|
+
for (const [name, { carrier, read, arrival }] of byName) {
|
|
226
|
+
const exception = { name, type: carrier.type, raw: 'raw' in read ? read.raw : undefined }
|
|
227
|
+
if (conflicted.has(name)) {
|
|
228
|
+
result.addressError.push({ ...exception, raw: carrier.record, reason: 'DUPLICATE_CONFLICT' })
|
|
229
|
+
} else if (read.kind === 'UNKNOWN') {
|
|
230
|
+
result.addressUnknown.push(exception)
|
|
231
|
+
} else if (read.kind === 'ERROR') {
|
|
232
|
+
result.addressError.push({ ...exception, reason: read.reason })
|
|
233
|
+
} else if (!resolveSlot(layout, read.address)) {
|
|
234
|
+
result.addressError.push({ ...exception, raw: read.address, reason: 'OUT_OF_RANGE' })
|
|
235
|
+
} else {
|
|
236
|
+
const key = slotKey(read.address)
|
|
237
|
+
if (!pending.has(key)) pending.set(key, [])
|
|
238
|
+
pending.get(key)!.push({ carrier, arrival })
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 적층 순서: 메시지 순서 우선. 없으면 기존 순서를 지키고 새 Carrier 는 도착 순서로 위에 얹는다
|
|
243
|
+
for (const [key, list] of pending) {
|
|
244
|
+
const [tier, slot] = key.split(':').map(Number)
|
|
245
|
+
const limit = resolveSlot(layout, { tier, slot })!.stackLimit
|
|
246
|
+
const rank = (e: { carrier: IncomingCarrier; arrival: number }) => {
|
|
247
|
+
if (e.carrier.stackOrder !== undefined) return [0, e.carrier.stackOrder, e.arrival]
|
|
248
|
+
const prev = previousOrder.get(e.carrier.name)
|
|
249
|
+
return prev !== undefined ? [1, prev, e.arrival] : [2, e.arrival, 0]
|
|
250
|
+
}
|
|
251
|
+
list.sort((a, b) => {
|
|
252
|
+
const ra = rank(a)
|
|
253
|
+
const rb = rank(b)
|
|
254
|
+
return ra[0] - rb[0] || ra[1] - rb[1] || ra[2] - rb[2]
|
|
255
|
+
})
|
|
256
|
+
const placed = list
|
|
257
|
+
.slice(0, limit)
|
|
258
|
+
.map(({ carrier }) => ({ name: carrier.name, type: carrier.type, address: { tier, slot } }))
|
|
259
|
+
result.slots.set(key, placed)
|
|
260
|
+
for (const { carrier } of list.slice(limit))
|
|
261
|
+
result.addressError.push({
|
|
262
|
+
name: carrier.name,
|
|
263
|
+
type: carrier.type,
|
|
264
|
+
raw: { tier, slot },
|
|
265
|
+
reason: 'STACK_LIMIT_EXCEEDED'
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return result
|
|
270
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - abnormal : CARRIER_NORMAL / CARRIER_ABNORMAL
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { Legend } from '../features/mcs-status-default'
|
|
14
|
+
import { Legend, legendColor, legendEntryOf } from '../features/mcs-status-default'
|
|
15
15
|
|
|
16
16
|
export type StockerGaugeMode = 'capacity' | 'carrier' | 'abnormal'
|
|
17
17
|
|
|
@@ -43,11 +43,11 @@ export interface CarrierColors {
|
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* carrier legend(테마)에서 full/empty/emptyEmpty 색상 추출.
|
|
46
|
-
* carrier 컴포넌트의 색 취득 로직(statusColor
|
|
46
|
+
* carrier 컴포넌트의 색 취득 로직(statusColor, legendEntryOf)과 동일하게,
|
|
47
47
|
* status 키(FULL/EMPTY/EMPTYEMPTY)로 직접 조회한다. 키/default 모두 없으면 CARRIER_COLORS 로 폴백.
|
|
48
48
|
*/
|
|
49
49
|
export function carrierColorsFromLegend(legend?: Legend): CarrierColors {
|
|
50
|
-
const pick = (key: string, fallback: string) => (legend && (
|
|
50
|
+
const pick = (key: string, fallback: string) => (legend && (legendColor(legendEntryOf(key, legend)) || legendColor(legend.default))) || fallback
|
|
51
51
|
return {
|
|
52
52
|
full: pick('FULL', CARRIER_COLORS.full),
|
|
53
53
|
empty: pick('EMPTY', CARRIER_COLORS.empty),
|
package/translations/en.json
CHANGED
|
@@ -74,5 +74,46 @@
|
|
|
74
74
|
"label.bright": "bright",
|
|
75
75
|
"label.coord-axis-x": "internal x axis",
|
|
76
76
|
"label.coord-invert-x": "invert x origin",
|
|
77
|
-
"label.coord-invert-y": "invert y origin"
|
|
77
|
+
"label.coord-invert-y": "invert y origin",
|
|
78
|
+
"label.load-layout.addressing": "Address in live data",
|
|
79
|
+
"label.load-layout.addressing-by": "Read by",
|
|
80
|
+
"label.load-layout.addressing-fields": "Fields",
|
|
81
|
+
"label.load-layout.addressing-none": "Not read (all shown as address unknown)",
|
|
82
|
+
"label.load-layout.addressing-pattern": "Location pattern",
|
|
83
|
+
"label.load-layout.field": "Field",
|
|
84
|
+
"label.load-layout.invalid": "The saved setting is invalid. Shown as one slot until fixed.",
|
|
85
|
+
"label.load-layout.issue.ADDRESSING_BY_INVALID": "Must be fields or a location pattern",
|
|
86
|
+
"label.load-layout.issue.ADDRESS_PATTERN_EMPTY": "The pattern is empty",
|
|
87
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SLOT_REQUIRED": "The pattern needs a slot token",
|
|
88
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SYNTAX": "Broken token near \"{text}\"",
|
|
89
|
+
"label.load-layout.issue.ADDRESS_PATTERN_TOKEN_REPEATED": "The {token} token may appear only once",
|
|
90
|
+
"label.load-layout.issue.ADDRESS_PATTERN_WIDTH_NOT_ALLOWED": "Digits are allowed only on the tier and slot tokens, not on {token}",
|
|
91
|
+
"label.load-layout.issue.DUPLICATE_CARRIER": "The same carrier came twice: {name}",
|
|
92
|
+
"label.load-layout.issue.FIELD_REQUIRED": "A field name is required",
|
|
93
|
+
"label.load-layout.param-cols": "Columns",
|
|
94
|
+
"label.load-layout.param-rows": "Rows",
|
|
95
|
+
"label.load-layout.param-stackLimit": "Stack limit",
|
|
96
|
+
"label.load-layout.param-upperElevation": "Upper tier elevation (mm)",
|
|
97
|
+
"label.load-layout.pattern-text": "Pattern",
|
|
98
|
+
"label.load-layout.preset-agv2x2": "AGV 2×2",
|
|
99
|
+
"label.load-layout.preset-agvParallel": "AGV parallel (left/right)",
|
|
100
|
+
"label.load-layout.preset-agvTandem": "AGV tandem (front/rear)",
|
|
101
|
+
"label.load-layout.preset-bufferPort2x2": "Buffer port 2×2",
|
|
102
|
+
"label.load-layout.preset-craneTwinFork": "Crane twin fork",
|
|
103
|
+
"label.load-layout.preset-dualTierConveyor": "Dual-tier conveyor",
|
|
104
|
+
"label.load-layout.preset-floorBlock": "Floor block",
|
|
105
|
+
"label.load-layout.preset-kind": "Kind",
|
|
106
|
+
"label.load-layout.preset-multiBufferPort": "Multi-buffer port (4)",
|
|
107
|
+
"label.load-layout.preset-note": "Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.",
|
|
108
|
+
"label.load-layout.preset-single": "Single slot",
|
|
109
|
+
"label.load-layout.slot-field": "Slot field",
|
|
110
|
+
"label.load-layout.source": "Source",
|
|
111
|
+
"label.load-layout.source-builtin": "Built-in default",
|
|
112
|
+
"label.load-layout.source-node": "Set on this holder",
|
|
113
|
+
"label.load-layout.source-none": "Not defined (one slot, stacked)",
|
|
114
|
+
"label.load-layout.tier-field": "Tier field",
|
|
115
|
+
"label.load-layout.tier-field-empty": "Empty = tier 1",
|
|
116
|
+
"label.load-layout.title": "Carrier load slots",
|
|
117
|
+
"label.load-layout.unit-missing": "Drawing unit is not set (mmPerUnit). Physical placement and fit checks are unavailable.",
|
|
118
|
+
"label.load-layout.unit-ok": "Drawing unit 1 = {mm} mm"
|
|
78
119
|
}
|
package/translations/ja.json
CHANGED
|
@@ -76,5 +76,46 @@
|
|
|
76
76
|
"label.bright": "明",
|
|
77
77
|
"label.coord-axis-x": "内部X軸",
|
|
78
78
|
"label.coord-invert-x": "X原点反転",
|
|
79
|
-
"label.coord-invert-y": "Y原点反転"
|
|
79
|
+
"label.coord-invert-y": "Y原点反転",
|
|
80
|
+
"label.load-layout.addressing": "Address in live data",
|
|
81
|
+
"label.load-layout.addressing-by": "Read by",
|
|
82
|
+
"label.load-layout.addressing-fields": "Fields",
|
|
83
|
+
"label.load-layout.addressing-none": "Not read (all shown as address unknown)",
|
|
84
|
+
"label.load-layout.addressing-pattern": "Location pattern",
|
|
85
|
+
"label.load-layout.field": "Field",
|
|
86
|
+
"label.load-layout.invalid": "The saved setting is invalid. Shown as one slot until fixed.",
|
|
87
|
+
"label.load-layout.issue.ADDRESSING_BY_INVALID": "Must be fields or a location pattern",
|
|
88
|
+
"label.load-layout.issue.ADDRESS_PATTERN_EMPTY": "The pattern is empty",
|
|
89
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SLOT_REQUIRED": "The pattern needs a slot token",
|
|
90
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SYNTAX": "Broken token near \"{text}\"",
|
|
91
|
+
"label.load-layout.issue.ADDRESS_PATTERN_TOKEN_REPEATED": "The {token} token may appear only once",
|
|
92
|
+
"label.load-layout.issue.ADDRESS_PATTERN_WIDTH_NOT_ALLOWED": "Digits are allowed only on the tier and slot tokens, not on {token}",
|
|
93
|
+
"label.load-layout.issue.DUPLICATE_CARRIER": "The same carrier came twice: {name}",
|
|
94
|
+
"label.load-layout.issue.FIELD_REQUIRED": "A field name is required",
|
|
95
|
+
"label.load-layout.param-cols": "Columns",
|
|
96
|
+
"label.load-layout.param-rows": "Rows",
|
|
97
|
+
"label.load-layout.param-stackLimit": "Stack limit",
|
|
98
|
+
"label.load-layout.param-upperElevation": "Upper tier elevation (mm)",
|
|
99
|
+
"label.load-layout.pattern-text": "Pattern",
|
|
100
|
+
"label.load-layout.preset-agv2x2": "AGV 2×2",
|
|
101
|
+
"label.load-layout.preset-agvParallel": "AGV parallel (left/right)",
|
|
102
|
+
"label.load-layout.preset-agvTandem": "AGV tandem (front/rear)",
|
|
103
|
+
"label.load-layout.preset-bufferPort2x2": "Buffer port 2×2",
|
|
104
|
+
"label.load-layout.preset-craneTwinFork": "Crane twin fork",
|
|
105
|
+
"label.load-layout.preset-dualTierConveyor": "Dual-tier conveyor",
|
|
106
|
+
"label.load-layout.preset-floorBlock": "Floor block",
|
|
107
|
+
"label.load-layout.preset-kind": "Kind",
|
|
108
|
+
"label.load-layout.preset-multiBufferPort": "Multi-buffer port (4)",
|
|
109
|
+
"label.load-layout.preset-note": "Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.",
|
|
110
|
+
"label.load-layout.preset-single": "Single slot",
|
|
111
|
+
"label.load-layout.slot-field": "Slot field",
|
|
112
|
+
"label.load-layout.source": "Source",
|
|
113
|
+
"label.load-layout.source-builtin": "Built-in default",
|
|
114
|
+
"label.load-layout.source-node": "Set on this holder",
|
|
115
|
+
"label.load-layout.source-none": "Not defined (one slot, stacked)",
|
|
116
|
+
"label.load-layout.tier-field": "Tier field",
|
|
117
|
+
"label.load-layout.tier-field-empty": "Empty = tier 1",
|
|
118
|
+
"label.load-layout.title": "Carrier load slots",
|
|
119
|
+
"label.load-layout.unit-missing": "Drawing unit is not set (mmPerUnit). Physical placement and fit checks are unavailable.",
|
|
120
|
+
"label.load-layout.unit-ok": "Drawing unit 1 = {mm} mm"
|
|
80
121
|
}
|
package/translations/ko.json
CHANGED
|
@@ -6,25 +6,25 @@
|
|
|
6
6
|
"label.carrier-empty-empty": "이중 공 캐리어",
|
|
7
7
|
"label.carrier-normal": "정상 캐리어",
|
|
8
8
|
"label.carrier-abnormal": "비정상 캐리어",
|
|
9
|
-
"label.high-watermark": "
|
|
10
|
-
"label.low-watermark": "
|
|
11
|
-
"label.legend-name": "
|
|
9
|
+
"label.high-watermark": "상한 기준값",
|
|
10
|
+
"label.low-watermark": "하한 기준값",
|
|
11
|
+
"label.legend-name": "범례",
|
|
12
12
|
"label.max-capacity": "최대 용량",
|
|
13
13
|
"label.carrier-animation-name": "캐리어 애니메이션",
|
|
14
|
-
"label.carrier-legend-name": "캐리어
|
|
15
|
-
"label.sc-state": "
|
|
16
|
-
"label.full-rate": "
|
|
17
|
-
"label.reserved-carrier": "
|
|
18
|
-
"label.transfer-command": "
|
|
19
|
-
"label.parent-object-id": "
|
|
20
|
-
"label.process-type": "
|
|
14
|
+
"label.carrier-legend-name": "캐리어 범례",
|
|
15
|
+
"label.sc-state": "SC 상태",
|
|
16
|
+
"label.full-rate": "적재율",
|
|
17
|
+
"label.reserved-carrier": "예약된 캐리어",
|
|
18
|
+
"label.transfer-command": "이송 명령",
|
|
19
|
+
"label.parent-object-id": "상위 오브젝트",
|
|
20
|
+
"label.process-type": "공정 유형",
|
|
21
21
|
"label.show-carrier-text": "캐리어 텍스트",
|
|
22
22
|
"label.show-text": "텍스트 보이기",
|
|
23
|
-
"label.sub-state": "
|
|
24
|
-
"label.banned": "
|
|
25
|
-
"label.type": "
|
|
26
|
-
"label.inout-type": "
|
|
27
|
-
"label.manual": "
|
|
23
|
+
"label.sub-state": "하위 상태",
|
|
24
|
+
"label.banned": "사용 금지",
|
|
25
|
+
"label.type": "유형",
|
|
26
|
+
"label.inout-type": "입출고 유형",
|
|
27
|
+
"label.manual": "수동",
|
|
28
28
|
"label.reserved": "예약됨",
|
|
29
29
|
"label.occupied": "점유됨",
|
|
30
30
|
"label.orientation": "방향",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"label.shelf-legend-name": "선반",
|
|
53
53
|
"label.equipment-legend-name": "장비",
|
|
54
54
|
"label.node-machine": "node machine",
|
|
55
|
-
"label.nodes": "
|
|
56
|
-
"label.vehicle-animation": "
|
|
55
|
+
"label.nodes": "노드",
|
|
56
|
+
"label.vehicle-animation": "차량 애니메이션",
|
|
57
57
|
"label.battery-rate-legend-name": "배터리 잔량",
|
|
58
58
|
"label.conveyor-orientation": "이송방향",
|
|
59
59
|
"component.backdrop": "배경",
|
|
@@ -77,5 +77,46 @@
|
|
|
77
77
|
"label.bright": "밝음",
|
|
78
78
|
"label.coord-axis-x": "내부 X축",
|
|
79
79
|
"label.coord-invert-x": "X 원점 반전",
|
|
80
|
-
"label.coord-invert-y": "Y 원점 반전"
|
|
80
|
+
"label.coord-invert-y": "Y 원점 반전",
|
|
81
|
+
"label.load-layout.addressing": "실시간 데이터의 주소",
|
|
82
|
+
"label.load-layout.addressing-by": "읽는 방식",
|
|
83
|
+
"label.load-layout.addressing-fields": "필드",
|
|
84
|
+
"label.load-layout.addressing-none": "주소 해석 안 함 (모든 적재 위치 미상)",
|
|
85
|
+
"label.load-layout.addressing-pattern": "위치 문자열 패턴",
|
|
86
|
+
"label.load-layout.field": "필드",
|
|
87
|
+
"label.load-layout.invalid": "저장된 적재 위치 설정이 올바르지 않습니다. 설정을 수정할 때까지 단일 적재 위치로 표시합니다.",
|
|
88
|
+
"label.load-layout.issue.ADDRESSING_BY_INVALID": "주소 해석 방식을 필드 또는 위치 문자열 패턴으로 지정해 주세요.",
|
|
89
|
+
"label.load-layout.issue.ADDRESS_PATTERN_EMPTY": "위치 문자열 패턴을 입력해 주세요.",
|
|
90
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SLOT_REQUIRED": "위치 문자열 패턴에 적재 위치 토큰을 포함해 주세요.",
|
|
91
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SYNTAX": "\"{text}\" 부근의 토큰 문법이 올바르지 않습니다.",
|
|
92
|
+
"label.load-layout.issue.ADDRESS_PATTERN_TOKEN_REPEATED": "{token} 토큰은 한 번만 사용할 수 있습니다.",
|
|
93
|
+
"label.load-layout.issue.ADDRESS_PATTERN_WIDTH_NOT_ALLOWED": "자릿수는 층·적재 위치 토큰에만 지정할 수 있습니다. ({token})",
|
|
94
|
+
"label.load-layout.issue.DUPLICATE_CARRIER": "동일한 캐리어가 중복 수신되었습니다: {name}",
|
|
95
|
+
"label.load-layout.issue.FIELD_REQUIRED": "필드 이름을 입력해 주세요.",
|
|
96
|
+
"label.load-layout.param-cols": "열 수",
|
|
97
|
+
"label.load-layout.param-rows": "행 수",
|
|
98
|
+
"label.load-layout.param-stackLimit": "적층 한도",
|
|
99
|
+
"label.load-layout.param-upperElevation": "위층 높이 (mm)",
|
|
100
|
+
"label.load-layout.pattern-text": "패턴",
|
|
101
|
+
"label.load-layout.preset-agv2x2": "AGV 2×2",
|
|
102
|
+
"label.load-layout.preset-agvParallel": "AGV 병렬 (좌·우)",
|
|
103
|
+
"label.load-layout.preset-agvTandem": "AGV 직렬 (앞·뒤)",
|
|
104
|
+
"label.load-layout.preset-bufferPort2x2": "버퍼 포트 2×2",
|
|
105
|
+
"label.load-layout.preset-craneTwinFork": "크레인 트윈 포크",
|
|
106
|
+
"label.load-layout.preset-dualTierConveyor": "복층 컨베이어",
|
|
107
|
+
"label.load-layout.preset-floorBlock": "평치 블록",
|
|
108
|
+
"label.load-layout.preset-kind": "종류",
|
|
109
|
+
"label.load-layout.preset-multiBufferPort": "멀티 버퍼 포트 (4)",
|
|
110
|
+
"label.load-layout.preset-note": "간격과 적재 위치 크기의 초기값은 균등 분할로 계산됩니다. 실제 설비 규격에 맞는지 확인해 주세요.",
|
|
111
|
+
"label.load-layout.preset-single": "단일 적재 위치",
|
|
112
|
+
"label.load-layout.slot-field": "적재 위치 필드",
|
|
113
|
+
"label.load-layout.source": "값의 출처",
|
|
114
|
+
"label.load-layout.source-builtin": "내장 기본값",
|
|
115
|
+
"label.load-layout.source-node": "이 홀더의 개별 설정",
|
|
116
|
+
"label.load-layout.source-none": "정의 없음 (단일 적재 위치에 적층)",
|
|
117
|
+
"label.load-layout.tier-field": "층 필드",
|
|
118
|
+
"label.load-layout.tier-field-empty": "비우면 1층",
|
|
119
|
+
"label.load-layout.title": "캐리어 적재 위치",
|
|
120
|
+
"label.load-layout.unit-missing": "도면 단위(mmPerUnit)가 정해지지 않아 물리 배치와 적재 판정을 할 수 없습니다.",
|
|
121
|
+
"label.load-layout.unit-ok": "도면 1단위 = {mm} mm"
|
|
81
122
|
}
|
package/translations/ms.json
CHANGED
|
@@ -76,5 +76,46 @@
|
|
|
76
76
|
"label.bright": "cerah",
|
|
77
77
|
"label.coord-axis-x": "internal x axis",
|
|
78
78
|
"label.coord-invert-x": "invert x origin",
|
|
79
|
-
"label.coord-invert-y": "invert y origin"
|
|
79
|
+
"label.coord-invert-y": "invert y origin",
|
|
80
|
+
"label.load-layout.addressing": "Address in live data",
|
|
81
|
+
"label.load-layout.addressing-by": "Read by",
|
|
82
|
+
"label.load-layout.addressing-fields": "Fields",
|
|
83
|
+
"label.load-layout.addressing-none": "Not read (all shown as address unknown)",
|
|
84
|
+
"label.load-layout.addressing-pattern": "Location pattern",
|
|
85
|
+
"label.load-layout.field": "Field",
|
|
86
|
+
"label.load-layout.invalid": "The saved setting is invalid. Shown as one slot until fixed.",
|
|
87
|
+
"label.load-layout.issue.ADDRESSING_BY_INVALID": "Must be fields or a location pattern",
|
|
88
|
+
"label.load-layout.issue.ADDRESS_PATTERN_EMPTY": "The pattern is empty",
|
|
89
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SLOT_REQUIRED": "The pattern needs a slot token",
|
|
90
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SYNTAX": "Broken token near \"{text}\"",
|
|
91
|
+
"label.load-layout.issue.ADDRESS_PATTERN_TOKEN_REPEATED": "The {token} token may appear only once",
|
|
92
|
+
"label.load-layout.issue.ADDRESS_PATTERN_WIDTH_NOT_ALLOWED": "Digits are allowed only on the tier and slot tokens, not on {token}",
|
|
93
|
+
"label.load-layout.issue.DUPLICATE_CARRIER": "The same carrier came twice: {name}",
|
|
94
|
+
"label.load-layout.issue.FIELD_REQUIRED": "A field name is required",
|
|
95
|
+
"label.load-layout.param-cols": "Columns",
|
|
96
|
+
"label.load-layout.param-rows": "Rows",
|
|
97
|
+
"label.load-layout.param-stackLimit": "Stack limit",
|
|
98
|
+
"label.load-layout.param-upperElevation": "Upper tier elevation (mm)",
|
|
99
|
+
"label.load-layout.pattern-text": "Pattern",
|
|
100
|
+
"label.load-layout.preset-agv2x2": "AGV 2×2",
|
|
101
|
+
"label.load-layout.preset-agvParallel": "AGV parallel (left/right)",
|
|
102
|
+
"label.load-layout.preset-agvTandem": "AGV tandem (front/rear)",
|
|
103
|
+
"label.load-layout.preset-bufferPort2x2": "Buffer port 2×2",
|
|
104
|
+
"label.load-layout.preset-craneTwinFork": "Crane twin fork",
|
|
105
|
+
"label.load-layout.preset-dualTierConveyor": "Dual-tier conveyor",
|
|
106
|
+
"label.load-layout.preset-floorBlock": "Floor block",
|
|
107
|
+
"label.load-layout.preset-kind": "Kind",
|
|
108
|
+
"label.load-layout.preset-multiBufferPort": "Multi-buffer port (4)",
|
|
109
|
+
"label.load-layout.preset-note": "Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.",
|
|
110
|
+
"label.load-layout.preset-single": "Single slot",
|
|
111
|
+
"label.load-layout.slot-field": "Slot field",
|
|
112
|
+
"label.load-layout.source": "Source",
|
|
113
|
+
"label.load-layout.source-builtin": "Built-in default",
|
|
114
|
+
"label.load-layout.source-node": "Set on this holder",
|
|
115
|
+
"label.load-layout.source-none": "Not defined (one slot, stacked)",
|
|
116
|
+
"label.load-layout.tier-field": "Tier field",
|
|
117
|
+
"label.load-layout.tier-field-empty": "Empty = tier 1",
|
|
118
|
+
"label.load-layout.title": "Carrier load slots",
|
|
119
|
+
"label.load-layout.unit-missing": "Drawing unit is not set (mmPerUnit). Physical placement and fit checks are unavailable.",
|
|
120
|
+
"label.load-layout.unit-ok": "Drawing unit 1 = {mm} mm"
|
|
80
121
|
}
|
package/translations/zh.json
CHANGED
|
@@ -76,5 +76,46 @@
|
|
|
76
76
|
"label.bright": "明亮",
|
|
77
77
|
"label.coord-axis-x": "内部X轴",
|
|
78
78
|
"label.coord-invert-x": "X原点反转",
|
|
79
|
-
"label.coord-invert-y": "Y原点反转"
|
|
79
|
+
"label.coord-invert-y": "Y原点反转",
|
|
80
|
+
"label.load-layout.addressing": "Address in live data",
|
|
81
|
+
"label.load-layout.addressing-by": "Read by",
|
|
82
|
+
"label.load-layout.addressing-fields": "Fields",
|
|
83
|
+
"label.load-layout.addressing-none": "Not read (all shown as address unknown)",
|
|
84
|
+
"label.load-layout.addressing-pattern": "Location pattern",
|
|
85
|
+
"label.load-layout.field": "Field",
|
|
86
|
+
"label.load-layout.invalid": "The saved setting is invalid. Shown as one slot until fixed.",
|
|
87
|
+
"label.load-layout.issue.ADDRESSING_BY_INVALID": "Must be fields or a location pattern",
|
|
88
|
+
"label.load-layout.issue.ADDRESS_PATTERN_EMPTY": "The pattern is empty",
|
|
89
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SLOT_REQUIRED": "The pattern needs a slot token",
|
|
90
|
+
"label.load-layout.issue.ADDRESS_PATTERN_SYNTAX": "Broken token near \"{text}\"",
|
|
91
|
+
"label.load-layout.issue.ADDRESS_PATTERN_TOKEN_REPEATED": "The {token} token may appear only once",
|
|
92
|
+
"label.load-layout.issue.ADDRESS_PATTERN_WIDTH_NOT_ALLOWED": "Digits are allowed only on the tier and slot tokens, not on {token}",
|
|
93
|
+
"label.load-layout.issue.DUPLICATE_CARRIER": "The same carrier came twice: {name}",
|
|
94
|
+
"label.load-layout.issue.FIELD_REQUIRED": "A field name is required",
|
|
95
|
+
"label.load-layout.param-cols": "Columns",
|
|
96
|
+
"label.load-layout.param-rows": "Rows",
|
|
97
|
+
"label.load-layout.param-stackLimit": "Stack limit",
|
|
98
|
+
"label.load-layout.param-upperElevation": "Upper tier elevation (mm)",
|
|
99
|
+
"label.load-layout.pattern-text": "Pattern",
|
|
100
|
+
"label.load-layout.preset-agv2x2": "AGV 2×2",
|
|
101
|
+
"label.load-layout.preset-agvParallel": "AGV parallel (left/right)",
|
|
102
|
+
"label.load-layout.preset-agvTandem": "AGV tandem (front/rear)",
|
|
103
|
+
"label.load-layout.preset-bufferPort2x2": "Buffer port 2×2",
|
|
104
|
+
"label.load-layout.preset-craneTwinFork": "Crane twin fork",
|
|
105
|
+
"label.load-layout.preset-dualTierConveyor": "Dual-tier conveyor",
|
|
106
|
+
"label.load-layout.preset-floorBlock": "Floor block",
|
|
107
|
+
"label.load-layout.preset-kind": "Kind",
|
|
108
|
+
"label.load-layout.preset-multiBufferPort": "Multi-buffer port (4)",
|
|
109
|
+
"label.load-layout.preset-note": "Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.",
|
|
110
|
+
"label.load-layout.preset-single": "Single slot",
|
|
111
|
+
"label.load-layout.slot-field": "Slot field",
|
|
112
|
+
"label.load-layout.source": "Source",
|
|
113
|
+
"label.load-layout.source-builtin": "Built-in default",
|
|
114
|
+
"label.load-layout.source-node": "Set on this holder",
|
|
115
|
+
"label.load-layout.source-none": "Not defined (one slot, stacked)",
|
|
116
|
+
"label.load-layout.tier-field": "Tier field",
|
|
117
|
+
"label.load-layout.tier-field-empty": "Empty = tier 1",
|
|
118
|
+
"label.load-layout.title": "Carrier load slots",
|
|
119
|
+
"label.load-layout.unit-missing": "Drawing unit is not set (mmPerUnit). Physical placement and fit checks are unavailable.",
|
|
120
|
+
"label.load-layout.unit-ok": "Drawing unit 1 = {mm} mm"
|
|
80
121
|
}
|