@things-factory/figure-ui 10.1.205 → 10.1.207
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/editor-styles.ts +16 -0
- package/client/modeller/editor.ts +53 -137
- package/client/modeller/fasten-form.ts +50 -11
- package/client/modeller/figure-facts.ts +1 -1
- package/client/modeller/figure-fasten-dialog.ts +3 -2
- package/client/modeller/figure-history-panel.ts +1 -29
- package/client/modeller/figure-links.ts +2 -2
- package/client/modeller/figure-preview.ts +10 -12
- package/client/modeller/figure-verdict.ts +2 -17
- package/client/modeller/gizmo.ts +10 -0
- package/client/modeller/keys.ts +10 -1
- package/client/modeller/links.ts +32 -41
- package/client/modeller/maturity.ts +11 -27
- package/client/modeller/palette.ts +1 -1
- package/client/modeller/product-session.ts +16 -4
- package/client/modeller/refusal-teller.ts +3 -25
- package/client/modeller/refusals.ts +20 -33
- package/client/pages/figure-modeller-page.ts +7 -22
- package/dist-client/modeller/editor-styles.js +16 -0
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +8 -11
- package/dist-client/modeller/editor.js +57 -138
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +16 -7
- package/dist-client/modeller/fasten-form.js +45 -11
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/figure-facts.js +1 -1
- package/dist-client/modeller/figure-facts.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.js +3 -3
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +0 -16
- package/dist-client/modeller/figure-history-panel.js +1 -27
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.js +2 -2
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-preview.js +8 -11
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-verdict.d.ts +0 -5
- package/dist-client/modeller/figure-verdict.js +2 -21
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +6 -0
- package/dist-client/modeller/gizmo.js +11 -0
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/keys.d.ts +1 -1
- package/dist-client/modeller/keys.js +6 -0
- package/dist-client/modeller/keys.js.map +1 -1
- package/dist-client/modeller/links.d.ts +16 -22
- package/dist-client/modeller/links.js +26 -39
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/maturity.d.ts +2 -4
- package/dist-client/modeller/maturity.js +12 -26
- package/dist-client/modeller/maturity.js.map +1 -1
- package/dist-client/modeller/palette.js +1 -1
- package/dist-client/modeller/palette.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +15 -13
- package/dist-client/modeller/product-session.js +15 -5
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/refusal-teller.d.ts +1 -12
- package/dist-client/modeller/refusal-teller.js +3 -22
- package/dist-client/modeller/refusal-teller.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +7 -17
- package/dist-client/modeller/refusals.js +17 -34
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +1 -3
- package/dist-client/pages/figure-modeller-page.js +7 -23
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/browser/acceptance-page.js +4 -4
- package/test/browser/detail-page.js +1 -3
- package/test/browser/detail-tabs-page.js +1 -2
- package/test/browser/links.check.mjs +32 -50
- package/test/browser/modeller.check.mjs +46 -19
- package/test/browser/parts-page.js +2 -2
- package/test/browser/parts.check.mjs +51 -12
- package/test/browser/product-page.check.mjs +6 -9
- package/test/browser/product-page.js +1 -2
- package/test/browser/screen-page.js +2 -6
- package/test/browser/screen.check.mjs +8 -65
- package/test/browser/section.check.mjs +27 -0
- package/test/fasten-form.test.ts +42 -2
- package/test/first-minute.test.ts +2 -3
- package/test/fixtures/joint-column.json +1 -66
- package/test/fixtures/joint-roller.json +1 -66
- package/test/fixtures/robot-arm-6dof.json +2 -67
- package/test/gizmo.test.ts +12 -1
- package/test/keys.test.ts +11 -0
- package/test/links.test.ts +39 -34
- package/test/parts-wiring.test.ts +1 -1
- package/test/properties.test.ts +9 -12
- package/test/refusals.test.ts +20 -42
- package/test/resize-with-placement.test.ts +13 -15
- package/test/revision.test.ts +1 -2
- package/test/verdict.test.ts +1 -15
- package/translations/en.json +8 -32
- package/translations/es.json +8 -32
- package/translations/ja.json +8 -32
- package/translations/ko.json +8 -32
- package/translations/ms.json +8 -32
- package/translations/zh.json +8 -32
- package/client/modeller/occupancy.ts +0 -51
- package/dist-client/modeller/occupancy.d.ts +0 -31
- package/dist-client/modeller/occupancy.js +0 -36
- package/dist-client/modeller/occupancy.js.map +0 -1
- package/test/release-panel.test.ts +0 -53
package/test/links.test.ts
CHANGED
|
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict'
|
|
|
10
10
|
import { addFigureBox, addFigurePrimitive, applyFigureAuthoring, createFigureAsset } from '@hatiolab/figure-model'
|
|
11
11
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
12
12
|
|
|
13
|
-
import { acceptsOf, canFasten, canFollow, dimensionNow, instanceRangeOf, proportionToFollow,
|
|
13
|
+
import { acceptsOf, canFasten, canFollow, dimensionNow, instanceRangeOf, proportionToFollow, rangeWith, turnsAboutAJoint, valueNow, fastenedTo, followableAxes, followOf, hasInstanceSize, positionIsShare, holdsThings, motionOf, placeFacesOf, placeIdFor, placesOn, positionsWorkedOut } from '../client/modeller/links.ts'
|
|
14
14
|
import { revisionOf } from './revision-of.ts'
|
|
15
15
|
|
|
16
16
|
const pair = (): FigureAsset => {
|
|
@@ -49,6 +49,10 @@ test('붙이기 — 누구에게 붙었나, 붙일 수 있는 모양인가', ()
|
|
|
49
49
|
assert.equal(canFasten(withDrum, 'drum'), true, '원기둥도 면으로 붙인다(figure-model 0.1.47)')
|
|
50
50
|
assert.equal(canFollow(withDrum, 'drum'), true, '원기둥은 반지름(X)과 길이(Y)를 따를 수 있다')
|
|
51
51
|
assert.deepEqual(followableAxes(withDrum, 'drum'), ['x', 'y'], '원기둥에는 제 Z 가 없다')
|
|
52
|
+
/* 구도 원기둥처럼 가로(X, Z 가 함께)와 높이(Y)를 따르고, 붙일 수 있다(사용자 2026-10-21). */
|
|
53
|
+
const withBall = addFigurePrimitive(asset, { id: 'ball', kind: 'sphere', dimensions: { radius: 50, radiusY: 50 }, position: { x: 0, y: 50, z: 0 } })
|
|
54
|
+
assert.deepEqual(followableAxes(withBall, 'ball'), ['x', 'y'], '구의 가로 반지름은 X 와 Z 가 한 값')
|
|
55
|
+
assert.equal(canFasten(withBall, 'ball'), true)
|
|
52
56
|
})
|
|
53
57
|
|
|
54
58
|
test('움직임 — 미끄러짐 · 돌기, 축, 무엇이 모느냐를 읽는다', () => {
|
|
@@ -206,38 +210,39 @@ test('made to follow in proportion, a measure is the share of the figure size it
|
|
|
206
210
|
assert.deepEqual(followCommandOf(revisionOf(linked), 'flange', 'y', 'part:base:x', followOf(linked, 'flange', 'y')), { kind: 'link-dimension', part: 'flange', axis: 'y', source: { of: 'part', part: 'base', axis: 'x' }, replace: true })
|
|
207
211
|
})
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
/* ADR-0087 결정 6(사용자 2026-10-21): 작성자가 좁힌 끝만 저장되고, 나머지는 부품에서 figure-model 이 계산한다. */
|
|
214
|
+
const openSides = () => {
|
|
215
|
+
const asset = sizedPair()
|
|
212
216
|
for (const axis of ['x', 'z'] as const) {
|
|
213
|
-
asset.document.model.inputs.push({ id: `size.${axis}`, unit: 'mm',
|
|
217
|
+
asset.document.model.inputs.push({ id: `size.${axis}`, unit: 'mm', role: 'design' } as never)
|
|
214
218
|
asset.designInputs[`size.${axis}`] = 200
|
|
215
219
|
}
|
|
220
|
+
return asset
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
test('도면에서의 크기 — 좁히지 않은 끝은 부품에서 계산하고, 좁힌 끝은 작성자의 값이다', () => {
|
|
224
|
+
assert.equal(instanceRangeOf(createFigureAsset('Probe')), undefined, '크기를 선언하지 않은 도형에는 범위가 없다')
|
|
225
|
+
const asset = openSides()
|
|
216
226
|
const y = asset.document.model.inputs.find(i => i.id === 'size.y')!
|
|
217
227
|
y.min = 90
|
|
218
228
|
y.max = 1000
|
|
219
229
|
const range = instanceRangeOf(asset)!
|
|
220
|
-
assert.deepEqual(range.
|
|
221
|
-
assert.deepEqual(range.
|
|
222
|
-
assert.equal(rangeDiffers(range.declared, range.measured), true)
|
|
223
|
-
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: range.measured, replace: true })
|
|
224
|
-
assert.equal(rangeDiffers(instanceRangeOf(asset)!.declared, instanceRangeOf(asset)!.measured), false, '다시 재면 같다')
|
|
230
|
+
assert.deepEqual(range.y, { min: 90, max: 1000, narrowed: { min: true, max: true } })
|
|
231
|
+
assert.deepEqual(range.x, { min: 0, max: Infinity, narrowed: { min: false, max: false } }, '막는 부품이 없으면 끝이 없다')
|
|
225
232
|
})
|
|
226
233
|
|
|
227
|
-
test('
|
|
228
|
-
let asset =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
assert.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
assert.deepEqual(instanceRangeOf(asset)!.declared.x, { min: 150, max: 20000 })
|
|
240
|
-
assert.throws(() => applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: rangeWith(next, 'x', 'min', 300)!, replace: true }), (e: { code?: string }) => e.code === 'INSTANCE_SIZE_RANGE', '저작한 크기(200)를 벗어나면 거절')
|
|
234
|
+
test('도면에서의 크기 — 숫자를 치면 그 끝을 좁히고, 칸을 비우면 다시 연다(갠트리, 09-25)', () => {
|
|
235
|
+
let asset = openSides()
|
|
236
|
+
const range = instanceRangeOf(asset)!
|
|
237
|
+
assert.deepEqual(rangeWith(range, 'x', 'min', 150), { x: { min: 150 }, y: { min: 1, max: 20000 } }, '다른 축의 좁힌 끝은 그대로')
|
|
238
|
+
assert.deepEqual(rangeWith(range, 'y', 'min', Number.NaN), { y: { max: 20000 } }, '비운 칸은 열린다')
|
|
239
|
+
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: rangeWith(range, 'x', 'min', 150), replace: true })
|
|
240
|
+
assert.deepEqual(instanceRangeOf(asset)!.x, { min: 150, max: Infinity, narrowed: { min: true, max: false } })
|
|
241
|
+
assert.throws(
|
|
242
|
+
() => applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: rangeWith(instanceRangeOf(asset)!, 'x', 'min', 300), replace: true }),
|
|
243
|
+
(e: { code?: string }) => e.code === 'INSTANCE_SIZE_RANGE',
|
|
244
|
+
'저작한 크기(200)를 벗어나면 거절'
|
|
245
|
+
)
|
|
241
246
|
})
|
|
242
247
|
|
|
243
248
|
test('한 비율로 커지는 도형 — 자기 숫자인 길이를 세고, 한 번에 따라가게 하며, 무엇이 남는지 이름을 댄다(아키텍트 판정 09-25)', () => {
|
|
@@ -376,7 +381,7 @@ test('면은 도형이 정한다 — 문틀(portal)은 바깥 여섯에 개구
|
|
|
376
381
|
dimensions: { width: 1100, height: 2200, depth: 150, jamb: 100, head: 100 },
|
|
377
382
|
position: { x: 0, y: 0, z: 0 }
|
|
378
383
|
})
|
|
379
|
-
asset = addFigurePrimitive(asset, { id: 'ball', kind: 'sphere', dimensions: {
|
|
384
|
+
asset = addFigurePrimitive(asset, { id: 'ball', kind: 'sphere', dimensions: { radius: 50, radiusY: 50 }, position: { x: 0, y: 0, z: 0 } })
|
|
380
385
|
asset = addFigurePrimitive(asset, { id: 'cone', kind: 'frustum', dimensions: { radiusTop: 20, radiusBottom: 60, height: 100 }, position: { x: 0, y: 0, z: 0 } })
|
|
381
386
|
asset = addFigureBox(asset, { id: 'leaf', size: { x: 890, y: 2095, z: 40 }, position: { x: 0, y: 0, z: 0 } })
|
|
382
387
|
asset = addFigurePrimitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 100, length: 400 }, position: { x: 0, y: 0, z: 0 } })
|
|
@@ -427,18 +432,18 @@ test('돌려 놓인 부품에도 모서리 맞춤을 내놓는다 — 지금 자
|
|
|
427
432
|
})
|
|
428
433
|
|
|
429
434
|
test('모델러에서 만든 도형은 부품에서 재어 도형 크기를 선언한다 — 그 뒤에야 부품이 도형 크기를 따를 수 있다 (사용자 09-25)', async () => {
|
|
430
|
-
const {
|
|
435
|
+
const { instanceSizeToDeclare, instanceRangeOf, hasInstanceSize } = await import('../client/modeller/links.ts')
|
|
431
436
|
const { compileFigureAsset } = await import('@hatiolab/figure-model')
|
|
432
437
|
let asset = createFigureAsset('Conveyor')
|
|
433
|
-
assert.equal(
|
|
438
|
+
assert.equal(instanceSizeToDeclare(asset), undefined, '부품이 없으면 잴 것이 없다')
|
|
434
439
|
asset = addFigureBox(asset, { id: 'frame', size: { x: 2000, y: 100, z: 600 }, position: { x: 0, y: 0, z: 0 } })
|
|
435
440
|
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'frame', to: { plane: 'mounting-plane' } })
|
|
436
441
|
assert.equal(hasInstanceSize(asset, 'x'), false, '새 도형에는 도형 크기가 없다')
|
|
437
|
-
const toDeclare =
|
|
442
|
+
const toDeclare = instanceSizeToDeclare(asset)!
|
|
438
443
|
assert.deepEqual(toDeclare.at, { x: 2000, y: 100, z: 600 }, '지금 부품의 크기가 선언할 크기')
|
|
439
|
-
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size',
|
|
440
|
-
assert.ok(instanceRangeOf(asset), '선언 뒤에는
|
|
441
|
-
assert.equal(
|
|
444
|
+
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', at: toDeclare.at })
|
|
445
|
+
assert.ok(instanceRangeOf(asset), '선언 뒤에는 도면에서의 크기가 보인다')
|
|
446
|
+
assert.equal(instanceSizeToDeclare(asset), undefined, '이미 있으면 다시 권하지 않는다')
|
|
442
447
|
assert.equal(hasInstanceSize(asset, 'x'), true, '부품이 도형 크기를 따르는 선택이 열린다')
|
|
443
448
|
asset = applyFigureAuthoring(asset, { kind: 'link-dimension', part: 'frame', axis: 'x', source: { of: 'instance', axis: 'x' } } as never)
|
|
444
449
|
const at = (size: number) => (compileFigureAsset({ ...asset, designInputs: { ...asset.designInputs, 'size.x': size } }).evaluate().geometry.find(g => g.id === 'frame')!.shape.dimensions as Record<string, number>)['size.x']
|
|
@@ -505,7 +510,7 @@ test('면 위 거리를 도형 크기 비율로 — 도형이 커지면 그 자
|
|
|
505
510
|
})
|
|
506
511
|
|
|
507
512
|
test('선언할 크기는 부품이 쉬고 있을 때의 크기 — 문짝이 도는 자리까지 재지 않는다 (2026-09-25)', async () => {
|
|
508
|
-
const {
|
|
513
|
+
const { instanceSizeToDeclare } = await import('../client/modeller/links.ts')
|
|
509
514
|
let asset = createFigureAsset('Door')
|
|
510
515
|
asset = addFigureBox(asset, { id: 'jamb', size: { x: 100, y: 2100, z: 150 }, position: { x: 0, y: 0, z: 0 } })
|
|
511
516
|
asset = addFigureBox(asset, { id: 'leaf', size: { x: 890, y: 2095, z: 40 }, position: { x: 0, y: 0, z: 0 } })
|
|
@@ -513,7 +518,7 @@ test('선언할 크기는 부품이 쉬고 있을 때의 크기 — 문짝이
|
|
|
513
518
|
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'leaf', to: { part: 'jamb', face: 'right' }, align: { y: 'min', z: 'max' }, gap: 5 })
|
|
514
519
|
const j = asset.joints!.find(o => o.id === 'leaf')!
|
|
515
520
|
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: { ...j, type: 'revolute', axis: 'Y', lowerLimit: -90, upperLimit: 0, state: { id: 'open' } } })
|
|
516
|
-
assert.deepEqual(
|
|
521
|
+
assert.deepEqual(instanceSizeToDeclare(asset)!.at, { x: 995, y: 2100, z: 150 })
|
|
517
522
|
})
|
|
518
523
|
|
|
519
524
|
test('바닥에 선 부품도 도형 크기를 선언한 도형이면 도형의 옆면에 맞출 수 있다 (2026-09-25)', async () => {
|
|
@@ -563,7 +568,7 @@ test('벽 배치 — 장착 평면은 Z = 0, 벽에서 나와 서고 가로질
|
|
|
563
568
|
const { mountingPlaneOf, alignAxesOf } = await import('../client/modeller/links.ts')
|
|
564
569
|
let a = addFigureBox(createFigureAsset('Lamp'), { id: 'plate', size: { x: 200, y: 300, z: 20 }, position: { x: 0, y: 0, z: 0 } })
|
|
565
570
|
assert.deepEqual(mountingPlaneOf(a), { normal: 'y', facings: ['meet', 'flush'] }, '배치 선언 전에는 바닥 — 올려 서거나 아래에 매달림')
|
|
566
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
571
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'wall' as never })
|
|
567
572
|
assert.deepEqual(mountingPlaneOf(a), { normal: 'z', facings: ['meet'] })
|
|
568
573
|
assert.deepEqual(alignAxesOf(a, 'plane').map(x => x.axis), ['x', 'y'])
|
|
569
574
|
})
|
|
@@ -21,7 +21,7 @@ test('지울 수 없는 부품은 사람의 말로 거절하고, 지우며 풀
|
|
|
21
21
|
/* The reason is told apart in refusals.test.ts; that the editor tells it, and what a removal let go of, is parts.check.mjs's. */
|
|
22
22
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
|
|
23
23
|
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
24
|
-
for (const key of ['part-is-referenced', 'part-
|
|
24
|
+
for (const key of ['part-is-referenced', 'part-row-along', 'part-row-carries'])
|
|
25
25
|
assert.ok(table[`figure.text.${key}`]?.includes('{part}'), `${lang} ${key}`)
|
|
26
26
|
/* What a removal let go of, in every language, with the names it carries. */
|
|
27
27
|
for (const [key, word] of [['removal-released', '{parts}'], ['removal-released-motion', '{control}'], ['removal-own-control', '{max}'], ['removal-fixed', '{parts}'], ['removal-bindings', '{places}'], ['edit-refused', '{code}'], ['edit-refused-node-limit', '{max}']])
|
package/test/properties.test.ts
CHANGED
|
@@ -9,10 +9,9 @@ import assert from 'node:assert/strict'
|
|
|
9
9
|
import { readFileSync } from 'node:fs'
|
|
10
10
|
import { fileURLToPath } from 'node:url'
|
|
11
11
|
|
|
12
|
-
import { addFigureBox, applyFigureAuthoring, createFigureAsset,
|
|
12
|
+
import { addFigureBox, applyFigureAuthoring, createFigureAsset, parseFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
13
13
|
|
|
14
14
|
import { tagsOf, figureFigureFacts } from '../client/modeller/figure-facts.ts'
|
|
15
|
-
import { occupancyFromParts } from '../client/modeller/occupancy.ts'
|
|
16
15
|
import { revisionOf } from './revision-of.ts'
|
|
17
16
|
import { wordsOf } from './words.ts'
|
|
18
17
|
|
|
@@ -32,18 +31,16 @@ test('탭이 읽는 사실 — 이름, 놓이는 곳, 이름 붙은 장착면뿐
|
|
|
32
31
|
assert.equal(read.mounting, undefined)
|
|
33
32
|
})
|
|
34
33
|
|
|
35
|
-
test('배치 기준을 고르면 그
|
|
34
|
+
test('배치 기준을 고르면 탭이 그 기준을 다시 읽는다', () => {
|
|
36
35
|
const source = serializeFigureAsset(probe())
|
|
37
|
-
const
|
|
36
|
+
const placed = applyFigureAuthoring(parseFigureAsset(source), { kind: 'set-placement', placement: 'ceiling' })
|
|
38
37
|
|
|
39
|
-
assert.equal(facts(
|
|
40
|
-
assert.ok(!inspectFigureAsset(declared).violations.some(v => v.code === 'OCCUPANCY_UNDECLARED'))
|
|
38
|
+
assert.equal(facts(placed).placement, 'ceiling')
|
|
41
39
|
})
|
|
42
40
|
|
|
43
41
|
test('장착면을 정하면 탭이 그 부품과 면을 다시 읽고, 해제하면 없어진다', () => {
|
|
44
42
|
const source = serializeFigureAsset(probe())
|
|
45
|
-
const
|
|
46
|
-
const mounted = applyFigureAuthoring(declared, { kind: 'set-mounting-face', part: 'body', face: 'bottom' })
|
|
43
|
+
const mounted = applyFigureAuthoring(parseFigureAsset(source), { kind: 'set-mounting-face', part: 'body', face: 'bottom' })
|
|
47
44
|
|
|
48
45
|
assert.deepEqual(facts(mounted).mounting, { part: 'body', face: 'bottom' })
|
|
49
46
|
assert.equal(facts(applyFigureAuthoring(mounted, { kind: 'clear-mounting-face' })).mounting, undefined)
|
|
@@ -76,12 +73,12 @@ test('속성 탭의 말은 다섯 말 모두에 있다', () => {
|
|
|
76
73
|
})
|
|
77
74
|
|
|
78
75
|
/*
|
|
79
|
-
* The walk without the standing group (user 2026-10-18): nothing on the tab
|
|
80
|
-
*
|
|
76
|
+
* The walk without the standing group (user 2026-10-18): nothing on the tab names a face. Whether the figure meets its
|
|
77
|
+
* plane the release check says from the parts.
|
|
81
78
|
*/
|
|
82
|
-
test('속성 탭에는
|
|
79
|
+
test('속성 탭에는 장착면을 고르는 명령이 없다 — 이름 붙은 장착면을 떼는 것만 있다', () => {
|
|
83
80
|
const tab = read('../client/modeller/figure-properties.ts')
|
|
84
81
|
const kinds = [...tab.matchAll(/kind: '([a-z-]+)'/g)].map(m => m[1]).sort()
|
|
85
82
|
assert.deepEqual(kinds, ['clear-mounting-face', 'rename', 'set-placement'])
|
|
86
|
-
assert.doesNotMatch(tab, /
|
|
83
|
+
assert.doesNotMatch(tab, /set-mounting-face/)
|
|
87
84
|
})
|
package/test/refusals.test.ts
CHANGED
|
@@ -15,9 +15,7 @@ import type { FigureAsset } from '@hatiolab/figure-model'
|
|
|
15
15
|
import {
|
|
16
16
|
clipLossOf,
|
|
17
17
|
editRefusalOf,
|
|
18
|
-
inDeclaredRoom,
|
|
19
18
|
inputRangeRefusalOf,
|
|
20
|
-
narrowingRetryOf,
|
|
21
19
|
partNameRefusalOf,
|
|
22
20
|
partRefusalOf,
|
|
23
21
|
placedRefusalOf,
|
|
@@ -26,6 +24,7 @@ import {
|
|
|
26
24
|
refusalRecordOf,
|
|
27
25
|
removalNoticeOf,
|
|
28
26
|
repeatPitchRefusalOf,
|
|
27
|
+
sizeRangeFitsOf,
|
|
29
28
|
sizeRangeRefusalOf
|
|
30
29
|
} from '../client/modeller/refusals.ts'
|
|
31
30
|
import { wordsOf } from './words.ts'
|
|
@@ -35,6 +34,7 @@ const say = (key: string, params: Record<string, string>) => {
|
|
|
35
34
|
assert.ok(key in ko, `no words for ${key}`)
|
|
36
35
|
return ko[key].replace(/\{(\w+)\}/g, (_: string, k: string) => params[k] ?? '')
|
|
37
36
|
}
|
|
37
|
+
|
|
38
38
|
/** The key and its values, to see which words were chosen. */
|
|
39
39
|
const keyed = (key: string, p: Record<string, string>) => `${key} ${JSON.stringify(p)}`
|
|
40
40
|
|
|
@@ -66,35 +66,30 @@ function conveyor(): FigureAsset {
|
|
|
66
66
|
return act(a, { kind: 'make-repeat', part: 'roller', axis: 'x', pitch: 200 })
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
/*
|
|
70
|
+
* 크기 범위로 남은 거절은 둘 — 작성자가 친 끝이 지금 크기를 빼거나 0 에서 시작할 때(ADR-0087 결정 6). 부품을 바꾸는 명령은
|
|
71
|
+
* 범위 때문에 거절되지 않고, 작성자가 좁힌 끝이 부품이 허락하는 데까지 옮겨진 것을 한 줄로 말한다(사용자 2026-10-21).
|
|
72
|
+
*/
|
|
73
|
+
test('크기 범위의 거절 둘 — 축 이름 · 숫자를 커널이 넘긴 값으로 말한다', () => {
|
|
74
74
|
const outside = sizeRangeRefusalOf(refusalOf(() => act(box(), declaring({ min: 500, max: 4000 }))), say)!
|
|
75
|
-
assert.
|
|
75
|
+
assert.equal(outside, '도면에서의 너비 (X) 범위 500 – 4,000 mm 에 지금 크기 400 mm 가 들어가지 않습니다.')
|
|
76
|
+
const open = sizeRangeRefusalOf(refusalOf(() => act(box(), { kind: 'declare-instance-size', range: { x: { min: 500 } } })), say)!
|
|
77
|
+
assert.equal(open, '도면에서의 너비 (X) 범위 500 – ∞ mm 에 지금 크기 400 mm 가 들어가지 않습니다.', '한쪽 끝만 친 범위')
|
|
76
78
|
const zero = sizeRangeRefusalOf(refusalOf(() => act(box(), declaring({ min: 0, max: 4000 }))), say)!
|
|
77
|
-
assert.
|
|
78
|
-
const
|
|
79
|
-
assert.ok(gone.includes('X') && gone.includes('400 mm') && gone.includes('이미 0'), gone)
|
|
80
|
-
for (const said of [small, large, outside, zero, gone]) assert.doesNotMatch(said, /below|above|declare|authored|INSTANCE_SIZE_RANGE/)
|
|
79
|
+
assert.equal(zero, '도면에서의 너비 (X) 최소는 0 보다 커야 합니다.')
|
|
80
|
+
for (const said of [outside, open, zero]) assert.doesNotMatch(said, /below|above|declare|authored|INSTANCE_SIZE_RANGE|다시 재기/)
|
|
81
81
|
assert.equal(sizeRangeRefusalOf(refusalOf(() => act(conveyor(), { kind: 'set-repeat', part: 'frame', pitch: 10 })), say), null, '다른 거절은 건드리지 않는다')
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
-
test('
|
|
84
|
+
test('부품을 줄이는 연결은 거절되지 않고, 좁혀 둔 끝이 옮겨진 것을 한 줄로 말한다 (사용자 2026-10-21)', () => {
|
|
85
85
|
const asset = panel()
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
assert.
|
|
90
|
-
assert.match(offer.label, /figure.button.narrow-range-from .*"axis":"X"/)
|
|
91
|
-
assert.match(offer.message, /link-needs-smaller-range .*"part":"panel"/)
|
|
92
|
-
assert.deepEqual(offer.retry, { ...link, narrowRange: true })
|
|
93
|
-
assert.doesNotThrow(() => act(asset, offer.retry), '권한 것은 받아들여진다')
|
|
94
|
-
assert.equal(narrowingRetryOf({ kind: 'attach' }, error, keyed), null, '연결이 아닌 명령에는 권하지 않는다')
|
|
86
|
+
const linked = act(asset, followsWidth(-50))
|
|
87
|
+
const fits = fm.figureSizeRangeFitsOf(asset, linked)
|
|
88
|
+
assert.equal(sizeRangeFitsOf(fits, say), '도면에서의 너비 (X) 최소를 1 mm 에서 50 mm 로 바꿨습니다.')
|
|
89
|
+
assert.equal(sizeRangeFitsOf([], say), '', '옮긴 것이 없으면 말하지 않는다')
|
|
95
90
|
})
|
|
96
91
|
|
|
97
|
-
test('지우기는 매단 것 · 따라 움직이는 것을 풀어 남기고 그 이름을 한 줄로 말한다; 남는 거절은
|
|
92
|
+
test('지우기는 매단 것 · 따라 움직이는 것을 풀어 남기고 그 이름을 한 줄로 말한다; 남는 거절은 줄 (아키텍트 판정 2026-09-27)', () => {
|
|
98
93
|
let a = fm.createFigureAsset('Bench')
|
|
99
94
|
a = fm.addFigureBox(a, { id: 'leg', size: { x: 60, y: 700, z: 60 }, position: { x: 0, y: 0, z: 0 } })
|
|
100
95
|
a = fm.addFigureBox(a, { id: 'top', size: { x: 1200, y: 40, z: 600 }, position: { x: 0, y: 0, z: 0 } })
|
|
@@ -116,37 +111,20 @@ test('지우기는 매단 것 · 따라 움직이는 것을 풀어 남기고 그
|
|
|
116
111
|
assert.match(lamp, /removal-own-control .*"part":"shade".*"control":"shade".*"min":"-180".*"max":"180"/, '따르던 것은 제 조작값을 받는다')
|
|
117
112
|
assert.equal(removalNoticeOf(fm.removePart(a, { kind: 'remove-part', target: 'shade' }).notice, keyed), null, '아무것도 풀지 않았으면 말하지 않는다')
|
|
118
113
|
|
|
119
|
-
const declared = act(a, { kind: 'declare-occupancy', proposal: fm.proposeFigureOccupancy(a), placement: 'floor' })
|
|
120
114
|
const why = (asset: FigureAsset, part: string) => partRefusalOf(refusalOf(() => fm.editFigurePart(asset, { kind: 'remove-part', target: part })), part, keyed)
|
|
121
|
-
assert.match(why(declared, 'shade')!, /part-in-declared-space .*"part":"shade"/, '잎인데 공간 선언에 들어 있다')
|
|
122
115
|
assert.match(why(conveyor(), 'frame')!, /part-row-along .*"part":"frame".*"rows":"roller"/, '줄이 따라 놓인 부품')
|
|
123
116
|
let hung = fm.addFigureBox(conveyor(), { id: 'cap', size: { x: 20, y: 20, z: 20 }, position: { x: 0, y: 0, z: 0 } })
|
|
124
117
|
hung = act(hung, { kind: 'attach', part: 'cap', to: { part: 'roller', face: 'top' } })
|
|
125
118
|
assert.match(why(hung, 'roller')!, /part-row-carries .*"part":"roller".*"others":"cap"/, '줄에 매달린 것')
|
|
126
119
|
})
|
|
127
120
|
|
|
128
|
-
test('공간 선언에 든 부품 — 한 번 눌러 지우고, 공간은 남은 부품에 다시 맞추며 그렇다고 한 줄 말한다(묻지 않는다, 2026-09-27)', () => {
|
|
129
|
-
let a = fm.addFigureBox(fm.createFigureAsset('Gate'), { id: 'housing', size: { x: 300, y: 1000, z: 300 }, position: { x: 0, y: 0, z: 0 } })
|
|
130
|
-
a = fm.addFigureBox(a, { id: 'stray', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
131
|
-
a = act(a, { kind: 'attach', part: 'housing', to: { plane: 'mounting-plane' } })
|
|
132
|
-
a = act(a, { kind: 'attach', part: 'stray', to: { plane: 'mounting-plane' } })
|
|
133
|
-
a = act(a, { kind: 'declare-occupancy', proposal: fm.proposeFigureOccupancy(a), placement: 'floor' })
|
|
134
|
-
assert.equal(inDeclaredRoom(refusalOf(() => fm.editFigurePart(a, { kind: 'remove-part', target: 'stray' }))), true)
|
|
135
|
-
assert.equal(inDeclaredRoom(refusalOf(() => fm.editFigurePart(conveyor(), { kind: 'remove-part', target: 'frame' }))), false, '줄은 이 길이 아니다')
|
|
136
|
-
const after = fm.editFigurePart(a, { kind: 'remove-part', target: 'stray', refitRoom: true })
|
|
137
|
-
assert.equal(after.document.model.nodes.some(n => n.id === 'stray'), false)
|
|
138
|
-
assert.ok(after.occupancy?.bounds, '공간은 남은 부품으로 다시 선언된다')
|
|
139
|
-
/* Removed in one click, nothing asked, the space fitted to the parts left and said so: parts.check.mjs. */
|
|
140
|
-
assert.ok(ko['figure.text.removal-space-refitted'])
|
|
141
|
-
})
|
|
142
|
-
|
|
143
121
|
test('배치를 바꿀 수 없는 까닭 — 평면에 붙은 부품과 새 평면이 받는 면을 말한다(벽 배치, 09-25)', () => {
|
|
144
122
|
const a = act(fm.addFigureBox(fm.createFigureAsset('Post'), { id: 'post', size: { x: 100, y: 1000, z: 100 }, position: { x: 0, y: 0, z: 0 } }), {
|
|
145
123
|
kind: 'attach',
|
|
146
124
|
part: 'post',
|
|
147
125
|
to: { plane: 'mounting-plane' }
|
|
148
126
|
})
|
|
149
|
-
const refused = refusalOf(() => act(a, { kind: '
|
|
127
|
+
const refused = refusalOf(() => act(a, { kind: 'set-placement', placement: 'wall' }))
|
|
150
128
|
const said = placementRefusalOf(refused, say)!
|
|
151
129
|
assert.equal(said, '장착 평면에 붙은 부품이 있어 놓이는 곳을 바꿀 수 없습니다: post. 먼저 이 부품을 떼어 주십시오.')
|
|
152
130
|
assert.equal(editRefusalOf(refused, say), said, '편집 거절의 말도 같다')
|
|
@@ -244,7 +222,7 @@ test('옮겨 적은 거절 — 기록은 커널의 거절과 같은 모양이라
|
|
|
244
222
|
part: 'post',
|
|
245
223
|
to: { plane: 'mounting-plane' }
|
|
246
224
|
})
|
|
247
|
-
const refused = refusalOf(() => act(a, { kind: '
|
|
225
|
+
const refused = refusalOf(() => act(a, { kind: 'set-placement', placement: 'wall' }))
|
|
248
226
|
const record = refusalRecordOf(refused)
|
|
249
227
|
assert.ok(record.details, 'its details kept')
|
|
250
228
|
assert.equal(editRefusalOf(record, say), editRefusalOf(refused, say))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
|
-
import { addFigureBox, createFigureAsset, applyFigureAuthoring, applyFigureProposal, compileFigureAsset, figureFollowOf,
|
|
4
|
+
import { addFigureBox, createFigureAsset, applyFigureAuthoring, applyFigureProposal, compileFigureAsset, figureFollowOf, parseFigureAsset, planFigureResize, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
5
5
|
import type { FigurePlacement } from '@hatiolab/figure-model'
|
|
6
6
|
import { createFigureProductSession } from '../client/modeller/product-session.js'
|
|
7
7
|
import { resizeRefusalOf } from '../client/modeller/refusals.js'
|
|
@@ -26,7 +26,7 @@ const START: Record<FigurePlacement, { x: number; y: number; z: number }> = {
|
|
|
26
26
|
for (const mode of ['floor', 'ceiling', 'wall', 'center'] as const) {
|
|
27
27
|
test(`${mode}: one step through the session -- undo, redo, save and open again`, async () => {
|
|
28
28
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: START[mode] })
|
|
29
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
29
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: mode })
|
|
30
30
|
const before = serializeFigureAsset(a)
|
|
31
31
|
let record = { id: 'ux', source: before }
|
|
32
32
|
const session = createFigureProductSession({ load: async () => record, save: async q => (record = { id: 'ux', source: q.source }) })
|
|
@@ -60,7 +60,7 @@ for (const mode of ['floor', 'ceiling', 'wall', 'center'] as const) {
|
|
|
60
60
|
test('a refusal is said in the author\'s words, never the kernel\'s English', () => {
|
|
61
61
|
const said: Array<[string, Record<string, string>]> = []
|
|
62
62
|
const say = (key: string, params: Record<string, string>) => (said.push([key, params]), key)
|
|
63
|
-
/* No
|
|
63
|
+
/* No placement set: the figure stands on the floor by default. */
|
|
64
64
|
const a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 }, rotation: { x: 0, y: 30, z: 0 } })
|
|
65
65
|
assert.equal(resizeRefusalOf(planFigureResize(a, { part: 'box' }), 'box', say), 'figure.text.resize-refused-turned')
|
|
66
66
|
assert.deepEqual(said.at(-1)![1].place, 'figure.label.resize-place-floor')
|
|
@@ -74,7 +74,7 @@ test('a refusal is said in the author\'s words, never the kernel\'s English', ()
|
|
|
74
74
|
*/
|
|
75
75
|
test('fresh, width only, and all set back to fixed are three different answers', async () => {
|
|
76
76
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 50, z: 30 } })
|
|
77
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
77
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
|
|
78
78
|
/* Fresh: all three are suggested -- and the button says "all three", never passing it off as the author's choice. */
|
|
79
79
|
assert.deepEqual(resizeAxesOf(a, 'box'), { axes: ['x', 'y', 'z'], basis: 'suggested' })
|
|
80
80
|
/* Width only ticked: width alone is planned, and height and depth stay fixed. */
|
|
@@ -99,19 +99,17 @@ test('fresh, width only, and all set back to fixed are three different answers',
|
|
|
99
99
|
|
|
100
100
|
test('a part off the floor is not moved by choosing its size response', () => {
|
|
101
101
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'shelf', size: { x: 400, y: 40, z: 300 }, position: { x: 0, y: 520, z: 0 } })
|
|
102
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
102
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
|
|
103
103
|
const asked = planFigureResize(a, { part: 'shelf', axes: resizeAxesOf(a, 'shelf').axes })
|
|
104
104
|
assert.deepEqual([asked.refusal?.code, asked.refusal?.mm], ['GAP_CHOICE', 500])
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
test('a
|
|
107
|
+
test('a part held by the floor shows X and Z in its position row, not Y', () => {
|
|
108
108
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 50, z: 30 } })
|
|
109
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
109
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
|
|
110
110
|
const resized = applyFigureProposal(a, planFigureResize(a, { part: 'box' }).steps)
|
|
111
|
-
const roomOnly = figureRoomOnlyInputsOf(resized, 'box')
|
|
112
|
-
assert.ok(roomOnly.includes('box.position.y'))
|
|
113
111
|
const fields = Object.entries(resized.designInputs).filter(([id]) => id.startsWith('box.')).map(([id, value]) => ({ id, unit: 'mm', role: 'design', value }))
|
|
114
|
-
const { vectors } = groupFields(fields
|
|
112
|
+
const { vectors } = groupFields(fields, 'box')
|
|
115
113
|
const position = vectors.find(v => v.kind === 'vector' && v.name === 'position')!
|
|
116
114
|
assert.ok(position.kind === 'vector' && position.cells.x && !position.cells.y && position.cells.z, 'X and Z still move it; Y is held by the floor')
|
|
117
115
|
})
|
|
@@ -125,7 +123,7 @@ test('the part component leaves its place in a size drag to the modeller, for ea
|
|
|
125
123
|
const { componentOf } = await import('../client/modeller/stage-model.js')
|
|
126
124
|
for (const mode of ['floor', 'ceiling', 'wall', 'center'] as const) {
|
|
127
125
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: START[mode] })
|
|
128
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
126
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: mode })
|
|
129
127
|
const body = compileFigureAsset(a).evaluate().geometry.find(g => g.id === 'box') as never
|
|
130
128
|
const state = componentOf(body, {}, { width: 1000, height: 1000 }, true)!
|
|
131
129
|
assert.equal(state.scalePlacement, 'owner', mode)
|
|
@@ -138,9 +136,9 @@ test('the part component leaves its place in a size drag to the modeller, for ea
|
|
|
138
136
|
test('a fastened part keeps its position and turn rows, with the numbers the rule gives', async () => {
|
|
139
137
|
const { placementOf } = await import('../client/modeller/stage-model.js')
|
|
140
138
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 50, z: 30 } })
|
|
141
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
139
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
|
|
142
140
|
const resized = applyFigureProposal(a, planFigureResize(a, { part: 'box' }).steps)
|
|
143
|
-
const fields = Object.entries(resized.designInputs).filter(([id]) => id.startsWith('box.')
|
|
141
|
+
const fields = Object.entries(resized.designInputs).filter(([id]) => id.startsWith('box.')).map(([id, value]) => ({ id, unit: 'mm', role: 'design', value }))
|
|
144
142
|
const { vectors } = groupFields(fields, 'box')
|
|
145
143
|
const rows = Object.fromEntries(vectors.map(v => [v.name, v]))
|
|
146
144
|
assert.ok(rows.rotation && rows.position, 'the turn row stands though every turn axis is the joint\'s')
|
|
@@ -171,7 +169,7 @@ test('a part fastened at any angle is sized by the gizmo too: its fastening keep
|
|
|
171
169
|
test('"open attachment settings" opens this part\'s fastening, as it is now, and not another tab', async () => {
|
|
172
170
|
const { fasteningOf } = await import('../client/modeller/links.js')
|
|
173
171
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 150, z: 30 } })
|
|
174
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
172
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
|
|
175
173
|
const kept = applyFigureProposal(a, planFigureResize(a, { part: 'box', gap: 'keep' }).steps)
|
|
176
174
|
const form = fasteningOf(revisionOf(kept), 'box')!
|
|
177
175
|
assert.equal(form.toPlane, true, 'on the plane')
|
|
@@ -186,7 +184,7 @@ test('"open attachment settings" opens this part\'s fastening, as it is now, and
|
|
|
186
184
|
test('the fastening form keeps a place that follows a position field, and fastening again keeps the field', async () => {
|
|
187
185
|
const { fasteningOf, alignOf } = await import('../client/modeller/links.js')
|
|
188
186
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 200, y: 50, z: 150 } })
|
|
189
|
-
a = applyFigureAuthoring(a, { kind: '
|
|
187
|
+
a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
|
|
190
188
|
const kept = applyFigureProposal(a, planFigureResize(a, { part: 'box' }).steps)
|
|
191
189
|
const form = fasteningOf(revisionOf(kept), 'box')!
|
|
192
190
|
assert.deepEqual([form.align.x, form.alignInput.x, form.align.z, form.alignInput.z], ['input', 'box.position.x', 'input', 'box.position.z'])
|
package/test/revision.test.ts
CHANGED
|
@@ -12,7 +12,7 @@ import assert from 'node:assert/strict'
|
|
|
12
12
|
import { readFileSync } from 'node:fs'
|
|
13
13
|
import { fileURLToPath } from 'node:url'
|
|
14
14
|
|
|
15
|
-
import { addFigureBox, applyFigureAuthoring, createFigureAsset, figureFaceOverhangOf, figurePartFaceOf, figurePlaneAlignChanges,
|
|
15
|
+
import { addFigureBox, applyFigureAuthoring, createFigureAsset, figureFaceOverhangOf, figurePartFaceOf, figurePlaneAlignChanges, figureRowOf, measureFigureWork, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
16
16
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
17
17
|
|
|
18
18
|
import { createFigureProductSession } from '../client/modeller/product-session.ts'
|
|
@@ -88,7 +88,6 @@ function selectionQuestions(shown: FigureRevision, part: string) {
|
|
|
88
88
|
gridStepOf(shown)
|
|
89
89
|
jointFanOf(shown, part)
|
|
90
90
|
partSummaryOf(asset, part, key => key, 'ko')
|
|
91
|
-
figureRoomOnlyInputsOf(asset, part)
|
|
92
91
|
for (const axis of ['x', 'y', 'z'] as const) measureNow(shown, part, axis)
|
|
93
92
|
for (const axis of ['x', 'z'] as const) canMirror(shown, part, axis, reach)
|
|
94
93
|
figurePlaneAlignChanges(asset, part)
|
package/test/verdict.test.ts
CHANGED
|
@@ -17,7 +17,6 @@ import { addFigureBox, createFigureAsset, editFigurePart, figureCostOf } from '@
|
|
|
17
17
|
import { budgetOf, figureReportOf, verdictMeasureOf } from '../client/modeller/maturity.ts'
|
|
18
18
|
import { newestAnswer, type FigureOutcome } from '../client/modeller/newest-answer.ts'
|
|
19
19
|
import { capacityFrom, SCENE_BUDGET } from '../client/modeller/scene-budget.ts'
|
|
20
|
-
import { moving } from './joints.ts'
|
|
21
20
|
import { wordsOf } from './words.ts'
|
|
22
21
|
|
|
23
22
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
@@ -37,7 +36,7 @@ test('개수는 목록 카드와 같은 셈이다 — 원가를 같은 capacityF
|
|
|
37
36
|
assert.deepEqual(measured.report, figureReportOf(asset), '탭은 그 셈을 부르고 보여 줄 뿐이다')
|
|
38
37
|
assert.equal(measured.told.instances, capacityFrom(cost.groups, cost.triangles)!.instances, '탭 이름표의 개수도 같은 셈에서 온다')
|
|
39
38
|
assert.deepEqual(measured.told.budget, budgetOf(figureReportOf(asset)), '부품 목록 머리의 줄도 같은 보고서에서 온다')
|
|
40
|
-
assert.deepEqual(verdictMeasureOf(undefined), { report: undefined,
|
|
39
|
+
assert.deepEqual(verdictMeasureOf(undefined), { report: undefined, told: { instances: undefined, budget: undefined } }, '연 도형이 없으면 잴 것도 알릴 개수도 없다')
|
|
41
40
|
})
|
|
42
41
|
|
|
43
42
|
test('판정은 발행 패널과 같은 서버 문에서 온다 — 화면이 따로 판정하지 않는다', () => {
|
|
@@ -80,19 +79,6 @@ test('판정 탭의 말은 다섯 말 모두에 있다 — 없으면 키가 그
|
|
|
80
79
|
}
|
|
81
80
|
})
|
|
82
81
|
|
|
83
|
-
test('감춘 부품도 「부품에 맞추기」가 잴 수 있는 부품이다', () => {
|
|
84
|
-
/* 새 도형은 부품 없이도 「공간 선언 없음」으로 막힌다. 그때 맞추기는 OCCUPANCY_NO_PARTS 로 던진다 — 단추는 잴 부품이 있을 때만. */
|
|
85
|
-
let asset = createFigureAsset('Probe')
|
|
86
|
-
asset = addFigureBox(asset, { id: 'base', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
87
|
-
asset = addFigureBox(asset, { id: 'lamp', size: { x: 100, y: 100, z: 100 }, position: { x: 200, y: 50, z: 0 } })
|
|
88
|
-
asset = moving(asset, 'base', { kind: 'turn', axis: 'y', id: 'base.turn', min: 0, max: 90 })
|
|
89
|
-
asset = editFigurePart(asset, { kind: 'set-visibility', target: 'lamp', shownWhen: { input: 'base.turn', atLeast: 80 } })
|
|
90
|
-
const cost = figureCostOf(asset)
|
|
91
|
-
assert.equal(cost.hidden, 1, '전제: 등은 지금 감춰져 있다')
|
|
92
|
-
assert.equal(verdictMeasureOf(asset).bodies, 2, '그려지는 하나와 감춘 하나')
|
|
93
|
-
assert.equal(verdictMeasureOf(createFigureAsset('Empty')).bodies, 0, '부품이 없으면 잴 것이 없다')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
82
|
test('a finding\'s affected parts are the figure\'s part names, not the format\'s words (user 2026-10-16: 「occupancy · roller-row-1-roller」)', async () => {
|
|
97
83
|
const { affectedPartsOf } = await import('../client/modeller/affected-parts.ts')
|
|
98
84
|
assert.deepEqual(affectedPartsOf('occupancy · roller-row-1-roller', ['box', 'roller-row-1-roller']), ['roller-row-1-roller'])
|