@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
@@ -57,7 +57,6 @@ const FIGURE_LIST_FIELDS = `
57
57
  score
58
58
  triangles
59
59
  groups
60
- format
61
60
  thumbnailUpdatedAt
62
61
  updatedAt
63
62
  updater { id name }
@@ -244,28 +243,6 @@ export async function inspectFigure(id: string): Promise<FigureInspection> {
244
243
  why
245
244
  how
246
245
  at
247
- fixes {
248
- findingWay
249
- part
250
- axis
251
- from
252
- to
253
- resolved
254
- safe
255
- }
256
- fixPlan {
257
- resolved
258
- safe
259
- fixes {
260
- findingWay
261
- part
262
- axis
263
- from
264
- to
265
- resolved
266
- safe
267
- }
268
- }
269
246
  blocking
270
247
  }
271
248
  }
@@ -297,28 +274,6 @@ export async function inspectFigureDraft(source: string, id?: string): Promise<F
297
274
  why
298
275
  how
299
276
  at
300
- fixes {
301
- findingWay
302
- part
303
- axis
304
- from
305
- to
306
- resolved
307
- safe
308
- }
309
- fixPlan {
310
- resolved
311
- safe
312
- fixes {
313
- findingWay
314
- part
315
- axis
316
- from
317
- to
318
- resolved
319
- safe
320
- }
321
- }
322
277
  blocking
323
278
  }
324
279
  }
@@ -8,7 +8,7 @@ import { i18next, localize } from '@operato/i18n'
8
8
  import { ScrollbarStyles } from '@operato/styles'
9
9
 
10
10
  import { fetchFigureVersions, inspectFigure, releaseFigure, revertFigureVersion } from '../graphql/index.js'
11
- import type { FigureGateFinding, FigureGateFix, FigureGateFixPlan, FigureInspection, FigureVersion } from '../types.js'
11
+ import type { FigureGateFinding, FigureInspection, FigureVersion } from '../types.js'
12
12
  import { OxPrompt } from '@operato/popup'
13
13
 
14
14
  /**
@@ -195,55 +195,6 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
195
195
  「이 중 어느 것이 발행을 막았나」를 사람이 다시 세어야 한다.
196
196
  */
197
197
 
198
- /* 설명으로 끝내지 않는다. 이 단추는 원본을 바꾸지 않고 모델러에 후보만 연다. */
199
- li[finding] button[preview-fix] {
200
- margin-top: 3px;
201
- padding: 4px 7px;
202
- font-weight: 700;
203
- color: var(--md-sys-color-on-primary-container, var(--md-sys-color-on-surface));
204
- background-color: var(--md-sys-color-primary-container, transparent);
205
- border: 0;
206
- border-radius: 5px;
207
- cursor: pointer;
208
- }
209
-
210
- li[finding] button[preview-fix]:hover {
211
- filter: brightness(1.08);
212
- }
213
-
214
- li[finding] button[apply-fix] {
215
- margin-top: 3px;
216
- padding: 4px 7px;
217
- font-weight: 700;
218
- color: var(--ui-on-accent, #fff);
219
- background-color: var(--md-sys-color-primary);
220
- border: 0;
221
- border-radius: 5px;
222
- cursor: pointer;
223
- }
224
-
225
- div[ratio-choice] {
226
- display: flex;
227
- flex-wrap: wrap;
228
- gap: 6px;
229
- margin-top: 4px;
230
- }
231
-
232
- div[ratio-choice] button:not([apply-fix]) {
233
- padding: 4px 7px;
234
- color: var(--md-sys-color-on-surface);
235
- background: transparent;
236
- border: 1px solid var(--md-sys-color-outline-variant);
237
- border-radius: 5px;
238
- cursor: pointer;
239
- }
240
-
241
- div[ratio-choice] button[selected] {
242
- color: var(--md-sys-color-on-primary-container);
243
- background: var(--md-sys-color-primary-container);
244
- border-color: transparent;
245
- }
246
-
247
198
  p[quiet] {
248
199
  margin: 0;
249
200
  line-height: 1.6;
@@ -258,25 +209,16 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
258
209
  @property({ type: Number }) figureVersion = 0
259
210
  /** 저장하지 않은 것이 있나 — 있으면 발행을 막는다. */
260
211
  @property({ type: Boolean }) dirty = false
261
- /**
262
- * 열린 도형의 형식. 발행 · 판본 · 복원은 figure id 로만 서버를 묻고 서버의 한 문이 두 형식을 다 읽으므로 두 화면이
263
- * 이 패널을 함께 쓴다. 갈리는 것은 조치 단추 하나다 — 부품을 옮기는 두 조치는 형식마다 다른 편집이라, 받는
264
- * 화면이 없는 단추를 그리면 눌러도 아무 일이 없다.
265
- */
266
- @property({ type: String }) format: 'v2' | 'v3' = 'v2'
267
212
  /**
268
213
  * 행의 타입. 판정 하나(`type-is-placeholder`)가 이것을 본다 — V3 자산은 타입을 원본에 적지 않아서, 타입을 바꾸면
269
214
  * 원본도 dirty 도 그대로다. 그래서 이 값이 바뀌면 판정을 다시 묻는다. 안 그러면 타입을 정한 뒤에도 「임시 타입」
270
215
  * 판정이 남아 발행 단추가 잠긴 채다.
271
216
  */
272
217
  @property({ type: String }) figureType?: string
273
- /** 직전 발행판의 기준 상자. 외형 변화 안내에서 X·Z 복원 선택지에만 쓴다. */
274
- @property({ attribute: false }) releasedBase?: { x: number; y: number; z: number }
275
218
 
276
219
  @state() private versions: FigureVersion[] = []
277
220
  @state() private busy = false
278
221
  @state() private failure = ''
279
- @state() private keepChangedAppearance = false
280
222
  /**
281
223
  * 발행 판정. **저장된 정본에 대한 것**이라 저장할 때마다 다시 묻는다(`figureVersion` 이 아니라
282
224
  * `dirty` 가 풀리는 순간이 그때다).
@@ -298,7 +240,6 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
298
240
  }
299
241
 
300
242
  private async inspect() {
301
- this.keepChangedAppearance = false
302
243
  if (!this.figureId || this.dirty) {
303
244
  this.inspection = undefined
304
245
  return
@@ -354,7 +295,6 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
354
295
  */
355
296
  const blocking = (this.inspection?.findings ?? []).filter(finding => finding.blocking)
356
297
  const advisory = (this.inspection?.findings ?? []).filter(finding => !finding.blocking)
357
- const appearanceChoiceNeeded = advisory.some(finding => finding.code === 'base-ratio-changed') && !this.keepChangedAppearance
358
298
 
359
299
  /*
360
300
  ── 「발행되나」에 **한 번만** 답한다 ─────────────────────────────────────
@@ -369,8 +309,6 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
369
309
  ? i18next.t('figure.text.release-needs-a-save-first')
370
310
  : released
371
311
  ? i18next.t('figure.text.already-released-edit-to-release-again')
372
- : appearanceChoiceNeeded
373
- ? i18next.t('figure.text.choose-appearance-change')
374
312
  : blocking.length
375
313
  ? i18next.t('figure.text.cannot-release-now')
376
314
  : ''
@@ -418,61 +356,9 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
418
356
  ${i18next.t('figure.button.fit-base-to-parts')}
419
357
  </button>`
420
358
  : nothing}
421
- ${finding.code === 'parts-off-placement-face' && this.format === 'v2'
422
- ? html`<button fit-base @click=${() => this.settleOnFloor()}>
423
- ${i18next.t('figure.button.settle-on-floor')}
424
- </button>`
425
- : nothing}
426
- ${finding.code === 'base-ratio-changed' && this.releasedBase
427
- ? html`<div ratio-choice>
428
- <button apply-fix @click=${() => this.restoreReleasedFootprint()}>
429
- ${i18next.t('figure.button.restore-released-base')}
430
- </button>
431
- <button ?selected=${this.keepChangedAppearance} @click=${() => this.keepChangedFootprint()}>
432
- ${i18next.t('figure.button.keep-changed-appearance')}
433
- </button>
434
- </div>
435
- <span how-line><md-icon>info</md-icon>${i18next.t('figure.text.appearance-choice-explained')}</span>`
436
- : nothing}
437
- ${(finding.fixes ?? []).map(fix => html`
438
- ${fix.safe ? html`
439
- <button apply-fix @click=${() => this.applyFix(fix)}>
440
- ${i18next.t('figure.button.apply-fix', { part: fix.part, axis: fix.axis.toUpperCase(), from: fix.from ?? 'auto', to: fix.to })}
441
- </button>
442
- ` : nothing}
443
- <button preview-fix @click=${() => this.previewFix(fix)}>
444
- ${i18next.t('figure.button.preview-fix', { part: fix.part, axis: fix.axis.toUpperCase(), from: fix.from ?? 'auto', to: fix.to })}
445
- </button>
446
- `)}
447
- ${finding.fixPlan?.safe ? html`
448
- <span how-line><md-icon>auto_fix_high</md-icon>${i18next.t('figure.text.recommended-fix-explained', { n: finding.fixPlan.fixes.length })}</span>
449
- <button apply-fix @click=${() => this.applyFixPlan(finding.fixPlan!)}>
450
- ${i18next.t('figure.button.apply-recommended-fix')}
451
- </button>
452
- ` : nothing}
453
359
  `)
454
360
  }
455
361
 
456
- /**
457
- * 게이트는 수정을 저장하지 않는다. 부모 모델러가 현재 정본과 이 한 수정안을 나란히 렌더하고,
458
- * 저작자가 '변경 받기'를 눌렀을 때에만 자기 작업 사본에 적용한다.
459
- */
460
- private previewFix(fix: FigureGateFix) {
461
- this.dispatchEvent(new CustomEvent('preview-sizing-fix', { detail: fix, bubbles: true, composed: true }))
462
- }
463
-
464
- /** 서버가 전수 재검사해 모든 차단 split이 사라진 후보만 작업 사본에 원클릭 적용한다. 저장은 하지 않는다. */
465
- private applyFix(fix: FigureGateFix) {
466
- if (!fix.safe) return
467
- this.dispatchEvent(new CustomEvent('apply-sizing-fix', { detail: fix, bubbles: true, composed: true }))
468
- }
469
-
470
- /** Apply the server-remeasured coordinated repair to the draft only; save remains explicit. */
471
- private applyFixPlan(plan: FigureGateFixPlan) {
472
- if (!plan.safe || plan.fixes.length === 0) return
473
- this.dispatchEvent(new CustomEvent('apply-sizing-fix-plan', { detail: plan, bubbles: true, composed: true }))
474
- }
475
-
476
362
  /**
477
363
  * 넘친 것을 담는 길 하나를 여기서도 내민다 — **같은 동작이 속성 패널에도 있다**(「부품에 맞추기」).
478
364
  *
@@ -482,44 +368,19 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
482
368
  * 여전히 저작자가 따로 한다.
483
369
  */
484
370
  /**
485
- * 「부품에 맞추기」를 내미는 판정. V2 는 기준 상자를 부품에 맞추고, V3 는 공간을 부품에서 다시 선언한다
486
- * (`declare-occupancy`) — 둘 다 부품은 그대로 두고 선언만 바꾼다. V3 에서 공간을 아직 선언하지 않은 도형도
487
- * 같은 단추로 선언한다: 새로 만든 V3 도형은 전부 그 판정으로 막히고, 화면에 다른 길이 없다.
488
- *
489
- * V3 의 장착면이 떠 있는 것은 여기 없다. 부품 하나를 평면에 붙이면 그 부품에 붙지 않은 것은 따라오지 않아
490
- * V2 의 「바닥에 앉히기」와 뜻이 다르다. 판정의 「어떻게」가 할 일을 말한다.
371
+ * 「부품에 맞추기」를 내미는 판정: 공간을 부품에서 다시 선언한다(`declare-occupancy`) — 부품은 그대로 두고 선언만
372
+ * 바꾼다. 공간을 아직 선언하지 않은 도형도 같은 단추로 선언한다: 새로 만든 도형은 전부 그 판정으로 막히고, 화면에
373
+ * 다른 길이 없다. 장착면이 떠 있는 것은 여기 없다 — 부품 하나를 평면에 붙여도 거기 붙지 않은 것은 따라오지 않는다.
374
+ * 판정의 「어떻게」가 할 일을 말한다.
491
375
  */
492
376
  private offersFit(code: string): boolean {
493
- return this.format === 'v3'
494
- ? code === 'part-outside-occupancy' || code === 'occupancy-undeclared'
495
- : code === 'part-outside-base'
377
+ return code === 'part-outside-occupancy' || code === 'occupancy-undeclared'
496
378
  }
497
379
 
498
380
  private fitBase() {
499
381
  this.dispatchEvent(new CustomEvent('fit-base-to-parts', { bubbles: true, composed: true }))
500
382
  }
501
383
 
502
- /**
503
- * 떠 있는 것을 앉힌다. 부품 하나를 고르고 「바닥에」를 누르는 길은 전부터 있었으나, 그 길은
504
- * 고른 것 하나만 옮기고 **여기서는 닿지 않는다.** 막혔다는 말을 읽은 자리에서 조치가 되게 한다.
505
- */
506
- private settleOnFloor() {
507
- this.dispatchEvent(new CustomEvent('settle-parts-on-floor', { bubbles: true, composed: true }))
508
- }
509
-
510
- /** 기준 상자의 X·Z만 직전 발행값으로 되돌린다. Y와 부품 편집은 보존한다. */
511
- private restoreReleasedFootprint() {
512
- if (!this.releasedBase) return
513
- this.dispatchEvent(new CustomEvent('restore-released-footprint', {
514
- detail: { x: this.releasedBase.x, z: this.releasedBase.z }, bubbles: true, composed: true
515
- }))
516
- }
517
-
518
- /** 현재 외형으로 발행하겠다는 명시적 선택이다. 저장·발행은 이어서 따로 한다. */
519
- private keepChangedFootprint() {
520
- this.keepChangedAppearance = true
521
- }
522
-
523
384
  private renderVersion(version: FigureVersion) {
524
385
  return html`
525
386
  <li>
@@ -9,7 +9,7 @@ import { SidePanelStyles } from './side-panel-styles.js'
9
9
 
10
10
  /*
11
11
  One rung per set of the format's capability names. Typed as the format's names, so a misspelt rung does not pass
12
- (ADR-0051 ④, the V2 ladder's lesson).
12
+ (ADR-0051 ④).
13
13
  */
14
14
  type Rung = 'none' | 'hold' | 'flow'
15
15
  const RUNGS: Readonly<Record<Rung, readonly FigureCapability[]>> = Object.freeze({
@@ -28,7 +28,7 @@ export function rungOf(names: readonly string[] | undefined): Rung | null {
28
28
  }
29
29
 
30
30
  /**
31
- * 자리·능력 — what the figure does on a board (V2's 「능력」 tab, parity row ⑤): the ladder from drawing only, to
31
+ * 자리·능력 — what the figure does on a board: the ladder from drawing only, to
32
32
  * holding things, to passing them on, and every place things are held, whatever part it is on. A place is not drawn,
33
33
  * so the list is the only way to find one; choosing a row selects its part, where the place is edited.
34
34
  */
@@ -11,10 +11,10 @@ import { SidePanelStyles } from './side-panel-styles.js'
11
11
  import { HelpToggles } from './v3-help.js'
12
12
 
13
13
  /**
14
- * 클립 — V2's animations (parity row ⑥): controls played by keys over time. A clip is one clock; each track plays one
14
+ * 클립 — controls played by keys over time. A clip is one clock; each track plays one
15
15
  * control. Scrubbing a clip shows its pose at that moment (one batch of input changes, not saved as the default); a key
16
16
  * is added at the scrubbed moment with the control's value there, so a pose is made with the controls' sliders and
17
- * then kept as a key -- the way V2's recorder worked. Every change is one part edit.
17
+ * then kept as a key. Every change is one part edit.
18
18
  */
19
19
  @customElement('figure-v3-clips')
20
20
  export class FigureV3Clips extends localize(i18next)(LitElement) {
@@ -11,7 +11,7 @@ import { dialShape } from './angle-dial.js'
11
11
  import { SidePanelStyles } from './side-panel-styles.js'
12
12
 
13
13
  /**
14
- * The figure's controls (parity row ⑥, V2's parameters and its pose tools): each value a board sets to pose the figure,
14
+ * The figure's controls and its pose tools: each value a board sets to pose the figure,
15
15
  * with a slider to try it, its name, range and travel time, and a button that keeps the pose being looked at as the
16
16
  * pose it stands in when placed.
17
17
  *
@@ -25,7 +25,7 @@ export class FigureV3Controls extends localize(i18next)(LitElement) {
25
25
  :host {
26
26
  display: block;
27
27
  }
28
- /* One control, one line: its name, a dial for an angle, a thin slider, the value (V2's preview row). */
28
+ /* One control, one line: its name, a dial for an angle, a thin slider, the value. */
29
29
  div[control] {
30
30
  display: grid;
31
31
  grid-template-columns: minmax(0, 1fr) 18px minmax(64px, 1.2fr) 58px 22px;
@@ -10,7 +10,7 @@ import '@material/web/button/text-button.js'
10
10
  import { firstSaveRefusalOf, releasableType, typeOf, type FirstSave } from './v3-first-save.js'
11
11
 
12
12
  /**
13
- * The first save of a new figure: its type and its name, asked once (V2's save dialog, in the suite's popup look).
13
+ * The first save of a new figure: its type and its name, asked once, in the suite's popup look.
14
14
  * Closes with the answer, or with nothing when cancelled -- then nothing is saved.
15
15
  */
16
16
  @customElement('figure-v3-first-save')
@@ -60,14 +60,14 @@ const freshRow = () => ({ axis: '' as Axis | '', pitch: 200, fills: true, count:
60
60
  const freshPlace = () => ({ role: 'grip' as 'seat' | 'grip', face: 'top' as V3Face, label: '', accepts: '', capacity: 1 })
61
61
 
62
62
  /**
63
- * What a part is tied to — its size response, what it is fastened to, how it moves (parity rows ④ ⑤ ⑥).
63
+ * What a part is tied to — its size response, what it is fastened to, how it moves.
64
64
  *
65
65
  * 「Build Once, Fit Anywhere」 lives here: a dimension that follows the instance size or another part is what lets
66
66
  * a figure be placed at any size, and a part fastened to the face it rests on is what keeps it there when that
67
67
  * happens. The release gate refuses a figure that falls apart when resized, and tells the author to fasten the
68
- * parts that come away — this is where they do it. Motion is the V2 joint: a slide or a turn driven by a control.
68
+ * parts that come away — this is where they do it. Motion is a joint: a slide or a turn driven by a control.
69
69
  *
70
- * The V2 inspector has these as its dimensions, placement and joint sections, in that order; so does this. Every
70
+ * The sections are dimensions, placement and joint, in that order. Every
71
71
  * change is one figure-model authoring command, sent as `v3-command` and run by the editor through the session — one
72
72
  * step of the undo history. Choices the command would refuse (a round body fastened, a size following an instance
73
73
  * size the figure does not declare) are not offered, so the author is not sent into a refusal.
@@ -158,7 +158,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
158
158
  `
159
159
  }
160
160
 
161
- /** Where things are held on this part: a seat things rest on, or a grip things are held in (parity row ⑤). */
161
+ /** Where things are held on this part: a seat things rest on, or a grip things are held in. */
162
162
  private renderPlaces(asset: V3Asset) {
163
163
  const faces = placeFacesOf(asset, this.partId)
164
164
  const p = this.place
@@ -294,7 +294,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
294
294
  }
295
295
  const [of, a, b] = choice.split(':')
296
296
  if (of === 'share') {
297
- /* V2's proportional rule: this measure is the share of the instance size it is now, so it grows with the figure. */
297
+ /* A proportional rule: this measure is the share of the instance size it is now, so it grows with the figure. */
298
298
  const size = asset.designInputs[`size.${a}`]
299
299
  if (!size) return
300
300
  const times = Math.round((current / size) * 1e6) / 1e6
@@ -306,7 +306,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
306
306
  }
307
307
 
308
308
  /**
309
- * Positions worked out from other values — V2's anchor rules, as the converter wrote them — each with the way to
309
+ * Positions worked out from other values, each with the way to
310
310
  * keep it where it is. This is the way out the release gate names when a resize leaves a part in the air.
311
311
  */
312
312
  private renderPositions(asset: V3Asset) {
@@ -7,7 +7,7 @@ import { activePalette } from '@hatiolab/things-scene'
7
7
  import type { V3AssetGeometry, V3Material, V3PartEdit } from '@hatiolab/figure-model'
8
8
  import { SidePanelStyles } from './side-panel-styles.js'
9
9
 
10
- /** The purpose each palette role is for, by its token — the V2 inspector's words for the same roles. */
10
+ /** The purpose each palette role is for, by its token. */
11
11
  const PURPOSE: Record<string, string> = {
12
12
  'palette.primary': 'primary',
13
13
  'palette.accent': 'accent',
@@ -27,15 +27,15 @@ const ROUND = new Set(['cylinder', 'ellipticCylinder', 'sphere', 'frustum'])
27
27
  const DEFAULT_SEGMENTS = 12
28
28
 
29
29
  /**
30
- * 재질 — a V3 part's material, drawn as the V2 inspector draws it (V3 parity row ③).
30
+ * 재질 — a V3 part's material.
31
31
  *
32
- * The V3 editor showed the colour role as a raw token (`palette.dark`) in a select and nothing else. The V3 material
32
+ * An earlier editor showed the colour role as a raw token (`palette.dark`) in a select and nothing else. The V3 material
33
33
  * holds the colour role, a preset, transparency and a glow; a round body also has how finely its rim is drawn. This
34
- * shows those, with the V2 words: painted colours and lit colours as two groups of swatches, each named for what it
34
+ * shows those: painted colours and lit colours as two groups of swatches, each named for what it
35
35
  * is for.
36
36
  *
37
- * Not here, because a V3 material has no place for them: flat shading and casting shadows (V2 fields the converter
38
- * does not carry). The glow's colour comes from the lit colours, as in V2.
37
+ * Not here, because a V3 material has no place for them: flat shading and casting
38
+ * shadows. The glow's colour comes from the lit colours.
39
39
  *
40
40
  * Every change leaves as one `v3-part-edit`; the editor runs it through the session, one step of the undo history.
41
41
  */
@@ -75,7 +75,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
75
75
  font: inherit;
76
76
  min-width: 0;
77
77
  }
78
- /* V2's segment picker: the four common counts as their silhouettes with their triangle cost, the rest folded. */
78
+ /* The segment picker: the four common counts as their silhouettes with their triangle cost, the rest folded. */
79
79
  div[segments] {
80
80
  display: grid;
81
81
  grid-template-columns: repeat(4, 1fr);
@@ -102,7 +102,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
102
102
  background-color: var(--md-sys-color-primary-container);
103
103
  border-color: transparent;
104
104
  }
105
- /* Small, and 12 and 16 both read as circles: large enough for the corners to show (V2). */
105
+ /* Small, and 12 and 16 both read as circles: large enough for the corners to show. */
106
106
  button[seg] svg {
107
107
  display: block;
108
108
  width: 32px;
@@ -166,7 +166,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
166
166
  if (!part) return nothing
167
167
  const material = part.material
168
168
  /*
169
- The roles offered are the app's palette, as in V2, with any the asset carries beyond it. A new asset carries
169
+ The roles offered are the app's palette, with any the asset carries beyond it. A new asset carries
170
170
  only its primary colour; a role it lacks is sent with its colour and added in the same edit (`put`).
171
171
  */
172
172
  const entries = Object.entries(this.offered())
@@ -282,7 +282,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
282
282
  return { ...(activePalette() as Record<string, string>), ...this.palette }
283
283
  }
284
284
 
285
- /** Intensity 0 is no glow at all, as in V2; a new glow takes the part's own colour until another is picked. */
285
+ /** Intensity 0 is no glow at all; a new glow takes the part's own colour until another is picked. */
286
286
  private glow(intensity: number, live = false) {
287
287
  const material = this.body!.material
288
288
  this.glowing = live ? intensity : undefined
@@ -310,7 +310,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
310
310
  }
311
311
  }
312
312
 
313
- /** A regular polygon of this many sides -- how round a body with this rim count looks from its end (V2). */
313
+ /** A regular polygon of this many sides -- how round a body with this rim count looks from its end. */
314
314
  function silhouette(segments: number) {
315
315
  const points: string[] = []
316
316
  for (let i = 0; i < segments; i++) {
@@ -11,14 +11,12 @@ import { partLabelOf } from './v3-fields.js'
11
11
  /**
12
12
  * 도형 팔레트와 부품 목록 — V3 자산의 것.
13
13
  *
14
- * `figure-parts`(V2) 와 **같은 자리, 같은 조작, 같은 낱말**이다. 저작자가 V2 에서 익힌 손이 그대로
15
- * 가야 한다: 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.
14
+ * 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.
16
15
  *
17
- * 다른 것은 읽는 대상뿐이다. V2 는 부품의 `primitive` 를 읽었고 여기서는 V3 평가가 준 `provider` 를
18
- * 읽는다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라
16
+ * 부품의 도형은 V3 평가가 준 `provider` 로 읽는다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라
19
17
  * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.
20
18
  *
21
- * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — V2 와 같은 규칙, 같은 사건 이름.
19
+ * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다.
22
20
  */
23
21
 
24
22
  /** 한 줄 — V3 평가에서 이 패널이 읽는 것만. */
@@ -50,8 +48,7 @@ const PROVIDER_ICON: Record<string, string> = {
50
48
 
51
49
  /** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */
52
50
  /*
53
- The palette: V2's shapes. A plate and a wall are boxes of a shape a person reaches for (V2 drew both as boxes), so
54
- they are starting sizes, not kinds of their own.
51
+ The palette. A plate and a wall are boxes of a shape a person reaches for, so they are starting sizes, not kinds of their own.
55
52
  */
56
53
  export type V3PaletteKind = V3PrimitiveCreation['kind'] | 'plate' | 'wall'
57
54
  const RECIPES: Array<{ kind: V3PaletteKind; icon: string; label: string }> = [
@@ -359,11 +356,11 @@ export class FigureV3Parts extends localize(i18next)(LitElement) {
359
356
  @property({ type: Boolean }) locked = false
360
357
  /**
361
358
  * 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의
362
- * 기준이 저작을 이끈다(V2 모델러의 정체성). 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.
359
+ * 기준이 저작을 이끈다. 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.
363
360
  */
364
361
  @property({ attribute: false }) budget?: V3BudgetLine
365
362
  /**
366
- * 디테일 등급의 부품 한도 — V2 의 S/M/L(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가
363
+ * 디테일 등급(S/M/L)의 부품 한도(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가
367
364
  * 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.
368
365
  */
369
366
  @property({ attribute: false }) partBudget?: { level: 'S' | 'M' | 'L' | null; used: number; limit: number | null }
@@ -411,7 +408,7 @@ export class FigureV3Parts extends localize(i18next)(LitElement) {
411
408
  `
412
409
  }
413
410
 
414
- /** 디테일 등급 — 고르면 한도가 바뀐다. V2 의 자리 그대로, 목록 머리 아래. */
411
+ /** 디테일 등급 — 고르면 한도가 바뀐다. 목록 머리 아래. */
415
412
  private renderLevel() {
416
413
  const b = this.partBudget
417
414
  if (!b) return nothing
@@ -11,15 +11,15 @@ import { dialShape } from './angle-dial.js'
11
11
  import { authoredSizeOf, PREVIEW_FACTOR, PREVIEW_WAYS, previewClampsOf, previewInstances } from './v3-preview-model.js'
12
12
 
13
13
  /**
14
- * The figure placed on a board -- V2's preview, for a V3 figure.
14
+ * The figure placed on a board.
15
15
  *
16
16
  * The editing canvas shows the figure at the size it was made. Half of what a figure is lies in how it answers a
17
17
  * different size, so here the figure being authored is registered as a board registers it and placed as
18
18
  * `FigureInstance`s: the size it was made, and each of the seven ways of growing one, two or three axes twice as
19
- * large, side by side at one scale, four to a row (V2's eight). The board draws them as it will on a real board --
19
+ * large, side by side at one scale, four to a row. The board draws them as it will on a real board --
20
20
  * placement, lights, environment, the same V3 renderer.
21
21
  *
22
- * The controls are V2's too: each value that poses the figure with a slider (and a dial for an angle), each clip's
22
+ * The controls: each value that poses the figure with a slider (and a dial for an angle), each clip's
23
23
  * speed, and 「이 자세를 기본으로」 to keep the pose as the one it stands in. Nothing here is edited; the canvas edits.
24
24
  */
25
25
 
@@ -17,11 +17,10 @@ const PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center', 'wall']
17
17
  const FACES = Object.keys(V3_FACES) as V3Face[]
18
18
 
19
19
  /**
20
- * 속성 — the V3 modeller's figure properties (parity row ②).
20
+ * 속성 — the modeller's figure properties.
21
21
  *
22
- * The V2 inspector opens with what the figure is: its name, the face it is placed by, the room it takes up, where
23
- * the catalogue files it. The V3 page had only the type. The same rows, in the same order, drawn the way the V2
24
- * inspector draws them.
22
+ * The tab opens with what the figure is: its name, the face it is placed by, the room it takes up, where the
23
+ * catalogue files it.
25
24
  *
26
25
  * Two kinds of change leave this tab, and it does neither itself:
27
26
  * - `v3-command` carries an authoring command (rename, declare the room, the mounting face). The page runs it
@@ -30,7 +29,7 @@ const FACES = Object.keys(V3_FACES) as V3Face[]
30
29
  * to the saved row at once, so they need a saved figure.
31
30
  *
32
31
  * Not here yet, because V3 has no place for them: editing the declared room by hand (V3 declares it from the
33
- * parts, as graph expressions), and the detail level (the converter records it as lost). Both wait on a V3 ruling.
32
+ * parts, as graph expressions), and the detail level. Both wait on a V3 ruling.
34
33
  */
35
34
  @customElement('figure-v3-properties')
36
35
  export class FigureV3Properties extends localize(i18next)(LitElement) {
@@ -81,7 +80,7 @@ export class FigureV3Properties extends localize(i18next)(LitElement) {
81
80
  render() {
82
81
  const facts = this.facts()
83
82
  if (!facts) return nothing
84
- /* The figure's type is the page's (it lives on the row); it is slotted into the first section, as V2 had it. */
83
+ /* The figure's type is the page's (it lives on the row); it is slotted into the first section. */
85
84
  return html`<section>
86
85
  <h3>${i18next.t('figure.label.group-figure')}</h3>
87
86
  <slot name="type"></slot>
@@ -19,12 +19,12 @@ import type { V3BudgetUse, V3MaturityAxis, V3Report } from './v3-maturity.js'
19
19
  export const VERDICT_SETTLE_MS = 400
20
20
 
21
21
  /**
22
- * 성숙도 — the V3 modeller's verdict while editing (parity row ①), laid out as V2's report (`figure-report.ts`).
22
+ * 성숙도 — the modeller's verdict while editing.
23
23
  *
24
- * V2's report showed, as the author edits: how many fit on one screen with the grade beside it and the budget that
24
+ * It shows, as the author edits: how many fit on one screen with the grade beside it and the budget that
25
25
  * decided the grade, the grade scale, six maturity axes as a radar and a list, the budget bars, the drawing cost,
26
- * the reuse trend, and the findings. The first V3 tab kept only the count and the findings (user, 2026-09-24:
27
- * 「분석부분도 많이 부족하고, 왜 이렇게 너프되었는지」). This is V2's report again, over V3 numbers.
26
+ * the reuse trend, and the findings. An earlier tab kept only the count and the findings (user, 2026-09-24:
27
+ * 「분석부분도 많이 부족하고, 왜 이렇게 너프되었는지」).
28
28
  *
29
29
  * ## What is computed where
30
30
  *
@@ -34,15 +34,14 @@ export const VERDICT_SETTLE_MS = 400
34
34
  *
35
35
  * The findings do not come from here: they come from the server's release door (`inspectFigureDraft`), the same
36
36
  * door and the same sentences as the release panel. A second judgement on the screen would say something different
37
- * the first time either changed. V2's report drew its findings from `validate`; V3's release judgement is the
38
- * server's, so that section is the server's answer.
37
+ * the first time either changed. The release judgement is the server's, so that section is the server's answer.
39
38
  *
40
39
  * ## The grade
41
40
  *
42
41
  * figure-model's `v3ScoreOf`, which the catalogue uses too: headroom against placing `LIMITS.screenInstances` of the
43
42
  * figure on one screen (chief architect, 2026-09-24) -- the screen's measured groups and its triangle target shared
44
- * per instance -- and V2's rule limits for independently moved parts and transparent materials, decided by the
45
- * tightest, on V2's scale. The two V2 budgets V3 has no field for are left out and the tab says so.
43
+ * per instance -- and the rule limits for independently moved parts and transparent materials, decided by the
44
+ * tightest. The two budgets V3 has no field for are left out and the tab says so.
46
45
  */
47
46
  /** A per-instance share of the screen is not a whole number; what an instance may draw is the whole part of it. */
48
47
  const limitOf = (use: V3BudgetUse) => Math.floor(use.limit).toLocaleString()
@@ -566,7 +565,7 @@ export class FigureV3Verdict extends localize(i18next)(LitElement) {
566
565
  `
567
566
  }
568
567
 
569
- /** V2's radar: rings at a third, two thirds and full, short names so the left edge is not cut. */
568
+ /** The radar: rings at a third, two thirds and full, short names so the left edge is not cut. */
570
569
  private renderRadar(axes: V3MaturityAxis[]) {
571
570
  const cx = 60
572
571
  const cy = 46
@@ -671,7 +670,7 @@ export class FigureV3Verdict extends localize(i18next)(LitElement) {
671
670
  }
672
671
 
673
672
  /**
674
- * The trend at its own size — V2's chart: both axes logarithmic, the growing lines against the flat ones, and a
673
+ * The trend at its own size: both axes logarithmic, the growing lines against the flat ones, and a
675
674
  * legend with each line's value at the last count.
676
675
  */
677
676
  private renderSheet(report: V3Report) {
@@ -4,9 +4,8 @@
4
4
  * ## 저장되지 않는다
5
5
  *
6
6
  * 이것은 자산이 아니라 **작업대**다. 조명을 밝게 하고 바닥을 켠 것은 그 사람이 지금
7
- * 보기 편한 상태일 뿐, 그 자산의 성질이 아니다. 그래서 `FigureSource` 에 안 들어간다 —
8
- * `toFigureSource` 는 판에서 `width` · `height` · `depth` 와 몇 가지만 집어 가고 나머지는
9
- * 버린다.
7
+ * 보기 편한 상태일 뿐, 그 자산의 성질이 아니다. 그래서 자산에 안 들어가고, 보는 사람의
8
+ * 브라우저에 남는다.
10
9
  *
11
10
  * 다른 사람이 같은 자산을 열면 자기 설정으로 본다. 그것이 맞다.
12
11
  *