@things-factory/figure-ui 10.1.46 → 10.1.48
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/modeller/angle-dial.ts +70 -0
- package/client/modeller/figure-settings.ts +328 -0
- package/client/modeller/figure-v3-capability.ts +137 -0
- package/client/modeller/figure-v3-clips.ts +253 -0
- package/client/modeller/figure-v3-controls.ts +212 -0
- package/client/modeller/figure-v3-links.ts +61 -81
- package/client/modeller/figure-v3-material.ts +108 -35
- package/client/modeller/figure-v3-parts.ts +96 -3
- package/client/modeller/figure-v3-preview.ts +326 -0
- package/client/modeller/figure-v3-properties.ts +29 -57
- package/client/modeller/figure-v3-verdict.ts +598 -60
- package/client/modeller/figure-view.ts +152 -0
- package/client/modeller/side-panel-styles.ts +326 -0
- package/client/modeller/v3-canvas.ts +254 -262
- package/client/modeller/v3-clips.ts +89 -0
- package/client/modeller/v3-controls.ts +54 -0
- package/client/modeller/v3-editor.ts +566 -223
- package/client/modeller/v3-fields.ts +84 -0
- package/client/modeller/v3-help.ts +41 -0
- package/client/modeller/v3-joint-fan.ts +292 -0
- package/client/modeller/v3-keys.ts +34 -0
- package/client/modeller/v3-links.ts +101 -9
- package/client/modeller/v3-live.ts +79 -0
- package/client/modeller/v3-maturity.ts +415 -0
- package/client/modeller/v3-palette.ts +45 -0
- package/client/modeller/v3-preview-model.ts +54 -0
- package/client/modeller/v3-stage-model.ts +159 -0
- package/client/modeller/v3-thumbnail.ts +8 -4
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/figure-v3-modeller-page.ts +66 -134
- 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/figure-settings.d.ts +39 -0
- package/dist-client/modeller/figure-settings.js +317 -0
- package/dist-client/modeller/figure-settings.js.map +1 -0
- package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
- package/dist-client/modeller/figure-v3-capability.js +141 -0
- package/dist-client/modeller/figure-v3-capability.js.map +1 -0
- package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
- package/dist-client/modeller/figure-v3-clips.js +268 -0
- package/dist-client/modeller/figure-v3-clips.js.map +1 -0
- package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
- package/dist-client/modeller/figure-v3-controls.js +226 -0
- package/dist-client/modeller/figure-v3-controls.js.map +1 -0
- package/dist-client/modeller/figure-v3-links.d.ts +4 -1
- package/dist-client/modeller/figure-v3-links.js +64 -82
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +4 -1
- package/dist-client/modeller/figure-v3-material.js +109 -35
- package/dist-client/modeller/figure-v3-material.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
- package/dist-client/modeller/figure-v3-parts.js +88 -3
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
- package/dist-client/modeller/figure-v3-preview.js +322 -0
- package/dist-client/modeller/figure-v3-preview.js.map +1 -0
- package/dist-client/modeller/figure-v3-properties.d.ts +1 -1
- package/dist-client/modeller/figure-v3-properties.js +29 -57
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
- package/dist-client/modeller/figure-v3-verdict.js +579 -59
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +118 -0
- package/dist-client/modeller/figure-view.js +78 -0
- package/dist-client/modeller/figure-view.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.d.ts +23 -0
- package/dist-client/modeller/side-panel-styles.js +326 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +44 -37
- package/dist-client/modeller/v3-canvas.js +230 -260
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +39 -0
- package/dist-client/modeller/v3-clips.js +58 -0
- package/dist-client/modeller/v3-clips.js.map +1 -0
- package/dist-client/modeller/v3-controls.d.ts +28 -0
- package/dist-client/modeller/v3-controls.js +28 -0
- package/dist-client/modeller/v3-controls.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +73 -1
- package/dist-client/modeller/v3-editor.js +593 -218
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +39 -0
- package/dist-client/modeller/v3-fields.js +69 -0
- package/dist-client/modeller/v3-fields.js.map +1 -0
- package/dist-client/modeller/v3-help.d.ts +18 -0
- package/dist-client/modeller/v3-help.js +37 -0
- package/dist-client/modeller/v3-help.js.map +1 -0
- package/dist-client/modeller/v3-joint-fan.d.ts +9 -0
- package/dist-client/modeller/v3-joint-fan.js +264 -0
- package/dist-client/modeller/v3-joint-fan.js.map +1 -0
- package/dist-client/modeller/v3-keys.d.ts +14 -0
- package/dist-client/modeller/v3-keys.js +23 -0
- package/dist-client/modeller/v3-keys.js.map +1 -0
- package/dist-client/modeller/v3-links.d.ts +35 -1
- package/dist-client/modeller/v3-links.js +101 -8
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-live.d.ts +31 -0
- package/dist-client/modeller/v3-live.js +58 -0
- package/dist-client/modeller/v3-live.js.map +1 -0
- package/dist-client/modeller/v3-maturity.d.ts +120 -0
- package/dist-client/modeller/v3-maturity.js +317 -0
- package/dist-client/modeller/v3-maturity.js.map +1 -0
- package/dist-client/modeller/v3-palette.d.ts +7 -0
- package/dist-client/modeller/v3-palette.js +42 -0
- package/dist-client/modeller/v3-palette.js.map +1 -0
- package/dist-client/modeller/v3-preview-model.d.ts +32 -0
- package/dist-client/modeller/v3-preview-model.js +48 -0
- package/dist-client/modeller/v3-preview-model.js.map +1 -0
- package/dist-client/modeller/v3-stage-model.d.ts +31 -0
- package/dist-client/modeller/v3-stage-model.js +121 -0
- package/dist-client/modeller/v3-stage-model.js.map +1 -0
- package/dist-client/modeller/v3-thumbnail.js +6 -3
- package/dist-client/modeller/v3-thumbnail.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
- package/dist-client/pages/figure-v3-modeller-page.js +64 -136
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
- package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/v3-clips.test.ts +70 -0
- package/test/v3-controls.test.ts +68 -0
- package/test/v3-fields.test.ts +71 -0
- package/test/v3-gizmo.test.ts +3 -5
- package/test/v3-joint-fan.test.ts +210 -0
- package/test/v3-keys.test.ts +37 -0
- package/test/v3-layout.test.ts +78 -0
- package/test/v3-links.test.ts +89 -6
- package/test/v3-live.test.ts +99 -0
- package/test/v3-material.test.ts +6 -0
- package/test/v3-maturity.test.ts +268 -0
- package/test/v3-palette-icons.test.ts +22 -0
- package/test/v3-parts-wiring.test.ts +3 -1
- package/test/v3-preview.test.ts +42 -0
- package/test/v3-properties.test.ts +4 -2
- package/test/v3-scene-canvas.test.ts +85 -0
- package/test/v3-stage-model.test.ts +25 -0
- package/test/v3-theme.test.ts +16 -25
- package/test/v3-thumbnail.test.ts +29 -0
- package/test/v3-verdict.test.ts +12 -6
- package/test/v3-view.test.ts +20 -0
- package/translations/en.json +90 -6
- package/translations/ja.json +92 -8
- package/translations/ko.json +90 -6
- package/translations/ms.json +92 -8
- package/translations/zh.json +92 -8
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **편집 캔버스는 V2 처럼 things-scene 편집 모드 위에 선다** (사용자 2026-09-24: 「v2 방식으로 하면 안되냐」).
|
|
3
|
+
*
|
|
4
|
+
* 부품마다 `figure-v3-part` 컴포넌트로 판에 세우면 고르기 · 주황 테두리 · 기즈모와 스냅 · 박스 선택 · 카메라(감속 ·
|
|
5
|
+
* 커서 쪽 확대 · 더블클릭 맞추기 · 시점 1~6) · 스튜디오 환경 · 좌표 바탕 · 조명과 그림자가 씬에서 온다. 여기서는 그
|
|
6
|
+
* 배선과, 몸체 ↔ 컴포넌트의 대응이 왕복하는지를 잰다.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from 'node:test'
|
|
9
|
+
import assert from 'node:assert/strict'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
import { changedKeys, componentOf, gizmoResultOf } from '../client/modeller/v3-stage-model.ts'
|
|
14
|
+
import type { V3RenderPart } from '../client/modeller/v3-renderer.ts'
|
|
15
|
+
|
|
16
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
17
|
+
const canvas = read('../client/modeller/v3-canvas.ts')
|
|
18
|
+
const board = { width: 2000, height: 2000 }
|
|
19
|
+
|
|
20
|
+
const turned = (deg: number) => {
|
|
21
|
+
const a = (deg * Math.PI) / 180
|
|
22
|
+
return [
|
|
23
|
+
[Math.cos(a), 0, Math.sin(a)],
|
|
24
|
+
[0, 1, 0],
|
|
25
|
+
[-Math.sin(a), 0, Math.cos(a)]
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
const box = (t: number[], r = turned(0)): V3RenderPart => ({
|
|
29
|
+
id: 'block',
|
|
30
|
+
shape: { provider: 'box', dimensions: { width: 200, height: 100, depth: 60 } },
|
|
31
|
+
pose: { r, t },
|
|
32
|
+
material: { token: 'palette.primary' }
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('things-scene 의 판을 편집 모드로 세우고, 보기 설정은 판의 상태 이름 그대로 넣는다', () => {
|
|
36
|
+
assert.ok(canvas.includes("import { create, Model, niceGridStep } from '@hatiolab/things-scene'"))
|
|
37
|
+
assert.match(canvas, /type: 'model-layer'/)
|
|
38
|
+
assert.match(canvas, /mode: 1\n/, '편집 모드 — 시점 단추 · 기즈모 · 도움말 · 단축키')
|
|
39
|
+
assert.ok(canvas.includes('...sceneLook(this.view ?? DEFAULT_VIEW)'))
|
|
40
|
+
assert.ok(canvas.includes('pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2)'), 'V2 의 두 배 표본')
|
|
41
|
+
assert.doesNotMatch(canvas, /new THREE\.|WebGLRenderer|OrbitControls|TransformControls/, '제 장면을 짜지 않는다')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('몸체는 자기 상자로 서고, 씬의 낱말(width·depth·height · left·top·zPos · 회전)로 적힌다', () => {
|
|
45
|
+
const state = componentOf(box([100, 50, -30]), { 'palette.primary': '#123456' }, board)!
|
|
46
|
+
assert.equal(state.type, 'figure-v3-part')
|
|
47
|
+
assert.equal(state.name, 'block')
|
|
48
|
+
assert.deepEqual([state.width, state.depth, state.height], [200, 100, 60])
|
|
49
|
+
assert.equal(state.left, 100 + 1000 - 100)
|
|
50
|
+
assert.equal(state.top, -30 + 1000 - 30)
|
|
51
|
+
assert.equal(state.zPos, 0, '가운데가 y 50, 높이 100 이면 바닥이 0')
|
|
52
|
+
assert.equal(state.color, '#123456')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('기즈모가 옮기면 원점이, 돌리면 각도가, 늘리면 자기 축의 배율이 돌아온다', () => {
|
|
56
|
+
const part = box([100, 50, -30])
|
|
57
|
+
const at = componentOf(part, {}, board)!
|
|
58
|
+
assert.deepEqual(gizmoResultOf(part, at, board), {}, '안 움직였으면 아무것도')
|
|
59
|
+
|
|
60
|
+
const moved = gizmoResultOf(part, { ...at, left: (at.left as number) + 40, zPos: 20 }, board)
|
|
61
|
+
assert.deepEqual(moved.position!.map(Math.round), [140, 70, -30])
|
|
62
|
+
assert.equal(moved.rotation, undefined)
|
|
63
|
+
|
|
64
|
+
const spun = gizmoResultOf(part, { ...at, rotation: -Math.PI / 6 }, board)
|
|
65
|
+
assert.deepEqual(spun.rotation!.map(v => Math.round(v * 10) / 10 + 0), [0, 30, 0])
|
|
66
|
+
|
|
67
|
+
const grown = gizmoResultOf(part, { ...at, width: 400, left: (at.left as number) - 100 }, board)
|
|
68
|
+
assert.deepEqual(grown.scale!.map(v => Math.round(v * 1000) / 1000), [2, 1, 1])
|
|
69
|
+
assert.equal(grown.position, undefined, '가운데서 양쪽으로 늘리면 원점은 그대로')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('돌린 몸체도 왕복한다 — 판에 적은 상태를 그대로 되읽으면 아무것도 안 바뀐다', () => {
|
|
73
|
+
const part = box([10, 50, 20], turned(35))
|
|
74
|
+
const at = componentOf(part, {}, board)!
|
|
75
|
+
assert.deepEqual(gizmoResultOf(part, at, board), {})
|
|
76
|
+
assert.equal(changedKeys(at, { ...at }), null, '같은 값은 다시 넣지 않는다 — 씬이 메아리를 내지 않게')
|
|
77
|
+
assert.deepEqual(changedKeys(at, { ...at, left: 1 }), { left: 1 })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('고르기와 끌기는 이름으로 알리고, 스스로 적은 것의 메아리는 끌기로 읽지 않는다', () => {
|
|
81
|
+
assert.ok(canvas.includes("root.on('selected'"))
|
|
82
|
+
assert.ok(canvas.includes("if (this.writing) return"))
|
|
83
|
+
assert.ok(canvas.includes("new CustomEvent('part-transformed', { detail: { id: part.id, result }"))
|
|
84
|
+
assert.ok(canvas.includes('setTimeout(() => this.sync())'), '받지 못한 변경은 입력이 둔 자리로 돌아간다')
|
|
85
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The editing board only grows while a figure is edited (user 2026-09-24: 「조작값을 변경하면, 편집기가 자꾸 리프레시」).
|
|
3
|
+
* A board that outgrows itself is stood up again and the camera is lost; a raised arm made the height larger and the
|
|
4
|
+
* height was measured afresh each time.
|
|
5
|
+
*/
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
import assert from 'node:assert/strict'
|
|
8
|
+
|
|
9
|
+
import { addV3Box, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
|
|
10
|
+
|
|
11
|
+
import { drawableV3Parts } from '../client/modeller/v3-renderer.ts'
|
|
12
|
+
import { boardFor } from '../client/modeller/v3-stage-model.ts'
|
|
13
|
+
|
|
14
|
+
const partsOf = (height: number) =>
|
|
15
|
+
drawableV3Parts(
|
|
16
|
+
compileV3Asset(addV3Box(createV3Asset('Probe'), { id: 'post', size: { x: 100, y: height, z: 100 }, position: { x: 0, y: height / 2, z: 0 } }))
|
|
17
|
+
.evaluate().geometry
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
test('the board keeps the height it had: a part that shrinks back does not stand a new board up', () => {
|
|
21
|
+
const tall = boardFor(partsOf(2000))
|
|
22
|
+
const after = boardFor(partsOf(500), tall)
|
|
23
|
+
assert.equal(after.depth, tall.depth)
|
|
24
|
+
assert.ok(boardFor(partsOf(3000), tall).depth > tall.depth, 'it still grows')
|
|
25
|
+
})
|
package/test/v3-theme.test.ts
CHANGED
|
@@ -20,24 +20,14 @@ const fixed = (text: string) =>
|
|
|
20
20
|
.replace(/var\(--[\w-]+,\s*#[0-9a-fA-F]{3,8}\)/g, '')
|
|
21
21
|
.match(/#[0-9a-fA-F]{6}\b|#[0-9a-fA-F]{3}\b(?![\w-])/g) ?? []
|
|
22
22
|
|
|
23
|
-
test('
|
|
24
|
-
assert.deepEqual(fixed(canvas), [], 'v3-canvas.ts')
|
|
23
|
+
test('편집기에 박힌 색이 없다 — 토큰의 대체값만 남는다', () => {
|
|
25
24
|
assert.deepEqual(fixed(editor), [], 'v3-editor.ts')
|
|
26
25
|
})
|
|
27
26
|
|
|
28
|
-
test('캔버스 바탕은
|
|
29
|
-
assert.
|
|
30
|
-
assert.
|
|
31
|
-
assert.match(
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('테마를 바꾸면 3D 바탕을 다시 읽는다 — 한 번만 읽으면 가운데만 밝은 판이 남는다', () => {
|
|
35
|
-
assert.match(canvas, /observe\(document\.body, \{ attributes: true, attributeFilter: \['class'\] \}\)/)
|
|
36
|
-
const onChange = canvas.slice(canvas.indexOf('new MutationObserver'), canvas.indexOf('this.themeObserver.observe'))
|
|
37
|
-
assert.match(onChange, /this\.paintBackground\(\)/)
|
|
38
|
-
assert.match(onChange, /this\.draw\(\)/, '다시 읽고 그리지 않으면 다음 움직임까지 옛 색이다')
|
|
39
|
-
const leave = canvas.slice(canvas.indexOf('disconnectedCallback()'))
|
|
40
|
-
assert.match(leave, /this\.themeObserver\?\.disconnect\(\)/, '떠난 캔버스가 body 를 계속 지켜본다')
|
|
27
|
+
test('캔버스 바탕은 V2 의 작업대 회색 — 판의 하늘색이라 환경 탭에서 바꾼다', () => {
|
|
28
|
+
assert.ok(canvas.includes('background-color: ${unsafeCSS(WORKBENCH)}'), '씬이 서기 전 · 2D 쪽도 같은 색')
|
|
29
|
+
assert.ok(canvas.includes("sky: 'color'") && canvas.includes('...sceneLook(this.view ?? DEFAULT_VIEW)'), '3D 바탕은 보기 설정의 skyColor')
|
|
30
|
+
assert.match(read('../client/modeller/figure-view.ts'), /skyColor: WORKBENCH/)
|
|
41
31
|
})
|
|
42
32
|
|
|
43
33
|
test('눌린 단추와 오류 칸은 셸이 두 테마 대비를 맞춘 칸을 쓴다', () => {
|
|
@@ -50,18 +40,19 @@ test('눌린 단추와 오류 칸은 셸이 두 테마 대비를 맞춘 칸을
|
|
|
50
40
|
assert.match(error, /--md-sys-color-on-error-container\)/)
|
|
51
41
|
})
|
|
52
42
|
|
|
53
|
-
test('편집기는 창이 아니라 자기 폭으로 접는다 —
|
|
43
|
+
test('편집기는 창이 아니라 자기 폭으로 접는다 — 레일이 폭을 먼저 가져간다', () => {
|
|
44
|
+
/* The widths - 200 | 1fr | 340, 160 | 300 under 860px, one column with the canvas first under 560px - are
|
|
45
|
+
ox-three-pane's, measured in @operato/layout's own test. The editor must not write the grid again. */
|
|
54
46
|
assert.match(editor, /container-type: inline-size/)
|
|
55
|
-
assert.doesNotMatch(editor, /@media \(max-width/, '창 폭 기준이 남아 있다 — 레일을 펼치면 편집기가
|
|
56
|
-
assert.match(editor,
|
|
57
|
-
|
|
58
|
-
assert.match(one, /grid-template-columns: minmax\(0, 1fr\)/, '420 아래에서 한 줄')
|
|
59
|
-
assert.match(one, /figure-v3-canvas \{\s*order: -1;/, '한 줄에서는 캔버스가 먼저')
|
|
47
|
+
assert.doesNotMatch(editor, /@media \(max-width/, '창 폭 기준이 남아 있다 — 레일을 펼치면 편집기가 넘친다')
|
|
48
|
+
assert.match(editor, /<ox-three-pane>/, '세 칸은 공용 틀')
|
|
49
|
+
assert.doesNotMatch(editor, /grid-template-columns/, '편집기가 격자를 다시 쓰지 않는다')
|
|
60
50
|
})
|
|
61
51
|
|
|
62
|
-
test('
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
test('양쪽 칸은 띠에서 접는다 — 접으면 캔버스가 그 폭을 갖고, 오른쪽 칸은 무엇도 가리지 않는다', () => {
|
|
53
|
+
assert.ok(editor.includes('.pane=${this.panes}'), '띠가 그 틀을 접는다')
|
|
54
|
+
assert.match(editor, /querySelector<OxThreePane>\('ox-three-pane'\)/, '띠에 건네는 틀은 편집기의 세 칸')
|
|
55
|
+
const aside = editor.slice(editor.indexOf(' aside {'), editor.indexOf('}', editor.indexOf(' aside {')))
|
|
65
56
|
assert.doesNotMatch(aside, /z-index/)
|
|
66
|
-
assert.
|
|
57
|
+
assert.doesNotMatch(aside, /border-left/, '칸 경계선은 틀이 긋는다 — 두 줄이 되지 않게')
|
|
67
58
|
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **저장하면 카드 그림이 남는다** (사용자 2026-09-24: 「why you don't save thumbnail when I save the figure?」).
|
|
3
|
+
*
|
|
4
|
+
* 캔버스가 씬 위로 옮겨 갈 때 찍는 길(`snapshot`)이 빠졌고, 찍는 쪽은 없는 메서드를 `?.()` 로 불러 아무 말 없이
|
|
5
|
+
* 그림 없이 저장했다. 브라우저에서만 찍히므로 여기서는 길이 이어져 있는지를 본다: 찍는 쪽이 보기 전용 캔버스를 세워
|
|
6
|
+
* `snapshot` 을 부르고, 캔버스는 씬의 3D 그림(`snapshot3d`)을 내며, 보기 전용이면 편집기에 아무것도 알리지 않는다.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from 'node:test'
|
|
9
|
+
import assert from 'node:assert/strict'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
14
|
+
|
|
15
|
+
test('the save takes the card picture from a view-only canvas, through the scene’s 3D snapshot', () => {
|
|
16
|
+
const shooter = read('../client/modeller/v3-thumbnail.ts')
|
|
17
|
+
assert.ok(shooter.includes('shooter.viewOnly = true'))
|
|
18
|
+
assert.ok(shooter.includes('return shooter.snapshot ? await shooter.snapshot(WIDTH, HEIGHT) : undefined'))
|
|
19
|
+
assert.ok(!shooter.includes('thumbnail?.()'), 'no call to a method the canvas does not have')
|
|
20
|
+
|
|
21
|
+
const canvas = read('../client/modeller/v3-canvas.ts')
|
|
22
|
+
assert.ok(canvas.includes('async snapshot(width = 480, height = 360)'))
|
|
23
|
+
assert.ok(canvas.includes("await layer.snapshot3d(width, height, 'image/webp', 0.85)"), 'the 3D view, not the 2D sheet')
|
|
24
|
+
const watch = canvas.slice(canvas.indexOf(' private watchScene()'), canvas.indexOf(' private showSelected()'))
|
|
25
|
+
assert.ok(watch.includes('|| this.viewOnly) return'), 'a view-only canvas tells the editor nothing')
|
|
26
|
+
|
|
27
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
28
|
+
assert.ok(editor.includes('const thumbnail = await captureV3Thumbnail('), 'the save asks for it')
|
|
29
|
+
})
|
package/test/v3-verdict.test.ts
CHANGED
|
@@ -29,8 +29,10 @@ test('개수는 목록 카드와 같은 셈이다 — V3 원가를 같은 capaci
|
|
|
29
29
|
|
|
30
30
|
assert.equal(cost.groups, 2, '전제: 그려지는 부품 둘이 묶음 둘')
|
|
31
31
|
assert.equal(capacityFrom(cost.groups, cost.triangles)?.instances, Math.floor(SCENE_BUDGET.groupsOnScreen / 2))
|
|
32
|
-
|
|
33
|
-
assert.ok(
|
|
32
|
+
const maturity = read('../client/modeller/v3-maturity.ts')
|
|
33
|
+
assert.ok(/capacityFrom\(cost\.groups, cost\.triangles\)/.test(maturity), '판정 탭이 카드와 같은 함수로 센다')
|
|
34
|
+
assert.ok(/report = asset \? v3ReportOf\(asset\) : undefined/.test(verdict), '탭은 그 셈을 부르고 보여 줄 뿐이다')
|
|
35
|
+
assert.ok(/instances: report\?\.capacity\.instances/.test(verdict), '탭 이름표의 개수도 같은 셈에서 온다')
|
|
34
36
|
})
|
|
35
37
|
|
|
36
38
|
test('판정은 발행 패널과 같은 서버 문에서 온다 — 화면이 따로 판정하지 않는다', () => {
|
|
@@ -56,15 +58,19 @@ test('판정 탭은 발행 패널과 같은 두 판정에 「부품에 맞추기
|
|
|
56
58
|
assert.ok(/<figure-v3-verdict[\s\S]*?@fit-base-to-parts=\$\{this\.fitToParts\}/.test(page), '누르면 공간 선언으로 이어진다')
|
|
57
59
|
})
|
|
58
60
|
|
|
59
|
-
test('
|
|
61
|
+
test('판정과 이력은 편집기의 탭에 서고, 감추되 떼지 않는다', () => {
|
|
62
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
60
63
|
assert.ok(/<figure-v3-verdict[\s\S]*?\.source=\$\{this\.draftSource\(\)\}/.test(page), '편집 중인 원본을 넘긴다')
|
|
61
64
|
assert.ok(/<figure-v3-verdict[\s\S]*?\.figureType=/.test(page), '타입을 바꾸면 다시 판정한다')
|
|
62
|
-
assert.ok(/<figure-v3-verdict\s
|
|
63
|
-
assert.ok(
|
|
65
|
+
assert.ok(/<figure-v3-verdict\s+slot="verdict"/.test(page) && /<figure-history-panel\s+slot="history"/.test(page))
|
|
66
|
+
assert.ok(editor.includes(`<div panel flush ?off=\${tab !== 'verdict'}><slot name="verdict"></slot></div>`), '고르지 않은 탭은 감춘다 — 판정은 마지막 답을 지킨다')
|
|
67
|
+
assert.ok(/@capacity-changed=/.test(page) && page.includes('.instances=${this.instances}'), '개수를 편집기에 넘긴다')
|
|
68
|
+
assert.ok(editor.includes("this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)"), '개수가 탭 이름표에 남는다')
|
|
64
69
|
})
|
|
65
70
|
|
|
66
71
|
test('판정 탭의 말은 다섯 말 모두에 있다 — 없으면 키가 그대로 찍힌다', () => {
|
|
67
|
-
const
|
|
72
|
+
const maturity = read('../client/modeller/v3-maturity.ts')
|
|
73
|
+
const keys = [...(verdict + maturity).matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
|
|
68
74
|
assert.ok(keys.includes('figure.text.verdict-asking'), '전제: 삼항 안의 키도 센다')
|
|
69
75
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
|
|
70
76
|
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **보기 — 작업대의 설정** (V3 동등표 ⑦, V2 의 「환경」 탭). 도형에 저장되지 않고, 보는 사람의 브라우저에 남는다.
|
|
3
|
+
*/
|
|
4
|
+
import { test } from 'node:test'
|
|
5
|
+
import assert from 'node:assert/strict'
|
|
6
|
+
import { readFileSync } from 'node:fs'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
11
|
+
|
|
12
|
+
test('환경 탭은 V2 의 보기 패널 그대로 — 설정은 판의 상태 이름이고, 도형이 아니라 브라우저에 둔다', () => {
|
|
13
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
14
|
+
assert.ok(editor.includes('<figure-settings .view=${this.view} @update-view=${(e: CustomEvent) => this.setView(e.detail.view)}></figure-settings>'))
|
|
15
|
+
assert.ok(editor.includes("localStorage.setItem('figure-v3-view'"))
|
|
16
|
+
assert.doesNotMatch(editor.slice(editor.indexOf('private setView'), editor.indexOf('private setView') + 400), /session/, '설정은 세션(도형)에 닿지 않는다')
|
|
17
|
+
const view = read('../client/modeller/figure-view.ts')
|
|
18
|
+
assert.ok(view.includes("'studio',") && view.includes("'rainy'"), '환경 여덟')
|
|
19
|
+
assert.ok(read('../client/modeller/figure-settings.ts').includes("i18next.t('figure.text.view-settings-are-not-saved')"))
|
|
20
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
"figure.button.duplicate-part": "duplicate",
|
|
16
16
|
"figure.button.fit-base-to-parts": "fit to parts",
|
|
17
17
|
"figure.button.fix-position": "fix here",
|
|
18
|
+
"figure.button.scale-position": "in proportion",
|
|
19
|
+
"figure.button.remeasure-range": "measure again",
|
|
20
|
+
"figure.label.sizing-rule": "when the size changes",
|
|
21
|
+
"figure.label.sizing-uniform": "one ratio (geometric mean of the three axes)",
|
|
22
|
+
"figure.button.scale-uniformly": "grow by one ratio",
|
|
23
|
+
"figure.text.sizing-uniform-proposed": "this figure has joints. growing it by one ratio keeps the joints together at any size.",
|
|
24
|
+
"figure.text.sizing-uniform-explained": "grows the whole figure by the geometric mean of the three sizes a board gives it. the feet stay on the floor; room left above the figure stays empty.",
|
|
18
25
|
"figure.button.mirror-x": "mirror X",
|
|
19
26
|
"figure.button.mirror-z": "mirror Z",
|
|
20
27
|
"figure.button.move-part-down": "move down",
|
|
@@ -24,7 +31,6 @@
|
|
|
24
31
|
"figure.button.pivot-set": "set",
|
|
25
32
|
"figure.button.pose-as-default": "make this the default pose",
|
|
26
33
|
"figure.button.preview-fix": "preview fix · {part} {axis} face: {from} → {to}",
|
|
27
|
-
"figure.button.redo": "redo",
|
|
28
34
|
"figure.button.release-figure": "release",
|
|
29
35
|
"figure.button.restore-released-base": "restore the previous model width",
|
|
30
36
|
"figure.button.keep-changed-appearance": "publish with the adjusted width",
|
|
@@ -43,6 +49,7 @@
|
|
|
43
49
|
"figure.label.gizmo-scale": "size",
|
|
44
50
|
"figure.label.gizmo-translate": "move",
|
|
45
51
|
"figure.label.position-worked-out": "moves with the figure size",
|
|
52
|
+
"figure.label.position-in-proportion": "in proportion to the figure size",
|
|
46
53
|
"figure.label.primitive-rounded-box": "rounded box",
|
|
47
54
|
"figure.text.catalog-needs-a-save": "can be set once the figure is saved.",
|
|
48
55
|
"figure.text.declared-space-declared": "declared from the parts.",
|
|
@@ -50,6 +57,9 @@
|
|
|
50
57
|
"figure.text.fasten-would-loop": "that part is already fastened to this one, so it cannot be fastened the other way.",
|
|
51
58
|
"figure.text.follow-would-loop": "that dimension already follows this part, so the two cannot follow each other.",
|
|
52
59
|
"figure.text.links-need-a-box": "size following works on box-shaped parts, fastening on boxes and cylinders.",
|
|
60
|
+
"figure.text.scale-position-explained": "keeps this place as a share of the figure size — at twice the size it is twice as far.",
|
|
61
|
+
"figure.text.repeat-member": "copy {index} of {count} made by repeating {name}.",
|
|
62
|
+
"figure.text.instance-range-can-widen": "measured again after the parts changed: {ranges} mm.",
|
|
53
63
|
"figure.text.mounting-face-needs-space": "declare the space first to choose one.",
|
|
54
64
|
"figure.text.part-is-referenced": "'{part}' cannot be deleted: another part is fastened to it or follows its size. Undo that link first.",
|
|
55
65
|
"figure.text.part-name-invalid": "'{name}' cannot be a part name. Start with a letter and use letters, digits, - and _.",
|
|
@@ -58,7 +68,6 @@
|
|
|
58
68
|
"figure.text.recommended-fix-explained": "{n} changes rechecked against every resize case. They apply to this draft only; review and save when ready.",
|
|
59
69
|
"figure.text.show-affected-parts": "show affected parts",
|
|
60
70
|
"figure.button.to-floor": "to floor",
|
|
61
|
-
"figure.button.undo": "undo",
|
|
62
71
|
"figure.label.accepts": "accepts",
|
|
63
72
|
"figure.label.anchor": "anchored face",
|
|
64
73
|
"figure.label.anchor-advanced": "advanced proportional sizing",
|
|
@@ -110,11 +119,12 @@
|
|
|
110
119
|
"figure.label.axis-scale-short": "scale",
|
|
111
120
|
"figure.label.axis-width": "width",
|
|
112
121
|
"figure.label.base-box": "base box (mm)",
|
|
113
|
-
"figure.label.budget-groups": "
|
|
122
|
+
"figure.label.budget-groups": "groups (draw calls)",
|
|
114
123
|
"figure.label.budget-independent": "independently moved parts",
|
|
115
124
|
"figure.label.budget-parts": "part count",
|
|
116
125
|
"figure.label.budget-surface": "surfaces outside the palette",
|
|
117
126
|
"figure.label.budget-transparent": "transparent materials",
|
|
127
|
+
"figure.label.budget-triangles": "triangles",
|
|
118
128
|
"figure.label.budgets": "budgets",
|
|
119
129
|
"figure.label.by-axis": "maturity by item",
|
|
120
130
|
"figure.label.candidate-review": "candidate review",
|
|
@@ -137,6 +147,8 @@
|
|
|
137
147
|
"figure.label.detail-level": "detail level",
|
|
138
148
|
"figure.label.distinct-materials": "distinct materials",
|
|
139
149
|
"figure.label.distinct-shapes": "distinct shapes",
|
|
150
|
+
"figure.label.draw-calls": "draw calls",
|
|
151
|
+
"figure.label.vertex-buffers": "vertex buffers",
|
|
140
152
|
"figure.label.draft": "draft",
|
|
141
153
|
"figure.label.emissive": "emissive",
|
|
142
154
|
"figure.label.emissive-color": "lit color",
|
|
@@ -253,6 +265,7 @@
|
|
|
253
265
|
"figure.label.repeat-count": "count",
|
|
254
266
|
"figure.label.repeat-pitch": "pitch",
|
|
255
267
|
"figure.label.reuse-trend": "reuse trend",
|
|
268
|
+
"figure.label.instance-count": "instance count",
|
|
256
269
|
"figure.label.role-in-capability": "role in the capability",
|
|
257
270
|
"figure.label.role-port-in": "inbound port",
|
|
258
271
|
"figure.label.role-port-out": "outbound port",
|
|
@@ -273,7 +286,6 @@
|
|
|
273
286
|
"figure.label.shapes": "shapes",
|
|
274
287
|
"figure.label.sizing-fixed": "keeps its size",
|
|
275
288
|
"figure.label.sizing-repeat": "repeats",
|
|
276
|
-
"figure.label.sizing-rule": "when the size changes",
|
|
277
289
|
"figure.label.sizing-scale": "scales proportionally",
|
|
278
290
|
"figure.label.sizing-stretch": "stretches on one axis",
|
|
279
291
|
"figure.label.sky-color": "sky color",
|
|
@@ -308,6 +320,12 @@
|
|
|
308
320
|
"figure.text.bound-by-triangles": "triangles reach the limit first ({per} per instance, {budget} per drawing).",
|
|
309
321
|
"figure.text.budget-measured-on": "Measured on {gpu}, {on}. A figure standing still costs nothing — only what is on screen and redrawn counts.",
|
|
310
322
|
"figure.text.budget-not-measured-yet": "this target has not been measured yet.",
|
|
323
|
+
"figure.text.budgets-not-in-v3": "off-palette surfaces and the part-count limit are not in the V3 format, so they are not shown.",
|
|
324
|
+
"figure.text.trend-chart-title": "drawing cost by instance count",
|
|
325
|
+
"figure.text.trend-chart-lead": "flat lines stay the same as instances grow. instances of the same size share them.",
|
|
326
|
+
"figure.text.trend-at-count": "at {n}",
|
|
327
|
+
"figure.text.trend-stays": "unchanged",
|
|
328
|
+
"figure.text.trend-flat-lines-overlap": "the flat lines overlap at {value}.",
|
|
311
329
|
"figure.text.cannot-release-now": "cannot be released yet",
|
|
312
330
|
"figure.text.fit-needs-parts": "there are no parts to fit to — add a shape first.",
|
|
313
331
|
"figure.text.nothing-blocks-release": "nothing in the figure as it stands blocks releasing it.",
|
|
@@ -362,8 +380,12 @@
|
|
|
362
380
|
"figure.text.floor-explained": "overlaps the coordinate ground, so it is usually off",
|
|
363
381
|
"figure.text.follow-camera-explained": "turn off to fix the shadow direction",
|
|
364
382
|
"figure.text.grade-decided-by": "what decided the grade",
|
|
365
|
-
"figure.text.grade-explained": "the grade is
|
|
383
|
+
"figure.text.grade-explained": "the grade is the headroom against placing {target} of this figure on one screen. the item closest to its limit decides it.",
|
|
366
384
|
"figure.text.grade-scale-lead": "grade scale (limit used)",
|
|
385
|
+
"figure.text.grade-target": "target: {target} on one screen — up to {groups} groups · {triangles} triangles each",
|
|
386
|
+
"figure.text.parts-budget": "{triangles} triangles · {groups} groups · {instances} on one screen",
|
|
387
|
+
"figure.text.parts-budget-over": "over the {budget} limit — {used} / {limit}",
|
|
388
|
+
"figure.text.parts-budget-open": "open maturity",
|
|
367
389
|
"figure.text.headroom-percent": "{percent}% headroom",
|
|
368
390
|
"figure.text.hemisphere-light-explained": "even light from sky and ground. it brightens shaded faces.",
|
|
369
391
|
"figure.text.hide-all-parts": "hide all",
|
|
@@ -387,6 +409,7 @@
|
|
|
387
409
|
"figure.text.keys-must-rise": "each key must come after the one before it.",
|
|
388
410
|
"figure.text.limit-comes-from-level": "the detail level sets this limit.",
|
|
389
411
|
"figure.text.maturity-blocked-no-binding": "state display is not measured yet.",
|
|
412
|
+
"figure.text.maturity-blocked-no-detail-level": "the V3 format has no detail level. the budgets and placement scale above take its place.",
|
|
390
413
|
"figure.text.maturity-blocked-no-parts": "nothing to measure — there are no parts.",
|
|
391
414
|
"figure.text.maturity-consistency-because": "checks texture use, recommended segment counts and the number of materials.",
|
|
392
415
|
"figure.text.maturity-consistency-clean": "no issues",
|
|
@@ -501,6 +524,8 @@
|
|
|
501
524
|
"figure.text.follows-explained": "What this side grows with. Stretch the figure on a board and this follows.",
|
|
502
525
|
"figure.label.follows-nothing": "fixed",
|
|
503
526
|
"figure.label.follows-instance": "instance size",
|
|
527
|
+
"figure.label.follows-instance-share": "in proportion to size",
|
|
528
|
+
"figure.label.instance-range": "sizes it can be placed at",
|
|
504
529
|
"figure.label.follows-part": "another part",
|
|
505
530
|
"figure.label.times": "times",
|
|
506
531
|
"figure.label.plus": "plus (mm)",
|
|
@@ -579,5 +604,64 @@
|
|
|
579
604
|
"figure.label.place-accepts": "Takes",
|
|
580
605
|
"figure.label.place-capacity": "How many",
|
|
581
606
|
"figure.text.place-accepts-needed": "Name at least one type it takes, separated by commas.",
|
|
582
|
-
"figure.button.add-place": "Add place"
|
|
607
|
+
"figure.button.add-place": "Add place",
|
|
608
|
+
"figure.label.controls": "Controls",
|
|
609
|
+
"figure.text.controls-explained": "A board or a connection sets these values to pose the figure. Move a slider to try one.",
|
|
610
|
+
"figure.text.no-controls": "No controls yet. Adding a motion to a part makes one.",
|
|
611
|
+
"figure.button.keep-pose": "Keep this pose as the default",
|
|
612
|
+
"figure.label.control-name": "Name",
|
|
613
|
+
"figure.text.control-starts-at": "Starts at {value} {unit} when placed on a board.",
|
|
614
|
+
"figure.label.control-sweep": "Full travel time (s)",
|
|
615
|
+
"figure.button.whole-figure": "Whole figure",
|
|
616
|
+
"figure.label.places-on-part": "Places on {part}",
|
|
617
|
+
"figure.text.choose-a-part-to-move": "Choose a part to set how it moves.",
|
|
618
|
+
"figure.text.loading-figure": "Loading",
|
|
619
|
+
"figure.text.no-places": "No places yet. Choose a part and add one here.",
|
|
620
|
+
"figure.text.opening-model": "Opening the model.",
|
|
621
|
+
"figure.text.saved": "Saved",
|
|
622
|
+
"figure.text.clips-explained": "Plays controls over time. Move the bar to see the pose at a moment; make a pose with the control sliders, then add a key at that moment.",
|
|
623
|
+
"figure.label.clip-duration": "Length (s)",
|
|
624
|
+
"figure.label.clip-time": "Clip time",
|
|
625
|
+
"figure.label.clip-loop": "After the end",
|
|
626
|
+
"figure.label.clip-loop-wrap": "Repeat",
|
|
627
|
+
"figure.label.clip-loop-hold": "Hold the last value",
|
|
628
|
+
"figure.button.remove-track": "Stop playing this control",
|
|
629
|
+
"figure.button.remove-key": "Remove key",
|
|
630
|
+
"figure.button.add-key-here": "Add a key at {seconds} s ({value})",
|
|
631
|
+
"figure.label.add-track": "Play a control",
|
|
632
|
+
"figure.label.clip": "Clip",
|
|
633
|
+
"figure.label.new-clip": "New clip",
|
|
634
|
+
"figure.button.add-track": "Add",
|
|
635
|
+
"figure.text.add-a-shape-to-start": "Add a shape to start modelling.",
|
|
636
|
+
"figure.label.primitive-frustum": "Cone",
|
|
637
|
+
"figure.label.primitive-plate": "Plate",
|
|
638
|
+
"figure.label.field-width": "Width",
|
|
639
|
+
"figure.label.field-height": "Height",
|
|
640
|
+
"figure.label.field-depth": "Depth",
|
|
641
|
+
"figure.label.field-radius": "Radius",
|
|
642
|
+
"figure.label.field-length": "Length",
|
|
643
|
+
"figure.label.field-round": "Corner radius",
|
|
644
|
+
"figure.label.field-left": "Left",
|
|
645
|
+
"figure.label.field-right": "Right",
|
|
646
|
+
"figure.label.field-bottom": "Bottom",
|
|
647
|
+
"figure.label.field-top": "Top",
|
|
648
|
+
"figure.label.field-openingLeft": "Opening left",
|
|
649
|
+
"figure.label.field-openingRight": "Opening right",
|
|
650
|
+
"figure.label.field-openingTop": "Opening top",
|
|
651
|
+
"figure.label.field-radiusX": "Radius X",
|
|
652
|
+
"figure.label.field-radiusY": "Radius Y",
|
|
653
|
+
"figure.label.field-radiusZ": "Radius Z",
|
|
654
|
+
"figure.label.field-radiusTop": "Top radius",
|
|
655
|
+
"figure.label.field-radiusBottom": "Bottom radius",
|
|
656
|
+
"figure.label.group-position": "Position",
|
|
657
|
+
"figure.label.group-rotation": "Rotation (°)",
|
|
658
|
+
"figure.label.group-size": "Size",
|
|
659
|
+
"figure.label.group-placement-size": "Position and size",
|
|
660
|
+
"figure.label.group-shape": "Shape",
|
|
661
|
+
"figure.label.group-figure-size": "Figure size",
|
|
662
|
+
"figure.label.group-part": "Part",
|
|
663
|
+
"figure.label.group-figure": "Figure",
|
|
664
|
+
"figure.label.group-standing": "Where it stands",
|
|
665
|
+
"figure.label.group-catalog": "Catalogue",
|
|
666
|
+
"figure.button.control-settings": "Name, range and time"
|
|
583
667
|
}
|