@things-factory/figure-ui 10.1.75 → 10.1.76

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.
Files changed (235) hide show
  1. package/client/figure-assistant.ts +3 -3
  2. package/client/graphql/{v3-store.ts → store.ts} +6 -6
  3. package/client/modeller/{v3-ai-proposal.ts → ai-proposal.ts} +8 -8
  4. package/client/modeller/{v3-align.ts → align.ts} +14 -14
  5. package/client/modeller/{v3-canvas.ts → canvas.ts} +13 -13
  6. package/client/modeller/{v3-clips.ts → clips.ts} +5 -5
  7. package/client/modeller/{v3-controls.ts → controls.ts} +2 -2
  8. package/client/modeller/{v3-editor.ts → editor.ts} +117 -117
  9. package/client/modeller/{figure-v3-capability.ts → figure-capability-panel.ts} +7 -7
  10. package/client/modeller/{figure-v3-clips.ts → figure-clips.ts} +10 -10
  11. package/client/modeller/{figure-v3-controls.ts → figure-controls.ts} +9 -9
  12. package/client/modeller/{v3-figure-facts.ts → figure-facts.ts} +11 -11
  13. package/client/modeller/{figure-v3-first-save.ts → figure-first-save.ts} +3 -3
  14. package/client/modeller/figure-history-panel.ts +1 -1
  15. package/client/modeller/{figure-v3-links.ts → figure-links.ts} +35 -35
  16. package/client/modeller/{figure-v3-material.ts → figure-material-panel.ts} +20 -20
  17. package/client/modeller/{figure-v3-parts.ts → figure-parts.ts} +15 -15
  18. package/client/modeller/{figure-v3-preview.ts → figure-preview.ts} +14 -14
  19. package/client/modeller/{figure-v3-properties.ts → figure-properties.ts} +25 -25
  20. package/client/modeller/{figure-v3-recipe-catalog.ts → figure-recipe-catalog.ts} +10 -10
  21. package/client/modeller/{figure-v3-verdict.ts → figure-verdict.ts} +24 -24
  22. package/client/modeller/gate-findings.ts +2 -2
  23. package/client/modeller/{v3-gizmo.ts → gizmo.ts} +11 -11
  24. package/client/modeller/{v3-joint-drag.ts → joint-drag.ts} +11 -11
  25. package/client/modeller/{v3-joint-fan.ts → joint-fan.ts} +13 -13
  26. package/client/modeller/{v3-links.ts → links.ts} +59 -59
  27. package/client/modeller/{v3-live.ts → live.ts} +9 -9
  28. package/client/modeller/{v3-maturity.ts → maturity.ts} +65 -65
  29. package/client/modeller/modeller-route.ts +1 -1
  30. package/client/modeller/{v3-occupancy.ts → occupancy.ts} +6 -6
  31. package/client/modeller/{v3-palette.ts → palette.ts} +3 -3
  32. package/client/modeller/{v3-preview-model.ts → preview-model.ts} +8 -8
  33. package/client/modeller/{v3-product-session.ts → product-session.ts} +25 -25
  34. package/client/modeller/{v3-recipe-source.ts → recipe-source.ts} +2 -2
  35. package/client/modeller/{v3-recipes.ts → recipes.ts} +9 -9
  36. package/client/modeller/{v3-renderer.ts → renderer.ts} +16 -16
  37. package/client/modeller/{v3-stage-model.ts → stage-model.ts} +15 -15
  38. package/client/modeller/{v3-thumbnail.ts → thumbnail.ts} +8 -8
  39. package/client/pages/figure-list-page.ts +4 -4
  40. package/client/pages/{figure-v3-modeller-page.ts → figure-modeller-page.ts} +37 -37
  41. package/client/route.ts +1 -5
  42. package/dist-client/figure-assistant.js +3 -3
  43. package/dist-client/figure-assistant.js.map +1 -1
  44. package/dist-client/graphql/store.d.ts +3 -0
  45. package/dist-client/graphql/store.js +39 -0
  46. package/dist-client/graphql/store.js.map +1 -0
  47. package/dist-client/modeller/ai-proposal.d.ts +51 -0
  48. package/dist-client/modeller/ai-proposal.js +64 -0
  49. package/dist-client/modeller/ai-proposal.js.map +1 -0
  50. package/dist-client/modeller/align.d.ts +30 -0
  51. package/dist-client/modeller/align.js +75 -0
  52. package/dist-client/modeller/align.js.map +1 -0
  53. package/dist-client/modeller/canvas.d.ts +72 -0
  54. package/dist-client/modeller/canvas.js +379 -0
  55. package/dist-client/modeller/canvas.js.map +1 -0
  56. package/dist-client/modeller/clips.d.ts +39 -0
  57. package/dist-client/modeller/clips.js +58 -0
  58. package/dist-client/modeller/clips.js.map +1 -0
  59. package/dist-client/modeller/controls.d.ts +28 -0
  60. package/dist-client/modeller/controls.js +28 -0
  61. package/dist-client/modeller/controls.js.map +1 -0
  62. package/dist-client/modeller/editor.d.ts +203 -0
  63. package/dist-client/modeller/editor.js +1526 -0
  64. package/dist-client/modeller/editor.js.map +1 -0
  65. package/dist-client/modeller/fields.d.ts +45 -0
  66. package/dist-client/modeller/fields.js +83 -0
  67. package/dist-client/modeller/fields.js.map +1 -0
  68. package/dist-client/modeller/figure-capability-panel.d.ts +22 -0
  69. package/dist-client/modeller/figure-capability-panel.js +141 -0
  70. package/dist-client/modeller/figure-capability-panel.js.map +1 -0
  71. package/dist-client/modeller/figure-clips.d.ts +31 -0
  72. package/dist-client/modeller/figure-clips.js +268 -0
  73. package/dist-client/modeller/figure-clips.js.map +1 -0
  74. package/dist-client/modeller/figure-controls.d.ts +36 -0
  75. package/dist-client/modeller/figure-controls.js +226 -0
  76. package/dist-client/modeller/figure-controls.js.map +1 -0
  77. package/dist-client/modeller/figure-facts.d.ts +35 -0
  78. package/dist-client/modeller/figure-facts.js +55 -0
  79. package/dist-client/modeller/figure-facts.js.map +1 -0
  80. package/dist-client/modeller/figure-first-save.d.ts +20 -0
  81. package/dist-client/modeller/figure-first-save.js +118 -0
  82. package/dist-client/modeller/figure-first-save.js.map +1 -0
  83. package/dist-client/modeller/figure-history-panel.d.ts +1 -1
  84. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  85. package/dist-client/modeller/figure-links.d.ts +77 -0
  86. package/dist-client/modeller/figure-links.js +649 -0
  87. package/dist-client/modeller/figure-links.js.map +1 -0
  88. package/dist-client/modeller/figure-material-panel.d.ts +39 -0
  89. package/dist-client/modeller/figure-material-panel.js +323 -0
  90. package/dist-client/modeller/figure-material-panel.js.map +1 -0
  91. package/dist-client/modeller/figure-parts.d.ts +69 -0
  92. package/dist-client/modeller/figure-parts.js +559 -0
  93. package/dist-client/modeller/figure-parts.js.map +1 -0
  94. package/dist-client/modeller/figure-preview.d.ts +34 -0
  95. package/dist-client/modeller/figure-preview.js +346 -0
  96. package/dist-client/modeller/figure-preview.js.map +1 -0
  97. package/dist-client/modeller/figure-properties.d.ts +45 -0
  98. package/dist-client/modeller/figure-properties.js +239 -0
  99. package/dist-client/modeller/figure-properties.js.map +1 -0
  100. package/dist-client/modeller/figure-recipe-catalog.d.ts +38 -0
  101. package/dist-client/modeller/figure-recipe-catalog.js +399 -0
  102. package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
  103. package/dist-client/modeller/figure-verdict.d.ts +64 -0
  104. package/dist-client/modeller/figure-verdict.js +787 -0
  105. package/dist-client/modeller/figure-verdict.js.map +1 -0
  106. package/dist-client/modeller/first-save.d.ts +17 -0
  107. package/dist-client/modeller/first-save.js +26 -0
  108. package/dist-client/modeller/first-save.js.map +1 -0
  109. package/dist-client/modeller/gate-findings.d.ts +2 -2
  110. package/dist-client/modeller/gate-findings.js +2 -2
  111. package/dist-client/modeller/gate-findings.js.map +1 -1
  112. package/dist-client/modeller/gizmo.d.ts +72 -0
  113. package/dist-client/modeller/gizmo.js +111 -0
  114. package/dist-client/modeller/gizmo.js.map +1 -0
  115. package/dist-client/modeller/help.d.ts +18 -0
  116. package/dist-client/modeller/help.js +37 -0
  117. package/dist-client/modeller/help.js.map +1 -0
  118. package/dist-client/modeller/joint-drag.d.ts +19 -0
  119. package/dist-client/modeller/joint-drag.js +97 -0
  120. package/dist-client/modeller/joint-drag.js.map +1 -0
  121. package/dist-client/modeller/joint-fan.d.ts +9 -0
  122. package/dist-client/modeller/joint-fan.js +276 -0
  123. package/dist-client/modeller/joint-fan.js.map +1 -0
  124. package/dist-client/modeller/keys.d.ts +22 -0
  125. package/dist-client/modeller/keys.js +26 -0
  126. package/dist-client/modeller/keys.js.map +1 -0
  127. package/dist-client/modeller/links.d.ts +267 -0
  128. package/dist-client/modeller/links.js +480 -0
  129. package/dist-client/modeller/links.js.map +1 -0
  130. package/dist-client/modeller/live.d.ts +37 -0
  131. package/dist-client/modeller/live.js +68 -0
  132. package/dist-client/modeller/live.js.map +1 -0
  133. package/dist-client/modeller/maturity.d.ts +120 -0
  134. package/dist-client/modeller/maturity.js +313 -0
  135. package/dist-client/modeller/maturity.js.map +1 -0
  136. package/dist-client/modeller/modeller-route.d.ts +1 -1
  137. package/dist-client/modeller/modeller-route.js +1 -1
  138. package/dist-client/modeller/modeller-route.js.map +1 -1
  139. package/dist-client/modeller/occupancy.d.ts +18 -0
  140. package/dist-client/modeller/occupancy.js +24 -0
  141. package/dist-client/modeller/occupancy.js.map +1 -0
  142. package/dist-client/modeller/palette.d.ts +7 -0
  143. package/dist-client/modeller/palette.js +46 -0
  144. package/dist-client/modeller/palette.js.map +1 -0
  145. package/dist-client/modeller/preview-model.d.ts +47 -0
  146. package/dist-client/modeller/preview-model.js +68 -0
  147. package/dist-client/modeller/preview-model.js.map +1 -0
  148. package/dist-client/modeller/product-session.d.ts +510 -0
  149. package/dist-client/modeller/product-session.js +157 -0
  150. package/dist-client/modeller/product-session.js.map +1 -0
  151. package/dist-client/modeller/recipe-source.d.ts +8 -0
  152. package/dist-client/modeller/recipe-source.js +14 -0
  153. package/dist-client/modeller/recipe-source.js.map +1 -0
  154. package/dist-client/modeller/recipes.d.ts +37 -0
  155. package/dist-client/modeller/recipes.js +103 -0
  156. package/dist-client/modeller/recipes.js.map +1 -0
  157. package/dist-client/modeller/refusals.d.ts +68 -0
  158. package/dist-client/modeller/refusals.js +142 -0
  159. package/dist-client/modeller/refusals.js.map +1 -0
  160. package/dist-client/modeller/renderer.d.ts +40 -0
  161. package/dist-client/modeller/renderer.js +73 -0
  162. package/dist-client/modeller/renderer.js.map +1 -0
  163. package/dist-client/modeller/stage-model.d.ts +39 -0
  164. package/dist-client/modeller/stage-model.js +147 -0
  165. package/dist-client/modeller/stage-model.js.map +1 -0
  166. package/dist-client/modeller/thumbnail.d.ts +3 -0
  167. package/dist-client/modeller/thumbnail.js +75 -0
  168. package/dist-client/modeller/thumbnail.js.map +1 -0
  169. package/dist-client/pages/figure-list-page.d.ts +1 -1
  170. package/dist-client/pages/figure-list-page.js +4 -4
  171. package/dist-client/pages/figure-list-page.js.map +1 -1
  172. package/dist-client/pages/figure-modeller-page.d.ts +102 -0
  173. package/dist-client/pages/figure-modeller-page.js +548 -0
  174. package/dist-client/pages/figure-modeller-page.js.map +1 -0
  175. package/dist-client/route.d.ts +1 -1
  176. package/dist-client/route.js +1 -5
  177. package/dist-client/route.js.map +1 -1
  178. package/dist-client/tsconfig.tsbuildinfo +1 -1
  179. package/docs/product-cutover.md +25 -0
  180. package/package.json +5 -5
  181. package/test/{v3-ai-proposal.test.ts → ai-proposal.test.ts} +6 -6
  182. package/test/{v3-align.test.ts → align.test.ts} +8 -8
  183. package/test/browser/modeller-page.js +30 -30
  184. package/test/browser/modeller.check.mjs +14 -14
  185. package/test/browser/serve.mjs +1 -1
  186. package/test/browser/thumbnail-page.js +6 -6
  187. package/test/{v3-clips.test.ts → clips.test.ts} +18 -18
  188. package/test/{v3-controls.test.ts → controls.test.ts} +15 -15
  189. package/test/{v3-fields.test.ts → fields.test.ts} +13 -13
  190. package/test/figure-assistant-surface.test.ts +3 -3
  191. package/test/{v3-first-save.test.ts → first-save.test.ts} +10 -10
  192. package/test/fixtures/joint-column.json +2 -2
  193. package/test/fixtures/joint-roller.json +2 -2
  194. package/test/fixtures/robot-arm-6dof.json +2 -2
  195. package/test/{v3-gizmo.test.ts → gizmo.test.ts} +33 -33
  196. package/test/{v3-joint-drag.test.ts → joint-drag.test.ts} +17 -17
  197. package/test/{v3-joint-fan.test.ts → joint-fan.test.ts} +18 -18
  198. package/test/joints.ts +6 -6
  199. package/test/{v3-keys.test.ts → keys.test.ts} +2 -2
  200. package/test/{v3-layout.test.ts → layout.test.ts} +8 -8
  201. package/test/{v3-links.test.ts → links.test.ts} +201 -201
  202. package/test/{v3-live.test.ts → live.test.ts} +18 -18
  203. package/test/{v3-material.test.ts → material.test.ts} +12 -12
  204. package/test/{v3-maturity.test.ts → maturity.test.ts} +74 -74
  205. package/test/modeller-route.test.ts +5 -6
  206. package/test/{v3-palette-icons.test.ts → palette-icons.test.ts} +1 -1
  207. package/test/{v3-parts-wiring.test.ts → parts-wiring.test.ts} +5 -5
  208. package/test/{v3-preview.test.ts → preview.test.ts} +12 -12
  209. package/test/{v3-product-session.test.ts → product-session.test.ts} +17 -17
  210. package/test/{v3-product-write.test.ts → product-write.test.ts} +20 -20
  211. package/test/{v3-properties.test.ts → properties.test.ts} +24 -24
  212. package/test/{v3-recipes.test.ts → recipes.test.ts} +9 -9
  213. package/test/{v3-refusals.test.ts → refusals.test.ts} +43 -43
  214. package/test/{v3-release-panel.test.ts → release-panel.test.ts} +21 -21
  215. package/test/renderer.test.ts +24 -0
  216. package/test/resolve-ts-siblings.mjs +1 -1
  217. package/test/{v3-scene-canvas.test.ts → scene-canvas.test.ts} +6 -6
  218. package/test/{v3-stage-model.test.ts → stage-model.test.ts} +5 -5
  219. package/test/{v3-theme.test.ts → theme.test.ts} +4 -4
  220. package/test/{v3-thumbnail.test.ts → thumbnail.test.ts} +4 -4
  221. package/test/{v3-verdict.test.ts → verdict.test.ts} +17 -17
  222. package/test/{v3-view.test.ts → view.test.ts} +2 -2
  223. package/things-factory.config.js +2 -3
  224. package/translations/en.json +2 -2
  225. package/translations/ja.json +2 -2
  226. package/translations/ko.json +2 -2
  227. package/translations/ms.json +2 -2
  228. package/translations/zh.json +2 -2
  229. package/docs/v3-product-cutover.md +0 -25
  230. package/test/v3-renderer.test.ts +0 -24
  231. /package/client/modeller/{v3-fields.ts → fields.ts} +0 -0
  232. /package/client/modeller/{v3-first-save.ts → first-save.ts} +0 -0
  233. /package/client/modeller/{v3-help.ts → help.ts} +0 -0
  234. /package/client/modeller/{v3-keys.ts → keys.ts} +0 -0
  235. /package/client/modeller/{v3-refusals.ts → refusals.ts} +0 -0
@@ -7,7 +7,7 @@ import assert from 'node:assert/strict'
7
7
  import { readFileSync } from 'node:fs'
8
8
  import { fileURLToPath } from 'node:url'
9
9
 
10
- import { groupFields, ownerOf, partLabelOf, repeatOf } from '../client/modeller/v3-fields.ts'
10
+ import { groupFields, ownerOf, partLabelOf, repeatOf } from '../client/modeller/fields.ts'
11
11
 
12
12
  const f = (id: string, value = 1, unit = 'mm') => ({ id, unit, value, role: 'design' })
13
13
 
@@ -30,7 +30,7 @@ test('도형은 크기(size.x · y · z)가 세 칸 한 줄', () => {
30
30
  })
31
31
 
32
32
  test('오른쪽 칸은 한 벌의 규격 — 요소마다 제 제목 · 칸 · 단추 규칙을 두지 않는다', () => {
33
- for (const file of ['v3-editor', 'figure-v3-properties', 'figure-v3-links', 'figure-v3-material', 'figure-v3-controls', 'figure-v3-clips', 'figure-v3-capability']) {
33
+ for (const file of ['editor', 'figure-properties', 'figure-links', 'figure-material-panel', 'figure-controls', 'figure-clips', 'figure-capability-panel']) {
34
34
  const source = readFileSync(fileURLToPath(new URL(`../client/modeller/${file}.ts`, import.meta.url)), 'utf8')
35
35
  assert.ok(source.includes('SidePanelStyles'), `${file} 가 공용 규격을 쓴다`)
36
36
  const styles = source.slice(source.indexOf('static styles'), source.indexOf('`]', source.indexOf('static styles')))
@@ -40,10 +40,10 @@ test('오른쪽 칸은 한 벌의 규격 — 요소마다 제 제목 · 칸 ·
40
40
 
41
41
  test('긴 설명은 제목 옆 「?」 로 연다 — 늘 펼쳐 두지 않는다', () => {
42
42
  for (const [file, key] of [
43
- ['figure-v3-links', 'figure.text.motion-explained'],
44
- ['figure-v3-links', 'figure.text.place-explained'],
45
- ['figure-v3-links', 'figure.text.fastening-explained'],
46
- ['figure-v3-clips', 'figure.text.clips-explained']
43
+ ['figure-links', 'figure.text.motion-explained'],
44
+ ['figure-links', 'figure.text.place-explained'],
45
+ ['figure-links', 'figure.text.fastening-explained'],
46
+ ['figure-clips', 'figure.text.clips-explained']
47
47
  ]) {
48
48
  const source = readFileSync(fileURLToPath(new URL(`../client/modeller/${file}.ts`, import.meta.url)), 'utf8')
49
49
  assert.ok(source.includes(`this.help.button('${key}')`) && source.includes(`this.help.hint('${key}')`), `${file}: ${key}`)
@@ -56,7 +56,7 @@ test('되풀이한 부품은 되풀이의 이름과 몇 번째인지로 읽는
56
56
  assert.equal(partLabelOf('["roller.repeat",2,"mark.placed"]'), 'mark 3', '줄의 부품에 붙은 것은 제 이름으로 (ADR-0093, 2026-09-26)')
57
57
  assert.equal(partLabelOf('deck'), 'deck', '보통 부품은 제 이름')
58
58
  assert.equal(partLabelOf('[not json'), '[not json')
59
- const parts = readFileSync(fileURLToPath(new URL('../client/modeller/figure-v3-parts.ts', import.meta.url)), 'utf8')
59
+ const parts = readFileSync(fileURLToPath(new URL('../client/modeller/figure-parts.ts', import.meta.url)), 'utf8')
60
60
  assert.ok(parts.includes('>${partLabelOf(part.id)}'), '목록이 이 이름으로 그린다')
61
61
  })
62
62
 
@@ -67,7 +67,7 @@ test('되풀이한 부품의 속성 이름 칸도 사람의 이름이고, 몇
67
67
  const carried = geometry.concat(Array.from({ length: 16 }, (_, i) => ({ id: JSON.stringify(['roller.repeat', i, 'mark.placed']) })))
68
68
  assert.deepEqual(repeatOf('["roller.repeat",14,"roller.value"]', carried), { name: 'roller', count: 16, index: 15 }, '롤러에 붙은 것은 롤러 수에 안 든다')
69
69
  assert.deepEqual(repeatOf('["roller.repeat",2,"mark.placed"]', carried), { name: 'mark', count: 16, index: 3 })
70
- const editor = readFileSync(fileURLToPath(new URL('../client/modeller/v3-editor.ts', import.meta.url)), 'utf8')
70
+ const editor = readFileSync(fileURLToPath(new URL('../client/modeller/editor.ts', import.meta.url)), 'utf8')
71
71
  assert.ok(editor.includes('.value=${partLabelOf(this.selected)}'), '사용자 2026-09-24: 「부품 이름 ???」')
72
72
  assert.ok(editor.includes('?disabled=${unavailable || !!repeatOf(this.selected, view.geometry)?.index}'), '복사본에는 바꿀 제 이름이 없고, 줄의 부품 자신은 이름을 바꿀 수 있다(walk 1, 2026-09-26)')
73
73
  assert.equal(repeatOf('roller', geometry)?.index, 0, '줄의 부품 자신은 몇 번째가 아니다')
@@ -79,18 +79,18 @@ test('편집기는 저작한 부품 단위로 고르고 보인다 — 되풀이
79
79
  assert.equal(ownerOf('["roller.repeat",2,"mark.placed"]'), 'mark', '롤러에 붙은 표시의 복사본을 누르면 그 표시를 고른다')
80
80
  assert.deepEqual(repeatOf('roller', [{ id: '["roller.repeat",0,"roller.value"]' }, { id: '["roller.repeat",1,"roller.value"]' }, { id: 'deck' }]), { name: 'roller', count: 2, index: 0 })
81
81
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
82
- const canvas = read('../client/modeller/v3-canvas.ts')
82
+ const canvas = read('../client/modeller/canvas.ts')
83
83
  assert.ok(canvas.includes('.filter(([name]) => ownerOf(name) === this.selected)'), '사용자 2026-09-24: 「why repeatable parts are not selected?」')
84
- const editor = read('../client/modeller/v3-editor.ts')
84
+ const editor = read('../client/modeller/editor.ts')
85
85
  assert.ok(editor.includes('this.selected = id ? ownerOf(id) : id'), '캔버스에서 복사본을 누르면 그 되풀이를 고른다')
86
86
  assert.ok(editor.includes('.parts=${this.partRows(view.geometry)}'), '목록은 저작한 부품 한 줄씩')
87
87
  assert.ok(editor.includes("this.session!.command({ kind: 'set-repeat', part: this.selected, pitch })"), 'pitch 는 명령으로 정한다')
88
- const parts = read('../client/modeller/figure-v3-parts.ts')
88
+ const parts = read('../client/modeller/figure-parts.ts')
89
89
  assert.ok(parts.includes('?disabled=${this.locked || full}'), '등급 한도에 이르면 팔레트가 잠긴다')
90
90
  })
91
91
 
92
92
  test('pitch 는 한 곳에서 하나의 값이고, 늘 set-repeat 로 바꿔 최대 길이가 따라온다', () => {
93
- const editor = readFileSync(fileURLToPath(new URL('../client/modeller/v3-editor.ts', import.meta.url)), 'utf8')
93
+ const editor = readFileSync(fileURLToPath(new URL('../client/modeller/editor.ts', import.meta.url)), 'utf8')
94
94
  assert.ok(editor.includes("groupFields(own, this.selected)"), '사용자 2026-09-24: 되풀이의 간격과 형상 치수의 repeat · pitch 가 같은 값으로 두 번 떴다')
95
95
  assert.ok(editor.includes("!i.id.startsWith(this.selected + '.repeat.')"))
96
96
  const pitch = editor.slice(editor.indexOf(' private setPitch('), editor.indexOf(' /** Choose a part,'))
@@ -104,7 +104,7 @@ test('pitch 는 한 곳에서 하나의 값이고, 늘 set-repeat 로 바꿔 최
104
104
  })
105
105
 
106
106
  test('기즈모로 끈, 규칙이 자리를 정하는 부품은 규칙을 지킨 채 옮겨지고, 관절·붙이기가 정하는 부품은 까닭을 말한다 — 튕겨 돌아가지 않는다', () => {
107
- const editor = readFileSync(fileURLToPath(new URL('../client/modeller/v3-editor.ts', import.meta.url)), 'utf8')
107
+ const editor = readFileSync(fileURLToPath(new URL('../client/modeller/editor.ts', import.meta.url)), 'utf8')
108
108
  const moved = editor.slice(editor.indexOf(' private transformed('), editor.indexOf(' /** 맞추기'))
109
109
  assert.ok(moved.includes("kind: 'nudge-part',"), '사용자 2026-09-24: 「기즈모 컨트롤도 전혀 작동이 안되는군」')
110
110
  assert.ok(moved.includes('...(Object.keys(grow).length ? { grow } : {})'), '크기 끌기도 배율로 — 「되는 듯하다가 다시 원래 크기로 돌아간다」')
@@ -1,5 +1,5 @@
1
1
  /*
2
- * The V3 modeller's AI - the framework dock talks about the figure on screen (design/ai/shared-dock.md §3).
2
+ * The modeller's AI - the framework dock talks about the figure on screen (design/ai/shared-dock.md §3).
3
3
  *
4
4
  * What this holds: ① a saved figure provides a surface anchored on its row ② the draft is read at the moment of
5
5
  * sending, not when the surface was provided ③ an unsaved figure provides nothing - no conversation without a row
@@ -11,7 +11,7 @@ import { readFileSync } from 'node:fs'
11
11
  import { fileURLToPath } from 'node:url'
12
12
  import { test } from 'node:test'
13
13
 
14
- const source = readFileSync(fileURLToPath(new URL('../client/pages/figure-v3-modeller-page.ts', import.meta.url)), 'utf8')
14
+ const source = readFileSync(fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url)), 'utf8')
15
15
 
16
16
  test('a saved figure provides a surface anchored on its row', () => {
17
17
  assert.match(source, /provideAssistantSurface\(this, \{/)
@@ -34,7 +34,7 @@ test('the surface is taken back when the page goes inactive or leaves', () => {
34
34
  })
35
35
 
36
36
  test('the dock applies a proposal through the page, on the version the model saw', () => {
37
- assert.match(source, /window\.addEventListener\('figure-v3-apply', this\.applyAI\)/)
37
+ assert.match(source, /window\.addEventListener\('figure-apply', this\.applyAI\)/)
38
38
  assert.match(source, /this\.session\.applyProposal\(detail\.steps, detail\.revision, detail\.context\)/)
39
39
  assert.match(source, /this\.takeNewFigureProposal\(\)/, 'a new figure opened for a proposal takes its steps')
40
40
  })
@@ -5,8 +5,8 @@ import { test } from 'node:test'
5
5
  import assert from 'node:assert/strict'
6
6
 
7
7
  import { placeholderType } from '@hatiolab/figure-model'
8
- import { firstSaveRefusalOf, releasableType, typeOf } from '../client/modeller/v3-first-save.ts'
9
- import { createV3ProductSession, type V3ProductStore } from '../client/modeller/v3-product-session.ts'
8
+ import { firstSaveRefusalOf, releasableType, typeOf } from '../client/modeller/first-save.ts'
9
+ import { createFigureProductSession, type FigureProductStore } from '../client/modeller/product-session.ts'
10
10
 
11
11
  test('치는 대로 타입의 모양이 된다 — 대문자 · 숫자 · 밑줄', () => {
12
12
  assert.equal(typeOf(' agv lifter '), 'AGV_LIFTER')
@@ -29,8 +29,8 @@ test('비었거나 이미 쓰는 이름이면 저장하지 않는다', () => {
29
29
 
30
30
  /** A store that remembers what it was asked. */
31
31
  function recording() {
32
- const requests: Parameters<V3ProductStore['save']>[0][] = []
33
- const store: V3ProductStore = {
32
+ const requests: Parameters<FigureProductStore['save']>[0][] = []
33
+ const store: FigureProductStore = {
34
34
  load: async id => ({ id, source: saved ?? '' }),
35
35
  save: async request => {
36
36
  requests.push(request)
@@ -44,7 +44,7 @@ function recording() {
44
44
 
45
45
  test('타입은 새 도형을 만드는 저장에만 실린다 — 이미 있는 도형의 저장은 타입을 보내지 않는다', async () => {
46
46
  const { store, requests } = recording()
47
- const session = createV3ProductSession(store)
47
+ const session = createFigureProductSession(store)
48
48
  session.startNew()
49
49
  await session.save({ type: 'AGV_LIFTER' })
50
50
  assert.equal(requests[0]!.type, 'AGV_LIFTER')
@@ -58,7 +58,7 @@ test('타입은 새 도형을 만드는 저장에만 실린다 — 이미 있는
58
58
 
59
59
  test('타입을 주지 않으면 싣지 않는다 — 서버가 임시 이름을 붙인다', async () => {
60
60
  const { store, requests } = recording()
61
- const session = createV3ProductSession(store)
61
+ const session = createFigureProductSession(store)
62
62
  session.startNew()
63
63
  await session.save()
64
64
  assert.equal('type' in requests[0]!, false)
@@ -66,17 +66,17 @@ test('타입을 주지 않으면 싣지 않는다 — 서버가 임시 이름을
66
66
 
67
67
  test('저장 전에 적은 타입은 처음 저장의 물음에 실려 온다 — 칸이 막혀 있어 적어도 사라졌다 (09-25)', async () => {
68
68
  const { readFileSync } = await import('node:fs')
69
- const page = readFileSync(new URL('../client/pages/figure-v3-modeller-page.ts', import.meta.url), 'utf8')
69
+ const page = readFileSync(new URL('../client/pages/figure-modeller-page.ts', import.meta.url), 'utf8')
70
70
  const field = page.slice(page.indexOf('<label slot="type" type-field>'), page.indexOf('</label>', page.indexOf('<label slot="type" type-field>')))
71
71
  assert.doesNotMatch(field, /disabled=\$\{!view\.id\}/, '저장 전에도 칸에 적을 수 있다')
72
72
  const rename = page.slice(page.indexOf('private renameType'), page.indexOf('private fitToParts'))
73
73
  assert.match(rename, /if \(!id\) \{\s*this\.row = \{ \.\.\.this\.row, type: wanted \|\| undefined \}/, '저장 전에는 행 대신 화면이 타입을 쥔다')
74
- assert.match(page, /<figure-v3-first-save \.type=\$\{this\.row\.type \?\? ''\}/, '물음은 그 타입으로 열린다')
74
+ assert.match(page, /<figure-first-save \.type=\$\{this\.row\.type \?\? ''\}/, '물음은 그 타입으로 열린다')
75
75
  })
76
76
 
77
77
  test('처음 저장한 도형은 주소가 제 것이 된다 — 모델러를 다시 열면 새 도형이 열린다 (2026-09-25)', async () => {
78
78
  const { readFileSync } = await import('node:fs')
79
- const page = readFileSync(new URL('../client/pages/figure-v3-modeller-page.ts', import.meta.url), 'utf8')
79
+ const page = readFileSync(new URL('../client/pages/figure-modeller-page.ts', import.meta.url), 'utf8')
80
80
  const changed = page.slice(page.indexOf('private changed = () => {'), page.indexOf('private async readRow'))
81
81
  assert.match(changed, /if \(view\.id && !this\.routeId && !view\.saving\) \{\s*this\.routeId = view\.id\s*navigate\(modellerPathOf\(\{ id: view\.id \}\), true\)/, '저장으로 id 를 얻으면 그 주소로 바꾼다(기록은 덮어쓴다)')
82
82
  /* The early return that kept the old figure reads the route id; it now matches only the figure the address names. */
@@ -85,7 +85,7 @@ test('처음 저장한 도형은 주소가 제 것이 된다 — 모델러를
85
85
 
86
86
  test('다른 도형을 열면 행을 먼저 비운다 — 앞 도형의 타입이 칸에 남지 않는다 (C2, 2026-09-25)', async () => {
87
87
  const { readFileSync } = await import('node:fs')
88
- const page = readFileSync(new URL('../client/pages/figure-v3-modeller-page.ts', import.meta.url), 'utf8')
88
+ const page = readFileSync(new URL('../client/pages/figure-modeller-page.ts', import.meta.url), 'utf8')
89
89
  const updated = page.slice(page.indexOf('async pageUpdated'), page.indexOf('private askFirstSave'))
90
90
  const opening = updated.slice(updated.indexOf('if (id) {'), updated.indexOf('this.session.open(id'))
91
91
  assert.match(opening, /this\.row = \{ version: 0 \}/, '열기 전에 행을 비운다')
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "figure-v3-asset-1",
2
+ "version": "figure-asset-1",
3
3
  "name": "COLUMN",
4
4
  "document": {
5
- "version": "figure-v3-prototype-1",
5
+ "version": "figure-prototype-1",
6
6
  "model": {
7
7
  "version": "r3-spatial-1",
8
8
  "inputs": [
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "figure-v3-asset-1",
2
+ "version": "figure-asset-1",
3
3
  "name": "ROLLER",
4
4
  "document": {
5
- "version": "figure-v3-prototype-1",
5
+ "version": "figure-prototype-1",
6
6
  "model": {
7
7
  "version": "r3-spatial-1",
8
8
  "inputs": [
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "figure-v3-asset-1",
2
+ "version": "figure-asset-1",
3
3
  "name": "ROBOT_ARM_6DOF",
4
4
  "document": {
5
- "version": "figure-v3-prototype-1",
5
+ "version": "figure-prototype-1",
6
6
  "model": {
7
7
  "version": "r3-spatial-1",
8
8
  "inputs": [
@@ -9,16 +9,16 @@ import assert from 'node:assert/strict'
9
9
  import { readFileSync } from 'node:fs'
10
10
  import { fileURLToPath } from 'node:url'
11
11
 
12
- import { addV3Box, addV3Primitive, applyV3Authoring, compileV3Asset, createV3Asset, createV3EditorWorkspace } from '@hatiolab/figure-model'
12
+ import { addFigureBox, addFigurePrimitive, applyFigureAuthoring, compileFigureAsset, createFigureAsset, createFigureEditorWorkspace } from '@hatiolab/figure-model'
13
13
 
14
- import { gizmoChanges, gizmoReachOf } from '../client/modeller/v3-gizmo.ts'
14
+ import { gizmoChanges, gizmoReachOf } 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')
18
18
  const probe = () => {
19
- let asset = createV3Asset('Probe')
20
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
21
- asset = addV3Primitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 150, z: 0 } })
19
+ let asset = createFigureAsset('Probe')
20
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
21
+ asset = addFigurePrimitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 150, z: 0 } })
22
22
  return asset
23
23
  }
24
24
  const apply = (asset: ReturnType<typeof probe>, changes: ReturnType<typeof gizmoChanges>) => {
@@ -27,7 +27,7 @@ const apply = (asset: ReturnType<typeof probe>, changes: ReturnType<typeof gizmo
27
27
  return next
28
28
  }
29
29
  const pose = (asset: ReturnType<typeof probe>, id: string) =>
30
- (compileV3Asset(asset).evaluate().geometry.find(g => (g as { id: string }).id === id) as unknown as { pose: { t: number[]; r: number[][] } }).pose
30
+ (compileFigureAsset(asset).evaluate().geometry.find(g => (g as { id: string }).id === id) as unknown as { pose: { t: number[]; r: number[][] } }).pose
31
31
 
32
32
  test('만든 상자는 셋 다, 원기둥도 셋 다 — 원기둥의 크기는 반지름과 길이로', () => {
33
33
  assert.deepEqual(gizmoReachOf(probe() as never, 'body'), {
@@ -40,9 +40,9 @@ test('만든 상자는 셋 다, 원기둥도 셋 다 — 원기둥의 크기는
40
40
  })
41
41
 
42
42
  test('편집기의 도형 팔레트로 만든 상자 · 둥근 상자도 크기를 낸다 — 입력 이름이 dimension.* 이다', () => {
43
- let asset = createV3Asset('Probe')
44
- asset = addV3Primitive(asset, { id: 'box-1', kind: 'box', dimensions: { width: 100, height: 100, depth: 100 }, position: { x: 0, y: 50, z: 0 } })
45
- asset = addV3Primitive(asset, { id: 'rb-1', kind: 'rounded-box', dimensions: { width: 100, height: 60, depth: 80, round: 10 }, position: { x: 0, y: 50, z: 0 } })
43
+ let asset = createFigureAsset('Probe')
44
+ asset = addFigurePrimitive(asset, { id: 'box-1', kind: 'box', dimensions: { width: 100, height: 100, depth: 100 }, position: { x: 0, y: 50, z: 0 } })
45
+ asset = addFigurePrimitive(asset, { id: 'rb-1', kind: 'rounded-box', dimensions: { width: 100, height: 60, depth: 80, round: 10 }, position: { x: 0, y: 50, z: 0 } })
46
46
  const box = gizmoReachOf(asset as never, 'box-1')
47
47
  assert.equal(box.scale, true, '편집기에서 만든 상자를 키울 수 없었다 (2026-09-24, :3300)')
48
48
  const changes = gizmoChanges('rb-1', gizmoReachOf(asset as never, 'rb-1'), { scale: [2, 1, 0.5] }, asset.designInputs)
@@ -51,9 +51,9 @@ test('편집기의 도형 팔레트로 만든 상자 · 둥근 상자도 크기
51
51
 
52
52
  test('다른 부품에 붙인 부품은 옮기지 않는다 — 다음 평가가 제자리로 돌려놓을 것을 움직이지 않는다', () => {
53
53
  /* attach takes box-shaped parts only. */
54
- const lidded = addV3Box(probe(), { id: 'lid', size: { x: 100, y: 20, z: 100 }, position: { x: 0, y: 300, z: 0 } })
54
+ const lidded = addFigureBox(probe(), { id: 'lid', size: { x: 100, y: 20, z: 100 }, position: { x: 0, y: 300, z: 0 } })
55
55
  assert.equal(gizmoReachOf(lidded as never, 'lid').translate, true, '전제: 붙이기 전에는 옮길 수 있다')
56
- const fastened = applyV3Authoring(lidded, { kind: 'attach', part: 'lid', face: 'bottom', to: { part: 'body', face: 'top' } })
56
+ const fastened = applyFigureAuthoring(lidded, { kind: 'attach', part: 'lid', face: 'bottom', to: { part: 'body', face: 'top' } })
57
57
  assert.equal(gizmoReachOf(fastened as never, 'lid').translate, false)
58
58
  })
59
59
 
@@ -79,16 +79,16 @@ test('크기는 배율만큼 치수를 바꾸고 1mm 아래로 가지 않는다'
79
79
  })
80
80
 
81
81
  test('끈 것은 세션의 한 걸음이다 — 한 번의 되돌리기로 돌아간다', () => {
82
- const workspace = createV3EditorWorkspace() as unknown as Record<string, Function>
82
+ const workspace = createFigureEditorWorkspace() as unknown as Record<string, Function>
83
83
  assert.ok(typeof workspace.apply === 'function')
84
- const editor = read('../client/modeller/v3-editor.ts')
84
+ const editor = read('../client/modeller/editor.ts')
85
85
  const moved = editor.slice(editor.indexOf(' private transformed('), editor.indexOf(' /** 맞추기'))
86
86
  assert.ok(moved.includes('this.run(() => this.session!.apply(changes))'), 'one apply, one step')
87
87
  assert.ok(moved.includes("kind: 'nudge-part',"), 'a nudge is one command, one step')
88
88
  })
89
89
 
90
90
  test('캔버스 — 기즈모 · W E R · 스냅은 씬의 편집 모드가 한다', () => {
91
- const canvas = read('../client/modeller/v3-canvas.ts')
91
+ const canvas = read('../client/modeller/canvas.ts')
92
92
  assert.match(canvas, /mode: 1\n/)
93
93
  assert.doesNotMatch(canvas, /TransformControls|OrbitControls/)
94
94
  })
@@ -102,16 +102,16 @@ test('기즈모의 말은 다섯 말 모두에 있다', () => {
102
102
 
103
103
  test('떼어 낸 부품은 다시 옮길 수 있다 — 움직임이 있는 부품은 아니다', () => {
104
104
  let asset = probe()
105
- asset = addV3Box(asset, { id: 'lid', size: { x: 100, y: 20, z: 100 }, position: { x: 0, y: 300, z: 0 } })
106
- const fastened = applyV3Authoring(asset, { kind: 'attach', part: 'lid', face: 'bottom', to: { part: 'body', face: 'top' } })
107
- const free = applyV3Authoring(fastened, { kind: 'detach', part: 'lid' })
105
+ asset = addFigureBox(asset, { id: 'lid', size: { x: 100, y: 20, z: 100 }, position: { x: 0, y: 300, z: 0 } })
106
+ const fastened = applyFigureAuthoring(asset, { kind: 'attach', part: 'lid', face: 'bottom', to: { part: 'body', face: 'top' } })
107
+ const free = applyFigureAuthoring(fastened, { kind: 'detach', part: 'lid' })
108
108
  assert.equal(gizmoReachOf(free as never, 'lid').translate, true)
109
109
  const slid = moving(free, 'lid', { kind: 'slide', axis: 'x', id: 'lid-slide', min: 0, max: 100, start: 0 })
110
- assert.equal(gizmoReachOf(slid as never, 'lid').translate, false, 'not its own numbers: a drag on a joint edits the joint (v3-joint-drag)')
110
+ assert.equal(gizmoReachOf(slid as never, 'lid').translate, false, 'not its own numbers: a drag on a joint edits the joint (joint-drag)')
111
111
  })
112
112
 
113
113
  test('끄는 동안은 모아 두고 놓을 때 한 번 적는다 — 크기 기즈모가 편집기와 싸우지 않는다', () => {
114
- const canvas = read('../client/modeller/v3-canvas.ts')
114
+ const canvas = read('../client/modeller/canvas.ts')
115
115
  const watch = canvas.slice(canvas.indexOf(' private watchScene()'), canvas.indexOf(' /** Components the gizmo changed'))
116
116
  assert.ok(watch.includes('this.pending.set(name, origin!)') && watch.includes('if (!this.pointerDown) this.commitSoon(80)'), '사용자 2026-09-24: 「기즈모 컨트롤 중에 scale은 제대로 작동하지 않는군」')
117
117
  assert.ok(!watch.includes("new CustomEvent('part-transformed'"), '움직일 때마다 알리지 않는다')
@@ -120,12 +120,12 @@ test('끄는 동안은 모아 두고 놓을 때 한 번 적는다 — 크기 기
120
120
 
121
121
 
122
122
  test('원기둥 · 구도 크기 기즈모가 먹는다 — 단면은 원으로', async () => {
123
- const { addV3Primitive, createV3Asset } = await import('@hatiolab/figure-model')
124
- const { paletteRecipe } = await import('../client/modeller/v3-palette.ts')
125
- const { gizmoChanges, gizmoReachOf } = await import('../client/modeller/v3-gizmo.ts')
126
- let asset = createV3Asset('Round') as any
127
- asset = addV3Primitive(asset, paletteRecipe('cylinder', 'drum') as any)
128
- asset = addV3Primitive(asset, paletteRecipe('sphere', 'ball') as any)
123
+ const { addFigurePrimitive, createFigureAsset } = await import('@hatiolab/figure-model')
124
+ const { paletteRecipe } = await import('../client/modeller/palette.ts')
125
+ const { gizmoChanges, gizmoReachOf } = await import('../client/modeller/gizmo.ts')
126
+ let asset = createFigureAsset('Round') as any
127
+ asset = addFigurePrimitive(asset, paletteRecipe('cylinder', 'drum') as any)
128
+ asset = addFigurePrimitive(asset, paletteRecipe('sphere', 'ball') as any)
129
129
  const values = { ...asset.designInputs }
130
130
  const drum = gizmoReachOf(asset, 'drum')
131
131
  assert.equal(drum.scale, true, '원기둥도 크기를 받는다')
@@ -144,13 +144,13 @@ test('원기둥 · 구도 크기 기즈모가 먹는다 — 단면은 원으로'
144
144
  * 묻혔다(09-26). 돌려 세운 부품은 그 높이가 위아래가 아니라 건드리지 않는다.
145
145
  */
146
146
  test('높이 칸 — 아랫면을 지킨다, 손잡이처럼', async () => {
147
- const { keepBottomChanges } = await import('../client/modeller/v3-gizmo.ts')
148
- let asset = createV3Asset('Post')
149
- asset = addV3Box(asset, { id: 'post', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
150
- asset = addV3Primitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 300, z: 0 } })
151
- asset = addV3Primitive(asset, { id: 'roller', kind: 'cylinder', dimensions: { radius: 30, length: 500 }, position: { x: 0, y: 30, z: 0 }, rotation: { x: 90, y: 0, z: 0 } })
147
+ const { keepBottomChanges } = await import('../client/modeller/gizmo.ts')
148
+ let asset = createFigureAsset('Post')
149
+ asset = addFigureBox(asset, { id: 'post', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
150
+ asset = addFigurePrimitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 300, z: 0 } })
151
+ asset = addFigurePrimitive(asset, { id: 'roller', kind: 'cylinder', dimensions: { radius: 30, length: 500 }, position: { x: 0, y: 30, z: 0 }, rotation: { x: 90, y: 0, z: 0 } })
152
152
  const bottom = (a: typeof asset, part: string) => {
153
- const g = compileV3Asset(a).evaluate().geometry.find(o => o.id === part)! as unknown as { pose: { t: number[] } }
153
+ const g = compileFigureAsset(a).evaluate().geometry.find(o => o.id === part)! as unknown as { pose: { t: number[] } }
154
154
  return g.pose.t[1]!
155
155
  }
156
156
  const grow = (part: string, input: string, value: number) => {
@@ -166,12 +166,12 @@ test('높이 칸 — 아랫면을 지킨다, 손잡이처럼', async () => {
166
166
  const rollerLength = gizmoReachOf(asset, 'roller').round!.along[0]!
167
167
  assert.deepEqual(keepBottomChanges('roller', gizmoReachOf(asset, 'roller'), rollerLength, 800, asset.designInputs), [], '누운 롤러의 길이는 위아래가 아니다')
168
168
  assert.deepEqual(keepBottomChanges('post', gizmoReachOf(asset, 'post'), gizmoReachOf(asset, 'post').sizes![0], 800, asset.designInputs), [], '폭은 가운데 그대로')
169
- assert.ok(read('../client/modeller/v3-editor.ts').includes('keepBottomChanges('), '숫자 칸이 부른다')
169
+ assert.ok(read('../client/modeller/editor.ts').includes('keepBottomChanges('), '숫자 칸이 부른다')
170
170
  })
171
171
 
172
172
  /* 손잡이는 키를 몰라도 찾는다 — 도구 막대에 옮기기 · 돌리기 · 크기, 제 숫자로 놓인 부품에만 (09-26). */
173
173
  test('도구 막대의 옮기기 · 돌리기 · 크기 — 키와 같은 길, 되쓸 수 있는 것만', () => {
174
- const editor = read('../client/modeller/v3-editor.ts')
174
+ const editor = read('../client/modeller/editor.ts')
175
175
  const bar = editor.slice(editor.indexOf('return html`<ox-canvas-bar'), editor.indexOf('</ox-canvas-bar>'))
176
176
  assert.ok(bar.includes("this.renderGizmoTools(!unavailable && this.middle === 'edit')"), '편집 중에만')
177
177
  const tools = editor.slice(editor.indexOf(' private renderGizmoTools('), editor.indexOf(" /** The modeller's keys"))
@@ -5,24 +5,24 @@
5
5
  import { test } from 'node:test'
6
6
  import assert from 'node:assert/strict'
7
7
 
8
- import { addV3Box, applyV3Authoring, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
9
- import type { V3Asset, V3Joint } from '@hatiolab/figure-model'
10
- import { jointDragOf } from '../client/modeller/v3-joint-drag.ts'
8
+ import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
9
+ import type { FigureAsset, FigureJoint } from '@hatiolab/figure-model'
10
+ import { jointDragOf } from '../client/modeller/joint-drag.ts'
11
11
 
12
12
  /** A post on the floor, and a link on its top. */
13
- function column(joint: Partial<V3Joint>): V3Asset {
14
- let asset = createV3Asset('Column')
15
- asset = addV3Box(asset, { id: 'post', size: { x: 100, y: 200, z: 100 }, position: { x: 0, y: 100, z: 0 } })
16
- asset = addV3Box(asset, { id: 'link', size: { x: 60, y: 300, z: 60 }, position: { x: 0, y: 350, z: 0 } })
17
- asset = applyV3Authoring(asset, { kind: 'attach', part: 'post', face: 'bottom', to: { plane: 'mounting-plane' } })
18
- return applyV3Authoring(asset, {
13
+ function column(joint: Partial<FigureJoint>): FigureAsset {
14
+ let asset = createFigureAsset('Column')
15
+ asset = addFigureBox(asset, { id: 'post', size: { x: 100, y: 200, z: 100 }, position: { x: 0, y: 100, z: 0 } })
16
+ asset = addFigureBox(asset, { id: 'link', size: { x: 60, y: 300, z: 60 }, position: { x: 0, y: 350, z: 0 } })
17
+ asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'post', face: 'bottom', to: { plane: 'mounting-plane' } })
18
+ return applyFigureAuthoring(asset, {
19
19
  kind: 'set-joint',
20
- joint: { id: 'bend', type: 'fixed', body0: { part: 'post', face: 'top' }, body1: { part: 'link', face: 'bottom' }, ...joint } as V3Joint
20
+ joint: { id: 'bend', type: 'fixed', body0: { part: 'post', face: 'top' }, body1: { part: 'link', face: 'bottom' }, ...joint } as FigureJoint
21
21
  })
22
22
  }
23
23
 
24
- const drawn = (asset: V3Asset, id: string, state: Record<string, number> = {}) =>
25
- compileV3Asset(asset).evaluate(state).geometry.find(g => g.id === id) as never as { id: string; pose: { t: number[]; r: number[][] } }
24
+ const drawn = (asset: FigureAsset, id: string, state: Record<string, number> = {}) =>
25
+ compileFigureAsset(asset).evaluate(state).geometry.find(g => g.id === id) as never as { id: string; pose: { t: number[]; r: number[][] } }
26
26
 
27
27
  /** The link turned `deg` about Z, as the gizmo would report it (degrees, XYZ). */
28
28
  const turnedZ = (deg: number) => [0, 0, deg] as [number, number, number]
@@ -58,7 +58,7 @@ test('고정 관절에 달린 부품을 옮기면 관절이 놓인 자리 —
58
58
  const up = jointDragOf(asset, 'link', part as never, { position: [part.pose.t[0]!, part.pose.t[1]! + 25, part.pose.t[2]!] }, {})
59
59
  assert.deepEqual(up, { kind: 'joint', joint: { ...asset.joints!.find(j => j.id === 'bend')!, origin: { gap: 25 } } })
60
60
  const aside = jointDragOf(asset, 'link', part as never, { position: [part.pose.t[0]! + 30, part.pose.t[1]!, part.pose.t[2]!] }, {})
61
- assert.deepEqual((aside as { joint: V3Joint }).joint.origin, { align: { x: { mm: 30 } } })
61
+ assert.deepEqual((aside as { joint: FigureJoint }).joint.origin, { align: { x: { mm: 30 } } })
62
62
  })
63
63
 
64
64
  test('관계를 끊는 끌기는 거절하고 까닭을 댄다', () => {
@@ -72,16 +72,16 @@ test('관계를 끊는 끌기는 거절하고 까닭을 댄다', () => {
72
72
  })
73
73
 
74
74
  test('관절이 없는 부품은 이 길로 오지 않는다 — 제 숫자를 쓰는 길', () => {
75
- let asset = createV3Asset('Free')
76
- asset = addV3Box(asset, { id: 'box', size: { x: 10, y: 10, z: 10 }, position: { x: 0, y: 5, z: 0 } })
75
+ let asset = createFigureAsset('Free')
76
+ asset = addFigureBox(asset, { id: 'box', size: { x: 10, y: 10, z: 10 }, position: { x: 0, y: 5, z: 0 } })
77
77
  assert.equal(jointDragOf(asset, 'box', drawn(asset, 'box') as never, { position: [10, 5, 0] }, {}), null)
78
78
  })
79
79
 
80
80
  test('크기 끌기는 관절이 아니라 부품의 치수 — 따라가는 치수는 두고 제 치수만 쓴다 (사용자 09-25: 「R 모드에서 크기가 변하지 않는다」)', async () => {
81
- const { gizmoChanges, gizmoReachOf } = await import('../client/modeller/v3-gizmo.ts')
81
+ const { gizmoChanges, gizmoReachOf } = await import('../client/modeller/gizmo.ts')
82
82
  let asset = column({ type: 'revolute', axis: 'Z', lowerLimit: -90, upperLimit: 90 })
83
83
  /* A square section: the link's depth follows its width. */
84
- asset = applyV3Authoring(asset, { kind: 'link-dimension', part: 'link', axis: 'z', source: { of: 'part', part: 'link', axis: 'x' } })
84
+ asset = applyFigureAuthoring(asset, { kind: 'link-dimension', part: 'link', axis: 'z', source: { of: 'part', part: 'link', axis: 'x' } })
85
85
  const part = drawn(asset, 'link')
86
86
  const scaled = { position: [part.pose.t[0]!, part.pose.t[1]! + 60, part.pose.t[2]!] as [number, number, number], scale: [1, 1.4, 1] as [number, number, number] }
87
87
  assert.equal(jointDragOf(asset, 'link', part as never, scaled, {}), null, '관절 자리로 받지 않는다')
@@ -8,20 +8,20 @@ import assert from 'node:assert/strict'
8
8
  import { readFileSync } from 'node:fs'
9
9
  import { fileURLToPath } from 'node:url'
10
10
 
11
- import { addV3Box, applyV3Authoring, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
12
- import type { V3Asset } from '@hatiolab/figure-model'
11
+ import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
12
+ import type { FigureAsset } from '@hatiolab/figure-model'
13
13
 
14
- import { JOINT_FAN, jointFanOf } from '../client/modeller/v3-joint-fan.ts'
14
+ import { JOINT_FAN, jointFanOf } from '../client/modeller/joint-fan.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')
18
18
 
19
- function arm(): V3Asset {
20
- let asset = createV3Asset('Probe')
21
- asset = addV3Box(asset, { id: 'base', size: { x: 200, y: 100, z: 200 }, position: { x: 0, y: 50, z: 0 } })
22
- asset = addV3Box(asset, { id: 'arm', size: { x: 400, y: 50, z: 50 }, position: { x: 200, y: 125, z: 0 } })
19
+ function arm(): FigureAsset {
20
+ let asset = createFigureAsset('Probe')
21
+ asset = addFigureBox(asset, { id: 'base', size: { x: 200, y: 100, z: 200 }, position: { x: 0, y: 50, z: 0 } })
22
+ asset = addFigureBox(asset, { id: 'arm', size: { x: 400, y: 50, z: 50 }, position: { x: 200, y: 125, z: 0 } })
23
23
  /* On the base's top, level with its left edge: it turns about that edge, at x = -100. */
24
- asset = applyV3Authoring(asset, { kind: 'attach', part: 'arm', face: 'bottom', to: { part: 'base', face: 'top' }, align: { x: 'min' } })
24
+ asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'arm', face: 'bottom', to: { part: 'base', face: 'top' }, align: { x: 'min' } })
25
25
  return moving(asset, 'arm', { kind: 'turn', axis: 'y', id: 'arm.turn', min: -45, max: 90 })
26
26
  }
27
27
  const PIVOT = [-100, 100, 0]
@@ -48,7 +48,7 @@ test('a part that turns gets a fan from the lowest value to the highest, where t
48
48
  assert.deepEqual(profile[0], { x: 0, y: 0 }, 'not a full turn: the fan starts at the pivot')
49
49
 
50
50
  const armAt = (deg: number) => {
51
- const body = compileV3Asset({ ...asset, stateDefaults: { ...asset.stateDefaults, 'arm.turn': deg } }).evaluate().geometry.find(g => g.id === 'arm')!
51
+ const body = compileFigureAsset({ ...asset, stateDefaults: { ...asset.stateDefaults, 'arm.turn': deg } }).evaluate().geometry.find(g => g.id === 'arm')!
52
52
  return direction((body as unknown as { pose: { t: number[] } }).pose.t)
53
53
  }
54
54
  const first = direction(world(fan, profile[1]!))
@@ -73,17 +73,17 @@ test('no fan for a part that does not turn, nor for a slide -- a fan in a guesse
73
73
  const ofArm = jointFanOf(arm(), 'arm') as unknown as { shape: { profile: unknown[] } }
74
74
  assert.deepEqual(onBase?.shape.profile, ofArm.shape.profile)
75
75
  assert.equal(jointFanOf(arm(), ''), null)
76
- let slider = createV3Asset('Probe')
77
- slider = addV3Box(slider, { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
76
+ let slider = createFigureAsset('Probe')
77
+ slider = addFigureBox(slider, { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
78
78
  slider = moving(slider, 'block', { kind: 'slide', axis: 'x', id: 'shift', min: 0, max: 60 })
79
79
  assert.equal(jointFanOf(slider, 'block'), null)
80
80
  })
81
81
 
82
82
  test('the editor draws it for the selected part, with the pose in hand; the canvas never lets it be picked', () => {
83
- const editor = read('../client/modeller/v3-editor.ts')
84
- assert.ok(editor.includes('.fan=${this.fanOf(asset as V3Asset | undefined)}'))
83
+ const editor = read('../client/modeller/editor.ts')
84
+ assert.ok(editor.includes('.fan=${this.fanOf(asset as FigureAsset | undefined)}'))
85
85
  assert.ok(editor.includes('return jointFanOf(asset, this.selected, { ...this.currentInputs(), ...this.live.values })'), 'the fan reads the pose the session holds')
86
- const canvas = read('../client/modeller/v3-canvas.ts')
86
+ const canvas = read('../client/modeller/canvas.ts')
87
87
  assert.ok(canvas.includes('{ ...fan, capturable: false, locked: true }'))
88
88
  })
89
89
 
@@ -94,8 +94,8 @@ test('the editor draws it for the selected part, with the pose in hand; the canv
94
94
  * the hinge centred on the wrist's axis (as ROBOT_ARM's elbow-housing is the elbow's); `badge` rides the hinge and holds
95
95
  * no pivot.
96
96
  */
97
- const fixture = (name: string): V3Asset => JSON.parse(readFileSync(fileURLToPath(new URL(`./fixtures/${name}.json`, import.meta.url)), 'utf8'))
98
- function column(): V3Asset {
97
+ const fixture = (name: string): FigureAsset => JSON.parse(readFileSync(fileURLToPath(new URL(`./fixtures/${name}.json`, import.meta.url)), 'utf8'))
98
+ function column(): FigureAsset {
99
99
  return fixture('joint-column')
100
100
  }
101
101
 
@@ -124,7 +124,7 @@ for (const [part, joint] of [
124
124
  const pivot = fan.pose.t
125
125
  const axis = [0, 1, 2].map(i => fan.pose.r[i]![1]!)
126
126
  const at = (s: number) =>
127
- compileV3Asset({ ...asset, stateDefaults: { ...asset.stateDefaults, [joint]: s } }).evaluate().geometry.find(g => g.id === part) as unknown as { pose: { t: number[]; r: number[][] } }
127
+ compileFigureAsset({ ...asset, stateDefaults: { ...asset.stateDefaults, [joint]: s } }).evaluate().geometry.find(g => g.id === part) as unknown as { pose: { t: number[]; r: number[][] } }
128
128
  const onFan = (p: { x: number; y: number }) => [0, 1, 2].map(i => pivot[i]! + fan.pose.r[i]![0]! * p.x + fan.pose.r[i]![2]! * p.y)
129
129
  const flat = (v: number[]) => sub(v, axis.map(a => a * dot(v, axis)))
130
130
 
@@ -160,7 +160,7 @@ test('a housing shows the joint it houses, not the one that carries it; a part t
160
160
  const axis = [0, 1, 2].map(i => fan.pose.r[i]![1]!)
161
161
  const flat = (v: number[]) => sub(v, axis.map(a => a * dot(v, axis)))
162
162
  const onFan = (p: { x: number; y: number }) => [0, 1, 2].map(i => fan.pose.t[i]! + fan.pose.r[i]![0]! * p.x + fan.pose.r[i]![2]! * p.y)
163
- const tipAt = (s: number) => (compileV3Asset({ ...asset, stateDefaults: { ...asset.stateDefaults, wrist: s } }).evaluate().geometry.find(g => g.id === 'tip') as unknown as { pose: { t: number[] } }).pose.t
163
+ const tipAt = (s: number) => (compileFigureAsset({ ...asset, stateDefaults: { ...asset.stateDefaults, wrist: s } }).evaluate().geometry.find(g => g.id === 'tip') as unknown as { pose: { t: number[] } }).pose.t
164
164
  const edges = fan.shape.profile.filter(p => p.x !== 0 || p.y !== 0)
165
165
  assert.ok(angle(flat(sub(onFan(edges[0]!), fan.pose.t)), flat(sub(tipAt(-120), fan.pose.t))) < 0.01)
166
166
  assert.ok(angle(flat(sub(onFan(edges[edges.length - 1]!), fan.pose.t)), flat(sub(tipAt(120), fan.pose.t))) < 0.01)
package/test/joints.ts CHANGED
@@ -2,24 +2,24 @@
2
2
  * Moving a part in a test, as the modeller does it now (ADR-0093): the joint above the part is given a turn or a
3
3
  * slide. A part fastened to nothing is stood on the floor first -- a joint has a parent.
4
4
  */
5
- import { applyV3Authoring } from '@hatiolab/figure-model'
6
- import type { V3Asset, V3Joint } from '@hatiolab/figure-model'
5
+ import { applyFigureAuthoring } from '@hatiolab/figure-model'
6
+ import type { FigureAsset, FigureJoint } from '@hatiolab/figure-model'
7
7
 
8
8
  export function moving(
9
- asset: V3Asset,
9
+ asset: FigureAsset,
10
10
  part: string,
11
11
  spec: { kind: 'slide' | 'turn'; axis: 'x' | 'y' | 'z'; id: string; min: number; max: number; start?: number }
12
- ): V3Asset {
12
+ ): FigureAsset {
13
13
  /* Stood on the floor where it is: its X and Z kept as offsets on the plane. */
14
14
  const at = (axis: 'x' | 'z') => ({ mm: asset.designInputs[`${part}.position.${axis}`] ?? 0 })
15
- const above: V3Joint = asset.joints?.find(j => j.body1.part === part) ?? {
15
+ const above: FigureJoint = asset.joints?.find(j => j.body1.part === part) ?? {
16
16
  id: part,
17
17
  type: 'fixed',
18
18
  body0: { plane: 'mounting-plane' },
19
19
  body1: { part, face: 'bottom' },
20
20
  origin: { align: { x: at('x'), z: at('z') } }
21
21
  }
22
- return applyV3Authoring(asset, {
22
+ return applyFigureAuthoring(asset, {
23
23
  kind: 'set-joint',
24
24
  joint: {
25
25
  ...above,
@@ -6,7 +6,7 @@ import assert from 'node:assert/strict'
6
6
  import { readFileSync } from 'node:fs'
7
7
  import { fileURLToPath } from 'node:url'
8
8
 
9
- import { keyActionOf } from '../client/modeller/v3-keys.ts'
9
+ import { keyActionOf } from '../client/modeller/keys.ts'
10
10
 
11
11
  test('되돌리기 · 다시 하기 — Ctrl 과 Cmd 모두, 부품을 고르지 않아도', () => {
12
12
  assert.equal(keyActionOf({ key: 'z', ctrlKey: true }, ''), 'undo')
@@ -31,7 +31,7 @@ test('칸에 치는 키는 칸의 것 — 이름 칸의 Backspace 는 글자를
31
31
  })
32
32
 
33
33
  test('편집기가 창의 키를 듣고, 떼어지면 놓는다', () => {
34
- const editor = readFileSync(fileURLToPath(new URL('../client/modeller/v3-editor.ts', import.meta.url)), 'utf8')
34
+ const editor = readFileSync(fileURLToPath(new URL('../client/modeller/editor.ts', import.meta.url)), 'utf8')
35
35
  assert.ok(editor.includes("window.addEventListener('keydown', this.onKey, true)") && editor.includes("window.removeEventListener('keydown', this.onKey, true)"))
36
36
  })
37
37