@things-factory/figure-service 10.1.205 → 10.1.207
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 +5 -0
- package/dist-server/service/figure/event-subscriber.js.map +1 -1
- package/dist-server/service/figure/figure-context.js +0 -2
- package/dist-server/service/figure/figure-context.js.map +1 -1
- package/dist-server/service/figure/figure-format.d.ts +9 -2
- package/dist-server/service/figure/figure-format.js +36 -69
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.js +2 -0
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-propose-steps.d.ts +1 -1
- package/dist-server/service/figure/figure-propose-steps.js +3 -5
- package/dist-server/service/figure/figure-propose-steps.js.map +1 -1
- package/dist-server/service/figure/figure-query.js +3 -5
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-revert.js +1 -1
- package/dist-server/service/figure/figure-revert.js.map +1 -1
- package/dist-server/service/figure/figure-type.js +1 -1
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/recipes/base-frame/recipe.json +4 -8
- package/recipes/belt-surface/recipe.json +2 -4
- package/recipes/bin-row/recipe.json +1 -2
- package/recipes/bollard-row/recipe.json +1 -2
- package/recipes/cabinet-doors/recipe.json +5 -10
- package/recipes/cable-tray/recipe.json +3 -6
- package/recipes/canopy/recipe.json +2 -4
- package/recipes/cart-handle/recipe.json +1 -2
- package/recipes/ceiling-rail-carriage/recipe.json +1 -2
- package/recipes/chain-rails/recipe.json +2 -4
- package/recipes/column-row/recipe.json +1 -2
- package/recipes/conveyor-skirts/recipe.json +4 -8
- package/recipes/double-hinged-door/recipe.json +6 -12
- package/recipes/drawer-stack/recipe.json +13 -26
- package/recipes/drip-tray/recipe.json +2 -4
- package/recipes/end-covers/recipe.json +4 -8
- package/recipes/ffu-row/recipe.json +1 -2
- package/recipes/floor-zone/recipe.json +4 -8
- package/recipes/folding-door/recipe.json +3 -6
- package/recipes/gantry-frame/recipe.json +1 -2
- package/recipes/guard-cover/recipe.json +2 -4
- package/recipes/handrail/recipe.json +2 -4
- package/recipes/hatch/recipe.json +4 -8
- package/recipes/hinged-door/recipe.json +4 -8
- package/recipes/hopper/recipe.json +1 -2
- package/recipes/ladder/recipe.json +3 -6
- package/recipes/lift-updown/recipe.json +2 -4
- package/recipes/linear-guide/recipe.json +1 -2
- package/recipes/locker-row/recipe.json +1 -2
- package/recipes/observation-window/recipe.json +4 -8
- package/recipes/operator-panel/recipe.json +4 -8
- package/recipes/partition-wall/recipe.json +3 -6
- package/recipes/peg-board/facts.md +1 -1
- package/recipes/peg-board/recipe.json +1 -2
- package/recipes/pipe-run/recipe.json +1 -2
- package/recipes/platform/recipe.json +2 -4
- package/recipes/pull-cord/recipe.json +1 -2
- package/recipes/roll-up-shutter/recipe.json +3 -6
- package/recipes/roller-row/recipe.json +2 -4
- package/recipes/room-shell/recipe.json +6 -12
- package/recipes/safety-fence/recipe.json +2 -4
- package/recipes/safety-mat/recipe.json +1 -2
- package/recipes/shelf-tiers/recipe.json +5 -10
- package/recipes/side-guides/recipe.json +2 -4
- package/recipes/sign-board/recipe.json +1 -2
- package/recipes/sliding-door/recipe.json +7 -14
- package/recipes/stairs/recipe.json +1 -2
- package/recipes/stopper/recipe.json +1 -2
- package/recipes/suction-pads/recipe.json +1 -2
- package/recipes/tank/recipe.json +2 -4
- package/recipes/toe-board/recipe.json +1 -2
- package/recipes/traffic-cones/recipe.json +1 -2
- package/recipes/transfer-belts/recipe.json +2 -4
- package/recipes/trough/recipe.json +1 -2
- package/recipes/turntable/recipe.json +1 -2
- package/recipes/workbench-top/recipe.json +2 -4
- package/scripts/templates.test.mts +2 -3
- package/server/service/figure/event-subscriber.ts +4 -0
- package/server/service/figure/figure-blueprint-query.test.ts +3 -0
- package/server/service/figure/figure-category-db.test.ts +2 -2
- package/server/service/figure/figure-context.test.ts +2 -2
- package/server/service/figure/figure-context.ts +0 -2
- package/server/service/figure/figure-deprecation-db.test.ts +2 -2
- package/server/service/figure/figure-format.test.ts +25 -105
- package/server/service/figure/figure-format.ts +40 -73
- package/server/service/figure/figure-inspect-db.test.ts +6 -5
- package/server/service/figure/figure-llm-smoke.test.ts +1 -1
- package/server/service/figure/figure-mutation.ts +2 -0
- package/server/service/figure/figure-namespace-db.test.ts +2 -2
- package/server/service/figure/figure-propose-steps.test.ts +15 -20
- package/server/service/figure/figure-propose-steps.ts +3 -5
- package/server/service/figure/figure-public-db.test.ts +2 -2
- package/server/service/figure/figure-query.ts +3 -6
- package/server/service/figure/figure-release-check.test.ts +2 -2
- package/server/service/figure/figure-release-record-db.test.ts +7 -6
- package/server/service/figure/figure-release-size-db.test.ts +156 -0
- package/server/service/figure/figure-revert.ts +2 -2
- package/server/service/figure/figure-tools.test.ts +2 -2
- package/server/service/figure/figure-type.ts +1 -1
- package/translations/en.json +4 -16
- package/translations/es.json +4 -16
- package/translations/ja.json +4 -16
- package/translations/ko.json +4 -16
- package/translations/ms.json +4 -16
- package/translations/zh.json +4 -16
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
applyFigureAuthoring,
|
|
14
14
|
createFigureAsset,
|
|
15
15
|
placeholderType,
|
|
16
|
-
proposeFigureOccupancy,
|
|
17
16
|
serializeFigureAsset
|
|
18
17
|
} from '@hatiolab/figure-model'
|
|
19
18
|
|
|
@@ -37,11 +36,11 @@ function loadCompiled(modulePath: string): any {
|
|
|
37
36
|
return require(resolved)
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
/** A figure that passes the gate on its own: a box standing on the floor
|
|
39
|
+
/** A figure that passes the gate on its own: a box standing on the floor. */
|
|
41
40
|
function sound(): string {
|
|
42
41
|
let asset = createFigureAsset('Probe')
|
|
43
42
|
asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
44
|
-
asset = applyFigureAuthoring(asset, { kind: '
|
|
43
|
+
asset = applyFigureAuthoring(asset, { kind: 'set-placement', placement: 'floor' })
|
|
45
44
|
return serializeFigureAsset(asset)
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -102,12 +101,14 @@ describe('inspectFigureDraft — the verdict while editing', () => {
|
|
|
102
101
|
it('judges the unsaved source, not the stored one, and still reads the row type', async () => {
|
|
103
102
|
const figure = await save(placeholderType())
|
|
104
103
|
let edited = createFigureAsset('Probe')
|
|
105
|
-
edited = addFigureBox(edited, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y:
|
|
104
|
+
edited = addFigureBox(edited, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 150, z: 0 } })
|
|
105
|
+
/* Its bottom named as the face it is mounted on, 100 mm above the floor: a finding the stored one does not have. */
|
|
106
|
+
edited = applyFigureAuthoring(edited, { kind: 'set-mounting-face', part: 'body', face: 'bottom' })
|
|
106
107
|
|
|
107
108
|
const inspection = await new FigureQuery().inspectFigureDraft(serializeFigureAsset(edited), context(), figure.id)
|
|
108
109
|
const codes = inspection.findings.map((f: any) => f.code)
|
|
109
110
|
|
|
110
|
-
expect(codes).toContain('
|
|
111
|
+
expect(codes).toContain('parts-off-placement-face')
|
|
111
112
|
expect(codes).toContain('type-is-placeholder')
|
|
112
113
|
})
|
|
113
114
|
|
|
@@ -43,7 +43,7 @@ function mixerDraft(): FigureAsset {
|
|
|
43
43
|
{ kind: 'attach', part: 'body', to: { plane: 'mounting-plane' } },
|
|
44
44
|
/* The body's front is its curved side: a point on it, by the outward normal there (ADR-0140 3.9; push Y, +Z is 270°). */
|
|
45
45
|
{ kind: 'attach', part: 'port', to: { part: 'body', surface: { around: 270, along: 'centre' } } },
|
|
46
|
-
{ kind: '
|
|
46
|
+
{ kind: 'set-placement', placement: 'floor' }
|
|
47
47
|
] as any)
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -447,6 +447,8 @@ export class FigureMutation {
|
|
|
447
447
|
state: 'released',
|
|
448
448
|
updater: user,
|
|
449
449
|
/* 판본 구독자가 이것들을 판본의 comment · assetFormat · kernelVersion · changes 로 옮긴다. Figure 의 칸이 아니다. */
|
|
450
|
+
/* The release's own copy, its size range written down; the figure keeps the author's (ADR-0087 decision 6). */
|
|
451
|
+
__releaseSource: record.source,
|
|
450
452
|
__releaseComment: comment ?? null,
|
|
451
453
|
__releaseFormat: record.format,
|
|
452
454
|
__releaseKernel: record.kernel,
|
|
@@ -10,7 +10,7 @@ const JSZip = require('jszip')
|
|
|
10
10
|
const shell = require('@things-factory/shell')
|
|
11
11
|
const authBase = require('@things-factory/auth-base')
|
|
12
12
|
const settingBase = require('@things-factory/setting-base')
|
|
13
|
-
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring,
|
|
13
|
+
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, readFigurePackage } = require('@hatiolab/figure-model')
|
|
14
14
|
|
|
15
15
|
let dataSource: DataSource | null = null
|
|
16
16
|
let Figure: any
|
|
@@ -60,7 +60,7 @@ afterAll(async () => {
|
|
|
60
60
|
const releasable = (label = 'Conveyor') => {
|
|
61
61
|
let a = addFigureBox(createFigureAsset(label), { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
62
62
|
a = applyFigureAuthoring(a, { kind: 'attach', part: 'body', to: { plane: 'mounting-plane' } })
|
|
63
|
-
return serializeFigureAsset(applyFigureAuthoring(a, { kind: '
|
|
63
|
+
return serializeFigureAsset(applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' }))
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
let seq = 0
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* the step the kernel refuses, and the gate findings the steps bring in, go back to the model; after three attempts
|
|
6
6
|
* the proposer says it could not make it, and nothing half made comes back.
|
|
7
7
|
*/
|
|
8
|
-
import { FIGURE_PROPOSAL_GRAMMAR, addFigureBox, applyFigureAuthoring, applyFigureProposal, createFigureAsset, inspectFigureAsset
|
|
8
|
+
import { FIGURE_PROPOSAL_GRAMMAR, addFigureBox, applyFigureAuthoring, applyFigureProposal, createFigureAsset, inspectFigureAsset } from '@hatiolab/figure-model'
|
|
9
9
|
import { AIAnswerNotJSON, type AIClient } from '@things-factory/ai-client-base'
|
|
10
10
|
|
|
11
11
|
import { ProposeFigureFailure, WORKED_EXAMPLE, judgeFigureAnswer, proposalFailureForPerson, proposeFigureSteps, rulesPromptFigure } from './figure-propose-steps'
|
|
@@ -38,10 +38,10 @@ const liftTable = [
|
|
|
38
38
|
{ kind: 'attach', part: 'base', to: { plane: 'mounting-plane' } },
|
|
39
39
|
{ kind: 'attach', part: 'column', to: { part: 'base', face: 'top' } },
|
|
40
40
|
{ kind: 'attach', part: 'top', to: { part: 'column', face: 'top' } },
|
|
41
|
-
{ kind: 'set-material', target: 'top', material: { token: 'palette.accent' }, palette: { 'palette.accent': '#d08a2c' } }
|
|
42
|
-
{ kind: 'fit-room', placement: 'floor' }
|
|
41
|
+
{ kind: 'set-material', target: 'top', material: { token: 'palette.accent' }, palette: { 'palette.accent': '#d08a2c' } }
|
|
43
42
|
]
|
|
44
|
-
|
|
43
|
+
/* The table's top named as the face it is mounted on: a finding the draft has, its face 740 mm off the plane. */
|
|
44
|
+
const offFace = [...liftTable, { kind: 'set-mounting-face', part: 'top', face: 'top' }]
|
|
45
45
|
const wrongFace = liftTable.map((s, i) => (i === 4 ? { ...s, face: 'left' } : s))
|
|
46
46
|
|
|
47
47
|
describe('the model is told the kernel\'s commands and the open draft', () => {
|
|
@@ -74,7 +74,6 @@ describe('the model is told the kernel\'s commands and the open draft', () => {
|
|
|
74
74
|
expect(judged.source!.appearance.map(a => a.target)).toEqual(['frame', 'support', 'roller'])
|
|
75
75
|
const kinds = WORKED_EXAMPLE.map(step => step.kind)
|
|
76
76
|
expect(kinds).toContain('make-repeat')
|
|
77
|
-
expect(kinds.at(-1)).toBe('fit-room')
|
|
78
77
|
expect(WORKED_EXAMPLE.filter(step => step.kind === 'attach' && 'plane' in (step as any).to).every(step => !('facing' in step))).toBe(true)
|
|
79
78
|
})
|
|
80
79
|
|
|
@@ -117,15 +116,9 @@ describe('the steps are held to the kernel and the gate', () => {
|
|
|
117
116
|
expect(said).toMatch(/SCHEMA: steps\[4\]: attach: column: face is a word attach does not take/)
|
|
118
117
|
})
|
|
119
118
|
|
|
120
|
-
it('a new figure must come out whole: a list that leaves the room undeclared is sent back', async () => {
|
|
121
|
-
const client = stubClient([{ steps: withoutRoom }, { steps: liftTable }])
|
|
122
|
-
const made = await proposeFigureSteps({ client, prompt: '리프트 테이블', name: 'Lift table' })
|
|
123
|
-
expect(made.attempts).toBe(2)
|
|
124
|
-
expect(client.sent[1]!.at(-1)!.content).toMatch(/OCCUPANCY_UNDECLARED/)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
119
|
it('a revision is not blamed for what the draft already had', async () => {
|
|
128
|
-
const draft = applyFigureProposal(createFigureAsset('Lift table'),
|
|
120
|
+
const draft = applyFigureProposal(createFigureAsset('Lift table'), offFace as any)
|
|
121
|
+
expect(inspectFigureAsset(draft).violations.map(v => v.code)).toEqual(['PARTS_OFF_PLACEMENT_FACE'])
|
|
129
122
|
const client = stubClient([{ steps: [{ kind: 'rename', name: 'Lift' }] }])
|
|
130
123
|
const made = await proposeFigureSteps({ client, prompt: '이름', base: draft })
|
|
131
124
|
expect(made.attempts).toBe(1)
|
|
@@ -227,28 +220,30 @@ describe('reference pictures go with the task', () => {
|
|
|
227
220
|
|
|
228
221
|
/*
|
|
229
222
|
A revision is not blamed for a finding the draft already had -- but the same finding made worse is the answer's.
|
|
230
|
-
Matching by code, part and axis alone excused a part pushed further out of the declared room
|
|
223
|
+
Matching by code, part and axis alone excused a part pushed further out of the declared room; the room is computed
|
|
224
|
+
now (ADR-0145), and a mounting face further off its plane is the same case.
|
|
231
225
|
*/
|
|
232
226
|
describe('a finding the draft had is excused only while it is no worse', () => {
|
|
233
227
|
const outside = (() => {
|
|
234
228
|
let asset = addFigureBox(createFigureAsset('Mixer'), { id: 'body', size: { x: 300, y: 400, z: 300 }, position: { x: 0, y: 200, z: 0 } })
|
|
235
|
-
asset = applyFigureAuthoring(asset, { kind: '
|
|
236
|
-
|
|
229
|
+
asset = applyFigureAuthoring(asset, { kind: 'set-placement', placement: 'floor' })
|
|
230
|
+
asset = addFigureBox(asset, { id: 'sign', size: { x: 100, y: 100, z: 40 }, position: { x: 300, y: 250, z: 0 } })
|
|
231
|
+
return applyFigureAuthoring(asset, { kind: 'set-mounting-face', part: 'sign', face: 'bottom' })
|
|
237
232
|
})()
|
|
238
233
|
|
|
239
234
|
it('the draft has the finding to begin with', () => {
|
|
240
|
-
expect(inspectFigureAsset(outside).violations.map(v => `${v.code} ${v.part} ${v.axis} ${v.mm}`)).toEqual(['
|
|
235
|
+
expect(inspectFigureAsset(outside).violations.map(v => `${v.code} ${v.part} ${v.axis} ${v.mm}`)).toEqual(['PARTS_OFF_PLACEMENT_FACE sign y 200'])
|
|
241
236
|
})
|
|
242
237
|
|
|
243
238
|
it('left as it was, or made smaller, it is excused', () => {
|
|
244
239
|
expect(judgeFigureAnswer(outside, { steps: [{ kind: 'rename', name: 'Mixer 2' }] }, true).reasons).toEqual([])
|
|
245
|
-
expect(judgeFigureAnswer(outside, { steps: [{ kind: 'nudge-part', part: 'sign', by: {
|
|
240
|
+
expect(judgeFigureAnswer(outside, { steps: [{ kind: 'nudge-part', part: 'sign', by: { y: -20 } }] }, true).reasons).toEqual([])
|
|
246
241
|
})
|
|
247
242
|
|
|
248
243
|
it('pushed further out, it is sent back as the answer\'s', () => {
|
|
249
|
-
const judged = judgeFigureAnswer(outside, { steps: [{ kind: 'nudge-part', part: 'sign', by: {
|
|
244
|
+
const judged = judgeFigureAnswer(outside, { steps: [{ kind: 'nudge-part', part: 'sign', by: { y: 100 } }] }, true)
|
|
250
245
|
expect(judged.source).toBeUndefined()
|
|
251
|
-
expect(judged.reasons).toEqual([expect.stringMatching(/^
|
|
246
|
+
expect(judged.reasons).toEqual([expect.stringMatching(/^PARTS_OFF_PLACEMENT_FACE \(sign\): .*worse than the 200 mm the figure already had/)])
|
|
252
247
|
})
|
|
253
248
|
})
|
|
254
249
|
|
|
@@ -92,8 +92,7 @@ export const WORKED_EXAMPLE: FigureProposalStep[] = [
|
|
|
92
92
|
{ kind: 'add-part', part: { id: 'roller', kind: 'box', dimensions: { width: 60, height: 60, depth: 560 } } },
|
|
93
93
|
{ kind: 'attach', part: 'roller', to: { part: 'frame', face: 'top' } },
|
|
94
94
|
{ kind: 'make-repeat', part: 'roller', axis: 'x', pitch: 160 },
|
|
95
|
-
{ kind: 'set-material', target: 'frame', material: { token: 'palette.frame' }, palette: { 'palette.frame': '#5b6770' } }
|
|
96
|
-
{ kind: 'fit-room', placement: 'floor' }
|
|
95
|
+
{ kind: 'set-material', target: 'frame', material: { token: 'palette.frame' }, palette: { 'palette.frame': '#5b6770' } }
|
|
97
96
|
] as FigureProposalStep[]
|
|
98
97
|
|
|
99
98
|
/** The rules and the words, all from figure-model's constants. */
|
|
@@ -114,8 +113,7 @@ export function rulesPromptFigure(): string {
|
|
|
114
113
|
'{ kind: "set-material", target, material: { token }, palette: { [token]: "#rrggbb" } }.',
|
|
115
114
|
'When the request says a measure is the same as, matches or follows another part or the figure, write link-dimension:',
|
|
116
115
|
'a copied number stays the same when the other one is resized.',
|
|
117
|
-
'Declare the sizes the figure may be placed at before any measure follows the figure.
|
|
118
|
-
'their fastenings or motions changed.',
|
|
116
|
+
'Declare the sizes the figure may be placed at before any measure follows the figure.',
|
|
119
117
|
'When revising, write only the commands the request needs. Everything not touched stays as it is.',
|
|
120
118
|
'',
|
|
121
119
|
'The commands (shape, then what it means):',
|
|
@@ -216,7 +214,7 @@ function partsAtRefusedStep(base: FigureAsset, steps: FigureProposalStep[], erro
|
|
|
216
214
|
/**
|
|
217
215
|
* Applies an answer on a copy of the base and returns the figure, or the reasons it was not accepted: the step the
|
|
218
216
|
* kernel refused, or the gate findings the steps brought in. When revising, a finding the draft already had is not the
|
|
219
|
-
* answer's; a new figure has none to excuse, so it must come out whole
|
|
217
|
+
* answer's; a new figure has none to excuse, so it must come out whole.
|
|
220
218
|
*/
|
|
221
219
|
export function judgeFigureAnswer(
|
|
222
220
|
base: FigureAsset,
|
|
@@ -12,7 +12,7 @@ const shell = require('@things-factory/shell')
|
|
|
12
12
|
const authBase = require('@things-factory/auth-base')
|
|
13
13
|
const settingBase = require('@things-factory/setting-base')
|
|
14
14
|
const attachmentBase = require('@things-factory/attachment-base')
|
|
15
|
-
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring,
|
|
15
|
+
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, readFigurePackage } = require('@hatiolab/figure-model')
|
|
16
16
|
|
|
17
17
|
let dataSource: DataSource | null = null
|
|
18
18
|
let Figure: any
|
|
@@ -65,7 +65,7 @@ afterAll(async () => {
|
|
|
65
65
|
const releasable = (label = 'Mixer') => {
|
|
66
66
|
let a = addFigureBox(createFigureAsset(label), { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
67
67
|
a = applyFigureAuthoring(a, { kind: 'attach', part: 'body', to: { plane: 'mounting-plane' } })
|
|
68
|
-
return serializeFigureAsset(applyFigureAuthoring(a, { kind: '
|
|
68
|
+
return serializeFigureAsset(applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' }))
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
let seq = 0
|
|
@@ -442,8 +442,9 @@ async function releaseNumbered(figure: Figure, version: number): Promise<{ figur
|
|
|
442
442
|
/**
|
|
443
443
|
* Finds the released source of a type in one domain, or null.
|
|
444
444
|
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
445
|
+
* Always the latest release's own copy, from history: it carries the size range written down at release (ADR-0087
|
|
446
|
+
* decision 6), and the figure's row -- released or a draft -- keeps only the ends its author narrowed. A figure that
|
|
447
|
+
* was never released gives null, because drafts never reach consumers.
|
|
447
448
|
*/
|
|
448
449
|
async function releasedSourceOf(
|
|
449
450
|
domainId: string,
|
|
@@ -452,10 +453,6 @@ async function releasedSourceOf(
|
|
|
452
453
|
const figure = await getRepository(Figure).findOne({ where: { domain: { id: domainId }, type } })
|
|
453
454
|
if (!figure) return null
|
|
454
455
|
|
|
455
|
-
if (figure.state === 'released' && figure.source) {
|
|
456
|
-
return { figure, source: figure.source, version: figure.version }
|
|
457
|
-
}
|
|
458
|
-
|
|
459
456
|
const latestReleased = await getRepository(FigureHistory).findOne({
|
|
460
457
|
where: { domain: { id: domainId }, originalId: figure.id, state: 'released' },
|
|
461
458
|
order: { version: 'DESC' }
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* reader's words, and their findings join the gate's -- a blocking one blocks, a said one does not; a check that
|
|
4
4
|
* throws blocks by name rather than being skipped; one taken off is gone.
|
|
5
5
|
*/
|
|
6
|
-
import { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring
|
|
6
|
+
import { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring } from '@hatiolab/figure-model'
|
|
7
7
|
|
|
8
8
|
import { inspectSource, registerFigureReleaseCheck } from './figure-format'
|
|
9
9
|
|
|
10
10
|
const source = () => {
|
|
11
11
|
let asset = addFigureBox(createFigureAsset('Mixer'), { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
12
12
|
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'body', to: { plane: 'mounting-plane' } })
|
|
13
|
-
return serializeFigureAsset(applyFigureAuthoring(asset, { kind: '
|
|
13
|
+
return serializeFigureAsset(applyFigureAuthoring(asset, { kind: 'set-placement', placement: 'floor' }))
|
|
14
14
|
}
|
|
15
15
|
const codes = (inspection: { findings: { code: string; blocking: boolean }[] }) => inspection.findings.map(f => [f.code, f.blocking])
|
|
16
16
|
|
|
@@ -8,7 +8,7 @@ const { DataSource: TypeOrmDataSource } = require('typeorm')
|
|
|
8
8
|
const shell = require('@things-factory/shell')
|
|
9
9
|
const authBase = require('@things-factory/auth-base')
|
|
10
10
|
const settingBase = require('@things-factory/setting-base')
|
|
11
|
-
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring,
|
|
11
|
+
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, palletizerRobotFigure, FIGURE_KERNEL_VERSION } = require('@hatiolab/figure-model')
|
|
12
12
|
|
|
13
13
|
let FigureNamespace: any
|
|
14
14
|
let dataSource: DataSource | null = null
|
|
@@ -69,7 +69,7 @@ async function release(extra: Record<string, unknown>) {
|
|
|
69
69
|
|
|
70
70
|
describe('a release record on its history row', () => {
|
|
71
71
|
it('keeps the format and the changes as they were', async () => {
|
|
72
|
-
const changes = [{ code: '
|
|
72
|
+
const changes = [{ code: 'size-changed', blocking: false, values: { before: 'x -50…50', after: 'x -150…150' }, detail: 'size changed' }]
|
|
73
73
|
const row = await release({ __releaseFormat: 'figure-asset-1', __releaseChanges: changes })
|
|
74
74
|
expect(row.assetFormat).toBe('figure-asset-1')
|
|
75
75
|
expect(JSON.parse(row.changes)).toEqual(changes)
|
|
@@ -90,9 +90,9 @@ describe('releaseFigure records it', () => {
|
|
|
90
90
|
const releasable = (width: number) => {
|
|
91
91
|
let a = addFigureBox(createFigureAsset('Mixer'), { id: 'body', size: { x: width, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
92
92
|
a = applyFigureAuthoring(a, { kind: 'attach', part: 'body', to: { plane: 'mounting-plane' } })
|
|
93
|
-
return serializeFigureAsset(applyFigureAuthoring(a, { kind: '
|
|
93
|
+
return serializeFigureAsset(applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' }))
|
|
94
94
|
}
|
|
95
|
-
it('first release: [], the second: the wider
|
|
95
|
+
it('first release: [], the second: the wider body', async () => {
|
|
96
96
|
const domain = await namespaced(await dataSource!.getRepository(shell.Domain).save({ name: 'release-door', subdomain: 'release-door' }))
|
|
97
97
|
const tx = dataSource!.manager
|
|
98
98
|
const context = { state: { domain, user: undefined, tx } }
|
|
@@ -106,9 +106,10 @@ describe('releaseFigure records it', () => {
|
|
|
106
106
|
/* Each release names the kernel that measured it (ADR-0109 C1). */
|
|
107
107
|
expect(rows.map((r: any) => r.kernelVersion)).toEqual([FIGURE_KERNEL_VERSION, FIGURE_KERNEL_VERSION])
|
|
108
108
|
const codes = JSON.parse(rows[1].changes).map((c: any) => c.code)
|
|
109
|
-
expect(codes).
|
|
109
|
+
expect(codes).toEqual(expect.arrayContaining(['size-changed', 'motion-range-changed']))
|
|
110
110
|
/* What each release measured, and its plan from above, are on its own row. */
|
|
111
|
-
|
|
111
|
+
const box = { x: { min: -50, max: 50 }, y: { min: 0, max: 100 }, z: { min: -50, max: 50 } }
|
|
112
|
+
expect(JSON.parse(rows[0].metrics)).toEqual({ body: box, motionRange: box })
|
|
112
113
|
expect(JSON.parse(rows[1].topView).shapes.map((s: any) => s.id)).toEqual(['body'])
|
|
113
114
|
})
|
|
114
115
|
})
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size range a board reads is the release's own copy (ADR-0087 decision 10, user 2026-10-21). The figure's row keeps
|
|
3
|
+
* only the ends its author narrowed; a release writes the rest down as far as the parts allow, on its history row. Every
|
|
4
|
+
* door a release leaves by -- one figure for a board, several by full name, the package another server imports -- hands
|
|
5
|
+
* out that copy, never the row. A release made a draft again opens what the release wrote down.
|
|
6
|
+
*/
|
|
7
|
+
import type { DataSource } from 'typeorm'
|
|
8
|
+
|
|
9
|
+
const { DataSource: TypeOrmDataSource } = require('typeorm')
|
|
10
|
+
const { createHash } = require('crypto')
|
|
11
|
+
const JSZip = require('jszip')
|
|
12
|
+
const shell = require('@things-factory/shell')
|
|
13
|
+
const authBase = require('@things-factory/auth-base')
|
|
14
|
+
const settingBase = require('@things-factory/setting-base')
|
|
15
|
+
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, readFigurePackage } = require('@hatiolab/figure-model')
|
|
16
|
+
|
|
17
|
+
let dataSource: DataSource | null = null
|
|
18
|
+
let Figure: any
|
|
19
|
+
let FigureMutation: any
|
|
20
|
+
let FigureQuery: any
|
|
21
|
+
let figurePackageOf: any
|
|
22
|
+
let revertKeepingDraft: any
|
|
23
|
+
let FigureHistory: any
|
|
24
|
+
let FigureNamespace: any
|
|
25
|
+
|
|
26
|
+
function loadCompiled(modulePath: string): any {
|
|
27
|
+
const { existsSync } = require('fs')
|
|
28
|
+
const { join } = require('path')
|
|
29
|
+
const resolved = join(__dirname, '..', '..', '..', 'dist-server', `${modulePath}.js`)
|
|
30
|
+
if (!existsSync(resolved))
|
|
31
|
+
throw new Error(`figure-service 산출물이 없다: ${resolved}\n먼저 빌드할 것:\n yarn workspace @things-factory/figure-service build:server`)
|
|
32
|
+
return require(resolved)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
Figure = loadCompiled('service/figure/figure').Figure
|
|
37
|
+
FigureHistory = loadCompiled('service/figure/figure-history').FigureHistory
|
|
38
|
+
FigureNamespace = loadCompiled('service/figure/figure-namespace-entity').FigureNamespace
|
|
39
|
+
const FigureReplacedDraft = loadCompiled('service/figure/figure-replaced-draft').FigureReplacedDraft
|
|
40
|
+
const Subscriber = loadCompiled('service/figure/event-subscriber').FigureHistoryEntitySubscriber
|
|
41
|
+
;({ FigureMutation } = loadCompiled('service/figure/figure-mutation'))
|
|
42
|
+
;({ FigureQuery } = loadCompiled('service/figure/figure-query'))
|
|
43
|
+
;({ figurePackageOf } = loadCompiled('service/figure/figure-package-export'))
|
|
44
|
+
;({ revertKeepingDraft } = loadCompiled('service/figure/figure-revert'))
|
|
45
|
+
dataSource = new TypeOrmDataSource({
|
|
46
|
+
type: 'sqlite',
|
|
47
|
+
database: ':memory:',
|
|
48
|
+
synchronize: true,
|
|
49
|
+
dropSchema: true,
|
|
50
|
+
namingStrategy: new shell.NamingStrategy(),
|
|
51
|
+
entities: [...shell.entities, ...authBase.entities, ...settingBase.entities, Figure, FigureHistory, FigureNamespace, FigureReplacedDraft],
|
|
52
|
+
subscribers: [Subscriber],
|
|
53
|
+
logging: false
|
|
54
|
+
})
|
|
55
|
+
await dataSource.initialize()
|
|
56
|
+
shell.addDataSource('default', dataSource)
|
|
57
|
+
shell.addDataSource('tx', dataSource)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
afterAll(async () => {
|
|
61
|
+
await dataSource?.destroy()
|
|
62
|
+
dataSource = null
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
The user's figure (2026-10-21): a box that follows the figure's size, with a cavity ticked on it. Below twice its wall it
|
|
67
|
+
has no inside, so its width has a smallest size its parts set; nothing limits how large it grows. The height is
|
|
68
|
+
narrowed by the author, 50 to 400 mm.
|
|
69
|
+
*/
|
|
70
|
+
const buttonBox = () => {
|
|
71
|
+
let a = addFigureBox(createFigureAsset('Button box'), { id: 'box', size: { x: 120, y: 60, z: 80 }, position: { x: 0, y: 30, z: 0 } })
|
|
72
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'box', to: { plane: 'mounting-plane' } })
|
|
73
|
+
a = applyFigureAuthoring(a, { kind: 'follow-size', part: 'box' })
|
|
74
|
+
a = applyFigureAuthoring(a, { kind: 'set-cavity', part: 'box', cavity: { wall: 10, start: { closed: 10 }, end: 'open' } })
|
|
75
|
+
a = applyFigureAuthoring(a, { kind: 'declare-instance-size', range: { y: { min: 50, max: 400 } }, replace: true })
|
|
76
|
+
return serializeFigureAsset(applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' }))
|
|
77
|
+
}
|
|
78
|
+
const sizeOf = (source: string | object, axis: 'x' | 'y' | 'z') => {
|
|
79
|
+
const asset = typeof source === 'string' ? JSON.parse(source) : source
|
|
80
|
+
const { min, max } = asset.document.model.inputs.find((i: any) => i.id === `size.${axis}`)
|
|
81
|
+
return { min, max }
|
|
82
|
+
}
|
|
83
|
+
/** The width's smallest size as the parts set it: twice the wall, and the kernel's numerical step above it. */
|
|
84
|
+
const widthFloor = (min: number | undefined) => typeof min === 'number' && min > 20 && min < 20.001
|
|
85
|
+
|
|
86
|
+
let seq = 0
|
|
87
|
+
async function domainWith(namespace: string) {
|
|
88
|
+
const name = `size-${++seq}`
|
|
89
|
+
const domain = await dataSource!.getRepository(shell.Domain).save({ name, subdomain: name })
|
|
90
|
+
await dataSource!.getRepository(FigureNamespace).save({ domain, name: namespace, state: 'active', isDefault: true })
|
|
91
|
+
return { domain, context: { state: { domain, user: undefined, tx: dataSource!.manager } } }
|
|
92
|
+
}
|
|
93
|
+
const creationId = () => require('crypto').randomUUID()
|
|
94
|
+
|
|
95
|
+
async function released(type: string) {
|
|
96
|
+
const ns = `size-${++seq}.hatiolab.com`
|
|
97
|
+
const { domain, context } = await domainWith(ns)
|
|
98
|
+
const saved = await new FigureMutation().saveFigure(buttonBox(), context, undefined, undefined, creationId(), undefined, type, undefined, undefined)
|
|
99
|
+
await new FigureMutation().releaseFigure(saved.id, 'first', context)
|
|
100
|
+
const figure = await dataSource!.getRepository(Figure).findOneOrFail({ where: { id: saved.id } })
|
|
101
|
+
const history = await dataSource!.getRepository(FigureHistory).findOneOrFail({ where: { originalId: saved.id, version: 1 } })
|
|
102
|
+
return { ns, domain, context, figure, history }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe('the size range a release carries', () => {
|
|
106
|
+
it('is written on the release, and the figure keeps only what its author narrowed', async () => {
|
|
107
|
+
const { figure, history } = await released('button-box')
|
|
108
|
+
expect(figure.state).toBe('released')
|
|
109
|
+
expect(sizeOf(figure.source, 'x')).toEqual({ min: undefined, max: undefined })
|
|
110
|
+
expect(sizeOf(figure.source, 'y')).toEqual({ min: 50, max: 400 })
|
|
111
|
+
const x = sizeOf(history.source, 'x')
|
|
112
|
+
expect(widthFloor(x.min)).toBe(true)
|
|
113
|
+
expect(x.max).toBeUndefined()
|
|
114
|
+
expect(sizeOf(history.source, 'y')).toEqual({ min: 50, max: 400 })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('is what every door a release leaves by hands out: one for a board, several by full name, a package', async () => {
|
|
118
|
+
const { ns, domain, context, history } = await released('button-box')
|
|
119
|
+
const one = await new FigureQuery().figureBlueprint('button-box', context)
|
|
120
|
+
expect(widthFloor(sizeOf(one.asset, 'x').min)).toBe(true)
|
|
121
|
+
const pinned = await new FigureQuery().figureBlueprint('button-box', context, 1)
|
|
122
|
+
expect(widthFloor(sizeOf(pinned.asset, 'x').min)).toBe(true)
|
|
123
|
+
const several = await new FigureQuery().figureBlueprints([`${ns}/button-box`], context)
|
|
124
|
+
expect(widthFloor(sizeOf(several.items[0].asset, 'x').min)).toBe(true)
|
|
125
|
+
|
|
126
|
+
const { bytes } = await figurePackageOf({
|
|
127
|
+
refs: [`${ns}/button-box@1`],
|
|
128
|
+
domain,
|
|
129
|
+
exportedBy: 'author',
|
|
130
|
+
sourceUrl: 'https://figure.hatiolab.com',
|
|
131
|
+
files: { put: async () => ({}), send: async () => {}, read: async () => Buffer.alloc(0) }
|
|
132
|
+
})
|
|
133
|
+
const zip = await JSZip.loadAsync(bytes)
|
|
134
|
+
const files = new Map<string, Uint8Array>()
|
|
135
|
+
for (const path of Object.keys(zip.files)) if (!zip.files[path].dir) files.set(path, await zip.files[path].async('uint8array'))
|
|
136
|
+
const read = readFigurePackage(files, (b: Uint8Array) => createHash('sha256').update(b).digest('hex'))
|
|
137
|
+
expect(read.releases[0].source).toBe(history.source)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('released again unchanged, nothing is said to have narrowed', async () => {
|
|
141
|
+
const { figure, context } = await released('button-box')
|
|
142
|
+
await dataSource!.getRepository(Figure).save({ ...figure, state: 'draft' })
|
|
143
|
+
await new FigureMutation().releaseFigure(figure.id, 'again', context)
|
|
144
|
+
const second = await dataSource!.getRepository(FigureHistory).findOneOrFail({ where: { originalId: figure.id, version: 2 } })
|
|
145
|
+
expect(JSON.parse(second.changes)).toEqual([])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('made a draft again by a revert, what the release wrote down is opened and the author keeps theirs', async () => {
|
|
149
|
+
const { figure, domain } = await released('button-box')
|
|
150
|
+
const draft = await dataSource!.getRepository(Figure).save({ ...figure, state: 'draft' })
|
|
151
|
+
await dataSource!.transaction(tx => revertKeepingDraft(tx, domain, undefined, { id: figure.id, version: 1, requestId: creationId(), expectedSource: draft.source }))
|
|
152
|
+
const back = await dataSource!.getRepository(Figure).findOneOrFail({ where: { id: figure.id } })
|
|
153
|
+
expect(sizeOf(back.source, 'x')).toEqual({ min: undefined, max: undefined })
|
|
154
|
+
expect(sizeOf(back.source, 'y')).toEqual({ min: 50, max: 400 })
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -5,7 +5,7 @@ import { GraphQLError } from 'graphql'
|
|
|
5
5
|
import { Figure } from './figure.js'
|
|
6
6
|
import { FigureHistory } from './figure-history.js'
|
|
7
7
|
import { FigureReplacedDraft } from './figure-replaced-draft.js'
|
|
8
|
-
import { parseSource } from './figure-format.js'
|
|
8
|
+
import { draftSourceOfRelease, parseSource } from './figure-format.js'
|
|
9
9
|
import { sourceConflict } from './figure-write.js'
|
|
10
10
|
|
|
11
11
|
/*
|
|
@@ -77,7 +77,7 @@ export async function revertKeepingDraft(
|
|
|
77
77
|
})
|
|
78
78
|
if (!history) throw refused('FIGURE_VERSION_NOT_FOUND', `Figure '${figure.type}' 에 ${request.version} 판이 없습니다.`)
|
|
79
79
|
/* The format is checked again: an old version may break today's rules, and is not let in quietly. */
|
|
80
|
-
const { source, derived, violations } = parseSource(history.source)
|
|
80
|
+
const { source, derived, violations } = parseSource(draftSourceOfRelease(history.source!))
|
|
81
81
|
|
|
82
82
|
const record = await keep(kept, {
|
|
83
83
|
domain: { id: domain.id },
|
|
@@ -42,11 +42,11 @@ function stubRepository(rows: any[]) {
|
|
|
42
42
|
const MINE = { id: 'domain-mine' }
|
|
43
43
|
const THEIRS = { id: 'domain-theirs' }
|
|
44
44
|
|
|
45
|
-
/** A stored figure: one body on the floor
|
|
45
|
+
/** A stored figure: one body on the floor. */
|
|
46
46
|
const SOURCE = (() => {
|
|
47
47
|
const model = require('@hatiolab/figure-model')
|
|
48
48
|
let asset = model.addFigureBox(model.createFigureAsset('Mixer'), { id: 'body', size: { x: 300, y: 400, z: 300 }, position: { x: 0, y: 200, z: 0 } })
|
|
49
|
-
asset = model.applyFigureAuthoring(asset, { kind: '
|
|
49
|
+
asset = model.applyFigureAuthoring(asset, { kind: 'set-placement', placement: 'floor' })
|
|
50
50
|
return asset
|
|
51
51
|
})()
|
|
52
52
|
|
|
@@ -176,7 +176,7 @@ export class FigureRevertResult {
|
|
|
176
176
|
*/
|
|
177
177
|
@ObjectType({ description: 'One finding from the release gate.' })
|
|
178
178
|
export class FigureGateFinding {
|
|
179
|
-
@Field({ description: 'Stable code of the gate finding, for example
|
|
179
|
+
@Field({ description: 'Stable code of the gate finding, for example parts-off-placement-face or parts-split-on-resize.' })
|
|
180
180
|
code: string
|
|
181
181
|
|
|
182
182
|
@Field({ description: 'Human-readable message.' })
|
package/translations/en.json
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"error.figure-proposal-failed": "The figure could not be made.",
|
|
3
3
|
"error.figure-release-does-not-compile": "the released source of {type} (version {version}) does not compile. open it in the modeller, fix it and release it again before it draws on a board.",
|
|
4
|
-
"figure.gate.occupancy-empty-axis.message": "The space the figure takes is empty on {axis}.",
|
|
5
|
-
"figure.gate.occupancy-undeclared.how": "Use \"fit to parts\" to set the space the figure takes from the parts as they are now.",
|
|
6
|
-
"figure.gate.occupancy-undeclared.how-no-parts": "Add a shape first, then use \"fit to parts\" to set the space the figure takes from it.",
|
|
7
|
-
"figure.gate.occupancy-undeclared.message": "The space this figure takes is not set yet.",
|
|
8
|
-
"figure.gate.occupancy-undeclared.why": "The space a figure takes sets the scale it is placed at on a board.",
|
|
9
|
-
"figure.gate.part-outside-occupancy.how": "Use \"fit to parts\" to fit the space the figure takes to the parts again, or move '{part}' {mm} mm inward on {axis}.",
|
|
10
|
-
"figure.gate.part-outside-occupancy.message": "'{part}' goes {mm} mm past the space the figure takes on {axis}.",
|
|
11
|
-
"figure.gate.part-outside-occupancy.why": "The space the figure takes sets the scale it is placed at. What lies past it is left out of that scale, so on a board this part lands outside the place given to the figure.",
|
|
12
4
|
"figure.gate.parts-off-placement-face.how": "Move '{part}' {mm} mm on Y so the face meets the plane, or choose a different mounting face.",
|
|
13
5
|
"figure.gate.parts-off-placement-face.message": "The mounting face of '{part}' is {mm} mm off the plane the figure stands on.",
|
|
14
6
|
"figure.gate.parts-off-placement-face.why": "The figure is declared to stand on that face. If the face does not reach the plane, the figure floats on the board by that much, and whatever is placed on it floats too.",
|
|
@@ -21,9 +13,6 @@
|
|
|
21
13
|
"figure.gate.placement-face-tilted.how": "Choose a mounting face parallel to the plane, or turn '{part}' upright.",
|
|
22
14
|
"figure.gate.placement-face-tilted.message": "The mounting face of '{part}' is tilted {degrees}° from the plane the figure stands on.",
|
|
23
15
|
"figure.gate.placement-face-tilted.why": "A tilted face cannot lie on the plane, so how the figure stands is not settled.",
|
|
24
|
-
"figure.gate.state-range-sampled.how": "To cover the whole range, re-author what could not be proven.",
|
|
25
|
-
"figure.gate.state-range-sampled.message": "The whole range of sizes and motion could not be proven, so {states} combinations of size and pose were checked.",
|
|
26
|
-
"figure.gate.state-range-sampled.why": "Between the combinations that were checked, a part may still go past the space the figure takes.",
|
|
27
16
|
"figure.gate.type-is-placeholder.how": "Set the type in the type field, then publish.",
|
|
28
17
|
"figure.gate.type-is-placeholder.message": "'{type}' is a temporary type given when the figure was first saved.",
|
|
29
18
|
"figure.gate.type-is-placeholder.why": "Boards call a figure by its type, and it cannot change once the figure is published.",
|
|
@@ -66,8 +55,10 @@
|
|
|
66
55
|
"figure.gate.contract.capability-gone.why": "Scenes attach behaviour by this name. Placed instances lose what it did.",
|
|
67
56
|
"figure.gate.contract.capability-added.message": "The capability '{name}' is new.",
|
|
68
57
|
"figure.gate.contract.capability-added.why": "It adds what the figure can do. Placed instances stand as they are.",
|
|
69
|
-
"figure.gate.contract.
|
|
70
|
-
"figure.gate.contract.
|
|
58
|
+
"figure.gate.contract.size-changed.message": "The figure's size changed: {before} → {after}.",
|
|
59
|
+
"figure.gate.contract.size-changed.why": "A board gives the figure a box of this size. One that moves to this release sees its box change.",
|
|
60
|
+
"figure.gate.contract.motion-range-changed.message": "The room the figure sweeps as it moves changed: {before} → {after}.",
|
|
61
|
+
"figure.gate.contract.motion-range-changed.why": "It is what the figure reaches when it moves. One that moves to this release sees it reach differently.",
|
|
71
62
|
"figure.gate.contract.contact-face-changed.message": "The face the figure is mounted by changed.",
|
|
72
63
|
"figure.gate.contract.contact-face-changed.why": "A consumer that moves to this release sees it stand on the new face.",
|
|
73
64
|
"figure.gate.contract.public-input-gone.message": "'{input}' is no longer set on a board.",
|
|
@@ -94,9 +85,6 @@
|
|
|
94
85
|
"figure.gate.contract.joint-list-unread.message": "The last release's joint list could not be read, so its joints were not compared: {reason}",
|
|
95
86
|
"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.",
|
|
96
87
|
"error.figure-release-not-found": "{type} has no release {version}. its latest release is {latest}.",
|
|
97
|
-
"figure.gate.origin-off-centre.message": "The origin is off the middle of the declared room: {off}.",
|
|
98
|
-
"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.",
|
|
99
|
-
"figure.gate.origin-off-centre.how": "Keep it when the figure is fastened there, as a door at its hinge. Move the origin to the middle to place it by its room.",
|
|
100
88
|
"figure.gate.off-mounting-plane.floor.clear.message": "'{part}' is {mm} mm above the floor.",
|
|
101
89
|
"figure.gate.off-mounting-plane.floor.through.message": "'{part}' goes {mm} mm below the floor.",
|
|
102
90
|
"figure.gate.off-mounting-plane.floor.beyond.message": "Every part is below the floor.",
|
package/translations/es.json
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"error.figure-proposal-failed": "No se pudo crear la figura.",
|
|
3
3
|
"error.figure-release-does-not-compile": "el código fuente publicado de {type} (versión {version}) no compila. Ábralo en el modelador, corríjalo y vuelva a publicarlo para que se dibuje en un tablero.",
|
|
4
|
-
"figure.gate.occupancy-empty-axis.message": "El espacio declarado está vacío en {axis}.",
|
|
5
|
-
"figure.gate.occupancy-undeclared.how": "Use «ajustar a las piezas» para declarar el espacio a partir de las piezas tal como están ahora.",
|
|
6
|
-
"figure.gate.occupancy-undeclared.how-no-parts": "Añada primero una forma y luego use «ajustar a las piezas» para declarar el espacio a partir de ella.",
|
|
7
|
-
"figure.gate.occupancy-undeclared.message": "Esta figura no ha declarado el espacio que ocupa.",
|
|
8
|
-
"figure.gate.occupancy-undeclared.why": "Sin un espacio declarado no se puede fijar la escala con la que se coloca una figura.",
|
|
9
|
-
"figure.gate.part-outside-occupancy.how": "Use «ajustar a las piezas» para volver a declarar el espacio a partir de las piezas, o mueva '{part}' {mm} mm hacia dentro en {axis}.",
|
|
10
|
-
"figure.gate.part-outside-occupancy.message": "'{part}' sobrepasa {mm} mm el espacio declarado en {axis}.",
|
|
11
|
-
"figure.gate.part-outside-occupancy.why": "El espacio declarado fija la escala con la que se coloca una figura. Lo que queda fuera no entra en esa escala, así que en un tablero esta pieza queda fuera del lugar asignado a la figura.",
|
|
12
4
|
"figure.gate.parts-off-placement-face.how": "Mueva '{part}' {mm} mm en Y para que la cara toque el plano, o elija otra cara de montaje.",
|
|
13
5
|
"figure.gate.parts-off-placement-face.message": "La cara de montaje de '{part}' está a {mm} mm del plano sobre el que se apoya la figura.",
|
|
14
6
|
"figure.gate.parts-off-placement-face.why": "La figura está declarada para apoyarse sobre esa cara. Si la cara no llega al plano, la figura flota esa distancia en el tablero, y lo que se coloque sobre ella también flota.",
|
|
@@ -21,9 +13,6 @@
|
|
|
21
13
|
"figure.gate.placement-face-tilted.how": "Elija una cara de montaje paralela al plano o ponga '{part}' en posición vertical.",
|
|
22
14
|
"figure.gate.placement-face-tilted.message": "La cara de montaje de '{part}' está inclinada {degrees}° respecto al plano sobre el que se apoya la figura.",
|
|
23
15
|
"figure.gate.placement-face-tilted.why": "Una cara inclinada no puede apoyarse sobre el plano, por lo que no queda definida la postura de la figura.",
|
|
24
|
-
"figure.gate.state-range-sampled.how": "Para cubrir todo el rango, vuelva a diseñar lo que no se pudo demostrar.",
|
|
25
|
-
"figure.gate.state-range-sampled.message": "No se pudo demostrar todo el rango de tamaños y movimientos, así que se comprobaron {states} combinaciones de tamaño y postura.",
|
|
26
|
-
"figure.gate.state-range-sampled.why": "Entre las combinaciones comprobadas, una pieza aún podría sobrepasar el espacio declarado.",
|
|
27
16
|
"figure.gate.type-is-placeholder.how": "Defina el tipo en el campo de tipo y luego publique.",
|
|
28
17
|
"figure.gate.type-is-placeholder.message": "'{type}' es un tipo provisional asignado cuando se guardó la figura por primera vez.",
|
|
29
18
|
"figure.gate.type-is-placeholder.why": "Los tableros llaman a una figura por su tipo, y no se puede cambiar una vez publicada la figura.",
|
|
@@ -66,8 +55,10 @@
|
|
|
66
55
|
"figure.gate.contract.capability-gone.why": "Las escenas asocian comportamientos mediante este nombre. Las instancias colocadas pierden lo que hacía.",
|
|
67
56
|
"figure.gate.contract.capability-added.message": "La capacidad '{name}' es nuevo.",
|
|
68
57
|
"figure.gate.contract.capability-added.why": "Amplía lo que puede hacer la figura. Las instancias colocadas se quedan como están.",
|
|
69
|
-
"figure.gate.contract.
|
|
70
|
-
"figure.gate.contract.
|
|
58
|
+
"figure.gate.contract.size-changed.message": "El tamaño de la figura cambió: {before} → {after}.",
|
|
59
|
+
"figure.gate.contract.size-changed.why": "Un tablero le da a la figura una caja de este tamaño. Al pasar a esta versión, su caja cambia.",
|
|
60
|
+
"figure.gate.contract.motion-range-changed.message": "El espacio que la figura recorre al moverse cambió: {before} → {after}.",
|
|
61
|
+
"figure.gate.contract.motion-range-changed.why": "Es lo que la figura alcanza al moverse. Al pasar a esta versión, alcanza otro espacio.",
|
|
71
62
|
"figure.gate.contract.contact-face-changed.message": "Cambió la cara por la que se monta la figura.",
|
|
72
63
|
"figure.gate.contract.contact-face-changed.why": "Quien pase a esta publicación verá que se apoya sobre la nueva cara.",
|
|
73
64
|
"figure.gate.contract.public-input-gone.message": "'{input}' ya no se define en un tablero.",
|
|
@@ -94,9 +85,6 @@
|
|
|
94
85
|
"figure.gate.contract.joint-list-unread.message": "No se pudo leer la lista de articulaciones de la última publicación, por lo que no se compararon sus articulaciones: {reason}",
|
|
95
86
|
"figure.gate.contract.joint-list-unread.why": "No se ha comprobado si esta publicación cambia lo que mueven los valores de un controlador. La última publicación se generó con un núcleo que no medía las articulaciones.",
|
|
96
87
|
"error.figure-release-not-found": "{type} no tiene la publicación {version}. Su última publicación es {latest}.",
|
|
97
|
-
"figure.gate.origin-off-centre.message": "El origen está desplazado del centro del espacio declarado: {off}.",
|
|
98
|
-
"figure.gate.origin-off-centre.why": "Un plano que coloca las figuras por el centro de su espacio sitúa esta a esa distancia de donde está dibujada.",
|
|
99
|
-
"figure.gate.origin-off-centre.how": "Manténgalo si la figura se fija en ese punto, como una puerta por su bisagra. Mueva el origen al centro para colocarla por su espacio.",
|
|
100
88
|
"figure.gate.off-mounting-plane.floor.clear.message": "'{part}' está {mm} mm por encima del suelo.",
|
|
101
89
|
"figure.gate.off-mounting-plane.floor.through.message": "'{part}' baja {mm} mm por debajo del suelo.",
|
|
102
90
|
"figure.gate.off-mounting-plane.floor.beyond.message": "Todas las piezas están por debajo del suelo.",
|