@things-factory/figure-ui 10.1.65 → 10.1.67
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 +0 -45
- package/client/modeller/figure-history-panel.ts +6 -145
- package/client/modeller/figure-v3-capability.ts +2 -2
- package/client/modeller/figure-v3-clips.ts +2 -2
- package/client/modeller/figure-v3-controls.ts +2 -2
- package/client/modeller/figure-v3-first-save.ts +1 -1
- package/client/modeller/figure-v3-links.ts +6 -6
- package/client/modeller/figure-v3-material.ts +11 -11
- package/client/modeller/figure-v3-parts.ts +7 -10
- package/client/modeller/figure-v3-preview.ts +3 -3
- package/client/modeller/figure-v3-properties.ts +5 -6
- package/client/modeller/figure-v3-verdict.ts +9 -10
- package/client/modeller/figure-view.ts +2 -3
- package/client/modeller/modeller-route.ts +2 -3
- package/client/modeller/scene-budget.ts +0 -14
- package/client/modeller/side-panel-styles.ts +5 -5
- package/client/modeller/v3-align.ts +16 -6
- package/client/modeller/v3-canvas.ts +12 -12
- package/client/modeller/v3-clips.ts +1 -1
- package/client/modeller/v3-controls.ts +1 -1
- package/client/modeller/v3-editor.ts +21 -21
- package/client/modeller/v3-fields.ts +1 -1
- package/client/modeller/v3-figure-facts.ts +1 -1
- package/client/modeller/v3-first-save.ts +3 -4
- package/client/modeller/v3-gizmo.ts +5 -5
- package/client/modeller/v3-help.ts +1 -1
- package/client/modeller/v3-joint-fan.ts +4 -5
- package/client/modeller/v3-keys.ts +1 -1
- package/client/modeller/v3-links.ts +9 -12
- package/client/modeller/v3-maturity.ts +22 -26
- package/client/modeller/v3-refusals.ts +17 -0
- package/client/modeller/v3-renderer.ts +1 -1
- package/client/modeller/v3-stage-model.ts +2 -2
- package/client/modeller/v3-thumbnail.ts +1 -3
- package/client/pages/figure-v3-modeller-page.ts +8 -11
- package/client/types.ts +2 -31
- package/dist-client/graphql/index.js +0 -45
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +4 -37
- package/dist-client/modeller/figure-history-panel.js +8 -154
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-v3-capability.d.ts +1 -1
- package/dist-client/modeller/figure-v3-capability.js +1 -1
- package/dist-client/modeller/figure-v3-capability.js.map +1 -1
- package/dist-client/modeller/figure-v3-clips.d.ts +2 -2
- package/dist-client/modeller/figure-v3-clips.js +2 -2
- package/dist-client/modeller/figure-v3-clips.js.map +1 -1
- package/dist-client/modeller/figure-v3-controls.d.ts +1 -1
- package/dist-client/modeller/figure-v3-controls.js +2 -2
- package/dist-client/modeller/figure-v3-controls.js.map +1 -1
- package/dist-client/modeller/figure-v3-first-save.d.ts +1 -1
- package/dist-client/modeller/figure-v3-first-save.js +1 -1
- package/dist-client/modeller/figure-v3-first-save.js.map +1 -1
- package/dist-client/modeller/figure-v3-links.d.ts +5 -5
- package/dist-client/modeller/figure-v3-links.js +6 -6
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +6 -6
- package/dist-client/modeller/figure-v3-material.js +11 -11
- package/dist-client/modeller/figure-v3-material.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +6 -8
- package/dist-client/modeller/figure-v3-parts.js +1 -1
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-preview.js.map +1 -1
- package/dist-client/modeller/figure-v3-properties.d.ts +4 -5
- package/dist-client/modeller/figure-v3-properties.js +5 -6
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +2 -2
- package/dist-client/modeller/figure-v3-verdict.js +9 -10
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +2 -3
- package/dist-client/modeller/figure-view.js.map +1 -1
- package/dist-client/modeller/modeller-route.d.ts +2 -3
- package/dist-client/modeller/modeller-route.js +2 -3
- package/dist-client/modeller/modeller-route.js.map +1 -1
- package/dist-client/modeller/scene-budget.d.ts +0 -11
- package/dist-client/modeller/scene-budget.js +0 -12
- package/dist-client/modeller/scene-budget.js.map +1 -1
- package/dist-client/modeller/side-panel-styles.d.ts +2 -2
- package/dist-client/modeller/side-panel-styles.js +5 -5
- package/dist-client/modeller/side-panel-styles.js.map +1 -1
- package/dist-client/modeller/v3-align.d.ts +9 -5
- package/dist-client/modeller/v3-align.js +13 -3
- package/dist-client/modeller/v3-align.js.map +1 -1
- package/dist-client/modeller/v3-canvas.d.ts +4 -4
- package/dist-client/modeller/v3-canvas.js +9 -9
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.js.map +1 -1
- package/dist-client/modeller/v3-controls.js.map +1 -1
- package/dist-client/modeller/v3-editor.d.ts +13 -13
- package/dist-client/modeller/v3-editor.js +19 -18
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.js.map +1 -1
- package/dist-client/modeller/v3-figure-facts.d.ts +1 -1
- package/dist-client/modeller/v3-figure-facts.js +1 -1
- package/dist-client/modeller/v3-figure-facts.js.map +1 -1
- package/dist-client/modeller/v3-first-save.js +3 -4
- package/dist-client/modeller/v3-first-save.js.map +1 -1
- package/dist-client/modeller/v3-gizmo.d.ts +5 -5
- package/dist-client/modeller/v3-gizmo.js +1 -1
- package/dist-client/modeller/v3-gizmo.js.map +1 -1
- package/dist-client/modeller/v3-help.d.ts +1 -1
- package/dist-client/modeller/v3-help.js +1 -1
- package/dist-client/modeller/v3-help.js.map +1 -1
- package/dist-client/modeller/v3-joint-fan.js +4 -5
- package/dist-client/modeller/v3-joint-fan.js.map +1 -1
- package/dist-client/modeller/v3-keys.js.map +1 -1
- package/dist-client/modeller/v3-links.d.ts +5 -7
- package/dist-client/modeller/v3-links.js +6 -8
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-maturity.d.ts +7 -7
- package/dist-client/modeller/v3-maturity.js +20 -24
- package/dist-client/modeller/v3-maturity.js.map +1 -1
- package/dist-client/modeller/v3-refusals.d.ts +6 -0
- package/dist-client/modeller/v3-refusals.js +18 -0
- package/dist-client/modeller/v3-refusals.js.map +1 -1
- package/dist-client/modeller/v3-renderer.js +1 -1
- package/dist-client/modeller/v3-renderer.js.map +1 -1
- package/dist-client/modeller/v3-stage-model.js.map +1 -1
- package/dist-client/modeller/v3-thumbnail.js +1 -3
- package/dist-client/modeller/v3-thumbnail.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +4 -5
- package/dist-client/pages/figure-v3-modeller-page.js +8 -11
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -29
- package/dist-client/types.js +3 -0
- package/dist-client/types.js.map +1 -1
- package/docs/v3-product-cutover.md +14 -24
- package/package.json +5 -5
- package/test/browser/modeller-page.js +6 -7
- package/test/browser/modeller.check.mjs +9 -16
- package/test/fixtures/joint-column.json +1138 -0
- package/test/fixtures/joint-roller.json +872 -0
- package/test/fixtures/robot-arm-6dof.json +2776 -0
- package/test/i18n-prefix-guard.test.ts +0 -31
- package/test/modeller-route.test.ts +2 -3
- package/test/v3-align.test.ts +9 -4
- package/test/v3-clips.test.ts +1 -1
- package/test/v3-controls.test.ts +1 -1
- package/test/v3-fields.test.ts +4 -4
- package/test/v3-first-save.test.ts +1 -1
- package/test/v3-gizmo.test.ts +5 -5
- package/test/v3-joint-fan.test.ts +12 -42
- package/test/v3-keys.test.ts +1 -1
- package/test/v3-layout.test.ts +4 -5
- package/test/v3-links.test.ts +3 -3
- package/test/v3-material.test.ts +3 -3
- package/test/v3-maturity.test.ts +8 -9
- package/test/v3-parts-wiring.test.ts +3 -3
- package/test/v3-preview.test.ts +1 -1
- package/test/v3-product-write.test.ts +1 -1
- package/test/v3-properties.test.ts +3 -3
- package/test/v3-refusals.test.ts +12 -0
- package/test/v3-release-panel.test.ts +5 -5
- package/test/v3-scene-canvas.test.ts +2 -2
- package/test/v3-theme.test.ts +2 -2
- package/test/v3-verdict.test.ts +2 -2
- package/test/v3-view.test.ts +2 -2
- package/translations/en.json +2 -143
- package/translations/ja.json +2 -143
- package/translations/ko.json +2 -143
- package/translations/ms.json +2 -143
- package/translations/zh.json +2 -143
- package/client/modeller/figure-source.ts +0 -437
- package/client/modeller/part-edits.ts +0 -312
- package/dist-client/modeller/figure-source.d.ts +0 -238
- package/dist-client/modeller/figure-source.js +0 -193
- package/dist-client/modeller/figure-source.js.map +0 -1
- package/dist-client/modeller/part-edits.d.ts +0 -89
- package/dist-client/modeller/part-edits.js +0 -270
- package/dist-client/modeller/part-edits.js.map +0 -1
- package/test/expect.ts +0 -57
- package/test/figure-source.test.ts +0 -511
- package/test/part-edits.test.ts +0 -350
|
@@ -28,8 +28,6 @@ import { readFileSync, readdirSync } from 'node:fs'
|
|
|
28
28
|
import { fileURLToPath } from 'node:url'
|
|
29
29
|
import { join } from 'node:path'
|
|
30
30
|
|
|
31
|
-
import { CHANNEL_PATHS, INTERPOLATIONS } from '@hatiolab/figure-model'
|
|
32
|
-
|
|
33
31
|
const HERE = fileURLToPath(new URL('.', import.meta.url))
|
|
34
32
|
const TRANSLATIONS = join(HERE, '../translations')
|
|
35
33
|
const CLIENT = join(HERE, '../client')
|
|
@@ -122,35 +120,6 @@ test('★ 한국어를 defaultValue 로 들고 있지 않다 — 번역 파일
|
|
|
122
120
|
assert.deepEqual(hard, [], `한국어 defaultValue:\n ${hard.join('\n ')}`)
|
|
123
121
|
})
|
|
124
122
|
|
|
125
|
-
/*
|
|
126
|
-
애니메이션 탭도 열거 값을 키에 이어 붙인다. 사다리와 **같은 모양의 함정**이라 같은 못을 박는다.
|
|
127
|
-
|
|
128
|
-
경로는 이름(`label`)과 단위 설명(`text.…-unit`)으로 갈린다. 한쪽만 넣으면 화면에 키가
|
|
129
|
-
그대로 뜨고, 넓은 앞머리가 위의 미사용 검사를 덮어 통과시킨다.
|
|
130
|
-
|
|
131
|
-
There is no drive enumeration to check. Whether a pose runs on time or on a value is which list it is
|
|
132
|
-
written in: animations run on time, parameters on a value (ADR-0051). The old `drive` field is gone.
|
|
133
|
-
|
|
134
|
-
열거 값을 여기 다시 적지 않고 형식에서 읽는다. 적어 두면 형식에 값을 하나 더할 때
|
|
135
|
-
검사가 조용히 통과한다.
|
|
136
|
-
*/
|
|
137
|
-
test('★ 열거 값마다 짝이 다 있다 — 경로 · 보간', () => {
|
|
138
|
-
const missing: string[] = []
|
|
139
|
-
const want = (key: string) => {
|
|
140
|
-
if (!(key in KO)) missing.push(key)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
for (const path of CHANNEL_PATHS) {
|
|
144
|
-
want(`figure.label.path-${path}`)
|
|
145
|
-
want(`figure.text.path-${path}-unit`)
|
|
146
|
-
}
|
|
147
|
-
for (const one of INTERPOLATIONS) {
|
|
148
|
-
want(`figure.label.interpolation-${one}`)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
assert.deepEqual(missing, [], `이어 붙여 부르는 키가 번역에 없다:\n ${missing.join('\n ')}`)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
123
|
/*
|
|
155
124
|
대체 언어(`fallbackLng`)는 **en** 이다 — 클라이언트(`@operato/i18n` 의 config)와 서버
|
|
156
125
|
(`i18n-base` 의 미들웨어) 양쪽 다.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* One modeller
|
|
3
|
-
* an old address all open it.
|
|
2
|
+
* One modeller. The catalogue, a new figure and an old address all open it.
|
|
4
3
|
*/
|
|
5
4
|
import { test } from 'node:test'
|
|
6
5
|
import assert from 'node:assert/strict'
|
|
@@ -16,7 +15,7 @@ test('every figure and a new one open the one modeller', () => {
|
|
|
16
15
|
assert.equal(modellerPathOf(), 'figure-modeller')
|
|
17
16
|
})
|
|
18
17
|
|
|
19
|
-
test('the route and the shell table send both addresses to the V3 page, and
|
|
18
|
+
test('the route and the shell table send both addresses to the V3 page, and no `figure-modeller-page` is registered', () => {
|
|
20
19
|
const route = read('../client/route.ts')
|
|
21
20
|
assert.doesNotMatch(route, /figure-modeller-page/)
|
|
22
21
|
const config = read('../things-factory.config.js')
|
package/test/v3-align.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **부품 맞추기 — 가운데 · 바닥 · 대칭 · 눈금** (2026-09-24).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 「맞추기」 여섯 단추. V3 의 틀은 바닥이 y 0, X · Z 가 0 에 가운데다. 각 도구가 부품의 **실제로 차지하는
|
|
5
5
|
* 상자**(돌린 부품이면 돈 부피)를 기준으로 맞추는지, 그 결과를 잰다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
11
11
|
|
|
12
12
|
import { addV3Box, createV3Asset, editV3Part } from '@hatiolab/figure-model'
|
|
13
13
|
|
|
14
|
-
import { alignChanges, boxOf, canMirror, gridStepOf, mirrorChanges } from '../client/modeller/v3-align.ts'
|
|
14
|
+
import { alignChanges, boxOf, canMirror, gridStepOf, mirrorChanges, niceStep } from '../client/modeller/v3-align.ts'
|
|
15
15
|
import { gizmoReachOf } from '../client/modeller/v3-gizmo.ts'
|
|
16
16
|
|
|
17
17
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
@@ -41,7 +41,7 @@ test('바닥은 부품의 가장 낮은 점을 y 0 에 둔다 — 돌린 부품
|
|
|
41
41
|
assert.ok(Math.abs(landed.centre.y - landed.extent.y / 2) <= 1, `45° 돈 부품의 모서리가 바닥에 닿는다 (${landed.centre.y - landed.extent.y / 2})`)
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
test('눈금은
|
|
44
|
+
test('눈금은 1 · 2 · 5 간격으로 자리와 크기를 맞추고, 크기를 0 으로 만들지 않는다', () => {
|
|
45
45
|
const asset = probe({ x: 133, y: 47, z: -71 })
|
|
46
46
|
const step = gridStepOf(asset)
|
|
47
47
|
assert.equal(step, 50, '가장 긴 폭 200 을 다섯 칸으로 — 40 이 아니라 50')
|
|
@@ -62,3 +62,8 @@ test('붙인 부품처럼 옮길 수 없는 것은 맞추기도 없다', () => {
|
|
|
62
62
|
assert.deepEqual(alignChanges(probe(), 'body', { translate: false, rotate: false, scale: false }, 'floor'), [])
|
|
63
63
|
assert.match(read('../client/modeller/v3-editor.ts'), /if \(!this\.selected \|\| !reach\.translate\) return null/)
|
|
64
64
|
})
|
|
65
|
+
|
|
66
|
+
test('the grid step reads well -- 27 or 33 would make coordinates unreadable -- and is never 0', () => {
|
|
67
|
+
assert.deepEqual([3000, 1000, 137, 4].map(niceStep), [500, 200, 20, 1])
|
|
68
|
+
assert.ok(niceStep(0) > 0 && niceStep(-100) > 0)
|
|
69
|
+
})
|
package/test/v3-clips.test.ts
CHANGED
package/test/v3-controls.test.ts
CHANGED
package/test/v3-fields.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* **값 칸의
|
|
2
|
+
* **값 칸의 묶음** (사용자 2026-09-24: 「적절한 그루핑이 없어서 가시성이 좋지 않다. 크기는 왜
|
|
3
3
|
* 한줄씩 차지하는가」). 위치 · 회전 · 크기는 X · Y · Z 세 칸 한 줄, 형상의 나머지 치수는 제 제목 아래 한 줄씩.
|
|
4
4
|
*/
|
|
5
5
|
import { test } from 'node:test'
|
|
@@ -38,7 +38,7 @@ test('오른쪽 칸은 한 벌의 규격 — 요소마다 제 제목 · 칸 ·
|
|
|
38
38
|
}
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
-
test('긴 설명은 제목 옆 「?」 로 연다 — 늘 펼쳐 두지 않는다
|
|
41
|
+
test('긴 설명은 제목 옆 「?」 로 연다 — 늘 펼쳐 두지 않는다', () => {
|
|
42
42
|
for (const [file, key] of [
|
|
43
43
|
['figure-v3-links', 'figure.text.motion-explained'],
|
|
44
44
|
['figure-v3-links', 'figure.text.place-explained'],
|
|
@@ -81,7 +81,7 @@ test('편집기는 저작한 부품 단위로 고르고 보인다 — 되풀이
|
|
|
81
81
|
assert.ok(editor.includes('.parts=${this.partRows(view.geometry)}'), '목록은 저작한 부품 한 줄씩')
|
|
82
82
|
assert.ok(editor.includes("this.session!.command({ kind: 'set-repeat', part: this.selected, pitch })"), 'pitch 는 명령으로 정한다')
|
|
83
83
|
const parts = read('../client/modeller/figure-v3-parts.ts')
|
|
84
|
-
assert.ok(parts.includes('?disabled=${this.locked || full}'), '등급 한도에 이르면 팔레트가 잠긴다
|
|
84
|
+
assert.ok(parts.includes('?disabled=${this.locked || full}'), '등급 한도에 이르면 팔레트가 잠긴다')
|
|
85
85
|
})
|
|
86
86
|
|
|
87
87
|
test('pitch 는 한 곳에서 하나의 값이고, 늘 set-repeat 로 바꿔 최대 길이가 따라온다', () => {
|
|
@@ -98,7 +98,7 @@ test('pitch 는 한 곳에서 하나의 값이고, 늘 set-repeat 로 바꿔 최
|
|
|
98
98
|
}
|
|
99
99
|
})
|
|
100
100
|
|
|
101
|
-
test('기즈모로
|
|
101
|
+
test('기즈모로 끈, 규칙이 자리를 정하는 부품은 규칙을 지킨 채 옮겨지고, 관절·붙이기가 정하는 부품은 까닭을 말한다 — 튕겨 돌아가지 않는다', () => {
|
|
102
102
|
const editor = readFileSync(fileURLToPath(new URL('../client/modeller/v3-editor.ts', import.meta.url)), 'utf8')
|
|
103
103
|
const moved = editor.slice(editor.indexOf(' private transformed('), editor.indexOf(' /** 맞추기'))
|
|
104
104
|
assert.ok(moved.includes("kind: 'nudge-part',"), '사용자 2026-09-24: 「기즈모 컨트롤도 전혀 작동이 안되는군」')
|
package/test/v3-gizmo.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **부품을 끌어서 옮기고 · 돌리고 · 키운다** (2026-09-24).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 부품을 고르면 기즈모가 붙고 W · E · R 로 옮기기 · 돌리기 · 크기가 갈린다. 끈 결과는 숫자 칸으로 되쓰인다 —
|
|
5
|
+
* 되쓸 숫자가 부품 자신의 것일 때만. 붙인 부품처럼 되쓸 수 없는 것은 기즈모가 내지 않는다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
8
8
|
import assert from 'node:assert/strict'
|
|
@@ -87,7 +87,7 @@ test('끈 것은 세션의 한 걸음이다 — 한 번의 되돌리기로 돌
|
|
|
87
87
|
assert.ok(moved.includes("kind: 'nudge-part',"), 'a nudge is one command, one step')
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
test('캔버스 — 기즈모 · W E R · 스냅은 씬의 편집 모드가
|
|
90
|
+
test('캔버스 — 기즈모 · W E R · 스냅은 씬의 편집 모드가 한다', () => {
|
|
91
91
|
const canvas = read('../client/modeller/v3-canvas.ts')
|
|
92
92
|
assert.match(canvas, /mode: 1\n/)
|
|
93
93
|
assert.doesNotMatch(canvas, /TransformControls|OrbitControls/)
|
|
@@ -119,7 +119,7 @@ test('끄는 동안은 모아 두고 놓을 때 한 번 적는다 — 크기 기
|
|
|
119
119
|
})
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
test('원기둥 · 구도 크기 기즈모가 먹는다 — 단면은 원으로
|
|
122
|
+
test('원기둥 · 구도 크기 기즈모가 먹는다 — 단면은 원으로', async () => {
|
|
123
123
|
const { addV3Primitive, createV3Asset } = await import('@hatiolab/figure-model')
|
|
124
124
|
const { paletteRecipe } = await import('../client/modeller/v3-palette.ts')
|
|
125
125
|
const { gizmoChanges, gizmoReachOf } = await import('../client/modeller/v3-gizmo.ts')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* **고른 부품이 어디서 얼마나 도는지** (사용자 2026-09-24: 「로봇암은 편집기에서 원래는 부품을 선택했을 때, 가변 각도가
|
|
3
|
-
* 표현이 되었었는데」).
|
|
3
|
+
* 표현이 되었었는데」). 관절의 가동 범위를 부채꼴로 저작면에 그린다. 부채꼴의 두 끝이 제어값의 최솟값과 최댓값에서
|
|
4
4
|
* 부품이 실제로 가는 쪽을 가리키는지를 — 평가해서 — 본다.
|
|
5
5
|
*/
|
|
6
6
|
import { test } from 'node:test'
|
|
@@ -8,7 +8,7 @@ import assert from 'node:assert/strict'
|
|
|
8
8
|
import { readFileSync } from 'node:fs'
|
|
9
9
|
import { fileURLToPath } from 'node:url'
|
|
10
10
|
|
|
11
|
-
import { addV3Box, applyV3Authoring, compileV3Asset,
|
|
11
|
+
import { addV3Box, applyV3Authoring, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
|
|
12
12
|
import type { V3Asset } from '@hatiolab/figure-model'
|
|
13
13
|
|
|
14
14
|
import { JOINT_FAN, jointFanOf } from '../client/modeller/v3-joint-fan.ts'
|
|
@@ -87,31 +87,16 @@ test('the editor draws it for the selected part, with the pose in hand; the canv
|
|
|
87
87
|
assert.ok(canvas.includes('{ ...fan, capturable: false, locked: true }'))
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
/* ── Joints
|
|
90
|
+
/* ── Joints on a chain (user 2026-09-24: 「로봇암 axis와 관절의 각도 표현을 다시 검증해보라」) ───────── */
|
|
91
91
|
|
|
92
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* A column: a link on a hinge at the root, a tip on a wrist inside it -- a first joint and a nested one. `housing` rides
|
|
94
|
+
* the hinge centred on the wrist's axis (as ROBOT_ARM's elbow-housing is the elbow's); `badge` rides the hinge and holds
|
|
95
|
+
* no pivot.
|
|
96
|
+
*/
|
|
97
|
+
const fixture = (name: string): V3Asset => JSON.parse(readFileSync(fileURLToPath(new URL(`./fixtures/${name}.json`, import.meta.url)), 'utf8'))
|
|
93
98
|
function column(): V3Asset {
|
|
94
|
-
|
|
95
|
-
version: 2,
|
|
96
|
-
type: 'COLUMN',
|
|
97
|
-
base: { x: 200, y: 700, z: 200 },
|
|
98
|
-
parts: [
|
|
99
|
-
{ name: 'base', primitive: 'cube', transform: { position: { x: 0, y: 50, z: 0 }, size: { x: 200, y: 100, z: 200 } }, material: { token: 'palette.dark' } },
|
|
100
|
-
{ name: 'link', primitive: 'cube', transform: { position: { x: 0, y: 300, z: 0 }, size: { x: 80, y: 400, z: 80 } }, material: { token: 'palette.primary' }, parent: 'base' },
|
|
101
|
-
{ name: 'tip', primitive: 'cube', transform: { position: { x: 0, y: 600, z: 0 }, size: { x: 60, y: 200, z: 60 } }, material: { token: 'palette.primary' }, parent: 'link' },
|
|
102
|
-
/* Rides the hinge, centred on the wrist's axis -- the wrist's housing, as ROBOT_ARM's elbow-housing is the elbow's. */
|
|
103
|
-
{ name: 'housing', primitive: 'cube', transform: { position: { x: 0, y: 500, z: 0 }, size: { x: 100, y: 100, z: 100 } }, material: { token: 'palette.dark' }, parent: 'link' },
|
|
104
|
-
/* Rides the hinge and holds no pivot. */
|
|
105
|
-
{ name: 'badge', primitive: 'cube', transform: { position: { x: 60, y: 300, z: 0 }, size: { x: 10, y: 40, z: 40 } }, material: { token: 'palette.dark' }, parent: 'link' }
|
|
106
|
-
],
|
|
107
|
-
joints: [
|
|
108
|
-
{ name: 'hinge', child: 'link', type: 'revolute', origin: { x: 0, y: 100, z: 0 }, axis: { x: 1, y: 0, z: 0 }, limits: { min: -90, max: 60 } },
|
|
109
|
-
{ name: 'wrist', child: 'tip', type: 'revolute', origin: { x: 0, y: 500, z: 0 }, axis: { x: 0, y: 0, z: 1 }, limits: { min: -120, max: 120 } }
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
const result = convertV2ToV3(source, { palette: { 'palette.dark': '#333333', 'palette.primary': '#3366cc' } } as never)
|
|
113
|
-
assert.equal(result.status, 'converted', JSON.stringify((result as { refusals?: unknown }).refusals))
|
|
114
|
-
return (result as unknown as { asset: V3Asset }).asset
|
|
99
|
+
return fixture('joint-column')
|
|
115
100
|
}
|
|
116
101
|
|
|
117
102
|
const sub = (a: number[], b: number[]) => a.map((v, i) => v - b[i]!)
|
|
@@ -195,23 +180,8 @@ test('one joint, one fan: the housing and the part it turns show the same fan, s
|
|
|
195
180
|
|
|
196
181
|
test('an axis running through a part does not make it that joint’s: a roll along a link shows on the roller, not the link', () => {
|
|
197
182
|
/* ROBOT_ARM's forearm showed the wrist roll, whose axis runs its length (2026-09-24 audit). */
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
type: 'ROLLER',
|
|
201
|
-
base: { x: 200, y: 800, z: 200 },
|
|
202
|
-
parts: [
|
|
203
|
-
{ name: 'base', primitive: 'cube', transform: { position: { x: 0, y: 50, z: 0 }, size: { x: 200, y: 100, z: 200 } }, material: { token: 'palette.dark' } },
|
|
204
|
-
{ name: 'link', primitive: 'cube', transform: { position: { x: 0, y: 300, z: 0 }, size: { x: 80, y: 400, z: 80 } }, material: { token: 'palette.primary' }, parent: 'base' },
|
|
205
|
-
{ name: 'roller', primitive: 'cube', transform: { position: { x: 30, y: 600, z: 0 }, size: { x: 120, y: 200, z: 60 } }, material: { token: 'palette.primary' }, parent: 'link' }
|
|
206
|
-
],
|
|
207
|
-
joints: [
|
|
208
|
-
{ name: 'hinge', child: 'link', type: 'revolute', origin: { x: 0, y: 100, z: 0 }, axis: { x: 1, y: 0, z: 0 }, limits: { min: -90, max: 60 } },
|
|
209
|
-
{ name: 'twist', child: 'roller', type: 'revolute', origin: { x: 0, y: 500, z: 0 }, axis: { x: 0, y: 1, z: 0 }, limits: { min: -170, max: 170 } }
|
|
210
|
-
]
|
|
211
|
-
}
|
|
212
|
-
const result = convertV2ToV3(source, { palette: { 'palette.dark': '#333333', 'palette.primary': '#3366cc' } } as never)
|
|
213
|
-
assert.equal(result.status, 'converted')
|
|
214
|
-
const asset = (result as unknown as { asset: V3Asset }).asset
|
|
183
|
+
/* A link on a hinge and a roller on a twist whose axis runs along the link. */
|
|
184
|
+
const asset = fixture('joint-roller')
|
|
215
185
|
const turns = (part: string) => (jointFanOf(asset, part) as unknown as { turns?: string } | null)?.turns ?? null
|
|
216
186
|
assert.equal(turns('link'), 'hinge', 'the twist axis runs through the link, and the link does not twist')
|
|
217
187
|
assert.equal(turns('roller'), 'twist')
|
package/test/v3-keys.test.ts
CHANGED
package/test/v3-layout.test.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
3
|
-
* 좋았다. 그 보다도 더 좋아야지.」).
|
|
2
|
+
* **모델러의 구성 — 세 칸과 오른쪽 탭** (사용자 2026-09-24).
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* 부품 목록 · 캔버스 · 오른쪽 칸(아이콘 탭 여섯). 전에는 부품 속성이 캔버스 아래 좁은 칸에 끼어 있었다.
|
|
6
5
|
* 이 시험은 소스에서 그 구성을 확인한다.
|
|
7
6
|
*/
|
|
8
7
|
import { test } from 'node:test'
|
|
@@ -24,7 +23,7 @@ test('세 칸 — 부품 목록 · 캔버스 · 오른쪽 칸, 캔버스 아래
|
|
|
24
23
|
assert.doesNotMatch(read('../client/pages/figure-v3-modeller-page.ts'), /<aside/)
|
|
25
24
|
})
|
|
26
25
|
|
|
27
|
-
test('오른쪽 칸의 탭
|
|
26
|
+
test('오른쪽 칸의 탭 여섯: 속성 · 능력 · 움직임 · 판정 · 환경 · 이력, 판정의 개수는 탭에 남는다', () => {
|
|
28
27
|
const tabs = [...renderBody.matchAll(/this\.renderTab\('([a-z]+)'/g)].map(m => m[1])
|
|
29
28
|
assert.deepEqual(tabs, ['properties', 'capability', 'motion', 'verdict', 'view', 'history'])
|
|
30
29
|
for (const tab of tabs) assert.ok(renderBody.includes(`?off=\${tab !== '${tab}'}`), `${tab} 는 감추되 떼지 않는다`)
|
|
@@ -66,7 +65,7 @@ test('띠 — 칸 접기 · 되돌리기 · 전체 보기 · 저장 상태 ·
|
|
|
66
65
|
}
|
|
67
66
|
})
|
|
68
67
|
|
|
69
|
-
test('탭 아이콘은 오른쪽 칸 맨 위 — 타입 칸은
|
|
68
|
+
test('탭 아이콘은 오른쪽 칸 맨 위 — 타입 칸은 속성 탭 안의 첫 칸', () => {
|
|
70
69
|
const aside = renderBody.slice(renderBody.indexOf('<aside slot="end">'), renderBody.indexOf('</aside>'))
|
|
71
70
|
assert.match(aside, /^<aside slot="end">\s*<div tabs>/, '탭이 첫째')
|
|
72
71
|
const properties = aside.slice(aside.indexOf("?off=${tab !== 'properties'}"), aside.indexOf("?off=${tab !== 'capability'}"))
|
package/test/v3-links.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* **V3 부품이 무엇에 묶여 있나 — 크기 따르기 · 붙이기 · 움직임** (
|
|
2
|
+
* **V3 부품이 무엇에 묶여 있나 — 크기 따르기 · 붙이기 · 움직임** (2026-09-24).
|
|
3
3
|
*
|
|
4
4
|
* 세 칸이 보여 주는 것은 figure-model 명령이 남긴 그래프를 거꾸로 읽은 것이다. 명령을 실제로 적용한 뒤 읽어서,
|
|
5
5
|
* 화면이 명령과 같은 말을 하는지 잰다.
|
|
@@ -157,7 +157,7 @@ test('받는 종류가 없으면 자리를 만들지 않는다 — 형식에 「
|
|
|
157
157
|
assert.deepEqual(acceptsOf(' , ,'), [])
|
|
158
158
|
})
|
|
159
159
|
|
|
160
|
-
/* ──
|
|
160
|
+
/* ── Proportional rules (user 2026-09-24: ROBOT_ARM at twice the size looked broken) ───────────── */
|
|
161
161
|
|
|
162
162
|
const sizedPair = () => {
|
|
163
163
|
let asset = createV3Asset('Probe')
|
|
@@ -175,7 +175,7 @@ test('비례 — 인스턴스 크기의 몫을 따르는 치수는 비례로 읽
|
|
|
175
175
|
const share = applyV3Authoring(asset, { kind: 'link-dimension', part: 'flange', axis: 'y', source: { of: 'instance', axis: 'y' }, times: 0.1 })
|
|
176
176
|
assert.deepEqual(followOf(share, 'flange', 'y'), { of: 'instance', axis: 'y', times: 0.1 })
|
|
177
177
|
|
|
178
|
-
/*
|
|
178
|
+
/* A span: the size less what the others take -- a sum, not a share (ROBOT_ARM's tool flange). */
|
|
179
179
|
const fill = structuredClone(asset)
|
|
180
180
|
const m = fill.document.model as unknown as { nodes: { id: string; op: string; args: string[]; outputs: Record<string, string> }[]; constants: { id: string; unit: string; value: number }[] }
|
|
181
181
|
m.constants.push({ id: 'k.gaps', unit: 'mm', value: -90 })
|
package/test/v3-material.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **부품의 재질을 고친다** (2026-09-24).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 전의 편집기는 색 역할을 날것의 이름(`palette.dark`)으로 고르는 칸 하나였다. 재질 칸이 내는 편집이 figure-model 에서
|
|
5
5
|
* 정말로 받아들여지고, 그려지는 것을 바꾸는지를 잰다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
@@ -67,7 +67,7 @@ test('자산에 없는 색 역할은 그 색을 함께 보낸다 — 새 도형
|
|
|
67
67
|
assert.match(material, /\.\.\.\(Object\.keys\(palette\)\.length \? \{ palette \} : \{\}\)/)
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
test('분할
|
|
70
|
+
test('분할 수 — 흔한 넷은 다각형 윤곽과 삼각형 수로, 나머지는 접어 둔다', () => {
|
|
71
71
|
const source = readFileSync(fileURLToPath(new URL('../client/modeller/figure-v3-material.ts', import.meta.url)), 'utf8')
|
|
72
72
|
assert.ok(source.includes('SEGMENT_PRESETS.slice(0, 4).map(') && source.includes('${silhouette(preset)}'))
|
|
73
73
|
assert.ok(source.includes('<details free') && source.includes('SEGMENT_PRESETS.slice(4)'))
|
package/test/v3-maturity.test.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **성숙도 탭** (사용자 2026-09-24: 「분석부분도 많이 부족하고, 왜 이렇게 너프되었는지」).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 화면이 그 순서와 말로 그리는지를 본다.
|
|
4
|
+
* 탭은 개수 · 등급과 눈금 · 등급을 정한 한도 · 여섯 축 radar · 한도 막대 · 그리는 비용 · 재사용 추이를 보인다. 여기서는
|
|
5
|
+
* 그 수들이 V3 자산에서 바르게 나오는지, V3 렌더러가 실제로 나눠 쓰는 것을 세는지, 화면이 그 순서와 말로 그리는지를 본다.
|
|
7
6
|
*/
|
|
8
7
|
import { test } from 'node:test'
|
|
9
8
|
import assert from 'node:assert/strict'
|
|
@@ -49,7 +48,7 @@ test('부품이 없으면 리포트가 없다 — 0 으로 그리지 않는다',
|
|
|
49
48
|
assert.equal(v3ReportOf(createV3Asset('Empty')), undefined)
|
|
50
49
|
})
|
|
51
50
|
|
|
52
|
-
test('
|
|
51
|
+
test('등급의 눈금 — 한도의 절반 A · 4분의 3 B · 한도 C · 넘치면 D', () => {
|
|
53
52
|
assert.deepEqual(GRADE_SCALE, [
|
|
54
53
|
['A', 0.5],
|
|
55
54
|
['B', 0.75],
|
|
@@ -104,7 +103,7 @@ test('투명 재질은 서로 다른 재질로 센다 — 둘이면 한도 1 을
|
|
|
104
103
|
assert.equal(v3ReportOf(same)!.score.budgets.find(b => b.key === 'transparent')!.used, 1, '같은 재질은 하나로 나눠 쓴다')
|
|
105
104
|
})
|
|
106
105
|
|
|
107
|
-
test('상태를 따라 움직이는 부품을 센다 —
|
|
106
|
+
test('상태를 따라 움직이는 부품을 센다 — 「따로 움직이는 부품」', () => {
|
|
108
107
|
let asset = createV3Asset('Probe')
|
|
109
108
|
asset = addV3Box(asset, cube('base', 0))
|
|
110
109
|
asset = addV3Box(asset, cube('arm', 200))
|
|
@@ -138,7 +137,7 @@ test('서로 다른 형상은 things-scene 이 정점 버퍼를 나눠 쓰는
|
|
|
138
137
|
assert.equal(report.axes.find(axis => axis.key === 'reuse')!.ratio, 1 - 4 / 6)
|
|
139
138
|
})
|
|
140
139
|
|
|
141
|
-
test('여섯 축 —
|
|
140
|
+
test('여섯 축 — 정해진 순서, 잴 수 없는 둘은 비율 없이 까닭을 단다', () => {
|
|
142
141
|
let asset = withPalette('palette.accent')
|
|
143
142
|
asset = addV3Box(asset, cube('a', 50))
|
|
144
143
|
asset = addV3Box(asset, cube('b', 150, 'palette.accent'))
|
|
@@ -161,7 +160,7 @@ test('여섯 축 — V2 의 순서, 잴 수 없는 둘은 비율 없이 까닭
|
|
|
161
160
|
assert.equal(byKey.readable!.reading, '2')
|
|
162
161
|
})
|
|
163
162
|
|
|
164
|
-
test('재질이 한도를 넘으면 일관성이 한 칸 내려간다 —
|
|
163
|
+
test('재질이 한도를 넘으면 일관성이 한 칸 내려간다 — 셋 중 하나', () => {
|
|
165
164
|
let asset = withPalette('t1', 't2', 't3')
|
|
166
165
|
asset = addV3Box(asset, cube('a', 0))
|
|
167
166
|
asset = addV3Box(asset, cube('b', 200, 't1'))
|
|
@@ -205,7 +204,7 @@ test('추이 — 1 에서 1만까지 1·2·5, 삼각형과 draw call 은 오르
|
|
|
205
204
|
assert.deepEqual(layout.xTicks.map(t => t.label), ['1', '10', '100', '1k', '10k'])
|
|
206
205
|
})
|
|
207
206
|
|
|
208
|
-
test('화면은
|
|
207
|
+
test('화면은 리포트의 차례로 그린다 — 개수 · 축 · 한도 · 그리는 비용 · 추이 · 발행 판정', () => {
|
|
209
208
|
const order = ['this.renderVerdict(report)', 'this.renderAxes(report)', 'this.renderBudgets(report)', 'this.renderMetrics(report)', 'this.renderCurve()', 'this.renderGate()']
|
|
210
209
|
// From the full report, past the early return that shows only guidance and the release verdict.
|
|
211
210
|
const at = order.map(call => verdict.indexOf(call, verdict.indexOf('${this.renderVerdict(report)}')))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **편집기의 부품 목록** (2026-09-24).
|
|
3
3
|
*
|
|
4
|
-
* `figure-v3-parts` 는
|
|
4
|
+
* `figure-v3-parts` 는 지어졌지만(fdc15e162e) 편집기 어디에도
|
|
5
5
|
* 걸리지 않았다. 편집기는 맨 단추 네 개와 이름 단추 목록이었다. 패널이 내는 사건마다 받는 자리가 있어야 한다 —
|
|
6
6
|
* 없으면 눌러도 아무 일이 없다.
|
|
7
7
|
*/
|
|
@@ -53,7 +53,7 @@ test('좁은 칸에서도 고른 줄의 이름과 조작이 다 보인다 —
|
|
|
53
53
|
assert.match(narrow, /li\[selected\] div\[ops\] \{\s*flex-basis: 100%;/)
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
test('둥근 상자는 제 이름으로 부른다 — 「사각
|
|
56
|
+
test('둥근 상자는 제 이름으로 부른다 — 「사각 단면」이 아니다', () => {
|
|
57
57
|
assert.match(panel, /kind: 'rounded-box', icon: 'crop_square', label: 'figure\.label\.primitive-rounded-box'/)
|
|
58
58
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms'])
|
|
59
59
|
assert.ok(JSON.parse(read(`../translations/${lang}.json`))['figure.label.primitive-rounded-box'], lang)
|
package/test/v3-preview.test.ts
CHANGED
|
@@ -9,7 +9,7 @@ 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
|
-
/* 점수는
|
|
12
|
+
/* 점수는 저장할 때 재지 않으므로 비운다. */
|
|
13
13
|
assert.equal(saved.score, null)
|
|
14
14
|
/* 예산은 잰다. 빈 자산은 0 이고, 그것은 「안 쟀다」가 아니라 「그릴 것이 없다」는 뜻이다. */
|
|
15
15
|
assert.equal(saved.triangles, 0)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **도형의 속성을 고친다** (2026-09-24).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 속성 탭은 이름 · 배치 기준 · 차지하는 공간 · 분류 · 태그를 맨 위에 둔다. 전에는 타입 칸 하나뿐이었다.
|
|
5
5
|
* 두 축을 본다: 탭이 내는 명령이 figure-model 에서 **정말로** 그 일을 하는가, 그리고 화면이 명령과 행 쓰기를 잇는가.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
@@ -53,7 +53,7 @@ test('이름을 바꾸면 자산의 이름이 바뀐다 — 저장하면 목록
|
|
|
53
53
|
assert.equal(facts(applyV3Authoring(probe(), { kind: 'rename', name: 'Roller conveyor' })).name, 'Roller conveyor')
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
test('태그 줄은
|
|
56
|
+
test('태그 줄은 쉼표로 나뉘어 목록이 된다', () => {
|
|
57
57
|
assert.deepEqual(tagsOf('#반출, 컨베이어,, 반출 '), ['반출', '컨베이어'])
|
|
58
58
|
assert.deepEqual(tagsOf(''), [], '빈 줄은 전부 지우는 것이다')
|
|
59
59
|
})
|
package/test/v3-refusals.test.ts
CHANGED
|
@@ -122,3 +122,15 @@ test('배치를 바꿀 수 없는 까닭 — 평면에 붙은 부품과 새 평
|
|
|
122
122
|
const props = readFileSync(fileURLToPath(new URL('../client/modeller/figure-v3-properties.ts', import.meta.url)), 'utf8')
|
|
123
123
|
assert.ok(props.includes('select.value = current'), '거절되면 목록이 원래 배치로 돌아온다')
|
|
124
124
|
})
|
|
125
|
+
|
|
126
|
+
test('범위 밖 값은 받을 수 있는 범위를 숫자로 말한다 — 커널의 영어가 아니다 (Figure 레인 :3300, 2026-09-26)', async () => {
|
|
127
|
+
const { inputRangeRefusalOf } = await import('../client/modeller/v3-refusals.ts')
|
|
128
|
+
const range = (message: string) => Object.assign(new Error(message), { code: 'INPUT_RANGE' })
|
|
129
|
+
const size = inputRangeRefusalOf(range('INPUT_RANGE: size.x: 6021100 outside [300,1200]'), say)!
|
|
130
|
+
assert.ok(size.includes('X') && size.includes('300') && size.includes('1,200') && size.includes('6,021,100'), size)
|
|
131
|
+
assert.ok(!/outside|INPUT_RANGE/.test(size))
|
|
132
|
+
const other = inputRangeRefusalOf(range('INPUT_RANGE: lift.stroke: -5 outside [0,1]'), say)!
|
|
133
|
+
assert.ok(other.includes('0') && other.includes('1') && other.includes('-5'), other)
|
|
134
|
+
assert.equal(inputRangeRefusalOf(range('INPUT_RANGE: steps: at the length it is authored (100 mm) a pitch under 1 mm makes more than 50 copies'), say), null)
|
|
135
|
+
assert.equal(inputRangeRefusalOf(Object.assign(new Error('x'), { code: 'EDIT_TARGET' }), say), null)
|
|
136
|
+
})
|
|
@@ -53,10 +53,10 @@ test('선언한 공간을 넘은 부품도 같은 한 번으로 풀리고, 배
|
|
|
53
53
|
|
|
54
54
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
55
55
|
|
|
56
|
-
test('
|
|
56
|
+
test('모델러가 발행 패널을 걸고, 패널이 내는 셋을 받는다', () => {
|
|
57
57
|
const page = read('../client/pages/figure-v3-modeller-page.ts')
|
|
58
58
|
|
|
59
|
-
assert.ok(/<figure-history-panel
|
|
59
|
+
assert.ok(/<figure-history-panel/.test(page), '패널을 건다')
|
|
60
60
|
for (const event of ['fit-base-to-parts', 'figure-released', 'figure-reverted']) {
|
|
61
61
|
assert.ok(page.includes(`@${event}=`), `패널이 내는 ${event} 를 받는 자리가 없다 — 눌러도 아무 일이 없다`)
|
|
62
62
|
}
|
|
@@ -65,9 +65,9 @@ test('V3 화면이 발행 패널을 V3 로 걸고, 패널이 내는 셋을 받
|
|
|
65
65
|
assert.ok(/:host \{[^}]*overflow: hidden/.test(page), '페이지가 주어진 폭 안에 든다')
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
test('패널은 받는 화면이 있는 조치만 그린다 —
|
|
68
|
+
test('패널은 받는 화면이 있는 조치만 그린다 — 「바닥에 앉히기」는 없다', () => {
|
|
69
69
|
const panel = read('../client/modeller/figure-history-panel.ts')
|
|
70
70
|
|
|
71
|
-
assert.ok(
|
|
72
|
-
assert.ok(/code === 'part-outside-occupancy' \|\| code === 'occupancy-undeclared'/.test(panel), '
|
|
71
|
+
assert.ok(!/settle-parts-on-floor|apply-sizing-fix|restore-released-footprint/.test(panel), '받는 화면이 없는 조치를 내지 않는다')
|
|
72
|
+
assert.ok(/code === 'part-outside-occupancy' \|\| code === 'occupancy-undeclared'/.test(panel), '두 판정에 맞추기를 낸다')
|
|
73
73
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* **편집 캔버스는
|
|
2
|
+
* **편집 캔버스는 things-scene 편집 모드 위에 선다** (사용자 2026-09-24).
|
|
3
3
|
*
|
|
4
4
|
* 부품마다 `figure-v3-part` 컴포넌트로 판에 세우면 고르기 · 주황 테두리 · 기즈모와 스냅 · 박스 선택 · 카메라(감속 ·
|
|
5
5
|
* 커서 쪽 확대 · 더블클릭 맞추기 · 시점 1~6) · 스튜디오 환경 · 좌표 바탕 · 조명과 그림자가 씬에서 온다. 여기서는 그
|
|
@@ -37,7 +37,7 @@ test('things-scene 의 판을 편집 모드로 세우고, 보기 설정은 판
|
|
|
37
37
|
assert.match(canvas, /type: 'model-layer'/)
|
|
38
38
|
assert.match(canvas, /mode: 1\n/, '편집 모드 — 시점 단추 · 기즈모 · 도움말 · 단축키')
|
|
39
39
|
assert.ok(canvas.includes('...sceneLook(this.view ?? DEFAULT_VIEW)'))
|
|
40
|
-
assert.ok(canvas.includes('pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2)'), '
|
|
40
|
+
assert.ok(canvas.includes('pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2)'), '두 배 표본')
|
|
41
41
|
assert.doesNotMatch(canvas, /new THREE\.|WebGLRenderer|OrbitControls|TransformControls/, '제 장면을 짜지 않는다')
|
|
42
42
|
})
|
|
43
43
|
|
package/test/v3-theme.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **모델러는 테마를 따른다** (2026-09-24).
|
|
3
3
|
*
|
|
4
4
|
* V3 캔버스와 편집기는 색을 박아 두었다. 어두운 테마에서 가운데만 밝은 판이 떴고, 경계선과 눌린 단추 · 오류 칸도
|
|
5
5
|
* 밝은 색 그대로였다. 색은 셸의 토큰에서 온다(UX 결정 — 바탕은 surface-container, 눌림은 secondary-container,
|
|
@@ -24,7 +24,7 @@ test('편집기에 박힌 색이 없다 — 토큰의 대체값만 남는다', (
|
|
|
24
24
|
assert.deepEqual(fixed(editor), [], 'v3-editor.ts')
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
test('캔버스 바탕은
|
|
27
|
+
test('캔버스 바탕은 작업대 회색 — 판의 하늘색이라 환경 탭에서 바꾼다', () => {
|
|
28
28
|
assert.ok(canvas.includes('background-color: ${unsafeCSS(WORKBENCH)}'), '씬이 서기 전 · 2D 쪽도 같은 색')
|
|
29
29
|
assert.ok(canvas.includes("sky: 'color'") && canvas.includes('...sceneLook(this.view ?? DEFAULT_VIEW)'), '3D 바탕은 보기 설정의 skyColor')
|
|
30
30
|
assert.match(read('../client/modeller/figure-view.ts'), /skyColor: WORKBENCH/)
|
package/test/v3-verdict.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* **편집하는 동안 판정한다** (2026-09-24).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 모델러는 고치는 동안 「한 화면에 몇 개」와 「무엇이 발행을 막나」를 보여 준다. 전에는 둘 다 없어서 막힌
|
|
5
5
|
* 까닭을 저장한 뒤 발행 패널에서야 알았다. 판정 탭이 편집 중인 원본으로 둘을 답한다.
|
|
6
6
|
*
|
|
7
7
|
* 세 축을 본다: 개수가 목록 카드와 같은 셈인가, 판정이 발행 패널과 같은 문 · 같은 문장인가, 화면이 탭과 조치를
|
package/test/v3-view.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* **보기 — 작업대의 설정** (
|
|
2
|
+
* **보기 — 작업대의 설정** (「환경」 탭). 도형에 저장되지 않고, 보는 사람의 브라우저에 남는다.
|
|
3
3
|
*/
|
|
4
4
|
import { test } from 'node:test'
|
|
5
5
|
import assert from 'node:assert/strict'
|
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
9
9
|
|
|
10
10
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
11
11
|
|
|
12
|
-
test('환경
|
|
12
|
+
test('환경 탭 — 설정은 판의 상태 이름이고, 도형이 아니라 브라우저에 둔다', () => {
|
|
13
13
|
const editor = read('../client/modeller/v3-editor.ts')
|
|
14
14
|
assert.ok(editor.includes('<figure-settings .view=${this.view} @update-view=${(e: CustomEvent) => this.setView(e.detail.view)}></figure-settings>'))
|
|
15
15
|
assert.ok(editor.includes("localStorage.setItem('figure-v3-view'"))
|