@things-factory/figure-ui 10.1.37 → 10.1.39
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/client/graphql/index.ts +53 -0
- package/client/graphql/v3-store.ts +8 -2
- package/client/modeller/figure-history-panel.ts +33 -87
- package/client/modeller/figure-v3-parts.ts +365 -0
- package/client/modeller/figure-v3-verdict.ts +247 -0
- package/client/modeller/gate-findings.ts +105 -0
- package/client/modeller/v3-editor.ts +11 -2
- package/client/modeller/v3-occupancy.ts +22 -0
- package/client/modeller/v3-product-session.ts +23 -4
- package/client/modeller/v3-renderer.ts +27 -18
- package/client/modeller/v3-thumbnail.ts +97 -0
- package/client/pages/figure-v3-modeller-page.ts +272 -12
- package/client/route.ts +8 -0
- package/dist-client/graphql/index.d.ts +8 -0
- package/dist-client/graphql/index.js +51 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/graphql/v3-store.js +8 -2
- package/dist-client/graphql/v3-store.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +21 -0
- package/dist-client/modeller/figure-history-panel.js +34 -87
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +41 -0
- package/dist-client/modeller/figure-v3-parts.js +352 -0
- package/dist-client/modeller/figure-v3-parts.js.map +1 -0
- package/dist-client/modeller/figure-v3-verdict.d.ts +50 -0
- package/dist-client/modeller/figure-v3-verdict.js +268 -0
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -0
- package/dist-client/modeller/gate-findings.d.ts +12 -0
- package/dist-client/modeller/gate-findings.js +102 -0
- package/dist-client/modeller/gate-findings.js.map +1 -0
- package/dist-client/modeller/v3-editor.js +11 -2
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-occupancy.d.ts +14 -0
- package/dist-client/modeller/v3-occupancy.js +22 -0
- package/dist-client/modeller/v3-occupancy.js.map +1 -0
- package/dist-client/modeller/v3-product-session.d.ts +44 -2
- package/dist-client/modeller/v3-product-session.js +13 -2
- package/dist-client/modeller/v3-product-session.js.map +1 -1
- package/dist-client/modeller/v3-renderer.d.ts +9 -0
- package/dist-client/modeller/v3-renderer.js +22 -30
- package/dist-client/modeller/v3-renderer.js.map +1 -1
- package/dist-client/modeller/v3-thumbnail.d.ts +3 -0
- package/dist-client/modeller/v3-thumbnail.js +74 -0
- package/dist-client/modeller/v3-thumbnail.js.map +1 -0
- package/dist-client/pages/figure-v3-modeller-page.d.ts +52 -1
- package/dist-client/pages/figure-v3-modeller-page.js +294 -12
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +7 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/test/resolve-ts-siblings.mjs +18 -0
- package/test/v3-product-write.test.ts +14 -2
- package/test/v3-release-panel.test.ts +73 -0
- package/test/v3-verdict.test.ts +82 -0
- package/things-factory.config.js +6 -1
- package/translations/en.json +79 -1
- package/translations/ja.json +79 -1
- package/translations/ko.json +79 -1
- package/translations/ms.json +79 -1
- package/translations/zh.json +79 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.39",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"clean": "npm run clean:server && npm run clean:client"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@hatiolab/figure-model": "^0.1.
|
|
30
|
-
"@hatiolab/things-scene": "^10.1.
|
|
29
|
+
"@hatiolab/figure-model": "^0.1.38",
|
|
30
|
+
"@hatiolab/things-scene": "^10.1.26",
|
|
31
31
|
"@material/web": "^2.0.0",
|
|
32
32
|
"@operato/data-grist": "^10.0.0",
|
|
33
33
|
"@operato/graphql": "^10.0.0",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"@operato/shell": "^10.0.0",
|
|
38
38
|
"@operato/styles": "^10.0.0",
|
|
39
39
|
"@operato/utils": "^10.0.0",
|
|
40
|
-
"@things-factory/figure-service": "^10.1.
|
|
40
|
+
"@things-factory/figure-service": "^10.1.39",
|
|
41
41
|
"three": "^0.185.1"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "fe5257ad8995fe7e79275e287f36e95b5fd32d1c"
|
|
44
44
|
}
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* 없으면 원래 오류가 그대로 난다(없는 것을 지어내지 않는다).
|
|
31
31
|
*/
|
|
32
32
|
import { registerHooks } from 'node:module'
|
|
33
|
+
import { createRequire } from 'node:module'
|
|
33
34
|
import { existsSync } from 'node:fs'
|
|
34
35
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
35
36
|
|
|
@@ -46,8 +47,25 @@ function tsSibling(specifier, parentURL, stripExtension) {
|
|
|
46
47
|
return existsSync(fileURLToPath(asTs)) ? asTs : undefined
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/*
|
|
51
|
+
* ── 패키지 이름 하나만 예외다: `@hatiolab/things-scene` (2026-09-24) ─────────────────────────
|
|
52
|
+
* 이 패키지의 client 는 브라우저 코드이고, things-scene 은 판이 둘이다 — 브라우저용 `things-scene.mjs` 와
|
|
53
|
+
* 서버 전송용 `things-scene-transfer.mjs`. Node 는 `exports` 의 `node` 조건으로 전송 판을 고르는데, 그 판에는
|
|
54
|
+
* figure 가 없다(`buildV3Geometry` · `registerFigure` …). 모델러 소스를 Node 에서 시험하면 **브라우저가
|
|
55
|
+
* 받을 판**을 봐야 맞다. 그래서 이 이름만 브라우저 판으로 되짚는다. 다른 패키지 이름은 그대로 둔다.
|
|
56
|
+
*/
|
|
57
|
+
const SCENE_BROWSER_BUNDLE = (() => {
|
|
58
|
+
try {
|
|
59
|
+
const require = createRequire(import.meta.url)
|
|
60
|
+
return pathToFileURL(require.resolve('@hatiolab/things-scene/package.json').replace(/package\.json$/, 'things-scene.mjs')).href
|
|
61
|
+
} catch {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
})()
|
|
65
|
+
|
|
49
66
|
registerHooks({
|
|
50
67
|
resolve(specifier, context, nextResolve) {
|
|
68
|
+
if (specifier === '@hatiolab/things-scene' && SCENE_BROWSER_BUNDLE) return { url: SCENE_BROWSER_BUNDLE, shortCircuit: true }
|
|
51
69
|
/* 상대 경로만 본다 — 패키지 이름은 그대로 둔다(의존 패키지의 산출물을 가리킨다). */
|
|
52
70
|
if (!specifier.startsWith('.')) return nextResolve(specifier, context)
|
|
53
71
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import { createV3Asset, serializeV3Asset } from '@hatiolab/figure-model'
|
|
3
|
+
import { addV3Box, createV3Asset, serializeV3Asset } from '@hatiolab/figure-model'
|
|
4
4
|
import { prepareV3Write, compareAndSwapV3 } from '../../figure-service/server/service/figure/figure-v3-write.js'
|
|
5
5
|
import { DatabaseSync } from 'node:sqlite'
|
|
6
6
|
|
|
@@ -9,11 +9,23 @@ test('V3 saves preserve the graph and clear unmeasured previous scores', () => {
|
|
|
9
9
|
const saved = prepareV3Write(source)
|
|
10
10
|
assert.equal(saved.source, source)
|
|
11
11
|
assert.equal(saved.name, '빈 자산')
|
|
12
|
+
/* 점수는 V2 재사용의 잣대다 — V3 를 잰 적이 없으므로 비운다. */
|
|
12
13
|
assert.equal(saved.score, null)
|
|
13
|
-
|
|
14
|
+
/* 예산은 잰다. 빈 자산은 0 이고, 그것은 「안 쟀다」가 아니라 「그릴 것이 없다」는 뜻이다. */
|
|
15
|
+
assert.equal(saved.triangles, 0)
|
|
14
16
|
assert.equal(saved.groups, 0)
|
|
15
17
|
assert.equal(saved.state, 'draft')
|
|
16
18
|
})
|
|
19
|
+
test('V3 예산은 실제로 그리는 것을 센다 — 상자 하나는 삼각형 12 다', () => {
|
|
20
|
+
const asset = addV3Box(createV3Asset('상자'), {
|
|
21
|
+
id: 'body',
|
|
22
|
+
size: { x: 100, y: 100, z: 100 },
|
|
23
|
+
position: { x: 0, y: 50, z: 0 }
|
|
24
|
+
})
|
|
25
|
+
const saved = prepareV3Write(serializeV3Asset(asset))
|
|
26
|
+
assert.equal(saved.triangles, 12)
|
|
27
|
+
assert.equal(saved.groups, 1)
|
|
28
|
+
})
|
|
17
29
|
test('source concurrency check requires exact baseline; no implicit overwrite', () => {
|
|
18
30
|
const source = serializeV3Asset(createV3Asset('A'))
|
|
19
31
|
assert.throws(() => prepareV3Write(source, { source }))
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **V3 화면에서 발행할 수 있다** (ADR-0087 5단계의 선행 조건, 2026-09-24).
|
|
3
|
+
*
|
|
4
|
+
* V3 모델러에는 발행 패널이 없었다 — 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없었다. 복사본 검증의
|
|
5
|
+
* 「32 개 발행 통과」는 스크립트가 서버의 문을 직접 부른 것이지 화면이 아니었다. 그리고 새로 만든 V3 도형은 전부
|
|
6
|
+
* 「공간 선언 없음」으로 발행이 막히는데, 그 선언을 할 단추가 화면 어디에도 없었다. 5단계에서 `figure-modeller`
|
|
7
|
+
* 가 이 화면으로 바뀌면 새 도형은 영영 발행할 수 없었다.
|
|
8
|
+
*
|
|
9
|
+
* 두 축을 본다: 조치가 **정말로** 판정을 푸는가(figure-model 의 게이트로 잰다), 그리고 화면이 그 조치와 패널을
|
|
10
|
+
* 실제로 잇는가.
|
|
11
|
+
*/
|
|
12
|
+
import { test } from 'node:test'
|
|
13
|
+
import assert from 'node:assert/strict'
|
|
14
|
+
import { readFileSync } from 'node:fs'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
addV3Box,
|
|
19
|
+
applyV3Authoring,
|
|
20
|
+
createV3Asset,
|
|
21
|
+
inspectV3Asset,
|
|
22
|
+
proposeV3Occupancy,
|
|
23
|
+
serializeV3Asset
|
|
24
|
+
} from '@hatiolab/figure-model'
|
|
25
|
+
|
|
26
|
+
import { occupancyFromParts } from '../client/modeller/v3-occupancy.ts'
|
|
27
|
+
|
|
28
|
+
const codes = (asset: Parameters<typeof inspectV3Asset>[0]) => inspectV3Asset(asset).violations.map(v => v.code)
|
|
29
|
+
|
|
30
|
+
test('새로 만든 V3 도형의 「공간 선언 없음」을 「부품에 맞추기」 한 번이 푼다', () => {
|
|
31
|
+
let asset = createV3Asset('Probe')
|
|
32
|
+
asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
33
|
+
assert.deepEqual(codes(asset), ['OCCUPANCY_UNDECLARED'], '전제: 선언 없는 도형은 막힌다')
|
|
34
|
+
|
|
35
|
+
const fitted = applyV3Authoring(asset, occupancyFromParts(serializeV3Asset(asset)))
|
|
36
|
+
|
|
37
|
+
assert.deepEqual(codes(fitted), [])
|
|
38
|
+
assert.equal(fitted.occupancy?.placement, 'floor', '처음 선언하는 도형은 바닥에 선다')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('선언한 공간을 넘은 부품도 같은 한 번으로 풀리고, 배치 기준은 지킨다', () => {
|
|
42
|
+
let asset = createV3Asset('Probe')
|
|
43
|
+
asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
44
|
+
asset = applyV3Authoring(asset, { kind: 'declare-occupancy', proposal: proposeV3Occupancy(asset, { over: 'range' }), placement: 'center' })
|
|
45
|
+
asset = addV3Box(asset, { id: 'arm', size: { x: 100, y: 100, z: 100 }, position: { x: 900, y: 50, z: 0 } })
|
|
46
|
+
assert.ok(codes(asset).includes('PART_OUTSIDE_OCCUPANCY'), '전제: 넘은 부품은 막힌다')
|
|
47
|
+
|
|
48
|
+
const fitted = applyV3Authoring(asset, occupancyFromParts(serializeV3Asset(asset)))
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(codes(fitted), [])
|
|
51
|
+
assert.equal(fitted.occupancy?.placement, 'center', '이미 선언된 배치 기준을 바꾸지 않는다')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
55
|
+
|
|
56
|
+
test('V3 화면이 발행 패널을 V3 로 걸고, 패널이 내는 셋을 받는다', () => {
|
|
57
|
+
const page = read('../client/pages/figure-v3-modeller-page.ts')
|
|
58
|
+
|
|
59
|
+
assert.ok(/<figure-history-panel[\s\S]*?format="v3"/.test(page), '패널을 V3 형식으로 건다')
|
|
60
|
+
for (const event of ['fit-base-to-parts', 'figure-released', 'figure-reverted']) {
|
|
61
|
+
assert.ok(page.includes(`@${event}=`), `패널이 내는 ${event} 를 받는 자리가 없다 — 눌러도 아무 일이 없다`)
|
|
62
|
+
}
|
|
63
|
+
assert.ok(/occupancyFromParts\(this\.session\.source\(\)\)/.test(page), '「부품에 맞추기」가 공간 선언으로 이어진다')
|
|
64
|
+
/* 없으면 셸이 페이지를 내용 폭(편집기 660 + 패널 320)으로 세워 패널이 오른쪽 밖으로 나간다 — 945 px 에서 봤다. */
|
|
65
|
+
assert.ok(/:host \{[^}]*overflow: hidden/.test(page), '페이지가 주어진 폭 안에 든다')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('패널은 받는 화면이 있는 조치만 그린다 — V3 에 「바닥에 앉히기」는 없다', () => {
|
|
69
|
+
const panel = read('../client/modeller/figure-history-panel.ts')
|
|
70
|
+
|
|
71
|
+
assert.ok(/'parts-off-placement-face' && this\.format === 'v2'/.test(panel), '앉히기는 V2 에서만')
|
|
72
|
+
assert.ok(/code === 'part-outside-occupancy' \|\| code === 'occupancy-undeclared'/.test(panel), 'V3 의 두 판정에 맞추기를 낸다')
|
|
73
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **V3 화면이 편집하는 동안 판정한다** (V3 동등표 ①, 2026-09-24).
|
|
3
|
+
*
|
|
4
|
+
* V2 모델러는 고치는 동안 「한 화면에 몇 개」와 「무엇이 발행을 막나」를 보여 준다. V3 화면은 둘 다 없어서 막힌
|
|
5
|
+
* 까닭을 저장한 뒤 발행 패널에서야 알았다. 판정 탭이 편집 중인 원본으로 둘을 답한다.
|
|
6
|
+
*
|
|
7
|
+
* 세 축을 본다: 개수가 목록 카드와 같은 셈인가, 판정이 발행 패널과 같은 문 · 같은 문장인가, 화면이 탭과 조치를
|
|
8
|
+
* 실제로 잇는가.
|
|
9
|
+
*/
|
|
10
|
+
import { test } from 'node:test'
|
|
11
|
+
import assert from 'node:assert/strict'
|
|
12
|
+
import { readFileSync } from 'node:fs'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
|
|
15
|
+
import { addV3Box, createV3Asset, v3CostOf } from '@hatiolab/figure-model'
|
|
16
|
+
|
|
17
|
+
import { capacityFrom, SCENE_BUDGET } from '../client/modeller/scene-budget.ts'
|
|
18
|
+
|
|
19
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
20
|
+
const verdict = read('../client/modeller/figure-v3-verdict.ts')
|
|
21
|
+
const page = read('../client/pages/figure-v3-modeller-page.ts')
|
|
22
|
+
const graphql = read('../client/graphql/index.ts')
|
|
23
|
+
|
|
24
|
+
test('개수는 목록 카드와 같은 셈이다 — V3 원가를 같은 capacityFrom 에 넣는다', () => {
|
|
25
|
+
let asset = createV3Asset('Probe')
|
|
26
|
+
asset = addV3Box(asset, { id: 'a', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
27
|
+
asset = addV3Box(asset, { id: 'b', size: { x: 100, y: 100, z: 100 }, position: { x: 200, y: 50, z: 0 } })
|
|
28
|
+
const cost = v3CostOf(asset)
|
|
29
|
+
|
|
30
|
+
assert.equal(cost.groups, 2, '전제: 그려지는 부품 둘이 묶음 둘')
|
|
31
|
+
assert.equal(capacityFrom(cost.groups, cost.triangles)?.instances, Math.floor(SCENE_BUDGET.groupsOnScreen / 2))
|
|
32
|
+
assert.ok(/capacityFrom\(cost\.groups, cost\.triangles\)/.test(verdict), '판정 탭이 카드와 같은 함수로 센다')
|
|
33
|
+
assert.ok(!/grade/.test(verdict.replace(/\/\*[\s\S]*?\*\//g, '')), 'V3 에는 잰 것이 없는 등급을 그리지 않는다')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('판정은 발행 패널과 같은 서버 문에서 온다 — 화면이 따로 판정하지 않는다', () => {
|
|
37
|
+
assert.ok(verdict.includes('inspectFigureDraft(source, this.figureId)'), '편집 중인 원본을 보낸다')
|
|
38
|
+
assert.ok(!/inspectV3Asset/.test(verdict), '화면이 제 판정을 가지면 발행 패널과 다른 말을 한다')
|
|
39
|
+
|
|
40
|
+
const fields = (name: string) => {
|
|
41
|
+
const start = graphql.indexOf(`${name}(`, graphql.indexOf(`query ${name[0].toUpperCase()}${name.slice(1)}`))
|
|
42
|
+
return graphql.slice(start, graphql.indexOf('`', start)).replace(/\s+/g, ' ').replace(/^[^{]*/, '')
|
|
43
|
+
}
|
|
44
|
+
assert.equal(fields('inspectFigureDraft'), fields('inspectFigure'), '두 질의가 같은 칸을 받는다 — 한쪽만 why·how 를 잃지 않게')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('늦게 온 옛 답이 새 답을 덮지 않는다', () => {
|
|
48
|
+
const guards = verdict.match(/question !== this\.asked/g) ?? []
|
|
49
|
+
assert.ok(guards.length >= 2, '성공과 실패 둘 다 가장 새 물음의 답인지 본다')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('판정 탭은 발행 패널과 같은 두 판정에 「부품에 맞추기」를 낸다', () => {
|
|
53
|
+
const panel = read('../client/modeller/figure-history-panel.ts')
|
|
54
|
+
const codes = (text: string) => [...text.matchAll(/'(part-outside-occupancy|occupancy-undeclared)'/g)].map(m => m[1]).sort()
|
|
55
|
+
assert.deepEqual(codes(verdict), codes(panel.slice(panel.indexOf('private offersFit'))))
|
|
56
|
+
assert.ok(/<figure-v3-verdict[\s\S]*?@fit-base-to-parts=\$\{this\.fitToParts\}/.test(page), '누르면 공간 선언으로 이어진다')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('화면이 탭 둘을 걸고, 감추되 떼지 않는다', () => {
|
|
60
|
+
assert.ok(/<figure-v3-verdict[\s\S]*?\.source=\$\{this\.draftSource\(\)\}/.test(page), '편집 중인 원본을 넘긴다')
|
|
61
|
+
assert.ok(/<figure-v3-verdict[\s\S]*?\.figureType=/.test(page), '타입을 바꾸면 다시 판정한다')
|
|
62
|
+
assert.ok(/<figure-v3-verdict\s+\?off=/.test(page) && /<figure-history-panel\s+\?off=/.test(page), '고르지 않은 탭은 감춘다')
|
|
63
|
+
assert.ok(/@capacity-changed=/.test(page) && /this\.instances\)/.test(page), '개수가 탭 이름표에 남는다')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('판정 탭의 말은 다섯 말 모두에 있다 — 없으면 키가 그대로 찍힌다', () => {
|
|
67
|
+
const keys = [...verdict.matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
|
|
68
|
+
assert.ok(keys.includes('figure.text.verdict-asking'), '전제: 삼항 안의 키도 센다')
|
|
69
|
+
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
|
|
70
|
+
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
71
|
+
for (const key of keys) assert.ok(table[key], `${lang}: ${key}`)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('부품이 없는 도형에는 「부품에 맞추기」를 내지 않고, 실패해도 편집기를 지우지 않는다', () => {
|
|
76
|
+
/* 새 도형은 부품 없이도 「공간 선언 없음」으로 막힌다. 그때 맞추기는 OCCUPANCY_NO_PARTS 로 던진다. */
|
|
77
|
+
assert.ok(/if \(!this\.bodies\) return nothing/.test(verdict), '잴 부품이 없으면 단추를 내지 않는다')
|
|
78
|
+
assert.ok(/bodies = cost \? cost\.groups \+ cost\.hidden : 0/.test(verdict), '감춘 부품도 잴 수 있는 부품이다')
|
|
79
|
+
const fit = page.slice(page.indexOf('private fitToParts'), page.indexOf('private released'))
|
|
80
|
+
assert.ok(!/this\.error\s*=/.test(fit), '맞추기 실패가 화면 전체를 오류 한 줄로 바꾸지 않는다')
|
|
81
|
+
assert.ok(fit.includes("i18next.t('figure.text.fit-needs-parts')"), '부품이 없다는 것을 사람의 말로 한다')
|
|
82
|
+
})
|
package/things-factory.config.js
CHANGED
|
@@ -5,7 +5,12 @@ export default {
|
|
|
5
5
|
route,
|
|
6
6
|
routes: [
|
|
7
7
|
{ tagname: 'figure-list-page', page: 'figure-list' },
|
|
8
|
-
{ tagname: 'figure-modeller-page', page: 'figure-modeller' }
|
|
8
|
+
{ tagname: 'figure-modeller-page', page: 'figure-modeller' },
|
|
9
|
+
/*
|
|
10
|
+
The V3 modeller under its own name until the catalogue's assets are V3 (ADR-0087 step 5). Without this row
|
|
11
|
+
the URL changes and no element is made — the shell builds the DOM from this table, not from route().
|
|
12
|
+
*/
|
|
13
|
+
{ tagname: 'figure-v3-modeller-page', page: 'figure-v3-modeller' }
|
|
9
14
|
],
|
|
10
15
|
bootstrap
|
|
11
16
|
}
|
package/translations/en.json
CHANGED
|
@@ -228,6 +228,7 @@
|
|
|
228
228
|
"figure.label.recipe-lift": "a lift changes height",
|
|
229
229
|
"figure.label.recipe-pusher": "a pusher moves into place",
|
|
230
230
|
"figure.label.release": "release",
|
|
231
|
+
"figure.label.release-verdict": "release check",
|
|
231
232
|
"figure.label.released-versions": "released versions",
|
|
232
233
|
"figure.label.repeat-axis": "axis it repeats along",
|
|
233
234
|
"figure.label.repeat-count": "count",
|
|
@@ -289,6 +290,11 @@
|
|
|
289
290
|
"figure.text.budget-measured-on": "Measured on {gpu}, {on}. A figure standing still costs nothing — only what is on screen and redrawn counts.",
|
|
290
291
|
"figure.text.budget-not-measured-yet": "this target has not been measured yet.",
|
|
291
292
|
"figure.text.cannot-release-now": "cannot be released yet",
|
|
293
|
+
"figure.text.fit-needs-parts": "there are no parts to fit to — add a shape first.",
|
|
294
|
+
"figure.text.nothing-blocks-release": "nothing in the figure as it stands blocks releasing it.",
|
|
295
|
+
"figure.text.verdict-asking": "checking…",
|
|
296
|
+
"figure.text.verdict-not-received": "the check did not come back — {reason}",
|
|
297
|
+
"figure.text.verdict-waiting": "the check follows your edits.",
|
|
292
298
|
"figure.text.canvas-appears-once-a-part-exists": "pick a shape on the left to add the first part.",
|
|
293
299
|
"figure.text.capability-hand-written": "set by hand, so it matches none of the levels above. it is saved under the names below.",
|
|
294
300
|
"figure.text.capacity-from-repeat": "capacity comes from the repeat setting and the instance size",
|
|
@@ -468,5 +474,77 @@
|
|
|
468
474
|
"figure.text.what-changed-in-this-release": "what changed in this release",
|
|
469
475
|
"figure.title.figures": "figures",
|
|
470
476
|
"figure.title.new-figure": "new figure",
|
|
471
|
-
"figure.title.save-new-figure": "save figure draft"
|
|
477
|
+
"figure.title.save-new-figure": "save figure draft",
|
|
478
|
+
"figure.text.no-parts-yet": "No parts yet. Pick a shape above to place one.",
|
|
479
|
+
"figure.label.primitive-portal": "Portal",
|
|
480
|
+
"figure.label.state-hidden": "hidden in this state",
|
|
481
|
+
"figure.label.follows": "Follows",
|
|
482
|
+
"figure.text.follows-explained": "What this side grows with. Stretch the figure on a board and this follows.",
|
|
483
|
+
"figure.label.follows-nothing": "fixed",
|
|
484
|
+
"figure.label.follows-instance": "instance size",
|
|
485
|
+
"figure.label.follows-part": "another part",
|
|
486
|
+
"figure.label.times": "times",
|
|
487
|
+
"figure.label.plus": "plus (mm)",
|
|
488
|
+
"figure.label.fastening": "Fastening",
|
|
489
|
+
"figure.text.fastening-explained": "Which face of this part meets which face of which part. It stays fastened when either is resized.",
|
|
490
|
+
"figure.label.fastened-to": "fastened to",
|
|
491
|
+
"figure.label.not-fastened": "not fastened",
|
|
492
|
+
"figure.label.my-face": "my face",
|
|
493
|
+
"figure.label.target-part": "target part",
|
|
494
|
+
"figure.label.target-face": "target face",
|
|
495
|
+
"figure.label.stands-on-plane": "stand on the mounting plane",
|
|
496
|
+
"figure.label.facing-meet": "faces meet",
|
|
497
|
+
"figure.label.facing-flush": "same plane",
|
|
498
|
+
"figure.label.gap": "gap (mm)",
|
|
499
|
+
"figure.label.align": "within the face",
|
|
500
|
+
"figure.label.align-centre": "centre",
|
|
501
|
+
"figure.label.align-min": "low edge",
|
|
502
|
+
"figure.label.align-max": "high edge",
|
|
503
|
+
"figure.button.fasten": "Fasten",
|
|
504
|
+
"figure.button.detach": "Detach",
|
|
505
|
+
"figure.label.face-left": "left",
|
|
506
|
+
"figure.label.face-right": "right",
|
|
507
|
+
"figure.label.face-bottom": "bottom",
|
|
508
|
+
"figure.label.face-top": "top",
|
|
509
|
+
"figure.label.face-back": "back",
|
|
510
|
+
"figure.label.face-front": "front",
|
|
511
|
+
"figure.label.motion": "Motion",
|
|
512
|
+
"figure.text.motion-explained": "How this part moves. Added on top of its fastening, never instead of it.",
|
|
513
|
+
"figure.label.motion-none": "does not move",
|
|
514
|
+
"figure.label.motion-slide": "slide",
|
|
515
|
+
"figure.label.motion-turn": "turn",
|
|
516
|
+
"figure.label.motion-axis": "axis",
|
|
517
|
+
"figure.label.control-id": "control name",
|
|
518
|
+
"figure.label.control-unit": "unit",
|
|
519
|
+
"figure.label.control-range": "range",
|
|
520
|
+
"figure.label.control-start": "start",
|
|
521
|
+
"figure.label.travel-follows": "travel follows",
|
|
522
|
+
"figure.button.add-motion": "Add motion",
|
|
523
|
+
"figure.button.remove-motion": "Remove motion",
|
|
524
|
+
"figure.label.room": "Room it takes up",
|
|
525
|
+
"figure.text.room-explained": "Bounds computed from the parts are a proposal. Nothing is declared until you confirm, and a declaration never widens on its own.",
|
|
526
|
+
"figure.label.room-rest": "as it stands",
|
|
527
|
+
"figure.label.room-range": "over its whole motion",
|
|
528
|
+
"figure.button.propose-room": "Compute bounds",
|
|
529
|
+
"figure.button.confirm-room": "Declare these bounds",
|
|
530
|
+
"figure.label.room-undeclared": "not declared yet",
|
|
531
|
+
"figure.label.room-declared": "declared",
|
|
532
|
+
"figure.label.mounting-face": "Mounting face",
|
|
533
|
+
"figure.text.mounting-face-explained": "The face the figure is mounted on. A separate choice from the room it takes up.",
|
|
534
|
+
"figure.button.set-mounting-face": "This face",
|
|
535
|
+
"figure.button.clear-mounting-face": "Clear",
|
|
536
|
+
"figure.label.mounting-face-none": "none",
|
|
537
|
+
"figure.label.dimensions": "Dimensions",
|
|
538
|
+
"figure.label.part-id": "part id",
|
|
539
|
+
"figure.text.part-id-is-stable": "The part id is what fastenings, motions and connections point at, so it does not change.",
|
|
540
|
+
"figure.label.verdict-proven": "proven over the whole range",
|
|
541
|
+
"figure.label.verdict-sampled": "sampled",
|
|
542
|
+
"figure.label.verdict-defaults": "default state only",
|
|
543
|
+
"figure.text.verdict-sampled-explained": "An operation could not be proven, so several states were sampled. It may fail between the states checked.",
|
|
544
|
+
"figure.label.bodies-drawn": "bodies drawn",
|
|
545
|
+
"figure.label.bodies-hidden": "bodies hidden",
|
|
546
|
+
"figure.label.state-inputs": "state inputs",
|
|
547
|
+
"figure.label.design-inputs": "design inputs",
|
|
548
|
+
"figure.text.release-blocked": "Release is blocked — {count}",
|
|
549
|
+
"figure.text.release-ready": "Ready to release"
|
|
472
550
|
}
|
package/translations/ja.json
CHANGED
|
@@ -226,6 +226,7 @@
|
|
|
226
226
|
"figure.label.recipe-lift": "リフトの高さが変わる",
|
|
227
227
|
"figure.label.recipe-pusher": "プッシャーが位置に移動する",
|
|
228
228
|
"figure.label.release": "公開",
|
|
229
|
+
"figure.label.release-verdict": "公開判定",
|
|
229
230
|
"figure.label.released-versions": "発行済みバージョン",
|
|
230
231
|
"figure.label.repeat-axis": "axis it repeats along",
|
|
231
232
|
"figure.label.repeat-count": "個数",
|
|
@@ -287,6 +288,11 @@
|
|
|
287
288
|
"figure.text.budget-measured-on": "{gpu} で {on} に測定した値です。静止している図形は負荷になりません — 画面に見えて再描画されるものだけを数えます。",
|
|
288
289
|
"figure.text.budget-not-measured-yet": "this target has not been measured yet.",
|
|
289
290
|
"figure.text.cannot-release-now": "今は公開できません",
|
|
291
|
+
"figure.text.fit-needs-parts": "合わせる部品がありません。先に形状を追加してください。",
|
|
292
|
+
"figure.text.nothing-blocks-release": "今の形のままなら公開を妨げるものはありません。",
|
|
293
|
+
"figure.text.verdict-asking": "判定中…",
|
|
294
|
+
"figure.text.verdict-not-received": "判定を受け取れませんでした — {reason}",
|
|
295
|
+
"figure.text.verdict-waiting": "編集すると判定します。",
|
|
290
296
|
"figure.text.canvas-appears-once-a-part-exists": "左の「図形」から 1 つ選んで最初の部品を追加してください。",
|
|
291
297
|
"figure.text.capability-hand-written": "手動で指定した能力のため、上の段階のどれにも当てはまりません。下の名前で保存されます。",
|
|
292
298
|
"figure.text.capacity-from-repeat": "収容数は繰り返し設定とインスタンスのサイズで決まります",
|
|
@@ -464,5 +470,77 @@
|
|
|
464
470
|
"figure.text.what-changed-in-this-release": "このバージョンで変わったこと",
|
|
465
471
|
"figure.title.figures": "figures",
|
|
466
472
|
"figure.title.new-figure": "new figure",
|
|
467
|
-
"figure.title.save-new-figure": "Figure の下書きを保存"
|
|
473
|
+
"figure.title.save-new-figure": "Figure の下書きを保存",
|
|
474
|
+
"figure.text.no-parts-yet": "No parts yet. Pick a shape above to place one.",
|
|
475
|
+
"figure.label.primitive-portal": "Portal",
|
|
476
|
+
"figure.label.state-hidden": "hidden in this state",
|
|
477
|
+
"figure.label.follows": "Follows",
|
|
478
|
+
"figure.text.follows-explained": "What this side grows with. Stretch the figure on a board and this follows.",
|
|
479
|
+
"figure.label.follows-nothing": "fixed",
|
|
480
|
+
"figure.label.follows-instance": "instance size",
|
|
481
|
+
"figure.label.follows-part": "another part",
|
|
482
|
+
"figure.label.times": "times",
|
|
483
|
+
"figure.label.plus": "plus (mm)",
|
|
484
|
+
"figure.label.fastening": "Fastening",
|
|
485
|
+
"figure.text.fastening-explained": "Which face of this part meets which face of which part. It stays fastened when either is resized.",
|
|
486
|
+
"figure.label.fastened-to": "fastened to",
|
|
487
|
+
"figure.label.not-fastened": "not fastened",
|
|
488
|
+
"figure.label.my-face": "my face",
|
|
489
|
+
"figure.label.target-part": "target part",
|
|
490
|
+
"figure.label.target-face": "target face",
|
|
491
|
+
"figure.label.stands-on-plane": "stand on the mounting plane",
|
|
492
|
+
"figure.label.facing-meet": "faces meet",
|
|
493
|
+
"figure.label.facing-flush": "same plane",
|
|
494
|
+
"figure.label.gap": "gap (mm)",
|
|
495
|
+
"figure.label.align": "within the face",
|
|
496
|
+
"figure.label.align-centre": "centre",
|
|
497
|
+
"figure.label.align-min": "low edge",
|
|
498
|
+
"figure.label.align-max": "high edge",
|
|
499
|
+
"figure.button.fasten": "Fasten",
|
|
500
|
+
"figure.button.detach": "Detach",
|
|
501
|
+
"figure.label.face-left": "left",
|
|
502
|
+
"figure.label.face-right": "right",
|
|
503
|
+
"figure.label.face-bottom": "bottom",
|
|
504
|
+
"figure.label.face-top": "top",
|
|
505
|
+
"figure.label.face-back": "back",
|
|
506
|
+
"figure.label.face-front": "front",
|
|
507
|
+
"figure.label.motion": "Motion",
|
|
508
|
+
"figure.text.motion-explained": "How this part moves. Added on top of its fastening, never instead of it.",
|
|
509
|
+
"figure.label.motion-none": "does not move",
|
|
510
|
+
"figure.label.motion-slide": "slide",
|
|
511
|
+
"figure.label.motion-turn": "turn",
|
|
512
|
+
"figure.label.motion-axis": "axis",
|
|
513
|
+
"figure.label.control-id": "control name",
|
|
514
|
+
"figure.label.control-unit": "unit",
|
|
515
|
+
"figure.label.control-range": "range",
|
|
516
|
+
"figure.label.control-start": "start",
|
|
517
|
+
"figure.label.travel-follows": "travel follows",
|
|
518
|
+
"figure.button.add-motion": "Add motion",
|
|
519
|
+
"figure.button.remove-motion": "Remove motion",
|
|
520
|
+
"figure.label.room": "Room it takes up",
|
|
521
|
+
"figure.text.room-explained": "Bounds computed from the parts are a proposal. Nothing is declared until you confirm, and a declaration never widens on its own.",
|
|
522
|
+
"figure.label.room-rest": "as it stands",
|
|
523
|
+
"figure.label.room-range": "over its whole motion",
|
|
524
|
+
"figure.button.propose-room": "Compute bounds",
|
|
525
|
+
"figure.button.confirm-room": "Declare these bounds",
|
|
526
|
+
"figure.label.room-undeclared": "not declared yet",
|
|
527
|
+
"figure.label.room-declared": "declared",
|
|
528
|
+
"figure.label.mounting-face": "Mounting face",
|
|
529
|
+
"figure.text.mounting-face-explained": "The face the figure is mounted on. A separate choice from the room it takes up.",
|
|
530
|
+
"figure.button.set-mounting-face": "This face",
|
|
531
|
+
"figure.button.clear-mounting-face": "Clear",
|
|
532
|
+
"figure.label.mounting-face-none": "none",
|
|
533
|
+
"figure.label.dimensions": "Dimensions",
|
|
534
|
+
"figure.label.part-id": "part id",
|
|
535
|
+
"figure.text.part-id-is-stable": "The part id is what fastenings, motions and connections point at, so it does not change.",
|
|
536
|
+
"figure.label.verdict-proven": "proven over the whole range",
|
|
537
|
+
"figure.label.verdict-sampled": "sampled",
|
|
538
|
+
"figure.label.verdict-defaults": "default state only",
|
|
539
|
+
"figure.text.verdict-sampled-explained": "An operation could not be proven, so several states were sampled. It may fail between the states checked.",
|
|
540
|
+
"figure.label.bodies-drawn": "bodies drawn",
|
|
541
|
+
"figure.label.bodies-hidden": "bodies hidden",
|
|
542
|
+
"figure.label.state-inputs": "state inputs",
|
|
543
|
+
"figure.label.design-inputs": "design inputs",
|
|
544
|
+
"figure.text.release-blocked": "Release is blocked — {count}",
|
|
545
|
+
"figure.text.release-ready": "Ready to release"
|
|
468
546
|
}
|
package/translations/ko.json
CHANGED
|
@@ -228,6 +228,7 @@
|
|
|
228
228
|
"figure.label.recipe-lift": "리프트 높이 변경",
|
|
229
229
|
"figure.label.recipe-pusher": "푸셔 위치 이동",
|
|
230
230
|
"figure.label.release": "발행",
|
|
231
|
+
"figure.label.release-verdict": "발행 판정",
|
|
231
232
|
"figure.label.released-versions": "발행된 버전",
|
|
232
233
|
"figure.label.repeat-axis": "늘어나는 축",
|
|
233
234
|
"figure.label.repeat-count": "개수",
|
|
@@ -289,6 +290,11 @@
|
|
|
289
290
|
"figure.text.budget-measured-on": "{gpu} 에서 {on} 에 잰 값입니다. 가만히 서 있는 도형은 값을 치르지 않습니다 — 화면에 보이고 다시 그려지는 것만 셉니다.",
|
|
290
291
|
"figure.text.budget-not-measured-yet": "아직 실측 전 목표치입니다.",
|
|
291
292
|
"figure.text.cannot-release-now": "지금은 발행할 수 없습니다",
|
|
293
|
+
"figure.text.fit-needs-parts": "맞출 부품이 없습니다. 먼저 도형을 추가하세요.",
|
|
294
|
+
"figure.text.nothing-blocks-release": "지금 모양 그대로면 발행을 막는 것이 없습니다.",
|
|
295
|
+
"figure.text.verdict-asking": "판정을 받는 중입니다…",
|
|
296
|
+
"figure.text.verdict-not-received": "판정을 받지 못했습니다 — {reason}",
|
|
297
|
+
"figure.text.verdict-waiting": "고치면 곧 판정합니다.",
|
|
292
298
|
"figure.text.canvas-appears-once-a-part-exists": "왼쪽 「도형」에서 하나를 선택해 첫 부품을 추가하세요.",
|
|
293
299
|
"figure.text.capability-hand-written": "직접 지정한 능력이라 위 단계 중 어디에도 해당하지 않습니다. 아래 이름으로 저장됩니다.",
|
|
294
300
|
"figure.text.capacity-from-repeat": "칸 수는 반복 설정과 인스턴스 크기로 정해집니다",
|
|
@@ -468,5 +474,77 @@
|
|
|
468
474
|
"figure.text.what-changed-in-this-release": "이번 버전에서 바뀐 것",
|
|
469
475
|
"figure.title.figures": "Figure",
|
|
470
476
|
"figure.title.new-figure": "새 Figure",
|
|
471
|
-
"figure.title.save-new-figure": "Figure 초안 저장"
|
|
477
|
+
"figure.title.save-new-figure": "Figure 초안 저장",
|
|
478
|
+
"figure.text.no-parts-yet": "아직 부품이 없습니다. 위에서 도형을 골라 놓으세요.",
|
|
479
|
+
"figure.label.primitive-portal": "문틀",
|
|
480
|
+
"figure.label.state-hidden": "지금 상태에서 안 보임",
|
|
481
|
+
"figure.label.follows": "크기 따라가기",
|
|
482
|
+
"figure.text.follows-explained": "이 변이 무엇을 따라 늘어나는지 정합니다. 보드에서 도형을 늘리면 그것을 따라갑니다.",
|
|
483
|
+
"figure.label.follows-nothing": "고정",
|
|
484
|
+
"figure.label.follows-instance": "인스턴스 크기",
|
|
485
|
+
"figure.label.follows-part": "다른 부품",
|
|
486
|
+
"figure.label.times": "배",
|
|
487
|
+
"figure.label.plus": "더하기 (mm)",
|
|
488
|
+
"figure.label.fastening": "붙이기",
|
|
489
|
+
"figure.text.fastening-explained": "이 부품의 어느 면을 어느 부품의 어느 면에 붙일지 정합니다. 크기를 바꿔도 붙어 있습니다.",
|
|
490
|
+
"figure.label.fastened-to": "붙은 곳",
|
|
491
|
+
"figure.label.not-fastened": "붙지 않음",
|
|
492
|
+
"figure.label.my-face": "내 면",
|
|
493
|
+
"figure.label.target-part": "상대 부품",
|
|
494
|
+
"figure.label.target-face": "상대 면",
|
|
495
|
+
"figure.label.stands-on-plane": "장착 평면에 세우기",
|
|
496
|
+
"figure.label.facing-meet": "맞닿게",
|
|
497
|
+
"figure.label.facing-flush": "같은 평면에",
|
|
498
|
+
"figure.label.gap": "간격 (mm)",
|
|
499
|
+
"figure.label.align": "면 안 위치",
|
|
500
|
+
"figure.label.align-centre": "가운데",
|
|
501
|
+
"figure.label.align-min": "낮은 쪽 모서리",
|
|
502
|
+
"figure.label.align-max": "높은 쪽 모서리",
|
|
503
|
+
"figure.button.fasten": "붙이기",
|
|
504
|
+
"figure.button.detach": "떼기",
|
|
505
|
+
"figure.label.face-left": "왼쪽",
|
|
506
|
+
"figure.label.face-right": "오른쪽",
|
|
507
|
+
"figure.label.face-bottom": "아랫면",
|
|
508
|
+
"figure.label.face-top": "윗면",
|
|
509
|
+
"figure.label.face-back": "뒷면",
|
|
510
|
+
"figure.label.face-front": "앞면",
|
|
511
|
+
"figure.label.motion": "움직임",
|
|
512
|
+
"figure.text.motion-explained": "이 부품이 어떻게 움직이는지 정합니다. 붙어 있는 관계는 그대로 두고 그 위에 더합니다.",
|
|
513
|
+
"figure.label.motion-none": "움직이지 않음",
|
|
514
|
+
"figure.label.motion-slide": "미끄러짐",
|
|
515
|
+
"figure.label.motion-turn": "회전",
|
|
516
|
+
"figure.label.motion-axis": "축",
|
|
517
|
+
"figure.label.control-id": "조작 이름",
|
|
518
|
+
"figure.label.control-unit": "단위",
|
|
519
|
+
"figure.label.control-range": "범위",
|
|
520
|
+
"figure.label.control-start": "시작값",
|
|
521
|
+
"figure.label.travel-follows": "이동 거리가 따르는 것",
|
|
522
|
+
"figure.button.add-motion": "움직임 추가",
|
|
523
|
+
"figure.button.remove-motion": "움직임 없애기",
|
|
524
|
+
"figure.label.room": "차지하는 공간",
|
|
525
|
+
"figure.text.room-explained": "부품에서 계산한 경계는 제안입니다. 확인해야 선언됩니다. 모델이 바뀌어도 선언은 저절로 넓어지지 않습니다.",
|
|
526
|
+
"figure.label.room-rest": "지금 자세",
|
|
527
|
+
"figure.label.room-range": "움직임 전체",
|
|
528
|
+
"figure.button.propose-room": "경계 계산",
|
|
529
|
+
"figure.button.confirm-room": "이 경계로 선언",
|
|
530
|
+
"figure.label.room-undeclared": "아직 선언하지 않음",
|
|
531
|
+
"figure.label.room-declared": "선언됨",
|
|
532
|
+
"figure.label.mounting-face": "장착면",
|
|
533
|
+
"figure.text.mounting-face-explained": "도형이 무엇 위에 놓이는지, 그 면을 지정합니다. 공간 선언과는 별개입니다.",
|
|
534
|
+
"figure.button.set-mounting-face": "이 면으로",
|
|
535
|
+
"figure.button.clear-mounting-face": "지정 해제",
|
|
536
|
+
"figure.label.mounting-face-none": "지정 안 함",
|
|
537
|
+
"figure.label.dimensions": "치수",
|
|
538
|
+
"figure.label.part-id": "부품 id",
|
|
539
|
+
"figure.text.part-id-is-stable": "부품 id 는 붙이기·움직임·연결이 가리키는 이름이라 바꾸지 않습니다.",
|
|
540
|
+
"figure.label.verdict-proven": "전 범위 증명",
|
|
541
|
+
"figure.label.verdict-sampled": "표본 검사",
|
|
542
|
+
"figure.label.verdict-defaults": "기본 상태만",
|
|
543
|
+
"figure.text.verdict-sampled-explained": "증명하지 못한 연산이 있어 여러 상태를 표본으로 검사했습니다. 검사한 상태 사이에서 어긋날 수 있습니다.",
|
|
544
|
+
"figure.label.bodies-drawn": "그리는 부품",
|
|
545
|
+
"figure.label.bodies-hidden": "숨은 부품",
|
|
546
|
+
"figure.label.state-inputs": "상태 입력",
|
|
547
|
+
"figure.label.design-inputs": "설계 입력",
|
|
548
|
+
"figure.text.release-blocked": "발행이 막힙니다 — {count}건",
|
|
549
|
+
"figure.text.release-ready": "발행할 수 있습니다"
|
|
472
550
|
}
|