@things-factory/figure-service 10.1.114 → 10.1.116
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-server/service/figure/event-subscriber.js +2 -1
- package/dist-server/service/figure/event-subscriber.js.map +1 -1
- package/dist-server/service/figure/figure-format.d.ts +1 -0
- package/dist-server/service/figure/figure-format.js +32 -1
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-history.d.ts +1 -0
- package/dist-server/service/figure/figure-history.js +9 -1
- package/dist-server/service/figure/figure-history.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.js +2 -1
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/service/figure/event-subscriber.ts +2 -1
- package/server/service/figure/figure-format.test.ts +1 -1
- package/server/service/figure/figure-format.ts +34 -3
- package/server/service/figure/figure-history.ts +14 -1
- package/server/service/figure/figure-mutation.ts +2 -1
- package/server/service/figure/figure-release-record-db.test.ts +45 -1
- package/translations/en.json +11 -0
- package/translations/ko.json +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-service",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.116",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -25,16 +25,16 @@
|
|
|
25
25
|
"test:templates": "node --test scripts/templates.test.mts"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@hatiolab/figure-model": "^0.2
|
|
28
|
+
"@hatiolab/figure-model": "^0.3.2",
|
|
29
29
|
"@operato/typeorm-history": "^10.0.0",
|
|
30
30
|
"@things-factory/ai-client-base": "^10.1.114",
|
|
31
|
-
"@things-factory/attachment-base": "^10.1.
|
|
32
|
-
"@things-factory/auth-base": "^10.1.
|
|
31
|
+
"@things-factory/attachment-base": "^10.1.116",
|
|
32
|
+
"@things-factory/auth-base": "^10.1.116",
|
|
33
33
|
"@things-factory/env": "^10.1.20",
|
|
34
34
|
"@things-factory/shell": "^10.1.106",
|
|
35
35
|
"graphql-scalars": "^1.22.4",
|
|
36
36
|
"graphql-upload": "^15.0.2",
|
|
37
37
|
"koa-router": "^7.4.0"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "49fa935cc703be70200eef4d29f473de98f871ce"
|
|
40
40
|
}
|
|
@@ -14,7 +14,7 @@ import { FigureHistory } from './figure-history.js'
|
|
|
14
14
|
*
|
|
15
15
|
* 발행 메모는 Figure 의 칸이 아니다. `releaseFigure` 가 저장할 엔티티에 `__releaseComment` 로 얹어
|
|
16
16
|
* 두면 여기서 판본의 `comment` 로 옮긴다 — 메모는 그 판의 것이지 도형의 것이 아니다. 정본 형식
|
|
17
|
-
* (`__releaseFormat`), 직전 판에 견준 변경(`__releaseChanges`), 발행 때 잰 값(`__releaseMetrics`)과
|
|
17
|
+
* (`__releaseFormat`), 잰 커널의 판(`__releaseKernel`, ADR-0109 C1), 직전 판에 견준 변경(`__releaseChanges`), 발행 때 잰 값(`__releaseMetrics`)과
|
|
18
18
|
* 윗모습(`__releaseTopView`)도 같은 길로 옮긴다(ADR-0104).
|
|
19
19
|
*/
|
|
20
20
|
/** What `releaseFigure` laid on the entity for this release, moved onto the history row. */
|
|
@@ -22,6 +22,7 @@ function releaseRecordOnto(history: FigureHistory, entity: Figure): FigureHistor
|
|
|
22
22
|
const e = entity as any
|
|
23
23
|
;(history as any).comment = e.__releaseComment ?? null
|
|
24
24
|
history.assetFormat = e.__releaseFormat ?? null
|
|
25
|
+
history.kernelVersion = e.__releaseKernel ?? null
|
|
25
26
|
/* [] stays "[]" (nothing changed); null stays null (not compared). */
|
|
26
27
|
history.changes = e.__releaseChanges == null ? null : JSON.stringify(e.__releaseChanges)
|
|
27
28
|
history.metrics = e.__releaseMetrics == null ? null : JSON.stringify(e.__releaseMetrics)
|
|
@@ -350,7 +350,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
350
350
|
it('every new change has its sentence in both languages', () => {
|
|
351
351
|
const KO = require('../../../translations/ko.json') as Record<string, string>
|
|
352
352
|
const EN = require('../../../translations/en.json') as Record<string, string>
|
|
353
|
-
for (const code of ['occupancy-changed', 'contact-face-changed', 'public-input-gone', 'public-input-range-narrowed', 'public-input-added'])
|
|
353
|
+
for (const code of ['occupancy-changed', 'contact-face-changed', 'public-input-gone', 'public-input-range-narrowed', 'public-input-added', 'state-input-joints-changed', 'joint-axis-changed', 'joint-motion-unit-changed', 'joint-child-changed', 'joint-list-unread'])
|
|
354
354
|
for (const dict of [KO, EN]) for (const field of ['message', 'why']) expect(dict[`figure.gate.contract.${code}.${field}`]).toBeTruthy()
|
|
355
355
|
})
|
|
356
356
|
})
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* board (`consumablePayloadOf`). The format is `figure-asset-1`, and the asset says so in its `version`; a source
|
|
4
4
|
* that does not is refused by name, because a source the server cannot identify is one it must not save.
|
|
5
5
|
*/
|
|
6
|
-
import { compileFigureAsset, inspectFigureAsset, isPlaceholderType, parseFigureAsset, serializeFigureAsset, figureContractChanges, figureCostOf, figureOriginCentredOf, figureReleaseMetricsOf, figureScoreOf, figureTopViewOf } from '@hatiolab/figure-model'
|
|
6
|
+
import { FIGURE_KERNEL_VERSION, compileFigureAsset, inspectFigureAsset, isPlaceholderType, parseFigureAsset, serializeFigureAsset, figureContractChanges, figureCostOf, figureOriginCentredOf, figureReleaseMetricsOf, figureScoreOf, figureTopViewOf } from '@hatiolab/figure-model'
|
|
7
7
|
import type { FigureAsset, FigureContractChange, FigureContractError, FigureReleaseMetrics, FigureTopView, FigureViolation } from '@hatiolab/figure-model'
|
|
8
8
|
|
|
9
9
|
import type { FigureFinding } from './figure-type.js'
|
|
@@ -461,6 +461,37 @@ function figureContractWording(change: FigureContractChange, say: Say): Pick<Gat
|
|
|
461
461
|
message: say(key('message'), { ...v, input: change.subject ?? '' }, `'${change.subject}' can now be set on a board.`),
|
|
462
462
|
why: say(key('why'), {}, 'Placed instances that give no value stand at its default.')
|
|
463
463
|
}
|
|
464
|
+
/* The joints a controller drives (ADR-0107 decision 7): what writing a state input moves, compared release to release. */
|
|
465
|
+
case 'state-input-joints-changed': {
|
|
466
|
+
const after = v.after || say('figure.gate.contract.joints-none', {}, 'nothing')
|
|
467
|
+
return {
|
|
468
|
+
message: say(key('message'), { ...v, after }, `The control '${v.id}' moved ${v.before}. It now moves ${after}.`),
|
|
469
|
+
why: say(key('why'), {}, 'A controller writes this value to drive those joints. After this release the same value moves other joints, or by other amounts.'),
|
|
470
|
+
...gone
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
case 'joint-axis-changed':
|
|
474
|
+
return {
|
|
475
|
+
message: say(key('message'), v, `The axis of the joint '${v.joint}' changed from ${v.before} to ${v.after}.`),
|
|
476
|
+
why: say(key('why'), {}, "A controller's stored values turn or slide the joint about this axis. After this release the same value moves it another way."),
|
|
477
|
+
...gone
|
|
478
|
+
}
|
|
479
|
+
case 'joint-motion-unit-changed':
|
|
480
|
+
return {
|
|
481
|
+
message: say(key('message'), v, `The joint '${v.joint}' now moves in ${v.after}, not ${v.before}.`),
|
|
482
|
+
why: say(key('why'), {}, "A controller's stored values were written for the old unit. After this release the same value means another motion."),
|
|
483
|
+
...gone
|
|
484
|
+
}
|
|
485
|
+
case 'joint-child-changed':
|
|
486
|
+
return {
|
|
487
|
+
message: say(key('message'), v, `The joint '${v.joint}' now moves '${v.after}' instead of '${v.before}'.`),
|
|
488
|
+
why: say(key('why'), {}, "A controller's values keep their meaning. On screen the joint now moves the new part.")
|
|
489
|
+
}
|
|
490
|
+
case 'joint-list-unread':
|
|
491
|
+
return {
|
|
492
|
+
message: say(key('message'), v, `The last release's joint list could not be read, so its joints were not compared: ${v.reason}`),
|
|
493
|
+
why: say(key('why'), {}, "Whether this release changes what a controller's values move has not been checked. The last release was made by a kernel that did not measure joints.")
|
|
494
|
+
}
|
|
464
495
|
}
|
|
465
496
|
}
|
|
466
497
|
|
|
@@ -473,9 +504,9 @@ function figureContractWording(change: FigureContractChange, say: Say): Pick<Gat
|
|
|
473
504
|
export function releaseRecordOf(
|
|
474
505
|
sourceJson: string,
|
|
475
506
|
releasedJson?: string | null
|
|
476
|
-
): { format: string; changes: FigureContractChange[] | null; metrics: FigureReleaseMetrics; topView: FigureTopView } {
|
|
507
|
+
): { format: string; kernel: string; changes: FigureContractChange[] | null; metrics: FigureReleaseMetrics; topView: FigureTopView } {
|
|
477
508
|
const asset = parseFigureAsset(sourceJson)
|
|
478
|
-
const measured = { format: asset.version, metrics: figureReleaseMetricsOf(asset), topView: figureTopViewOf(asset, null) }
|
|
509
|
+
const measured = { format: asset.version, kernel: FIGURE_KERNEL_VERSION, metrics: figureReleaseMetricsOf(asset), topView: figureTopViewOf(asset, null) }
|
|
479
510
|
if (!releasedJson) return { ...measured, changes: [] }
|
|
480
511
|
let before: FigureAsset
|
|
481
512
|
try {
|
|
@@ -124,11 +124,24 @@ export class FigureHistory implements HistoryEntityInterface<Figure> {
|
|
|
124
124
|
})
|
|
125
125
|
changes?: string
|
|
126
126
|
|
|
127
|
+
/*
|
|
128
|
+
* 이 판을 잰 커널의 판(figure-model `FIGURE_KERNEL_VERSION`). 점수 · 묶음 · metrics 는 그 커널의 규칙으로 센 값이다 —
|
|
129
|
+
* 규칙이 바뀌어도 발행된 값을 다시 세서 덮지 않고(ADR-0109 C1), 읽는 쪽은 이 칸으로 「남긴 값」과 「지금 규칙의 값」을
|
|
130
|
+
* 가른다. 이 칸이 생기기 전에 발행된 판은 비어 있다.
|
|
131
|
+
*/
|
|
132
|
+
@Column({ nullable: true })
|
|
133
|
+
@Field({
|
|
134
|
+
nullable: true,
|
|
135
|
+
description:
|
|
136
|
+
'The figure-model kernel version that measured this release (score, groups, metrics). null for releases made before releases recorded it.'
|
|
137
|
+
})
|
|
138
|
+
kernelVersion?: string
|
|
139
|
+
|
|
127
140
|
@Column({ nullable: true, ...longTextColumn() })
|
|
128
141
|
@Field({
|
|
129
142
|
nullable: true,
|
|
130
143
|
description:
|
|
131
|
-
'What this version measured once, as JSON (figure-model figureReleaseMetricsOf): { atLargest?: { size, triangles, groups }, linear?: { scalable, parts, probe } }.
|
|
144
|
+
'What this version measured once, as JSON (figure-model figureReleaseMetricsOf): { atLargest?: { size, triangles, groups }, linear?: { scalable, parts, probe }, origin?: { centred, off }, joints?: { schema, derivation, joints, unlisted } }. atLargest and linear only for a figure that takes its size from the instance. joints is the joint list a controller reads (schema 2, figure-model figureJointsOf), present when anything moves.'
|
|
132
145
|
})
|
|
133
146
|
metrics?: string
|
|
134
147
|
|
|
@@ -348,9 +348,10 @@ export class FigureMutation {
|
|
|
348
348
|
version: nextVersion,
|
|
349
349
|
state: 'released',
|
|
350
350
|
updater: user,
|
|
351
|
-
/* 판본 구독자가 이것들을 판본의 comment · assetFormat · changes 로 옮긴다. Figure 의 칸이 아니다. */
|
|
351
|
+
/* 판본 구독자가 이것들을 판본의 comment · assetFormat · kernelVersion · changes 로 옮긴다. Figure 의 칸이 아니다. */
|
|
352
352
|
__releaseComment: comment ?? null,
|
|
353
353
|
__releaseFormat: record.format,
|
|
354
|
+
__releaseKernel: record.kernel,
|
|
354
355
|
__releaseChanges: record.changes,
|
|
355
356
|
__releaseMetrics: record.metrics,
|
|
356
357
|
__releaseTopView: record.topView
|
|
@@ -7,13 +7,14 @@ import type { DataSource } from 'typeorm'
|
|
|
7
7
|
const { DataSource: TypeOrmDataSource } = require('typeorm')
|
|
8
8
|
const shell = require('@things-factory/shell')
|
|
9
9
|
const authBase = require('@things-factory/auth-base')
|
|
10
|
-
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, proposeFigureOccupancy } = require('@hatiolab/figure-model')
|
|
10
|
+
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, proposeFigureOccupancy, palletizerRobotFigure, FIGURE_KERNEL_VERSION } = require('@hatiolab/figure-model')
|
|
11
11
|
|
|
12
12
|
let dataSource: DataSource | null = null
|
|
13
13
|
let Figure: any
|
|
14
14
|
let FigureHistory: any
|
|
15
15
|
let Subscriber: any
|
|
16
16
|
let FigureMutation: any
|
|
17
|
+
let releaseRecordOf: any
|
|
17
18
|
|
|
18
19
|
function loadCompiled(modulePath: string): any {
|
|
19
20
|
const { existsSync } = require('fs')
|
|
@@ -29,6 +30,7 @@ beforeAll(async () => {
|
|
|
29
30
|
FigureHistory = loadCompiled('service/figure/figure-history').FigureHistory
|
|
30
31
|
Subscriber = loadCompiled('service/figure/event-subscriber').FigureHistoryEntitySubscriber
|
|
31
32
|
FigureMutation = loadCompiled('service/figure/figure-mutation').FigureMutation
|
|
33
|
+
releaseRecordOf = loadCompiled('service/figure/figure-format').releaseRecordOf
|
|
32
34
|
dataSource = new TypeOrmDataSource({
|
|
33
35
|
type: 'sqlite',
|
|
34
36
|
database: ':memory:',
|
|
@@ -65,6 +67,8 @@ describe('a release record on its history row', () => {
|
|
|
65
67
|
expect(row.comment).toBe('first')
|
|
66
68
|
expect(row.metrics).toBeNull()
|
|
67
69
|
expect(row.topView).toBeNull()
|
|
70
|
+
/* A row the release did not stamp says so: not measured by a known kernel, rather than by today's. */
|
|
71
|
+
expect(row.kernelVersion).toBeNull()
|
|
68
72
|
})
|
|
69
73
|
it('[] stays nothing changed, null stays not compared', async () => {
|
|
70
74
|
expect((await release({ __releaseFormat: 'figure-asset-1', __releaseChanges: [] })).changes).toBe('[]')
|
|
@@ -90,6 +94,8 @@ describe('releaseFigure records it', () => {
|
|
|
90
94
|
const rows = await dataSource!.getRepository(FigureHistory).find({ where: { originalId: figure.id }, order: { version: 'ASC' } })
|
|
91
95
|
expect(rows.map((r: any) => [r.version, r.assetFormat, r.comment])).toEqual([[1, 'figure-asset-1', 'one'], [2, 'figure-asset-1', 'two']])
|
|
92
96
|
expect(rows[0].changes).toBe('[]')
|
|
97
|
+
/* Each release names the kernel that measured it (ADR-0109 C1). */
|
|
98
|
+
expect(rows.map((r: any) => r.kernelVersion)).toEqual([FIGURE_KERNEL_VERSION, FIGURE_KERNEL_VERSION])
|
|
93
99
|
const codes = JSON.parse(rows[1].changes).map((c: any) => c.code)
|
|
94
100
|
expect(codes).toContain('occupancy-changed')
|
|
95
101
|
/* What each release measured, and its plan from above, are on its own row. */
|
|
@@ -97,3 +103,41 @@ describe('releaseFigure records it', () => {
|
|
|
97
103
|
expect(JSON.parse(rows[1].topView).shapes.map((s: any) => s.id)).toEqual(['body'])
|
|
98
104
|
})
|
|
99
105
|
})
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
The joint list a controller reads rides the release's metrics (ADR-0107 decision 7): measured once, at release, and
|
|
109
|
+
read back from the history row. A re-release that turns a joint about another axis is refused with the sentence.
|
|
110
|
+
*/
|
|
111
|
+
describe('a release of a figure that moves keeps its joint list', () => {
|
|
112
|
+
const palletizer = (j4Axis?: string) => {
|
|
113
|
+
const { asset } = palletizerRobotFigure()
|
|
114
|
+
if (!j4Axis) return serializeFigureAsset(asset)
|
|
115
|
+
const j4 = asset.joints.find((j: any) => j.id === 'j4')
|
|
116
|
+
return serializeFigureAsset(applyFigureAuthoring(asset, { kind: 'set-joint', joint: { ...j4, axis: j4Axis } }))
|
|
117
|
+
}
|
|
118
|
+
it('schema 2, the wrist following -(j2 + j3), controls with both units; another axis is refused', async () => {
|
|
119
|
+
const domain = await dataSource!.getRepository(shell.Domain).save({ name: 'release-joints', subdomain: 'release-joints' })
|
|
120
|
+
const context = { state: { domain, user: undefined, tx: dataSource!.manager } }
|
|
121
|
+
const figure = await dataSource!.getRepository(Figure).save({ type: 'PALLETIZER', name: 'palletizer', source: palletizer(), state: 'draft', version: 0, domain })
|
|
122
|
+
await new FigureMutation().releaseFigure(figure.id, 'one', context)
|
|
123
|
+
|
|
124
|
+
const [row] = await dataSource!.getRepository(FigureHistory).find({ where: { originalId: figure.id } })
|
|
125
|
+
const list = JSON.parse(row.metrics).joints
|
|
126
|
+
expect(list.schema).toBe(2)
|
|
127
|
+
const byId = new Map(list.joints.map((j: any) => [j.id, j]))
|
|
128
|
+
expect(byId.get('wrist')).toMatchObject({
|
|
129
|
+
type: 'revolute',
|
|
130
|
+
follows: { sum: [{ joint: 'j2', multiplier: -1 }, { joint: 'j3', multiplier: -1 }], offset: 0 }
|
|
131
|
+
})
|
|
132
|
+
expect((byId.get('wrist') as any).control).toBeUndefined()
|
|
133
|
+
for (const id of ['j1', 'j2', 'j3', 'j4']) expect((byId.get(id) as any).control).toMatchObject({ inputUnit: 'deg', motionUnit: 'deg' })
|
|
134
|
+
expect(list.unlisted).toEqual([])
|
|
135
|
+
|
|
136
|
+
/* The same figure with J4 turning about X: the kernel's comparison blocks it, and the door refuses with its sentence. */
|
|
137
|
+
const changes = releaseRecordOf(palletizer('X'), row.source).changes
|
|
138
|
+
expect(changes).toContainEqual(expect.objectContaining({ code: 'joint-axis-changed', blocking: true, subject: 'j4' }))
|
|
139
|
+
await dataSource!.getRepository(Figure).save({ ...(await dataSource!.getRepository(Figure).findOneOrFail({ where: { id: figure.id } })), domain, source: palletizer('X'), state: 'draft' })
|
|
140
|
+
await expect(new FigureMutation().releaseFigure(figure.id, 'two', context)).rejects.toThrow(/The axis of the joint 'j4' changed/)
|
|
141
|
+
expect(await dataSource!.getRepository(FigureHistory).count({ where: { originalId: figure.id } })).toBe(1)
|
|
142
|
+
})
|
|
143
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -75,6 +75,17 @@
|
|
|
75
75
|
"figure.gate.contract.public-input-range-narrowed.why": "An instance may have set a value outside the new range. Which values are placed is not recorded.",
|
|
76
76
|
"figure.gate.contract.public-input-added.message": "'{input}' can now be set on a board.",
|
|
77
77
|
"figure.gate.contract.public-input-added.why": "Placed instances that give no value stand at its default.",
|
|
78
|
+
"figure.gate.contract.joints-none": "nothing",
|
|
79
|
+
"figure.gate.contract.state-input-joints-changed.message": "The control '{id}' moved {before}. It now moves {after}.",
|
|
80
|
+
"figure.gate.contract.state-input-joints-changed.why": "A controller writes this value to drive those joints. After this release the same value moves other joints, or by other amounts.",
|
|
81
|
+
"figure.gate.contract.joint-axis-changed.message": "The axis of the joint '{joint}' changed from {before} to {after}.",
|
|
82
|
+
"figure.gate.contract.joint-axis-changed.why": "A controller's stored values turn or slide the joint about this axis. After this release the same value moves it another way.",
|
|
83
|
+
"figure.gate.contract.joint-motion-unit-changed.message": "The joint '{joint}' now moves in {after}, not {before}.",
|
|
84
|
+
"figure.gate.contract.joint-motion-unit-changed.why": "A controller's stored values were written for the old unit. After this release the same value means another motion.",
|
|
85
|
+
"figure.gate.contract.joint-child-changed.message": "The joint '{joint}' now moves '{after}' instead of '{before}'.",
|
|
86
|
+
"figure.gate.contract.joint-child-changed.why": "A controller's values keep their meaning. On screen the joint now moves the new part.",
|
|
87
|
+
"figure.gate.contract.joint-list-unread.message": "The last release's joint list could not be read, so its joints were not compared: {reason}",
|
|
88
|
+
"figure.gate.contract.joint-list-unread.why": "Whether this release changes what a controller's values move has not been checked. The last release was made by a kernel that did not measure joints.",
|
|
78
89
|
"error.figure-release-not-found": "{type} has no release {version}. its latest release is {latest}.",
|
|
79
90
|
"figure.gate.origin-off-centre.message": "The origin is off the middle of the declared room: {off}.",
|
|
80
91
|
"figure.gate.origin-off-centre.why": "A drawing that places figures by the middle of their room puts this one that far from where it is drawn.",
|
package/translations/ko.json
CHANGED
|
@@ -75,6 +75,17 @@
|
|
|
75
75
|
"figure.gate.contract.public-input-range-narrowed.why": "새 범위 밖의 값을 준 인스턴스가 있을 수 있습니다. 어떤 값이 놓였는지는 기록하지 않습니다.",
|
|
76
76
|
"figure.gate.contract.public-input-added.message": "'{input}' 값을 이제 보드에서 정할 수 있습니다.",
|
|
77
77
|
"figure.gate.contract.public-input-added.why": "값을 주지 않은 인스턴스는 기본값으로 섭니다.",
|
|
78
|
+
"figure.gate.contract.joints-none": "없음",
|
|
79
|
+
"figure.gate.contract.state-input-joints-changed.message": "'{id}' 조작값이 움직이던 관절이 {before} 에서 {after} 로 바뀌었습니다.",
|
|
80
|
+
"figure.gate.contract.state-input-joints-changed.why": "컨트롤러는 이 값을 써서 그 관절들을 움직입니다. 이 발행 뒤에는 같은 값이 다른 관절을 움직이거나 다른 양만큼 움직입니다.",
|
|
81
|
+
"figure.gate.contract.joint-axis-changed.message": "'{joint}' 관절의 축이 {before} 에서 {after} 로 바뀌었습니다.",
|
|
82
|
+
"figure.gate.contract.joint-axis-changed.why": "컨트롤러가 저장한 값은 이 축을 기준으로 관절을 회전하거나 이동합니다. 이 발행 뒤에는 같은 값이 다른 방향으로 움직입니다.",
|
|
83
|
+
"figure.gate.contract.joint-motion-unit-changed.message": "'{joint}' 관절의 움직임 단위가 {before} 에서 {after} 로 바뀌었습니다.",
|
|
84
|
+
"figure.gate.contract.joint-motion-unit-changed.why": "컨트롤러가 저장한 값은 옛 단위에 맞춰 쓰였습니다. 이 발행 뒤에는 같은 값이 다른 움직임이 됩니다.",
|
|
85
|
+
"figure.gate.contract.joint-child-changed.message": "'{joint}' 관절이 '{before}' 대신 '{after}' 부품을 움직입니다.",
|
|
86
|
+
"figure.gate.contract.joint-child-changed.why": "컨트롤러의 값은 뜻이 그대로입니다. 화면에서는 그 관절이 새 부품을 움직입니다.",
|
|
87
|
+
"figure.gate.contract.joint-list-unread.message": "직전 발행본의 관절 목록을 읽지 못해 관절을 비교하지 않았습니다: {reason}",
|
|
88
|
+
"figure.gate.contract.joint-list-unread.why": "이 발행이 컨트롤러의 값이 움직이는 것을 바꾸는지는 확인하지 않았습니다. 직전 발행본은 관절을 재지 않던 커널로 발행되었습니다.",
|
|
78
89
|
"error.figure-release-not-found": "{type} 에는 {version} 번 발행판이 없습니다. 최신 발행판은 {latest} 번입니다.",
|
|
79
90
|
"figure.gate.origin-off-centre.message": "원점이 차지 공간 가운데에서 비켜 있습니다: {off}.",
|
|
80
91
|
"figure.gate.origin-off-centre.why": "공간 가운데로 도형을 놓는 도면에서는 그린 자리에서 그만큼 떨어져 놓입니다.",
|