@things-factory/figure-ui 10.1.204 → 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/canvas.ts +91 -9
- package/client/modeller/editor-styles.ts +23 -22
- package/client/modeller/editor.ts +217 -184
- package/client/modeller/fasten-form.ts +130 -12
- package/client/modeller/figure-fasten-dialog.ts +45 -11
- package/client/modeller/figure-fasten-drawing.ts +233 -12
- package/client/modeller/figure-links.ts +2 -2
- package/client/modeller/figure-preview.ts +6 -6
- package/client/modeller/figure-section-editor.ts +6 -6
- package/client/modeller/figure-slot-layout-panel.ts +1 -1
- package/client/modeller/gizmo.ts +22 -1
- package/client/modeller/keys.ts +10 -1
- package/client/modeller/links.ts +79 -46
- 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/client/modeller/section-drawing.ts +7 -7
- package/client/modeller/size-drag.ts +112 -0
- package/client/modeller/slot-layout-host.ts +3 -3
- package/client/modeller/stage-model.ts +6 -9
- package/client/pages/figure-modeller-page.ts +1 -4
- package/dist-client/modeller/canvas.d.ts +24 -3
- package/dist-client/modeller/canvas.js +96 -11
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +23 -22
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +35 -35
- package/dist-client/modeller/editor.js +221 -193
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +46 -4
- package/dist-client/modeller/fasten-form.js +120 -11
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.d.ts +18 -4
- package/dist-client/modeller/figure-fasten-dialog.js +49 -12
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.d.ts +33 -1
- package/dist-client/modeller/figure-fasten-drawing.js +235 -11
- package/dist-client/modeller/figure-fasten-drawing.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/figure-section-editor.d.ts +3 -3
- package/dist-client/modeller/figure-section-editor.js +4 -4
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +18 -1
- package/dist-client/modeller/gizmo.js +19 -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 +30 -24
- package/dist-client/modeller/links.js +67 -42
- 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/modeller/section-drawing.d.ts +3 -4
- package/dist-client/modeller/section-drawing.js +10 -9
- package/dist-client/modeller/section-drawing.js.map +1 -1
- package/dist-client/modeller/size-drag.d.ts +38 -0
- package/dist-client/modeller/size-drag.js +85 -0
- package/dist-client/modeller/size-drag.js.map +1 -0
- package/dist-client/modeller/slot-layout-host.d.ts +1 -3
- package/dist-client/modeller/slot-layout-host.js +3 -3
- package/dist-client/modeller/slot-layout-host.js.map +1 -1
- package/dist-client/modeller/stage-model.d.ts +5 -7
- package/dist-client/modeller/stage-model.js +6 -6
- package/dist-client/modeller/stage-model.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +0 -2
- package/dist-client/pages/figure-modeller-page.js +1 -6
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/align.test.ts +7 -11
- package/test/browser/acceptance-page.js +11 -1
- package/test/browser/editing.check.mjs +17 -13
- package/test/browser/links-page.js +2 -2
- package/test/browser/links.check.mjs +124 -69
- package/test/browser/modeller-page.js +30 -18
- package/test/browser/modeller-revision.check.mjs +39 -0
- package/test/browser/modeller.check.mjs +77 -26
- package/test/browser/parts.check.mjs +49 -0
- package/test/browser/screen-page.js +0 -2
- package/test/browser/screen.check.mjs +11 -13
- package/test/browser/section.check.mjs +29 -2
- package/test/fasten-form.test.ts +102 -1
- package/test/gizmo.test.ts +25 -16
- package/test/keys.test.ts +11 -0
- package/test/links.test.ts +46 -36
- package/test/refusals.test.ts +17 -21
- package/test/resize-with-placement.test.ts +14 -13
- package/test/revision.test.ts +3 -2
- package/test/section-editor.test.ts +9 -10
- package/test/size-drag.test.ts +114 -0
- package/test/slot-layout-host.test.ts +13 -8
- package/translations/en.json +32 -34
- package/translations/es.json +32 -34
- package/translations/ja.json +32 -34
- package/translations/ko.json +32 -34
- package/translations/ms.json +32 -34
- package/translations/zh.json +32 -34
|
@@ -93,7 +93,7 @@ const tab = name => read(l => probe.tab(l), `figure.label.tab-short-${name}`).th
|
|
|
93
93
|
/** A fastening the part has is said in one line; 「바꾸기」 opens the fastening window on it. */
|
|
94
94
|
async function changeFastening() {
|
|
95
95
|
await until(() => look.all('button[change-fastening]').length === 1, [], 'the fastening said in one line')
|
|
96
|
-
await (
|
|
96
|
+
await press('button[change-fastening]')
|
|
97
97
|
await until(() => !!probe.fastenTargets(), [], 'the fastening window open')
|
|
98
98
|
}
|
|
99
99
|
/** The window's list: each row's part and words, whether chosen, whether it can be. Null while the window is closed. */
|
|
@@ -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')
|
|
@@ -388,22 +403,22 @@ describe('a new fastening for a door leaf: chosen in the window, seen before it
|
|
|
388
403
|
})
|
|
389
404
|
const ghosts = () => read(() => look.drawn().filter(n => n.startsWith('__preview')))
|
|
390
405
|
|
|
391
|
-
test('started, the window opens
|
|
406
|
+
test('started, the window opens in the standard popup, asking what to fasten to, nothing chosen', async () => {
|
|
392
407
|
await pick('leaf')
|
|
393
408
|
await press('button[fasten-start]')
|
|
394
409
|
const list = await until(() => probe.fastenTargets(), [], 'the fastening window')
|
|
395
410
|
assert.deepEqual(list.filter(t => t.selected), [], 'nothing chosen')
|
|
396
411
|
assert.ok(list.some(t => t.target === 'frame' && !t.disabled), 'the frame offered')
|
|
397
412
|
assert.deepEqual((await all('[apply]')).map(b => b.disabled), [true], 'nothing to apply yet')
|
|
398
|
-
/*
|
|
399
|
-
|
|
400
|
-
assert.deepEqual({ ...panel, overCanvas: undefined }, { title: 'figure.label.fastening', role: 'dialog', popup: false, overCanvas: undefined })
|
|
401
|
-
assert.ok(panel.overCanvas <= 0.1, `the canvas stays in sight: ${panel.overCanvas} of its width covered`)
|
|
413
|
+
/* The standard popup, moved by its head, with no backdrop: the canvas can be pressed while it is open (ADR-0140 stage 4). */
|
|
414
|
+
assert.deepEqual(await read(() => probe.fastenPopup()), { title: 'figure.label.fastening', movable: true, backdrop: false, role: 'dialog' })
|
|
402
415
|
})
|
|
403
416
|
|
|
404
|
-
|
|
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 () => {
|
|
405
419
|
await aimAt('frame')
|
|
406
|
-
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')
|
|
407
422
|
const told = (await heard('preview-command')).filter(Boolean).at(-1)
|
|
408
423
|
assert.equal(told?.action.kind, 'attach')
|
|
409
424
|
assert.equal((await source()).joints?.some(j => j.body1.part === 'leaf') ?? false, false, 'nothing written')
|
|
@@ -424,15 +439,15 @@ describe('a new fastening for a door leaf: chosen in the window, seen before it
|
|
|
424
439
|
assert.deepEqual(await ghosts(), [], 'nothing of the earlier result drawn')
|
|
425
440
|
})
|
|
426
441
|
|
|
427
|
-
test('another
|
|
442
|
+
test('another tab pressed closes the window with nothing written, and nothing it tried is drawn', async () => {
|
|
428
443
|
const start = await state()
|
|
429
|
-
/* No backdrop
|
|
430
|
-
|
|
431
|
-
await pick('frame')
|
|
444
|
+
/* No backdrop: the press reaches the tab, and the window, which is the properties tab's, closes. */
|
|
445
|
+
await click(await read(l => probe.tab(l), 'figure.label.tab-short-motion'), 'the motion tab')
|
|
432
446
|
await until(() => !probe.fastenTargets(), [], 'the window closed')
|
|
433
447
|
await until(() => !look.drawn().some(n => n.startsWith('__preview')), [], 'nothing it tried drawn')
|
|
448
|
+
assert.equal(await read(() => look.tab()), 'figure.label.tab-short-motion', 'the press reached the tab')
|
|
434
449
|
assert.equal((await state()).revision, start.revision, 'nothing written')
|
|
435
|
-
await
|
|
450
|
+
await tab('properties')
|
|
436
451
|
})
|
|
437
452
|
|
|
438
453
|
test('"fasten it first" from the motion tab opens this part\'s window, on what it has, on the properties tab', async () => {
|
|
@@ -445,7 +460,7 @@ describe('a new fastening for a door leaf: chosen in the window, seen before it
|
|
|
445
460
|
|
|
446
461
|
test('Esc closes the window and tells the editor to clear what it tried; the leaf stays selected', async () => {
|
|
447
462
|
await aimAt('frame')
|
|
448
|
-
await until(() => look.drawn().includes('
|
|
463
|
+
await until(() => look.drawn().includes('__target-face'), [], 'the face it would meet drawn')
|
|
449
464
|
await read(() => look.hear('preview-command', 'dialog'))
|
|
450
465
|
await page().keyboard.press('Escape')
|
|
451
466
|
await until(() => !probe.fastenTargets(), [], 'the window closed')
|
|
@@ -461,9 +476,9 @@ describe('a new fastening for a door leaf: chosen in the window, seen before it
|
|
|
461
476
|
await aimAt('frame')
|
|
462
477
|
/*
|
|
463
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
|
|
464
|
-
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.
|
|
465
480
|
*/
|
|
466
|
-
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')
|
|
467
482
|
await settled()
|
|
468
483
|
await sleep(200)
|
|
469
484
|
await click(await at('path[face="front"]'), 'the front face in the drawing')
|
|
@@ -490,13 +505,13 @@ async function pressFixAlignment() {
|
|
|
490
505
|
describe('past the edge of the face a part is fastened to', () => {
|
|
491
506
|
before(async () => {
|
|
492
507
|
let a = fasten(box(createFigureAsset('Overhangs'), 'frame', [1000, 100, 400], [0, 50, 0]), 'frame', 'plane')
|
|
493
|
-
/* Pushed 30 mm past the frame's right edge; a mesh turned on it, its box past the edge; on a drum's round end; wider than the frame, off to one side. */
|
|
508
|
+
/* Pushed 30 mm past the frame's right edge; a mesh turned on it, its box past the edge; on a drum's round end, within its square and past its rim; wider than the frame, off to one side. */
|
|
494
509
|
a = fasten(box(a, 'out', [100, 100, 100]), 'out', 'frame', { align: { x: { mm: 480 } } })
|
|
495
510
|
const mesh = { asset: 'a'.repeat(64), format: 'glb', unit: 'mm', bounds: { min: { x: -50, y: -50, z: -50 }, max: { x: 50, y: 50, z: 50 } }, triangles: 12, primitives: 1 }
|
|
496
511
|
a = addFigurePrimitive(a, { id: 'tilted', kind: 'mesh', dimensions: { size: 100 }, mesh, position: { x: 0, y: 0, z: 0 } })
|
|
497
512
|
a = applyFigureAuthoring(a, { kind: 'set-joint', joint: { id: 'tilted', type: 'fixed', body0: { part: 'frame', face: 'top' }, body1: { part: 'tilted' }, origin: { align: { x: { mm: 460 } }, childRotate: { y: 30 } } } })
|
|
498
513
|
a = fasten(addFigurePrimitive(a, { id: 'drum', kind: 'cylinder', dimensions: { radius: 100, length: 200 }, position: { x: 0, y: 0, z: 0 } }), 'drum', 'frame')
|
|
499
|
-
a = fasten(box(a, 'cap', [50, 50, 50]), 'cap', 'drum')
|
|
514
|
+
a = fasten(box(a, 'cap', [50, 50, 50]), 'cap', 'drum', { align: { x: { mm: 70 }, z: { mm: 70 } } })
|
|
500
515
|
await mount('overhangs', fasten(box(a, 'plate', [1600, 20, 200]), 'plate', 'frame', { align: { x: { mm: 200 } } }))
|
|
501
516
|
})
|
|
502
517
|
const overhang = () => read(() => look.all('div[overhang] p').map(p => p.text))
|
|
@@ -524,13 +539,13 @@ describe('past the edge of the face a part is fastened to', () => {
|
|
|
524
539
|
await until(() => look.all('div[overhang] p', 'figure.text.overhang-possible').length === 1, [], 'said it may be')
|
|
525
540
|
})
|
|
526
541
|
|
|
527
|
-
test('on a round face
|
|
542
|
+
test('on a round face, measured on the disc itself: past its rim though within its square, and "fix alignment" brings it in (ADR-0140 3.9)', async () => {
|
|
528
543
|
await pick('cap')
|
|
529
|
-
await until(() => look.all('div[overhang] p', 'figure.text.overhang-
|
|
544
|
+
await until(() => look.all('div[overhang] p', 'figure.text.overhang-out').length === 1, [], 'said past the rim')
|
|
530
545
|
const start = await state()
|
|
531
546
|
await pressFixAlignment()
|
|
532
|
-
await until(
|
|
533
|
-
|
|
547
|
+
await until(r => probe.state().revision === r + 1, [start.revision], 'one step')
|
|
548
|
+
await until(() => look.all('div[overhang] p', 'figure.text.overhang-out').length === 0, [], 'within the rim')
|
|
534
549
|
})
|
|
535
550
|
|
|
536
551
|
test('wider than the face: said in the relation with which side, not as a warning', async () => {
|
|
@@ -626,41 +641,32 @@ function twistedFollowingSize() {
|
|
|
626
641
|
return applyFigureAuthoring(twisted(), { kind: 'follow-size', part: 'blade' })
|
|
627
642
|
}
|
|
628
643
|
|
|
629
|
-
|
|
630
|
-
|
|
644
|
+
/*
|
|
645
|
+
ADR-0140 4.1 · 4.4: a part fastened at any angle is sized by a drag too -- its size alone is written, and its fastening
|
|
646
|
+
keeps it. Before, the drag was withheld and a notice sent the author to the size fields.
|
|
647
|
+
*/
|
|
648
|
+
describe('a part fastened twisted is sized by a drag: its size alone, its fastening keeps it', () => {
|
|
649
|
+
test('the resize tool is on, and a size drag writes its size in one step, the fastening as it was, with nothing said', async () => {
|
|
631
650
|
await mount('twisted', twisted())
|
|
632
651
|
await pick('blade')
|
|
633
|
-
assert.equal((await at('button[tool]', 'figure.button.gizmo-scale'))?.disabled,
|
|
652
|
+
assert.equal((await at('button[tool]', 'figure.button.gizmo-scale'))?.disabled, false, 'the resize tool is on')
|
|
634
653
|
await read(() => probe.told())
|
|
635
654
|
const start = await state()
|
|
636
|
-
await read(() =>
|
|
637
|
-
await
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
assert.deepEqual(await read(() => probe.
|
|
641
|
-
assert.
|
|
642
|
-
})
|
|
643
|
-
|
|
644
|
-
test('the R key says why, and the notice\'s button puts the cursor in the part\'s size field', async () => {
|
|
645
|
-
await mount('twisted', twisted())
|
|
646
|
-
await pick('blade')
|
|
647
|
-
await read(() => probe.told())
|
|
648
|
-
await page().keyboard.press('KeyR')
|
|
649
|
-
await until(() => probe.told().some(n => n.message === 'figure.text.gizmo-size-turned-fastening' && n.action === 'figure.button.to-size-fields'), [], 'the reason said with its way on')
|
|
655
|
+
const joint = await read(() => probe.joint('blade'))
|
|
656
|
+
await read(() => look.transformed('blade', { scale: [1.5, 1, 1], grab: { x: 1, y: 0, z: 0 } }))
|
|
657
|
+
await until(r => probe.state().revision === r + 1, [start.revision], 'one step')
|
|
658
|
+
assert.equal(await read(() => probe.value('blade.size.x')), 450, 'its size')
|
|
659
|
+
assert.deepEqual(await read(() => probe.joint('blade')), joint, 'its fastening as it was')
|
|
660
|
+
assert.deepEqual(await read(() => probe.told()), [], 'nothing to say')
|
|
650
661
|
assert.equal((await state()).error, '', 'no error')
|
|
651
|
-
/* From another tab: the button brings the properties tab and the size field. */
|
|
652
|
-
await tab('motion')
|
|
653
|
-
assert.equal(await read(() => probe.pressNoticeButton()), true)
|
|
654
|
-
await until(() => probe.focused()?.label?.startsWith('blade.size.'), [], 'the blade\'s size field focused')
|
|
655
|
-
assert.equal(await read(() => look.tab()), 'figure.label.tab-short-properties')
|
|
656
662
|
})
|
|
657
663
|
|
|
658
664
|
/*
|
|
659
|
-
:3300, 2026-10-19 (the fan's blade-2): a part whose size is not its own to type
|
|
660
|
-
|
|
665
|
+
:3300, 2026-10-19 (the fan's blade-2): a part whose size is not its own to type has the resize tool off; R says the
|
|
666
|
+
tool's own words, and puts no gizmo on it.
|
|
661
667
|
*/
|
|
662
668
|
for (const [label, make] of [['another part', 'twistedFollowing'], ['the figure\'s size', 'twistedFollowingSize']])
|
|
663
|
-
test(`a part whose size follows ${label} is not
|
|
669
|
+
test(`a part whose size follows ${label} is not sent to size fields it cannot type in`, async () => {
|
|
664
670
|
await mount(`twisted-${make}`, make === 'twistedFollowing' ? twistedFollowing() : twistedFollowingSize())
|
|
665
671
|
await pick('blade')
|
|
666
672
|
await read(() => probe.told())
|
|
@@ -668,12 +674,10 @@ describe('a part fastened twisted is not sized by a drag', () => {
|
|
|
668
674
|
await sleep(400)
|
|
669
675
|
const told = await read(() => probe.told())
|
|
670
676
|
assert.deepEqual(told.filter(n => n.action), [], `no way to size fields: ${JSON.stringify(told)}`)
|
|
671
|
-
assert.ok(!told.some(n => n.message === 'figure.text.gizmo-size-turned-fastening'), 'not said to be attached turned')
|
|
672
677
|
/* R does what the bar's resize tool does, off for this part: no gizmo it cannot use, and the tool's own words (:3300, 10.1.196). */
|
|
673
678
|
assert.equal((await state()).attached, null, 'no gizmo on it')
|
|
674
679
|
assert.deepEqual(told, [{ level: 'info', message: 'figure.text.gizmo-not-own', action: null }])
|
|
675
680
|
})
|
|
676
|
-
|
|
677
681
|
})
|
|
678
682
|
|
|
679
683
|
describe('a box made to follow the figure size on the floor', () => {
|
|
@@ -684,14 +688,14 @@ describe('a box made to follow the figure size on the floor', () => {
|
|
|
684
688
|
await pick('box')
|
|
685
689
|
})
|
|
686
690
|
|
|
687
|
-
test('its height is the room\'s only, shown apart; its row shows the number the floor gives; a size drag
|
|
691
|
+
test('its height is the room\'s only, shown apart; its row shows the number the floor gives; a size drag is drawn by the modeller', async () => {
|
|
688
692
|
const roomOnly = await all('input[aria-label="box.position.y"]')
|
|
689
693
|
assert.equal(roomOnly.length, 1, 'one field for it, apart from the position row')
|
|
690
694
|
assert.equal(roomOnly[0].disabled, true, 'not typed into')
|
|
691
695
|
assert.equal((await all('section[room-only] input[aria-label="box.position.y"]')).length, 1, 'in the room\'s section')
|
|
692
696
|
const held = (await all('div[vector-row] input[readonly]')).filter(i => i.label?.startsWith('position.y'))
|
|
693
697
|
assert.deepEqual(held.map(i => i.value), ['50'], 'the height the floor gives, read-only')
|
|
694
|
-
assert.
|
|
698
|
+
assert.equal(await read(() => look.scalePlacement('box')), 'owner', 'its place in a size drag is the modeller\'s (ADR-0140 4.4)')
|
|
695
699
|
})
|
|
696
700
|
|
|
697
701
|
test('the room is declared again only when the author confirms', async () => {
|
|
@@ -798,7 +802,7 @@ describe('the fastening window: tried freely, written once', () => {
|
|
|
798
802
|
await until(w => JSON.stringify(JSON.parse(document.querySelector('figure-editor').session.source()).joints.find(j => j.body1.part === 'lamp')) === w, [JSON.stringify(was)], 'one undo: the fastening as it was')
|
|
799
803
|
})
|
|
800
804
|
|
|
801
|
-
test('the
|
|
805
|
+
test('moved by the popup\'s head out of the way of the canvas: nothing written, still open', async () => {
|
|
802
806
|
await pick('lamp')
|
|
803
807
|
await changeFastening()
|
|
804
808
|
const rect = () => read(() => {
|
|
@@ -806,21 +810,21 @@ describe('the fastening window: tried freely, written once', () => {
|
|
|
806
810
|
return r ? { x: Math.round(r.left), y: Math.round(r.top) } : null
|
|
807
811
|
})
|
|
808
812
|
const was = await rect()
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
return { x: r.left + 40, y: r.top + r.height / 2 }
|
|
813
|
+
const head = await read(() => {
|
|
814
|
+
const h = window.fastenDialog().closest('ox-floating-overlay').shadowRoot.querySelector('[header] h1').getBoundingClientRect()
|
|
815
|
+
return { x: h.left + h.width / 2, y: h.top + h.height / 2 }
|
|
813
816
|
})
|
|
814
817
|
const start = await state()
|
|
815
|
-
await page().mouse.move(
|
|
818
|
+
await page().mouse.move(head.x, head.y)
|
|
816
819
|
await page().mouse.down()
|
|
817
|
-
for (let i = 1; i <= 6; i++) await page().mouse.move(
|
|
820
|
+
for (let i = 1; i <= 6; i++) await page().mouse.move(head.x - 30 * i, head.y + 20 * i)
|
|
818
821
|
await page().mouse.up()
|
|
819
|
-
await
|
|
822
|
+
const moved = await rect()
|
|
823
|
+
assert.deepEqual([moved.x - was.x, moved.y - was.y], [-180, 120], `moved with the pointer: ${JSON.stringify([was, moved])}`)
|
|
820
824
|
assert.equal(await windowOpen(), true, 'still open')
|
|
821
|
-
assert.deepEqual(await rect(), was, 'the window where it was')
|
|
822
825
|
assert.equal((await state()).revision, start.revision, 'nothing written')
|
|
823
|
-
|
|
826
|
+
/* Moved down, its foot may be past the page's: closed by Esc, as the window takes it. */
|
|
827
|
+
await page().keyboard.press('Escape')
|
|
824
828
|
await until(() => !probe.fastenTargets(), [], 'the window closed')
|
|
825
829
|
})
|
|
826
830
|
|
|
@@ -857,6 +861,57 @@ describe('a motion on the motion tab', () => {
|
|
|
857
861
|
})
|
|
858
862
|
})
|
|
859
863
|
|
|
864
|
+
describe('pressed on the canvas while the fastening window is open (ADR-0140 stage 4)', () => {
|
|
865
|
+
before(async () => {
|
|
866
|
+
/* A drum standing at (0, 600, 0), radius 100; a tag on the floor, its fastening to change. */
|
|
867
|
+
let a = addFigurePrimitive(createFigureAsset('Pressed'), { id: 'drum', kind: 'cylinder', dimensions: { radius: 100, length: 400 }, position: { x: 0, y: 600, z: 0 } })
|
|
868
|
+
a = fasten(box(a, 'tag', [40, 40, 40], [600, 20, 600]), 'tag', 'plane')
|
|
869
|
+
await mount('pressed', a)
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
test("the drum's side pressed: the window aims at the point pressed, and the tag stays chosen", async () => {
|
|
873
|
+
await pick('tag')
|
|
874
|
+
await changeFastening()
|
|
875
|
+
/* The scene's click, as a press on the drum's side facing +Z, 50 mm above its middle, tells it (`pickedAt`). */
|
|
876
|
+
const said = await read(async () => {
|
|
877
|
+
const deep = (s, root = document) => [...root.querySelectorAll(s)].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(s, e.shadowRoot)))
|
|
878
|
+
const canvas = deep('figure-canvas')[0]
|
|
879
|
+
const layer = canvas.scene.root
|
|
880
|
+
const drum = layer.components.find(c => c.state.name === 'drum')
|
|
881
|
+
drum.realObject.pickedAt = { point: [0, 650, 100], normal: [0, 0, 1] }
|
|
882
|
+
/* As the scene's click does: told on the component, it comes up to the board with the component as its origin. */
|
|
883
|
+
drum.trigger('click', new MouseEvent('click'))
|
|
884
|
+
await new Promise(done => setTimeout(done, 300))
|
|
885
|
+
const dialog = deep('figure-fasten-dialog')[0]
|
|
886
|
+
await dialog.updateComplete
|
|
887
|
+
return { target: dialog.form.target, surface: dialog.form.surface, selected: deep('figure-editor')[0].selected }
|
|
888
|
+
})
|
|
889
|
+
assert.deepEqual(said, { target: 'drum', surface: { around: 270, along: { mm: 50 } }, selected: 'tag' })
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
test('the window open, the drum pressed with the mouse on the canvas: aimed at, the window still open', async () => {
|
|
893
|
+
await press('[cancel]')
|
|
894
|
+
await until(() => !probe.fastenTargets(), [], 'the window closed')
|
|
895
|
+
await changeFastening()
|
|
896
|
+
/* Moved by its head off the drum, as a hand does, then the drum pressed where it is drawn. */
|
|
897
|
+
const head = await read(() => {
|
|
898
|
+
const h = window.fastenDialog().closest('ox-floating-overlay').shadowRoot.querySelector('[header] h1').getBoundingClientRect()
|
|
899
|
+
return { x: h.left + h.width / 2, y: h.top + h.height / 2 }
|
|
900
|
+
})
|
|
901
|
+
await page().mouse.move(head.x, head.y)
|
|
902
|
+
await page().mouse.down()
|
|
903
|
+
for (let i = 1; i <= 10; i++) await page().mouse.move(head.x + 35 * i, head.y)
|
|
904
|
+
await page().mouse.up()
|
|
905
|
+
await settled()
|
|
906
|
+
const drum = await read(() => probe.partOnPage('drum'))
|
|
907
|
+
assert.ok(await read(p => { const r = window.fastenDialog().getBoundingClientRect(); return p.x < r.left || p.x > r.right || p.y < r.top || p.y > r.bottom }, drum), 'the drum clear of the window')
|
|
908
|
+
await click(drum, 'the drum on the canvas')
|
|
909
|
+
await until(() => probe.fastenTargets()?.some(t => t.target === 'drum' && t.selected), [], 'the drum aimed at in the window')
|
|
910
|
+
assert.equal(await windowOpen(), true, 'still open: no backdrop took the press')
|
|
911
|
+
assert.equal((await state()).selected, 'tag', 'the tag still chosen')
|
|
912
|
+
})
|
|
913
|
+
})
|
|
914
|
+
|
|
860
915
|
test('the page raised no error', () => {
|
|
861
916
|
assert.deepEqual(opened.errors, [])
|
|
862
917
|
})
|
|
@@ -62,6 +62,15 @@ function sourceOf(kind, text) {
|
|
|
62
62
|
asset.palette = { ...asset.palette, ...PALETTE }
|
|
63
63
|
return serializeFigureAsset(addFigureBox(asset, { id: 'room', size: { x: 4000, y: 3000, z: 6000 }, position: { x: 0, y: 1500, z: 0 } }))
|
|
64
64
|
}
|
|
65
|
+
if (kind === 'shelf') {
|
|
66
|
+
/* A bed on the floor and a slat on its top: a part that can be made a row (the fold checks). */
|
|
67
|
+
let asset = createFigureAsset('Shelf')
|
|
68
|
+
asset.palette = { ...asset.palette, ...PALETTE }
|
|
69
|
+
asset = addFigureBox(asset, { id: 'bed', size: { x: 2000, y: 400, z: 600 }, position: { x: 0, y: 200, z: 0 } })
|
|
70
|
+
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'bed', to: { plane: 'mounting-plane' } })
|
|
71
|
+
asset = addFigureBox(asset, { id: 'slat', size: { x: 60, y: 20, z: 600 }, position: { x: 0, y: 410, z: 0 } })
|
|
72
|
+
return serializeFigureAsset(applyFigureAuthoring(asset, { kind: 'attach', part: 'slat', to: { part: 'bed', face: 'top' } }))
|
|
73
|
+
}
|
|
65
74
|
if (kind === 'rollers') {
|
|
66
75
|
/* A roller row whose rollers spin in place, each carrying a mark (ADR-0093, 2026-09-26). */
|
|
67
76
|
let asset = createFigureAsset('Rollers')
|
|
@@ -71,7 +80,8 @@ function sourceOf(kind, text) {
|
|
|
71
80
|
asset = addFigurePrimitive(asset, { id: 'roller', kind: 'cylinder', dimensions: { radius: 40, length: 560 }, position: { x: 0, y: 0, z: 0 }, rotation: { x: 90, y: 0, z: 0 } })
|
|
72
81
|
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'roller', facing: 'centre', to: { part: 'bed', face: 'top' }, gap: 40 })
|
|
73
82
|
asset = addFigureBox(asset, { id: 'mark', size: { x: 30, y: 30, z: 560 }, position: { x: 0, y: 0, z: 0 } })
|
|
74
|
-
|
|
83
|
+
/* On the roller's side at 0° round its own Y -- its own +X (ADR-0140 3.9). */
|
|
84
|
+
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'mark', to: { part: 'roller', surface: { around: 0, along: 'centre' } } })
|
|
75
85
|
const j = asset.joints.find(o => o.body1.part === 'roller')
|
|
76
86
|
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: { ...j, type: 'revolute', axis: 'Z', lowerLimit: -360, upperLimit: 360, state: { id: 'spin' } } })
|
|
77
87
|
return serializeFigureAsset(applyFigureAuthoring(asset, { kind: 'make-repeat', part: 'roller', axis: 'x', pitch: 200 }))
|
|
@@ -232,23 +242,10 @@ window.probe = {
|
|
|
232
242
|
const chip = deep('figure-fasten-dialog')[0]?.shadowRoot.querySelector('ox-input-select-buttons')?.shadowRoot?.querySelector(`md-filter-chip[data-value="${CSS.escape(part)}"]`)
|
|
233
243
|
return chip ? centreOf(chip) : null
|
|
234
244
|
},
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
fastenPanel() {
|
|
240
|
-
const dialog = deep('figure-fasten-dialog')[0]
|
|
241
|
-
const panel = dialog?.closest('[fasten-panel]')
|
|
242
|
-
if (!panel) return null
|
|
243
|
-
const p = panel.getBoundingClientRect(),
|
|
244
|
-
c = canvas().getBoundingClientRect()
|
|
245
|
-
const across = Math.max(0, Math.min(p.right, c.right) - Math.max(p.left, c.left))
|
|
246
|
-
return {
|
|
247
|
-
title: panel.querySelector('h3')?.textContent.trim(),
|
|
248
|
-
role: dialog.getAttribute('role'),
|
|
249
|
-
popup: deep('ox-floating-overlay').length > 0,
|
|
250
|
-
overCanvas: Math.round((across / c.width) * 100) / 100
|
|
251
|
-
}
|
|
245
|
+
/** The fastening window's popup, as the standard overlay holds it: its title, whether its head moves it, its backdrop. */
|
|
246
|
+
fastenPopup() {
|
|
247
|
+
const overlay = deep('figure-fasten-dialog')[0]?.closest('ox-floating-overlay')
|
|
248
|
+
return overlay ? { title: overlay.title, movable: overlay.movable, backdrop: !!overlay.backdrop, role: overlay.querySelector('figure-fasten-dialog').getAttribute('role') } : null
|
|
252
249
|
},
|
|
253
250
|
/** What the notification surface was handed since the last call: its level, its words, its button's label. */
|
|
254
251
|
told() {
|
|
@@ -380,6 +377,21 @@ window.probe = {
|
|
|
380
377
|
const b = deep('button[mode]').find(b => b.title === `figure.text.mode-${mode}-explained`)
|
|
381
378
|
return b ? centreOf(b) : null
|
|
382
379
|
},
|
|
380
|
+
/**
|
|
381
|
+
* A size drag as the scene tells it (ADR-0140 4.4): a frame of it -- the part grown by `scale` along its own axes, the
|
|
382
|
+
* handle `grab` held -- or its release. The scene's own gizmo is not driven: what the canvas tells the editor is.
|
|
383
|
+
*/
|
|
384
|
+
sizeFrame(id, scale, grab) {
|
|
385
|
+
canvas().dispatchEvent(new CustomEvent('part-sizing', { detail: { id, result: { scale, grab } }, bubbles: true, composed: true }))
|
|
386
|
+
},
|
|
387
|
+
sizeRelease(id, scale, grab) {
|
|
388
|
+
canvas().dispatchEvent(new CustomEvent('part-transformed', { detail: { id, result: { scale, grab } }, bubbles: true, composed: true }))
|
|
389
|
+
},
|
|
390
|
+
/** Where the canvas is told to draw a part now: its pose as given to the canvas. */
|
|
391
|
+
drawnPose(id) {
|
|
392
|
+
const part = canvas().parts.find(p => p.id === id)
|
|
393
|
+
return part ? { t: [...part.pose.t], r: part.pose.r.map(row => [...row]) } : null
|
|
394
|
+
},
|
|
383
395
|
/** The drawn position of a part's component, as the canvas shows it now. */
|
|
384
396
|
drawn(id) {
|
|
385
397
|
const c = canvas().scene.root.components.find(c => c.state.name === id)
|
|
@@ -109,6 +109,45 @@ describe('work counted on the robot arm', () => {
|
|
|
109
109
|
})
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
+
/*
|
|
113
|
+
ADR-0140 4.4: a size drag is drawn from figure-model frame by frame -- the figure compiled once when the drag begins, the
|
|
114
|
+
instance evaluator stepped each frame -- and the release writes what the last frame drew: no position differs, and the
|
|
115
|
+
release compiles no more than a release with no frames before it.
|
|
116
|
+
*/
|
|
117
|
+
describe('a size drag drawn frame by frame on the robot arm', () => {
|
|
118
|
+
const grab = { x: 0, y: 1, z: 0 }
|
|
119
|
+
const far = (a, b) => Math.max(...a.t.map((v, i) => Math.abs(v - b.t[i])), ...a.r.flat().map((v, i) => Math.abs(v - b.r.flat()[i])))
|
|
120
|
+
|
|
121
|
+
test('each frame compiles no figure, and moves what hangs from the part', async () => {
|
|
122
|
+
await mount('arm')
|
|
123
|
+
await click(await read(id => probe.partOnPage(id), 'upper-arm'))
|
|
124
|
+
await settled()
|
|
125
|
+
const still = await read(() => probe.drawnPose('forearm'))
|
|
126
|
+
await read(() => probe.sizeFrame('upper-arm', [1, 1.1, 1], { x: 0, y: 1, z: 0 }))
|
|
127
|
+
await drawn()
|
|
128
|
+
for (const s of [1.2, 1.3]) {
|
|
129
|
+
const { spent } = await counted(() => read(s => probe.sizeFrame('upper-arm', [1, s, 1], { x: 0, y: 1, z: 0 }), s))
|
|
130
|
+
assert.equal(spent.compiles, 0, `${s}: ${JSON.stringify(spent)}`)
|
|
131
|
+
}
|
|
132
|
+
assert.ok((await read(() => probe.drawnPose('forearm'))).t[1] > still.t[1] + 1, 'the forearm rides up with it')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('the release writes what the last frame drew: no position differs', async () => {
|
|
136
|
+
const before = await read(() => probe.drawnPose('forearm'))
|
|
137
|
+
const revision = (await read(() => probe.state())).revision
|
|
138
|
+
const { spent } = await counted(() => read(g => probe.sizeRelease('upper-arm', [1, 1.3, 1], g), grab))
|
|
139
|
+
assert.equal((await read(() => probe.state())).revision, revision + 1, 'one step')
|
|
140
|
+
const written = await read(() => probe.body('forearm'))
|
|
141
|
+
assert.ok(far(before, written) < 1e-6, `the forearm where the last frame drew it: ${far(before, written)}`)
|
|
142
|
+
/* Released with no frames before it, on the same figure: the release costs the same or more. */
|
|
143
|
+
await mount('arm')
|
|
144
|
+
await click(await read(id => probe.partOnPage(id), 'upper-arm'))
|
|
145
|
+
await settled()
|
|
146
|
+
const bare = await counted(() => read(g => probe.sizeRelease('upper-arm', [1, 1.3, 1], g), grab))
|
|
147
|
+
assert.ok(spent.compiles <= bare.spent.compiles, `released after frames ${JSON.stringify(spent)}, without ${JSON.stringify(bare.spent)}`)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
112
151
|
describe('the edit canvas and the preview stay standing', () => {
|
|
113
152
|
before(() => mount('box'))
|
|
114
153
|
|