@things-factory/figure-ui 10.1.75 → 10.1.76
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/figure-assistant.ts +3 -3
- package/client/graphql/{v3-store.ts → store.ts} +6 -6
- package/client/modeller/{v3-ai-proposal.ts → ai-proposal.ts} +8 -8
- package/client/modeller/{v3-align.ts → align.ts} +14 -14
- package/client/modeller/{v3-canvas.ts → canvas.ts} +13 -13
- package/client/modeller/{v3-clips.ts → clips.ts} +5 -5
- package/client/modeller/{v3-controls.ts → controls.ts} +2 -2
- package/client/modeller/{v3-editor.ts → editor.ts} +117 -117
- package/client/modeller/{figure-v3-capability.ts → figure-capability-panel.ts} +7 -7
- package/client/modeller/{figure-v3-clips.ts → figure-clips.ts} +10 -10
- package/client/modeller/{figure-v3-controls.ts → figure-controls.ts} +9 -9
- package/client/modeller/{v3-figure-facts.ts → figure-facts.ts} +11 -11
- package/client/modeller/{figure-v3-first-save.ts → figure-first-save.ts} +3 -3
- package/client/modeller/figure-history-panel.ts +1 -1
- package/client/modeller/{figure-v3-links.ts → figure-links.ts} +35 -35
- package/client/modeller/{figure-v3-material.ts → figure-material-panel.ts} +20 -20
- package/client/modeller/{figure-v3-parts.ts → figure-parts.ts} +15 -15
- package/client/modeller/{figure-v3-preview.ts → figure-preview.ts} +14 -14
- package/client/modeller/{figure-v3-properties.ts → figure-properties.ts} +25 -25
- package/client/modeller/{figure-v3-recipe-catalog.ts → figure-recipe-catalog.ts} +10 -10
- package/client/modeller/{figure-v3-verdict.ts → figure-verdict.ts} +24 -24
- package/client/modeller/gate-findings.ts +2 -2
- package/client/modeller/{v3-gizmo.ts → gizmo.ts} +11 -11
- package/client/modeller/{v3-joint-drag.ts → joint-drag.ts} +11 -11
- package/client/modeller/{v3-joint-fan.ts → joint-fan.ts} +13 -13
- package/client/modeller/{v3-links.ts → links.ts} +59 -59
- package/client/modeller/{v3-live.ts → live.ts} +9 -9
- package/client/modeller/{v3-maturity.ts → maturity.ts} +65 -65
- package/client/modeller/modeller-route.ts +1 -1
- package/client/modeller/{v3-occupancy.ts → occupancy.ts} +6 -6
- package/client/modeller/{v3-palette.ts → palette.ts} +3 -3
- package/client/modeller/{v3-preview-model.ts → preview-model.ts} +8 -8
- package/client/modeller/{v3-product-session.ts → product-session.ts} +25 -25
- package/client/modeller/{v3-recipe-source.ts → recipe-source.ts} +2 -2
- package/client/modeller/{v3-recipes.ts → recipes.ts} +9 -9
- package/client/modeller/{v3-renderer.ts → renderer.ts} +16 -16
- package/client/modeller/{v3-stage-model.ts → stage-model.ts} +15 -15
- package/client/modeller/{v3-thumbnail.ts → thumbnail.ts} +8 -8
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/{figure-v3-modeller-page.ts → figure-modeller-page.ts} +37 -37
- package/client/route.ts +1 -5
- package/dist-client/figure-assistant.js +3 -3
- package/dist-client/figure-assistant.js.map +1 -1
- package/dist-client/graphql/store.d.ts +3 -0
- package/dist-client/graphql/store.js +39 -0
- package/dist-client/graphql/store.js.map +1 -0
- package/dist-client/modeller/ai-proposal.d.ts +51 -0
- package/dist-client/modeller/ai-proposal.js +64 -0
- package/dist-client/modeller/ai-proposal.js.map +1 -0
- package/dist-client/modeller/align.d.ts +30 -0
- package/dist-client/modeller/align.js +75 -0
- package/dist-client/modeller/align.js.map +1 -0
- package/dist-client/modeller/canvas.d.ts +72 -0
- package/dist-client/modeller/canvas.js +379 -0
- package/dist-client/modeller/canvas.js.map +1 -0
- package/dist-client/modeller/clips.d.ts +39 -0
- package/dist-client/modeller/clips.js +58 -0
- package/dist-client/modeller/clips.js.map +1 -0
- package/dist-client/modeller/controls.d.ts +28 -0
- package/dist-client/modeller/controls.js +28 -0
- package/dist-client/modeller/controls.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +203 -0
- package/dist-client/modeller/editor.js +1526 -0
- package/dist-client/modeller/editor.js.map +1 -0
- package/dist-client/modeller/fields.d.ts +45 -0
- package/dist-client/modeller/fields.js +83 -0
- package/dist-client/modeller/fields.js.map +1 -0
- package/dist-client/modeller/figure-capability-panel.d.ts +22 -0
- package/dist-client/modeller/figure-capability-panel.js +141 -0
- package/dist-client/modeller/figure-capability-panel.js.map +1 -0
- package/dist-client/modeller/figure-clips.d.ts +31 -0
- package/dist-client/modeller/figure-clips.js +268 -0
- package/dist-client/modeller/figure-clips.js.map +1 -0
- package/dist-client/modeller/figure-controls.d.ts +36 -0
- package/dist-client/modeller/figure-controls.js +226 -0
- package/dist-client/modeller/figure-controls.js.map +1 -0
- package/dist-client/modeller/figure-facts.d.ts +35 -0
- package/dist-client/modeller/figure-facts.js +55 -0
- package/dist-client/modeller/figure-facts.js.map +1 -0
- package/dist-client/modeller/figure-first-save.d.ts +20 -0
- package/dist-client/modeller/figure-first-save.js +118 -0
- package/dist-client/modeller/figure-first-save.js.map +1 -0
- package/dist-client/modeller/figure-history-panel.d.ts +1 -1
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +77 -0
- package/dist-client/modeller/figure-links.js +649 -0
- package/dist-client/modeller/figure-links.js.map +1 -0
- package/dist-client/modeller/figure-material-panel.d.ts +39 -0
- package/dist-client/modeller/figure-material-panel.js +323 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -0
- package/dist-client/modeller/figure-parts.d.ts +69 -0
- package/dist-client/modeller/figure-parts.js +559 -0
- package/dist-client/modeller/figure-parts.js.map +1 -0
- package/dist-client/modeller/figure-preview.d.ts +34 -0
- package/dist-client/modeller/figure-preview.js +346 -0
- package/dist-client/modeller/figure-preview.js.map +1 -0
- package/dist-client/modeller/figure-properties.d.ts +45 -0
- package/dist-client/modeller/figure-properties.js +239 -0
- package/dist-client/modeller/figure-properties.js.map +1 -0
- package/dist-client/modeller/figure-recipe-catalog.d.ts +38 -0
- package/dist-client/modeller/figure-recipe-catalog.js +399 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
- package/dist-client/modeller/figure-verdict.d.ts +64 -0
- package/dist-client/modeller/figure-verdict.js +787 -0
- package/dist-client/modeller/figure-verdict.js.map +1 -0
- package/dist-client/modeller/first-save.d.ts +17 -0
- package/dist-client/modeller/first-save.js +26 -0
- package/dist-client/modeller/first-save.js.map +1 -0
- package/dist-client/modeller/gate-findings.d.ts +2 -2
- package/dist-client/modeller/gate-findings.js +2 -2
- package/dist-client/modeller/gate-findings.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +72 -0
- package/dist-client/modeller/gizmo.js +111 -0
- package/dist-client/modeller/gizmo.js.map +1 -0
- package/dist-client/modeller/help.d.ts +18 -0
- package/dist-client/modeller/help.js +37 -0
- package/dist-client/modeller/help.js.map +1 -0
- package/dist-client/modeller/joint-drag.d.ts +19 -0
- package/dist-client/modeller/joint-drag.js +97 -0
- package/dist-client/modeller/joint-drag.js.map +1 -0
- package/dist-client/modeller/joint-fan.d.ts +9 -0
- package/dist-client/modeller/joint-fan.js +276 -0
- package/dist-client/modeller/joint-fan.js.map +1 -0
- package/dist-client/modeller/keys.d.ts +22 -0
- package/dist-client/modeller/keys.js +26 -0
- package/dist-client/modeller/keys.js.map +1 -0
- package/dist-client/modeller/links.d.ts +267 -0
- package/dist-client/modeller/links.js +480 -0
- package/dist-client/modeller/links.js.map +1 -0
- package/dist-client/modeller/live.d.ts +37 -0
- package/dist-client/modeller/live.js +68 -0
- package/dist-client/modeller/live.js.map +1 -0
- package/dist-client/modeller/maturity.d.ts +120 -0
- package/dist-client/modeller/maturity.js +313 -0
- package/dist-client/modeller/maturity.js.map +1 -0
- package/dist-client/modeller/modeller-route.d.ts +1 -1
- package/dist-client/modeller/modeller-route.js +1 -1
- package/dist-client/modeller/modeller-route.js.map +1 -1
- package/dist-client/modeller/occupancy.d.ts +18 -0
- package/dist-client/modeller/occupancy.js +24 -0
- package/dist-client/modeller/occupancy.js.map +1 -0
- package/dist-client/modeller/palette.d.ts +7 -0
- package/dist-client/modeller/palette.js +46 -0
- package/dist-client/modeller/palette.js.map +1 -0
- package/dist-client/modeller/preview-model.d.ts +47 -0
- package/dist-client/modeller/preview-model.js +68 -0
- package/dist-client/modeller/preview-model.js.map +1 -0
- package/dist-client/modeller/product-session.d.ts +510 -0
- package/dist-client/modeller/product-session.js +157 -0
- package/dist-client/modeller/product-session.js.map +1 -0
- package/dist-client/modeller/recipe-source.d.ts +8 -0
- package/dist-client/modeller/recipe-source.js +14 -0
- package/dist-client/modeller/recipe-source.js.map +1 -0
- package/dist-client/modeller/recipes.d.ts +37 -0
- package/dist-client/modeller/recipes.js +103 -0
- package/dist-client/modeller/recipes.js.map +1 -0
- package/dist-client/modeller/refusals.d.ts +68 -0
- package/dist-client/modeller/refusals.js +142 -0
- package/dist-client/modeller/refusals.js.map +1 -0
- package/dist-client/modeller/renderer.d.ts +40 -0
- package/dist-client/modeller/renderer.js +73 -0
- package/dist-client/modeller/renderer.js.map +1 -0
- package/dist-client/modeller/stage-model.d.ts +39 -0
- package/dist-client/modeller/stage-model.js +147 -0
- package/dist-client/modeller/stage-model.js.map +1 -0
- package/dist-client/modeller/thumbnail.d.ts +3 -0
- package/dist-client/modeller/thumbnail.js +75 -0
- package/dist-client/modeller/thumbnail.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +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-modeller-page.d.ts +102 -0
- package/dist-client/pages/figure-modeller-page.js +548 -0
- package/dist-client/pages/figure-modeller-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +1 -5
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/docs/product-cutover.md +25 -0
- package/package.json +5 -5
- package/test/{v3-ai-proposal.test.ts → ai-proposal.test.ts} +6 -6
- package/test/{v3-align.test.ts → align.test.ts} +8 -8
- package/test/browser/modeller-page.js +30 -30
- package/test/browser/modeller.check.mjs +14 -14
- package/test/browser/serve.mjs +1 -1
- package/test/browser/thumbnail-page.js +6 -6
- package/test/{v3-clips.test.ts → clips.test.ts} +18 -18
- package/test/{v3-controls.test.ts → controls.test.ts} +15 -15
- package/test/{v3-fields.test.ts → fields.test.ts} +13 -13
- package/test/figure-assistant-surface.test.ts +3 -3
- package/test/{v3-first-save.test.ts → first-save.test.ts} +10 -10
- package/test/fixtures/joint-column.json +2 -2
- package/test/fixtures/joint-roller.json +2 -2
- package/test/fixtures/robot-arm-6dof.json +2 -2
- package/test/{v3-gizmo.test.ts → gizmo.test.ts} +33 -33
- package/test/{v3-joint-drag.test.ts → joint-drag.test.ts} +17 -17
- package/test/{v3-joint-fan.test.ts → joint-fan.test.ts} +18 -18
- package/test/joints.ts +6 -6
- package/test/{v3-keys.test.ts → keys.test.ts} +2 -2
- package/test/{v3-layout.test.ts → layout.test.ts} +8 -8
- package/test/{v3-links.test.ts → links.test.ts} +201 -201
- package/test/{v3-live.test.ts → live.test.ts} +18 -18
- package/test/{v3-material.test.ts → material.test.ts} +12 -12
- package/test/{v3-maturity.test.ts → maturity.test.ts} +74 -74
- package/test/modeller-route.test.ts +5 -6
- package/test/{v3-palette-icons.test.ts → palette-icons.test.ts} +1 -1
- package/test/{v3-parts-wiring.test.ts → parts-wiring.test.ts} +5 -5
- package/test/{v3-preview.test.ts → preview.test.ts} +12 -12
- package/test/{v3-product-session.test.ts → product-session.test.ts} +17 -17
- package/test/{v3-product-write.test.ts → product-write.test.ts} +20 -20
- package/test/{v3-properties.test.ts → properties.test.ts} +24 -24
- package/test/{v3-recipes.test.ts → recipes.test.ts} +9 -9
- package/test/{v3-refusals.test.ts → refusals.test.ts} +43 -43
- package/test/{v3-release-panel.test.ts → release-panel.test.ts} +21 -21
- package/test/renderer.test.ts +24 -0
- package/test/resolve-ts-siblings.mjs +1 -1
- package/test/{v3-scene-canvas.test.ts → scene-canvas.test.ts} +6 -6
- package/test/{v3-stage-model.test.ts → stage-model.test.ts} +5 -5
- package/test/{v3-theme.test.ts → theme.test.ts} +4 -4
- package/test/{v3-thumbnail.test.ts → thumbnail.test.ts} +4 -4
- package/test/{v3-verdict.test.ts → verdict.test.ts} +17 -17
- package/test/{v3-view.test.ts → view.test.ts} +2 -2
- package/things-factory.config.js +2 -3
- package/translations/en.json +2 -2
- package/translations/ja.json +2 -2
- package/translations/ko.json +2 -2
- package/translations/ms.json +2 -2
- package/translations/zh.json +2 -2
- package/docs/v3-product-cutover.md +0 -25
- package/test/v3-renderer.test.ts +0 -24
- /package/client/modeller/{v3-fields.ts → fields.ts} +0 -0
- /package/client/modeller/{v3-first-save.ts → first-save.ts} +0 -0
- /package/client/modeller/{v3-help.ts → help.ts} +0 -0
- /package/client/modeller/{v3-keys.ts → keys.ts} +0 -0
- /package/client/modeller/{v3-refusals.ts → refusals.ts} +0 -0
|
@@ -7,16 +7,16 @@ import assert from 'node:assert/strict'
|
|
|
7
7
|
import { readFileSync } from 'node:fs'
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
|
|
11
11
|
|
|
12
|
-
import { liveOverridesOf, liveOverSession, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from '../client/modeller/
|
|
12
|
+
import { liveOverridesOf, liveOverSession, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from '../client/modeller/live.ts'
|
|
13
13
|
import { moving } from './joints.ts'
|
|
14
14
|
|
|
15
15
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
16
16
|
|
|
17
17
|
const slider = () => {
|
|
18
|
-
let asset =
|
|
19
|
-
asset =
|
|
18
|
+
let asset = createFigureAsset('Slider')
|
|
19
|
+
asset = addFigureBox(asset, { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
20
20
|
return moving(asset, 'block', { kind: 'slide', axis: 'x', id: 'shift', min: 0, max: 60, start: 0 })
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -69,29 +69,29 @@ test('이름표를 끌면 값이 크기에 맞는 걸음으로 움직인다 —
|
|
|
69
69
|
})
|
|
70
70
|
|
|
71
71
|
test('속성창 숫자 칸은 치는 동안 알리고, 이름표를 끌 수 있다 — 기록은 change 에서', () => {
|
|
72
|
-
const editor = read('../client/modeller/
|
|
72
|
+
const editor = read('../client/modeller/editor.ts')
|
|
73
73
|
assert.ok(editor.includes('@input=${(e: Event) => this.typing(group.cells[axis] as never, e)}'))
|
|
74
74
|
assert.ok(editor.includes('@input=${(e: Event) => this.typing(field as never, e)}'))
|
|
75
75
|
assert.ok(editor.includes('@pointerdown=${(e: PointerEvent) => this.scrub(group.cells[axis] as never, e, unavailable)}'))
|
|
76
|
-
for (const event of ['@
|
|
77
|
-
assert.ok(editor.includes('liveOverSession(asset as
|
|
76
|
+
for (const event of ['@state-live=', '@states-live=', '@part-live=']) assert.ok(editor.includes(event), event)
|
|
77
|
+
assert.ok(editor.includes('liveOverSession(asset as FigureAsset, this.currentInputs(), this.live)'), '캔버스는 세션 위에 끄는 값을 그린다 (행동은 아래 시험)')
|
|
78
78
|
const run = editor.slice(editor.indexOf(' private run(action'), editor.indexOf(' private hold('))
|
|
79
79
|
assert.ok(run.includes('this.live = NOTHING_LIVE'), '기록하거나 거절되면 끄는 값을 버린다')
|
|
80
|
-
const material = read('../client/modeller/figure-
|
|
80
|
+
const material = read('../client/modeller/figure-material-panel.ts')
|
|
81
81
|
assert.ok(material.includes('@input=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value), true)}'))
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
test('슬라이더와 클립 막대는 끄는 동안 알리고, 놓을 때 한 번 기록한다', () => {
|
|
85
|
-
const controls = read('../client/modeller/figure-
|
|
85
|
+
const controls = read('../client/modeller/figure-controls.ts')
|
|
86
86
|
assert.ok(controls.includes('@input=${(e: Event) => this.drag(c, number(e))}') && controls.includes('@change=${(e: Event) => this.try(c, number(e))}'))
|
|
87
|
-
assert.ok(controls.includes("new CustomEvent('
|
|
88
|
-
const clips = read('../client/modeller/figure-
|
|
87
|
+
assert.ok(controls.includes("new CustomEvent('state-live'"))
|
|
88
|
+
const clips = read('../client/modeller/figure-clips.ts')
|
|
89
89
|
assert.ok(clips.includes('@input=${(e: Event) => this.scrub(clip, number(e), true)}'))
|
|
90
|
-
assert.ok(clips.includes("new CustomEvent('
|
|
90
|
+
assert.ok(clips.includes("new CustomEvent('states-live'"))
|
|
91
91
|
})
|
|
92
92
|
|
|
93
93
|
test('클립 트랙의 머리는 이름 한 줄, 그 아래 반복 방식과 지우기 — 이름이 한 글자씩 쌓이지 않는다', () => {
|
|
94
|
-
const clips = read('../client/modeller/figure-
|
|
94
|
+
const clips = read('../client/modeller/figure-clips.ts')
|
|
95
95
|
const head = clips.slice(clips.indexOf(' div[track-head] {'), clips.indexOf(' div[keys] {'))
|
|
96
96
|
assert.ok(head.includes('display: grid;') && !head.includes('display: flex;'), '공통 스타일의 폭 100% 가 이름을 누르지 않는다')
|
|
97
97
|
assert.ok(head.includes('grid-column: 1 / -1;'), '이름은 제 줄')
|
|
@@ -102,14 +102,14 @@ test('끄는 동안은 세션이 지금 든 자세 위에 그린다 — 다른
|
|
|
102
102
|
/* Two joints: the author turned one with its control; then drags the other. */
|
|
103
103
|
let asset = slider()
|
|
104
104
|
/* An arm on a post of its own, turning about the post's left edge -- so a swing moves it, and the slide does not. */
|
|
105
|
-
asset =
|
|
106
|
-
asset =
|
|
107
|
-
asset =
|
|
108
|
-
asset =
|
|
105
|
+
asset = addFigureBox(asset, { id: 'post', size: { x: 100, y: 100, z: 100 }, position: { x: 400, y: 50, z: 0 } })
|
|
106
|
+
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'post', face: 'bottom', to: { plane: 'mounting-plane' }, align: { x: { mm: 400 } } })
|
|
107
|
+
asset = addFigureBox(asset, { id: 'arm', size: { x: 100, y: 20, z: 20 }, position: { x: 400, y: 110, z: 0 } })
|
|
108
|
+
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'arm', face: 'bottom', to: { part: 'post', face: 'top' }, align: { x: 'min' } })
|
|
109
109
|
asset = moving(asset, 'arm', { kind: 'turn', axis: 'y', id: 'swing', min: -180, max: 180 })
|
|
110
110
|
const current = { ...asset.designInputs, ...asset.stateDefaults, swing: 90 }
|
|
111
111
|
const armAt = (parts: { id: string; pose: { t: number[] } }[]) => parts.find(p => p.id === 'arm')!.pose.t.map(v => Math.round(v))
|
|
112
|
-
const committed = armAt(
|
|
112
|
+
const committed = armAt(compileFigureAsset({ ...asset, stateDefaults: { ...asset.stateDefaults, swing: 90 } }).evaluate().geometry as never)
|
|
113
113
|
const dragging = armAt(liveOverSession(asset, current, { values: { shift: 30 } }) as never)
|
|
114
114
|
assert.deepEqual(dragging, committed, '사용자 2026-09-24: 「조작을 끝내면 완전히 다른 위치로 된다」 — swing 90 이 끄는 동안에도 남는다')
|
|
115
115
|
const overSaved = armAt(livePartsOf(asset, { values: { shift: 30 } }) as never)
|
|
@@ -9,23 +9,23 @@ import assert from 'node:assert/strict'
|
|
|
9
9
|
import { readFileSync } from 'node:fs'
|
|
10
10
|
import { fileURLToPath } from 'node:url'
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { addFigureBox, addFigurePrimitive, compileFigureAsset, createFigureAsset, editFigurePart, figureCostOf } from '@hatiolab/figure-model'
|
|
13
13
|
|
|
14
14
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
15
|
-
const material = read('../client/modeller/figure-
|
|
16
|
-
const editor = read('../client/modeller/
|
|
15
|
+
const material = read('../client/modeller/figure-material-panel.ts')
|
|
16
|
+
const editor = read('../client/modeller/editor.ts')
|
|
17
17
|
|
|
18
18
|
const probe = () => {
|
|
19
|
-
let asset =
|
|
20
|
-
asset =
|
|
21
|
-
asset =
|
|
19
|
+
let asset = createFigureAsset('Probe')
|
|
20
|
+
asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
21
|
+
asset = addFigurePrimitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
22
22
|
return asset
|
|
23
23
|
}
|
|
24
24
|
const drawn = (asset: ReturnType<typeof probe>, id: string) =>
|
|
25
|
-
|
|
25
|
+
compileFigureAsset(asset).evaluate().geometry.find(g => (g as { id: string }).id === id)!
|
|
26
26
|
|
|
27
27
|
test('재질 칸이 보내는 모양 그대로 figure-model 이 받고, 그려지는 재질이 바뀐다', () => {
|
|
28
|
-
const edited =
|
|
28
|
+
const edited = editFigurePart(probe(), {
|
|
29
29
|
kind: 'set-material',
|
|
30
30
|
target: 'body',
|
|
31
31
|
material: { token: 'palette.accent', preset: 'metal', transparent: true, emissive: { token: 'palette.accent', intensity: 0.5, on: true } },
|
|
@@ -47,8 +47,8 @@ test('발광 세기 0 은 발광을 없앤다 — 빈 칸을 남기지 않는다
|
|
|
47
47
|
|
|
48
48
|
test('곡면 분할은 둥근 몸체에만 내고, 고르면 삼각형 수가 그만큼 바뀐다', () => {
|
|
49
49
|
assert.match(material, /const ROUND = new Set\(\['cylinder', 'ellipticCylinder', 'sphere', 'frustum'\]\)/)
|
|
50
|
-
const coarse =
|
|
51
|
-
const fine =
|
|
50
|
+
const coarse = figureCostOf(editFigurePart(probe(), { kind: 'set-segments', target: 'drum', segments: 8 })).triangles
|
|
51
|
+
const fine = figureCostOf(editFigurePart(probe(), { kind: 'set-segments', target: 'drum', segments: 48 })).triangles
|
|
52
52
|
assert.ok(fine > coarse, `48 분할이 8 분할보다 삼각형이 많다 (${coarse} → ${fine})`)
|
|
53
53
|
})
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ test('색 역할은 날것의 이름이 아니라 쓰임새로 부른다', () =>
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
test('편집기는 재질 칸의 편집을 세션의 이력 하나로 돌린다', () => {
|
|
61
|
-
assert.match(editor, /<figure-
|
|
61
|
+
assert.match(editor, /<figure-material-panel[\s\S]*?@part-edit=\$\{\(e: CustomEvent\) => this\.run\(\(\) => this\.session!\.edit\(e\.detail\.edit\)\)\}/)
|
|
62
62
|
})
|
|
63
63
|
|
|
64
64
|
test('자산에 없는 색 역할은 그 색을 함께 보낸다 — 새 도형의 팔레트는 기본색 하나다', () => {
|
|
@@ -68,7 +68,7 @@ test('자산에 없는 색 역할은 그 색을 함께 보낸다 — 새 도형
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
test('분할 수 — 흔한 넷은 다각형 윤곽과 삼각형 수로, 나머지는 접어 둔다', () => {
|
|
71
|
-
const source = readFileSync(fileURLToPath(new URL('../client/modeller/figure-
|
|
71
|
+
const source = readFileSync(fileURLToPath(new URL('../client/modeller/figure-material-panel.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)'))
|
|
74
74
|
})
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* **성숙도 탭** (사용자 2026-09-24: 「분석부분도 많이 부족하고, 왜 이렇게 너프되었는지」).
|
|
3
3
|
*
|
|
4
4
|
* 탭은 개수 · 등급과 눈금 · 등급을 정한 한도 · 여섯 축 radar · 한도 막대 · 그리는 비용 · 재사용 추이를 보인다. 여기서는
|
|
5
|
-
* 그 수들이
|
|
5
|
+
* 그 수들이 자산에서 바르게 나오는지, 렌더러가 실제로 나눠 쓰는 것을 세는지, 화면이 그 순서와 말로 그리는지를 본다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
8
8
|
import assert from 'node:assert/strict'
|
|
9
9
|
import { readFileSync } from 'node:fs'
|
|
10
10
|
import { fileURLToPath } from 'node:url'
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import type {
|
|
14
|
-
import {
|
|
12
|
+
import { addFigureBox, addFigurePrimitive, applyFigureAuthoring, compileFigureAsset, createFigureAsset, LIMITS, figureScoreOf } from '@hatiolab/figure-model'
|
|
13
|
+
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
14
|
+
import { acquireFigureGeometry, releaseFigureGeometry } from '@hatiolab/things-scene'
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
BOARD_ASSETS,
|
|
@@ -20,19 +20,19 @@ import {
|
|
|
20
20
|
decadeSteps,
|
|
21
21
|
GRADE_SCALE,
|
|
22
22
|
gradeOf,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from '../client/modeller/
|
|
23
|
+
FIGURE_BUDGET_KEYS,
|
|
24
|
+
figureReportOf
|
|
25
|
+
} from '../client/modeller/maturity.ts'
|
|
26
26
|
import { capacityFrom } from '../client/modeller/scene-budget.ts'
|
|
27
27
|
import { moving } from './joints.ts'
|
|
28
28
|
|
|
29
29
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
30
|
-
const verdict = read('../client/modeller/figure-
|
|
31
|
-
const maturity = read('../client/modeller/
|
|
30
|
+
const verdict = read('../client/modeller/figure-verdict.ts')
|
|
31
|
+
const maturity = read('../client/modeller/maturity.ts')
|
|
32
32
|
|
|
33
33
|
/** An asset with the given tokens in its palette, so boxes can differ in colour. */
|
|
34
|
-
function withPalette(...tokens: string[]):
|
|
35
|
-
const asset =
|
|
34
|
+
function withPalette(...tokens: string[]): FigureAsset {
|
|
35
|
+
const asset = createFigureAsset('Probe')
|
|
36
36
|
for (const token of tokens) asset.palette[token] = '#336699'
|
|
37
37
|
return asset
|
|
38
38
|
}
|
|
@@ -45,7 +45,7 @@ const cube = (id: string, x: number, token = 'palette.primary', extra: Record<st
|
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
test('부품이 없으면 리포트가 없다 — 0 으로 그리지 않는다', () => {
|
|
48
|
-
assert.equal(
|
|
48
|
+
assert.equal(figureReportOf(createFigureAsset('Empty')), undefined)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
test('등급의 눈금 — 한도의 절반 A · 4분의 3 B · 한도 C · 넘치면 D', () => {
|
|
@@ -61,76 +61,76 @@ test('등급의 눈금 — 한도의 절반 A · 4분의 3 B · 한도 C · 넘
|
|
|
61
61
|
assert.equal(gradeOf(1.01), 'D')
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
-
test('등급은 figure-model 의
|
|
65
|
-
let asset =
|
|
66
|
-
asset =
|
|
67
|
-
const one =
|
|
68
|
-
assert.deepEqual(one.score,
|
|
69
|
-
assert.deepEqual(
|
|
64
|
+
test('등급은 figure-model 의 figureScoreOf 그대로다 — 목록과 이 탭이 한 셈으로 매긴다', () => {
|
|
65
|
+
let asset = createFigureAsset('Probe')
|
|
66
|
+
asset = addFigureBox(asset, cube('a', 0))
|
|
67
|
+
const one = figureReportOf(asset)!
|
|
68
|
+
assert.deepEqual(one.score, figureScoreOf(asset))
|
|
69
|
+
assert.deepEqual(FIGURE_BUDGET_KEYS.filter(k => k !== 'parts'), one.score.budgets.map(b => b.key), '화면이 말을 찾는 키와 셈의 키가 같다 — 등급을 안 정하면 부품 수 한도는 없다')
|
|
70
70
|
})
|
|
71
71
|
|
|
72
72
|
test('draw call 한도 3 은 없다 — 한 화면 166 대 목표에 견준다(수석 아키텍트 판정 09-24)', () => {
|
|
73
|
-
let asset =
|
|
74
|
-
for (let i = 0; i < 4; i++) asset =
|
|
75
|
-
const four =
|
|
73
|
+
let asset = createFigureAsset('Probe')
|
|
74
|
+
for (let i = 0; i < 4; i++) asset = addFigureBox(asset, cube(`b${i}`, i * 200))
|
|
75
|
+
const four = figureReportOf(asset)!
|
|
76
76
|
assert.equal(four.score.grade, 'A', '예전 한도 3 으로는 D 였다')
|
|
77
77
|
const groups = four.score.budgets.find(b => b.key === 'groups')!
|
|
78
78
|
assert.equal(groups.limit, LIMITS.groupsOnScreen / LIMITS.screenInstances)
|
|
79
79
|
assert.ok(four.capacity.instances >= 2 * LIMITS.screenInstances, 'A 는 목표의 두 배가 들어간다는 뜻')
|
|
80
80
|
})
|
|
81
81
|
|
|
82
|
-
test('감춘 부품은 그리지 않으므로 한도에 들지 않는다 —
|
|
83
|
-
let asset =
|
|
84
|
-
asset =
|
|
85
|
-
asset =
|
|
86
|
-
const report =
|
|
82
|
+
test('감춘 부품은 그리지 않으므로 한도에 들지 않는다 — figureCostOf 와 같은 셈', () => {
|
|
83
|
+
let asset = createFigureAsset('Probe')
|
|
84
|
+
asset = addFigureBox(asset, cube('a', 0))
|
|
85
|
+
asset = addFigureBox(asset, cube('b', 200))
|
|
86
|
+
const report = figureReportOf(asset)!
|
|
87
87
|
assert.equal(report.score.budgets.find(b => b.key === 'groups')!.used, report.cost.groups)
|
|
88
88
|
assert.equal(report.capacity.instances, capacityFrom(report.cost.groups, report.cost.triangles)!.instances)
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
test('투명 재질은 서로 다른 재질로 센다 — 둘이면 한도 1 을 넘고, 그것이 등급을 정한다', () => {
|
|
92
92
|
let asset = withPalette('glass.a', 'glass.b')
|
|
93
|
-
asset =
|
|
94
|
-
asset =
|
|
95
|
-
const report =
|
|
93
|
+
asset = addFigureBox(asset, cube('a', 0, 'glass.a', { transparent: true }))
|
|
94
|
+
asset = addFigureBox(asset, cube('b', 200, 'glass.b', { transparent: true }))
|
|
95
|
+
const report = figureReportOf(asset)!
|
|
96
96
|
assert.equal(report.score.budgets.find(b => b.key === 'transparent')!.used, 2)
|
|
97
97
|
assert.equal(report.score.tightest.key, 'transparent')
|
|
98
98
|
assert.equal(report.score.grade, 'D')
|
|
99
99
|
|
|
100
100
|
let same = withPalette('glass.a')
|
|
101
|
-
same =
|
|
102
|
-
same =
|
|
103
|
-
assert.equal(
|
|
101
|
+
same = addFigureBox(same, cube('a', 0, 'glass.a', { transparent: true }))
|
|
102
|
+
same = addFigureBox(same, cube('b', 200, 'glass.a', { transparent: true }))
|
|
103
|
+
assert.equal(figureReportOf(same)!.score.budgets.find(b => b.key === 'transparent')!.used, 1, '같은 재질은 하나로 나눠 쓴다')
|
|
104
104
|
})
|
|
105
105
|
|
|
106
106
|
test('상태를 따라 움직이는 부품을 센다 — 「따로 움직이는 부품」', () => {
|
|
107
|
-
let asset =
|
|
108
|
-
asset =
|
|
109
|
-
asset =
|
|
110
|
-
assert.equal(
|
|
107
|
+
let asset = createFigureAsset('Probe')
|
|
108
|
+
asset = addFigureBox(asset, cube('base', 0))
|
|
109
|
+
asset = addFigureBox(asset, cube('arm', 200))
|
|
110
|
+
assert.equal(figureReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 0, '전제: 움직임이 없으면 0')
|
|
111
111
|
|
|
112
112
|
asset = moving(asset, 'arm', { kind: 'turn', axis: 'y', id: 'arm.turn', min: 0, max: 90 })
|
|
113
|
-
assert.equal(
|
|
113
|
+
assert.equal(figureReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 1, '도는 팔 하나')
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
test('서로 다른 형상은 things-scene 이 정점 버퍼를 나눠 쓰는 수와 같다', () => {
|
|
117
|
-
let asset =
|
|
118
|
-
asset =
|
|
119
|
-
asset =
|
|
120
|
-
asset =
|
|
121
|
-
asset =
|
|
122
|
-
asset =
|
|
123
|
-
asset =
|
|
117
|
+
let asset = createFigureAsset('Probe')
|
|
118
|
+
asset = addFigureBox(asset, cube('a', 0))
|
|
119
|
+
asset = addFigureBox(asset, cube('b', 200))
|
|
120
|
+
asset = addFigureBox(asset, { id: 'c', size: { x: 100, y: 300, z: 100 }, position: { x: 400, y: 150, z: 0 } })
|
|
121
|
+
asset = addFigurePrimitive(asset, { id: 'd', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 600, y: 50, z: 0 } })
|
|
122
|
+
asset = addFigurePrimitive(asset, { id: 'e', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 800, y: 50, z: 0 } })
|
|
123
|
+
asset = addFigurePrimitive(asset, { id: 'f', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 1000, y: 50, z: 0 } })
|
|
124
124
|
// The same cylinder, rounder: another vertex buffer in the renderer, so another shape here.
|
|
125
125
|
asset.appearance.find(look => look.target === 'f')!.segments = 24
|
|
126
|
-
const report =
|
|
126
|
+
const report = figureReportOf(asset)!
|
|
127
127
|
|
|
128
|
-
const bodies =
|
|
129
|
-
const held = bodies.map(body =>
|
|
128
|
+
const bodies = compileFigureAsset(asset).evaluate().geometry
|
|
129
|
+
const held = bodies.map(body => acquireFigureGeometry((body as { shape: never }).shape, (body as { segments?: number }).segments))
|
|
130
130
|
try {
|
|
131
131
|
assert.equal(report.distinctShapes, new Set(held).size, '렌더러의 은행과 같은 키')
|
|
132
132
|
} finally {
|
|
133
|
-
held.forEach(geometry =>
|
|
133
|
+
held.forEach(geometry => releaseFigureGeometry(geometry))
|
|
134
134
|
}
|
|
135
135
|
assert.equal(report.distinctShapes, 4, '같은 상자 둘 · 키 큰 상자 · 같은 원기둥 둘 · 분할이 다른 원기둥')
|
|
136
136
|
assert.deepEqual(report.reuse, { distinct: 4, parts: 6, saved: 2 })
|
|
@@ -139,9 +139,9 @@ test('서로 다른 형상은 things-scene 이 정점 버퍼를 나눠 쓰는
|
|
|
139
139
|
|
|
140
140
|
test('여섯 축 — 정해진 순서, 잴 수 없는 둘은 비율 없이 까닭을 단다', () => {
|
|
141
141
|
let asset = withPalette('palette.accent')
|
|
142
|
-
asset =
|
|
143
|
-
asset =
|
|
144
|
-
const axes =
|
|
142
|
+
asset = addFigureBox(asset, cube('a', 50))
|
|
143
|
+
asset = addFigureBox(asset, cube('b', 150, 'palette.accent'))
|
|
144
|
+
const axes = figureReportOf(asset)!.axes
|
|
145
145
|
|
|
146
146
|
assert.deepEqual(
|
|
147
147
|
axes.map(axis => axis.key),
|
|
@@ -149,7 +149,7 @@ test('여섯 축 — 정해진 순서, 잴 수 없는 둘은 비율 없이 까
|
|
|
149
149
|
)
|
|
150
150
|
const byKey = Object.fromEntries(axes.map(axis => [axis.key, axis]))
|
|
151
151
|
assert.equal(byKey.detail!.ratio, undefined)
|
|
152
|
-
assert.equal(byKey.detail!.blocked, 'figure.text.maturity-blocked-no-detail-level', '디테일 등급은
|
|
152
|
+
assert.equal(byKey.detail!.blocked, 'figure.text.maturity-blocked-no-detail-level', '디테일 등급은 이 모델에 없다(ADR-0087)')
|
|
153
153
|
assert.equal(byKey.reactive!.ratio, undefined)
|
|
154
154
|
assert.equal(byKey.reactive!.blocked, 'figure.text.maturity-blocked-no-binding')
|
|
155
155
|
|
|
@@ -162,11 +162,11 @@ test('여섯 축 — 정해진 순서, 잴 수 없는 둘은 비율 없이 까
|
|
|
162
162
|
|
|
163
163
|
test('재질이 한도를 넘으면 일관성이 한 칸 내려간다 — 셋 중 하나', () => {
|
|
164
164
|
let asset = withPalette('t1', 't2', 't3')
|
|
165
|
-
asset =
|
|
166
|
-
asset =
|
|
167
|
-
asset =
|
|
168
|
-
asset =
|
|
169
|
-
const report =
|
|
165
|
+
asset = addFigureBox(asset, cube('a', 0))
|
|
166
|
+
asset = addFigureBox(asset, cube('b', 200, 't1'))
|
|
167
|
+
asset = addFigureBox(asset, cube('c', 400, 't2'))
|
|
168
|
+
asset = addFigureBox(asset, cube('d', 600, 't3'))
|
|
169
|
+
const report = figureReportOf(asset)!
|
|
170
170
|
assert.equal(report.distinctMaterials, 4)
|
|
171
171
|
const consistency = report.axes.find(axis => axis.key === 'consistency')!
|
|
172
172
|
assert.equal(consistency.ratio, 2 / 3)
|
|
@@ -174,18 +174,18 @@ test('재질이 한도를 넘으면 일관성이 한 칸 내려간다 — 셋
|
|
|
174
174
|
})
|
|
175
175
|
|
|
176
176
|
test('한 색이 발자국을 다 덮으면 식별성은 0 이다 — 위에서 보면 한 덩어리', () => {
|
|
177
|
-
let asset =
|
|
178
|
-
asset =
|
|
179
|
-
asset =
|
|
180
|
-
assert.equal(
|
|
177
|
+
let asset = createFigureAsset('Probe')
|
|
178
|
+
asset = addFigureBox(asset, { id: 'slab', size: { x: 400, y: 20, z: 400 }, position: { x: 0, y: 10, z: 0 } })
|
|
179
|
+
asset = addFigureBox(asset, { id: 'under', size: { x: 100, y: 5, z: 100 }, position: { x: 0, y: 2.5, z: 0 } })
|
|
180
|
+
assert.equal(figureReportOf(asset)!.axes.find(axis => axis.key === 'readable')!.ratio, 0, '아래 부품은 가려진다')
|
|
181
181
|
})
|
|
182
182
|
|
|
183
183
|
test('추이 — 1 에서 1만까지 1·2·5, 삼각형과 draw call 은 오르고 정점 버퍼와 재질은 평평하다', () => {
|
|
184
184
|
assert.deepEqual(decadeSteps(1, 100), [1, 2, 5, 10, 20, 50, 100])
|
|
185
|
-
let asset =
|
|
186
|
-
asset =
|
|
187
|
-
asset =
|
|
188
|
-
const report =
|
|
185
|
+
let asset = createFigureAsset('Probe')
|
|
186
|
+
asset = addFigureBox(asset, cube('a', 0))
|
|
187
|
+
asset = addFigureBox(asset, cube('b', 200))
|
|
188
|
+
const report = figureReportOf(asset)!
|
|
189
189
|
const last = report.curve[report.curve.length - 1]!
|
|
190
190
|
assert.equal(report.curve[0]!.instances, 1)
|
|
191
191
|
assert.equal(last.instances, 10000)
|
|
@@ -223,7 +223,7 @@ test('radar 와 추이 그림의 조각은 svg 템플릿이다 — html 로 만
|
|
|
223
223
|
|
|
224
224
|
test('화면이 키를 이어 붙여 찾는 말도 다섯 말 모두에 있다', () => {
|
|
225
225
|
const keys = [
|
|
226
|
-
...
|
|
226
|
+
...FIGURE_BUDGET_KEYS.map(key => `figure.label.budget-${key}`),
|
|
227
227
|
...['scale', 'consistency', 'reuse', 'detail', 'reactive', 'readable'].flatMap(key => [`figure.label.axis-${key}`, `figure.label.axis-${key}-short`]),
|
|
228
228
|
'figure.text.bound-by-groups',
|
|
229
229
|
'figure.text.bound-by-triangles',
|
|
@@ -237,9 +237,9 @@ test('화면이 키를 이어 붙여 찾는 말도 다섯 말 모두에 있다',
|
|
|
237
237
|
|
|
238
238
|
test('부품 목록 머리의 값 한 줄 — 삼각형 · 묶음 · 한 화면 몇 대 · 등급, D 면 넘친 한도를 적는다', () => {
|
|
239
239
|
assert.equal(budgetOf(undefined), undefined, '잴 것이 없으면 줄도 없다')
|
|
240
|
-
let asset =
|
|
241
|
-
asset =
|
|
242
|
-
const line = budgetOf(
|
|
240
|
+
let asset = createFigureAsset('Probe')
|
|
241
|
+
asset = addFigureBox(asset, cube('a', 0))
|
|
242
|
+
const line = budgetOf(figureReportOf(asset))!
|
|
243
243
|
assert.deepEqual(
|
|
244
244
|
{ triangles: line.triangles, groups: line.groups, grade: line.grade },
|
|
245
245
|
{ triangles: 12, groups: 1, grade: 'A' }
|
|
@@ -247,17 +247,17 @@ test('부품 목록 머리의 값 한 줄 — 삼각형 · 묶음 · 한 화면
|
|
|
247
247
|
assert.equal(line.instances, capacityFrom(1, 12)!.instances)
|
|
248
248
|
|
|
249
249
|
let glass = withPalette('glass.a', 'glass.b')
|
|
250
|
-
glass =
|
|
251
|
-
glass =
|
|
252
|
-
const over = budgetOf(
|
|
250
|
+
glass = addFigureBox(glass, cube('a', 0, 'glass.a', { transparent: true }))
|
|
251
|
+
glass = addFigureBox(glass, cube('b', 200, 'glass.b', { transparent: true }))
|
|
252
|
+
const over = budgetOf(figureReportOf(glass))!
|
|
253
253
|
assert.equal(over.grade, 'D')
|
|
254
254
|
assert.deepEqual(over.tightest, { key: 'transparent', used: 2, limit: 1 })
|
|
255
255
|
|
|
256
|
-
const parts = read('../client/modeller/figure-
|
|
256
|
+
const parts = read('../client/modeller/figure-parts.ts')
|
|
257
257
|
const drawn = parts.slice(parts.indexOf(' private renderBudget()'), parts.indexOf(' /** 한꺼번에 감추고'))
|
|
258
258
|
assert.ok(drawn.includes("const over = b.grade === 'D'") && drawn.includes("'figure.text.parts-budget-over'"), 'D 에서 넘친 한도를 적는다')
|
|
259
259
|
assert.ok(parts.includes("new CustomEvent('show-maturity'"), '누르면 성숙도로')
|
|
260
|
-
const editor = read('../client/modeller/
|
|
260
|
+
const editor = read('../client/modeller/editor.ts')
|
|
261
261
|
assert.ok(editor.includes('.budget=${this.budget}') && editor.includes("@show-maturity=${() => (this.tab = 'verdict')}"))
|
|
262
262
|
assert.ok(verdict.includes('budget: budgetOf(report)'), '성숙도 탭이 같은 보고서에서 알린다')
|
|
263
263
|
})
|
|
@@ -15,13 +15,12 @@ test('every figure and a new one open the one modeller', () => {
|
|
|
15
15
|
assert.equal(modellerPathOf(), 'figure-modeller')
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
test('
|
|
18
|
+
test('one modeller page at one address: the route and the shell table agree', () => {
|
|
19
19
|
const route = read('../client/route.ts')
|
|
20
|
-
assert.
|
|
20
|
+
assert.equal(route.match(/case 'figure-modeller':/g)?.length, 1)
|
|
21
|
+
assert.match(route, /import\('\.\/pages\/figure-modeller-page'\)/)
|
|
21
22
|
const config = read('../things-factory.config.js')
|
|
22
|
-
assert.match(
|
|
23
|
-
assert.match(config, /tagname: 'figure-v3-modeller-page', page: 'figure-v3-modeller'/)
|
|
24
|
-
assert.doesNotMatch(config, /'figure-modeller-page'/)
|
|
23
|
+
assert.equal(config.match(/tagname: 'figure-modeller-page', page: 'figure-modeller'/g)?.length, 1)
|
|
25
24
|
})
|
|
26
25
|
|
|
27
26
|
test('the catalogue opens figures through the one path', () => {
|
|
@@ -30,7 +29,7 @@ test('the catalogue opens figures through the one path', () => {
|
|
|
30
29
|
})
|
|
31
30
|
|
|
32
31
|
test('reloading or closing the tab with unsaved work asks first — the shell does not ask canDeactivate then (09-26)', () => {
|
|
33
|
-
const page = read('../client/pages/figure-
|
|
32
|
+
const page = read('../client/pages/figure-modeller-page.ts')
|
|
34
33
|
const hold = page.slice(page.indexOf('private holdUnsaved'), page.indexOf('connectedCallback()'))
|
|
35
34
|
assert.match(hold, /session\.read\(\)\.dirty/, 'only when there is something unsaved')
|
|
36
35
|
assert.match(hold, /event\.preventDefault\(\)/)
|
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
14
14
|
const RENDERS = new Set(['deployed_code', 'database', 'crop_square', 'circle', 'change_history', 'hexagon', 'check_box_outline_blank', 'crop_portrait', 'door_front'])
|
|
15
15
|
|
|
16
16
|
test('every shape in the palette has an icon the font draws', () => {
|
|
17
|
-
const parts = readFileSync(fileURLToPath(new URL('../client/modeller/figure-
|
|
17
|
+
const parts = readFileSync(fileURLToPath(new URL('../client/modeller/figure-parts.ts', import.meta.url)), 'utf8')
|
|
18
18
|
const recipes = parts.slice(parts.indexOf('const RECIPES'), parts.indexOf(']', parts.indexOf('const RECIPES')))
|
|
19
19
|
const icons = [...recipes.matchAll(/icon: '([a-z_]+)'/g)].map(m => m[1]!)
|
|
20
20
|
assert.ok(icons.length >= 9)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* **편집기의 부품 목록** (2026-09-24).
|
|
3
3
|
*
|
|
4
|
-
* `figure-
|
|
4
|
+
* `figure-parts` 는 지어졌지만(fdc15e162e) 편집기 어디에도
|
|
5
5
|
* 걸리지 않았다. 편집기는 맨 단추 네 개와 이름 단추 목록이었다. 패널이 내는 사건마다 받는 자리가 있어야 한다 —
|
|
6
6
|
* 없으면 눌러도 아무 일이 없다.
|
|
7
7
|
*/
|
|
@@ -11,14 +11,14 @@ import { readFileSync } from 'node:fs'
|
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
|
|
13
13
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
14
|
-
const editor = read('../client/modeller/
|
|
15
|
-
const panel = read('../client/modeller/figure-
|
|
14
|
+
const editor = read('../client/modeller/editor.ts')
|
|
15
|
+
const panel = read('../client/modeller/figure-parts.ts')
|
|
16
16
|
|
|
17
17
|
test('패널이 내는 사건마다 편집기에 받는 자리가 있다', () => {
|
|
18
18
|
const asked = new Set([...panel.matchAll(/this\.ask\('([a-z-]+)'/g)].map(m => m[1]))
|
|
19
19
|
for (const name of [...panel.matchAll(/op\('([a-z-]+)'/g)].map(m => m[1])) asked.add(name)
|
|
20
20
|
assert.ok(asked.size >= 8, `전제: 패널의 사건을 읽었다 (${[...asked].join(', ')})`)
|
|
21
|
-
const listener = editor.slice(editor.indexOf('<figure-
|
|
21
|
+
const listener = editor.slice(editor.indexOf('<figure-parts'), editor.indexOf('</figure-parts>'))
|
|
22
22
|
for (const name of asked) assert.ok(listener.includes(`@${name}=`), `${name} 을 받는 자리가 없다`)
|
|
23
23
|
})
|
|
24
24
|
|
|
@@ -37,7 +37,7 @@ test('작업대에서 가린 부품은 캔버스에서 빠지고, 다른 도형
|
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
test('지울 수 없는 부품은 사람의 말로 거절하고, 지우며 풀어 준 것은 사람의 말로 알린다 — 다섯 말 모두', () => {
|
|
40
|
-
/* Since 2026-09-25 the reason is told apart (partRefusalOf, tested in
|
|
40
|
+
/* Since 2026-09-25 the reason is told apart (partRefusalOf, tested in refusals); the editor hands the refusal to it. */
|
|
41
41
|
assert.match(editor, /private removePart[\s\S]*?partRefusalOf\(error, id,/)
|
|
42
42
|
assert.match(editor, /private removePart[\s\S]*?this\.notice = removalNoticeOf\(view\.removal,/, '지운 뒤 풀어 준 것을 알린다')
|
|
43
43
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
|
|
@@ -7,9 +7,9 @@ import assert from 'node:assert/strict'
|
|
|
7
7
|
import { readFileSync } from 'node:fs'
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { addFigureBox, createFigureAsset } from '@hatiolab/figure-model'
|
|
11
11
|
|
|
12
|
-
import { authoredSizeOf, PREVIEW_WAYS, previewClampsOf, previewInstances } from '../client/modeller/
|
|
12
|
+
import { authoredSizeOf, PREVIEW_WAYS, previewClampsOf, previewInstances } from '../client/modeller/preview-model.ts'
|
|
13
13
|
|
|
14
14
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
15
15
|
|
|
@@ -26,31 +26,31 @@ test('여덟 — 기준과 일곱 조합, 한 줄에 넷, 늘린 축만 두 배
|
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
test('크기를 선언하지 않은 도형은 부품이 차지하는 범위를 기준 크기로', () => {
|
|
29
|
-
const asset =
|
|
29
|
+
const asset = addFigureBox(createFigureAsset('Fixed'), { id: 'body', size: { x: 300, y: 200, z: 100 }, position: { x: 0, y: 100, z: 0 } })
|
|
30
30
|
assert.deepEqual(authoredSizeOf(asset), { x: 300, y: 200, z: 100 })
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
test('편집 | 미리보기 — 띠에서 오가고, 미리보기는 도면처럼 등록해 세우며 제 타입으로 남의 도면을 건드리지 않는다', () => {
|
|
34
|
-
const editor = read('../client/modeller/
|
|
34
|
+
const editor = read('../client/modeller/editor.ts')
|
|
35
35
|
assert.ok(editor.includes("(['edit', 'preview'] as const).map("))
|
|
36
|
-
assert.ok(editor.includes('<figure-
|
|
37
|
-
const preview = read('../client/modeller/figure-
|
|
38
|
-
assert.ok(preview.includes('
|
|
39
|
-
assert.ok(preview.includes('`figure-
|
|
36
|
+
assert.ok(editor.includes('<figure-preview'))
|
|
37
|
+
const preview = read('../client/modeller/figure-preview.ts')
|
|
38
|
+
assert.ok(preview.includes('registerFigure(type, asset)') && preview.includes('unregisterFigure(this.registered)'))
|
|
39
|
+
assert.ok(preview.includes('`figure-preview-${this.context || \'new\'}`'))
|
|
40
40
|
assert.ok(preview.includes("kind: 'set-state-defaults'"), '이 자세를 기본으로')
|
|
41
41
|
assert.ok(preview.includes('dialShape('), '각도는 다이얼로')
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
test('미리보기 — 선언하지 않은 크기를 묻는 변형은 그려지는 크기를 말한다(높이만 따르는 조명탑, 아키텍트 판정 09-25)', async () => {
|
|
45
45
|
const fm = await import('@hatiolab/figure-model')
|
|
46
|
-
let a = fm.
|
|
47
|
-
a = fm.
|
|
48
|
-
a = fm.
|
|
46
|
+
let a = fm.addFigureBox(fm.createFigureAsset('Tower'), { id: 'mast', size: { x: 200, y: 2000, z: 300 }, position: { x: 0, y: 0, z: 0 } })
|
|
47
|
+
a = fm.applyFigureAuthoring(a, { kind: 'attach', part: 'mast', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
48
|
+
a = fm.applyFigureAuthoring(a, { kind: 'declare-instance-size', range: { x: { min: 200, max: 200 }, y: { min: 1000, max: 3000 }, z: { min: 300, max: 300 } } })
|
|
49
49
|
const clamps = previewClampsOf(a, authoredSizeOf(a))
|
|
50
50
|
assert.deepEqual(clamps['base'], [], '기준은 그대로')
|
|
51
51
|
assert.deepEqual(clamps['x'], [{ axis: 'x', asked: 400, used: 200 }])
|
|
52
52
|
assert.deepEqual(clamps['y'], [{ axis: 'y', asked: 4000, used: 3000 }], '높이도 선언 최대까지')
|
|
53
53
|
assert.deepEqual(clamps['x·y·z'].map(c => c.axis), ['x', 'y', 'z'])
|
|
54
|
-
const view = readFileSync(fileURLToPath(new URL('../client/modeller/figure-
|
|
54
|
+
const view = readFileSync(fileURLToPath(new URL('../client/modeller/figure-preview.ts', import.meta.url)), 'utf8')
|
|
55
55
|
assert.ok(view.includes("i18next.t('figure.text.preview-clamped'"), '범례에 적는다')
|
|
56
56
|
})
|