@things-factory/figure-ui 10.1.205 → 10.1.206
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 +43 -41
- package/client/modeller/fasten-form.ts +50 -11
- package/client/modeller/figure-fasten-dialog.ts +3 -2
- package/client/modeller/figure-links.ts +2 -2
- package/client/modeller/figure-preview.ts +6 -6
- package/client/modeller/gizmo.ts +10 -0
- package/client/modeller/keys.ts +10 -1
- package/client/modeller/links.ts +32 -41
- 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 +19 -26
- 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 +7 -3
- package/dist-client/modeller/editor.js +47 -42
- 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-fasten-dialog.d.ts +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-links.d.ts +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 +4 -4
- package/dist-client/modeller/figure-preview.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/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 +6 -14
- package/dist-client/modeller/refusals.js +16 -27
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/browser/acceptance-page.js +1 -1
- package/test/browser/links.check.mjs +30 -13
- package/test/browser/modeller.check.mjs +45 -16
- package/test/browser/parts.check.mjs +49 -0
- package/test/browser/section.check.mjs +27 -0
- package/test/fasten-form.test.ts +42 -2
- package/test/gizmo.test.ts +12 -1
- package/test/keys.test.ts +11 -0
- package/test/links.test.ts +38 -33
- package/test/refusals.test.ts +17 -21
- package/translations/en.json +8 -13
- package/translations/es.json +8 -13
- package/translations/ja.json +8 -13
- package/translations/ko.json +8 -13
- package/translations/ms.json +8 -13
- package/translations/zh.json +8 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.206",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"clean": "npm run clean:server && npm run clean:client"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@hatiolab/figure-model": "^0.9.
|
|
30
|
+
"@hatiolab/figure-model": "^0.9.6",
|
|
31
31
|
"@hatiolab/things-scene": "^10.1.95",
|
|
32
32
|
"@material/web": "^2.0.0",
|
|
33
33
|
"@operato/data-grist": "^10.29.43",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"@operato/slot-layout-editor": "^10.29.43",
|
|
43
43
|
"@operato/styles": "^10.0.0",
|
|
44
44
|
"@operato/utils": "^10.29.43",
|
|
45
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
46
|
-
"@things-factory/figure-service": "^10.1.
|
|
47
|
-
"@things-factory/figure-viewer": "^10.1.
|
|
48
|
-
"@things-factory/setting-base": "^10.1.
|
|
45
|
+
"@things-factory/ai-assistant": "^10.1.206",
|
|
46
|
+
"@things-factory/figure-service": "^10.1.206",
|
|
47
|
+
"@things-factory/figure-viewer": "^10.1.206",
|
|
48
|
+
"@things-factory/setting-base": "^10.1.206",
|
|
49
49
|
"three": "^0.185.1"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "18588a058a4a6cb21c379b2da5517e077c6442ba"
|
|
52
52
|
}
|
|
@@ -162,7 +162,7 @@ export const MODELS = {
|
|
|
162
162
|
ball: () => {
|
|
163
163
|
let a = createFigureAsset('Ball')
|
|
164
164
|
a.palette = { ...a.palette, ...PALETTE }
|
|
165
|
-
a = addFigurePrimitive(a, { id: 'ball', kind: 'sphere', dimensions: {
|
|
165
|
+
a = addFigurePrimitive(a, { id: 'ball', kind: 'sphere', dimensions: { radius: 200, radiusY: 200 }, position: { x: 0, y: 500, z: 0 } })
|
|
166
166
|
return serializeFigureAsset(applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' }))
|
|
167
167
|
},
|
|
168
168
|
/* 500 mm above the floor: keep the gap, or be brought onto it. */
|
|
@@ -241,7 +241,7 @@ describe('a part made to follow the figure size, and let go of it', () => {
|
|
|
241
241
|
})
|
|
242
242
|
})
|
|
243
243
|
|
|
244
|
-
/* A base and a flange, sized
|
|
244
|
+
/* A base and a flange, sized, saved with ends on every axis: the height from 90 mm, the others from 1 mm. */
|
|
245
245
|
function sizedPair(turning) {
|
|
246
246
|
let a = box(createFigureAsset('Probe'), 'base', [200, 100, 200], [0, 50, 0])
|
|
247
247
|
a = addFigurePrimitive(a, { id: 'flange', kind: 'cylinder', dimensions: { radius: 20, length: 10 }, position: { x: 0, y: 105, z: 0 } })
|
|
@@ -256,16 +256,11 @@ function sizedPair(turning) {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
describe('the sizes a figure may be placed at, and how it grows', () => {
|
|
259
|
-
test('
|
|
259
|
+
test('a figure saved with ends reads them as typed, and nothing is offered to measure again', async () => {
|
|
260
260
|
await mount('sized-pair', sizedPair(false))
|
|
261
261
|
/* The figure's range is the 「반응형」 tab's. */
|
|
262
262
|
await tab('response')
|
|
263
|
-
|
|
264
|
-
await click(await at('button', 'figure.button.remeasure-range'), 'remeasure')
|
|
265
|
-
await oneStep(start)
|
|
266
|
-
const y = (await source()).document.model.inputs.find(i => i.id === 'size.y')
|
|
267
|
-
assert.deepEqual([y.min, y.max], [1, 1000], 'the height from 1 mm, as measured')
|
|
268
|
-
assert.equal(await at('button', 'figure.button.remeasure-range'), null, 'nothing left to measure again')
|
|
263
|
+
assert.equal(await at('button', 'figure.button.remeasure-range'), null, 'nothing to measure again')
|
|
269
264
|
})
|
|
270
265
|
|
|
271
266
|
test('a figure with no joint is not urged to grow by one ratio; declared, the rule is said', async () => {
|
|
@@ -282,6 +277,26 @@ describe('the sizes a figure may be placed at, and how it grows', () => {
|
|
|
282
277
|
})
|
|
283
278
|
})
|
|
284
279
|
|
|
280
|
+
/*
|
|
281
|
+
A link that makes the height impossible below 95 mm is made, not refused (user 2026-10-21): the smallest height the
|
|
282
|
+
author narrowed to, 90 mm, moves in to 95 in the same step, and one line says from what to what (ADR-0087 decision 6).
|
|
283
|
+
*/
|
|
284
|
+
describe('a part change a narrowed end cannot hold', () => {
|
|
285
|
+
test('is one step; the end moves in, said in one line', async () => {
|
|
286
|
+
await mount('sized-pair-moved', sizedPair(false))
|
|
287
|
+
await pick('flange')
|
|
288
|
+
await read(() => look.hear('notify'))
|
|
289
|
+
const start = await state()
|
|
290
|
+
await read(() => look.tell('shape', 'command', { action: { kind: 'link-dimension', part: 'flange', axis: 'y', source: { of: 'instance', axis: 'y' }, plus: -95 } }))
|
|
291
|
+
await oneStep(start)
|
|
292
|
+
assert.equal((await state()).error, '', 'not refused')
|
|
293
|
+
const y = (await source()).document.model.inputs.find(i => i.id === 'size.y')
|
|
294
|
+
assert.ok(y.min > 95 && y.min < 95.001 && y.max === 1000, `the smallest height moved in: ${y.min}`)
|
|
295
|
+
const told = (await heard('notify')).filter(n => n?.level === 'info' && String(n.message).includes('figure.text.size-range-moved-min'))
|
|
296
|
+
assert.equal(told.length, 1, 'said once')
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
285
300
|
/* A conveyor's rollers in a row across its frame, 200 mm apart. */
|
|
286
301
|
function conveyor() {
|
|
287
302
|
let a = fasten(box(createFigureAsset('Conveyor'), 'frame', [2000, 100, 600], [0, 50, 0]), 'frame', 'plane')
|
|
@@ -399,9 +414,11 @@ describe('a new fastening for a door leaf: chosen in the window, seen before it
|
|
|
399
414
|
assert.deepEqual(await read(() => probe.fastenPopup()), { title: 'figure.label.fastening', movable: true, backdrop: false, role: 'dialog' })
|
|
400
415
|
})
|
|
401
416
|
|
|
402
|
-
|
|
417
|
+
/* Chosen in the window, the gap is the distance the leaf stands at: it is fastened where it is (ADR-0140 stage 4, 「현재 상태로 붙이기」). */
|
|
418
|
+
test('the frame chosen: the editor is told the command, and the canvas draws the face it meets; the leaf stays where it stands', async () => {
|
|
403
419
|
await aimAt('frame')
|
|
404
|
-
await until(() => look.drawn().includes('
|
|
420
|
+
await until(() => look.drawn().includes('__target-face') && look.all('[preview-words]', 'figure.text.preview-stays').length > 0, [], 'the face drawn and the leaf said to stay')
|
|
421
|
+
assert.deepEqual(await ghosts(), [], 'nothing drawn where it would go: it does not move')
|
|
405
422
|
const told = (await heard('preview-command')).filter(Boolean).at(-1)
|
|
406
423
|
assert.equal(told?.action.kind, 'attach')
|
|
407
424
|
assert.equal((await source()).joints?.some(j => j.body1.part === 'leaf') ?? false, false, 'nothing written')
|
|
@@ -443,7 +460,7 @@ describe('a new fastening for a door leaf: chosen in the window, seen before it
|
|
|
443
460
|
|
|
444
461
|
test('Esc closes the window and tells the editor to clear what it tried; the leaf stays selected', async () => {
|
|
445
462
|
await aimAt('frame')
|
|
446
|
-
await until(() => look.drawn().includes('
|
|
463
|
+
await until(() => look.drawn().includes('__target-face'), [], 'the face it would meet drawn')
|
|
447
464
|
await read(() => look.hear('preview-command', 'dialog'))
|
|
448
465
|
await page().keyboard.press('Escape')
|
|
449
466
|
await until(() => !probe.fastenTargets(), [], 'the window closed')
|
|
@@ -459,9 +476,9 @@ describe('a new fastening for a door leaf: chosen in the window, seen before it
|
|
|
459
476
|
await aimAt('frame')
|
|
460
477
|
/*
|
|
461
478
|
The frame's faces are offered once the drawing has the frame: a press before that is not a choice. And the press is
|
|
462
|
-
aimed once the window has drawn what the choice brings -- the
|
|
479
|
+
aimed once the window has drawn what the choice brings -- the face it would meet -- or it lands where the face was.
|
|
463
480
|
*/
|
|
464
|
-
await until(() => look.face('front')?.pickable && look.drawn().includes('
|
|
481
|
+
await until(() => look.face('front')?.pickable && look.drawn().includes('__target-face'), [], 'the front face offered, the face it would meet drawn')
|
|
465
482
|
await settled()
|
|
466
483
|
await sleep(200)
|
|
467
484
|
await click(await at('path[face="front"]'), 'the front face in the drawing')
|
|
@@ -452,8 +452,8 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
452
452
|
assert.equal((await declared())['size.x'][1], 3000, 'the largest width is what was typed')
|
|
453
453
|
await type('size.x.min', '500')
|
|
454
454
|
assert.notEqual((await state()).error, '', 'the figure is 400 wide: a smallest of 500 is refused')
|
|
455
|
-
assert.equal((await declared())['size.x'][0]
|
|
456
|
-
assert.
|
|
455
|
+
assert.equal((await declared())['size.x'][0] ?? null, null, 'and the smallest is still open (ADR-0087 decision 6)')
|
|
456
|
+
assert.deepEqual(await (await field('size.x.min')).evaluate(i => [i.value, i.placeholder]), ['', '0'], 'the field empty again, what the parts allow written faintly in it')
|
|
457
457
|
})
|
|
458
458
|
|
|
459
459
|
/*
|
|
@@ -467,6 +467,34 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
467
467
|
assert.notEqual(said, 'figure.text.edit-refused', `said in its own words: ${said}`)
|
|
468
468
|
})
|
|
469
469
|
|
|
470
|
+
test('a field emptied opens its end again, in one step: the largest is what the parts allow, nothing', async () => {
|
|
471
|
+
const field = () =>
|
|
472
|
+
page().evaluateHandle(() => {
|
|
473
|
+
const deep = (root = document) => [...root.querySelectorAll('input')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
474
|
+
return deep().find(i => i.getAttribute('aria-label') === 'size.x.max') ?? null
|
|
475
|
+
})
|
|
476
|
+
/* Selected where it is drawn, as a person does, then typed over. */
|
|
477
|
+
const typeOver = async keys => {
|
|
478
|
+
const at = await (await field()).evaluate(i => {
|
|
479
|
+
i.scrollIntoView({ block: 'center' })
|
|
480
|
+
const r = i.getBoundingClientRect()
|
|
481
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
|
|
482
|
+
})
|
|
483
|
+
await page().mouse.click(at.x, at.y, { clickCount: 3 })
|
|
484
|
+
await keys()
|
|
485
|
+
await page().keyboard.press('Tab')
|
|
486
|
+
await sleep(600)
|
|
487
|
+
}
|
|
488
|
+
await typeOver(() => page().keyboard.type('3000'))
|
|
489
|
+
assert.equal(await (await field()).evaluate(i => i.value), '3000', 'narrowed first')
|
|
490
|
+
const start = await state()
|
|
491
|
+
await typeOver(() => page().keyboard.press('Backspace'))
|
|
492
|
+
const after = await state()
|
|
493
|
+
assert.equal(after.error, '', 'not refused')
|
|
494
|
+
assert.equal(after.revision, start.revision + 1, 'one step')
|
|
495
|
+
assert.deepEqual(await (await field()).evaluate(i => [i.value, i.placeholder]), ['', '∞'], 'empty, and nothing limits it')
|
|
496
|
+
})
|
|
497
|
+
|
|
470
498
|
test('a part follows the figure\'s width less a difference: chosen and typed, the width is the size less it', async () => {
|
|
471
499
|
await click(await read(() => probe.partOnPage('box')))
|
|
472
500
|
assert.equal((await state()).selected, 'box')
|
|
@@ -500,23 +528,17 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
500
528
|
await page().keyboard.type('-50')
|
|
501
529
|
await page().keyboard.press('Tab')
|
|
502
530
|
await sleep(600)
|
|
503
|
-
/*
|
|
504
|
-
|
|
505
|
-
|
|
531
|
+
/*
|
|
532
|
+
The sizes the figure may be drawn at are worked out from its parts (ADR-0087 decision 6), not refused against a
|
|
533
|
+
range nobody chose: the link is taken as typed, with nothing asked.
|
|
534
|
+
*/
|
|
535
|
+
assert.equal((await state()).error, '', 'taken')
|
|
536
|
+
assert.equal((await body('box')).dimensions['size.x'], 350, 'the figure\'s 400 less 50')
|
|
506
537
|
const asked = await read(() => {
|
|
507
538
|
const deep = (root = document) => [...root.querySelectorAll('ox-prompt')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
508
|
-
|
|
509
|
-
const confirm = prompt?.shadowRoot?.querySelector('#confirm')
|
|
510
|
-
if (!confirm) return null
|
|
511
|
-
const r = confirm.getBoundingClientRect()
|
|
512
|
-
return { text: prompt.text, label: confirm.textContent.trim(), x: r.left + r.width / 2, y: r.top + r.height / 2 }
|
|
539
|
+
return deep().length
|
|
513
540
|
})
|
|
514
|
-
assert.
|
|
515
|
-
assert.match(asked.label, /figure.button.narrow-range-from/, 'the same link is offered with the sizes narrowed')
|
|
516
|
-
await click(asked)
|
|
517
|
-
await sleep(600)
|
|
518
|
-
assert.equal((await state()).error, '', 'taken')
|
|
519
|
-
assert.equal((await body('box')).dimensions['size.x'], 350, 'the figure\'s 400 less 50')
|
|
541
|
+
assert.equal(asked, 0, 'nothing asked')
|
|
520
542
|
})
|
|
521
543
|
|
|
522
544
|
test('grown by one ratio, every part is made to follow it in one step, and the offer goes', async () => {
|
|
@@ -534,8 +556,15 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
534
556
|
return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
|
|
535
557
|
}))
|
|
536
558
|
}
|
|
559
|
+
/* Back to the whole figure: a key in a field is the field's, so the field typed in is left first, then Esc. */
|
|
560
|
+
await page().evaluate(() => {
|
|
561
|
+
let focused = document.activeElement
|
|
562
|
+
while (focused?.shadowRoot?.activeElement) focused = focused.shadowRoot.activeElement
|
|
563
|
+
focused?.blur()
|
|
564
|
+
})
|
|
537
565
|
await page().keyboard.press('Escape')
|
|
538
566
|
await sleep(300)
|
|
567
|
+
assert.equal((await state()).selected, '', 'nothing picked')
|
|
539
568
|
await press('figure.button.scale-uniformly')
|
|
540
569
|
assert.equal((await state()).error, '', 'grows by one ratio')
|
|
541
570
|
const start = await state()
|
|
@@ -141,6 +141,55 @@ describe('the parts list: each button does what it says, one step of the session
|
|
|
141
141
|
})
|
|
142
142
|
})
|
|
143
143
|
|
|
144
|
+
/*
|
|
145
|
+
Copy and paste with the canvas focused (user 2026-10-21): left to the scene, Ctrl+C / Ctrl+V copied the canvas object
|
|
146
|
+
with the part's name -- two objects of one name, the copy moving the original and never a part of its own.
|
|
147
|
+
*/
|
|
148
|
+
describe('copy and paste on the canvas: a part of its own, named by figure-model', () => {
|
|
149
|
+
before(() => mount('box', 'three'))
|
|
150
|
+
|
|
151
|
+
const withControl = async code => {
|
|
152
|
+
await page().keyboard.down('Control')
|
|
153
|
+
await page().keyboard.press(code)
|
|
154
|
+
await page().keyboard.up('Control')
|
|
155
|
+
await sleep(400)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
test('Ctrl+C then Ctrl+V on the canvas: one step, a new row with a name of its own, one canvas object per name', async () => {
|
|
159
|
+
await click(await read(() => probe.partOnPage('b')), 'b on the canvas')
|
|
160
|
+
assert.equal((await state()).selected, 'b', 'picked on the canvas, the canvas focused')
|
|
161
|
+
const was = await ids()
|
|
162
|
+
const step = await revision()
|
|
163
|
+
await withControl('KeyC')
|
|
164
|
+
await withControl('KeyV')
|
|
165
|
+
const now = await ids()
|
|
166
|
+
const made = now.filter(id => !was.includes(id))
|
|
167
|
+
assert.equal(made.length, 1, `one new part: ${now}`)
|
|
168
|
+
assert.notEqual(made[0], 'b')
|
|
169
|
+
assert.equal(await revision(), step + 1, 'one step')
|
|
170
|
+
assert.equal((await state()).selected, made[0], 'the copy is the part selected')
|
|
171
|
+
const drawn = await read(() => parts.drawn())
|
|
172
|
+
assert.equal(new Set(drawn).size, drawn.length, `no two canvas objects of one name: ${drawn}`)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('Ctrl+X and Ctrl+Shift+V are kept from the scene: nothing cut or pasted on the canvas alone', async () => {
|
|
176
|
+
await click(await read(() => probe.partOnPage('b')), 'b on the canvas')
|
|
177
|
+
const was = await ids()
|
|
178
|
+
const step = await revision()
|
|
179
|
+
await withControl('KeyX')
|
|
180
|
+
await page().keyboard.down('Control')
|
|
181
|
+
await page().keyboard.down('Shift')
|
|
182
|
+
await page().keyboard.press('KeyV')
|
|
183
|
+
await page().keyboard.up('Shift')
|
|
184
|
+
await page().keyboard.up('Control')
|
|
185
|
+
await sleep(400)
|
|
186
|
+
assert.deepEqual(await ids(), was)
|
|
187
|
+
assert.equal(await revision(), step)
|
|
188
|
+
const drawn = await read(() => parts.drawn())
|
|
189
|
+
assert.deepEqual([...drawn].sort(), [...was].sort(), 'the canvas draws the figure\'s parts, no more, no fewer')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
144
193
|
describe('hidden on the workbench: off the canvas, not out of the figure, and let go when another figure opens', () => {
|
|
145
194
|
before(() => mount('box', 'three'))
|
|
146
195
|
|
|
@@ -330,6 +330,33 @@ describe('the section editor on a box whose sizes follow the figure', () => {
|
|
|
330
330
|
})
|
|
331
331
|
})
|
|
332
332
|
|
|
333
|
+
/*
|
|
334
|
+
* The user's case (2026-10-21, 「수정본 검증 - 기본 버튼 상자」): 「속 공간 만들기」 ticked on a box whose sizes follow the
|
|
335
|
+
* figure's was refused, for a 1 mm the figure had never been asked to go to. Now it is one step and nothing is refused:
|
|
336
|
+
* how small the figure can then be is computed from the walls (ADR-0087 decision 6).
|
|
337
|
+
*/
|
|
338
|
+
describe('the inside made on a box whose sizes follow the figure', () => {
|
|
339
|
+
before(async () => {
|
|
340
|
+
await mount('follows')
|
|
341
|
+
await pick('box')
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
test('ticked, it is one step, drawn, and not refused', async () => {
|
|
345
|
+
const start = await state()
|
|
346
|
+
/* Clicked where it is drawn, as a person does. */
|
|
347
|
+
const at = await (await element('label[toggle] input', { within: 'figure-section-panel' })).evaluate(e => {
|
|
348
|
+
e.scrollIntoView({ block: 'center' })
|
|
349
|
+
const r = e.getBoundingClientRect()
|
|
350
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
|
|
351
|
+
})
|
|
352
|
+
await page().mouse.click(at.x, at.y)
|
|
353
|
+
await until(r => probe.state().revision === r + 1, 'one step', start.revision)
|
|
354
|
+
assert.equal((await state()).error, '', 'not refused')
|
|
355
|
+
await until(() => probe.sectionPanel()?.cavityDrawing, 'the inside drawn')
|
|
356
|
+
assert.ok((await held('box')).cavity, 'the box has its inside')
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
333
360
|
describe('a drawn section\'s end drawn to one point', () => {
|
|
334
361
|
test('turned on, the length is called the height', async () => {
|
|
335
362
|
await mount('wedge')
|
package/test/fasten-form.test.ts
CHANGED
|
@@ -9,7 +9,7 @@ import assert from 'node:assert/strict'
|
|
|
9
9
|
import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
|
|
10
10
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
11
11
|
|
|
12
|
-
import { aimFasten, aimFastenAt, fastenChanged, fastenDetailWordsOf, fastenFormOf, fastenPlanOf, fastenSentenceOf, fastenTargetsOf, freshFasten } from '../client/modeller/fasten-form.ts'
|
|
12
|
+
import { aimFasten, aimFastenAt, fastenChanged, fastenDetailWordsOf, fastenFormOf, fastenPlanOf, fastenSentenceOf, fastenTargetsOf, freshFasten, keptGapOf } from '../client/modeller/fasten-form.ts'
|
|
13
13
|
import { revisionOf } from './revision-of.ts'
|
|
14
14
|
|
|
15
15
|
const box = (asset: FigureAsset, id: string, [x, y, z]: number[], at = [0, 0, 0]) => addFigureBox(asset, { id, size: { x, y, z }, position: { x: at[0]!, y: at[1]!, z: at[2]! } })
|
|
@@ -94,6 +94,46 @@ test('the drawing\'s changes: another face aims again, the way, the gap, a place
|
|
|
94
94
|
assert.equal(fastenPlanOf(r, 'roller', f).targetFace, 'front')
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
+
/*
|
|
98
|
+
* 「현재 상태로 붙이기」(ADR-0140 stage 4, user 2026-10-20): aimed at a face, the gap starts at the distance the part
|
|
99
|
+
* stands from it, so fastening does not move it. Changing how it meets the face works it out again; a number typed is
|
|
100
|
+
* the author's and stays; a press on the canvas puts the part there, with no gap.
|
|
101
|
+
*/
|
|
102
|
+
const centreOf = (asset: FigureAsset, id: string) => (compileFigureAsset(asset).evaluate().geometry as Array<{ id: string; pose?: { t: number[] } }>).find(g => g.id === id)!.pose!.t
|
|
103
|
+
/* A plate on the floor, its top at 20 mm, and a lid 40 mm above it. */
|
|
104
|
+
function floating() {
|
|
105
|
+
let a = fasten(box(createFigureAsset('Lift'), 'plate', [600, 20, 600], [0, 10, 0]), 'plate', 'plane')
|
|
106
|
+
return revisionOf(box(a, 'lid', [100, 20, 100], [0, 70, 0]))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
test('aimed at a face, the gap is where the part stands, and fastening it does not move it', () => {
|
|
110
|
+
const r = floating()
|
|
111
|
+
const f = keptGapOf(r, 'lid', aimFasten(r, 'lid', fastenFormOf(r, 'lid'), { part: 'plate' }))
|
|
112
|
+
assert.deepEqual([f.gap, f.gapFrom], [40, 'kept'])
|
|
113
|
+
const applied = applyFigureAuthoring(r.asset, fastenPlanOf(r, 'lid', f).action!)
|
|
114
|
+
assert.deepEqual(centreOf(applied, 'lid'), centreOf(r.asset, 'lid'), 'the lid where it was')
|
|
115
|
+
const onPlane = keptGapOf(r, 'lid', aimFasten(r, 'lid', f, { plane: true }))
|
|
116
|
+
assert.equal(onPlane.gap, 60, 'aimed at the floor instead: 60 mm above it')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('another way of meeting works the gap out again; a number typed stays; a press on the canvas puts it there', () => {
|
|
120
|
+
const r = floating()
|
|
121
|
+
const aimed = keptGapOf(r, 'lid', aimFasten(r, 'lid', fastenFormOf(r, 'lid'), { part: 'plate' }))
|
|
122
|
+
const flush = fastenChanged(r, 'lid', aimed, { mode: 'flush' })
|
|
123
|
+
const applied = applyFigureAuthoring(r.asset, fastenPlanOf(r, 'lid', flush).action!)
|
|
124
|
+
assert.deepEqual(centreOf(applied, 'lid'), centreOf(r.asset, 'lid'), 'flush, and still where it was')
|
|
125
|
+
assert.deepEqual(fastenDetailWordsOf(aimed, say), [], 'the gap it stands at is the default: nothing said on the fold')
|
|
126
|
+
const typed = fastenChanged(r, 'lid', aimed, { gap: 5 })
|
|
127
|
+
assert.equal(typed.gapFrom, 'typed')
|
|
128
|
+
assert.deepEqual(fastenDetailWordsOf(typed, say), ['figure.text.fasten-summary-gap(mm=5)'])
|
|
129
|
+
assert.equal(fastenChanged(r, 'lid', typed, { mode: 'flush' }).gap, 5, 'the author\'s number is left')
|
|
130
|
+
assert.equal(keptGapOf(r, 'lid', aimFasten(r, 'lid', typed, { plane: true })).gap, 5)
|
|
131
|
+
const plate = (r.geometry as Array<{ id: string }>).find(g => g.id === 'plate')!.id
|
|
132
|
+
const pressed = aimFastenAt(r, 'lid', aimed, { id: plate, point: [0, 20, 0], normal: [0, 1, 0] }).form!
|
|
133
|
+
assert.deepEqual([pressed.gap, pressed.gapFrom], [0, 'pressed'])
|
|
134
|
+
assert.equal(fastenChanged(r, 'lid', pressed, { mode: 'flush' }).gap, 0, 'pressed: left at 0')
|
|
135
|
+
})
|
|
136
|
+
|
|
97
137
|
test('the target list: every other part, the one hanging from this part listed as a loop', () => {
|
|
98
138
|
const r = conveyor()
|
|
99
139
|
assert.deepEqual(fastenTargetsOf(r, 'frame'), [
|
|
@@ -107,7 +147,7 @@ test('the target list: every other part, the one hanging from this part listed a
|
|
|
107
147
|
async function curved() {
|
|
108
148
|
const { addFigurePrimitive } = await import('@hatiolab/figure-model')
|
|
109
149
|
let a = addFigurePrimitive(createFigureAsset('Curved'), { id: 'drum', kind: 'cylinder', dimensions: { radius: 100, length: 1600 }, position: { x: 0, y: 0, z: 0 } } as never)
|
|
110
|
-
a = addFigurePrimitive(a, { id: 'ball', kind: 'sphere', dimensions: {
|
|
150
|
+
a = addFigurePrimitive(a, { id: 'ball', kind: 'sphere', dimensions: { radius: 100, radiusY: 100 }, position: { x: 0, y: 0, z: 3000 } } as never)
|
|
111
151
|
a = box(a, 'tag', [40, 40, 40], [600, 600, 600])
|
|
112
152
|
return box(a, 'cap', [40, 40, 40], [0, 600, 3000])
|
|
113
153
|
}
|
package/test/gizmo.test.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
11
11
|
|
|
12
12
|
import { addFigureBox, addFigurePrimitive, applyFigureAuthoring, compileFigureAsset, createFigureAsset, createFigureEditorWorkspace } from '@hatiolab/figure-model'
|
|
13
13
|
|
|
14
|
-
import { gizmoChanges, gizmoReachOf } from '../client/modeller/gizmo.ts'
|
|
14
|
+
import { gizmoChanges, gizmoReachOf, sameMeasureAxesOf } from '../client/modeller/gizmo.ts'
|
|
15
15
|
import { moving } from './joints.ts'
|
|
16
16
|
|
|
17
17
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
@@ -159,3 +159,14 @@ test('도구 막대의 옮기기 · 돌리기 · 크기 — 키와 같은 길,
|
|
|
159
159
|
const ko = JSON.parse(read('../translations/ko.json'))
|
|
160
160
|
assert.deepEqual(['translate', 'rotate', 'scale'].map(m => ko['figure.button.gizmo-' + m]), ['옮기기', '돌리기', '크기 바꾸기'])
|
|
161
161
|
})
|
|
162
|
+
|
|
163
|
+
/* ADR-0140 4.1: the size handles that change one measure are said while the size tool is on. */
|
|
164
|
+
test('같은 치수 — 원기둥의 X · Z 핸들은 한 지름, 구는 셋이 하나, 상자는 따로', () => {
|
|
165
|
+
const at = { x: 0, y: 0, z: 0 }
|
|
166
|
+
const drum = addFigurePrimitive(createFigureAsset('Drum'), { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 200 }, position: at })
|
|
167
|
+
const ball = addFigurePrimitive(createFigureAsset('Ball'), { id: 'ball', kind: 'sphere', dimensions: { radius: 50, radiusY: 50 }, position: at })
|
|
168
|
+
const crate = addFigureBox(createFigureAsset('Crate'), { id: 'crate', size: { x: 100, y: 100, z: 100 }, position: at })
|
|
169
|
+
assert.deepEqual(sameMeasureAxesOf(gizmoReachOf(drum, 'drum')), ['x', 'z'])
|
|
170
|
+
assert.deepEqual(sameMeasureAxesOf(gizmoReachOf(ball, 'ball')), ['x', 'y', 'z'])
|
|
171
|
+
assert.equal(sameMeasureAxesOf(gizmoReachOf(crate, 'crate')), undefined)
|
|
172
|
+
})
|
package/test/keys.test.ts
CHANGED
|
@@ -41,3 +41,14 @@ test('캔버스에서 누른 W · E · R 은 씬이 이미 받았다 — 또 넘
|
|
|
41
41
|
for (const key of ['w', 'e', 'r']) assert.equal(keyActionOf({ key, inScene: true }, 'arm'), null, key)
|
|
42
42
|
assert.equal(keyActionOf({ key: 'Delete', inScene: true }, 'arm'), 'remove', '다른 키는 그대로')
|
|
43
43
|
})
|
|
44
|
+
|
|
45
|
+
/* Ctrl+C · Ctrl+V are the modeller's: the scene's copied the canvas object with the part's name (user 2026-10-21). */
|
|
46
|
+
test('복사 · 붙여넣기는 모델러의 것 — 고른 부품의 Ctrl+C 는 복사, Ctrl+V 는 붙여넣기, 씬의 잘라내기 · 여러 개 붙여넣기는 막는다', () => {
|
|
47
|
+
assert.equal(keyActionOf({ key: 'c', ctrlKey: true }, 'arm'), 'copy')
|
|
48
|
+
assert.equal(keyActionOf({ key: 'C', metaKey: true }, 'arm'), 'copy')
|
|
49
|
+
assert.equal(keyActionOf({ key: 'c', ctrlKey: true }, ''), 'hold', '고른 것이 없어도 씬에 넘기지 않는다')
|
|
50
|
+
assert.equal(keyActionOf({ key: 'v', ctrlKey: true }, ''), 'paste', '고른 것이 없어도 붙여넣기')
|
|
51
|
+
assert.equal(keyActionOf({ key: 'V', ctrlKey: true, shiftKey: true }, 'arm'), 'hold')
|
|
52
|
+
assert.equal(keyActionOf({ key: 'x', ctrlKey: true }, 'arm'), 'hold')
|
|
53
|
+
assert.equal(keyActionOf({ key: 'v', ctrlKey: true, target: { tagName: 'INPUT' } }, ''), null, '칸 안의 붙여넣기는 칸의 것')
|
|
54
|
+
})
|
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 () => {
|