@things-factory/figure-ui 10.1.65 → 10.1.67

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 (176) hide show
  1. package/client/graphql/index.ts +0 -45
  2. package/client/modeller/figure-history-panel.ts +6 -145
  3. package/client/modeller/figure-v3-capability.ts +2 -2
  4. package/client/modeller/figure-v3-clips.ts +2 -2
  5. package/client/modeller/figure-v3-controls.ts +2 -2
  6. package/client/modeller/figure-v3-first-save.ts +1 -1
  7. package/client/modeller/figure-v3-links.ts +6 -6
  8. package/client/modeller/figure-v3-material.ts +11 -11
  9. package/client/modeller/figure-v3-parts.ts +7 -10
  10. package/client/modeller/figure-v3-preview.ts +3 -3
  11. package/client/modeller/figure-v3-properties.ts +5 -6
  12. package/client/modeller/figure-v3-verdict.ts +9 -10
  13. package/client/modeller/figure-view.ts +2 -3
  14. package/client/modeller/modeller-route.ts +2 -3
  15. package/client/modeller/scene-budget.ts +0 -14
  16. package/client/modeller/side-panel-styles.ts +5 -5
  17. package/client/modeller/v3-align.ts +16 -6
  18. package/client/modeller/v3-canvas.ts +12 -12
  19. package/client/modeller/v3-clips.ts +1 -1
  20. package/client/modeller/v3-controls.ts +1 -1
  21. package/client/modeller/v3-editor.ts +21 -21
  22. package/client/modeller/v3-fields.ts +1 -1
  23. package/client/modeller/v3-figure-facts.ts +1 -1
  24. package/client/modeller/v3-first-save.ts +3 -4
  25. package/client/modeller/v3-gizmo.ts +5 -5
  26. package/client/modeller/v3-help.ts +1 -1
  27. package/client/modeller/v3-joint-fan.ts +4 -5
  28. package/client/modeller/v3-keys.ts +1 -1
  29. package/client/modeller/v3-links.ts +9 -12
  30. package/client/modeller/v3-maturity.ts +22 -26
  31. package/client/modeller/v3-refusals.ts +17 -0
  32. package/client/modeller/v3-renderer.ts +1 -1
  33. package/client/modeller/v3-stage-model.ts +2 -2
  34. package/client/modeller/v3-thumbnail.ts +1 -3
  35. package/client/pages/figure-v3-modeller-page.ts +8 -11
  36. package/client/types.ts +2 -31
  37. package/dist-client/graphql/index.js +0 -45
  38. package/dist-client/graphql/index.js.map +1 -1
  39. package/dist-client/modeller/figure-history-panel.d.ts +4 -37
  40. package/dist-client/modeller/figure-history-panel.js +8 -154
  41. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  42. package/dist-client/modeller/figure-v3-capability.d.ts +1 -1
  43. package/dist-client/modeller/figure-v3-capability.js +1 -1
  44. package/dist-client/modeller/figure-v3-capability.js.map +1 -1
  45. package/dist-client/modeller/figure-v3-clips.d.ts +2 -2
  46. package/dist-client/modeller/figure-v3-clips.js +2 -2
  47. package/dist-client/modeller/figure-v3-clips.js.map +1 -1
  48. package/dist-client/modeller/figure-v3-controls.d.ts +1 -1
  49. package/dist-client/modeller/figure-v3-controls.js +2 -2
  50. package/dist-client/modeller/figure-v3-controls.js.map +1 -1
  51. package/dist-client/modeller/figure-v3-first-save.d.ts +1 -1
  52. package/dist-client/modeller/figure-v3-first-save.js +1 -1
  53. package/dist-client/modeller/figure-v3-first-save.js.map +1 -1
  54. package/dist-client/modeller/figure-v3-links.d.ts +5 -5
  55. package/dist-client/modeller/figure-v3-links.js +6 -6
  56. package/dist-client/modeller/figure-v3-links.js.map +1 -1
  57. package/dist-client/modeller/figure-v3-material.d.ts +6 -6
  58. package/dist-client/modeller/figure-v3-material.js +11 -11
  59. package/dist-client/modeller/figure-v3-material.js.map +1 -1
  60. package/dist-client/modeller/figure-v3-parts.d.ts +6 -8
  61. package/dist-client/modeller/figure-v3-parts.js +1 -1
  62. package/dist-client/modeller/figure-v3-parts.js.map +1 -1
  63. package/dist-client/modeller/figure-v3-preview.js.map +1 -1
  64. package/dist-client/modeller/figure-v3-properties.d.ts +4 -5
  65. package/dist-client/modeller/figure-v3-properties.js +5 -6
  66. package/dist-client/modeller/figure-v3-properties.js.map +1 -1
  67. package/dist-client/modeller/figure-v3-verdict.d.ts +2 -2
  68. package/dist-client/modeller/figure-v3-verdict.js +9 -10
  69. package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
  70. package/dist-client/modeller/figure-view.d.ts +2 -3
  71. package/dist-client/modeller/figure-view.js.map +1 -1
  72. package/dist-client/modeller/modeller-route.d.ts +2 -3
  73. package/dist-client/modeller/modeller-route.js +2 -3
  74. package/dist-client/modeller/modeller-route.js.map +1 -1
  75. package/dist-client/modeller/scene-budget.d.ts +0 -11
  76. package/dist-client/modeller/scene-budget.js +0 -12
  77. package/dist-client/modeller/scene-budget.js.map +1 -1
  78. package/dist-client/modeller/side-panel-styles.d.ts +2 -2
  79. package/dist-client/modeller/side-panel-styles.js +5 -5
  80. package/dist-client/modeller/side-panel-styles.js.map +1 -1
  81. package/dist-client/modeller/v3-align.d.ts +9 -5
  82. package/dist-client/modeller/v3-align.js +13 -3
  83. package/dist-client/modeller/v3-align.js.map +1 -1
  84. package/dist-client/modeller/v3-canvas.d.ts +4 -4
  85. package/dist-client/modeller/v3-canvas.js +9 -9
  86. package/dist-client/modeller/v3-canvas.js.map +1 -1
  87. package/dist-client/modeller/v3-clips.js.map +1 -1
  88. package/dist-client/modeller/v3-controls.js.map +1 -1
  89. package/dist-client/modeller/v3-editor.d.ts +13 -13
  90. package/dist-client/modeller/v3-editor.js +19 -18
  91. package/dist-client/modeller/v3-editor.js.map +1 -1
  92. package/dist-client/modeller/v3-fields.js.map +1 -1
  93. package/dist-client/modeller/v3-figure-facts.d.ts +1 -1
  94. package/dist-client/modeller/v3-figure-facts.js +1 -1
  95. package/dist-client/modeller/v3-figure-facts.js.map +1 -1
  96. package/dist-client/modeller/v3-first-save.js +3 -4
  97. package/dist-client/modeller/v3-first-save.js.map +1 -1
  98. package/dist-client/modeller/v3-gizmo.d.ts +5 -5
  99. package/dist-client/modeller/v3-gizmo.js +1 -1
  100. package/dist-client/modeller/v3-gizmo.js.map +1 -1
  101. package/dist-client/modeller/v3-help.d.ts +1 -1
  102. package/dist-client/modeller/v3-help.js +1 -1
  103. package/dist-client/modeller/v3-help.js.map +1 -1
  104. package/dist-client/modeller/v3-joint-fan.js +4 -5
  105. package/dist-client/modeller/v3-joint-fan.js.map +1 -1
  106. package/dist-client/modeller/v3-keys.js.map +1 -1
  107. package/dist-client/modeller/v3-links.d.ts +5 -7
  108. package/dist-client/modeller/v3-links.js +6 -8
  109. package/dist-client/modeller/v3-links.js.map +1 -1
  110. package/dist-client/modeller/v3-maturity.d.ts +7 -7
  111. package/dist-client/modeller/v3-maturity.js +20 -24
  112. package/dist-client/modeller/v3-maturity.js.map +1 -1
  113. package/dist-client/modeller/v3-refusals.d.ts +6 -0
  114. package/dist-client/modeller/v3-refusals.js +18 -0
  115. package/dist-client/modeller/v3-refusals.js.map +1 -1
  116. package/dist-client/modeller/v3-renderer.js +1 -1
  117. package/dist-client/modeller/v3-renderer.js.map +1 -1
  118. package/dist-client/modeller/v3-stage-model.js.map +1 -1
  119. package/dist-client/modeller/v3-thumbnail.js +1 -3
  120. package/dist-client/modeller/v3-thumbnail.js.map +1 -1
  121. package/dist-client/pages/figure-v3-modeller-page.d.ts +4 -5
  122. package/dist-client/pages/figure-v3-modeller-page.js +8 -11
  123. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  124. package/dist-client/route.d.ts +1 -1
  125. package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
  126. package/dist-client/tsconfig.tsbuildinfo +1 -1
  127. package/dist-client/types.d.ts +2 -29
  128. package/dist-client/types.js +3 -0
  129. package/dist-client/types.js.map +1 -1
  130. package/docs/v3-product-cutover.md +14 -24
  131. package/package.json +5 -5
  132. package/test/browser/modeller-page.js +6 -7
  133. package/test/browser/modeller.check.mjs +9 -16
  134. package/test/fixtures/joint-column.json +1138 -0
  135. package/test/fixtures/joint-roller.json +872 -0
  136. package/test/fixtures/robot-arm-6dof.json +2776 -0
  137. package/test/i18n-prefix-guard.test.ts +0 -31
  138. package/test/modeller-route.test.ts +2 -3
  139. package/test/v3-align.test.ts +9 -4
  140. package/test/v3-clips.test.ts +1 -1
  141. package/test/v3-controls.test.ts +1 -1
  142. package/test/v3-fields.test.ts +4 -4
  143. package/test/v3-first-save.test.ts +1 -1
  144. package/test/v3-gizmo.test.ts +5 -5
  145. package/test/v3-joint-fan.test.ts +12 -42
  146. package/test/v3-keys.test.ts +1 -1
  147. package/test/v3-layout.test.ts +4 -5
  148. package/test/v3-links.test.ts +3 -3
  149. package/test/v3-material.test.ts +3 -3
  150. package/test/v3-maturity.test.ts +8 -9
  151. package/test/v3-parts-wiring.test.ts +3 -3
  152. package/test/v3-preview.test.ts +1 -1
  153. package/test/v3-product-write.test.ts +1 -1
  154. package/test/v3-properties.test.ts +3 -3
  155. package/test/v3-refusals.test.ts +12 -0
  156. package/test/v3-release-panel.test.ts +5 -5
  157. package/test/v3-scene-canvas.test.ts +2 -2
  158. package/test/v3-theme.test.ts +2 -2
  159. package/test/v3-verdict.test.ts +2 -2
  160. package/test/v3-view.test.ts +2 -2
  161. package/translations/en.json +2 -143
  162. package/translations/ja.json +2 -143
  163. package/translations/ko.json +2 -143
  164. package/translations/ms.json +2 -143
  165. package/translations/zh.json +2 -143
  166. package/client/modeller/figure-source.ts +0 -437
  167. package/client/modeller/part-edits.ts +0 -312
  168. package/dist-client/modeller/figure-source.d.ts +0 -238
  169. package/dist-client/modeller/figure-source.js +0 -193
  170. package/dist-client/modeller/figure-source.js.map +0 -1
  171. package/dist-client/modeller/part-edits.d.ts +0 -89
  172. package/dist-client/modeller/part-edits.js +0 -270
  173. package/dist-client/modeller/part-edits.js.map +0 -1
  174. package/test/expect.ts +0 -57
  175. package/test/figure-source.test.ts +0 -511
  176. package/test/part-edits.test.ts +0 -350
@@ -1 +0,0 @@
1
- {"version":3,"file":"part-edits.js","sourceRoot":"","sources":["../../client/modeller/part-edits.ts"],"names":[],"mappings":"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","sourcesContent":["import { EXTRUDE_PRIMITIVES, SEGMENTED_PRIMITIVES, SEGMENT_PRESETS, PART_LIMIT } from '@hatiolab/figure-model'\nimport type { DetailLevel, PrimitiveKind } from '@hatiolab/figure-model'\n\nimport { AXIS_KEYS, draftExtent } from './figure-source.js'\nimport type { FigureDraft, PartModel, PlaneAxis } from './figure-source.js'\n\n/**\n * 부품 편집 규칙. **씬 모델의 용어로** 다룬다.\n *\n * 편집 중에 기준이 되는 데이터는 씬 모델이다 — 기준 상자 위에 부품 컴포넌트가 놓인 상태.\n * 그래서 편집도 씬의 용어(`left`·`top`·`zPos`·`width`·`height`·`depth`)로 한다.\n * FigureSource 의 좌표계로 바꾸는 변환은 `figure-source.ts` 한 곳에서만 한다.\n *\n * 사용자가 보는 축(X·Y·Z)이 씬의 어느 속성인지도 `figure-source.ts` 의 `AXIS_KEYS` 가 기준이다.\n * 여기서 다시 정의하지 않는다.\n *\n * 모든 함수가 **새 배열·새 객체를 반환한다** — 원본을 수정하지 않는다. Lit 이 변경을\n * 감지하려면 참조가 달라져야 하고, 실행 취소를 붙일 때도 이전 상태가 남아 있어야 한다.\n */\n\n/** 중복되지 않는 이름을 만든다. 부품 이름은 저장되는 식별자라 중복되면 검증에서 걸린다. */\nfunction freeName(taken: Set<string>, base: string): string {\n if (!taken.has(base)) return base\n for (let n = 2; ; n++) {\n const candidate = `${base}-${n}`\n if (!taken.has(candidate)) return candidate\n }\n}\n\nconst round = (value: number) => Math.round(value)\n\n/** 값을 두 끝 사이로 당긴다. 끝이 뒤집혀 있으면(부품이 상자보다 크다) 앞의 끝을 따른다. */\nfunction clamp(value: number, least: number, most: number): number {\n return Math.max(least, Math.min(most, value))\n}\n\n/** 이 초안에 부품을 몇 개까지 넣을 수 있는지. 디테일 등급이 결정한다. */\nexport function partLimit(draft: FigureDraft): number {\n return PART_LIMIT[(draft.detailLevel ?? 'M') as DetailLevel]\n}\n\n/**\n * 새 부품 하나를 만든다.\n *\n * 기준 상자의 3분의 1 크기다. 추가하자마자 화면에 보여야 한다 — 크기 0 으로 놓으면\n * 사용자에게는 아무 일도 일어나지 않은 것으로 보인다.\n *\n * ## 어디에 놓나 — 고른 부품 **위**다\n *\n * 전에는 언제나 바닥 한가운데였다. 첫 부품은 그 자리가 맞지만 둘째부터는 **첫 부품 속에**\n * 들어간다 — 같은 자리에 같은 크기로 놓이니 화면에는 덩어리 하나뿐이고, 저작자는 무엇이\n * 늘었는지 못 본다. 「추가하자마자 보여야 한다」는 그때 이미 깨져 있었다.\n *\n * 그래서 고른 부품이 있으면 그 부품의 **윗면**에 얹고 발자국을 가운데로 맞춘다. 기계는\n * 대개 그렇게 쌓인다(받침 → 어깨 → 팔). 관절을 걸 때도 이 자리가 필요하다 — 맞닿는 면이\n * 실제로 있어야 `seamOrigin` 이 회전 중심을 그 면에서 잡는다.\n *\n * 위에 얹으면 기준 상자를 넘는 경우에는 바닥 한가운데로 간다. 넘은 채로 놓으면 발행이\n * 막히고(`part-outside-base`), 그것을 고치는 일이 「부품을 추가했다」의 대가가 된다.\n * 가로·세로는 상자 안으로 당긴다 — 고른 부품이 가장자리에 있으면 그 위가 상자 밖이다.\n */\nexport function newPart(\n draft: FigureDraft,\n parts: PartModel[],\n primitive: PrimitiveKind,\n onTopOf?: PartModel\n): PartModel {\n const taken = new Set(parts.map(part => part.name))\n\n const width = Math.max(1, Math.round(draft.width / 3))\n const height = Math.max(1, Math.round(draft.height / 3))\n const depth = Math.max(1, Math.round(draft.depth / 3))\n\n // 바닥 한가운데 — 고를 부품이 없을 때, 그리고 위가 상자 밖일 때의 자리.\n const middle = {\n left: Math.round((draft.width - width) / 2),\n top: Math.round((draft.height - height) / 2),\n // `zPos` 는 부피의 밑면이라 0 이 곧 상자 바닥이다.\n zPos: 0\n }\n const above = onTopOf && {\n left: clamp(Math.round(onTopOf.left + onTopOf.width / 2 - width / 2), 0, draft.width - width),\n top: clamp(Math.round(onTopOf.top + onTopOf.height / 2 - height / 2), 0, draft.height - height),\n zPos: Math.round(onTopOf.zPos + onTopOf.depth)\n }\n const at = above && above.zPos + depth <= draft.depth ? above : middle\n\n const part: PartModel = {\n type: 'figure-part',\n name: freeName(taken, primitive),\n primitive,\n left: at.left,\n top: at.top,\n width,\n height,\n zPos: at.zPos,\n depth,\n // 팔레트 토큰은 기존 부품의 것을 물려받는다 — 토큰이 늘어날 때마다 재질 그룹이 하나씩 는다.\n token: parts[0]?.token ?? 'palette.primary'\n }\n\n if (SEGMENTED_PRIMITIVES.includes(primitive)) {\n part.segments = SEGMENT_PRESETS[1]\n }\n if (EXTRUDE_PRIMITIVES.includes(primitive)) {\n part.round = 0\n }\n if (primitive === 'polygon') {\n // polygon 은 점이 3개 이상이어야 한다. 없으면 검증에서 막히므로 삼각형으로 시작한다.\n // 단면의 점은 부품 중심을 기준으로 적는다.\n part.path = [\n { x: -Math.round(width / 2), y: -Math.round(height / 2) },\n { x: Math.round(width / 2), y: -Math.round(height / 2) },\n { x: 0, y: Math.round(height / 2) }\n ]\n }\n\n return part\n}\n\n/**\n * 씬에서 온 부품을 밀리미터로 맞춘다.\n *\n * 기즈모는 비율을 곱해서 크기를 낸다 — 420 에 1.72 를 곱하면 722.4180046035418 이고, 손대지\n * 않은 축도 384.99999999999994 처럼 흔들린다. 저작자가 끈 결과가 소수 넷째 자리까지 남을\n * 까닭이 없고, 칸에는 「722.4…」로 잘려 보인다. 이 편집기의 다른 길은 전부 정수 밀리미터로\n * 맞춘다(부품을 새로 놓을 때 · 가운데 맞추기 · 격자 맞추기).\n *\n * 크기는 1 밑으로 내려가지 않는다 — 0 이면 부피가 사라지고 배율이 0 으로 나뉜다.\n */\nexport function roundPart(part: PartModel): PartModel {\n return {\n ...part,\n left: round(part.left),\n top: round(part.top),\n zPos: round(part.zPos),\n width: Math.max(1, round(part.width)),\n height: Math.max(1, round(part.height)),\n depth: Math.max(1, round(part.depth))\n }\n}\n\nexport function addPart(\n draft: FigureDraft,\n parts: PartModel[],\n primitive: PrimitiveKind,\n onTopOf?: PartModel\n): PartModel[] {\n return [...parts, newPart(draft, parts, primitive, onTopOf)]\n}\n\nexport function removePart(parts: PartModel[], index: number): PartModel[] {\n return parts.filter((_, at) => at !== index)\n}\n\n/** 부품을 복제한다. 이름만 새로 붙인다 — 중복되면 검증에서 걸린다. */\nexport function duplicatePart(parts: PartModel[], index: number): PartModel[] {\n const origin = parts[index]\n if (!origin) return parts\n\n const copy: PartModel = JSON.parse(JSON.stringify(origin))\n copy.name = freeName(new Set(parts.map(part => part.name)), `${origin.name}-copy`)\n\n const next = [...parts]\n next.splice(index + 1, 0, copy)\n return next\n}\n\n/** 목록에서 순서를 바꾼다. 범위를 벗어나면 그대로 둔다. */\nexport function movePart(parts: PartModel[], index: number, to: number): PartModel[] {\n if (to < 0 || to >= parts.length || index === to) return parts\n\n const next = [...parts]\n const [moved] = next.splice(index, 1)\n next.splice(to, 0, moved!)\n return next\n}\n\nexport function updatePart(parts: PartModel[], index: number, part: PartModel): PartModel[] {\n return parts.map((old, at) => (at === index ? part : old))\n}\n\n/**\n * 정밀 배치 도구 — 좌표를 손으로 계산하지 않게 한다.\n *\n * 도면 편집기는 자산을 **배치**하는 도구라 대략적인 위치로도 충분하다. 여기는 자산을\n * **만드는** 도구라 다르다. 모터를 몸통 정중앙에 놓거나, 다리를 네 귀퉁이에 대칭으로\n * 놓거나, 부품을 바닥에 정확히 붙이는 작업은 손으로 계산하면 늘 조금씩 어긋난다.\n */\n\nfunction put(parts: PartModel[], index: number, fields: Partial<PartModel>): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n return updatePart(parts, index, { ...part, ...fields })\n}\n\n/** 위치의 한 축만 바꾼다. 어느 속성인지는 `AXIS_KEYS` 가 결정한다. */\nfunction putAt(parts: PartModel[], index: number, axis: PlaneAxis, value: number): PartModel[] {\n return put(parts, index, { [AXIS_KEYS[axis].at]: value } as Partial<PartModel>)\n}\n\n/** 부품을 기준 상자의 해당 축 가운데로 옮긴다. */\nexport function centerPart(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n return putAt(parts, index, axis, Math.round((draftExtent(draft, axis) - part[AXIS_KEYS[axis].size]) / 2))\n}\n\n/** 부품을 상자 바닥(또는 해당 축의 시작)에 붙인다. */\nexport function alignPartToStart(parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n return putAt(parts, index, axis, 0)\n}\n\n/** 부품을 상자의 끝에 붙인다. */\nexport function alignPartToEnd(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n return putAt(parts, index, axis, Math.round(draftExtent(draft, axis) - part[AXIS_KEYS[axis].size]))\n}\n\n/**\n * 부품을 축 기준으로 대칭 복제한다.\n *\n * 다리 넷, 팔 둘 같은 형상을 이것 없이 만들면 고생한다. 이름은 **어느 쪽인지 알 수\n * 있게** 붙인다 — `leg` 를 x 축으로 대칭하면 `leg-x`.\n */\nexport function mirrorPart(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part || !canMirror(draft, parts, index, axis)) return parts\n\n const copy = JSON.parse(JSON.stringify(part)) as PartModel\n copy.name = freeName(new Set(parts.map(p => p.name)), `${part.name}-${axis}`)\n\n const next = [...parts]\n next.splice(index + 1, 0, copy)\n return putAt(next, index + 1, axis, mirroredAt(draft, part, axis))\n}\n\n/** 대칭했을 때의 위치. 상자의 해당 축 안에서 뒤집는다. */\nfunction mirroredAt(draft: FigureDraft, part: PartModel, axis: PlaneAxis): number {\n const { at, size } = AXIS_KEYS[axis]\n return Math.round(draftExtent(draft, axis) - part[at] - part[size])\n}\n\n/**\n * 대칭이 의미가 있는지.\n *\n * 이미 그 축의 가운데에 있는 부품을 대칭하면 **같은 자리에 사본이 생긴다.** 화면에는\n * 아무 변화가 없어 보이는데 부품 수만 늘고, 겹친 면에서 z-fighting 이 일어난다.\n * 만들어 놓고 알리는 대신 미리 막는다.\n */\nexport function canMirror(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): boolean {\n const part = parts[index]\n if (!part) return false\n\n return mirroredAt(draft, part, axis) !== Math.round(part[AXIS_KEYS[axis].at])\n}\n\n/**\n * 부품의 위치와 크기를 격자에 맞춘다.\n *\n * 격자 간격은 상자 크기에서 계산한다 — 고정 값이면 작은 형상에서는 너무 크고 큰\n * 형상에서는 너무 잘다. 3D 바닥 격자와 같은 계산이라 화면에서 보이는 것과 일치한다.\n */\nexport function snapPart(draft: FigureDraft, parts: PartModel[], index: number): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n const step = gridStep(draft)\n const round = (value: number) => Math.round(value / step) * step\n\n return put(parts, index, {\n left: round(part.left),\n top: round(part.top),\n zPos: round(part.zPos),\n // 크기는 0 이 되면 안 된다 — 부품이 사라진 것처럼 보인다\n width: Math.max(step, round(part.width)),\n height: Math.max(step, round(part.height)),\n depth: Math.max(step, round(part.depth))\n })\n}\n\n/**\n * 격자 간격. 3D 바닥에 그려지는 격자와 같은 값이다.\n *\n * **세 축을 다 본다.** 평면만 보면 키가 큰 모델에서 격자가 지나치게 잘아진다. 시그널타워\n * (바닥 160 x 160, 높이 1100)는 평면 기준으로 간격이 20 이 나와서, 옆에서 보면 높이가\n * 격자 55 칸이 된다. 격자는 크기를 가늠하는 자이므로 모델 전체에 대해 읽혀야 한다.\n */\nexport function gridStep(draft: FigureDraft): number {\n return niceStep(Math.max(draft.width, draft.height, draft.depth))\n}\n\n/**\n * 주어진 길이에 어울리는 **읽기 좋은** 격자 간격. 다섯 칸 정도가 되게 잡는다.\n *\n * 그냥 나누면 27·33 같은 값이 나온다. 그 격자에 맞추면 좌표가 54·81 이 되어 읽기\n * 어렵다. 1·2·5 배수로 올림한다 — 자와 차트 눈금이 쓰는 방식이다.\n *\n * 격자가 성겨야 형상이 눈에 먼저 들어온다. 열 칸으로 잡았더니 격자가 촘촘해 배경이\n * 먼저 보였다.\n */\nexport function niceStep(span: number): number {\n const raw = Math.max(span, 1) / 5\n const magnitude = Math.pow(10, Math.floor(Math.log10(raw)))\n const n = raw / magnitude\n\n const multiple = n < 1.5 ? 1 : n < 3.5 ? 2 : n < 7.5 ? 5 : 10\n return Math.max(1, multiple * magnitude)\n}\n"]}
package/test/expect.ts DELETED
@@ -1,57 +0,0 @@
1
- /*
2
- * A thin `expect` over `node:assert`, so Jest-shaped tests can run here.
3
- *
4
- * ── Why this exists ──────────────────────────────────────────────────────────
5
- * This package runs `node --test test/*.test.ts`. Two suites were written in Jest's shape and
6
- * left under `client/modeller/`, where **neither runner picked them up** — `npm test` does not
7
- * look there, and the repository's Jest is not configured for this package. They had not executed
8
- * since they were written.
9
- *
10
- * That mattered more than the count. `figure-source.ts` carries a comment saying the round-trip
11
- * guard "now reads the field list the format declares rather than trusting the fixture", and that
12
- * guard lives in one of those files. The source leaned on a test that never ran.
13
- *
14
- * Rewriting 662 lines of assertions by hand would have changed what they mean in places nobody
15
- * would notice. Eight matchers cover both files, so the cheaper and safer move is to give them
16
- * the eight.
17
- *
18
- * Only add a matcher here when a test needs it. This is a bridge for suites that already exist,
19
- * not a second assertion library for new ones — new tests in this package use `node:assert`
20
- * directly, like every other file under `test/`.
21
- */
22
- import assert from 'node:assert/strict'
23
-
24
- export interface Expectation {
25
- toBe(expected: unknown): void
26
- toEqual(expected: unknown): void
27
- toMatchObject(expected: Record<string, unknown>): void
28
- toHaveLength(expected: number): void
29
- toBeUndefined(): void
30
- toBeGreaterThan(expected: number): void
31
- toBeLessThan(expected: number): void
32
- toBeGreaterThanOrEqual(expected: number): void
33
- toBeLessThanOrEqual(expected: number): void
34
- }
35
-
36
- export function expect(actual: any): Expectation {
37
- return {
38
- /* Jest's toBe is Object.is; assert.strictEqual is the same comparison. */
39
- toBe: expected => assert.strictEqual(actual, expected),
40
- toEqual: expected => assert.deepStrictEqual(actual, expected),
41
- toMatchObject: expected => {
42
- for (const [key, value] of Object.entries(expected)) {
43
- assert.deepStrictEqual(
44
- actual?.[key],
45
- value,
46
- `${key}: ${JSON.stringify(actual?.[key])} !== ${JSON.stringify(value)}`
47
- )
48
- }
49
- },
50
- toHaveLength: expected => assert.strictEqual(actual?.length, expected, `length ${actual?.length} !== ${expected}`),
51
- toBeUndefined: () => assert.strictEqual(actual, undefined),
52
- toBeGreaterThan: expected => assert.ok(actual > expected, `${actual} is not greater than ${expected}`),
53
- toBeLessThan: expected => assert.ok(actual < expected, `${actual} is not less than ${expected}`),
54
- toBeGreaterThanOrEqual: expected => assert.ok(actual >= expected, `${actual} is not >= ${expected}`),
55
- toBeLessThanOrEqual: expected => assert.ok(actual <= expected, `${actual} is not <= ${expected}`)
56
- }
57
- }
@@ -1,511 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { fileURLToPath } from 'node:url'
3
- import { describe, it, test } from 'node:test'
4
-
5
- import { expect } from './expect.ts'
6
-
7
- import { existsSync, readFileSync } from 'fs'
8
- import { dirname, join } from 'path'
9
-
10
- import { toFigureSource, fromFigureSource, partToFigure, partFromFigure } from '../client/modeller/figure-source.ts'
11
- import type { FigureDraft, PartModel } from '../client/modeller/figure-source.ts'
12
- import type { FigurePlacement, FigureSource } from '@hatiolab/figure-model'
13
-
14
- /**
15
- * 씬 모델 ↔ `FigureSource`.
16
- *
17
- * 저작 중의 정본은 씬 모델이고, 형식은 저장하거나 판정할 때 접어서 만든다. 두 어휘가
18
- * 다르므로 **옮기는 일이 한 곳에만** 있어야 하고, 그 한 곳이 맞는지를 여기서 못 박는다.
19
- *
20
- * 가장 중요한 것은 왕복이다. 접었다 펴면 같아야 하고 펴서 접어도 같아야 한다 —
21
- * 어느 한쪽에서만 맞으면 AI 가 제안해 온 정본을 편집하는 순간 값이 샌다.
22
- */
23
-
24
- /** 판 3000 x 2000 x 800 — 세 축이 다 달라야 축을 바꿔 써도 시험이 잡는다. */
25
- const DRAFT: FigureDraft = {
26
- width: 3000,
27
- height: 2000,
28
- depth: 800,
29
- figureType: 'AGV',
30
- detailLevel: 'L'
31
- }
32
-
33
- const PART: PartModel = {
34
- type: 'figure-part',
35
- name: 'deck',
36
- primitive: 'cube',
37
- left: 1100,
38
- top: 700,
39
- width: 800,
40
- height: 600,
41
- zPos: 300,
42
- depth: 200,
43
- token: 'palette.primary'
44
- }
45
-
46
- describe('접기 — 씬 모델을 형식으로', () => {
47
- it('판이 기준 상자가 된다 — 높이는 판의 depth 다', () => {
48
- const { base } = toFigureSource(DRAFT, [])
49
- expect(base).toEqual({ x: 3000, y: 800, z: 2000 })
50
- })
51
-
52
- it('부품 자리가 판 중심 기준이 된다', () => {
53
- const [part] = toFigureSource(DRAFT, [PART]).parts
54
-
55
- // 평면 중심 (1100+400, 700+300) = (1500, 1000), 판 중심 (1500, 1000) → 원점
56
- expect(part.transform.position.x).toBe(0)
57
- expect(part.transform.position.z).toBe(0)
58
- })
59
-
60
- it('zPos is the underside; the format writes the part centre above the box bottom', () => {
61
- const [part] = toFigureSource(DRAFT, [PART]).parts
62
-
63
- // underside 300 + half the 200 thickness = centre 400, measured from the box bottom (edition 2)
64
- expect(part.transform.position.y).toBe(400)
65
- })
66
-
67
- it('가운데를 벗어난 부품이 그만큼 어긋나 적힌다', () => {
68
- const moved = { ...PART, left: 1200, top: 900, zPos: 500 }
69
- const [part] = toFigureSource(DRAFT, [moved]).parts
70
-
71
- expect(part.transform.position).toEqual({ x: 100, y: 600, z: 200 })
72
- })
73
-
74
- it('크기가 축 이름으로 바뀐다 — 두께가 y 다', () => {
75
- const [part] = toFigureSource(DRAFT, [PART]).parts
76
- expect(part.transform.size).toEqual({ x: 800, y: 200, z: 600 })
77
- })
78
-
79
- /*
80
- 씬은 라디안을 쓰고 이름이 뒤섞여 있다 — rotationY 가 three 의 z 로 간다.
81
- 형식은 도를 쓰고 축 이름이 그대로다.
82
- */
83
- it('회전이 도로 바뀌고 축이 제자리를 찾는다', () => {
84
- const turned = { ...PART, rotationX: Math.PI / 6, rotation: Math.PI / 2, rotationY: Math.PI }
85
- const [part] = toFigureSource(DRAFT, [turned]).parts
86
-
87
- expect(part.transform.rotation).toEqual({ x: 30, y: -90, z: 180 })
88
- })
89
-
90
- it('회전이 없으면 필드를 두지 않는다', () => {
91
- const [part] = toFigureSource(DRAFT, [PART]).parts
92
- expect(part.transform.rotation).toBeUndefined()
93
- })
94
-
95
- it('재질이 한 덩어리로 모인다', () => {
96
- const [part] = toFigureSource(DRAFT, [{ ...PART, preset: 'metal', flatShading: false }]).parts
97
- expect(part.material).toEqual({ token: 'palette.primary', preset: 'metal', flatShading: false })
98
- })
99
-
100
- it('단면 값이 shape 으로 모인다', () => {
101
- const [part] = toFigureSource(DRAFT, [{ ...PART, primitive: 'rect', round: 40 }]).parts
102
- expect(part.shape).toEqual({ round: 40 })
103
- })
104
-
105
- it('단면 값이 없으면 shape 을 두지 않는다', () => {
106
- expect(toFigureSource(DRAFT, [PART]).parts[0].shape).toBeUndefined()
107
- })
108
- })
109
-
110
- describe('펴기 — 형식을 씬 모델로', () => {
111
- const SOURCE: FigureSource = {
112
- version: 2,
113
- type: 'AGV',
114
- base: { x: 3000, y: 800, z: 2000 },
115
- detailLevel: 'L',
116
- parts: [
117
- {
118
- name: 'deck',
119
- primitive: 'cube',
120
- transform: { position: { x: 100, y: 600, z: 200 }, size: { x: 800, y: 200, z: 600 } },
121
- material: { token: 'palette.primary' }
122
- }
123
- ]
124
- }
125
-
126
- it('기준 상자가 판이 된다', () => {
127
- const { draft } = fromFigureSource(SOURCE)
128
- expect(draft).toMatchObject({ width: 3000, height: 2000, depth: 800 })
129
- })
130
-
131
- it('중심 기준 자리가 씬의 모서리·밑면으로 바뀐다', () => {
132
- const [part] = fromFigureSource(SOURCE).parts
133
- expect(part).toMatchObject({ left: 1200, top: 900, zPos: 500 })
134
- })
135
-
136
- it('부품이 씬 타입을 갖는다 — 판에 그대로 놓을 수 있어야 한다', () => {
137
- expect(fromFigureSource(SOURCE).parts[0].type).toBe('figure-part')
138
- })
139
- })
140
-
141
- /*
142
- 왕복.
143
-
144
- 이것이 이 모듈의 존재 이유다. 한쪽에서만 맞으면 AI 가 제안해 온 정본을 펴서
145
- 편집하고 다시 접는 순간 값이 샌다 — 그것도 조용히.
146
- */
147
- /**
148
- * 형식이 가진 것을 되도록 다 담은 정본. 안 쓰이는 필드도 넣는다.
149
- *
150
- * describe 밖에 둔다 — 아래 「픽스처가 형식을 다 담는다」가 이것을 형식의 필드 목록과
151
- * 맞대어 본다.
152
- */
153
- /** 형식이 가진 것을 되도록 다 담은 정본. 안 쓰이는 필드도 넣는다. */
154
- const FULL: FigureSource = {
155
- version: 2,
156
- type: 'AGV',
157
- base: { x: 3000, y: 800, z: 2000 },
158
- placement: 'floor',
159
- detailLevel: 'L',
160
- styleKit: 'factory',
161
- parts: [
162
- {
163
- name: 'deck',
164
- primitive: 'cube',
165
- transform: { position: { x: 100, y: 200, z: 200 }, size: { x: 800, y: 200, z: 600 } },
166
- material: { token: 'palette.primary', preset: 'metal', flatShading: true }
167
- },
168
- {
169
- name: 'wheel',
170
- primitive: 'cylinder',
171
- segments: 24,
172
- transform: {
173
- position: { x: -700, y: -260, z: 420 },
174
- size: { x: 180, y: 70, z: 180 },
175
- rotation: { z: 90 }
176
- },
177
- material: { token: 'palette.dark', preset: 'rubber' }
178
- },
179
- {
180
- name: 'panel',
181
- primitive: 'rect',
182
- // 속을 판 단면. 한 겹 안이라 처음 판 시험이 못 봤던 자리다.
183
- shape: { round: 40, hollow: { wall: 8, floor: 4 } },
184
- transform: {
185
- position: { x: 0, y: 260, z: -500 },
186
- size: { x: 400, y: 30, z: 200 },
187
- rotation: { x: 15, y: -30, z: 45 }
188
- },
189
- material: { token: 'palette.accent', transparent: true },
190
- /* Fixed to the deck, so the deck joint carries it (ADR-0066). */
191
- parent: 'deck',
192
- // 아직 아무 일도 안 하는 필드들. 잃어버리면 안 된다.
193
- materialSlot: 'cap',
194
- keepRound: false,
195
- sizing: 'fixed',
196
- anchor: { x: 'span', y: 'max', z: 'center' },
197
- label: { name: 'rpm', source: 'speed', when: 'zoomed' }
198
- },
199
- {
200
- name: 'foot',
201
- primitive: 'polygon',
202
- shape: {
203
- path: [
204
- { x: -60, y: -40 },
205
- { x: 60, y: -40 },
206
- { x: 0, y: 40 }
207
- ]
208
- },
209
- transform: { position: { x: 900, y: -340, z: -600 }, size: { x: 120, y: 80, z: 80 } },
210
- material: { token: 'palette.neutral' },
211
- sizing: 'repeat',
212
- repeat: { axis: 'x', pitch: 200 }
213
- },
214
- {
215
- /*
216
- 물건을 놓는 자리이면서 드나드는 문이다 — AGV 의 상판이 그렇다. **그려지지 않으므로**
217
- 재질이 없다.
218
-
219
- 그리지 않는 것이라 화면으로는 왕복에서 누락됐는지 알 수 없다. 자리를 잃어버린 도형은
220
- 멀쩡해 보이면서 아무것도 받지 못한다.
221
- */
222
- name: 'seat',
223
- primitive: 'cube',
224
- transform: { position: { x: 0, y: 420, z: 0 }, size: { x: 700, y: 500, z: 500 } },
225
- capability: { roles: ['slot', 'port-in', 'port-out'], accepts: ['PALLET'], capacity: 2 }
226
- }
227
- ],
228
- /* 이 도형이 지는 능력. 부품이 아니라 도형 전체의 것이다. */
229
- capabilities: ['FlowNode'],
230
- /*
231
- Joints (ADR-0066). Nothing on the editing canvas moves them, which is exactly why they are here: a
232
- field the modeller does not edit is the one that disappears on open and save without anyone seeing.
233
- */
234
- joints: [
235
- {
236
- name: 'deck-lift',
237
- child: 'deck',
238
- type: 'prismatic',
239
- origin: { x: 100, y: 100, z: 200 },
240
- axis: { x: 0, y: 1, z: 0 },
241
- limits: { min: 0, max: 400 }
242
- }
243
- ],
244
- /*
245
- 움직임. 편집면이 아직 안 만지는 자리이며, **바로 그래서 여기 있다.**
246
-
247
- 이 픽스처가 「형식이 가진 것을 되도록 다 담은 정본」인데 clip 이 빠져 있었고, 그
248
- 사이 `toFigureSource` 가 clip 을 안 실었다. 시험은 초록이었다 — 픽스처에 없는 것은
249
- 시험이 못 본다. 표본 롤러에 clip 을 적고 미리보기에서 아무것도 안 돌고 나서야 알았다.
250
- */
251
- animations: [
252
- {
253
- name: 'spin',
254
- channels: [
255
- {
256
- target: 'wheel',
257
- path: 'rotation',
258
- pivot: { x: 0, y: 0, z: 0 },
259
- keys: [
260
- { at: 0, value: { x: 0, y: 0, z: 0 } },
261
- { at: 1, value: { x: 0, y: 360, z: 0 } }
262
- ]
263
- }
264
- ]
265
- }
266
- ],
267
- /*
268
- 값이 만드는 자세. 애니메이션과 **다른 칸이라** 따로 담는다 — 같은 이유로 여기 있다.
269
- 한 칸이 새면 열었다 저장하는 것만으로 조용히 지워진다.
270
- */
271
- parameters: [
272
- {
273
- name: 'lift',
274
- label: '포크 높이',
275
- range: { unit: 'mm', min: 0, max: 1800 },
276
- default: 0,
277
- clip: {
278
- duration: 2.5,
279
- channels: [
280
- {
281
- target: 'wheel',
282
- path: 'translation',
283
- keys: [
284
- { at: 0, value: { x: 0, y: 0, z: 0 } },
285
- { at: 1, value: { x: 0, y: 1800, z: 0 } }
286
- ]
287
- }
288
- ]
289
- }
290
- },
291
- {
292
- /* A joint channel: no path, one joint coordinate per key. */
293
- name: 'deck-height',
294
- range: { unit: 'mm', min: 0, max: 400 },
295
- clip: {
296
- channels: [
297
- {
298
- target: 'deck-lift',
299
- keys: [
300
- { at: 0, value: 0 },
301
- { at: 1, value: 400 }
302
- ]
303
- }
304
- ]
305
- }
306
- }
307
- ]
308
- }
309
-
310
- describe('왕복해도 같다', () => {
311
-
312
- it('펴서 접으면 그대로다', () => {
313
- const { draft, parts } = fromFigureSource(FULL)
314
- expect(toFigureSource(draft, parts)).toEqual(FULL)
315
- })
316
-
317
- it('접어서 펴면 그대로다', () => {
318
- const { draft, parts } = fromFigureSource(FULL)
319
- const again = fromFigureSource(toFigureSource(draft, parts))
320
-
321
- expect(again.draft).toEqual(draft)
322
- expect(again.parts).toEqual(parts)
323
- })
324
-
325
- it('두 번 왕복해도 값이 밀리지 않는다 — 라디안·도를 오가도', () => {
326
- let source = FULL
327
- for (let i = 0; i < 5; i++) {
328
- const { draft, parts } = fromFigureSource(source)
329
- source = toFigureSource(draft, parts)
330
- }
331
- expect(source).toEqual(FULL)
332
- })
333
-
334
- it('움직임도 살아남는다 — 열었다 저장하는 것만으로 지워지던 자리다', () => {
335
- const { draft, parts } = fromFigureSource(FULL)
336
-
337
- expect(toFigureSource(draft, parts).animations).toEqual(FULL.animations)
338
- })
339
-
340
- it('값이 만드는 자세도 살아남는다 — 애니메이션과 다른 칸이라 따로 샐 수 있다', () => {
341
- const { draft, parts } = fromFigureSource(FULL)
342
-
343
- expect(toFigureSource(draft, parts).parameters).toEqual(FULL.parameters)
344
- })
345
-
346
- it('시각이 흐르는 clip 은 그대로 애니메이션에 남는다', () => {
347
- const { draft } = fromFigureSource(FULL)
348
-
349
- expect(draft.animations).toHaveLength(1)
350
- expect(draft.animations![0]!.name).toBe('spin')
351
- })
352
-
353
- it('안 쓰이는 필드도 살아남는다 — 조용히 사라지면 저작자가 알 길이 없다', () => {
354
- const { draft, parts } = fromFigureSource(FULL)
355
- const panel = toFigureSource(draft, parts).parts.find(part => part.name === 'panel')!
356
-
357
- expect(panel.materialSlot).toBe('cap')
358
- expect(panel.sizing).toBe('fixed')
359
- expect(panel.label).toEqual({ name: 'rpm', source: 'speed', when: 'zoomed' })
360
- })
361
- })
362
-
363
- /*
364
- 픽스처가 형식을 따라가는지.
365
-
366
- **이 파일에서 필드가 네 번 샜다** — `ports` · `animations` · `anchor` · `shape.hollow`.
367
- 네 번 다 왕복 시험은 초록이었다. 위의 `FULL` 이 「형식이 가진 것을 되도록 다 담은
368
- 정본」이라고 적혀 있었지만 실제로는 그때그때 손으로 채운 것이라, 형식에 필드가 늘면
369
- 픽스처는 그대로 있고 시험은 늘어난 자리를 보지 못했다.
370
-
371
- 네 번째 것은 **한 겹 안**이라 최상위만 세던 첫 판이 놓쳤다. 그래서 픽스처의 키를
372
- 깊이 상관없이 다 모아 놓고 맞댄다.
373
-
374
- 그래서 픽스처를 믿지 않고 **형식이 스스로 밝힌 필드 목록**을 읽는다. 형식이 붙은
375
- 타입 선언(`dist/types.d.ts`)이 정본이고, 거기 적힌 이름이 픽스처에 없으면 여기서
376
- 깨진다. 다음 번 필드는 화면이 아니라 이 시험이 먼저 잡는다.
377
- */
378
- describe('픽스처가 형식을 다 담는다', () => {
379
- /**
380
- * 형식이 붙여 보낸 타입 선언 파일.
381
- *
382
- * `require.resolve` 로는 못 잡는다 — jest 의 resolver 가 `.d.ts` 를 모듈로 보지
383
- * 않는다. 올라가면서 찾고, 못 찾으면 조용히 건너뛰지 않고 여기서 깨진다.
384
- */
385
- function declarationPath(): string {
386
- const here = dirname(fileURLToPath(import.meta.url))
387
- for (let at = here; at !== dirname(at); at = dirname(at)) {
388
- const candidate = join(at, 'node_modules/@hatiolab/figure-model/dist/types.d.ts')
389
- if (existsSync(candidate)) return candidate
390
- }
391
- throw new Error('figure-model 의 타입 선언을 못 찾았다 — 이 시험은 그것이 정본이다')
392
- }
393
-
394
- /** 붙은 타입 선언에서 한 인터페이스의 필드 이름을 뽑는다. */
395
- function fieldsOf(name: string): string[] {
396
- const declared = readFileSync(declarationPath(), 'utf-8')
397
- const start = declared.indexOf(`export interface ${name} {`)
398
- expect(start).toBeGreaterThan(-1)
399
-
400
- // 중첩이 없는 평평한 인터페이스다. 다음 닫는 중괄호가 끝이다.
401
- const body = declared.slice(start, declared.indexOf('\n}', start))
402
- return [...body.matchAll(/^\s{4}(\w+)\??:/gm)].map(m => m[1])
403
- }
404
-
405
- /** 픽스처가 쓴 키를 깊이 상관없이 모은다 — 흘린 필드가 한 겹 안에 있었다. */
406
- function keysDeep(value: unknown, into = new Set<string>()): Set<string> {
407
- if (Array.isArray(value)) value.forEach(one => keysDeep(one, into))
408
- else if (value && typeof value === 'object') {
409
- for (const [key, one] of Object.entries(value)) {
410
- into.add(key)
411
- keysDeep(one, into)
412
- }
413
- }
414
- return into
415
- }
416
-
417
- const WRITTEN = keysDeep(FULL)
418
-
419
- /*
420
- `fold` 가 실제로 낱개로 푸는 것들이다. 통째로 나르는 구조(`transform` 처럼)는 필드가
421
- 늘어도 안 새므로 여기 없다.
422
- */
423
- for (const name of ['FigureSource', 'FigurePart', 'PartShape', 'HollowSpec']) {
424
- it(`${name} 가 가진 모든 필드가 왕복 픽스처에 있다`, () => {
425
- expect(fieldsOf(name).filter(field => !WRITTEN.has(field))).toEqual([])
426
- })
427
- }
428
- })
429
-
430
- /*
431
- * ── The placement is a height, not a flip ────────────────────────────────────
432
- *
433
- * `placement` says which reference surface a figure of this kind attaches to. It is a fact about
434
- * the figure *type*, and what acts on it is the scene placing an instance: a floor figure stands on
435
- * the ground, a ceiling one hangs at ceiling height less its own. `operato-scene`'s archetype says
436
- * the same — *ceiling: zPos derived from ceiling minus depth* (`archetypes.ts`).
437
- *
438
- * **Nothing about the authored shape depends on it.** The format measures every part from the box
439
- * centre, so two figures with identical parts and different placements have identical `parts`.
440
- *
441
- * It was wired the other way for a while: the editing canvas fed the figure's placement to the
442
- * model layer's `state.placement`, a scene-wide coordinate mode that flips what `zPos` names for
443
- * everything in that layer. It stood in for the archetype because the modeller holds one figure —
444
- * and every number the author had typed then meant a different face depending on a dropdown, while
445
- * what got saved never changed. These four tests hold that line.
446
- */
447
- test('배치를 바꿔도 부품의 자리는 한 글자도 안 바뀐다', () => {
448
- const source: FigureSource = {
449
- type: 'OHT_TEST',
450
- base: { x: 2, y: 3, z: 2 },
451
- parts: [
452
- /* 두께가 서로 달라야 한다 — 두께에 비례하는 어긋남은 통째로 옮기는 것과 달리 형상을 벌린다. */
453
- { name: 'rail', primitive: 'cube', transform: { position: { x: 0, y: 1.425, z: 0 }, size: { x: 2, y: 0.15, z: 0.4 } }, material: { token: 'palette.primary' } },
454
- { name: 'belt', primitive: 'cube', transform: { position: { x: 0, y: 0, z: 0 }, size: { x: 0.1, y: 1, z: 0.1 } }, material: { token: 'palette.neutral' } }
455
- ]
456
- }
457
-
458
- const authored = JSON.stringify(source.parts)
459
-
460
- for (const placement of ['floor', 'ceiling', 'center'] as const) {
461
- const { draft, parts } = fromFigureSource({ ...source, placement })
462
- assert.equal(
463
- JSON.stringify(toFigureSource(draft, parts).parts),
464
- authored,
465
- `${placement}: 배치가 저작한 자리를 바꿨다`
466
- )
467
- }
468
- })
469
-
470
- test('편집 캔버스의 zPos 는 배치와 무관하게 밑면이다', () => {
471
- /*
472
- 저작자가 인스펙터에 적는 수다. 그 뜻이 드롭다운에 따라 밑면↔윗면으로 바뀌면, 아무것도
473
- 안 고쳤는데 모든 부품의 높이가 달라진 것으로 보인다. OHT 에서 실제로 그랬다 —
474
- overhead-rail 이 1.35 에서 -1.5 가 되었다.
475
- */
476
- const part: PartModel = { type: 'figure-part', name: 'rail', primitive: 'cube', left: 0, top: 0, width: 2, height: 0.4, zPos: 1.35, depth: 0.15 }
477
- const draft: FigureDraft = { width: 2, height: 2, depth: 3 }
478
-
479
- for (const placement of [undefined, 'floor', 'ceiling', 'center'] as const) {
480
- const written = partToFigure(part, { ...draft, placement })
481
- /* underside 1.35 + half the 0.15 thickness = centre 1.425, measured from the box bottom. */
482
- assert.equal(written.transform.position.y, 1.425, `${placement}: 접는 쪽이 배치를 봤다`)
483
- assert.equal(partFromFigure(written, { ...draft, placement }).zPos, 1.35, `${placement}: 펴는 쪽이 배치를 봤다`)
484
- }
485
- })
486
-
487
- describe('배치는 씬의 좌표 모드가 아니다', () => {
488
- const read = (rel: string) => readFileSync(join(dirname(fileURLToPath(import.meta.url)), rel), 'utf8')
489
-
490
- /*
491
- 소스를 읽는 검사다. Lit 컴포넌트가 document 없이 안 뜨는 자리라 그렇고, 무엇보다
492
- **없어야 하는 것이 없는지**를 묻는 검사는 호출로는 물을 수가 없다.
493
- */
494
- it('변환이 좌표 모드를 아예 모른다', () => {
495
- const source = read('../client/modeller/figure-source.ts')
496
-
497
- assert.doesNotMatch(source, /zPosOffset|toScenePlacement/, '변환에 좌표 모드가 다시 들어왔다')
498
- assert.doesNotMatch(
499
- source,
500
- /placement.*=>.*'inverted'|'inverted'.*:/,
501
- '씬의 좌표 낱말이 변환에 들어왔다'
502
- )
503
- })
504
-
505
- })
506
-
507
- describe('보기 설정이 편집 캔버스로 새지 않는다', () => {
508
- const read = (rel: string) => readFileSync(join(dirname(fileURLToPath(import.meta.url)), rel), 'utf8')
509
-
510
- })
511
-