@things-factory/figure-ui 10.1.27 → 10.1.29
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 +23 -0
- package/client/modeller/angle-dial.ts +70 -0
- package/client/modeller/base-box.ts +104 -0
- package/client/modeller/figure-animations.ts +90 -17
- package/client/modeller/figure-canvas.ts +24 -6
- package/client/modeller/figure-inspector.ts +169 -5
- package/client/modeller/figure-preview.ts +110 -3
- package/client/modeller/figure-report.ts +20 -32
- package/client/modeller/figure-side.ts +10 -1
- package/client/modeller/figure-source.ts +8 -0
- package/client/modeller/joint-edits.ts +66 -5
- package/client/modeller/joint-gizmo.ts +180 -0
- package/client/modeller/part-edits.ts +75 -9
- package/client/modeller/pose-defaults.ts +67 -0
- package/client/modeller/proposal.ts +9 -3
- package/client/modeller/scene-budget.ts +31 -40
- package/client/pages/figure-list-page.ts +4 -9
- package/client/pages/figure-modeller-page.ts +144 -43
- package/client/types.ts +2 -0
- package/dist-client/graphql/index.d.ts +7 -0
- package/dist-client/graphql/index.js +21 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/angle-dial.d.ts +32 -0
- package/dist-client/modeller/angle-dial.js +41 -0
- package/dist-client/modeller/angle-dial.js.map +1 -0
- package/dist-client/modeller/base-box.d.ts +47 -0
- package/dist-client/modeller/base-box.js +52 -0
- package/dist-client/modeller/base-box.js.map +1 -0
- package/dist-client/modeller/figure-animations.d.ts +19 -4
- package/dist-client/modeller/figure-animations.js +86 -17
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +9 -0
- package/dist-client/modeller/figure-canvas.js +20 -6
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +37 -0
- package/dist-client/modeller/figure-inspector.js +159 -5
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +26 -0
- package/dist-client/modeller/figure-preview.js +99 -3
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +11 -6
- package/dist-client/modeller/figure-report.js +20 -35
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +5 -1
- package/dist-client/modeller/figure-side.js +13 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +7 -0
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/joint-edits.d.ts +23 -3
- package/dist-client/modeller/joint-edits.js +53 -5
- package/dist-client/modeller/joint-edits.js.map +1 -1
- package/dist-client/modeller/joint-gizmo.d.ts +34 -0
- package/dist-client/modeller/joint-gizmo.js +158 -0
- package/dist-client/modeller/joint-gizmo.js.map +1 -0
- package/dist-client/modeller/part-edits.d.ts +29 -4
- package/dist-client/modeller/part-edits.js +61 -9
- package/dist-client/modeller/part-edits.js.map +1 -1
- package/dist-client/modeller/pose-defaults.d.ts +9 -0
- package/dist-client/modeller/pose-defaults.js +35 -0
- package/dist-client/modeller/pose-defaults.js.map +1 -0
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +8 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/modeller/scene-budget.d.ts +31 -34
- package/dist-client/modeller/scene-budget.js +28 -37
- package/dist-client/modeller/scene-budget.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +4 -9
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +50 -0
- package/dist-client/pages/figure-modeller-page.js +141 -42
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -0
- package/dist-client/types.js.map +1 -1
- package/package.json +5 -5
- package/test/ai-proposal-contract.test.ts +16 -11
- package/test/angle-dial.test.ts +78 -0
- package/test/base-box.test.ts +165 -0
- package/test/expect.ts +2 -0
- package/test/i18n-prefix-guard.test.ts +17 -0
- package/test/joint-edits.test.ts +37 -2
- package/test/joint-gizmo.test.ts +174 -0
- package/test/joint-needle.test.ts +162 -0
- package/test/part-edits.test.ts +100 -0
- package/test/pose-defaults.test.ts +71 -0
- package/test/scene-budget.test.ts +82 -0
- package/translations/en.json +16 -9
- package/translations/ja.json +16 -9
- package/translations/ko.json +16 -9
- package/translations/ms.json +16 -9
- package/translations/zh.json +16 -9
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from 'node:fs'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
import { compile, validate } from '@hatiolab/figure-model'
|
|
8
|
+
import type { FigureSource } from '@hatiolab/figure-model'
|
|
9
|
+
|
|
10
|
+
import { jointNeedles, NEEDLE } from '../client/modeller/joint-gizmo.ts'
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* 미리보기에서 관절이 돌았다는 것을 보이게 하는 바늘.
|
|
14
|
+
*
|
|
15
|
+
* 이 시험이 지키는 것은 하나다 — **축에서 비켜나 있을 것.** 축 위에 붙으면 대칭인 부품과 같이
|
|
16
|
+
* 돌면서 같은 그림이 되고, 그러면 바늘을 붙인 뜻이 없어진다. 없는 것보다 나쁘다: 화면에 무엇이
|
|
17
|
+
* 하나 더 생겼는데 여전히 아무것도 안 움직인다.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const source = (over: Partial<FigureSource> = {}): FigureSource => ({
|
|
21
|
+
version: 2,
|
|
22
|
+
type: 'ARM',
|
|
23
|
+
base: { x: 400, y: 1200, z: 400 },
|
|
24
|
+
placement: 'floor',
|
|
25
|
+
detailLevel: 'M',
|
|
26
|
+
parts: [
|
|
27
|
+
{
|
|
28
|
+
name: 'post',
|
|
29
|
+
primitive: 'cylinder',
|
|
30
|
+
transform: { position: { x: 0, y: 100, z: 0 }, size: { x: 200, y: 200, z: 200 } },
|
|
31
|
+
material: { token: 'palette.dark' }
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'arm',
|
|
35
|
+
primitive: 'cylinder',
|
|
36
|
+
transform: { position: { x: 0, y: 400, z: 0 }, size: { x: 120, y: 400, z: 120 } },
|
|
37
|
+
material: { token: 'palette.primary' },
|
|
38
|
+
parent: 'post'
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
joints: [
|
|
42
|
+
{
|
|
43
|
+
name: 'turn',
|
|
44
|
+
child: 'arm',
|
|
45
|
+
type: 'revolute',
|
|
46
|
+
origin: { x: 0, y: 200, z: 0 },
|
|
47
|
+
axis: { x: 0, y: 1, z: 0 },
|
|
48
|
+
limits: { min: -90, max: 90 }
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
...over
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('관절 바늘', () => {
|
|
55
|
+
it('관절이 모는 부품에 자식으로 붙는다 — 그래야 관절이 도는 만큼 같이 돈다', () => {
|
|
56
|
+
const [needle] = jointNeedles(source())
|
|
57
|
+
|
|
58
|
+
assert.equal(needle!.parent, 'arm')
|
|
59
|
+
assert.equal(needle!.name, `${NEEDLE}turn`)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('축에서 비켜나 선다 — 축 위에 있으면 대칭인 부품과 같은 그림이다', () => {
|
|
63
|
+
const [needle] = jointNeedles(source())
|
|
64
|
+
const { position, size } = needle!.transform
|
|
65
|
+
|
|
66
|
+
/* y 축으로 도는 관절: 바늘은 x 로 비켜나고 x 로 길다. */
|
|
67
|
+
assert.ok(position.x > 0, '축에서 비켜났다')
|
|
68
|
+
assert.equal(position.y, 200, '기준점 높이 그대로')
|
|
69
|
+
assert.ok(size.x > size.y && size.x > size.z, 'x 로 길다')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('가로축으로 도는 관절은 z 로 비켜난다', () => {
|
|
73
|
+
const [needle] = jointNeedles(source({ joints: [{ ...source().joints![0]!, axis: { x: 1, y: 0, z: 0 } }] }))
|
|
74
|
+
const { position, size } = needle!.transform
|
|
75
|
+
|
|
76
|
+
assert.ok(position.z > 0)
|
|
77
|
+
assert.ok(size.z > size.x && size.z > size.y)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('세로축(z)으로 도는 관절은 x 로 비켜난다', () => {
|
|
81
|
+
const [needle] = jointNeedles(source({ joints: [{ ...source().joints![0]!, axis: { x: 0, y: 0, z: 1 } }] }))
|
|
82
|
+
assert.ok(needle!.transform.position.x > 0)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('비스듬한 축은 그리지 않는다 — 비켜날 쪽을 못 고른다', () => {
|
|
86
|
+
const oblique = source({ joints: [{ ...source().joints![0]!, axis: { x: 1, y: 1, z: 0 } }] })
|
|
87
|
+
assert.deepEqual(jointNeedles(oblique), [])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('길이는 도는 부품에서 온다. 작은 부품에서도 보일 만큼은 된다', () => {
|
|
91
|
+
const [big] = jointNeedles(source())
|
|
92
|
+
assert.equal(big!.transform.size.x, Math.round(400 * 0.6))
|
|
93
|
+
|
|
94
|
+
const small = source({
|
|
95
|
+
parts: source().parts.map(part =>
|
|
96
|
+
part.name === 'arm'
|
|
97
|
+
? { ...part, transform: { ...part.transform, size: { x: 10, y: 10, z: 10 } } }
|
|
98
|
+
: part
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
assert.equal(jointNeedles(small)[0]!.transform.size.x, 60)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('굵기는 길이에 견준다 — 8mm 로 고정했더니 미리보기에서 한 픽셀이 안 됐다', () => {
|
|
105
|
+
const [needle] = jointNeedles(source())
|
|
106
|
+
const { size } = needle!.transform
|
|
107
|
+
|
|
108
|
+
/* 길이 240 짜리 막대는 48 로 두껍다. 부품이 작아도 6 밑으로는 안 내려간다. */
|
|
109
|
+
assert.equal(size.y, Math.round(size.x / 5))
|
|
110
|
+
assert.equal(size.z, size.y)
|
|
111
|
+
|
|
112
|
+
const small = source({
|
|
113
|
+
parts: source().parts.map(part =>
|
|
114
|
+
part.name === 'arm' ? { ...part, transform: { ...part.transform, size: { x: 10, y: 10, z: 10 } } } : part
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
assert.equal(jointNeedles(small)[0]!.transform.size.y, Math.max(6, Math.round(60 / 5)))
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('관절이 없으면 바늘도 없다', () => {
|
|
121
|
+
assert.deepEqual(jointNeedles(source({ joints: [] })), [])
|
|
122
|
+
assert.deepEqual(jointNeedles(source({ joints: undefined })), [])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('바늘을 붙인 형식이 그대로 선다 — 미리보기가 이것을 compile 한다', () => {
|
|
126
|
+
const shown: FigureSource = { ...source(), parts: [...source().parts, ...jointNeedles(source())] }
|
|
127
|
+
|
|
128
|
+
assert.deepEqual(validate(shown).errors, [])
|
|
129
|
+
assert.doesNotThrow(() => compile(shown))
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* 바늘은 **미리보기에만** 있다. 저장 형식으로 새어 나가면 발행된 도형에 없던 막대가 생기고,
|
|
135
|
+
* 계약 판정에서는 부품이 하나 늘어난 것으로 읽힌다.
|
|
136
|
+
*/
|
|
137
|
+
describe('바늘이 미리보기 밖으로 안 나간다', () => {
|
|
138
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
139
|
+
|
|
140
|
+
it('미리보기가 붙여서 세운다', () => {
|
|
141
|
+
const preview = read('../client/modeller/figure-preview.ts')
|
|
142
|
+
|
|
143
|
+
assert.ok(/jointNeedles\(source\)/.test(preview), '붙인다')
|
|
144
|
+
assert.ok(/registerFigure\(compile\(shown\)/.test(preview), '붙인 것을 세운다')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('저작면과 저장은 원본 그대로다', () => {
|
|
148
|
+
const page = read('../client/pages/figure-modeller-page.ts')
|
|
149
|
+
const canvas = read('../client/modeller/figure-canvas.ts')
|
|
150
|
+
|
|
151
|
+
assert.ok(!/jointNeedles/.test(page), '페이지는 바늘을 모른다')
|
|
152
|
+
assert.ok(!/jointNeedles/.test(canvas), '저작 캔버스는 바늘을 모른다')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('원본 목록을 건드리지 않는다 — 같은 배열에 밀어 넣으면 저장에 들어간다', () => {
|
|
156
|
+
const original = source()
|
|
157
|
+
const before = original.parts.length
|
|
158
|
+
jointNeedles(original)
|
|
159
|
+
|
|
160
|
+
assert.equal(original.parts.length, before)
|
|
161
|
+
})
|
|
162
|
+
})
|
package/test/part-edits.test.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import { describe, it, test } from 'node:test'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
3
5
|
|
|
4
6
|
import { expect } from './expect.ts'
|
|
5
7
|
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
niceStep,
|
|
18
20
|
partLimit,
|
|
19
21
|
removePart,
|
|
22
|
+
roundPart,
|
|
20
23
|
snapPart,
|
|
21
24
|
updatePart
|
|
22
25
|
} from '../client/modeller/part-edits.ts'
|
|
@@ -55,6 +58,53 @@ const DECK: PartModel = {
|
|
|
55
58
|
token: 'palette.primary'
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/*
|
|
62
|
+
* 기즈모에서 온 숫자.
|
|
63
|
+
*
|
|
64
|
+
* 화면에서 끌면 씬이 비율을 곱한 값을 돌려준다. 그 값이 그대로 부품에 들어가면 저장 형식에
|
|
65
|
+
* 소수 넷째 자리가 남고, 크기 칸은 잘려서 보인다.
|
|
66
|
+
*/
|
|
67
|
+
describe('씬에서 온 부품을 밀리미터로 맞춘다', () => {
|
|
68
|
+
const part = (over: Partial<PartModel> = {}): PartModel => ({ ...DECK, ...over })
|
|
69
|
+
|
|
70
|
+
it('끈 크기와 자리를 정수로 맞춘다', () => {
|
|
71
|
+
const dragged = part({ left: 384.99999999999994, top: 380, zPos: 930, width: 368.98743869823886, height: 140, depth: 722.4180046035418 })
|
|
72
|
+
const fixed = roundPart(dragged)
|
|
73
|
+
|
|
74
|
+
expect(fixed.left).toBe(385)
|
|
75
|
+
expect(fixed.width).toBe(369)
|
|
76
|
+
expect(fixed.depth).toBe(722)
|
|
77
|
+
expect(fixed.top).toBe(380)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('크기는 1 밑으로 안 내려간다 — 0 이면 부피가 사라지고 배율이 0 으로 나뉜다', () => {
|
|
81
|
+
const tiny = roundPart(part({ width: 0.2, height: 0.4, depth: 0 }))
|
|
82
|
+
expect(tiny.width).toBe(1)
|
|
83
|
+
expect(tiny.height).toBe(1)
|
|
84
|
+
expect(tiny.depth).toBe(1)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('맞춘 값을 다시 맞춰도 같다', () => {
|
|
88
|
+
const once = roundPart(part({ left: 12.5, width: 7.5 }))
|
|
89
|
+
expect(roundPart(once)).toEqual(once)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('캔버스가 씬의 변경을 받을 때 그것을 지난다 — 안 부르면 위 넷이 전부 초록인 채로 소수가 남는다', () => {
|
|
93
|
+
const canvas = readFileSync(fileURLToPath(new URL('../client/modeller/figure-canvas.ts', import.meta.url)), 'utf8')
|
|
94
|
+
|
|
95
|
+
assert.ok(/roundPart/.test(canvas), 'import')
|
|
96
|
+
assert.ok(/this\.tell\('part-changed', \{ name, part: roundPart\(/.test(canvas), '올려 보내기 전에 맞춘다')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('크기·자리 밖의 값은 건드리지 않는다', () => {
|
|
100
|
+
const kept = roundPart(part({ name: 'arm', token: 'palette.dark', segments: 12, zPos: 3.4 }))
|
|
101
|
+
expect(kept.name).toBe('arm')
|
|
102
|
+
expect(kept.token).toBe('palette.dark')
|
|
103
|
+
expect(kept.segments).toBe(12)
|
|
104
|
+
expect(kept.zPos).toBe(3)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
58
108
|
describe('부품 생성', () => {
|
|
59
109
|
it('기준 상자의 3분의 1 크기로 생성된다 — 추가하자마자 보여야 한다', () => {
|
|
60
110
|
const part = newPart(DRAFT, [], 'cube')
|
|
@@ -98,6 +148,56 @@ describe('부품 생성', () => {
|
|
|
98
148
|
expect(newPart(DRAFT, [], 'polygon').path).toHaveLength(3)
|
|
99
149
|
})
|
|
100
150
|
|
|
151
|
+
/*
|
|
152
|
+
* 둘째 부품이 어디에 서는가.
|
|
153
|
+
*
|
|
154
|
+
* 언제나 바닥 한가운데였을 때, 둘째 부품은 첫 부품 **속에** 들어갔다 — 같은 자리에 같은
|
|
155
|
+
* 크기라 화면에는 덩어리 하나뿐이고 저작자는 무엇이 늘었는지 못 봤다.
|
|
156
|
+
*/
|
|
157
|
+
describe('고른 부품 위에 얹는다', () => {
|
|
158
|
+
/* 갑판을 바닥에 내려 둔다 — 그 위에 3분의 1 짜리가 들어가야 얹는 갈래를 볼 수 있다. */
|
|
159
|
+
const FLOOR_DECK = { ...DECK, zPos: 0 }
|
|
160
|
+
|
|
161
|
+
it('윗면에 서고 발자국이 가운데로 맞는다', () => {
|
|
162
|
+
const part = newPart(DRAFT, [FLOOR_DECK], 'cube', FLOOR_DECK)
|
|
163
|
+
|
|
164
|
+
expect(part.zPos).toBe(FLOOR_DECK.zPos + FLOOR_DECK.depth)
|
|
165
|
+
// 자리도 크기도 정수로 맞추므로 0.5 까지는 어긋난다
|
|
166
|
+
const middle = { x: FLOOR_DECK.left + FLOOR_DECK.width / 2, y: FLOOR_DECK.top + FLOOR_DECK.height / 2 }
|
|
167
|
+
expect(Math.abs(part.left + part.width / 2 - middle.x)).toBeLessThanOrEqual(0.5)
|
|
168
|
+
expect(Math.abs(part.top + part.height / 2 - middle.y)).toBeLessThanOrEqual(0.5)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('얹으면 상자를 넘을 때는 바닥 한가운데로 간다 — 추가가 발행을 막지 않는다', () => {
|
|
172
|
+
/* 갑판이 이미 높다: 500 + 200 위에 267 을 더 쌓으면 상자 높이 800 을 넘는다. */
|
|
173
|
+
const part = newPart(DRAFT, [DECK], 'cube', DECK)
|
|
174
|
+
|
|
175
|
+
expect(part.zPos).toBe(0)
|
|
176
|
+
expect(Math.abs(part.left + part.width / 2 - DRAFT.width / 2)).toBeLessThanOrEqual(0.5)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('가장자리 부품 위에서도 상자 안에 남는다', () => {
|
|
180
|
+
const corner = { ...DECK, left: 0, top: 0, width: 200, height: 200, zPos: 0, depth: 100 }
|
|
181
|
+
const part = newPart(DRAFT, [corner], 'cube', corner)
|
|
182
|
+
|
|
183
|
+
expect(part.left).toBe(0)
|
|
184
|
+
expect(part.top).toBe(0)
|
|
185
|
+
expect(part.left + part.width).toBeLessThanOrEqual(DRAFT.width)
|
|
186
|
+
expect(part.top + part.height).toBeLessThanOrEqual(DRAFT.height)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('고른 것이 없으면 전과 같이 바닥 한가운데다', () => {
|
|
190
|
+
const alone = newPart(DRAFT, [DECK], 'cube')
|
|
191
|
+
expect(alone.zPos).toBe(0)
|
|
192
|
+
expect(Math.abs(alone.left + alone.width / 2 - DRAFT.width / 2)).toBeLessThanOrEqual(0.5)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('쌓은 둘이 서로 겹치지 않는다 — 이 규칙이 지키는 것이 그것이다', () => {
|
|
196
|
+
const [, stacked] = addPart(DRAFT, [FLOOR_DECK], 'cylinder', FLOOR_DECK)
|
|
197
|
+
expect(stacked!.zPos).toBeGreaterThanOrEqual(FLOOR_DECK.zPos + FLOOR_DECK.depth)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
101
201
|
/*
|
|
102
202
|
화면에서 멀쩡해 보여도 저장이 거부되면 사용자는 이유를 알 수 없다. 새로 만든 부품은
|
|
103
203
|
손대지 않은 상태로도 검증을 통과해야 한다.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { poseAsDefaults, poseChanges } from '../client/modeller/pose-defaults.ts'
|
|
5
|
+
import type { FigureDraft } from '../client/modeller/figure-source.ts'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* 미리보기에서 세운 자세를 기본값으로 앉힌다.
|
|
9
|
+
*
|
|
10
|
+
* 관절 도형의 휴지 자세(값 0)는 곧게 선 기둥이라, 기본값이 없으면 목록에서도 도면에서도 그
|
|
11
|
+
* 모양으로 선다. 옮겨 적는 일을 손으로 하면 여섯 축에 여섯 번이고, 옮기는 사이 화면의 자세와
|
|
12
|
+
* 적은 수가 갈린다.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const DRAFT: FigureDraft = {
|
|
16
|
+
version: 2,
|
|
17
|
+
figureType: 'ARM',
|
|
18
|
+
width: 900,
|
|
19
|
+
height: 900,
|
|
20
|
+
depth: 1900,
|
|
21
|
+
detailLevel: 'L',
|
|
22
|
+
parameters: [
|
|
23
|
+
{ name: 'shoulder', range: { unit: 'deg', min: -120, max: 120 }, default: 0, clip: { channels: [] } },
|
|
24
|
+
{ name: 'elbow', range: { unit: 'deg', min: -150, max: 150 }, default: 0, clip: { channels: [] } },
|
|
25
|
+
/* 기본값을 안 적은 파라미터. 형식은 그때 범위의 처음에 선다. */
|
|
26
|
+
{ name: 'hoist', range: { unit: 'mm', min: 0, max: 1800 }, clip: { channels: [] } }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('이 자세를 기본으로', () => {
|
|
31
|
+
it('민 것만 앉히고 나머지는 건드리지 않는다', () => {
|
|
32
|
+
const next = poseAsDefaults(DRAFT, { shoulder: -35 })
|
|
33
|
+
|
|
34
|
+
assert.equal(next.parameters![0]!.default, -35)
|
|
35
|
+
assert.equal(next.parameters![1], DRAFT.parameters![1], '안 민 파라미터는 같은 객체 그대로다')
|
|
36
|
+
assert.equal(next.parameters![2], DRAFT.parameters![2])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('이미 그 값이면 초안을 그대로 돌려준다 — 고친 것이 없는데 「저장 안 됨」이 붙지 않게', () => {
|
|
40
|
+
assert.equal(poseAsDefaults(DRAFT, { shoulder: 0 }), DRAFT)
|
|
41
|
+
assert.equal(poseAsDefaults(DRAFT, {}), DRAFT)
|
|
42
|
+
/* 기본값을 안 적은 파라미터는 범위의 처음에 서 있다. 그 값을 다시 적지 않는다. */
|
|
43
|
+
assert.equal(poseAsDefaults(DRAFT, { hoist: 0 }), DRAFT)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('기본값을 안 적었던 파라미터에도 앉는다', () => {
|
|
47
|
+
const next = poseAsDefaults(DRAFT, { hoist: 1200 })
|
|
48
|
+
assert.equal(next.parameters![2]!.default, 1200)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('범위 밖은 끝에서 문다 — 다른 길로 들어온 값도 형식을 지나야 한다', () => {
|
|
52
|
+
const next = poseAsDefaults(DRAFT, { elbow: 400 })
|
|
53
|
+
assert.equal(next.parameters![1]!.default, 150)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('수가 아닌 값은 안 말한 것으로 친다', () => {
|
|
57
|
+
for (const bad of [NaN, Infinity, undefined as unknown as number]) {
|
|
58
|
+
assert.equal(poseAsDefaults(DRAFT, { shoulder: bad }), DRAFT, String(bad))
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('무엇이 달라지는지 먼저 말할 수 있다 — 단추를 띄울지 이것으로 정한다', () => {
|
|
63
|
+
assert.deepEqual(poseChanges(DRAFT, { shoulder: -35, elbow: 0 }), [{ name: 'shoulder', from: 0, to: -35 }])
|
|
64
|
+
assert.deepEqual(poseChanges(DRAFT, { shoulder: 0 }), [])
|
|
65
|
+
assert.deepEqual(poseChanges(undefined, { shoulder: -35 }), [])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('없는 파라미터 이름은 무시한다 — 후보를 보다 남은 값이 섞여도 초안을 늘리지 않는다', () => {
|
|
69
|
+
assert.equal(poseAsDefaults(DRAFT, { 'no-such-thing': 42 }), DRAFT)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from 'node:fs'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
import { capacityFrom, MEASURED, SCENE_BUDGET } from '../client/modeller/scene-budget.ts'
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* 「몇 개까지 편한가」의 분모.
|
|
12
|
+
*
|
|
13
|
+
* 이 수는 **잰 값이다**(2026-09-18, Apple M5). 시험이 지키는 것은 셈이 아니라 그 사실이다 —
|
|
14
|
+
* 분모가 조용히 목표치로 돌아가거나, 잰 기계와 날짜가 화면에서 빠지면 사람이 어디서나 참인
|
|
15
|
+
* 수로 읽는다.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
describe('한 화면이 감당하는 양', () => {
|
|
19
|
+
it('분모는 화면에 보이는 부품 묶음이고, 잰 기계와 날짜를 달고 있다', () => {
|
|
20
|
+
assert.equal(SCENE_BUDGET.groupsOnScreen, 4000)
|
|
21
|
+
assert.equal(MEASURED.gpu, 'Apple M5')
|
|
22
|
+
assert.equal(MEASURED.on, '2026-09-18')
|
|
23
|
+
assert.equal(MEASURED.fps, 60)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('묶음으로 갈리면 묶음 수로 나눈다 — 그림자를 다시 곱하지 않는다', () => {
|
|
27
|
+
/*
|
|
28
|
+
실측이 그림자를 켠 채 잰 값이라 배수가 이미 안에 있다. 전에는 여기서 2 를 곱했고,
|
|
29
|
+
그 곱을 남겨 두면 같은 값을 두 번 세어 개수가 절반으로 나온다.
|
|
30
|
+
*/
|
|
31
|
+
const robot = capacityFrom(9, 200)!
|
|
32
|
+
assert.equal(robot.bound, 'groups')
|
|
33
|
+
assert.equal(robot.per, 9)
|
|
34
|
+
assert.equal(robot.instances, Math.floor(4000 / 9))
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('삼각형이 먼저 차면 그쪽으로 갈린다', () => {
|
|
38
|
+
const heavy = capacityFrom(2, 100_000)!
|
|
39
|
+
assert.equal(heavy.bound, 'triangles')
|
|
40
|
+
assert.equal(heavy.instances, 5)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('잴 것이 없으면 답하지 않는다 — 「무한히 놓을 수 있다」가 아니다', () => {
|
|
44
|
+
assert.equal(capacityFrom(0, 0), undefined)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
실측한 두 도형이 이 분모로 되돌아오는지 본다. 분모를 누가 고치면 이 둘이 먼저 어긋난다.
|
|
49
|
+
*/
|
|
50
|
+
it('잰 두 도형이 잰 수와 맞다', () => {
|
|
51
|
+
/* 묶음 9 짜리 6 축 로봇: 600 대에서 59.5fps, 700 대에서 56.7fps 였다. */
|
|
52
|
+
assert.ok(capacityFrom(9, 200)!.instances <= 600, '잰 값보다 많이 놓으라고 하면 안 된다')
|
|
53
|
+
/* 묶음 2 짜리 컨베이어: 1,600 대에서 59.3fps, 2,500 대에서 36.8fps 였다. */
|
|
54
|
+
const conveyor = capacityFrom(2, 100)!
|
|
55
|
+
assert.ok(conveyor.instances >= 1600 && conveyor.instances <= 2500)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* 놓는 개수로 값을 말하지 않는다 — ADR-0051 ③.
|
|
61
|
+
*
|
|
62
|
+
* 보고서에 「100개 기준 draw call」 · 「1만개 기준」이 오래 남아 있었다. 놓는 개수는 값을 치르지
|
|
63
|
+
* 않는다(가만히 선 도형은 프레임 비용 0, 카메라 밖은 컬링). 치르는 것은 한 화면에 보이는 개수 ×
|
|
64
|
+
* 그 도형의 묶음 수이고, 그 답은 위의 실측 분모가 한다.
|
|
65
|
+
*/
|
|
66
|
+
describe('놓는 개수 기준 문구가 화면에 없다', () => {
|
|
67
|
+
const HERE = fileURLToPath(new URL('.', import.meta.url))
|
|
68
|
+
|
|
69
|
+
it('보고서가 「N개 기준」 칸을 그리지 않는다', () => {
|
|
70
|
+
const report = readFileSync(join(HERE, '../client/modeller/figure-report.ts'), 'utf8')
|
|
71
|
+
|
|
72
|
+
assert.ok(!/at-100|at-10k/.test(report), '개수 기준 칸')
|
|
73
|
+
assert.ok(!/costOf\(blueprint, \d/.test(report), '개수를 넣어 셈하지 않는다')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('번역에 그 낱말이 남아 있지 않다', () => {
|
|
77
|
+
const ko = JSON.parse(readFileSync(join(HERE, '../translations/ko.json'), 'utf8')) as Record<string, string>
|
|
78
|
+
const left = Object.entries(ko).filter(([key]) => /at-100|at-10k/.test(key))
|
|
79
|
+
|
|
80
|
+
assert.deepEqual(left, [])
|
|
81
|
+
})
|
|
82
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"figure.button.delete-figure": "delete",
|
|
13
13
|
"figure.button.discard": "discard",
|
|
14
14
|
"figure.button.duplicate-part": "duplicate",
|
|
15
|
+
"figure.button.fit-base-to-parts": "fit to parts",
|
|
15
16
|
"figure.button.mirror-x": "mirror X",
|
|
16
17
|
"figure.button.mirror-z": "mirror Z",
|
|
17
18
|
"figure.button.move-part-down": "move down",
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"figure.button.new-figure": "new figure",
|
|
20
21
|
"figure.button.pivot-clear": "part centre",
|
|
21
22
|
"figure.button.pivot-set": "set",
|
|
23
|
+
"figure.button.pose-as-default": "make this the default pose",
|
|
22
24
|
"figure.button.preview-fix": "preview fix · {part} {axis} face: {from} → {to}",
|
|
23
25
|
"figure.button.redo": "redo",
|
|
24
26
|
"figure.button.release-figure": "release",
|
|
@@ -82,8 +84,9 @@
|
|
|
82
84
|
"figure.label.axis-scale": "scale",
|
|
83
85
|
"figure.label.axis-scale-short": "scale",
|
|
84
86
|
"figure.label.axis-width": "width",
|
|
85
|
-
"figure.label.
|
|
87
|
+
"figure.label.base-box": "base box (mm)",
|
|
86
88
|
"figure.label.budget-groups": "material groups",
|
|
89
|
+
"figure.label.budget-independent": "independently moved parts",
|
|
87
90
|
"figure.label.budget-parts": "part count",
|
|
88
91
|
"figure.label.budget-surface": "surfaces outside the palette",
|
|
89
92
|
"figure.label.budget-transparent": "transparent materials",
|
|
@@ -110,8 +113,6 @@
|
|
|
110
113
|
"figure.label.distinct-materials": "distinct materials",
|
|
111
114
|
"figure.label.distinct-shapes": "distinct shapes",
|
|
112
115
|
"figure.label.draft": "draft",
|
|
113
|
-
"figure.label.draw-calls-at-100": "draw calls for 100",
|
|
114
|
-
"figure.label.draw-calls-at-10k": "draw calls at 10k instances",
|
|
115
116
|
"figure.label.emissive": "emissive",
|
|
116
117
|
"figure.label.emissive-color": "lit color",
|
|
117
118
|
"figure.label.environment": "environment",
|
|
@@ -260,8 +261,8 @@
|
|
|
260
261
|
"figure.label.tools": "align",
|
|
261
262
|
"figure.label.transparent": "transparent",
|
|
262
263
|
"figure.label.triangles": "triangles",
|
|
263
|
-
"figure.label.
|
|
264
|
-
"figure.label.
|
|
264
|
+
"figure.label.unit-none": "no unit",
|
|
265
|
+
"figure.label.unit-type-it": "type it…",
|
|
265
266
|
"figure.label.weight": "drawing cost",
|
|
266
267
|
"figure.label.what-this-does": "what this figure does",
|
|
267
268
|
"figure.text.accepts-anything": "empty means anything",
|
|
@@ -271,15 +272,15 @@
|
|
|
271
272
|
"figure.text.already-released-edit-to-release-again": "already released. edit and save to go back to draft, then release again.",
|
|
272
273
|
"figure.text.anchor-explained": "where this part stays as the box grows. two touching parts on different faces drift apart as it grows.",
|
|
273
274
|
"figure.text.anchors-are-not-drawn": "slots and ports are not drawn on a drawing; only the editor shows them.",
|
|
274
|
-
"figure.text.animated-parts-cost": "each moving part adds one draw call.",
|
|
275
|
-
"figure.text.animated-parts-over": "over the limit. many of these on one drawing will slow it down.",
|
|
276
275
|
"figure.text.animation-empty-examples": "a turning roller · a sliding door leaf · a rising lift · a blinking beacon.",
|
|
277
276
|
"figure.text.animation-empty-what-it-is": "no motion yet. add a clip to move parts.",
|
|
278
277
|
"figure.text.animation-needs-a-named-part": "add a part first.",
|
|
279
278
|
"figure.text.assistant-proposed-a-figure": "the proposal is shown on the right.",
|
|
280
279
|
"figure.text.axes-not-measurable-yet": "left off the chart because they cannot be measured: {axes}. the reasons are below.",
|
|
281
|
-
"figure.text.
|
|
280
|
+
"figure.text.base-box-explained": "The box that sets the scale when the figure is placed on a drawing. A part reaching outside it blocks release. Changing the size leaves the parts where they are.",
|
|
281
|
+
"figure.text.bound-by-groups": "Part groups fill up first — this figure is {per} groups, and one screen draws {budget} groups at 60fps.",
|
|
282
282
|
"figure.text.bound-by-triangles": "triangles reach the limit first ({per} per instance, {budget} per drawing).",
|
|
283
|
+
"figure.text.budget-measured-on": "Measured on {gpu}, {on}. A figure standing still costs nothing — only what is on screen and redrawn counts.",
|
|
283
284
|
"figure.text.budget-not-measured-yet": "this target has not been measured yet.",
|
|
284
285
|
"figure.text.cannot-release-now": "cannot be released yet",
|
|
285
286
|
"figure.text.canvas-appears-once-a-part-exists": "pick a shape on the left to add the first part.",
|
|
@@ -296,6 +297,7 @@
|
|
|
296
297
|
"figure.text.change-capabilities": "capabilities",
|
|
297
298
|
"figure.text.change-changed": "a changed part",
|
|
298
299
|
"figure.text.change-detail": "the detail level",
|
|
300
|
+
"figure.text.change-joints": "joints",
|
|
299
301
|
"figure.text.change-placement": "placement",
|
|
300
302
|
"figure.text.change-removed": "a part being removed",
|
|
301
303
|
"figure.text.change-styleKit": "the style kit",
|
|
@@ -307,6 +309,7 @@
|
|
|
307
309
|
"figure.text.clip-needs-a-name": "enter a name.",
|
|
308
310
|
"figure.text.collapse-parts-pane": "collapse the part list",
|
|
309
311
|
"figure.text.collapse-properties-pane": "collapse the property pane",
|
|
312
|
+
"figure.text.contract-not-compared": "Could not check whether this change breaks what is already placed — {why}",
|
|
310
313
|
"figure.text.coordinate-ground-explained": "draws the origin and a grid on the floor",
|
|
311
314
|
"figure.text.delete-figure-confirm": "delete {name}? drawings already using this type will no longer find it.",
|
|
312
315
|
"figure.text.designate-in-properties": "nothing designated yet. select a part and set its role in the properties tab.",
|
|
@@ -323,6 +326,7 @@
|
|
|
323
326
|
"figure.text.figure-type-example": "e.g. OHT, AGV_LIFTER",
|
|
324
327
|
"figure.text.figure-type-placeholder": "Figure type identifier (e.g. OHT)",
|
|
325
328
|
"figure.text.find-by-this-tag": "find by this tag",
|
|
329
|
+
"figure.text.fit-base-explained": "Shrinks the box to the smallest one holding every part. The centre stays put, so a part sitting off-centre leaves room on the other side.",
|
|
326
330
|
"figure.text.fits-n-instances": "up to {n}",
|
|
327
331
|
"figure.text.floor-explained": "overlaps the coordinate ground, so it is usually off",
|
|
328
332
|
"figure.text.follow-camera-explained": "turn off to fix the shadow direction",
|
|
@@ -333,7 +337,9 @@
|
|
|
333
337
|
"figure.text.hemisphere-light-explained": "even light from sky and ground. it brightens shaded faces.",
|
|
334
338
|
"figure.text.hide-all-parts": "hide all",
|
|
335
339
|
"figure.text.hide-this-part": "hide this part while you work. not saved",
|
|
336
|
-
"figure.text.how-many-fit": "how many
|
|
340
|
+
"figure.text.how-many-fit": "how many can be on screen before it slows down",
|
|
341
|
+
"figure.text.independent-parts-cost": "each part that carries its own transform adds one draw call.",
|
|
342
|
+
"figure.text.independent-parts-over": "over the limit. fewer of these fit on one screen at once.",
|
|
337
343
|
"figure.text.instances-unit": "instances",
|
|
338
344
|
"figure.text.interpolation-explained": "how the gap between keys is filled.",
|
|
339
345
|
"figure.text.joint-channel-value-deg": "key values are joint angles in degrees.",
|
|
@@ -392,6 +398,7 @@
|
|
|
392
398
|
"figure.text.picked-changes-do-not-validate": "the selected changes do not make a valid figure",
|
|
393
399
|
"figure.text.pivot-defaults-to-the-part-centre": "left unset, the part turns about its own centre. coordinates are local to the part.",
|
|
394
400
|
"figure.text.placement-explained": "sets the anchor plane in 3D scene (floor: AGV/conveyor, ceiling: OHT/hoist, center: sensor/drone).",
|
|
401
|
+
"figure.text.pose-as-default-explained": "Writes the pose you have set as the parameters’ defaults, so the figure stands like this when nobody gives it a value.",
|
|
395
402
|
"figure.text.preview-blocked-by-errors": "cannot show the preview because of format errors. see the maturity tab for the reason.",
|
|
396
403
|
"figure.text.preview-needs-a-part": "add a part to see how it looks placed on a drawing.",
|
|
397
404
|
"figure.text.proposal-feedback-note": "optional note for the next suggestion (this session only)",
|
package/translations/ja.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"figure.button.delete-figure": "delete",
|
|
13
13
|
"figure.button.discard": "discard",
|
|
14
14
|
"figure.button.duplicate-part": "duplicate",
|
|
15
|
+
"figure.button.fit-base-to-parts": "部品に合わせる",
|
|
15
16
|
"figure.button.mirror-x": "mirror X",
|
|
16
17
|
"figure.button.mirror-z": "mirror Z",
|
|
17
18
|
"figure.button.move-part-down": "move down",
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"figure.button.new-figure": "new figure",
|
|
20
21
|
"figure.button.pivot-clear": "部品の中心",
|
|
21
22
|
"figure.button.pivot-set": "設定",
|
|
23
|
+
"figure.button.pose-as-default": "この姿勢を既定に",
|
|
22
24
|
"figure.button.preview-fix": "修正案をプレビュー · {part} {axis} 固定面: {from} → {to}",
|
|
23
25
|
"figure.button.redo": "やり直す",
|
|
24
26
|
"figure.button.release-figure": "公開する",
|
|
@@ -82,8 +84,9 @@
|
|
|
82
84
|
"figure.label.axis-scale": "scale",
|
|
83
85
|
"figure.label.axis-scale-short": "scale",
|
|
84
86
|
"figure.label.axis-width": "width",
|
|
85
|
-
"figure.label.
|
|
87
|
+
"figure.label.base-box": "基準ボックス (mm)",
|
|
86
88
|
"figure.label.budget-groups": "material groups",
|
|
89
|
+
"figure.label.budget-independent": "個別に動く部品",
|
|
87
90
|
"figure.label.budget-parts": "part count",
|
|
88
91
|
"figure.label.budget-surface": "surfaces outside the palette",
|
|
89
92
|
"figure.label.budget-transparent": "transparent materials",
|
|
@@ -110,8 +113,6 @@
|
|
|
110
113
|
"figure.label.distinct-materials": "distinct materials",
|
|
111
114
|
"figure.label.distinct-shapes": "distinct shapes",
|
|
112
115
|
"figure.label.draft": "下書き",
|
|
113
|
-
"figure.label.draw-calls-at-100": "draw calls for 100",
|
|
114
|
-
"figure.label.draw-calls-at-10k": "draw calls at 10k instances",
|
|
115
116
|
"figure.label.emissive": "emissive",
|
|
116
117
|
"figure.label.emissive-color": "lit color",
|
|
117
118
|
"figure.label.environment": "environment",
|
|
@@ -260,8 +261,8 @@
|
|
|
260
261
|
"figure.label.tools": "align",
|
|
261
262
|
"figure.label.transparent": "transparent",
|
|
262
263
|
"figure.label.triangles": "triangles",
|
|
263
|
-
"figure.label.
|
|
264
|
-
"figure.label.
|
|
264
|
+
"figure.label.unit-none": "単位なし",
|
|
265
|
+
"figure.label.unit-type-it": "直接入力…",
|
|
265
266
|
"figure.label.weight": "weight",
|
|
266
267
|
"figure.label.what-this-does": "この Figure の役割",
|
|
267
268
|
"figure.text.accepts-anything": "空欄なら何でも可",
|
|
@@ -271,15 +272,15 @@
|
|
|
271
272
|
"figure.text.already-released-edit-to-release-again": "すでに公開済みです。修正して保存すると下書きに戻り、そこで再び公開できます。",
|
|
272
273
|
"figure.text.anchor-explained": "ボックスが伸びるときにこの部品が付いている面です。接する部品と違う面に付くと、伸びたときに間が空きます。",
|
|
273
274
|
"figure.text.anchors-are-not-drawn": "スロットとポートは図面には描かれず、編集画面にだけ表示されます。",
|
|
274
|
-
"figure.text.animated-parts-cost": "動く部品ごとに draw call が 1 増えます。",
|
|
275
|
-
"figure.text.animated-parts-over": "上限を超えました。1 つの図面に多く置くと画面が遅くなります。",
|
|
276
275
|
"figure.text.animation-empty-examples": "回るローラー · スライドする扉 · 上下するリフト · 点滅する表示灯。",
|
|
277
276
|
"figure.text.animation-empty-what-it-is": "まだ動きがありません。clip を追加して部品を動かしてください。",
|
|
278
277
|
"figure.text.animation-needs-a-named-part": "先に部品を追加してください。",
|
|
279
278
|
"figure.text.assistant-proposed-a-figure": "提案を右側に表示しました。",
|
|
280
279
|
"figure.text.axes-not-measurable-yet": "測定できない項目はチャートから外しました: {axes}。理由は下にあります。",
|
|
281
|
-
"figure.text.
|
|
280
|
+
"figure.text.base-box-explained": "図面に置くときの倍率を決めるボックスです。部品がこのボックスを越えると発行できません。サイズを変えても部品はその場に残ります。",
|
|
281
|
+
"figure.text.bound-by-groups": "部品グループが先に埋まります — この図形は {per} グループで、1 画面は {budget} グループまで 60fps で描きます。",
|
|
282
282
|
"figure.text.bound-by-triangles": "三角形が先に上限に達します (1 個あたり {per}、図面あたり {budget})。",
|
|
283
|
+
"figure.text.budget-measured-on": "{gpu} で {on} に測定した値です。静止している図形は負荷になりません — 画面に見えて再描画されるものだけを数えます。",
|
|
283
284
|
"figure.text.budget-not-measured-yet": "this target has not been measured yet.",
|
|
284
285
|
"figure.text.cannot-release-now": "今は公開できません",
|
|
285
286
|
"figure.text.canvas-appears-once-a-part-exists": "左の「図形」から 1 つ選んで最初の部品を追加してください。",
|
|
@@ -296,6 +297,7 @@
|
|
|
296
297
|
"figure.text.change-capabilities": "能力",
|
|
297
298
|
"figure.text.change-changed": "a changed part",
|
|
298
299
|
"figure.text.change-detail": "the detail level",
|
|
300
|
+
"figure.text.change-joints": "関節",
|
|
299
301
|
"figure.text.change-placement": "配置基準",
|
|
300
302
|
"figure.text.change-removed": "a part being removed",
|
|
301
303
|
"figure.text.change-styleKit": "the style kit",
|
|
@@ -307,6 +309,7 @@
|
|
|
307
309
|
"figure.text.clip-needs-a-name": "名前を入力してください。",
|
|
308
310
|
"figure.text.collapse-parts-pane": "collapse the part list",
|
|
309
311
|
"figure.text.collapse-properties-pane": "collapse the property pane",
|
|
312
|
+
"figure.text.contract-not-compared": "この修正がすでに配置されたものを壊すか確認できませんでした — {why}",
|
|
310
313
|
"figure.text.coordinate-ground-explained": "原点と目盛りを床に表示します",
|
|
311
314
|
"figure.text.delete-figure-confirm": "delete {name}? drawings already using this type will no longer find it.",
|
|
312
315
|
"figure.text.designate-in-properties": "まだ指定された部品がありません。部品を選び、プロパティタブで「能力での役割」を指定してください。",
|
|
@@ -323,6 +326,7 @@
|
|
|
323
326
|
"figure.text.figure-type-example": "例: OHT, AGV_LIFTER",
|
|
324
327
|
"figure.text.figure-type-placeholder": "Figure 識別タイプ (例: OHT)",
|
|
325
328
|
"figure.text.find-by-this-tag": "find by this tag",
|
|
329
|
+
"figure.text.fit-base-explained": "すべての部品を包む最小のボックスに変えます。中心はそのままなので、偏った部品があると反対側に余裕が残ります。",
|
|
326
330
|
"figure.text.fits-n-instances": "up to {n}",
|
|
327
331
|
"figure.text.floor-explained": "座標の下地と重なるため、通常はオフにします",
|
|
328
332
|
"figure.text.follow-camera-explained": "オフにすると影の向きが固定されます",
|
|
@@ -333,7 +337,9 @@
|
|
|
333
337
|
"figure.text.hemisphere-light-explained": "空と床から均一に照らす光です。陰になった面を明るくします。",
|
|
334
338
|
"figure.text.hide-all-parts": "hide all",
|
|
335
339
|
"figure.text.hide-this-part": "hide this part while you work. not saved",
|
|
336
|
-
"figure.text.how-many-fit": "
|
|
340
|
+
"figure.text.how-many-fit": "画面に出しても遅くならない数",
|
|
341
|
+
"figure.text.independent-parts-cost": "個別に動く部品ごとに draw call が 1 増えます。",
|
|
342
|
+
"figure.text.independent-parts-over": "上限を超えました。1 つの画面に一緒に置ける台数がその分減ります。",
|
|
337
343
|
"figure.text.instances-unit": "instances",
|
|
338
344
|
"figure.text.interpolation-explained": "キーの間をどうつなぐか。",
|
|
339
345
|
"figure.text.joint-channel-value-deg": "キーの値は関節の角度 (度) です。",
|
|
@@ -392,6 +398,7 @@
|
|
|
392
398
|
"figure.text.picked-changes-do-not-validate": "the selected changes do not make a valid figure",
|
|
393
399
|
"figure.text.pivot-defaults-to-the-part-centre": "未設定なら部品の中心で回転します。座標は部品基準です。",
|
|
394
400
|
"figure.text.placement-explained": "3D シーンでの基準面を決めます (床: AGV/コンベヤ、天井: OHT/ホイスト、中心: センサー/ドローン)。",
|
|
401
|
+
"figure.text.pose-as-default-explained": "今の姿勢をパラメータの既定値として書き込みます。値が与えられないとき、この姿勢で立ちます。",
|
|
395
402
|
"figure.text.preview-blocked-by-errors": "形式エラーのためプレビューを表示できません。成熟度タブで理由を確認してください。",
|
|
396
403
|
"figure.text.preview-needs-a-part": "部品を追加すると、図面に置いたときの姿が表示されます。",
|
|
397
404
|
"figure.text.proposal-feedback-note": "次の提案に反映するメモ (このセッションのみ)",
|