@things-factory/figure-ui 10.1.74 → 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 (244) 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} +137 -126
  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-refusals.ts → refusals.ts} +62 -13
  37. package/client/modeller/{v3-renderer.ts → renderer.ts} +16 -16
  38. package/client/modeller/{v3-stage-model.ts → stage-model.ts} +15 -15
  39. package/client/modeller/{v3-thumbnail.ts → thumbnail.ts} +8 -8
  40. package/client/pages/figure-list-page.ts +4 -4
  41. package/client/pages/{figure-v3-modeller-page.ts → figure-modeller-page.ts} +37 -37
  42. package/client/route.ts +1 -5
  43. package/dist-client/figure-assistant.js +3 -3
  44. package/dist-client/figure-assistant.js.map +1 -1
  45. package/dist-client/graphql/store.d.ts +3 -0
  46. package/dist-client/graphql/store.js +39 -0
  47. package/dist-client/graphql/store.js.map +1 -0
  48. package/dist-client/modeller/ai-proposal.d.ts +51 -0
  49. package/dist-client/modeller/ai-proposal.js +64 -0
  50. package/dist-client/modeller/ai-proposal.js.map +1 -0
  51. package/dist-client/modeller/align.d.ts +30 -0
  52. package/dist-client/modeller/align.js +75 -0
  53. package/dist-client/modeller/align.js.map +1 -0
  54. package/dist-client/modeller/canvas.d.ts +72 -0
  55. package/dist-client/modeller/canvas.js +379 -0
  56. package/dist-client/modeller/canvas.js.map +1 -0
  57. package/dist-client/modeller/clips.d.ts +39 -0
  58. package/dist-client/modeller/clips.js +58 -0
  59. package/dist-client/modeller/clips.js.map +1 -0
  60. package/dist-client/modeller/controls.d.ts +28 -0
  61. package/dist-client/modeller/controls.js +28 -0
  62. package/dist-client/modeller/controls.js.map +1 -0
  63. package/dist-client/modeller/editor.d.ts +203 -0
  64. package/dist-client/modeller/editor.js +1526 -0
  65. package/dist-client/modeller/editor.js.map +1 -0
  66. package/dist-client/modeller/fields.d.ts +45 -0
  67. package/dist-client/modeller/fields.js +83 -0
  68. package/dist-client/modeller/fields.js.map +1 -0
  69. package/dist-client/modeller/figure-capability-panel.d.ts +22 -0
  70. package/dist-client/modeller/figure-capability-panel.js +141 -0
  71. package/dist-client/modeller/figure-capability-panel.js.map +1 -0
  72. package/dist-client/modeller/figure-clips.d.ts +31 -0
  73. package/dist-client/modeller/figure-clips.js +268 -0
  74. package/dist-client/modeller/figure-clips.js.map +1 -0
  75. package/dist-client/modeller/figure-controls.d.ts +36 -0
  76. package/dist-client/modeller/figure-controls.js +226 -0
  77. package/dist-client/modeller/figure-controls.js.map +1 -0
  78. package/dist-client/modeller/figure-facts.d.ts +35 -0
  79. package/dist-client/modeller/figure-facts.js +55 -0
  80. package/dist-client/modeller/figure-facts.js.map +1 -0
  81. package/dist-client/modeller/figure-first-save.d.ts +20 -0
  82. package/dist-client/modeller/figure-first-save.js +118 -0
  83. package/dist-client/modeller/figure-first-save.js.map +1 -0
  84. package/dist-client/modeller/figure-history-panel.d.ts +1 -1
  85. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  86. package/dist-client/modeller/figure-links.d.ts +77 -0
  87. package/dist-client/modeller/figure-links.js +649 -0
  88. package/dist-client/modeller/figure-links.js.map +1 -0
  89. package/dist-client/modeller/figure-material-panel.d.ts +39 -0
  90. package/dist-client/modeller/figure-material-panel.js +323 -0
  91. package/dist-client/modeller/figure-material-panel.js.map +1 -0
  92. package/dist-client/modeller/figure-parts.d.ts +69 -0
  93. package/dist-client/modeller/figure-parts.js +559 -0
  94. package/dist-client/modeller/figure-parts.js.map +1 -0
  95. package/dist-client/modeller/figure-preview.d.ts +34 -0
  96. package/dist-client/modeller/figure-preview.js +346 -0
  97. package/dist-client/modeller/figure-preview.js.map +1 -0
  98. package/dist-client/modeller/figure-properties.d.ts +45 -0
  99. package/dist-client/modeller/figure-properties.js +239 -0
  100. package/dist-client/modeller/figure-properties.js.map +1 -0
  101. package/dist-client/modeller/figure-recipe-catalog.d.ts +38 -0
  102. package/dist-client/modeller/figure-recipe-catalog.js +399 -0
  103. package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
  104. package/dist-client/modeller/figure-verdict.d.ts +64 -0
  105. package/dist-client/modeller/figure-verdict.js +787 -0
  106. package/dist-client/modeller/figure-verdict.js.map +1 -0
  107. package/dist-client/modeller/first-save.d.ts +17 -0
  108. package/dist-client/modeller/first-save.js +26 -0
  109. package/dist-client/modeller/first-save.js.map +1 -0
  110. package/dist-client/modeller/gate-findings.d.ts +2 -2
  111. package/dist-client/modeller/gate-findings.js +2 -2
  112. package/dist-client/modeller/gate-findings.js.map +1 -1
  113. package/dist-client/modeller/gizmo.d.ts +72 -0
  114. package/dist-client/modeller/gizmo.js +111 -0
  115. package/dist-client/modeller/gizmo.js.map +1 -0
  116. package/dist-client/modeller/help.d.ts +18 -0
  117. package/dist-client/modeller/help.js +37 -0
  118. package/dist-client/modeller/help.js.map +1 -0
  119. package/dist-client/modeller/joint-drag.d.ts +19 -0
  120. package/dist-client/modeller/joint-drag.js +97 -0
  121. package/dist-client/modeller/joint-drag.js.map +1 -0
  122. package/dist-client/modeller/joint-fan.d.ts +9 -0
  123. package/dist-client/modeller/joint-fan.js +276 -0
  124. package/dist-client/modeller/joint-fan.js.map +1 -0
  125. package/dist-client/modeller/keys.d.ts +22 -0
  126. package/dist-client/modeller/keys.js +26 -0
  127. package/dist-client/modeller/keys.js.map +1 -0
  128. package/dist-client/modeller/links.d.ts +267 -0
  129. package/dist-client/modeller/links.js +480 -0
  130. package/dist-client/modeller/links.js.map +1 -0
  131. package/dist-client/modeller/live.d.ts +37 -0
  132. package/dist-client/modeller/live.js +68 -0
  133. package/dist-client/modeller/live.js.map +1 -0
  134. package/dist-client/modeller/maturity.d.ts +120 -0
  135. package/dist-client/modeller/maturity.js +313 -0
  136. package/dist-client/modeller/maturity.js.map +1 -0
  137. package/dist-client/modeller/modeller-route.d.ts +1 -1
  138. package/dist-client/modeller/modeller-route.js +1 -1
  139. package/dist-client/modeller/modeller-route.js.map +1 -1
  140. package/dist-client/modeller/occupancy.d.ts +18 -0
  141. package/dist-client/modeller/occupancy.js +24 -0
  142. package/dist-client/modeller/occupancy.js.map +1 -0
  143. package/dist-client/modeller/palette.d.ts +7 -0
  144. package/dist-client/modeller/palette.js +46 -0
  145. package/dist-client/modeller/palette.js.map +1 -0
  146. package/dist-client/modeller/preview-model.d.ts +47 -0
  147. package/dist-client/modeller/preview-model.js +68 -0
  148. package/dist-client/modeller/preview-model.js.map +1 -0
  149. package/dist-client/modeller/product-session.d.ts +510 -0
  150. package/dist-client/modeller/product-session.js +157 -0
  151. package/dist-client/modeller/product-session.js.map +1 -0
  152. package/dist-client/modeller/recipe-source.d.ts +8 -0
  153. package/dist-client/modeller/recipe-source.js +14 -0
  154. package/dist-client/modeller/recipe-source.js.map +1 -0
  155. package/dist-client/modeller/recipes.d.ts +37 -0
  156. package/dist-client/modeller/recipes.js +103 -0
  157. package/dist-client/modeller/recipes.js.map +1 -0
  158. package/dist-client/modeller/refusals.d.ts +68 -0
  159. package/dist-client/modeller/refusals.js +142 -0
  160. package/dist-client/modeller/refusals.js.map +1 -0
  161. package/dist-client/modeller/renderer.d.ts +40 -0
  162. package/dist-client/modeller/renderer.js +73 -0
  163. package/dist-client/modeller/renderer.js.map +1 -0
  164. package/dist-client/modeller/stage-model.d.ts +39 -0
  165. package/dist-client/modeller/stage-model.js +147 -0
  166. package/dist-client/modeller/stage-model.js.map +1 -0
  167. package/dist-client/modeller/thumbnail.d.ts +3 -0
  168. package/dist-client/modeller/thumbnail.js +75 -0
  169. package/dist-client/modeller/thumbnail.js.map +1 -0
  170. package/dist-client/modeller/v3-clips.d.ts +1 -1
  171. package/dist-client/modeller/v3-editor.d.ts +5 -0
  172. package/dist-client/modeller/v3-editor.js +21 -10
  173. package/dist-client/modeller/v3-editor.js.map +1 -1
  174. package/dist-client/modeller/v3-product-session.d.ts +31 -0
  175. package/dist-client/modeller/v3-refusals.d.ts +37 -5
  176. package/dist-client/modeller/v3-refusals.js +59 -16
  177. package/dist-client/modeller/v3-refusals.js.map +1 -1
  178. package/dist-client/pages/figure-list-page.d.ts +1 -1
  179. package/dist-client/pages/figure-list-page.js +4 -4
  180. package/dist-client/pages/figure-list-page.js.map +1 -1
  181. package/dist-client/pages/figure-modeller-page.d.ts +102 -0
  182. package/dist-client/pages/figure-modeller-page.js +548 -0
  183. package/dist-client/pages/figure-modeller-page.js.map +1 -0
  184. package/dist-client/route.d.ts +1 -1
  185. package/dist-client/route.js +1 -5
  186. package/dist-client/route.js.map +1 -1
  187. package/dist-client/tsconfig.tsbuildinfo +1 -1
  188. package/docs/product-cutover.md +25 -0
  189. package/package.json +5 -5
  190. package/test/{v3-ai-proposal.test.ts → ai-proposal.test.ts} +6 -6
  191. package/test/{v3-align.test.ts → align.test.ts} +8 -8
  192. package/test/browser/modeller-page.js +30 -30
  193. package/test/browser/modeller.check.mjs +17 -16
  194. package/test/browser/serve.mjs +1 -1
  195. package/test/browser/thumbnail-page.js +6 -6
  196. package/test/{v3-clips.test.ts → clips.test.ts} +18 -18
  197. package/test/{v3-controls.test.ts → controls.test.ts} +15 -15
  198. package/test/{v3-fields.test.ts → fields.test.ts} +13 -13
  199. package/test/figure-assistant-surface.test.ts +3 -3
  200. package/test/{v3-first-save.test.ts → first-save.test.ts} +10 -10
  201. package/test/fixtures/joint-column.json +2 -2
  202. package/test/fixtures/joint-roller.json +2 -2
  203. package/test/fixtures/robot-arm-6dof.json +2 -2
  204. package/test/{v3-gizmo.test.ts → gizmo.test.ts} +33 -33
  205. package/test/{v3-joint-drag.test.ts → joint-drag.test.ts} +17 -17
  206. package/test/{v3-joint-fan.test.ts → joint-fan.test.ts} +18 -18
  207. package/test/joints.ts +6 -6
  208. package/test/{v3-keys.test.ts → keys.test.ts} +2 -2
  209. package/test/{v3-layout.test.ts → layout.test.ts} +8 -8
  210. package/test/{v3-links.test.ts → links.test.ts} +201 -201
  211. package/test/{v3-live.test.ts → live.test.ts} +18 -18
  212. package/test/{v3-material.test.ts → material.test.ts} +12 -12
  213. package/test/{v3-maturity.test.ts → maturity.test.ts} +74 -74
  214. package/test/modeller-route.test.ts +5 -6
  215. package/test/{v3-palette-icons.test.ts → palette-icons.test.ts} +1 -1
  216. package/test/{v3-parts-wiring.test.ts → parts-wiring.test.ts} +11 -7
  217. package/test/{v3-preview.test.ts → preview.test.ts} +12 -12
  218. package/test/{v3-product-session.test.ts → product-session.test.ts} +17 -17
  219. package/test/{v3-product-write.test.ts → product-write.test.ts} +20 -20
  220. package/test/{v3-properties.test.ts → properties.test.ts} +24 -24
  221. package/test/{v3-recipes.test.ts → recipes.test.ts} +10 -10
  222. package/test/refusals.test.ts +166 -0
  223. package/test/{v3-release-panel.test.ts → release-panel.test.ts} +21 -21
  224. package/test/renderer.test.ts +24 -0
  225. package/test/resolve-ts-siblings.mjs +1 -1
  226. package/test/{v3-scene-canvas.test.ts → scene-canvas.test.ts} +6 -6
  227. package/test/{v3-stage-model.test.ts → stage-model.test.ts} +5 -5
  228. package/test/{v3-theme.test.ts → theme.test.ts} +4 -4
  229. package/test/{v3-thumbnail.test.ts → thumbnail.test.ts} +4 -4
  230. package/test/{v3-verdict.test.ts → verdict.test.ts} +17 -17
  231. package/test/{v3-view.test.ts → view.test.ts} +2 -2
  232. package/things-factory.config.js +2 -3
  233. package/translations/en.json +15 -7
  234. package/translations/ja.json +15 -7
  235. package/translations/ko.json +15 -7
  236. package/translations/ms.json +15 -7
  237. package/translations/zh.json +15 -7
  238. package/docs/v3-product-cutover.md +0 -25
  239. package/test/v3-refusals.test.ts +0 -136
  240. package/test/v3-renderer.test.ts +0 -24
  241. /package/client/modeller/{v3-fields.ts → fields.ts} +0 -0
  242. /package/client/modeller/{v3-first-save.ts → first-save.ts} +0 -0
  243. /package/client/modeller/{v3-help.ts → help.ts} +0 -0
  244. /package/client/modeller/{v3-keys.ts → keys.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * **편집 캔버스는 things-scene 편집 모드 위에 선다** (사용자 2026-09-24).
3
3
  *
4
- * 부품마다 `figure-v3-part` 컴포넌트로 판에 세우면 고르기 · 주황 테두리 · 기즈모와 스냅 · 박스 선택 · 카메라(감속 ·
4
+ * 부품마다 `figure-part` 컴포넌트로 판에 세우면 고르기 · 주황 테두리 · 기즈모와 스냅 · 박스 선택 · 카메라(감속 ·
5
5
  * 커서 쪽 확대 · 더블클릭 맞추기 · 시점 1~6) · 스튜디오 환경 · 좌표 바탕 · 조명과 그림자가 씬에서 온다. 여기서는 그
6
6
  * 배선과, 몸체 ↔ 컴포넌트의 대응이 왕복하는지를 잰다.
7
7
  */
@@ -10,11 +10,11 @@ import assert from 'node:assert/strict'
10
10
  import { readFileSync } from 'node:fs'
11
11
  import { fileURLToPath } from 'node:url'
12
12
 
13
- import { changedKeys, componentOf, gizmoResultOf } from '../client/modeller/v3-stage-model.ts'
14
- import type { V3RenderPart } from '../client/modeller/v3-renderer.ts'
13
+ import { changedKeys, componentOf, gizmoResultOf } from '../client/modeller/stage-model.ts'
14
+ import type { FigureRenderPart } from '../client/modeller/renderer.ts'
15
15
 
16
16
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
17
- const canvas = read('../client/modeller/v3-canvas.ts')
17
+ const canvas = read('../client/modeller/canvas.ts')
18
18
  const board = { width: 2000, height: 2000 }
19
19
 
20
20
  const turned = (deg: number) => {
@@ -25,7 +25,7 @@ const turned = (deg: number) => {
25
25
  [-Math.sin(a), 0, Math.cos(a)]
26
26
  ]
27
27
  }
28
- const box = (t: number[], r = turned(0)): V3RenderPart => ({
28
+ const box = (t: number[], r = turned(0)): FigureRenderPart => ({
29
29
  id: 'block',
30
30
  shape: { provider: 'box', dimensions: { width: 200, height: 100, depth: 60 } },
31
31
  pose: { r, t },
@@ -43,7 +43,7 @@ test('things-scene 의 판을 편집 모드로 세우고, 보기 설정은 판
43
43
 
44
44
  test('몸체는 자기 상자로 서고, 씬의 낱말(width·depth·height · left·top·zPos · 회전)로 적힌다', () => {
45
45
  const state = componentOf(box([100, 50, -30]), { 'palette.primary': '#123456' }, board)!
46
- assert.equal(state.type, 'figure-v3-part')
46
+ assert.equal(state.type, 'figure-part')
47
47
  assert.equal(state.name, 'block')
48
48
  assert.deepEqual([state.width, state.depth, state.height], [200, 100, 60])
49
49
  assert.equal(state.left, 100 + 1000 - 100)
@@ -6,14 +6,14 @@
6
6
  import { test } from 'node:test'
7
7
  import assert from 'node:assert/strict'
8
8
 
9
- import { addV3Box, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
9
+ import { addFigureBox, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
10
10
 
11
- import { drawableV3Parts } from '../client/modeller/v3-renderer.ts'
12
- import { boardFor } from '../client/modeller/v3-stage-model.ts'
11
+ import { drawableFigureParts } from '../client/modeller/renderer.ts'
12
+ import { boardFor } from '../client/modeller/stage-model.ts'
13
13
 
14
14
  const partsOf = (height: number) =>
15
- drawableV3Parts(
16
- compileV3Asset(addV3Box(createV3Asset('Probe'), { id: 'post', size: { x: 100, y: height, z: 100 }, position: { x: 0, y: height / 2, z: 0 } }))
15
+ drawableFigureParts(
16
+ compileFigureAsset(addFigureBox(createFigureAsset('Probe'), { id: 'post', size: { x: 100, y: height, z: 100 }, position: { x: 0, y: height / 2, z: 0 } }))
17
17
  .evaluate().geometry
18
18
  )
19
19
 
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * **모델러는 테마를 따른다** (2026-09-24).
3
3
  *
4
- * V3 캔버스와 편집기는 색을 박아 두었다. 어두운 테마에서 가운데만 밝은 판이 떴고, 경계선과 눌린 단추 · 오류 칸도
4
+ * 캔버스와 편집기는 색을 박아 두었다. 어두운 테마에서 가운데만 밝은 판이 떴고, 경계선과 눌린 단추 · 오류 칸도
5
5
  * 밝은 색 그대로였다. 색은 셸의 토큰에서 온다(UX 결정 — 바탕은 surface-container, 눌림은 secondary-container,
6
6
  * 오류는 error-container).
7
7
  */
@@ -11,8 +11,8 @@ import { readFileSync } from 'node:fs'
11
11
  import { fileURLToPath } from 'node:url'
12
12
 
13
13
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
14
- const canvas = read('../client/modeller/v3-canvas.ts')
15
- const editor = read('../client/modeller/v3-editor.ts')
14
+ const canvas = read('../client/modeller/canvas.ts')
15
+ const editor = read('../client/modeller/editor.ts')
16
16
 
17
17
  /** Hex colours that are not a `var(--token, #fallback)` fallback. */
18
18
  const fixed = (text: string) =>
@@ -21,7 +21,7 @@ const fixed = (text: string) =>
21
21
  .match(/#[0-9a-fA-F]{6}\b|#[0-9a-fA-F]{3}\b(?![\w-])/g) ?? []
22
22
 
23
23
  test('편집기에 박힌 색이 없다 — 토큰의 대체값만 남는다', () => {
24
- assert.deepEqual(fixed(editor), [], 'v3-editor.ts')
24
+ assert.deepEqual(fixed(editor), [], 'editor.ts')
25
25
  })
26
26
 
27
27
  test('캔버스 바탕은 작업대 회색 — 판의 하늘색이라 환경 탭에서 바꾼다', () => {
@@ -13,17 +13,17 @@ import { fileURLToPath } from 'node:url'
13
13
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
14
14
 
15
15
  test('the save takes the card picture from a view-only canvas, through the scene’s 3D snapshot', () => {
16
- const shooter = read('../client/modeller/v3-thumbnail.ts')
16
+ const shooter = read('../client/modeller/thumbnail.ts')
17
17
  assert.ok(shooter.includes('shooter.viewOnly = true'))
18
18
  assert.ok(shooter.includes('return shooter.snapshot ? await shooter.snapshot(WIDTH, HEIGHT) : undefined'))
19
19
  assert.ok(!shooter.includes('thumbnail?.()'), 'no call to a method the canvas does not have')
20
20
 
21
- const canvas = read('../client/modeller/v3-canvas.ts')
21
+ const canvas = read('../client/modeller/canvas.ts')
22
22
  assert.ok(canvas.includes('async snapshot(width = 480, height = 360)'))
23
23
  assert.ok(canvas.includes("await layer.snapshot3d(width, height, 'image/webp', 0.85)"), 'the 3D view, not the 2D sheet')
24
24
  const watch = canvas.slice(canvas.indexOf(' private watchScene()'), canvas.indexOf(' private showSelected()'))
25
25
  assert.ok(watch.includes('|| this.viewOnly) return'), 'a view-only canvas tells the editor nothing')
26
26
 
27
- const editor = read('../client/modeller/v3-editor.ts')
28
- assert.ok(editor.includes('const thumbnail = await captureV3Thumbnail('), 'the save asks for it')
27
+ const editor = read('../client/modeller/editor.ts')
28
+ assert.ok(editor.includes('const thumbnail = await captureFigureThumbnail('), 'the save asks for it')
29
29
  })
@@ -12,32 +12,32 @@ import assert from 'node:assert/strict'
12
12
  import { readFileSync } from 'node:fs'
13
13
  import { fileURLToPath } from 'node:url'
14
14
 
15
- import { addV3Box, createV3Asset, v3CostOf } from '@hatiolab/figure-model'
15
+ import { addFigureBox, createFigureAsset, figureCostOf } from '@hatiolab/figure-model'
16
16
 
17
17
  import { capacityFrom, SCENE_BUDGET } from '../client/modeller/scene-budget.ts'
18
18
 
19
19
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
20
- const verdict = read('../client/modeller/figure-v3-verdict.ts')
21
- const page = read('../client/pages/figure-v3-modeller-page.ts')
20
+ const verdict = read('../client/modeller/figure-verdict.ts')
21
+ const page = read('../client/pages/figure-modeller-page.ts')
22
22
  const graphql = read('../client/graphql/index.ts')
23
23
 
24
- test('개수는 목록 카드와 같은 셈이다 — V3 원가를 같은 capacityFrom 에 넣는다', () => {
25
- let asset = createV3Asset('Probe')
26
- asset = addV3Box(asset, { id: 'a', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
27
- asset = addV3Box(asset, { id: 'b', size: { x: 100, y: 100, z: 100 }, position: { x: 200, y: 50, z: 0 } })
28
- const cost = v3CostOf(asset)
24
+ test('개수는 목록 카드와 같은 셈이다 — 원가를 같은 capacityFrom 에 넣는다', () => {
25
+ let asset = createFigureAsset('Probe')
26
+ asset = addFigureBox(asset, { id: 'a', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
27
+ asset = addFigureBox(asset, { id: 'b', size: { x: 100, y: 100, z: 100 }, position: { x: 200, y: 50, z: 0 } })
28
+ const cost = figureCostOf(asset)
29
29
 
30
30
  assert.equal(cost.groups, 2, '전제: 그려지는 부품 둘이 묶음 둘')
31
31
  assert.equal(capacityFrom(cost.groups, cost.triangles)?.instances, Math.floor(SCENE_BUDGET.groupsOnScreen / 2))
32
- const maturity = read('../client/modeller/v3-maturity.ts')
32
+ const maturity = read('../client/modeller/maturity.ts')
33
33
  assert.ok(/capacityFrom\(cost\.groups, cost\.triangles\)/.test(maturity), '판정 탭이 카드와 같은 함수로 센다')
34
- assert.ok(/report = asset \? v3ReportOf\(asset\) : undefined/.test(verdict), '탭은 그 셈을 부르고 보여 줄 뿐이다')
34
+ assert.ok(/report = asset \? figureReportOf\(asset\) : undefined/.test(verdict), '탭은 그 셈을 부르고 보여 줄 뿐이다')
35
35
  assert.ok(/instances: report\?\.capacity\.instances/.test(verdict), '탭 이름표의 개수도 같은 셈에서 온다')
36
36
  })
37
37
 
38
38
  test('판정은 발행 패널과 같은 서버 문에서 온다 — 화면이 따로 판정하지 않는다', () => {
39
39
  assert.ok(verdict.includes('inspectFigureDraft(source, this.figureId)'), '편집 중인 원본을 보낸다')
40
- assert.ok(!/inspectV3Asset/.test(verdict), '화면이 제 판정을 가지면 발행 패널과 다른 말을 한다')
40
+ assert.ok(!/inspectFigureAsset/.test(verdict), '화면이 제 판정을 가지면 발행 패널과 다른 말을 한다')
41
41
 
42
42
  const fields = (name: string) => {
43
43
  const start = graphql.indexOf(`${name}(`, graphql.indexOf(`query ${name[0].toUpperCase()}${name.slice(1)}`))
@@ -55,21 +55,21 @@ test('판정 탭은 발행 패널과 같은 두 판정에 「부품에 맞추기
55
55
  const panel = read('../client/modeller/figure-history-panel.ts')
56
56
  const codes = (text: string) => [...text.matchAll(/'(part-outside-occupancy|occupancy-undeclared)'/g)].map(m => m[1]).sort()
57
57
  assert.deepEqual(codes(verdict), codes(panel.slice(panel.indexOf('private offersFit'))))
58
- assert.ok(/<figure-v3-verdict[\s\S]*?@fit-base-to-parts=\$\{this\.fitToParts\}/.test(page), '누르면 공간 선언으로 이어진다')
58
+ assert.ok(/<figure-verdict[\s\S]*?@fit-base-to-parts=\$\{this\.fitToParts\}/.test(page), '누르면 공간 선언으로 이어진다')
59
59
  })
60
60
 
61
61
  test('판정과 이력은 편집기의 탭에 서고, 감추되 떼지 않는다', () => {
62
- const editor = read('../client/modeller/v3-editor.ts')
63
- assert.ok(/<figure-v3-verdict[\s\S]*?\.source=\$\{this\.draftSource\(\)\}/.test(page), '편집 중인 원본을 넘긴다')
64
- assert.ok(/<figure-v3-verdict[\s\S]*?\.figureType=/.test(page), '타입을 바꾸면 다시 판정한다')
65
- assert.ok(/<figure-v3-verdict\s+slot="verdict"/.test(page) && /<figure-history-panel\s+slot="history"/.test(page))
62
+ const editor = read('../client/modeller/editor.ts')
63
+ assert.ok(/<figure-verdict[\s\S]*?\.source=\$\{this\.draftSource\(\)\}/.test(page), '편집 중인 원본을 넘긴다')
64
+ assert.ok(/<figure-verdict[\s\S]*?\.figureType=/.test(page), '타입을 바꾸면 다시 판정한다')
65
+ assert.ok(/<figure-verdict\s+slot="verdict"/.test(page) && /<figure-history-panel\s+slot="history"/.test(page))
66
66
  assert.ok(editor.includes(`<div panel flush ?off=\${tab !== 'verdict'}><slot name="verdict"></slot></div>`), '고르지 않은 탭은 감춘다 — 판정은 마지막 답을 지킨다')
67
67
  assert.ok(/@capacity-changed=/.test(page) && page.includes('.instances=${this.instances}'), '개수를 편집기에 넘긴다')
68
68
  assert.ok(editor.includes("this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)"), '개수가 탭 이름표에 남는다')
69
69
  })
70
70
 
71
71
  test('판정 탭의 말은 다섯 말 모두에 있다 — 없으면 키가 그대로 찍힌다', () => {
72
- const maturity = read('../client/modeller/v3-maturity.ts')
72
+ const maturity = read('../client/modeller/maturity.ts')
73
73
  const keys = [...(verdict + maturity).matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
74
74
  assert.ok(keys.includes('figure.text.verdict-asking'), '전제: 삼항 안의 키도 센다')
75
75
  for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
@@ -10,9 +10,9 @@ import { fileURLToPath } from 'node:url'
10
10
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
11
11
 
12
12
  test('환경 탭 — 설정은 판의 상태 이름이고, 도형이 아니라 브라우저에 둔다', () => {
13
- const editor = read('../client/modeller/v3-editor.ts')
13
+ const editor = read('../client/modeller/editor.ts')
14
14
  assert.ok(editor.includes('<figure-settings .view=${this.view} @update-view=${(e: CustomEvent) => this.setView(e.detail.view)}></figure-settings>'))
15
- assert.ok(editor.includes("localStorage.setItem('figure-v3-view'"))
15
+ assert.ok(editor.includes("localStorage.setItem('figure-view'"))
16
16
  assert.doesNotMatch(editor.slice(editor.indexOf('private setView'), editor.indexOf('private setView') + 400), /session/, '설정은 세션(도형)에 닿지 않는다')
17
17
  const view = read('../client/modeller/figure-view.ts')
18
18
  assert.ok(view.includes("'studio',") && view.includes("'rainy'"), '환경 여덟')
@@ -5,9 +5,8 @@ export default {
5
5
  route,
6
6
  routes: [
7
7
  { tagname: 'figure-list-page', page: 'figure-list' },
8
- /* One modeller since ADR-0087 step 5; the old V3 address is an alias. The shell builds the DOM from this table. */
9
- { tagname: 'figure-v3-modeller-page', page: 'figure-modeller' },
10
- { tagname: 'figure-v3-modeller-page', page: 'figure-v3-modeller' }
8
+ /* The shell builds the DOM from this table. */
9
+ { tagname: 'figure-modeller-page', page: 'figure-modeller' }
11
10
  ],
12
11
  bootstrap
13
12
  }
@@ -73,7 +73,7 @@
73
73
  "figure.text.input-range-size": "figure size {axis} can only be set between {min} and {max} mm. value given: {value} mm",
74
74
  "figure.text.input-range": "this value can only be set between {min} and {max}. value given: {value}",
75
75
  "figure.text.mounting-face-needs-space": "declare the space first to choose one.",
76
- "figure.text.part-is-referenced": "'{part}' cannot be deleted: another part is fastened to it or follows its size. Undo that link first.",
76
+ "figure.text.part-is-referenced": "'{part}' cannot be deleted: another part uses its values as they are.",
77
77
  "figure.text.part-name-invalid": "'{name}' cannot be a part name. Start with a letter and use letters, digits, - and _.",
78
78
  "figure.text.part-name-taken": "'{name}' is already used in this figure.",
79
79
  "figure.text.placement-redeclares": "changing it declares the space again from the parts as they are now.",
@@ -311,7 +311,7 @@
311
311
  "figure.text.bound-by-triangles": "triangles reach the limit first ({per} per instance, {budget} per drawing).",
312
312
  "figure.text.budget-measured-on": "Measured on {gpu}, {on}. A figure standing still costs nothing — only what is on screen and redrawn counts.",
313
313
  "figure.text.budget-not-measured-yet": "this target has not been measured yet.",
314
- "figure.text.budgets-not-in-v3": "off-palette surfaces are not in the V3 format, so they are not shown. the part-count limit shows once a detail level is set.",
314
+ "figure.text.budgets-not-in": "off-palette surfaces are not in the format, so they are not shown. the part-count limit shows once a detail level is set.",
315
315
  "figure.text.trend-chart-title": "drawing cost by instance count",
316
316
  "figure.text.trend-chart-lead": "flat lines stay the same as instances grow. instances of the same size share them.",
317
317
  "figure.text.trend-at-count": "at {n}",
@@ -377,7 +377,7 @@
377
377
  "figure.text.joint-value-beyond-limits": "outside the joint limits.",
378
378
  "figure.text.limit-comes-from-level": "the detail level sets this limit.",
379
379
  "figure.text.maturity-blocked-no-binding": "state display is not measured yet.",
380
- "figure.text.maturity-blocked-no-detail-level": "the V3 format has no detail level. the budgets and placement scale above take its place.",
380
+ "figure.text.maturity-blocked-no-detail-level": "the format has no detail level. the budgets and placement scale above take its place.",
381
381
  "figure.text.maturity-blocked-no-parts": "nothing to measure — there are no parts.",
382
382
  "figure.text.maturity-consistency-because": "checks texture use, recommended segment counts and the number of materials.",
383
383
  "figure.text.maturity-consistency-clean": "no issues",
@@ -632,10 +632,7 @@
632
632
  "figure.button.narrow-range-from": "narrow {axis} to {mm} mm and up, and link",
633
633
  "figure.text.link-needs-larger-range": "with this link the figure cannot be drawn when its {axis} is over {mm} mm, and the declared sizes go above that.",
634
634
  "figure.button.narrow-range-to": "narrow {axis} to {mm} mm and under, and link",
635
- "figure.text.part-holds-others": "'{part}' holds {others}; take them off first.",
636
635
  "figure.text.part-in-declared-space": "'{part}' is in the declared space, so it cannot simply be deleted. it can be deleted with the space fitted to the parts left.",
637
- "figure.text.part-is-followed": "{others} follow the size or place of '{part}'; make them their own numbers first.",
638
- "figure.text.part-is-led": "{others} move with '{part}'; give them their own control first.",
639
636
  "figure.button.change-motion": "change the motion",
640
637
  "figure.button.apply-motion-change": "apply the change",
641
638
  "figure.button.cancel-motion-change": "leave it",
@@ -655,5 +652,16 @@
655
652
  "figure.label.least": "least",
656
653
  "figure.label.most": "most",
657
654
  "figure.button.publish-input": "publish to boards",
658
- "figure.button.apply": "apply"
655
+ "figure.button.apply": "apply",
656
+ "figure.text.part-row-along": "The row {rows} is laid along '{part}', so '{part}' cannot be deleted. Fasten the row to another part, or undo the repeat, first.",
657
+ "figure.text.part-row-carries": "{others} hangs on every copy of the row '{part}'; take it off first.",
658
+ "figure.text.removal-released": "{parts} stay where they were, no longer fastened.",
659
+ "figure.text.removal-released-motion": "{part} stays where it was; its fastening and motion (control {control}) are gone.",
660
+ "figure.text.removal-own-control": "{part} now moves on its own control {control} ({min} to {max}).",
661
+ "figure.text.removal-fixed": "The values {parts} followed are now fixed numbers.",
662
+ "figure.text.removal-bindings": "{places} went with it.",
663
+ "figure.text.removal-mounting-face": "The mounting face is released.",
664
+ "figure.text.edit-refused": "This edit cannot be made. ({code})",
665
+ "figure.text.edit-refused-node-limit": "The figure is too large to add more. It holds up to {max} nodes.",
666
+ "figure.text.recipe-step-refused": "Stopped at step {step} of the recipe. {why} ({code})"
659
667
  }
@@ -73,7 +73,7 @@
73
73
  "figure.text.input-range-size": "図形サイズ {axis} は {min} ~ {max} mm の間でのみ設定できます。入力値: {value} mm",
74
74
  "figure.text.input-range": "この値は {min} ~ {max} の間でのみ設定できます。入力値: {value}",
75
75
  "figure.text.mounting-face-needs-space": "先に空間を宣言すると選べます。",
76
- "figure.text.part-is-referenced": "'{part}' には他の部品が取り付けられているか、そのサイズに従っているため削除できません。先にその連結を外してください。",
76
+ "figure.text.part-is-referenced": "他の部品が '{part}' の値をそのまま使っているため削除できません。",
77
77
  "figure.text.part-name-invalid": "'{name}' は部品名に使えません。英字で始め、英字・数字・-・_ のみ使えます。",
78
78
  "figure.text.part-name-taken": "'{name}' はこの図形ですでに使われている名前です。",
79
79
  "figure.text.placement-redeclares": "変更すると、今の部品で空間を宣言し直します。",
@@ -311,7 +311,7 @@
311
311
  "figure.text.bound-by-triangles": "三角形が先に上限に達します (1 個あたり {per}、図面あたり {budget})。",
312
312
  "figure.text.budget-measured-on": "{gpu} で {on} に測定した値です。静止している図形は負荷になりません — 画面に見えて再描画されるものだけを数えます。",
313
313
  "figure.text.budget-not-measured-yet": "this target has not been measured yet.",
314
- "figure.text.budgets-not-in-v3": "パレット外の表面は V3 形式にない項目のため表示しません。部品数の上限は詳細度を決めると表示されます。",
314
+ "figure.text.budgets-not-in": "パレット外の表面は 形式にない項目のため表示しません。部品数の上限は詳細度を決めると表示されます。",
315
315
  "figure.text.trend-chart-title": "インスタンス数による描画コスト",
316
316
  "figure.text.trend-chart-lead": "平らな線はインスタンスが増えても変わらない値です。同じサイズのインスタンスが共有します。",
317
317
  "figure.text.trend-at-count": "{n}個のとき",
@@ -377,7 +377,7 @@
377
377
  "figure.text.joint-value-beyond-limits": "関節の可動範囲外です。",
378
378
  "figure.text.limit-comes-from-level": "the detail level sets this limit.",
379
379
  "figure.text.maturity-blocked-no-binding": "状態表現はまだ測定していません。",
380
- "figure.text.maturity-blocked-no-detail-level": "V3 形式には詳細等級がありません。上の上限と配置規模で判断します。",
380
+ "figure.text.maturity-blocked-no-detail-level": "形式には詳細等級がありません。上の上限と配置規模で判断します。",
381
381
  "figure.text.maturity-blocked-no-parts": "nothing to measure — there are no parts.",
382
382
  "figure.text.maturity-consistency-because": "テクスチャの使用、推奨分割数の遵守、材質の種類数を確認します。",
383
383
  "figure.text.maturity-consistency-clean": "no issues",
@@ -632,10 +632,7 @@
632
632
  "figure.button.narrow-range-from": "{axis} を {mm} mm からに絞って連結",
633
633
  "figure.text.link-needs-larger-range": "この連結で図形の {axis} が {mm} mm を超えると描けません。宣言された置けるサイズはそれより大きくまで開いています。",
634
634
  "figure.button.narrow-range-to": "{axis} を {mm} mm までに絞って連結",
635
- "figure.text.part-holds-others": "'{part}' に {others} が付いているため削除できません。先に外してください。",
636
635
  "figure.text.part-in-declared-space": "'{part}' は宣言された空間に含まれるため、そのままでは削除できません。削除と同時に空間を残りの部品に合わせ直せます。",
637
- "figure.text.part-is-followed": "{others} が '{part}' のサイズか位置に従っているため削除できません。先に自分の数値に変えてください。",
638
- "figure.text.part-is-led": "{others} が '{part}' に連動しているため削除できません。先に自分の操作値に変えてください。",
639
636
  "figure.button.change-motion": "動きを直す",
640
637
  "figure.button.apply-motion-change": "動きを変える",
641
638
  "figure.button.cancel-motion-change": "直さない",
@@ -655,5 +652,16 @@
655
652
  "figure.label.least": "最小",
656
653
  "figure.label.most": "最大",
657
654
  "figure.button.publish-input": "ボードに公開",
658
- "figure.button.apply": "適用"
655
+ "figure.button.apply": "適用",
656
+ "figure.text.part-row-along": "列 {rows} が '{part}' に沿って並んでいるため '{part}' を削除できません。列を他の部品に取り付けるか、繰り返しを解除してから削除してください。",
657
+ "figure.text.part-row-carries": "{others} が列 '{part}' の各コピーに付いているため削除できません。先に外してください。",
658
+ "figure.text.removal-released": "{parts} はその場に残り、取り付けが外れました。",
659
+ "figure.text.removal-released-motion": "{part} はその場に残り、取り付けと動き(操作値 {control})が外れました。",
660
+ "figure.text.removal-own-control": "{part} は自分の操作値 {control}({min}~{max})で動くようになりました。",
661
+ "figure.text.removal-fixed": "{parts} が従っていた値は現在の数値に固定されました。",
662
+ "figure.text.removal-bindings": "{places} も一緒に削除しました。",
663
+ "figure.text.removal-mounting-face": "取付面が外れました。",
664
+ "figure.text.edit-refused": "この編集はできません。({code})",
665
+ "figure.text.edit-refused-node-limit": "図形が大きいためこれ以上追加できません。ノードは {max} 個までです。",
666
+ "figure.text.recipe-step-refused": "レシピの {step} 番目の手順で止まりました。{why} ({code})"
659
667
  }
@@ -73,7 +73,7 @@
73
73
  "figure.text.input-range-size": "도형 크기 {axis} 는 {min} ~ {max} mm 사이에서만 정할 수 있습니다. 넣은 값: {value} mm",
74
74
  "figure.text.input-range": "이 값은 {min} ~ {max} 사이에서만 정할 수 있습니다. 넣은 값: {value}",
75
75
  "figure.text.mounting-face-needs-space": "공간을 먼저 선언해야 고를 수 있습니다.",
76
- "figure.text.part-is-referenced": "'{part}' 에 다른 부품이 붙어 있거나 그 크기를 따르고 있어 지울 수 없습니다. 그 연결을 먼저 푸세요.",
76
+ "figure.text.part-is-referenced": "다른 부품이 '{part}' 의 값을 그대로 쓰고 있어 지울 수 없습니다.",
77
77
  "figure.text.part-name-invalid": "'{name}' 은 부품 이름으로 쓸 수 없습니다. 영문자로 시작하고 영문자·숫자·-·_ 만 씁니다.",
78
78
  "figure.text.part-name-taken": "'{name}' 은 이 도형에서 이미 쓰는 이름입니다.",
79
79
  "figure.text.placement-redeclares": "바꾸면 지금 부품으로 공간을 다시 선언합니다.",
@@ -311,7 +311,7 @@
311
311
  "figure.text.bound-by-triangles": "삼각형이 먼저 한도에 도달합니다 (1개당 {per}, 도면당 {budget}).",
312
312
  "figure.text.budget-measured-on": "{gpu} 에서 {on} 에 잰 값입니다. 가만히 서 있는 도형은 값을 치르지 않습니다 — 화면에 보이고 다시 그려지는 것만 셉니다.",
313
313
  "figure.text.budget-not-measured-yet": "아직 실측 전 목표치입니다.",
314
- "figure.text.budgets-not-in-v3": "팔레트 외 표면 한도는 V3 형식에 없는 항목이라 표시하지 않습니다. 부품 수 한도는 디테일 등급을 정하면 보입니다.",
314
+ "figure.text.budgets-not-in": "팔레트 외 표면 한도는 형식에 없는 항목이라 표시하지 않습니다. 부품 수 한도는 디테일 등급을 정하면 보입니다.",
315
315
  "figure.text.trend-chart-title": "인스턴스 수에 따른 그리는 비용",
316
316
  "figure.text.trend-chart-lead": "평평한 선은 인스턴스가 늘어도 그대로인 값입니다. 같은 크기의 인스턴스가 공유합니다.",
317
317
  "figure.text.trend-at-count": "{n}개일 때",
@@ -377,7 +377,7 @@
377
377
  "figure.text.joint-value-beyond-limits": "관절의 가동 범위 밖입니다.",
378
378
  "figure.text.limit-comes-from-level": "이 한도는 디테일 등급이 정합니다.",
379
379
  "figure.text.maturity-blocked-no-binding": "상태 표현은 아직 측정하지 않습니다.",
380
- "figure.text.maturity-blocked-no-detail-level": "V3 형식에는 디테일 등급이 없습니다. 위의 한도와 배치 규모로 판단합니다.",
380
+ "figure.text.maturity-blocked-no-detail-level": "형식에는 디테일 등급이 없습니다. 위의 한도와 배치 규모로 판단합니다.",
381
381
  "figure.text.maturity-blocked-no-parts": "부품이 없어 측정할 것이 없습니다.",
382
382
  "figure.text.maturity-consistency-because": "텍스처 사용 여부, 권장 분할 수 준수, 재질 종류 수를 확인합니다.",
383
383
  "figure.text.maturity-consistency-clean": "문제 없음",
@@ -632,10 +632,7 @@
632
632
  "figure.button.narrow-range-from": "{axis} 를 {mm} mm 부터로 좁히고 연결",
633
633
  "figure.text.link-needs-larger-range": "이 연결로 도형 {axis} 가 {mm} mm 보다 크면 그릴 수 없습니다. 지금 선언된 놓일 수 있는 크기는 그보다 큰 데까지 열려 있습니다.",
634
634
  "figure.button.narrow-range-to": "{axis} 를 {mm} mm 까지로 좁히고 연결",
635
- "figure.text.part-holds-others": "'{part}' 에 {others} 이 매달려 있어 지울 수 없습니다. 먼저 떼어 주세요.",
636
635
  "figure.text.part-in-declared-space": "'{part}' 는 선언된 공간에 들어 있어 그냥 지울 수 없습니다. 지우면서 공간을 남은 부품에 다시 맞출 수 있습니다.",
637
- "figure.text.part-is-followed": "{others} 이 '{part}' 의 크기나 자리를 따르고 있어 지울 수 없습니다. 먼저 그 부품을 제 숫자로 바꾸세요.",
638
- "figure.text.part-is-led": "{others} 이 '{part}' 를 따라 움직여 지울 수 없습니다. 먼저 제 조작값으로 바꾸세요.",
639
636
  "figure.button.change-motion": "움직임 고치기",
640
637
  "figure.button.apply-motion-change": "움직임 바꾸기",
641
638
  "figure.button.cancel-motion-change": "고치지 않음",
@@ -655,5 +652,16 @@
655
652
  "figure.label.least": "최소",
656
653
  "figure.label.most": "최대",
657
654
  "figure.button.publish-input": "보드에 공개",
658
- "figure.button.apply": "적용"
655
+ "figure.button.apply": "적용",
656
+ "figure.text.part-row-along": "{rows} 줄이 '{part}' 를 따라 놓여 있어 '{part}' 를 지울 수 없습니다. 줄을 다른 부품에 붙이거나 되풀이를 없앤 뒤 지우세요.",
657
+ "figure.text.part-row-carries": "{others} 이 줄 '{part}' 의 복제마다 붙어 있어 지울 수 없습니다. 먼저 떼어 주세요.",
658
+ "figure.text.removal-released": "{parts} 는 그 자리에 남고 붙임이 풀렸습니다.",
659
+ "figure.text.removal-released-motion": "{part} 는 그 자리에 남고, 붙임과 움직임(조작값 {control})이 풀렸습니다.",
660
+ "figure.text.removal-own-control": "{part} 는 이제 제 조작값 {control}({min}~{max})로 움직입니다.",
661
+ "figure.text.removal-fixed": "{parts} 가 따르던 값은 지금 수로 고정되었습니다.",
662
+ "figure.text.removal-bindings": "{places} 도 함께 지웠습니다.",
663
+ "figure.text.removal-mounting-face": "장착면이 풀렸습니다.",
664
+ "figure.text.edit-refused": "이 편집은 할 수 없습니다. ({code})",
665
+ "figure.text.edit-refused-node-limit": "도형이 커서 더 넣을 수 없습니다. 노드는 {max} 개까지입니다.",
666
+ "figure.text.recipe-step-refused": "레시피의 {step} 번째 단계에서 막혔습니다. {why} ({code})"
659
667
  }
@@ -73,7 +73,7 @@
73
73
  "figure.text.input-range-size": "saiz rajah {axis} hanya boleh ditetapkan antara {min} dan {max} mm. nilai diberi: {value} mm",
74
74
  "figure.text.input-range": "nilai ini hanya boleh ditetapkan antara {min} dan {max}. nilai diberi: {value}",
75
75
  "figure.text.mounting-face-needs-space": "isytiharkan ruang dahulu untuk memilih.",
76
- "figure.text.part-is-referenced": "'{part}' tidak boleh dipadam: bahagian lain dipasang padanya atau mengikut saiznya. Leraikan pautan itu dahulu.",
76
+ "figure.text.part-is-referenced": "'{part}' tidak boleh dipadam: bahagian lain menggunakan nilainya seperti sedia ada.",
77
77
  "figure.text.part-name-invalid": "'{name}' tidak boleh menjadi nama bahagian. Mulakan dengan huruf dan gunakan huruf, digit, - dan _.",
78
78
  "figure.text.part-name-taken": "'{name}' sudah digunakan dalam bentuk ini.",
79
79
  "figure.text.placement-redeclares": "menukarnya mengisytiharkan semula ruang daripada bahagian semasa.",
@@ -311,7 +311,7 @@
311
311
  "figure.text.bound-by-triangles": "segi tiga mencapai had dahulu ({per} setiap satu, {budget} setiap lukisan).",
312
312
  "figure.text.budget-measured-on": "Diukur pada {gpu}, {on}. Figura yang diam tidak menelan kos — hanya yang kelihatan dan dilukis semula dikira.",
313
313
  "figure.text.budget-not-measured-yet": "this target has not been measured yet.",
314
- "figure.text.budgets-not-in-v3": "permukaan luar palet tiada dalam format V3, jadi tidak dipaparkan. had bilangan bahagian dipaparkan apabila tahap perincian ditetapkan.",
314
+ "figure.text.budgets-not-in": "permukaan luar palet tiada dalam format ini, jadi tidak dipaparkan. had bilangan bahagian dipaparkan apabila tahap perincian ditetapkan.",
315
315
  "figure.text.trend-chart-title": "kos lukisan mengikut bilangan instans",
316
316
  "figure.text.trend-chart-lead": "garis mendatar kekal sama apabila instans bertambah. instans yang sama saiz berkongsinya.",
317
317
  "figure.text.trend-at-count": "pada {n}",
@@ -377,7 +377,7 @@
377
377
  "figure.text.joint-value-beyond-limits": "di luar had sendi.",
378
378
  "figure.text.limit-comes-from-level": "the detail level sets this limit.",
379
379
  "figure.text.maturity-blocked-no-binding": "paparan keadaan belum diukur.",
380
- "figure.text.maturity-blocked-no-detail-level": "format V3 tiada tahap perincian. had dan skala penempatan di atas menggantikannya.",
380
+ "figure.text.maturity-blocked-no-detail-level": "format tiada tahap perincian. had dan skala penempatan di atas menggantikannya.",
381
381
  "figure.text.maturity-blocked-no-parts": "nothing to measure — there are no parts.",
382
382
  "figure.text.maturity-consistency-because": "menyemak penggunaan tekstur, bilangan segmen yang disyorkan dan bilangan bahan.",
383
383
  "figure.text.maturity-consistency-clean": "no issues",
@@ -632,10 +632,7 @@
632
632
  "figure.button.narrow-range-from": "sempitkan {axis} dari {mm} mm dan pautkan",
633
633
  "figure.text.link-needs-larger-range": "dengan pautan ini rajah tidak boleh dilukis apabila {axis} melebihi {mm} mm, dan saiz yang diisytihar pergi lebih tinggi.",
634
634
  "figure.button.narrow-range-to": "sempitkan {axis} hingga {mm} mm dan pautkan",
635
- "figure.text.part-holds-others": "'{part}' memegang {others}; tanggalkan dahulu.",
636
635
  "figure.text.part-in-declared-space": "'{part}' berada dalam ruang yang diisytihar, jadi ia tidak boleh dipadam begitu sahaja. ia boleh dipadam dengan ruang dimuatkan semula pada bahagian yang tinggal.",
637
- "figure.text.part-is-followed": "{others} mengikut saiz atau tempat '{part}'; jadikan nombor sendiri dahulu.",
638
- "figure.text.part-is-led": "{others} bergerak bersama '{part}'; beri kawalan sendiri dahulu.",
639
636
  "figure.button.change-motion": "ubah gerakan",
640
637
  "figure.button.apply-motion-change": "guna perubahan",
641
638
  "figure.button.cancel-motion-change": "biarkan",
@@ -655,5 +652,16 @@
655
652
  "figure.label.least": "terkecil",
656
653
  "figure.label.most": "terbesar",
657
654
  "figure.button.publish-input": "terbitkan ke papan",
658
- "figure.button.apply": "guna"
655
+ "figure.button.apply": "guna",
656
+ "figure.text.part-row-along": "Barisan {rows} disusun sepanjang '{part}', jadi '{part}' tidak boleh dipadam. Pasangkan barisan pada bahagian lain, atau batalkan ulangan, dahulu.",
657
+ "figure.text.part-row-carries": "{others} tergantung pada setiap salinan barisan '{part}'; tanggalkan dahulu.",
658
+ "figure.text.removal-released": "{parts} kekal di tempatnya, tidak lagi dipasang.",
659
+ "figure.text.removal-released-motion": "{part} kekal di tempatnya; pemasangan dan gerakannya (kawalan {control}) telah hilang.",
660
+ "figure.text.removal-own-control": "{part} kini bergerak dengan kawalannya sendiri {control} ({min} hingga {max}).",
661
+ "figure.text.removal-fixed": "Nilai yang diikuti oleh {parts} kini nombor tetap.",
662
+ "figure.text.removal-bindings": "{places} turut dipadam.",
663
+ "figure.text.removal-mounting-face": "Muka pemasangan dilepaskan.",
664
+ "figure.text.edit-refused": "Suntingan ini tidak boleh dibuat. ({code})",
665
+ "figure.text.edit-refused-node-limit": "Rajah terlalu besar untuk ditambah. Ia memuatkan sehingga {max} nod.",
666
+ "figure.text.recipe-step-refused": "Terhenti pada langkah {step} resipi. {why} ({code})"
659
667
  }
@@ -73,7 +73,7 @@
73
73
  "figure.text.input-range-size": "图形尺寸 {axis} 只能设置在 {min} 到 {max} mm 之间。输入值:{value} mm",
74
74
  "figure.text.input-range": "此值只能设置在 {min} 到 {max} 之间。输入值:{value}",
75
75
  "figure.text.mounting-face-needs-space": "请先声明空间,然后才能选择。",
76
- "figure.text.part-is-referenced": "无法删除 '{part}':有其他部件固定在它上面或跟随它的尺寸。请先解除该连接。",
76
+ "figure.text.part-is-referenced": "其他部件直接使用 '{part}' 的值,无法删除。",
77
77
  "figure.text.part-name-invalid": "'{name}' 不能作为部件名称。请以字母开头,只使用字母、数字、- 和 _。",
78
78
  "figure.text.part-name-taken": "'{name}' 在此图形中已被使用。",
79
79
  "figure.text.placement-redeclares": "更改后将按当前部件重新声明空间。",
@@ -311,7 +311,7 @@
311
311
  "figure.text.bound-by-triangles": "三角形先达到上限 (每个 {per},每个图面 {budget})。",
312
312
  "figure.text.budget-measured-on": "于 {on} 在 {gpu} 上实测。静止的图形不产生开销 — 只计算屏幕上被重绘的部分。",
313
313
  "figure.text.budget-not-measured-yet": "this target has not been measured yet.",
314
- "figure.text.budgets-not-in-v3": "调色板外表面不属于 V3 格式,因此不显示。设置细节等级后会显示部件数限额。",
314
+ "figure.text.budgets-not-in": "调色板外表面不属于 格式,因此不显示。设置细节等级后会显示部件数限额。",
315
315
  "figure.text.trend-chart-title": "按实例数的绘制成本",
316
316
  "figure.text.trend-chart-lead": "平线表示实例增加时保持不变的值,同尺寸的实例共享这些值。",
317
317
  "figure.text.trend-at-count": "{n} 个时",
@@ -377,7 +377,7 @@
377
377
  "figure.text.joint-value-beyond-limits": "超出关节的活动范围。",
378
378
  "figure.text.limit-comes-from-level": "the detail level sets this limit.",
379
379
  "figure.text.maturity-blocked-no-binding": "状态表现尚未测量。",
380
- "figure.text.maturity-blocked-no-detail-level": "V3 格式没有细节等级。以上方的限额和布置规模进行判断。",
380
+ "figure.text.maturity-blocked-no-detail-level": "格式没有细节等级。以上方的限额和布置规模进行判断。",
381
381
  "figure.text.maturity-blocked-no-parts": "nothing to measure — there are no parts.",
382
382
  "figure.text.maturity-consistency-because": "检查纹理使用、推荐分段数以及材质种类数量。",
383
383
  "figure.text.maturity-consistency-clean": "no issues",
@@ -632,10 +632,7 @@
632
632
  "figure.button.narrow-range-from": "将 {axis} 收窄为 {mm} mm 起并连接",
633
633
  "figure.text.link-needs-larger-range": "此连接下,图形 {axis} 大于 {mm} mm 时无法绘制,而声明的可放置尺寸高于此值。",
634
634
  "figure.button.narrow-range-to": "将 {axis} 收窄为 {mm} mm 以内并连接",
635
- "figure.text.part-holds-others": "'{part}' 上挂着 {others},无法删除。请先卸下。",
636
635
  "figure.text.part-in-declared-space": "'{part}' 在已声明的空间内,不能直接删除。可以在删除的同时让空间重新适配剩余部件。",
637
- "figure.text.part-is-followed": "{others} 跟随 '{part}' 的尺寸或位置,无法删除。请先改为自身数值。",
638
- "figure.text.part-is-led": "{others} 跟随 '{part}' 运动,无法删除。请先改为自身控制值。",
639
636
  "figure.button.change-motion": "修改运动",
640
637
  "figure.button.apply-motion-change": "应用修改",
641
638
  "figure.button.cancel-motion-change": "不修改",
@@ -655,5 +652,16 @@
655
652
  "figure.label.least": "最小",
656
653
  "figure.label.most": "最大",
657
654
  "figure.button.publish-input": "公开到看板",
658
- "figure.button.apply": "应用"
655
+ "figure.button.apply": "应用",
656
+ "figure.text.part-row-along": "阵列 {rows} 沿 '{part}' 排列,无法删除 '{part}'。请先将阵列固定到其他部件或取消重复。",
657
+ "figure.text.part-row-carries": "{others} 挂在阵列 '{part}' 的每个副本上,无法删除。请先卸下。",
658
+ "figure.text.removal-released": "{parts} 留在原处,已解除固定。",
659
+ "figure.text.removal-released-motion": "{part} 留在原处,固定和运动(控制值 {control})已解除。",
660
+ "figure.text.removal-own-control": "{part} 现在由自己的控制值 {control}({min}~{max})驱动。",
661
+ "figure.text.removal-fixed": "{parts} 跟随的值已固定为当前数值。",
662
+ "figure.text.removal-bindings": "{places} 也一并删除。",
663
+ "figure.text.removal-mounting-face": "安装面已解除。",
664
+ "figure.text.edit-refused": "无法进行此编辑。({code})",
665
+ "figure.text.edit-refused-node-limit": "图形过大,无法再添加。节点上限为 {max} 个。",
666
+ "figure.text.recipe-step-refused": "配方在第 {step} 步停止。{why} ({code})"
659
667
  }
@@ -1,25 +0,0 @@
1
- # V3 제품 현황
2
-
3
- 형식은 V3(`figure-v3-asset-1`) 하나다. V1·V2 형식은 2026-09-26 코드와 명세에서 제거했다.
4
-
5
- ## 제품 코드
6
-
7
- - `client/modeller/v3-product-session.ts`: 모델 ID·로드·저장 경계를 V3 workspace에 연결한다. 다른 모델의 지연 응답을 버리며 로드 실패 시 이전 형상을 노출하지 않는다. UI/AI는 같은 리비전과 undo/redo를 사용한다. AI 편집은 요청 당시 context/revision을 필수로 받는다.
8
- - `client/modeller/v3-canvas.ts`: things-scene 편집 모드의 판 위에 평가된 V3 형상을 부품마다 `figure-v3-part` 컴포넌트로 세운다. 새 문맥에서 렌더링이 실패하면 이전 모델을 지운다.
9
- - `client/modeller/v3-editor.ts`: 부품 목록 · 캔버스 · 오른쪽 탭(속성 · 능력 · 움직임 · 판정 · 환경 · 이력). 실행 취소/다시 실행과 저장 연결.
10
- - `client/graphql/v3-store.ts`: 정본을 그대로 `saveFigureV3`로 보낸다.
11
- - figure-service의 `saveFigureV3`: 도메인 권한 검사, V3 검증, 정확한 이전 정본을 조건으로 하는 atomic UPDATE, 생성 요청 ID를 이용한 재시도. SQLite에서 지원하지 않는 행 잠금은 사용하지 않는다. 재지 않은 성능 점수/삼각형 수는 null로 둔다. 실제 운영 DB 저장 테스트는 아직 하지 않았다.
12
- - `client/pages/figure-v3-modeller-page.ts`: 위 요소를 묶는 페이지. `route.ts`의 `figure-modeller`와 `figure-v3-modeller`가 이 페이지를 연다.
13
-
14
- ## 남은 항목
15
-
16
- 1. operato-figure의 AI-dock과 figure-service 도구/제안 생성기를 V3 정본 및 승인된 트랜잭션 계약으로 연결한다. 페이지 이벤트만 추가한 것은 AI 통합 완료가 아니다.
17
- 2. 발행 게이트, 썸네일, 카탈로그/배치 소비처가 V3 평가 결과를 사용하는지 확인한다.
18
-
19
- ## 검증
20
-
21
- - figure-ui `npm test`: 251개 통과(2026-09-26).
22
- - SQLite in-memory 테스트: 테넌트 경계·이전 정본 비교·오래된 편집 거절. 실제 TypeORM 리졸버/제품 DB end-to-end 검증을 대체하지 않는다.
23
- - 브라우저 검사(`npm run test:browser`): 기즈모 끌기 · 자세 슬라이더를 실제 마우스로 조작한다.
24
-
25
- 배포는 사용자가 담당한다.