@things-factory/figure-ui 10.1.55 → 10.1.56

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/figure-ui",
3
- "version": "10.1.55",
3
+ "version": "10.1.56",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "@things-factory:registry": "https://registry.npmjs.org"
@@ -27,8 +27,8 @@
27
27
  "clean": "npm run clean:server && npm run clean:client"
28
28
  },
29
29
  "dependencies": {
30
- "@hatiolab/figure-model": "^0.1.64",
31
- "@hatiolab/things-scene": "^10.1.41",
30
+ "@hatiolab/figure-model": "^0.1.65",
31
+ "@hatiolab/things-scene": "^10.1.42",
32
32
  "@material/web": "^2.0.0",
33
33
  "@operato/data-grist": "^10.0.0",
34
34
  "@operato/graphql": "^10.0.0",
@@ -40,8 +40,8 @@
40
40
  "@operato/styles": "^10.0.0",
41
41
  "@operato/utils": "^10.0.0",
42
42
  "@things-factory/ai-assistant": "^10.1.53",
43
- "@things-factory/figure-service": "^10.1.55",
43
+ "@things-factory/figure-service": "^10.1.56",
44
44
  "three": "^0.185.1"
45
45
  },
46
- "gitHead": "52b94398791a18dc0fd8be7184bf9ae2d8e68a84"
46
+ "gitHead": "b84bf4228ae6c2dce368ba15fc8014cd150acfed"
47
47
  }
@@ -417,6 +417,48 @@ describe('a figure made in the modeller is given its size from its parts (user 2
417
417
  })
418
418
  })
419
419
 
420
+ describe('a motion is changed where it is (2026-09-25)', () => {
421
+ before(async () => {
422
+ await mount('arm')
423
+ await click(await read(() => probe.row('upper-arm')))
424
+ })
425
+
426
+ test('the shoulder\'s range is changed from the motion tab: one step, the same joint, its new limit', async () => {
427
+ await click(await read(() => probe.tab('figure.label.motion')))
428
+ const jointOf = part => read(part => (JSON.parse(document.querySelector('figure-v3-editor').session.source()).joints ?? []).find(j => j.body1.part === part) ?? null, part)
429
+ const was = await jointOf('upper-arm')
430
+ assert.ok(was, 'the upper arm has its joint')
431
+ const button = async text =>
432
+ page().evaluate(text => {
433
+ const deep = (root = document) => [...root.querySelectorAll('button')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
434
+ const b = deep().find(x => x.textContent.includes(text))
435
+ if (!b) return null
436
+ b.scrollIntoView({ block: 'center' })
437
+ const r = b.getBoundingClientRect()
438
+ return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
439
+ }, text)
440
+ await click(await button('figure.button.change-motion'))
441
+ const max = await page().evaluateHandle(() => {
442
+ const deep = (root = document) => [...root.querySelectorAll('input[type=number]')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
443
+ const inputs = deep().filter(i => i.closest('div[pair]') && i.getRootNode().host?.tagName === 'FIGURE-V3-LINKS')
444
+ return inputs[inputs.length - 1] ?? null
445
+ })
446
+ assert.ok(await max.evaluate(i => !!i), 'the form is open with the range')
447
+ await max.evaluate(i => i.scrollIntoView({ block: 'center' }))
448
+ await max.click({ clickCount: 3 })
449
+ await page().keyboard.type('45')
450
+ await page().keyboard.press('Tab')
451
+ const start = await state()
452
+ await click(await button('figure.button.apply-motion-change'))
453
+ const after = await state()
454
+ assert.equal(after.error, '', 'not refused')
455
+ assert.equal(after.revision, start.revision + 1, 'one step')
456
+ const now = await jointOf('upper-arm')
457
+ assert.equal(now.upperLimit, 45, 'the new limit')
458
+ assert.equal(now.id, was.id, 'the same joint')
459
+ })
460
+ })
461
+
420
462
  test('the page raised no error', () => {
421
463
  assert.deepEqual(opened.errors, [])
422
464
  })
@@ -444,3 +444,37 @@ test('바닥에 선 부품도 도형 크기를 선언한 도형이면 도형의
444
444
  assert.deepEqual(alignAxesOf(asset, 'post', 'plane'), [{ axis: 'x', edges: true }, { axis: 'z', edges: true }])
445
445
  assert.deepEqual(alignOf(asset, 'post', 'plane', { x: 'min' }, {}), { x: 'min' })
446
446
  })
447
+
448
+ test('움직임은 그 자리에서 고친다 — 폼에 지금 관절을 채우고, 그대로 보내면 같은 관절이다 (2026-09-25)', async () => {
449
+ const { motionFormOf, motionJoint } = await import('../client/modeller/v3-links.ts')
450
+ const base = { id: 'j', body0: { part: 'a', face: 'top' as const }, body1: { part: 'b', face: 'bottom' as const } }
451
+ const joints = [
452
+ { ...base, type: 'prismatic' as const, axis: 'Z' as const, lowerLimit: 0, upperLimit: 2500, state: { id: 'extend' } },
453
+ { ...base, type: 'revolute' as const, axis: 'Y' as const, lowerLimit: -90, upperLimit: 0, state: { id: 'open' } },
454
+ { ...base, type: 'revolute' as const, axis: 'Y' as const, state: { id: 'spin' } },
455
+ { ...base, type: 'prismatic' as const, axis: 'X' as const, mimic: { joint: 'k', multiplier: -1 } },
456
+ { ...base, type: 'prismatic' as const, axis: 'X' as const, travel: { source: { of: 'part' as const, part: 'rail', axis: 'x' as const }, plus: -160 }, state: { id: 'x' } }
457
+ ]
458
+ for (const joint of joints) {
459
+ const form = motionFormOf(joint)
460
+ assert.deepEqual(motionJoint(joint, form, 'b'), joint, `${joint.type} ${JSON.stringify(joint.mimic ?? joint.travel ?? joint.lowerLimit ?? 'endless')}`)
461
+ }
462
+ /* A leader's stroke made to follow the base's length, where it is. */
463
+ const changed = motionJoint(joints[0]!, { ...motionFormOf(joints[0]!), travel: 'base:z', travelPlus: -400 }, 'b')
464
+ assert.deepEqual(changed.travel, { source: { of: 'part', part: 'base', axis: 'z' }, plus: -400 })
465
+ assert.equal(changed.state?.id, 'extend', '같은 조작값 이름')
466
+ })
467
+
468
+ test('바닥에 선 부품도 무엇에 붙어 있는지 읽는다 — 그래야 옮겨 붙일 수 있다 (문설주, 2026-09-25)', async () => {
469
+ const { heldBy } = await import('../client/modeller/v3-links.ts')
470
+ let asset = createV3Asset('Door')
471
+ asset = addV3Box(asset, { id: 'jamb', size: { x: 100, y: 2100, z: 150 }, position: { x: 0, y: 0, z: 0 } })
472
+ asset = addV3Box(asset, { id: 'head', size: { x: 1100, y: 100, z: 150 }, position: { x: 0, y: 0, z: 0 } })
473
+ assert.equal(heldBy(asset, 'jamb'), null)
474
+ asset = applyV3Authoring(asset, { kind: 'attach', part: 'jamb', face: 'bottom', to: { plane: 'mounting-plane' } })
475
+ asset = applyV3Authoring(asset, { kind: 'attach', part: 'head', face: 'bottom', to: { part: 'jamb', face: 'top' } })
476
+ assert.deepEqual(heldBy(asset, 'jamb'), { plane: true }, '장착 평면')
477
+ assert.deepEqual(heldBy(asset, 'head'), { part: 'jamb' })
478
+ /* What the form then sends: a fastening that replaces the one there. */
479
+ assert.doesNotThrow(() => applyV3Authoring(asset, { kind: 'attach', part: 'jamb', face: 'bottom', to: { plane: 'mounting-plane' }, align: { x: { mm: -500 } }, replace: !!heldBy(asset, 'jamb') }))
480
+ })
@@ -724,5 +724,9 @@
724
724
  "figure.text.part-holds-others": "'{part}' holds {others}; take them off first.",
725
725
  "figure.text.part-in-declared-space": "'{part}' is in the declared space; declare the space again without it, or fit it to the parts, first.",
726
726
  "figure.text.part-is-followed": "{others} follow the size or place of '{part}'; make them their own numbers first.",
727
- "figure.text.part-is-led": "{others} move with '{part}'; give them their own control first."
727
+ "figure.text.part-is-led": "{others} move with '{part}'; give them their own control first.",
728
+ "figure.button.change-motion": "change the motion",
729
+ "figure.button.apply-motion-change": "apply the change",
730
+ "figure.button.cancel-motion-change": "leave it",
731
+ "figure.label.mounting-plane": "the mounting plane"
728
732
  }
@@ -724,5 +724,9 @@
724
724
  "figure.text.part-holds-others": "'{part}' に {others} が付いているため削除できません。先に外してください。",
725
725
  "figure.text.part-in-declared-space": "'{part}' は宣言された空間に含まれるため削除できません。この部品なしで空間を宣言し直すか「部品に合わせる」をしてから削除してください。",
726
726
  "figure.text.part-is-followed": "{others} が '{part}' のサイズか位置に従っているため削除できません。先に自分の数値に変えてください。",
727
- "figure.text.part-is-led": "{others} が '{part}' に連動しているため削除できません。先に自分の操作値に変えてください。"
727
+ "figure.text.part-is-led": "{others} が '{part}' に連動しているため削除できません。先に自分の操作値に変えてください。",
728
+ "figure.button.change-motion": "動きを直す",
729
+ "figure.button.apply-motion-change": "動きを変える",
730
+ "figure.button.cancel-motion-change": "直さない",
731
+ "figure.label.mounting-plane": "取付面"
728
732
  }
@@ -724,5 +724,9 @@
724
724
  "figure.text.part-holds-others": "'{part}' 에 {others} 이 매달려 있어 지울 수 없습니다. 먼저 떼어 주세요.",
725
725
  "figure.text.part-in-declared-space": "'{part}' 는 선언된 공간에 들어 있어 지울 수 없습니다. 이 부품 없이 공간을 다시 선언하거나 「부품에 맞추기」를 한 뒤 지우세요.",
726
726
  "figure.text.part-is-followed": "{others} 이 '{part}' 의 크기나 자리를 따르고 있어 지울 수 없습니다. 먼저 그 부품을 제 숫자로 바꾸세요.",
727
- "figure.text.part-is-led": "{others} 이 '{part}' 를 따라 움직여 지울 수 없습니다. 먼저 제 조작값으로 바꾸세요."
727
+ "figure.text.part-is-led": "{others} 이 '{part}' 를 따라 움직여 지울 수 없습니다. 먼저 제 조작값으로 바꾸세요.",
728
+ "figure.button.change-motion": "움직임 고치기",
729
+ "figure.button.apply-motion-change": "움직임 바꾸기",
730
+ "figure.button.cancel-motion-change": "고치지 않음",
731
+ "figure.label.mounting-plane": "장착 평면"
728
732
  }
@@ -724,5 +724,9 @@
724
724
  "figure.text.part-holds-others": "'{part}' memegang {others}; tanggalkan dahulu.",
725
725
  "figure.text.part-in-declared-space": "'{part}' berada dalam ruang yang diisytihar; isytihar semula ruang tanpanya, atau muatkan pada bahagian, dahulu.",
726
726
  "figure.text.part-is-followed": "{others} mengikut saiz atau tempat '{part}'; jadikan nombor sendiri dahulu.",
727
- "figure.text.part-is-led": "{others} bergerak bersama '{part}'; beri kawalan sendiri dahulu."
727
+ "figure.text.part-is-led": "{others} bergerak bersama '{part}'; beri kawalan sendiri dahulu.",
728
+ "figure.button.change-motion": "ubah gerakan",
729
+ "figure.button.apply-motion-change": "guna perubahan",
730
+ "figure.button.cancel-motion-change": "biarkan",
731
+ "figure.label.mounting-plane": "satah pemasangan"
728
732
  }
@@ -724,5 +724,9 @@
724
724
  "figure.text.part-holds-others": "'{part}' 上挂着 {others},无法删除。请先卸下。",
725
725
  "figure.text.part-in-declared-space": "'{part}' 在已声明的空间内,无法删除。请先在不含它的情况下重新声明空间或「贴合部件」。",
726
726
  "figure.text.part-is-followed": "{others} 跟随 '{part}' 的尺寸或位置,无法删除。请先改为自身数值。",
727
- "figure.text.part-is-led": "{others} 跟随 '{part}' 运动,无法删除。请先改为自身控制值。"
727
+ "figure.text.part-is-led": "{others} 跟随 '{part}' 运动,无法删除。请先改为自身控制值。",
728
+ "figure.button.change-motion": "修改运动",
729
+ "figure.button.apply-motion-change": "应用修改",
730
+ "figure.button.cancel-motion-change": "不修改",
731
+ "figure.label.mounting-plane": "安装平面"
728
732
  }