@things-factory/figure-service 10.1.115 → 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/figure-format.js +31 -0
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-history.js +1 -1
- package/dist-server/service/figure/figure-history.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/service/figure/figure-format.test.ts +1 -1
- package/server/service/figure/figure-format.ts +31 -0
- package/server/service/figure/figure-history.ts +1 -1
- package/server/service/figure/figure-release-record-db.test.ts +41 -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
|
}
|
|
@@ -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
|
})
|
|
@@ -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
|
|
|
@@ -141,7 +141,7 @@ export class FigureHistory implements HistoryEntityInterface<Figure> {
|
|
|
141
141
|
@Field({
|
|
142
142
|
nullable: true,
|
|
143
143
|
description:
|
|
144
|
-
'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.'
|
|
145
145
|
})
|
|
146
146
|
metrics?: string
|
|
147
147
|
|
|
@@ -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, FIGURE_KERNEL_VERSION } = 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:',
|
|
@@ -101,3 +103,41 @@ describe('releaseFigure records it', () => {
|
|
|
101
103
|
expect(JSON.parse(rows[1].topView).shapes.map((s: any) => s.id)).toEqual(['body'])
|
|
102
104
|
})
|
|
103
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": "공간 가운데로 도형을 놓는 도면에서는 그린 자리에서 그만큼 떨어져 놓입니다.",
|