@things-factory/figure-ui 10.1.210 → 10.1.211
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.ts +24 -32
- package/client/modeller/figure-links.ts +61 -37
- package/client/modeller/figure-size-editor.ts +41 -4
- package/client/modeller/links.ts +61 -22
- package/client/modeller/live.ts +33 -0
- package/dist-client/modeller/editor.d.ts +5 -0
- package/dist-client/modeller/editor.js +24 -32
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +2 -0
- package/dist-client/modeller/figure-links.js +66 -39
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-size-editor.d.ts +3 -1
- package/dist-client/modeller/figure-size-editor.js +45 -4
- package/dist-client/modeller/figure-size-editor.js.map +1 -1
- package/dist-client/modeller/links.d.ts +14 -5
- package/dist-client/modeller/links.js +61 -23
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/live.d.ts +14 -0
- package/dist-client/modeller/live.js +31 -0
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/test/browser/editing.check.mjs +38 -0
- package/test/browser/modeller.check.mjs +55 -0
- package/test/links.test.ts +39 -0
- package/translations/en.json +3 -0
- package/translations/es.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
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.211",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"@things-factory/setting-base": "^10.1.208",
|
|
49
49
|
"three": "^0.185.1"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "1b781d2bbb963380eda73ab7020091987393de37"
|
|
52
52
|
}
|
|
@@ -374,6 +374,44 @@ describe('values in hand on the properties tab: drawn at once, written on change
|
|
|
374
374
|
})
|
|
375
375
|
})
|
|
376
376
|
|
|
377
|
+
/* A size of the part's own on the 「배치」 tab's size editor is a value in hand as any field is (user 2026-09-24). */
|
|
378
|
+
describe('a size in hand on the arrangement tab: drawn while typed or dragged, written once', () => {
|
|
379
|
+
const WIDTH = 'box X figure.label.size-measure'
|
|
380
|
+
const wide = async () => Math.round((await look('box')).size[0])
|
|
381
|
+
let was
|
|
382
|
+
before(async () => {
|
|
383
|
+
await mount('box')
|
|
384
|
+
await pick('box')
|
|
385
|
+
await click(await read(() => probe.tab('figure.label.tab-arrangement')))
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
test('a width typed is drawn while it is typed, and written once on Enter', async () => {
|
|
389
|
+
was = await wide()
|
|
390
|
+
const start = await revision()
|
|
391
|
+
await type(WIDTH, '700')
|
|
392
|
+
assert.equal(await wide(), 700, 'drawn 700 wide while typed')
|
|
393
|
+
assert.equal(await revision(), start, 'nothing written yet')
|
|
394
|
+
await page().keyboard.press('Enter')
|
|
395
|
+
await sleep(400)
|
|
396
|
+
assert.equal(await revision(), start + 1)
|
|
397
|
+
assert.equal(await read(() => probe.value('box.size.x')), 700)
|
|
398
|
+
await press(...UNDO)
|
|
399
|
+
assert.equal(await wide(), was, 'undone, drawn as it stands')
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
test('its label dragged sideways scrubs the width: drawn while held, one step on release', async () => {
|
|
403
|
+
const start = await revision()
|
|
404
|
+
const label = await read(id => editing.fieldLabel(id), WIDTH)
|
|
405
|
+
assert.ok(label, 'the X of the size editor')
|
|
406
|
+
const held = await drag(label, 60, async () => ({ drawn: await wide(), shown: await read(id => editing.fieldValue(id), WIDTH), revision: await revision() }))
|
|
407
|
+
assert.notEqual(held.drawn, was, 'drawn while held')
|
|
408
|
+
assert.equal(String(held.drawn), held.shown, 'the field follows what is drawn')
|
|
409
|
+
assert.equal(held.revision, start, 'nothing written while held')
|
|
410
|
+
assert.equal(await revision(), start + 1, 'the release is one step')
|
|
411
|
+
assert.equal(await read(() => probe.value('box.size.x')), held.drawn)
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
|
|
377
415
|
describe('the pose sliders: drawn while dragged, one step on release; the pose kept as the default (user 2026-09-24)', () => {
|
|
378
416
|
before(async () => {
|
|
379
417
|
await mount('six-axis')
|
|
@@ -630,6 +630,61 @@ describe('a motion is changed where it is (2026-09-25)', () => {
|
|
|
630
630
|
assert.equal(now.motion[0].upperLimit, 45, 'the new limit')
|
|
631
631
|
assert.equal(now.id, was.id, 'the same joint')
|
|
632
632
|
})
|
|
633
|
+
|
|
634
|
+
/* A joint is a list of axes (ADR-0159): one more is added where the motion is said, and taken away again. */
|
|
635
|
+
const jointOf = part => read(part => (JSON.parse(document.querySelector('figure-editor').session.source()).joints ?? []).find(j => j.body1.part === part) ?? null, part)
|
|
636
|
+
const button = async (text, attribute) =>
|
|
637
|
+
page().evaluate(
|
|
638
|
+
(text, attribute) => {
|
|
639
|
+
const deep = (root = document) => [...root.querySelectorAll('button')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
640
|
+
const b = deep().find(x => x.textContent.includes(text) && (!attribute || x.hasAttribute(attribute)))
|
|
641
|
+
if (!b) return null
|
|
642
|
+
b.scrollIntoView({ block: 'center' })
|
|
643
|
+
const r = b.getBoundingClientRect()
|
|
644
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
|
|
645
|
+
},
|
|
646
|
+
text,
|
|
647
|
+
attribute
|
|
648
|
+
)
|
|
649
|
+
const rows = () =>
|
|
650
|
+
page().evaluate(() => {
|
|
651
|
+
const deep = (root = document) => [...root.querySelectorAll('div[motion-axis]')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
652
|
+
return deep().length
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
test('an axis added to the shoulder: one step, after the first, on an axis the first does not use, with a control of its own', async () => {
|
|
656
|
+
const was = await jointOf('upper-arm')
|
|
657
|
+
await click(await button('figure.button.add-axis', 'add-axis'), 'add an axis')
|
|
658
|
+
const start = await state()
|
|
659
|
+
await click(await button('figure.button.add-axis'), 'the form\'s add-axis')
|
|
660
|
+
const after = await state()
|
|
661
|
+
assert.equal(after.error, '', 'not refused')
|
|
662
|
+
assert.equal(after.revision, start.revision + 1, 'one step')
|
|
663
|
+
const now = await jointOf('upper-arm')
|
|
664
|
+
assert.equal(now.motion.length, 2)
|
|
665
|
+
assert.deepEqual(now.motion[0], was.motion[0], 'the first axis as it was')
|
|
666
|
+
assert.notEqual(now.motion[1].axis, now.motion[0].axis, 'another axis')
|
|
667
|
+
assert.equal(now.motion[1].state.id, 'upper-arm-turn-2')
|
|
668
|
+
assert.equal(await rows(), 2, 'a row an axis')
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
test('the second axis taken away: one step, the first as it was', async () => {
|
|
672
|
+
const was = await jointOf('upper-arm')
|
|
673
|
+
const start = await state()
|
|
674
|
+
const removes = await page().evaluate(() => {
|
|
675
|
+
const deep = (root = document) => [...root.querySelectorAll('button[remove-axis]')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
676
|
+
const b = deep()[1]
|
|
677
|
+
if (!b) return null
|
|
678
|
+
b.scrollIntoView({ block: 'center' })
|
|
679
|
+
const r = b.getBoundingClientRect()
|
|
680
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
|
|
681
|
+
})
|
|
682
|
+
await click(removes, 'remove the second axis')
|
|
683
|
+
assert.equal((await state()).revision, start.revision + 1, 'one step')
|
|
684
|
+
const now = await jointOf('upper-arm')
|
|
685
|
+
assert.deepEqual(now.motion, [was.motion[0]], 'the first axis alone, as it was')
|
|
686
|
+
assert.equal(await rows(), 1)
|
|
687
|
+
})
|
|
633
688
|
})
|
|
634
689
|
|
|
635
690
|
describe('the modeller made easier to find and to keep (2026-09-26 baseline)', () => {
|
package/test/links.test.ts
CHANGED
|
@@ -703,3 +703,42 @@ test('a follower of several leaders and a stroke that is a sum of lengths are wr
|
|
|
703
703
|
assert.equal(slide.travel, '')
|
|
704
704
|
assert.deepEqual(motionJoint(reach, slide, 'wrist').motion![0]!.travel, reach.motion[0]!.travel, 'saving keeps the summed stroke')
|
|
705
705
|
})
|
|
706
|
+
|
|
707
|
+
test('어깨는 축 둘 — 축 추가는 쓰지 않은 축으로 시작하고, 고쳐도 다른 축은 그대로, 같은 관절의 다른 축을 따를 수 있고, 축을 빼도 남은 축의 조작값 이름은 그대로 (ADR-0159)', async () => {
|
|
708
|
+
const { leadersFor, motionJoint, motionFormOf, motionOf, withoutAxis, controlIdFor } = await import('../client/modeller/links.ts')
|
|
709
|
+
const { compileFigureAsset, motionIdOf } = await import('@hatiolab/figure-model')
|
|
710
|
+
let asset = createFigureAsset('Shoulder')
|
|
711
|
+
asset = addFigureBox(asset, { id: 'body', size: { x: 300, y: 400, z: 200 }, position: { x: 0, y: 200, z: 0 } })
|
|
712
|
+
asset = addFigureBox(asset, { id: 'arm', size: { x: 60, y: 300, z: 60 }, position: { x: 0, y: 550, z: 0 } })
|
|
713
|
+
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'body', to: { plane: 'mounting-plane' } })
|
|
714
|
+
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'arm', to: { part: 'body', face: 'top' } })
|
|
715
|
+
const jointOf = () => asset.joints!.find(j => j.body1.part === 'arm')!
|
|
716
|
+
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: motionJoint(jointOf(), { ...motionFormOf(jointOf()), kind: 'turn', axis: 'x', id: 'pitch', min: -60, max: 180 }, 'arm') })
|
|
717
|
+
|
|
718
|
+
const fresh = motionFormOf(jointOf(), 1)
|
|
719
|
+
assert.deepEqual([fresh.kind, fresh.axis, fresh.id, fresh.min, fresh.max], ['turn', 'y', '', -90, 90], '새 축은 쓰지 않은 축의 돎')
|
|
720
|
+
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: motionJoint(jointOf(), fresh, 'arm', 1) })
|
|
721
|
+
assert.equal(jointOf().motion!.length, 2)
|
|
722
|
+
assert.equal(controlIdFor('arm', 'turn', 1), 'arm-turn-2')
|
|
723
|
+
assert.deepEqual(motionOf(asset, 'arm', 1), { kind: 'turn', axis: 'y', control: { id: 'arm-turn-2', unit: 'deg', min: -90, max: 90 } }, '둘째 축은 기록대로 읽는다')
|
|
724
|
+
assert.equal(motionOf(asset, 'arm')?.control?.id, 'pitch', '첫 축은 그대로')
|
|
725
|
+
|
|
726
|
+
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: motionJoint(jointOf(), { ...motionFormOf(jointOf(), 1), min: -30, max: 30 }, 'arm', 1) })
|
|
727
|
+
assert.deepEqual([jointOf().motion![0]!.lowerLimit, jointOf().motion![0]!.upperLimit, jointOf().motion![1]!.lowerLimit], [-60, 180, -30], '둘째 축만 바뀐다')
|
|
728
|
+
|
|
729
|
+
/* The second axis turns the arm about Y, on top of the first. */
|
|
730
|
+
const turned = (state: Record<string, number>) => compileFigureAsset(asset).evaluate(state).geometry.find(g => g.id === 'arm')!.pose.r
|
|
731
|
+
assert.notDeepEqual(turned({ 'arm-turn-2': 30 }), turned({}))
|
|
732
|
+
|
|
733
|
+
assert.deepEqual(leadersFor(asset, 'arm', 'turn', motionIdOf(jointOf(), 1)).map(a => a.id), ['arm'], '같은 관절의 첫 축은 따를 수 있다')
|
|
734
|
+
assert.deepEqual(leadersFor(asset, 'arm', 'turn', motionIdOf(jointOf(), 0)).map(a => a.id), ['arm.2'], '제 축은 고르지 않는다')
|
|
735
|
+
|
|
736
|
+
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: motionJoint(jointOf(), { ...motionFormOf(jointOf(), 2), id: 'roll' }, 'arm', 2) })
|
|
737
|
+
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: withoutAxis(jointOf(), 0) })
|
|
738
|
+
assert.deepEqual(
|
|
739
|
+
jointOf().motion!.map(a => [a.id, a.state?.id]),
|
|
740
|
+
[['arm.2', 'arm-turn-2'], ['arm.3', 'roll']],
|
|
741
|
+
'앞 축을 빼도 뒤 축은 제 이름과 조작값을 지킨다'
|
|
742
|
+
)
|
|
743
|
+
assert.notDeepEqual(turned({ 'arm-turn-2': 30 }), turned({}), '남은 축은 같은 조작값으로 움직인다')
|
|
744
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -594,6 +594,9 @@
|
|
|
594
594
|
"figure.label.control-start": "start",
|
|
595
595
|
"figure.label.travel-follows": "travel follows",
|
|
596
596
|
"figure.button.add-motion": "Add motion",
|
|
597
|
+
"figure.button.add-axis": "Add axis",
|
|
598
|
+
"figure.button.remove-axis": "Remove axis",
|
|
599
|
+
"figure.label.motion-axis-n": "Axis {n}",
|
|
597
600
|
"figure.button.remove-motion": "Remove motion",
|
|
598
601
|
"figure.label.room": "Room it takes up",
|
|
599
602
|
"figure.text.room-explained": "Bounds computed from the parts are a proposal. Nothing is declared until you confirm, and a declaration never widens on its own.",
|
package/translations/es.json
CHANGED
|
@@ -594,6 +594,9 @@
|
|
|
594
594
|
"figure.label.control-start": "inicio",
|
|
595
595
|
"figure.label.travel-follows": "el recorrido sigue a",
|
|
596
596
|
"figure.button.add-motion": "Añadir movimiento",
|
|
597
|
+
"figure.button.add-axis": "Añadir eje",
|
|
598
|
+
"figure.button.remove-axis": "Quitar eje",
|
|
599
|
+
"figure.label.motion-axis-n": "Eje {n}",
|
|
597
600
|
"figure.button.remove-motion": "Quitar movimiento",
|
|
598
601
|
"figure.label.room": "Espacio que ocupa",
|
|
599
602
|
"figure.text.room-explained": "Los límites calculados a partir de las piezas son una propuesta. No se declara nada hasta que lo confirme, y una declaración nunca se amplía por sí sola.",
|
package/translations/ja.json
CHANGED
|
@@ -594,6 +594,9 @@
|
|
|
594
594
|
"figure.label.control-start": "start",
|
|
595
595
|
"figure.label.travel-follows": "travel follows",
|
|
596
596
|
"figure.button.add-motion": "Add motion",
|
|
597
|
+
"figure.button.add-axis": "軸を追加",
|
|
598
|
+
"figure.button.remove-axis": "軸を削除",
|
|
599
|
+
"figure.label.motion-axis-n": "軸 {n}",
|
|
597
600
|
"figure.button.remove-motion": "Remove motion",
|
|
598
601
|
"figure.label.room": "Room it takes up",
|
|
599
602
|
"figure.text.room-explained": "Bounds computed from the parts are a proposal. Nothing is declared until you confirm, and a declaration never widens on its own.",
|
package/translations/ko.json
CHANGED
|
@@ -594,6 +594,9 @@
|
|
|
594
594
|
"figure.label.control-start": "시작값",
|
|
595
595
|
"figure.label.travel-follows": "이동 거리가 따르는 것",
|
|
596
596
|
"figure.button.add-motion": "움직임 추가",
|
|
597
|
+
"figure.button.add-axis": "축 추가",
|
|
598
|
+
"figure.button.remove-axis": "축 삭제",
|
|
599
|
+
"figure.label.motion-axis-n": "축 {n}",
|
|
597
600
|
"figure.button.remove-motion": "움직임 삭제",
|
|
598
601
|
"figure.label.room": "차지하는 공간",
|
|
599
602
|
"figure.text.room-explained": "부품에서 계산한 경계는 권장 범위입니다. 확인 후 공간 선언에 반영됩니다. 모델을 변경해도 선언된 범위는 자동으로 확장되지 않습니다.",
|
package/translations/ms.json
CHANGED
|
@@ -594,6 +594,9 @@
|
|
|
594
594
|
"figure.label.control-start": "start",
|
|
595
595
|
"figure.label.travel-follows": "travel follows",
|
|
596
596
|
"figure.button.add-motion": "Add motion",
|
|
597
|
+
"figure.button.add-axis": "Tambah paksi",
|
|
598
|
+
"figure.button.remove-axis": "Buang paksi",
|
|
599
|
+
"figure.label.motion-axis-n": "Paksi {n}",
|
|
597
600
|
"figure.button.remove-motion": "Remove motion",
|
|
598
601
|
"figure.label.room": "Room it takes up",
|
|
599
602
|
"figure.text.room-explained": "Bounds computed from the parts are a proposal. Nothing is declared until you confirm, and a declaration never widens on its own.",
|
package/translations/zh.json
CHANGED
|
@@ -594,6 +594,9 @@
|
|
|
594
594
|
"figure.label.control-start": "start",
|
|
595
595
|
"figure.label.travel-follows": "travel follows",
|
|
596
596
|
"figure.button.add-motion": "Add motion",
|
|
597
|
+
"figure.button.add-axis": "添加轴",
|
|
598
|
+
"figure.button.remove-axis": "删除轴",
|
|
599
|
+
"figure.label.motion-axis-n": "轴 {n}",
|
|
597
600
|
"figure.button.remove-motion": "Remove motion",
|
|
598
601
|
"figure.label.room": "Room it takes up",
|
|
599
602
|
"figure.text.room-explained": "Bounds computed from the parts are a proposal. Nothing is declared until you confirm, and a declaration never widens on its own.",
|