@things-factory/figure-ui 10.1.26 → 10.1.28

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 (83) hide show
  1. package/client/graphql/index.ts +23 -0
  2. package/client/modeller/base-box.ts +104 -0
  3. package/client/modeller/figure-animations.ts +253 -32
  4. package/client/modeller/figure-canvas.ts +18 -3
  5. package/client/modeller/figure-inspector.ts +292 -3
  6. package/client/modeller/figure-preview.ts +57 -2
  7. package/client/modeller/figure-report.ts +9 -4
  8. package/client/modeller/figure-side.ts +9 -1
  9. package/client/modeller/figure-source.ts +24 -4
  10. package/client/modeller/figure-view.ts +20 -0
  11. package/client/modeller/joint-edits.ts +262 -0
  12. package/client/modeller/joint-gizmo.ts +118 -0
  13. package/client/modeller/pose-defaults.ts +67 -0
  14. package/client/modeller/proposal.ts +9 -3
  15. package/client/modeller/scene-budget.ts +31 -40
  16. package/client/pages/figure-list-page.ts +4 -9
  17. package/client/pages/figure-modeller-page.ts +124 -41
  18. package/dist-client/graphql/index.d.ts +7 -0
  19. package/dist-client/graphql/index.js +21 -0
  20. package/dist-client/graphql/index.js.map +1 -1
  21. package/dist-client/modeller/base-box.d.ts +47 -0
  22. package/dist-client/modeller/base-box.js +52 -0
  23. package/dist-client/modeller/base-box.js.map +1 -0
  24. package/dist-client/modeller/figure-animations.d.ts +29 -0
  25. package/dist-client/modeller/figure-animations.js +228 -25
  26. package/dist-client/modeller/figure-animations.js.map +1 -1
  27. package/dist-client/modeller/figure-canvas.d.ts +9 -0
  28. package/dist-client/modeller/figure-canvas.js +14 -3
  29. package/dist-client/modeller/figure-canvas.js.map +1 -1
  30. package/dist-client/modeller/figure-inspector.d.ts +37 -0
  31. package/dist-client/modeller/figure-inspector.js +253 -2
  32. package/dist-client/modeller/figure-inspector.js.map +1 -1
  33. package/dist-client/modeller/figure-preview.d.ts +15 -0
  34. package/dist-client/modeller/figure-preview.js +48 -2
  35. package/dist-client/modeller/figure-preview.js.map +1 -1
  36. package/dist-client/modeller/figure-report.d.ts +3 -2
  37. package/dist-client/modeller/figure-report.js +9 -4
  38. package/dist-client/modeller/figure-report.js.map +1 -1
  39. package/dist-client/modeller/figure-side.d.ts +5 -1
  40. package/dist-client/modeller/figure-side.js +12 -0
  41. package/dist-client/modeller/figure-side.js.map +1 -1
  42. package/dist-client/modeller/figure-source.d.ts +15 -0
  43. package/dist-client/modeller/figure-source.js +8 -4
  44. package/dist-client/modeller/figure-source.js.map +1 -1
  45. package/dist-client/modeller/figure-view.d.ts +16 -0
  46. package/dist-client/modeller/figure-view.js +15 -0
  47. package/dist-client/modeller/figure-view.js.map +1 -1
  48. package/dist-client/modeller/joint-edits.d.ts +49 -0
  49. package/dist-client/modeller/joint-edits.js +223 -0
  50. package/dist-client/modeller/joint-edits.js.map +1 -0
  51. package/dist-client/modeller/joint-gizmo.d.ts +31 -0
  52. package/dist-client/modeller/joint-gizmo.js +104 -0
  53. package/dist-client/modeller/joint-gizmo.js.map +1 -0
  54. package/dist-client/modeller/pose-defaults.d.ts +9 -0
  55. package/dist-client/modeller/pose-defaults.js +35 -0
  56. package/dist-client/modeller/pose-defaults.js.map +1 -0
  57. package/dist-client/modeller/proposal.d.ts +1 -1
  58. package/dist-client/modeller/proposal.js +8 -3
  59. package/dist-client/modeller/proposal.js.map +1 -1
  60. package/dist-client/modeller/scene-budget.d.ts +31 -34
  61. package/dist-client/modeller/scene-budget.js +28 -37
  62. package/dist-client/modeller/scene-budget.js.map +1 -1
  63. package/dist-client/pages/figure-list-page.js +4 -9
  64. package/dist-client/pages/figure-list-page.js.map +1 -1
  65. package/dist-client/pages/figure-modeller-page.d.ts +43 -0
  66. package/dist-client/pages/figure-modeller-page.js +121 -40
  67. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  68. package/dist-client/tsconfig.tsbuildinfo +1 -1
  69. package/dist-server/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +3 -3
  71. package/test/ai-proposal-contract.test.ts +16 -11
  72. package/test/base-box.test.ts +154 -0
  73. package/test/figure-source.test.ts +32 -0
  74. package/test/joint-edits.test.ts +165 -0
  75. package/test/joint-gizmo.test.ts +174 -0
  76. package/test/pose-defaults.test.ts +71 -0
  77. package/test/preview-board-depth.test.ts +27 -0
  78. package/test/scene-budget.test.ts +53 -0
  79. package/translations/en.json +37 -2
  80. package/translations/ja.json +37 -2
  81. package/translations/ko.json +37 -2
  82. package/translations/ms.json +37 -2
  83. package/translations/zh.json +37 -2
@@ -1 +1 @@
1
- {"version":3,"file":"figure-animations.js","sourceRoot":"","sources":["../../client/modeller/figure-animations.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { AXES, CHANNEL_PATHS, INTERPOLATIONS, LIMITS } from '@hatiolab/figure-model'\nimport type { ChannelPath, Vec3 } from '@hatiolab/figure-model'\n\nimport type { FigureDraft, PartModel } from './figure-source.js'\n\n/**\n * 이 도형이 **어떻게 움직이는가.**\n *\n * ## 왜 탭 하나가 필요한가\n *\n * 움직임은 형식에 있고 런타임까지 서 있었다 — 검증이 촘촘히 보고, 청사진이 움직이는 부품을\n * 단독 묶음으로 빼고(합치면 따로 못 움직인다), 씬이 frameClock 을 구독해 실제로 돌린다.\n * 미리보기에도 clip 마다 슬라이더가 있었다.\n *\n * **만들 자리만 없었다.** 그래서 움직이는 도형을 얻는 길이 「AI 가 후보로 내놓는 것」 하나였고,\n * 채널 하나를 고치려면 손댈 화면이 없었다.\n *\n * ## 부품이 아니라 도형의 것이다\n *\n * clip 하나가 부품 여럿을 함께 움직인다 — 문 열림은 왼짝과 오른짝이 반대로 미끄러지는 것\n * 하나다. 그래서 부품을 고르고 「이 부품의 움직임」을 적는 형태가 될 수 없고, 속성 탭이 아니라\n * 자기 탭에 산다.\n *\n * ## 트윈은 타임라인을 재생하지 않는다\n *\n * 「문 열기 clip 을 재생하라」가 아니라 **「지금 40% 열려 있다」**다. 그 둘은 다른 것이라\n * 이 탭도 둘로 나뉜다.\n *\n * 애니메이션 시각이 흐른다. 인스턴스가 주는 수는 배속이다 — 도는 롤러\n * 파라미터 값이 자세를 만든다. 인스턴스가 주는 수는 물리량이다 — 1200mm 내려온 호이스트\n *\n * 한동안 뒤엣것이 애니메이션 안에 `drive: 'hold'` 로 숨어 있었다. 형식의 판단은 맞았는데\n * **자리와 이름이 틀려서**, 「길이값을 변수로 노출하라」는 요청에 저작자도 AI 도 이것을\n * 못 찾았다 — 애니메이션 칸에 있을 거라고 생각하지 않는다(ADR-0051).\n */\n\ntype Clip = NonNullable<FigureDraft['animations']>[number]\ntype Parameter = NonNullable<FigureDraft['parameters']>[number]\ntype Channel = Clip['channels'][number]\ntype Keyframe = Channel['keys'][number]\n\n/** 어느 목록의 몇째인가. 채널·키 편집기가 둘을 함께 쓴다. */\ntype MotionKind = 'clip' | 'parameter'\n\n/** A first-success motion: intent first, raw channel editing second. */\ninterface MotionRecipe {\n id: 'conveyor' | 'gate' | 'pusher' | 'lift'\n name: string\n kind: MotionKind\n path: ChannelPath\n end: Vec3\n pivot?: Vec3\n /** 파라미터 레시피만. 인스턴스가 주고받는 물리량이다. */\n range?: Parameter['range']\n}\n\n/*\n 레시피의 `end` 가 곧 그 범위의 끝 자세다 — 리프트가 100mm 올라가는 곡선이면 범위도\n 0~100mm 다. 둘이 어긋나면 저작자가 1200 을 주고도 100 만 올라가는 것을 보게 된다.\n*/\nconst MOTION_RECIPES: readonly MotionRecipe[] = [\n { id: 'conveyor', name: 'conveyor-running', kind: 'clip', path: 'rotation', end: { x: 360, y: 0, z: 0 } },\n {\n id: 'gate',\n name: 'gate-open',\n kind: 'parameter',\n path: 'rotation',\n end: { x: 0, y: 90, z: 0 },\n pivot: { x: 0, y: 0, z: 0 },\n range: { unit: '%', min: 0, max: 100 }\n },\n {\n id: 'pusher',\n name: 'pusher-position',\n kind: 'parameter',\n path: 'translation',\n end: { x: 100, y: 0, z: 0 },\n range: { unit: 'mm', min: 0, max: 100 }\n },\n {\n id: 'lift',\n name: 'lift-height',\n kind: 'parameter',\n path: 'translation',\n end: { x: 0, y: 100, z: 0 },\n range: { unit: 'mm', min: 0, max: 100 }\n }\n]\n\n/** 새 파라미터의 범위. 단위는 저작자가 고쳐 쓴다 — 형식은 단위를 해석하지 않는다. */\nconst NEW_RANGE: Parameter['range'] = { unit: 'mm', min: 0, max: 1000 }\n\n/** 새 clip 의 키 둘. **하나로 만들지 않는다** — 키 하나는 움직임이 아니라 자세다. */\nconst NEW_KEYS: Keyframe[] = [\n { at: 0, value: { x: 0, y: 0, z: 0 } },\n { at: 1, value: { x: 0, y: 0, z: 0 } }\n]\n\n/** `scale` 만 1 에서 시작한다 — 0 은 부품을 지운다. */\nfunction startValue(path: ChannelPath): Vec3 {\n return path === 'scale' ? { x: 1, y: 1, z: 1 } : { x: 0, y: 0, z: 0 }\n}\n\nfunction keysFor(path: ChannelPath): Keyframe[] {\n return NEW_KEYS.map(key => ({ at: key.at, value: startValue(path) }))\n}\n\n/**\n * 이미 쓴 이름을 비켜 간다.\n *\n * **두 목록을 함께 본다.** 인스턴스는 이름 하나로 값을 주므로 애니메이션과 파라미터가\n * 같은 이름을 가지면 어느 쪽에 준 것인지 알 수 없고, 형식이 그것을 오류로 막는다.\n */\nfunction freeName(taken: readonly { name: string }[], stem: string): string {\n const used = new Set(taken.map(one => one.name))\n if (!used.has(stem)) return stem\n for (let n = 2; ; n++) if (!used.has(`${stem}-${n}`)) return `${stem}-${n}`\n}\n\n@customElement('figure-animations')\nexport class FigureAnimations extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 다른 탭들과 같은 눈금이다 */\n h3 {\n margin: 0 0 2px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n div[recipes] { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }\n button[recipe] { min-height: 54px; align-items: flex-start; flex-direction: column; text-align: left; }\n button[recipe] strong { font-size: 0.74rem; }\n button[recipe] span { color: var(--md-sys-color-on-surface-variant); font-size: 0.68rem; line-height: 1.35; }\n\n /* ── clip ─────────────────────────────────────────────────────── */\n\n div[clip] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n div[clip][on] {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container-low);\n }\n\n div[head] {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n div[head] input[name] {\n flex: 1;\n min-width: 0;\n }\n\n /* ── channel ──────────────────────────────────────────────────── */\n\n div[channel] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 6px 8px;\n border-left: 2px solid var(--md-sys-color-outline-variant);\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[channel][orphan] {\n border-left-color: var(--md-sys-color-error);\n }\n\n div[row] {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n div[row] > label {\n flex: none;\n min-width: 3.2em;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* ── keyframe ─────────────────────────────────────────────────── */\n\n table[keys] {\n width: 100%;\n border-collapse: collapse;\n }\n table[keys] th {\n padding: 0 2px 2px;\n font-size: 0.68rem;\n font-weight: 600;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n }\n table[keys] td {\n padding: 1px 2px;\n }\n table[keys] input {\n width: 100%;\n min-width: 0;\n }\n table[keys] td[drop] {\n width: 1.6em;\n }\n\n input,\n select {\n box-sizing: border-box;\n padding: 3px 5px;\n font: inherit;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 3px;\n }\n input:focus,\n select:focus {\n outline: none;\n border-color: var(--md-sys-color-primary);\n }\n input[bad] {\n border-color: var(--md-sys-color-error);\n }\n\n button {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 3px 8px;\n font: inherit;\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 3px;\n cursor: pointer;\n }\n button:hover:not(:disabled) {\n background-color: var(--md-sys-color-surface-container-high);\n }\n button:disabled {\n opacity: 0.4;\n cursor: default;\n }\n button[plain] {\n padding: 1px 4px;\n background: none;\n border: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[plain]:hover:not(:disabled) {\n color: var(--md-sys-color-error);\n background: none;\n }\n button md-icon {\n font-size: 14px;\n width: 14px;\n height: 14px;\n }\n\n /* ── 예산 ─────────────────────────────────────────────────────── */\n\n div[budget] {\n display: flex;\n align-items: baseline;\n gap: 6px;\n }\n div[budget] strong {\n font-size: 0.86rem;\n font-weight: 700;\n }\n div[budget][over] strong {\n color: var(--md-sys-color-error);\n }\n\n p[warn] {\n margin: 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n `\n ]\n\n @property({ type: Object }) draft?: FigureDraft\n @property({ type: Array }) parts: PartModel[] = []\n\n /**\n * 펼쳐 둔 것. 이름이 아니라 **목록과 자리**로 잡는다 — 이름은 편집 중에 바뀐다.\n *\n * 목록이 둘이므로 자리 하나로는 모자란다. `clip:0` 과 `parameter:0` 은 다른 것이다.\n */\n @state() private open = ''\n @state() private recipeTarget = ''\n\n private get clips(): Clip[] {\n return this.draft?.animations ?? []\n }\n\n private get parameters(): Parameter[] {\n return this.draft?.parameters ?? []\n }\n\n /** 두 목록의 이름을 한자리에서 본다 — 인스턴스가 이름 하나로 값을 준다. */\n private get allNames(): { name: string }[] {\n return [...this.clips, ...this.parameters]\n }\n\n private channelsOf(kind: MotionKind, at: number): Channel[] {\n return (kind === 'clip' ? this.clips[at]?.channels : this.parameters[at]?.clip?.channels) ?? []\n }\n\n /** 부품 이름 — 채널이 이것을 가리킨다. */\n private get partNames(): string[] {\n return this.parts.map(part => part.name).filter(name => !!name)\n }\n\n /**\n * 몇 부품이 움직이나 — 채널이 가리키는 **서로 다른 부품**의 수.\n *\n * clip 수가 아니다. 한 부품을 세 clip 이 움직여도 그 부품 하나가 단독 묶음이 될 뿐이다.\n * 청사진이 묶음을 가르는 기준과 같은 셈이어야 화면의 수와 판정의 수가 맞는다.\n */\n private get movingParts(): number {\n const moving = new Set<string>()\n for (const clip of this.clips) for (const channel of clip.channels) moving.add(channel.target)\n for (const parameter of this.parameters) {\n for (const channel of parameter.clip?.channels ?? []) moving.add(channel.target)\n }\n return moving.size\n }\n\n render(): TemplateResult {\n if (!this.draft) return html``\n\n const nameless = this.partNames.length === 0\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.animation')}</h3>\n </section>\n\n ${this.renderRecipes()}\n\n <section>\n <h3>${i18next.t('figure.label.parameters')}</h3>\n <p quiet>${i18next.t('figure.text.parameter-is-a-value-not-a-timeline')}</p>\n ${this.parameters.map((parameter, at) => this.renderParameter(parameter, at))}\n <button @click=${() => this.addParameter()} ?disabled=${nameless}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-parameter')}\n </button>\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.clips')}</h3>\n <p quiet>${i18next.t('figure.text.clip-is-time-running')}</p>\n ${this.clips.map((clip, at) => this.renderClip(clip, at))}\n <button @click=${() => this.addClip()} ?disabled=${nameless}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-clip')}\n </button>\n ${nameless ? html`<p quiet>${i18next.t('figure.text.animation-needs-a-named-part')}</p>` : nothing}\n </section>\n\n ${this.movingParts === 0 ? nothing : this.renderBudget()}\n `\n }\n\n /**\n * 빈 화면 — **이 탭이 무엇인지 가르치는 자리다.**\n *\n * 「clip 을 추가하세요」라고만 쓰면 처음 여는 사람은 clip 이 무엇인지 모른 채 단추를 누른다.\n * 그래서 무엇을 만들 수 있는지 예를 들고, 그것이 어떻게 움직이는지(사실이 들어온다)를 적는다.\n */\n private renderRecipes(): TemplateResult {\n return html`\n <section>\n <h3>${i18next.t('figure.label.motion-intent')}</h3>\n <p quiet>${i18next.t('figure.text.motion-intent-help')}</p>\n ${this.partNames.length\n ? html`<select .value=${this.recipeTarget} @change=${(e: Event) => (this.recipeTarget = (e.target as HTMLSelectElement).value)}>\n ${this.partNames.map(name => html`<option value=${name}>${name}</option>`)}\n </select>\n <div recipes>\n ${MOTION_RECIPES.map(recipe => html`\n <button recipe @click=${() => this.addRecipe(recipe)}>\n <strong>${i18next.t('figure.label.recipe-' + recipe.id)}</strong>\n <span>${i18next.t('figure.text.recipe-' + recipe.id)}</span>\n </button>\n `)}\n </div>`\n : html`<p quiet>${i18next.t('figure.text.animation-needs-a-named-part')}</p>`}\n </section>\n `\n }\n\n /** 이름이 다른 것과 겹치나. 두 목록을 함께 본다 — 인스턴스는 이름 하나로 값을 준다. */\n private taken(kind: MotionKind, at: number, name: string): boolean {\n const mine = kind === 'clip' ? this.clips[at] : this.parameters[at]\n return this.allNames.some(one => one !== mine && one.name === name)\n }\n\n private renderClip(clip: Clip, at: number): TemplateResult {\n const key = `clip:${at}`\n const on = this.open === key\n const duplicate = this.taken('clip', at, clip.name)\n const nameless = !clip.name?.trim()\n\n return html`\n <div clip ?on=${on}>\n <div head>\n <button plain @click=${() => (this.open = on ? '' : key)} title=${i18next.t('figure.label.channels')}>\n <md-icon>${on ? 'expand_more' : 'chevron_right'}</md-icon>\n </button>\n <input\n name\n .value=${live(clip.name ?? '')}\n ?bad=${duplicate || nameless}\n placeholder=${i18next.t('figure.label.clip-name')}\n title=${duplicate ? i18next.t('figure.text.clip-name-taken') : i18next.t('figure.text.clip-name-is-how-it-is-driven')}\n @change=${(e: Event) => this.renameClip(at, (e.target as HTMLInputElement).value)}\n />\n <button plain @click=${() => this.removeClip(at)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n\n ${duplicate ? html`<p warn>${i18next.t('figure.text.clip-name-taken')}</p>` : nothing}\n ${nameless ? html`<p warn>${i18next.t('figure.text.clip-needs-a-name')}</p>` : nothing}\n ${on ? this.renderChannels('clip', at) : nothing}\n </div>\n `\n }\n\n /**\n * 파라미터 하나 — **이름·범위·자세.**\n *\n * 범위가 화면에 있어야 하는 이유는 그것이 **인스턴스와 주고받는 말**이기 때문이다.\n * 곡선은 0~1 이고 인스턴스는 mm 로 준다. 그 사이를 런타임이 맵하는데, 무엇부터 무엇\n * 까지인지 저작자가 정하지 않으면 값을 줄 수가 없다.\n */\n private renderParameter(parameter: Parameter, at: number): TemplateResult {\n const key = `parameter:${at}`\n const on = this.open === key\n const duplicate = this.taken('parameter', at, parameter.name)\n const nameless = !parameter.name?.trim()\n const range = parameter.range ?? NEW_RANGE\n const backwards = !(range.max > range.min)\n const slowness = parameter.clip?.duration ?? 0\n\n return html`\n <div clip ?on=${on}>\n <div head>\n <button plain @click=${() => (this.open = on ? '' : key)} title=${i18next.t('figure.label.channels')}>\n <md-icon>${on ? 'expand_more' : 'chevron_right'}</md-icon>\n </button>\n <input\n name\n .value=${live(parameter.name ?? '')}\n ?bad=${duplicate || nameless}\n placeholder=${i18next.t('figure.label.parameter-name')}\n title=${duplicate\n ? i18next.t('figure.text.clip-name-taken')\n : i18next.t('figure.text.parameter-name-is-how-a-value-arrives')}\n @change=${(e: Event) => this.renameParameter(at, (e.target as HTMLInputElement).value)}\n />\n <button plain @click=${() => this.removeParameter(at)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n\n ${duplicate ? html`<p warn>${i18next.t('figure.text.clip-name-taken')}</p>` : nothing}\n ${nameless ? html`<p warn>${i18next.t('figure.text.parameter-needs-a-name')}</p>` : nothing}\n\n <div row>\n <label>${i18next.t('figure.label.parameter-range')}</label>\n <input\n type=\"number\"\n step=\"1\"\n ?bad=${backwards}\n .value=${live(String(range.min))}\n aria-label=\"min\"\n @change=${(e: Event) => this.setRange(at, { min: Number((e.target as HTMLInputElement).value) || 0 })}\n />\n <input\n type=\"number\"\n step=\"1\"\n ?bad=${backwards}\n .value=${live(String(range.max))}\n aria-label=\"max\"\n @change=${(e: Event) => this.setRange(at, { max: Number((e.target as HTMLInputElement).value) || 0 })}\n />\n <input\n unit\n .value=${live(range.unit ?? '')}\n placeholder=\"mm\"\n aria-label=\"unit\"\n title=${i18next.t('figure.text.unit-is-a-word-not-a-rule')}\n @change=${(e: Event) => this.setRange(at, { unit: (e.target as HTMLInputElement).value.trim() })}\n />\n </div>\n ${backwards ? html`<p warn>${i18next.t('figure.text.range-needs-room')}</p>` : nothing}\n\n <div row>\n <label>${i18next.t('figure.label.parameter-default')}</label>\n <input\n type=\"number\"\n step=\"1\"\n .value=${live(String(parameter.default ?? range.min))}\n @change=${(e: Event) => this.setDefault(at, Number((e.target as HTMLInputElement).value) || 0)}\n />\n </div>\n\n <div row>\n <label>${i18next.t('figure.label.parameter-duration')}</label>\n <input\n type=\"number\"\n step=\"0.1\"\n min=\"0\"\n ?bad=${slowness < 0}\n .value=${live(parameter.clip?.duration === undefined ? '' : String(parameter.clip.duration))}\n placeholder=\"0\"\n aria-label=\"duration\"\n @change=${(e: Event) => this.setDuration(at, (e.target as HTMLInputElement).value)}\n />\n </div>\n <p quiet>${i18next.t('figure.text.parameter-duration-empty-is-instant')}</p>\n\n ${on ? this.renderChannels('parameter', at) : nothing}\n </div>\n `\n }\n\n private renderChannels(kind: MotionKind, at: number): TemplateResult {\n const channels = this.channelsOf(kind, at)\n\n return html`\n <p quiet>${i18next.t(kind === 'clip' ? 'figure.text.clip-is-time-running' : 'figure.text.parameter-curve-is-the-value-axis')}</p>\n\n ${channels.length === 0 ? html`<p warn>${i18next.t('figure.text.clip-needs-a-channel')}</p>` : nothing}\n ${channels.map((channel, j) => this.renderChannel(kind, channel, at, j))}\n\n <button @click=${() => this.addChannel(kind, at)}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-channel')}\n </button>\n `\n }\n\n /**\n * 채널 하나 — 어느 부품의 어느 속성을 움직이나.\n *\n * 없는 부품을 가리키는 채널은 붉게 낸다. 부품 이름을 바꾸면 그 순간 이렇게 되는데,\n * 검증은 저장할 때 말하고 여기서는 **그 자리에서** 보여 준다.\n */\n private renderChannel(kind: MotionKind, channel: Channel, at: number, j: number): TemplateResult {\n const orphan = !this.partNames.includes(channel.target)\n const turning = channel.path === 'rotation'\n\n return html`\n <div channel ?orphan=${orphan}>\n <div row>\n <label>${i18next.t('figure.label.channel-target')}</label>\n <select\n .value=${live(channel.target ?? '')}\n @change=${(e: Event) => this.setChannel(kind, at, j, { target: (e.target as HTMLSelectElement).value })}\n >\n ${orphan ? html`<option value=${channel.target}>${channel.target}</option>` : nothing}\n ${this.partNames.map(name => html`<option value=${name}>${name}</option>`)}\n </select>\n <button plain @click=${() => this.removeChannel(kind, at, j)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n\n ${orphan ? html`<p warn>${i18next.t('figure.text.channel-target-is-gone')}</p>` : nothing}\n\n <div row>\n <label>${i18next.t('figure.label.channel-path')}</label>\n <select\n .value=${live(channel.path)}\n @change=${(e: Event) => this.setPath(kind, at, j, (e.target as HTMLSelectElement).value as ChannelPath)}\n >\n ${CHANNEL_PATHS.map(path => html`<option value=${path}>${i18next.t('figure.label.path-' + path)}</option>`)}\n </select>\n <select\n .value=${live(channel.interpolation ?? 'linear')}\n title=${i18next.t('figure.text.interpolation-explained')}\n @change=${(e: Event) =>\n this.setChannel(kind, at, j, { interpolation: (e.target as HTMLSelectElement).value as Channel['interpolation'] })}\n >\n ${INTERPOLATIONS.map(\n one => html`<option value=${one}>${i18next.t('figure.label.interpolation-' + one)}</option>`\n )}\n </select>\n </div>\n\n <p quiet>${i18next.t('figure.text.path-' + channel.path + '-unit')}</p>\n\n ${turning ? this.renderPivot(kind, channel, at, j) : nothing}\n ${this.renderKeys(kind, channel, at, j)}\n </div>\n `\n }\n\n /**\n * 회전 중심 — **`rotation` 에서만 뜻이 있다.**\n *\n * 그래서 경로가 rotation 일 때만 낸다. 언제나 내면 저작자가 옮김·배율에도 적고, 검증이\n * 저장할 때 「쓰이지 않는다」고 알린다. 안 보이면 적을 일이 없다.\n */\n private renderPivot(kind: MotionKind, channel: Channel, at: number, j: number): TemplateResult {\n const pivot = channel.pivot\n const on = pivot !== undefined\n\n return html`\n <div row>\n <label>${i18next.t('figure.label.channel-pivot')}</label>\n <button\n @click=${() => this.setChannel(kind, at, j, { pivot: on ? undefined : { x: 0, y: 0, z: 0 } })}\n title=${i18next.t('figure.text.pivot-defaults-to-the-part-centre')}\n >\n ${i18next.t(on ? 'figure.button.pivot-clear' : 'figure.button.pivot-set')}\n </button>\n ${on\n ? AXES.map(\n axis => html`\n <input\n type=\"number\"\n step=\"1\"\n .value=${live(String(pivot![axis]))}\n aria-label=${axis}\n @change=${(e: Event) =>\n this.setChannel(kind, at, j, {\n pivot: { ...pivot!, [axis]: Number((e.target as HTMLInputElement).value) || 0 }\n })}\n />\n `\n )\n : nothing}\n </div>\n `\n }\n\n /**\n * 키프레임 표.\n *\n * **둘 미만으로 못 내려간다.** 하나뿐이면 움직이는 것이 아니라 자세 하나이고, 형식이\n * 오류로 막는다. 저장하고 나서 알려 주는 것보다 지우는 단추를 죽이는 것이 낫다.\n *\n * 시각이 오름차순이어야 하는 것도 형식의 규칙이다. 여기서는 어긋난 줄을 붉게 내고 값을\n * 고치지는 않는다 — 저작자가 3 을 1 로 고치던 중일 수 있다.\n */\n private renderKeys(kind: MotionKind, channel: Channel, at: number, j: number): TemplateResult {\n const keys = channel.keys ?? []\n /*\n Keys on a parameter curve sit on the 0..1 value span (ADR-0051). A key above 1 is unreachable and\n the format refuses it. The screen marks it and leaves the number alone: rewriting what the author\n typed would hide what they wrote.\n */\n const onValues = kind === 'parameter'\n const outside = (key: Keyframe) => onValues && key.at > 1\n\n return html`\n <table keys>\n <tr>\n <th>${i18next.t(onValues ? 'figure.label.key-at-value' : 'figure.label.key-at')}</th>\n ${AXES.map(axis => html`<th>${axis}</th>`)}\n <th></th>\n </tr>\n ${keys.map((key, k) => {\n const backwards = k > 0 && key.at <= (keys[k - 1]!.at ?? 0)\n return html`\n <tr>\n <td>\n <input\n type=\"number\"\n step=${onValues ? '0.05' : '0.1'}\n min=\"0\"\n max=${onValues ? '1' : nothing}\n ?bad=${backwards || key.at < 0 || outside(key)}\n .value=${live(String(key.at))}\n title=${backwards\n ? i18next.t('figure.text.keys-must-rise')\n : outside(key)\n ? i18next.t('figure.text.parameter-key-within-span')\n : ''}\n @change=${(e: Event) =>\n this.setKey(kind, at, j, k, { at: Math.max(0, Number((e.target as HTMLInputElement).value) || 0) })}\n />\n </td>\n ${AXES.map(\n axis => html`\n <td>\n <input\n type=\"number\"\n step=${channel.path === 'scale' ? '0.1' : '1'}\n .value=${live(String(key.value[axis]))}\n aria-label=${axis}\n @change=${(e: Event) =>\n this.setKey(kind, at, j, k, {\n value: { ...key.value, [axis]: Number((e.target as HTMLInputElement).value) || 0 }\n })}\n />\n </td>\n `\n )}\n <td drop>\n <button\n plain\n ?disabled=${keys.length <= 2}\n title=${keys.length <= 2 ? i18next.t('figure.text.two-keys-at-least') : i18next.t('figure.button.remove')}\n @click=${() => this.removeKey(kind, at, j, k)}\n >\n <md-icon>close</md-icon>\n </button>\n </td>\n </tr>\n `\n })}\n </table>\n\n ${keys.some((key, k) => k > 0 && key.at <= (keys[k - 1]!.at ?? 0))\n ? html`<p warn>${i18next.t('figure.text.keys-must-rise')}</p>`\n : nothing}\n ${keys.some(outside) ? html`<p warn>${i18next.t('figure.text.parameter-key-within-span')}</p>` : nothing}\n\n <button @click=${() => this.addKey(kind, at, j)}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-key')}\n </button>\n `\n }\n\n /**\n * 움직이는 부품 예산.\n *\n * **넘어도 막지 않는다.** 움직이는 부품은 저마다 단독 묶음이라 draw call 이 늘고 프레임마다\n * 갱신이 는다 — 그 대가가 수로 보이면 저작자가 아낀다. 랜드마크 설비 하나는 정교해도 되고,\n * 그 값이 도면 예산에 드러나면 된다.\n */\n private renderBudget(): TemplateResult {\n const used = this.movingParts\n const over = used > LIMITS.animatedParts\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.budget-animations')}</h3>\n <div budget ?over=${over}>\n <strong>${used}</strong>\n <span quiet>/ ${LIMITS.animatedParts}</span>\n </div>\n <p quiet>${i18next.t('figure.text.animated-parts-cost')}</p>\n ${over ? html`<p warn>${i18next.t('figure.text.animated-parts-over')}</p>` : nothing}\n </section>\n `\n }\n\n // ─── 고치기 ───────────────────────────────────────────────────────────────\n\n /**\n * 초안에 실어 올려보낸다.\n *\n * `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로\n * 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.\n */\n private tell(over: { animations?: Clip[]; parameters?: Parameter[] }): void {\n if (!this.draft) return\n\n const draft: FigureDraft = { ...this.draft, ...over }\n if (!draft.animations?.length) delete draft.animations\n if (!draft.parameters?.length) delete draft.parameters\n\n this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }))\n }\n\n private patchClip(at: number, patch: Partial<Clip>): void {\n this.tell({ animations: this.clips.map((clip, i) => (i === at ? { ...clip, ...patch } : clip)) })\n }\n\n private patchParameter(at: number, patch: Partial<Parameter>): void {\n this.tell({ parameters: this.parameters.map((one, i) => (i === at ? { ...one, ...patch } : one)) })\n }\n\n /** 채널 묶음을 통째로 얹는다. **어느 목록이든 같은 자리에 닿는다.** */\n private setChannels(kind: MotionKind, at: number, channels: Channel[]): void {\n if (kind === 'clip') this.patchClip(at, { channels })\n else {\n const parameter = this.parameters[at]\n if (!parameter) return\n this.patchParameter(at, { clip: { ...parameter.clip, channels } })\n }\n }\n\n private patchChannel(kind: MotionKind, at: number, j: number, next: Channel): void {\n this.setChannels(\n kind,\n at,\n this.channelsOf(kind, at).map((channel, i) => (i === j ? next : channel))\n )\n }\n\n private addClip(): void {\n const target = this.partNames[0]\n if (!target) return\n\n const clip: Clip = {\n name: freeName(this.allNames, 'clip-1'),\n channels: [{ target, path: 'translation', keys: keysFor('translation') }]\n }\n\n this.open = `clip:${this.clips.length}`\n this.tell({ animations: [...this.clips, clip] })\n }\n\n private addParameter(): void {\n const target = this.partNames[0]\n if (!target) return\n\n const parameter: Parameter = {\n name: freeName(this.allNames, 'parameter-1'),\n range: { ...NEW_RANGE },\n clip: { channels: [{ target, path: 'translation', keys: keysFor('translation') }] }\n }\n\n this.open = `parameter:${this.parameters.length}`\n this.tell({ parameters: [...this.parameters, parameter] })\n }\n\n /**\n * Recipe creates a valid, editable two-pose motion; it never saves or publishes the Figure.\n *\n * 어느 목록으로 가는지는 레시피가 안다 — 도는 롤러는 애니메이션이고 열리는 문은\n * 파라미터다. 저작자에게 그 갈래를 먼저 고르게 하지 않는 이유가 이것이다.\n */\n private addRecipe(recipe: MotionRecipe): void {\n const target = this.recipeTarget || this.partNames[0]\n if (!target) return\n\n const channel: Channel = {\n target,\n path: recipe.path,\n keys: [{ at: 0, value: startValue(recipe.path) }, { at: 1, value: { ...recipe.end } }]\n }\n if (recipe.pivot) channel.pivot = { ...recipe.pivot }\n\n const name = freeName(this.allNames, recipe.name)\n\n if (recipe.kind === 'clip') {\n this.open = `clip:${this.clips.length}`\n this.tell({ animations: [...this.clips, { name, channels: [channel] }] })\n return\n }\n\n this.open = `parameter:${this.parameters.length}`\n this.tell({\n parameters: [...this.parameters, { name, range: { ...(recipe.range ?? NEW_RANGE) }, clip: { channels: [channel] } }]\n })\n }\n\n private renameClip(at: number, name: string): void {\n this.patchClip(at, { name: name.trim() })\n }\n\n private renameParameter(at: number, name: string): void {\n this.patchParameter(at, { name: name.trim() })\n }\n\n private setRange(at: number, patch: Partial<Parameter['range']>): void {\n const parameter = this.parameters[at]\n if (!parameter) return\n this.patchParameter(at, { range: { ...(parameter.range ?? NEW_RANGE), ...patch } })\n }\n\n /**\n * Transition time. Clearing the input removes the field, which means instant. 0 is instant too, but\n * a stored 0 and a missing field are different sources, so an empty input is never saved as 0.\n */\n private setDuration(at: number, raw: string): void {\n const parameter = this.parameters[at]\n if (!parameter) return\n const { duration: _dropped, ...rest } = parameter.clip\n const text = raw.trim()\n const clip = text === '' || !Number.isFinite(Number(text)) ? rest : { ...rest, duration: Number(text) }\n this.patchParameter(at, { clip })\n }\n\n private setDefault(at: number, value: number): void {\n this.patchParameter(at, { default: value })\n }\n\n private removeClip(at: number): void {\n this.open = ''\n this.tell({ animations: this.clips.filter((_, i) => i !== at) })\n }\n\n private removeParameter(at: number): void {\n this.open = ''\n this.tell({ parameters: this.parameters.filter((_, i) => i !== at) })\n }\n\n private addChannel(kind: MotionKind, at: number): void {\n const target = this.partNames[0]\n if (!target) return\n\n this.setChannels(kind, at, [\n ...this.channelsOf(kind, at),\n { target, path: 'translation', keys: keysFor('translation') }\n ])\n }\n\n private setChannel(kind: MotionKind, at: number, j: number, patch: Partial<Channel>): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel) return\n\n const next: Channel = { ...channel, ...patch }\n /* `undefined` 를 얹는 것은 지우는 뜻이다. 남겨 두면 저장 형식에 빈 칸이 실린다. */\n if ('pivot' in patch && patch.pivot === undefined) delete next.pivot\n this.patchChannel(kind, at, j, next)\n }\n\n /**\n * 경로를 바꾼다 — **값까지 함께 옮긴다.**\n *\n * 옮김·회전의 0 은 「제자리」인데 배율의 0 은 **부품을 지운다.** 경로만 바꾸고 값을 그대로\n * 두면 저작자는 부품이 사라진 것을 보고 도구가 고장 난 줄 안다. 그래서 옮김·회전 ↔ 배율을\n * 건널 때 시작값을 그 경로의 제자리로 되돌린다. 시각은 지킨다 — 저작자가 짠 박자다.\n */\n private setPath(kind: MotionKind, at: number, j: number, path: ChannelPath): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel) return\n\n const crossing = (channel.path === 'scale') !== (path === 'scale')\n const next: Channel = {\n ...channel,\n path,\n keys: crossing ? channel.keys.map(key => ({ at: key.at, value: startValue(path) })) : channel.keys\n }\n /* 회전이 아니면 회전 중심은 쓰이지 않는다. 남겨 두면 검증이 「쓰이지 않는다」고 알린다. */\n if (path !== 'rotation') delete next.pivot\n\n this.patchChannel(kind, at, j, next)\n }\n\n private removeChannel(kind: MotionKind, at: number, j: number): void {\n this.setChannels(\n kind,\n at,\n this.channelsOf(kind, at).filter((_, i) => i !== j)\n )\n }\n\n /** 마지막 키에서 1 초 뒤. 값은 그 키를 그대로 받는다 — 붙여 놓고 고치는 것이 짜기 쉽다. */\n private addKey(kind: MotionKind, at: number, j: number): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel) return\n\n const last = channel.keys[channel.keys.length - 1]\n\n /*\n A parameter curve ends at 1, so appending a key would make it unreachable. Insert it between the last two keys.\n */\n if (kind === 'parameter' && channel.keys.length >= 2) {\n const before = channel.keys[channel.keys.length - 2]!\n const middle: Keyframe = { at: (before.at + last!.at) / 2, value: { ...before.value } }\n this.patchChannel(kind, at, j, { ...channel, keys: [...channel.keys.slice(0, -1), middle, last!] })\n return\n }\n\n const key: Keyframe = { at: (last?.at ?? 0) + 1, value: { ...(last?.value ?? startValue(channel.path)) } }\n this.patchChannel(kind, at, j, { ...channel, keys: [...channel.keys, key] })\n }\n\n private setKey(kind: MotionKind, at: number, j: number, k: number, patch: Partial<Keyframe>): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel) return\n this.patchChannel(kind, at, j, {\n ...channel,\n keys: channel.keys.map((key, i) => (i === k ? { ...key, ...patch } : key))\n })\n }\n\n /** 둘 미만으로 내려가지 않는다 — 단추가 이미 죽어 있지만 밖에서 불릴 수 있다. */\n private removeKey(kind: MotionKind, at: number, j: number, k: number): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel || channel.keys.length <= 2) return\n this.patchChannel(kind, at, j, { ...channel, keys: channel.keys.filter((_, i) => i !== k) })\n }\n}\n"]}
1
+ {"version":3,"file":"figure-animations.js","sourceRoot":"","sources":["../../client/modeller/figure-animations.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { AXES, CHANNEL_PATHS, INTERPOLATIONS, LIMITS } from '@hatiolab/figure-model'\nimport type { ChannelPath, JointChannel, PartChannel, Vec3 } from '@hatiolab/figure-model'\n\nimport type { FigureDraft, PartModel } from './figure-source.js'\n\n/**\n * 이 도형이 **어떻게 움직이는가.**\n *\n * ## 왜 탭 하나가 필요한가\n *\n * 움직임은 형식에 있고 런타임까지 서 있었다 — 검증이 촘촘히 보고, 청사진이 움직이는 부품을\n * 단독 묶음으로 빼고(합치면 따로 못 움직인다), 씬이 frameClock 을 구독해 실제로 돌린다.\n * 미리보기에도 clip 마다 슬라이더가 있었다.\n *\n * **만들 자리만 없었다.** 그래서 움직이는 도형을 얻는 길이 「AI 가 후보로 내놓는 것」 하나였고,\n * 채널 하나를 고치려면 손댈 화면이 없었다.\n *\n * ## 부품이 아니라 도형의 것이다\n *\n * clip 하나가 부품 여럿을 함께 움직인다 — 문 열림은 왼짝과 오른짝이 반대로 미끄러지는 것\n * 하나다. 그래서 부품을 고르고 「이 부품의 움직임」을 적는 형태가 될 수 없고, 속성 탭이 아니라\n * 자기 탭에 산다.\n *\n * ## 트윈은 타임라인을 재생하지 않는다\n *\n * 「문 열기 clip 을 재생하라」가 아니라 **「지금 40% 열려 있다」**다. 그 둘은 다른 것이라\n * 이 탭도 둘로 나뉜다.\n *\n * 애니메이션 시각이 흐른다. 인스턴스가 주는 수는 배속이다 — 도는 롤러\n * 파라미터 값이 자세를 만든다. 인스턴스가 주는 수는 물리량이다 — 1200mm 내려온 호이스트\n *\n * 한동안 뒤엣것이 애니메이션 안에 `drive: 'hold'` 로 숨어 있었다. 형식의 판단은 맞았는데\n * **자리와 이름이 틀려서**, 「길이값을 변수로 노출하라」는 요청에 저작자도 AI 도 이것을\n * 못 찾았다 — 애니메이션 칸에 있을 거라고 생각하지 않는다(ADR-0051).\n */\n\ntype Clip = NonNullable<FigureDraft['animations']>[number]\ntype Parameter = NonNullable<FigureDraft['parameters']>[number]\ntype Channel = Clip['channels'][number]\ntype Keyframe = PartChannel['keys'][number]\ntype JointKey = JointChannel['keys'][number]\n\n/** A channel that drives a joint names the joint and carries one number per key (ADR-0066). */\nfunction isJointChannel(channel: Channel): channel is JointChannel {\n return channel.path === undefined\n}\n\n/** 어느 목록의 몇째인가. 채널·키 편집기가 둘을 함께 쓴다. */\ntype MotionKind = 'clip' | 'parameter'\n\n/** A first-success motion: intent first, raw channel editing second. */\ninterface MotionRecipe {\n id: 'conveyor' | 'gate' | 'pusher' | 'lift'\n name: string\n kind: MotionKind\n path: ChannelPath\n end: Vec3\n pivot?: Vec3\n /** 파라미터 레시피만. 인스턴스가 주고받는 물리량이다. */\n range?: Parameter['range']\n}\n\n/*\n 레시피의 `end` 가 곧 그 범위의 끝 자세다 — 리프트가 100mm 올라가는 곡선이면 범위도\n 0~100mm 다. 둘이 어긋나면 저작자가 1200 을 주고도 100 만 올라가는 것을 보게 된다.\n*/\nconst MOTION_RECIPES: readonly MotionRecipe[] = [\n { id: 'conveyor', name: 'conveyor-running', kind: 'clip', path: 'rotation', end: { x: 360, y: 0, z: 0 } },\n {\n id: 'gate',\n name: 'gate-open',\n kind: 'parameter',\n path: 'rotation',\n end: { x: 0, y: 90, z: 0 },\n pivot: { x: 0, y: 0, z: 0 },\n range: { unit: '%', min: 0, max: 100 }\n },\n {\n id: 'pusher',\n name: 'pusher-position',\n kind: 'parameter',\n path: 'translation',\n end: { x: 100, y: 0, z: 0 },\n range: { unit: 'mm', min: 0, max: 100 }\n },\n {\n id: 'lift',\n name: 'lift-height',\n kind: 'parameter',\n path: 'translation',\n end: { x: 0, y: 100, z: 0 },\n range: { unit: 'mm', min: 0, max: 100 }\n }\n]\n\n/** 새 파라미터의 범위. 단위는 저작자가 고쳐 쓴다 — 형식은 단위를 해석하지 않는다. */\nconst NEW_RANGE: Parameter['range'] = { unit: 'mm', min: 0, max: 1000 }\n\n/** 새 clip 의 키 둘. **하나로 만들지 않는다** — 키 하나는 움직임이 아니라 자세다. */\nconst NEW_KEYS: Keyframe[] = [\n { at: 0, value: { x: 0, y: 0, z: 0 } },\n { at: 1, value: { x: 0, y: 0, z: 0 } }\n]\n\n/** `scale` 만 1 에서 시작한다 — 0 은 부품을 지운다. */\nfunction startValue(path: ChannelPath): Vec3 {\n return path === 'scale' ? { x: 1, y: 1, z: 1 } : { x: 0, y: 0, z: 0 }\n}\n\nfunction keysFor(path: ChannelPath): Keyframe[] {\n return NEW_KEYS.map(key => ({ at: key.at, value: startValue(path) }))\n}\n\n/**\n * 이미 쓴 이름을 비켜 간다.\n *\n * **두 목록을 함께 본다.** 인스턴스는 이름 하나로 값을 주므로 애니메이션과 파라미터가\n * 같은 이름을 가지면 어느 쪽에 준 것인지 알 수 없고, 형식이 그것을 오류로 막는다.\n */\nfunction freeName(taken: readonly { name: string }[], stem: string): string {\n const used = new Set(taken.map(one => one.name))\n if (!used.has(stem)) return stem\n for (let n = 2; ; n++) if (!used.has(`${stem}-${n}`)) return `${stem}-${n}`\n}\n\n/**\n * 파라미터 범위에 적히는 단위 — **권하는 것이지 가두는 것이 아니다.**\n *\n * 관절이 만드는 파라미터가 `deg` 와 `mm` 을 쓰고(회전과 직선), 그리퍼·문 같은 값은 `%` 를 쓴다.\n * 속도는 `mm/s` 와 `rpm`, 시간은 `s` 다. 이 여섯이 지금 쓰이는 전부이고, 목록에 없는 말을 적는\n * 길은 그대로 열려 있다.\n */\nconst PARAMETER_UNITS = ['deg', 'mm', '%', 'mm/s', 'rpm', 's'] as const\n\n/** 목록 대신 손으로 적겠다는 표. `select` 의 값으로 쓴다. */\nconst UNIT_TYPE_IT = '__type-it'\n\n@customElement('figure-animations')\nexport class FigureAnimations extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 다른 탭들과 같은 눈금이다 */\n h3 {\n margin: 0 0 2px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n div[recipes] { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }\n button[recipe] { min-height: 54px; align-items: flex-start; flex-direction: column; text-align: left; }\n button[recipe] strong { font-size: 0.74rem; }\n button[recipe] span { color: var(--md-sys-color-on-surface-variant); font-size: 0.68rem; line-height: 1.35; }\n\n /* ── clip ─────────────────────────────────────────────────────── */\n\n div[clip] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n div[clip][on] {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container-low);\n }\n\n div[head] {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n div[head] input[name] {\n flex: 1;\n min-width: 0;\n }\n\n /* ── channel ──────────────────────────────────────────────────── */\n\n div[channel] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 6px 8px;\n border-left: 2px solid var(--md-sys-color-outline-variant);\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[channel][orphan] {\n border-left-color: var(--md-sys-color-error);\n }\n\n div[row] {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n div[row] > label {\n flex: none;\n min-width: 3.2em;\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n 칸이 줄어들 줄 알아야 한다. 숫자 입력칸은 제 기본 너비(20자 남짓)를 갖고 있고 flex 는\n 기본 크기 아래로는 안 줄이므로, 범위처럼 한 줄에 셋이 서는 줄은 패널 밖으로 밀려\n 오른쪽 칸이 잘렸다.\n */\n div[row] > input {\n flex: 1 1 0;\n min-width: 0;\n }\n /* 단위는 「mm」 정도의 짧은 말이라 숫자 칸보다 좁게 둔다. 고르는 칸도 같은 폭이다. */\n div[row] > input[unit],\n div[row] > select[unit] {\n flex: 0 1 5.5em;\n min-width: 0;\n }\n\n /* ── keyframe ─────────────────────────────────────────────────── */\n\n table[keys] {\n width: 100%;\n border-collapse: collapse;\n }\n table[keys] th {\n padding: 0 2px 2px;\n font-size: 0.68rem;\n font-weight: 600;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n }\n table[keys] td {\n padding: 1px 2px;\n }\n table[keys] input {\n width: 100%;\n min-width: 0;\n }\n table[keys] td[drop] {\n width: 1.6em;\n }\n\n input,\n select {\n box-sizing: border-box;\n padding: 3px 5px;\n font: inherit;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 3px;\n }\n input:focus,\n select:focus {\n outline: none;\n border-color: var(--md-sys-color-primary);\n }\n input[bad] {\n border-color: var(--md-sys-color-error);\n }\n\n button {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 3px 8px;\n font: inherit;\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 3px;\n cursor: pointer;\n }\n button:hover:not(:disabled) {\n background-color: var(--md-sys-color-surface-container-high);\n }\n button:disabled {\n opacity: 0.4;\n cursor: default;\n }\n button[plain] {\n padding: 1px 4px;\n background: none;\n border: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[plain]:hover:not(:disabled) {\n color: var(--md-sys-color-error);\n background: none;\n }\n button md-icon {\n font-size: 14px;\n width: 14px;\n height: 14px;\n }\n\n /* ── 예산 ─────────────────────────────────────────────────────── */\n\n div[budget] {\n display: flex;\n align-items: baseline;\n gap: 6px;\n }\n div[budget] strong {\n font-size: 0.86rem;\n font-weight: 700;\n }\n div[budget][over] strong {\n color: var(--md-sys-color-error);\n }\n\n p[warn] {\n margin: 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n `\n ]\n\n @property({ type: Object }) draft?: FigureDraft\n @property({ type: Array }) parts: PartModel[] = []\n\n /**\n * 펼쳐 둔 것. 이름이 아니라 **목록과 자리**로 잡는다 — 이름은 편집 중에 바뀐다.\n *\n * 목록이 둘이므로 자리 하나로는 모자란다. `clip:0` 과 `parameter:0` 은 다른 것이다.\n */\n @state() private open = ''\n @state() private recipeTarget = ''\n\n /** 단위를 손으로 적는 중인 파라미터. 목록에 없는 말을 적으려고 연 칸이다. */\n @state() private typingUnit = new Set<number>()\n\n private get clips(): Clip[] {\n return this.draft?.animations ?? []\n }\n\n private get parameters(): Parameter[] {\n return this.draft?.parameters ?? []\n }\n\n /** 두 목록의 이름을 한자리에서 본다 — 인스턴스가 이름 하나로 값을 준다. */\n private get allNames(): { name: string }[] {\n return [...this.clips, ...this.parameters]\n }\n\n private channelsOf(kind: MotionKind, at: number): Channel[] {\n return (kind === 'clip' ? this.clips[at]?.channels : this.parameters[at]?.clip?.channels) ?? []\n }\n\n /** 부품 이름 — 채널이 이것을 가리킨다. */\n private get partNames(): string[] {\n return this.parts.map(part => part.name).filter(name => !!name)\n }\n\n /** Joints a channel can drive instead of a part (ADR-0066). */\n private get joints(): NonNullable<FigureDraft['joints']> {\n return this.draft?.joints ?? []\n }\n\n /**\n * 몇 부품이 움직이나 — 채널이 가리키는 **서로 다른 부품**의 수.\n *\n * clip 수가 아니다. 한 부품을 세 clip 이 움직여도 그 부품 하나가 단독 묶음이 될 뿐이다.\n * 청사진이 묶음을 가르는 기준과 같은 셈이어야 화면의 수와 판정의 수가 맞는다.\n */\n private get movingParts(): number {\n const moving = new Set<string>()\n for (const clip of this.clips) for (const channel of clip.channels) moving.add(channel.target)\n for (const parameter of this.parameters) {\n for (const channel of parameter.clip?.channels ?? []) moving.add(channel.target)\n }\n return moving.size\n }\n\n render(): TemplateResult {\n if (!this.draft) return html``\n\n const nameless = this.partNames.length === 0\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.animation')}</h3>\n </section>\n\n ${this.renderRecipes()}\n\n <section>\n <h3>${i18next.t('figure.label.parameters')}</h3>\n <p quiet>${i18next.t('figure.text.parameter-is-a-value-not-a-timeline')}</p>\n ${this.parameters.map((parameter, at) => this.renderParameter(parameter, at))}\n <button @click=${() => this.addParameter()} ?disabled=${nameless}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-parameter')}\n </button>\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.clips')}</h3>\n <p quiet>${i18next.t('figure.text.clip-is-time-running')}</p>\n ${this.clips.map((clip, at) => this.renderClip(clip, at))}\n <button @click=${() => this.addClip()} ?disabled=${nameless}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-clip')}\n </button>\n ${nameless ? html`<p quiet>${i18next.t('figure.text.animation-needs-a-named-part')}</p>` : nothing}\n </section>\n\n ${this.movingParts === 0 ? nothing : this.renderBudget()}\n `\n }\n\n /**\n * 빈 화면 — **이 탭이 무엇인지 가르치는 자리다.**\n *\n * 「clip 을 추가하세요」라고만 쓰면 처음 여는 사람은 clip 이 무엇인지 모른 채 단추를 누른다.\n * 그래서 무엇을 만들 수 있는지 예를 들고, 그것이 어떻게 움직이는지(사실이 들어온다)를 적는다.\n */\n private renderRecipes(): TemplateResult {\n return html`\n <section>\n <h3>${i18next.t('figure.label.motion-intent')}</h3>\n <p quiet>${i18next.t('figure.text.motion-intent-help')}</p>\n ${this.partNames.length\n ? html`<select .value=${this.recipeTarget} @change=${(e: Event) => (this.recipeTarget = (e.target as HTMLSelectElement).value)}>\n ${this.partNames.map(name => html`<option value=${name}>${name}</option>`)}\n </select>\n <div recipes>\n ${MOTION_RECIPES.map(recipe => html`\n <button recipe @click=${() => this.addRecipe(recipe)}>\n <strong>${i18next.t('figure.label.recipe-' + recipe.id)}</strong>\n <span>${i18next.t('figure.text.recipe-' + recipe.id)}</span>\n </button>\n `)}\n </div>`\n : html`<p quiet>${i18next.t('figure.text.animation-needs-a-named-part')}</p>`}\n </section>\n `\n }\n\n /** 이름이 다른 것과 겹치나. 두 목록을 함께 본다 — 인스턴스는 이름 하나로 값을 준다. */\n private taken(kind: MotionKind, at: number, name: string): boolean {\n const mine = kind === 'clip' ? this.clips[at] : this.parameters[at]\n return this.allNames.some(one => one !== mine && one.name === name)\n }\n\n private renderClip(clip: Clip, at: number): TemplateResult {\n const key = `clip:${at}`\n const on = this.open === key\n const duplicate = this.taken('clip', at, clip.name)\n const nameless = !clip.name?.trim()\n\n return html`\n <div clip ?on=${on}>\n <div head>\n <button plain @click=${() => (this.open = on ? '' : key)} title=${i18next.t('figure.label.channels')}>\n <md-icon>${on ? 'expand_more' : 'chevron_right'}</md-icon>\n </button>\n <input\n name\n .value=${live(clip.name ?? '')}\n ?bad=${duplicate || nameless}\n placeholder=${i18next.t('figure.label.clip-name')}\n title=${duplicate ? i18next.t('figure.text.clip-name-taken') : i18next.t('figure.text.clip-name-is-how-it-is-driven')}\n @change=${(e: Event) => this.renameClip(at, (e.target as HTMLInputElement).value)}\n />\n <button plain @click=${() => this.removeClip(at)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n\n ${duplicate ? html`<p warn>${i18next.t('figure.text.clip-name-taken')}</p>` : nothing}\n ${nameless ? html`<p warn>${i18next.t('figure.text.clip-needs-a-name')}</p>` : nothing}\n ${on ? this.renderChannels('clip', at) : nothing}\n </div>\n `\n }\n\n /**\n * 파라미터 하나 — **이름·범위·자세.**\n *\n * 범위가 화면에 있어야 하는 이유는 그것이 **인스턴스와 주고받는 말**이기 때문이다.\n * 곡선은 0~1 이고 인스턴스는 mm 로 준다. 그 사이를 런타임이 맵하는데, 무엇부터 무엇\n * 까지인지 저작자가 정하지 않으면 값을 줄 수가 없다.\n */\n private renderParameter(parameter: Parameter, at: number): TemplateResult {\n const key = `parameter:${at}`\n const on = this.open === key\n const duplicate = this.taken('parameter', at, parameter.name)\n const nameless = !parameter.name?.trim()\n const range = parameter.range ?? NEW_RANGE\n const backwards = !(range.max > range.min)\n const slowness = parameter.clip?.duration ?? 0\n\n return html`\n <div clip ?on=${on}>\n <div head>\n <button plain @click=${() => (this.open = on ? '' : key)} title=${i18next.t('figure.label.channels')}>\n <md-icon>${on ? 'expand_more' : 'chevron_right'}</md-icon>\n </button>\n <input\n name\n .value=${live(parameter.name ?? '')}\n ?bad=${duplicate || nameless}\n placeholder=${i18next.t('figure.label.parameter-name')}\n title=${duplicate\n ? i18next.t('figure.text.clip-name-taken')\n : i18next.t('figure.text.parameter-name-is-how-a-value-arrives')}\n @change=${(e: Event) => this.renameParameter(at, (e.target as HTMLInputElement).value)}\n />\n <button plain @click=${() => this.removeParameter(at)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n\n ${duplicate ? html`<p warn>${i18next.t('figure.text.clip-name-taken')}</p>` : nothing}\n ${nameless ? html`<p warn>${i18next.t('figure.text.parameter-needs-a-name')}</p>` : nothing}\n\n <div row>\n <label>${i18next.t('figure.label.parameter-range')}</label>\n <input\n type=\"number\"\n step=\"1\"\n ?bad=${backwards}\n .value=${live(String(range.min))}\n aria-label=\"min\"\n @change=${(e: Event) => this.setRange(at, { min: Number((e.target as HTMLInputElement).value) || 0 })}\n />\n <input\n type=\"number\"\n step=\"1\"\n ?bad=${backwards}\n .value=${live(String(range.max))}\n aria-label=\"max\"\n @change=${(e: Event) => this.setRange(at, { max: Number((e.target as HTMLInputElement).value) || 0 })}\n />\n ${this.renderUnit(at, range.unit ?? '')}\n </div>\n ${backwards ? html`<p warn>${i18next.t('figure.text.range-needs-room')}</p>` : nothing}\n\n <div row>\n <label>${i18next.t('figure.label.parameter-default')}</label>\n <input\n type=\"number\"\n step=\"1\"\n .value=${live(String(parameter.default ?? range.min))}\n @change=${(e: Event) => this.setDefault(at, Number((e.target as HTMLInputElement).value) || 0)}\n />\n </div>\n\n <div row>\n <label>${i18next.t('figure.label.parameter-duration')}</label>\n <input\n type=\"number\"\n step=\"0.1\"\n min=\"0\"\n ?bad=${slowness < 0}\n .value=${live(parameter.clip?.duration === undefined ? '' : String(parameter.clip.duration))}\n placeholder=\"0\"\n aria-label=\"duration\"\n @change=${(e: Event) => this.setDuration(at, (e.target as HTMLInputElement).value)}\n />\n </div>\n <p quiet>${i18next.t('figure.text.parameter-duration-empty-is-instant')}</p>\n\n ${on ? this.renderChannels('parameter', at) : nothing}\n </div>\n `\n }\n\n private renderChannels(kind: MotionKind, at: number): TemplateResult {\n const channels = this.channelsOf(kind, at)\n\n return html`\n <p quiet>${i18next.t(kind === 'clip' ? 'figure.text.clip-is-time-running' : 'figure.text.parameter-curve-is-the-value-axis')}</p>\n\n ${channels.length === 0 ? html`<p warn>${i18next.t('figure.text.clip-needs-a-channel')}</p>` : nothing}\n ${channels.map((channel, j) => this.renderChannel(kind, channel, at, j))}\n\n <button @click=${() => this.addChannel(kind, at)}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-channel')}\n </button>\n `\n }\n\n /**\n * 채널 하나 — 어느 부품의 어느 속성을 움직이나.\n *\n * 없는 부품을 가리키는 채널은 붉게 낸다. 부품 이름을 바꾸면 그 순간 이렇게 되는데,\n * 검증은 저장할 때 말하고 여기서는 **그 자리에서** 보여 준다.\n */\n private renderChannel(kind: MotionKind, channel: Channel, at: number, j: number): TemplateResult {\n const joint = this.joints.find(one => one.name === channel.target)\n const orphan = !this.partNames.includes(channel.target) && !joint\n const target = html`\n <div row>\n <label>${i18next.t('figure.label.channel-target')}</label>\n <select\n .value=${live(channel.target ?? '')}\n @change=${(e: Event) => this.retarget(kind, at, j, (e.target as HTMLSelectElement).value)}\n >\n ${orphan ? html`<option value=${channel.target}>${channel.target}</option>` : nothing}\n <optgroup label=${i18next.t('figure.label.channel-target-parts')}>\n ${this.partNames.map(name => html`<option value=${name}>${name}</option>`)}\n </optgroup>\n ${this.joints.length > 0\n ? html`<optgroup label=${i18next.t('figure.label.channel-target-joints')}>\n ${this.joints.map(one => html`<option value=${one.name}>${one.name}</option>`)}\n </optgroup>`\n : nothing}\n </select>\n <button plain @click=${() => this.removeChannel(kind, at, j)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n `\n\n /* A joint channel has no path or pivot: the joint says what moves. Each key is the joint coordinate. */\n if (isJointChannel(channel)) {\n const unit = joint?.type === 'prismatic' ? 'mm' : 'deg'\n return html`\n <div channel ?orphan=${orphan}>\n ${target} ${orphan ? html`<p warn>${i18next.t('figure.text.channel-target-is-gone')}</p>` : nothing}\n <p quiet>${i18next.t('figure.text.joint-channel-value-' + unit)}</p>\n ${this.renderJointKeys(kind, channel, at, j, joint?.limits)}\n </div>\n `\n }\n\n const turning = channel.path === 'rotation'\n\n return html`\n <div channel ?orphan=${orphan}>\n ${target}\n\n ${orphan ? html`<p warn>${i18next.t('figure.text.channel-target-is-gone')}</p>` : nothing}\n\n <div row>\n <label>${i18next.t('figure.label.channel-path')}</label>\n <select\n .value=${live(channel.path)}\n @change=${(e: Event) => this.setPath(kind, at, j, (e.target as HTMLSelectElement).value as ChannelPath)}\n >\n ${CHANNEL_PATHS.map(path => html`<option value=${path}>${i18next.t('figure.label.path-' + path)}</option>`)}\n </select>\n <select\n .value=${live(channel.interpolation ?? 'linear')}\n title=${i18next.t('figure.text.interpolation-explained')}\n @change=${(e: Event) =>\n this.setChannel(kind, at, j, { interpolation: (e.target as HTMLSelectElement).value as Channel['interpolation'] })}\n >\n ${INTERPOLATIONS.map(\n one => html`<option value=${one}>${i18next.t('figure.label.interpolation-' + one)}</option>`\n )}\n </select>\n </div>\n\n <p quiet>${i18next.t('figure.text.path-' + channel.path + '-unit')}</p>\n\n ${turning ? this.renderPivot(kind, channel, at, j) : nothing}\n ${this.renderKeys(kind, channel, at, j)}\n </div>\n `\n }\n\n /**\n * 회전 중심 — **`rotation` 에서만 뜻이 있다.**\n *\n * 그래서 경로가 rotation 일 때만 낸다. 언제나 내면 저작자가 옮김·배율에도 적고, 검증이\n * 저장할 때 「쓰이지 않는다」고 알린다. 안 보이면 적을 일이 없다.\n */\n private renderPivot(kind: MotionKind, channel: PartChannel, at: number, j: number): TemplateResult {\n const pivot = channel.pivot\n const on = pivot !== undefined\n\n return html`\n <div row>\n <label>${i18next.t('figure.label.channel-pivot')}</label>\n <button\n @click=${() => this.setChannel(kind, at, j, { pivot: on ? undefined : { x: 0, y: 0, z: 0 } })}\n title=${i18next.t('figure.text.pivot-defaults-to-the-part-centre')}\n >\n ${i18next.t(on ? 'figure.button.pivot-clear' : 'figure.button.pivot-set')}\n </button>\n ${on\n ? AXES.map(\n axis => html`\n <input\n type=\"number\"\n step=\"1\"\n .value=${live(String(pivot![axis]))}\n aria-label=${axis}\n @change=${(e: Event) =>\n this.setChannel(kind, at, j, {\n pivot: { ...pivot!, [axis]: Number((e.target as HTMLInputElement).value) || 0 }\n })}\n />\n `\n )\n : nothing}\n </div>\n `\n }\n\n /**\n * 키프레임 표.\n *\n * **둘 미만으로 못 내려간다.** 하나뿐이면 움직이는 것이 아니라 자세 하나이고, 형식이\n * 오류로 막는다. 저장하고 나서 알려 주는 것보다 지우는 단추를 죽이는 것이 낫다.\n *\n * 시각이 오름차순이어야 하는 것도 형식의 규칙이다. 여기서는 어긋난 줄을 붉게 내고 값을\n * 고치지는 않는다 — 저작자가 3 을 1 로 고치던 중일 수 있다.\n */\n /**\n * Keys of a joint channel: a position and one joint coordinate each. A value outside the joint's limits\n * is marked and left as typed; the format refuses it on save.\n */\n private renderJointKeys(\n kind: MotionKind,\n channel: JointChannel,\n at: number,\n j: number,\n limits?: { min: number; max: number }\n ): TemplateResult {\n const keys = channel.keys ?? []\n const onValues = kind === 'parameter'\n const beyond = (key: JointKey) => !!limits && (key.value < limits.min || key.value > limits.max)\n\n return html`\n <table keys>\n <tr>\n <th>${i18next.t(onValues ? 'figure.label.key-at-value' : 'figure.label.key-at')}</th>\n <th>${i18next.t('figure.label.joint-value')}</th>\n <th></th>\n </tr>\n ${keys.map((key, k) => {\n const backwards = k > 0 && key.at <= (keys[k - 1]!.at ?? 0)\n return html`\n <tr>\n <td>\n <input\n type=\"number\"\n step=${onValues ? '0.05' : '0.1'}\n min=\"0\"\n max=${onValues ? '1' : nothing}\n ?bad=${backwards || key.at < 0 || (onValues && key.at > 1)}\n .value=${live(String(key.at))}\n @change=${(e: Event) =>\n this.setJointKey(kind, at, j, k, { at: Math.max(0, Number((e.target as HTMLInputElement).value) || 0) })}\n />\n </td>\n <td>\n <input\n type=\"number\"\n step=\"1\"\n ?bad=${beyond(key)}\n .value=${live(String(key.value))}\n title=${beyond(key) ? i18next.t('figure.text.joint-value-beyond-limits') : ''}\n @change=${(e: Event) => this.setJointKey(kind, at, j, k, { value: Number((e.target as HTMLInputElement).value) || 0 })}\n />\n </td>\n <td drop>\n <button\n plain\n ?disabled=${keys.length <= 2}\n title=${keys.length <= 2 ? i18next.t('figure.text.two-keys-at-least') : i18next.t('figure.button.remove')}\n @click=${() => this.removeKey(kind, at, j, k)}\n >\n <md-icon>close</md-icon>\n </button>\n </td>\n </tr>\n `\n })}\n </table>\n ${keys.some(beyond) ? html`<p warn>${i18next.t('figure.text.joint-value-beyond-limits')}</p>` : nothing}\n <button @click=${() => this.addKey(kind, at, j)}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-key')}\n </button>\n `\n }\n\n private renderKeys(kind: MotionKind, channel: PartChannel, at: number, j: number): TemplateResult {\n const keys = channel.keys ?? []\n /*\n Keys on a parameter curve sit on the 0..1 value span (ADR-0051). A key above 1 is unreachable and\n the format refuses it. The screen marks it and leaves the number alone: rewriting what the author\n typed would hide what they wrote.\n */\n const onValues = kind === 'parameter'\n const outside = (key: Keyframe) => onValues && key.at > 1\n\n return html`\n <table keys>\n <tr>\n <th>${i18next.t(onValues ? 'figure.label.key-at-value' : 'figure.label.key-at')}</th>\n ${AXES.map(axis => html`<th>${axis}</th>`)}\n <th></th>\n </tr>\n ${keys.map((key, k) => {\n const backwards = k > 0 && key.at <= (keys[k - 1]!.at ?? 0)\n return html`\n <tr>\n <td>\n <input\n type=\"number\"\n step=${onValues ? '0.05' : '0.1'}\n min=\"0\"\n max=${onValues ? '1' : nothing}\n ?bad=${backwards || key.at < 0 || outside(key)}\n .value=${live(String(key.at))}\n title=${backwards\n ? i18next.t('figure.text.keys-must-rise')\n : outside(key)\n ? i18next.t('figure.text.parameter-key-within-span')\n : ''}\n @change=${(e: Event) =>\n this.setKey(kind, at, j, k, { at: Math.max(0, Number((e.target as HTMLInputElement).value) || 0) })}\n />\n </td>\n ${AXES.map(\n axis => html`\n <td>\n <input\n type=\"number\"\n step=${channel.path === 'scale' ? '0.1' : '1'}\n .value=${live(String(key.value[axis]))}\n aria-label=${axis}\n @change=${(e: Event) =>\n this.setKey(kind, at, j, k, {\n value: { ...key.value, [axis]: Number((e.target as HTMLInputElement).value) || 0 }\n })}\n />\n </td>\n `\n )}\n <td drop>\n <button\n plain\n ?disabled=${keys.length <= 2}\n title=${keys.length <= 2 ? i18next.t('figure.text.two-keys-at-least') : i18next.t('figure.button.remove')}\n @click=${() => this.removeKey(kind, at, j, k)}\n >\n <md-icon>close</md-icon>\n </button>\n </td>\n </tr>\n `\n })}\n </table>\n\n ${keys.some((key, k) => k > 0 && key.at <= (keys[k - 1]!.at ?? 0))\n ? html`<p warn>${i18next.t('figure.text.keys-must-rise')}</p>`\n : nothing}\n ${keys.some(outside) ? html`<p warn>${i18next.t('figure.text.parameter-key-within-span')}</p>` : nothing}\n\n <button @click=${() => this.addKey(kind, at, j)}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-key')}\n </button>\n `\n }\n\n /**\n * 움직이는 부품 예산.\n *\n * **넘어도 막지 않는다.** 움직이는 부품은 저마다 단독 묶음이라 draw call 이 늘고 프레임마다\n * 갱신이 는다 — 그 대가가 수로 보이면 저작자가 아낀다. 랜드마크 설비 하나는 정교해도 되고,\n * 그 값이 도면 예산에 드러나면 된다.\n */\n private renderBudget(): TemplateResult {\n const used = this.movingParts\n const over = used > LIMITS.animatedParts\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.budget-animations')}</h3>\n <div budget ?over=${over}>\n <strong>${used}</strong>\n <span quiet>/ ${LIMITS.animatedParts}</span>\n </div>\n <p quiet>${i18next.t('figure.text.animated-parts-cost')}</p>\n ${over ? html`<p warn>${i18next.t('figure.text.animated-parts-over')}</p>` : nothing}\n </section>\n `\n }\n\n // ─── 고치기 ───────────────────────────────────────────────────────────────\n\n /**\n * 초안에 실어 올려보낸다.\n *\n * `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로\n * 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.\n */\n private tell(over: { animations?: Clip[]; parameters?: Parameter[] }): void {\n if (!this.draft) return\n\n const draft: FigureDraft = { ...this.draft, ...over }\n if (!draft.animations?.length) delete draft.animations\n if (!draft.parameters?.length) delete draft.parameters\n\n this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }))\n }\n\n private patchClip(at: number, patch: Partial<Clip>): void {\n this.tell({ animations: this.clips.map((clip, i) => (i === at ? { ...clip, ...patch } : clip)) })\n }\n\n private patchParameter(at: number, patch: Partial<Parameter>): void {\n this.tell({ parameters: this.parameters.map((one, i) => (i === at ? { ...one, ...patch } : one)) })\n }\n\n /** 채널 묶음을 통째로 얹는다. **어느 목록이든 같은 자리에 닿는다.** */\n private setChannels(kind: MotionKind, at: number, channels: Channel[]): void {\n if (kind === 'clip') this.patchClip(at, { channels })\n else {\n const parameter = this.parameters[at]\n if (!parameter) return\n this.patchParameter(at, { clip: { ...parameter.clip, channels } })\n }\n }\n\n private patchChannel(kind: MotionKind, at: number, j: number, next: Channel): void {\n this.setChannels(\n kind,\n at,\n this.channelsOf(kind, at).map((channel, i) => (i === j ? next : channel))\n )\n }\n\n private addClip(): void {\n const target = this.partNames[0]\n if (!target) return\n\n const clip: Clip = {\n name: freeName(this.allNames, 'clip-1'),\n channels: [{ target, path: 'translation', keys: keysFor('translation') }]\n }\n\n this.open = `clip:${this.clips.length}`\n this.tell({ animations: [...this.clips, clip] })\n }\n\n private addParameter(): void {\n const target = this.partNames[0]\n if (!target) return\n\n const parameter: Parameter = {\n name: freeName(this.allNames, 'parameter-1'),\n range: { ...NEW_RANGE },\n clip: { channels: [{ target, path: 'translation', keys: keysFor('translation') }] }\n }\n\n this.open = `parameter:${this.parameters.length}`\n this.tell({ parameters: [...this.parameters, parameter] })\n }\n\n /**\n * Recipe creates a valid, editable two-pose motion; it never saves or publishes the Figure.\n *\n * 어느 목록으로 가는지는 레시피가 안다 — 도는 롤러는 애니메이션이고 열리는 문은\n * 파라미터다. 저작자에게 그 갈래를 먼저 고르게 하지 않는 이유가 이것이다.\n */\n private addRecipe(recipe: MotionRecipe): void {\n const target = this.recipeTarget || this.partNames[0]\n if (!target) return\n\n const channel: Channel = {\n target,\n path: recipe.path,\n keys: [{ at: 0, value: startValue(recipe.path) }, { at: 1, value: { ...recipe.end } }]\n }\n if (recipe.pivot) channel.pivot = { ...recipe.pivot }\n\n const name = freeName(this.allNames, recipe.name)\n\n if (recipe.kind === 'clip') {\n this.open = `clip:${this.clips.length}`\n this.tell({ animations: [...this.clips, { name, channels: [channel] }] })\n return\n }\n\n this.open = `parameter:${this.parameters.length}`\n this.tell({\n parameters: [...this.parameters, { name, range: { ...(recipe.range ?? NEW_RANGE) }, clip: { channels: [channel] } }]\n })\n }\n\n private renameClip(at: number, name: string): void {\n this.patchClip(at, { name: name.trim() })\n }\n\n private renameParameter(at: number, name: string): void {\n this.patchParameter(at, { name: name.trim() })\n }\n\n /**\n * 단위 칸 — **목록이 보이는 채로, 적는 길도 열어 둔다.**\n *\n * 처음에는 `datalist` 를 붙였다. 그것은 브라우저가 쥐고 있는 기능이라 **눌러 보기 전에는 목록이\n * 있는지조차 안 보인다** — 화살표도 없고, 어디에 어떻게 뜨는지도 브라우저가 정한다. 고를 수\n * 있다는 것을 알리는 것이 이 칸의 목적이므로 `select` 로 바꿨다.\n *\n * 형식은 단위를 규칙으로 쓰지 않으므로(표시용 말이다) 목록에 가두지 않는다. 마지막 줄이\n * 「직접 적기」이고, 목록에 없는 말이 이미 적혀 있으면 처음부터 적는 칸으로 연다.\n */\n private renderUnit(at: number, unit: string) {\n const listed = (PARAMETER_UNITS as readonly string[]).includes(unit)\n const typing = this.typingUnit.has(at) || (unit !== '' && !listed)\n\n if (typing) {\n return html`<input\n unit\n .value=${live(unit)}\n placeholder=\"mm\"\n aria-label=\"unit\"\n title=${i18next.t('figure.text.unit-is-a-word-not-a-rule')}\n @change=${(e: Event) => this.setRange(at, { unit: (e.target as HTMLInputElement).value.trim() })}\n />`\n }\n\n return html`<select\n unit\n aria-label=\"unit\"\n title=${i18next.t('figure.text.unit-is-a-word-not-a-rule')}\n @change=${(e: Event) => this.chooseUnit(at, (e.target as HTMLSelectElement).value)}\n >\n <option value=\"\" ?selected=${unit === ''}>${i18next.t('figure.label.unit-none')}</option>\n ${PARAMETER_UNITS.map(one => html`<option value=${one} ?selected=${one === unit}>${one}</option>`)}\n <option value=${UNIT_TYPE_IT}>${i18next.t('figure.label.unit-type-it')}</option>\n </select>`\n }\n\n /** 고른 것을 넣는다. 「직접 적기」는 값이 아니라 **칸을 바꾸는 것**이다. */\n private chooseUnit(at: number, value: string): void {\n if (value === UNIT_TYPE_IT) {\n this.typingUnit = new Set([...this.typingUnit, at])\n return\n }\n\n const next = new Set(this.typingUnit)\n next.delete(at)\n this.typingUnit = next\n this.setRange(at, { unit: value })\n }\n\n private setRange(at: number, patch: Partial<Parameter['range']>): void {\n const parameter = this.parameters[at]\n if (!parameter) return\n this.patchParameter(at, { range: { ...(parameter.range ?? NEW_RANGE), ...patch } })\n }\n\n /**\n * Transition time. Clearing the input removes the field, which means instant. 0 is instant too, but\n * a stored 0 and a missing field are different sources, so an empty input is never saved as 0.\n */\n private setDuration(at: number, raw: string): void {\n const parameter = this.parameters[at]\n if (!parameter) return\n const { duration: _dropped, ...rest } = parameter.clip\n const text = raw.trim()\n const clip = text === '' || !Number.isFinite(Number(text)) ? rest : { ...rest, duration: Number(text) }\n this.patchParameter(at, { clip })\n }\n\n private setDefault(at: number, value: number): void {\n this.patchParameter(at, { default: value })\n }\n\n private removeClip(at: number): void {\n this.open = ''\n this.tell({ animations: this.clips.filter((_, i) => i !== at) })\n }\n\n private removeParameter(at: number): void {\n this.open = ''\n this.tell({ parameters: this.parameters.filter((_, i) => i !== at) })\n }\n\n private addChannel(kind: MotionKind, at: number): void {\n const target = this.partNames[0]\n if (!target) return\n\n this.setChannels(kind, at, [\n ...this.channelsOf(kind, at),\n { target, path: 'translation', keys: keysFor('translation') }\n ])\n }\n\n /**\n * Points a channel at another part or joint. Crossing between the two changes the channel's shape: a\n * joint channel has no path and one number per key, a part channel a path and a vector. The key positions\n * are kept, since they are the author's timing; values restart at the rest pose.\n */\n private retarget(kind: MotionKind, at: number, j: number, target: string): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel) return\n const toJoint = this.joints.some(one => one.name === target)\n\n if (toJoint && !isJointChannel(channel)) {\n const next: JointChannel = { target, keys: channel.keys.map(key => ({ at: key.at, value: 0 })) }\n if (channel.interpolation !== undefined) next.interpolation = channel.interpolation\n this.patchChannel(kind, at, j, next)\n return\n }\n if (!toJoint && isJointChannel(channel)) {\n const next: PartChannel = { target, path: 'translation', keys: channel.keys.map(key => ({ at: key.at, value: startValue('translation') })) }\n if (channel.interpolation !== undefined) next.interpolation = channel.interpolation\n this.patchChannel(kind, at, j, next)\n return\n }\n this.patchChannel(kind, at, j, { ...channel, target } as Channel)\n }\n\n private setChannel(kind: MotionKind, at: number, j: number, patch: Partial<PartChannel>): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel || isJointChannel(channel)) return\n\n const next: PartChannel = { ...channel, ...patch }\n /* `undefined` 를 얹는 것은 지우는 뜻이다. 남겨 두면 저장 형식에 빈 칸이 실린다. */\n if ('pivot' in patch && patch.pivot === undefined) delete next.pivot\n this.patchChannel(kind, at, j, next)\n }\n\n /**\n * 경로를 바꾼다 — **값까지 함께 옮긴다.**\n *\n * 옮김·회전의 0 은 「제자리」인데 배율의 0 은 **부품을 지운다.** 경로만 바꾸고 값을 그대로\n * 두면 저작자는 부품이 사라진 것을 보고 도구가 고장 난 줄 안다. 그래서 옮김·회전 ↔ 배율을\n * 건널 때 시작값을 그 경로의 제자리로 되돌린다. 시각은 지킨다 — 저작자가 짠 박자다.\n */\n private setPath(kind: MotionKind, at: number, j: number, path: ChannelPath): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel || isJointChannel(channel)) return\n\n const crossing = (channel.path === 'scale') !== (path === 'scale')\n const next: PartChannel = {\n ...channel,\n path,\n keys: crossing ? channel.keys.map(key => ({ at: key.at, value: startValue(path) })) : channel.keys\n }\n /* 회전이 아니면 회전 중심은 쓰이지 않는다. 남겨 두면 검증이 「쓰이지 않는다」고 알린다. */\n if (path !== 'rotation') delete next.pivot\n\n this.patchChannel(kind, at, j, next)\n }\n\n private removeChannel(kind: MotionKind, at: number, j: number): void {\n this.setChannels(\n kind,\n at,\n this.channelsOf(kind, at).filter((_, i) => i !== j)\n )\n }\n\n /** 마지막 키에서 1 초 뒤. 값은 그 키를 그대로 받는다 — 붙여 놓고 고치는 것이 짜기 쉽다. */\n private addKey(kind: MotionKind, at: number, j: number): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel) return\n\n if (isJointChannel(channel)) {\n const keys = channel.keys\n const end = keys[keys.length - 1]\n if (kind === 'parameter' && keys.length >= 2) {\n const before = keys[keys.length - 2]!\n const middle: JointKey = { at: (before.at + end!.at) / 2, value: before.value }\n this.patchChannel(kind, at, j, { ...channel, keys: [...keys.slice(0, -1), middle, end!] })\n return\n }\n this.patchChannel(kind, at, j, { ...channel, keys: [...keys, { at: (end?.at ?? 0) + 1, value: end?.value ?? 0 }] })\n return\n }\n\n const last = channel.keys[channel.keys.length - 1]\n\n /*\n A parameter curve ends at 1, so appending a key would make it unreachable. Insert it between the last two keys.\n */\n if (kind === 'parameter' && channel.keys.length >= 2) {\n const before = channel.keys[channel.keys.length - 2]!\n const middle: Keyframe = { at: (before.at + last!.at) / 2, value: { ...before.value } }\n this.patchChannel(kind, at, j, { ...channel, keys: [...channel.keys.slice(0, -1), middle, last!] })\n return\n }\n\n const key: Keyframe = { at: (last?.at ?? 0) + 1, value: { ...(last?.value ?? startValue(channel.path)) } }\n this.patchChannel(kind, at, j, { ...channel, keys: [...channel.keys, key] })\n }\n\n private setJointKey(kind: MotionKind, at: number, j: number, k: number, patch: Partial<JointKey>): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel || !isJointChannel(channel)) return\n this.patchChannel(kind, at, j, { ...channel, keys: channel.keys.map((key, i) => (i === k ? { ...key, ...patch } : key)) })\n }\n\n private setKey(kind: MotionKind, at: number, j: number, k: number, patch: Partial<Keyframe>): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel || isJointChannel(channel)) return\n this.patchChannel(kind, at, j, {\n ...channel,\n keys: channel.keys.map((key, i) => (i === k ? { ...key, ...patch } : key))\n })\n }\n\n /** 둘 미만으로 내려가지 않는다 — 단추가 이미 죽어 있지만 밖에서 불릴 수 있다. */\n private removeKey(kind: MotionKind, at: number, j: number, k: number): void {\n const channel = this.channelsOf(kind, at)[j]\n if (!channel || channel.keys.length <= 2) return\n this.patchChannel(kind, at, j, { ...channel, keys: (channel.keys as { at: number }[]).filter((_, i) => i !== k) } as Channel)\n }\n}\n"]}
@@ -49,6 +49,13 @@ export declare class FigureCanvas extends FigureCanvas_base {
49
49
  revision: number;
50
50
  /** 지금 고른 부품 이름. 목록에서 고른 것을 캔버스에 알린다. */
51
51
  picked?: string;
52
+ /**
53
+ * 관절이 도는 자리를 그리는 부채꼴(`joint-gizmo.ts`). 저작면에만 있고 저장되지 않는다.
54
+ *
55
+ * 부품과 같은 길로 세운다 — 씬에 겹쳐 그리는 통로를 따로 뚫지 않는다. `locked` 라 클릭도
56
+ * 마퀴도 집지 않는다.
57
+ */
58
+ gizmo?: PartModel;
52
59
  /**
53
60
  * 보기 전용 — 씬에서 일어난 일을 밖에 알리지 않는다.
54
61
  *
@@ -125,6 +132,8 @@ export declare class FigureCanvas extends FigureCanvas_base {
125
132
  * 다시 세우면 고른 것과 카메라가 날아가서, 숫자 하나 고칠 때마다 화면이 처음으로
126
133
  * 돌아간다.
127
134
  */
135
+ /** 판에 세울 것 전부 — 저작한 부품과, 있으면 관절 부채꼴. */
136
+ private standing3d;
128
137
  private syncParts;
129
138
  /**
130
139
  * 씬에서 일어난 일을 밖에 알린다.
@@ -192,6 +192,8 @@ let FigureCanvas = class FigureCanvas extends localize(i18next)(LitElement) {
192
192
  this.standing = undefined;
193
193
  if (changed.has('draft') || changed.has('parts') || changed.has('figureId') || changed.has('revision'))
194
194
  this.standUp();
195
+ else if (changed.has('gizmo'))
196
+ this.syncParts();
195
197
  if (changed.has('view'))
196
198
  this.applyView();
197
199
  if (changed.has('picked'))
@@ -266,7 +268,7 @@ let FigureCanvas = class FigureCanvas extends localize(i18next)(LitElement) {
266
268
  없어야 한다: 좌표 모눈과 겹쳐 싸우고 원점 아래쪽을 가린다.
267
269
  */
268
270
  fillStyle: PAPER,
269
- components: this.parts.map(part => ({ ...part }))
271
+ components: this.standing3d().map(part => ({ ...part }))
270
272
  },
271
273
  // **편집 모드**로 만든다. 도면의 3D 모델러가 그 모드에서 카메라 시점 버튼 ·
272
274
  // 기즈모 모드 · 도움말 · 단축키를 띄운다.
@@ -354,11 +356,16 @@ let FigureCanvas = class FigureCanvas extends localize(i18next)(LitElement) {
354
356
  * 다시 세우면 고른 것과 카메라가 날아가서, 숫자 하나 고칠 때마다 화면이 처음으로
355
357
  * 돌아간다.
356
358
  */
359
+ /** 판에 세울 것 전부 — 저작한 부품과, 있으면 관절 부채꼴. */
360
+ standing3d() {
361
+ return this.gizmo ? [...this.parts, this.gizmo] : this.parts;
362
+ }
357
363
  syncParts() {
358
364
  const root = this.scene?.root;
359
365
  if (!root)
360
366
  return;
361
- const wanted = new Map(this.parts.map(part => [part.name, part]));
367
+ const all = this.standing3d();
368
+ const wanted = new Map(all.map(part => [part.name, part]));
362
369
  const standing = this.partsOnCanvas();
363
370
  // 없어진 것을 제거한다
364
371
  for (const [name, component] of standing) {
@@ -367,7 +374,7 @@ let FigureCanvas = class FigureCanvas extends localize(i18next)(LitElement) {
367
374
  component.dispose?.();
368
375
  }
369
376
  }
370
- for (const part of this.parts) {
377
+ for (const part of all) {
371
378
  const found = standing.get(part.name);
372
379
  if (found) {
373
380
  /*
@@ -471,6 +478,10 @@ __decorate([
471
478
  property({ type: String }),
472
479
  __metadata("design:type", String)
473
480
  ], FigureCanvas.prototype, "picked", void 0);
481
+ __decorate([
482
+ property({ type: Object }),
483
+ __metadata("design:type", Object)
484
+ ], FigureCanvas.prototype, "gizmo", void 0);
474
485
  __decorate([
475
486
  property({ type: Boolean }),
476
487
  __metadata("design:type", Object)
@@ -1 +1 @@
1
- {"version":3,"file":"figure-canvas.js","sourceRoot":"","sources":["../../client/modeller/figure-canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { create, Model } from '@hatiolab/things-scene'\n\nimport { gridStep } from './part-edits.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH } from './figure-view.js'\nimport type { ViewSettings } from './figure-view.js'\nimport type { FigureDraft, PartModel } from './figure-source.js'\n\n/**\n * 판의 종이 색 — 거의 흰색.\n *\n * 2D 편집 도구(손잡이 · 안내선 · 선택 테)가 어두운 색으로 그려진다. 종이가 어두우면\n * 그것들이 안 보여 편집을 할 수 없다. 순백이 아닌 것은 판의 가장자리가 작업면과\n * 구별되게 하려는 것이다.\n */\nconst PAPER = '#f4f4f4'\n\n/**\n * 달라진 값만 골라 낸다. 같은 것뿐이면 `null`.\n *\n * 값 비교는 JSON 으로 한다 — 경로(`path`)처럼 배열인 값이 있어서 얕은 비교로는 늘\n * 다르다고 나온다.\n */\nfunction changedKeys(\n now: Record<string, unknown>,\n wanted: Record<string, unknown>\n): Record<string, unknown> | null {\n const diff: Record<string, unknown> = {}\n let any = false\n\n for (const [key, value] of Object.entries(wanted)) {\n const before = now[key]\n const same =\n before === value || (before !== undefined && JSON.stringify(before) === JSON.stringify(value))\n if (!same) {\n diff[key] = value\n any = true\n }\n }\n\n return any ? diff : null\n}\n\n/** 씬이 돌려주는 것 중 우리가 쓰는 부분. */\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n /** 모델 전체가 보이게 화면에 맞춘다. 'ratio' 는 비율을 지키며 맞춘다. */\n fit?: (type?: string) => void\n /**\n * **판**이다 — 루트가 아니다.\n *\n * `create()` 가 돌려주는 `root` 는 `model-layer` 이고, 그 위에 진짜 루트가 하나 더\n * 있다. 부품은 판에 붙고, 선택 알림은 루트가 낸다. 둘을 섞으면 알림이 안 온다 —\n * 실제로 그랬다.\n */\n root?: SceneLayer\n}\n\ninterface SceneLayer {\n invalidate?: () => void\n /** 보기 설정을 넣는 자리. 씬이 스스로 반응한다. */\n set?: (patch: Record<string, unknown>) => void\n /** 3D 카메라를 모델 전체가 보이게 맞춘다. 10.1.4 부터 있다. */\n fitCameraToModel?: (margin?: number) => void\n components?: SceneComponent[]\n selected?: SceneComponent[]\n app?: unknown\n addComponent?: (component: unknown) => void\n removeComponent?: (component: unknown) => void\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n /** 판 위의 루트. 선택 알림이 여기서 난다. */\n root?: {\n selected?: SceneComponent[]\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n }\n}\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown> | string, value?: unknown) => void\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n removeSelf?: (completely?: boolean) => void\n dispose?: () => void\n}\n\n/**\n * 편집 캔버스 — 부품들을 기준 상자 위에 세우고, 여기서 고치게 한다.\n *\n * ## 미리보기가 아니다\n *\n * 한동안 이름이 figure-preview 였다. 원본을 컴파일해 컴포넌트 하나로 세우던 시절에는\n * 정말 보기만 하는 화면이었다. 부품을 컴포넌트로 바꾸면서 편집이 여기서 일어나게 되었다.\n * 부품을 고르고, 2D 로 바꿔 편집하고, 기즈모로 끄는 일이 전부 이 안에서 일어난다.\n * 이름만 남아 있었다.\n *\n * ## 부품이 컴포넌트다\n *\n * 한동안 원본 데이터를 컴파일해 **컴포넌트 하나**로 놓고, 그 안에서 부품을 찾아 다뤘다.\n * 배포용 형태를 편집 화면에 그대로 쓴 것이다. 그래서 어느 부품이 선택됐나 · 무엇을 감쌀까 ·\n * 무엇을 끌까를 전부 따로 뚫어야 했다.\n *\n * 이제 부품 하나가 컴포넌트 하나다. 선택 · 기즈모 · 윤곽선 · 다중 선택 · 되돌리기가\n * 씬의 기본 동작으로 그냥 된다.\n *\n * ## 초안이 곧 기준 상자다\n *\n * 초안의 `width` · `height` · `depth` 가 `FigureSource.base` 다. 부품 좌표를 변환할 때\n * 상자 중심만 빼면 된다 — `figure-source.ts` 가 그 일을 한다.\n *\n * ## 다시 세우지 않는다\n *\n * 부품이 바뀌면 씬을 통째로 다시 세우지 않고 **바뀐 컴포넌트만 고친다.** 다시 세우면\n * 고른 것과 카메라가 날아가서, 숫자 하나 고칠 때마다 화면이 처음으로 돌아간다.\n * 판 크기가 바뀔 때만 다시 세운다 — 판은 씬을 만들 때 정해진다.\n */\n@customElement('figure-canvas')\nexport class FigureCanvas extends localize(i18next)(LitElement) {\n static styles = css`\n /*\n 판 바깥을 우리가 칠한다.\n\n 무대에는 셋이 있다 — 판의 면(2D 는 종이, 3D 는 바닥판), 판 바깥, 좌표 바탕.\n 어느 것도 원본 데이터에 저장되지 않는다. 보려고 만든 것이므로 편집하기 좋게 잡는다.\n\n 2D 에는 바깥을 칠하는 사람이 없다. 씬의 2D 는 캔버스를 투명으로 지우고\n 끝이라(layer.ts 의 clearRect), 뒤에 있는 것이 그대로 비친다. 테마 토큰을\n 깔아 두었더니 어두운 데서 까맣게 나왔다.\n\n 3D 하늘과 같은 값을 쓴다. 갈리면 2D 로 갔을 때 바탕색이 바뀐다.\n */\n :host {\n position: relative;\n display: flex;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n\n /*\n 씬이 이 칸을 자기 것으로 삼으면서 인라인 스타일로 position: relative 를 박는다.\n\n 그러면 우리가 CSS 로 준 position: absolute 가 밀리고, 같이 준 inset: 0 도 함께 뜻을\n 잃는다. relative 인 블록은 상위 배치에서 크기를 받는데, 자식(캔버스)이 전부 절대 배치라\n 내용 폭이 0 이다. 그래서 폭 0 짜리 칸이 되고 캔버스도 폭 0 이 된다.\n\n 화면은 그냥 회색으로 남는다. 게다가 마우스 좌표를 NDC 로 바꿀 때 폭으로 나누므로\n 값이 무한대가 되어 기즈모를 집을 수 없다 — \"기즈모가 안 잡힌다\"의 실제 원인이\n 이것이었다.\n\n 그래서 width 와 height 를 직접 준다. 인라인으로 position 이 무엇으로 바뀌든 칸은\n 부모를 채운다.\n */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n\n /* 세울 수 없는 상태를 빈 화면으로 두지 않는다 — 왜 안 보이는지 말한다 */\n div[cannot] {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-large, 12px);\n text-align: center;\n font: var(--label-font, inherit);\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n `\n\n /**\n * 편집 중인 내용. **씬 모델 그대로 받는다.**\n *\n * 한동안 `FigureSource` 로 받아 여기서 변환했다. 그런데 모델러 페이지가 갖고 있는 것이\n * 이미 씬 모델이므로, 그러면 값을 하나 고칠 때마다 왕복 변환이 한 번씩 일어난다.\n * 무손실이긴 하지만 하는 일이 없다 — 초안이 곧 판이고 부품이 곧 컴포넌트다.\n */\n @property({ type: String }) figureId?: string\n @property({ type: Object }) draft?: FigureDraft\n /** 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다(`figure-view.ts`). */\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Number }) revision = 0\n /** 지금 고른 부품 이름. 목록에서 고른 것을 캔버스에 알린다. */\n @property({ type: String }) picked?: string\n\n /**\n * 보기 전용 — 씬에서 일어난 일을 밖에 알리지 않는다.\n *\n * AI 후보를 나란히 세울 때 쓴다. 후보는 아직 편집 대상이 아닌데, 씬은 컴포넌트를\n * 만들면서 상태를 정리하고 그때 변경 이벤트를 낸다. 그 이벤트가 그대로 올라가면\n * **후보의 값이 편집 중인 데이터를 덮어쓴다** — 실제로 그랬다. 받지 않기로 한 부품\n * 위치가 오류보고없이 반영되어 있었고, 후보를 버려도 되돌아오지 않았다.\n */\n @property({ type: Boolean }) viewOnly = false\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n /** 세울 수 없는 사유. 없으면 세운 것이다. */\n @state() private cannot = ''\n\n private scene?: SceneHandle\n\n /**\n * 지금 세워진 것을 그림으로 낸다 — 카드에 쓸 썸네일.\n *\n * 부르는 쪽은 이 부품을 **숨은 자리에 480×360 으로 하나 더 세워** 부른다\n * (`figure-thumbnail.ts`). 보이는 저작면을 찍지 않는 이유는, 저작자가 돌려 둔 각도와 확대,\n * 그리고 창 크기에 따라 달라지는 칸 비율이 그대로 카드에 박히기 때문이다 — 나사를 확대해 보던\n * 중에 저장하면 그 자산의 얼굴이 나사가 된다. 목록은 견주는 자리라 프레임이 일정해야 한다.\n *\n * 못 찍으면 `undefined` 다. 빈 그림을 만들어 「그림이 있다」고 말하지 않는다.\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n /*\n **3D 를 낼 수 있을 때만 찍는다.**\n\n 씬의 `toDataURL` 은 **2D 씬을 렌더한다.** 그것으로 찍어 보니 흰 종이 한 장이 나왔다\n (480×291 · 834바이트 · 순백 — 2D 판의 면인 `fillStyle: PAPER` 이 화면 전체였다). 3D 픽셀은\n `model-layer.renderer3d`(THREE.WebGLRenderer) 쪽에 있고, 그 렌더러는\n `preserveDrawingBuffer: false` 라 **그린 직후 같은 task 에서** 읽어야 한다. 그 절차를 아는\n 것은 씬이므로, things-scene 이 그 일을 공개 API 로 내주면 여기서 부른다.\n\n 아직 없으면 `undefined` 다. 흰 종이를 썸네일이라고 저장하면 카드가 「그림이 있다」고 말하면서\n 아무것도 보여 주지 않고, 저장할 때마다 판 번호만 올라간다 — 없는 것보다 나쁘다.\n */\n const layer = this.scene?.root as\n | (SceneLayer & {\n snapshot3d?: (\n width?: number,\n height?: number,\n type?: string,\n quality?: number\n ) => Promise<string | undefined>\n })\n | undefined\n\n if (!layer?.snapshot3d) {\n return undefined\n }\n\n try {\n /*\n 알파가 필요 없고 카드에 들어갈 그림이라 webp 로 찍는다. png 는 같은 그림이 서너 배 무겁고,\n 그 무게가 도형마다 DB 와 목록 응답에 실린다. webp 를 못 만드는 브라우저는 캔버스가 알아서\n png 를 돌려준다 — 우리가 판정하지 않는다.\n */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n /* 원인을 지어내지 않는다. 못 찍은 것이 저장을 막지는 않는다. */\n console.warn(`[thumbnail] 찍지 못했습니다 — ${(e as Error).message}`)\n return undefined\n }\n }\n /** 지금 씬이 서 있는 판. 이것이 달라지면 다시 세운다. */\n private standing?: string\n /** 칸 크기를 따라가게 지켜본다. 안 지켜보면 0x0 으로 고정된다. */\n private watcher?: ResizeObserver\n\n render() {\n return html`\n <div stage></div>\n ${this.cannot ? html`<div cannot>${this.cannot}</div>` : ''}\n `\n }\n\n willUpdate(changed: Map<string, unknown>) {\n if (!this.hasUpdated) return\n\n if (changed.has('revision')) this.standing = undefined\n if (changed.has('draft') || changed.has('parts') || changed.has('figureId') || changed.has('revision')) this.standUp()\n if (changed.has('view')) this.applyView()\n if (changed.has('picked')) this.showPicked()\n }\n\n firstUpdated() {\n this.standUp()\n\n // 씬은 세울 때의 칸 크기를 읽는다. 저작면이 아직 배치되기 전이면 0x0 으로 굳어\n // **캔버스는 붙었는데 아무것도 안 보이는** 상태가 된다 — 실제로 그렇게 났다.\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * 받은 것을 씬에 반영한다.\n *\n * **판이 그대로면 다시 세우지 않는다.** 다시 세우면 고른 것과 카메라가 날아가서,\n * 숫자 하나 고칠 때마다 화면이 처음으로 돌아간다.\n */\n private standUp() {\n const draft = this.draft\n if (!draft) {\n this.teardown()\n this.cannot = i18next.t('figure.text.canvas-appears-once-a-part-exists')\n return\n }\n\n // 새 Figure의 빈 초안에서는 이전 자산의 씬을 반드시 버린다.\n if (this.parts.length === 0) {\n this.teardown()\n this.cannot = i18next.t('figure.text.canvas-appears-once-a-part-exists')\n return\n }\n\n const signature = `${this.figureId ?? ''}:${draft.width}x${draft.height}x${draft.depth}`\n if (this.scene && this.standing === signature) {\n this.syncParts()\n return\n }\n\n this.teardown()\n\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: draft.width,\n height: draft.height,\n depth: draft.depth,\n threed: true,\n environment: 'studio',\n /*\n 표본을 화면보다 더 쓴다. MSAA 만으로는 실루엣의 계단이 남는다 — 픽셀\n 비율만 올려 보니 눈에 띄게 매끄러워졌다. 저작면은 작은 칸에 자산 하나라\n 그릴 것이 적으니 표본을 더 써도 된다.\n */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n /*\n 좌표 바탕을 씬에게 맡긴다. 우리가 만들어 얹었더니 2D 로 갔다 오면\n 사라졌다 — 그때 씬이 통째로 다시 서면서 새 THREE.Scene 을 만들기 때문이다.\n 도면은 이 값을 안 주므로 안 보인다.\n */\n ...this.sceneView(),\n gridStep: gridStep(draft),\n sky: 'color',\n /*\n 판의 면 — 2D 는 밝은 종이, 3D 는 없음.\n\n 종이는 밝아야 한다: 2D 편집 손잡이와 안내선이 어두운 색이다. 바닥판은\n 없어야 한다: 좌표 모눈과 겹쳐 싸우고 원점 아래쪽을 가린다.\n */\n fillStyle: PAPER,\n components: this.parts.map(part => ({ ...part }))\n },\n // **편집 모드**로 만든다. 도면의 3D 모델러가 그 모드에서 카메라 시점 버튼 ·\n // 기즈모 모드 · 도움말 · 단축키를 띄운다.\n mode: 1\n }) as SceneHandle\n\n this.standing = signature\n this.watchScene()\n this.scene.resize?.()\n /*\n 열자마자 모델 전체가 보이게 맞춘다.\n\n 카메라는 2D 뷰포트에서 만들어진다. 그런데 그 뷰포트의 처음 배율은 컨테이너\n 크기에서 오지 모델 크기에서 오지 않는다. 그래서 가로로 긴 모델(컨베이어가\n 그렇다)을 열면 화면 밖으로 나가 벨트 한 조각만 보였다.\n\n **다시 세울 때만 맞춘다.** 창 크기가 바뀔 때마다 맞추면 사용자가 돌려 둔 시점과\n 확대가 매번 되돌아간다.\n */\n this.scene.fit?.('ratio')\n /*\n 3D 카메라는 2D 쪽 fit 으로 안 움직인다. 처음 3D 로 설 때 모델 크기를 보지 않기\n 때문이다. 씬이 내주는 자리로 따로 맞춘다.\n\n `?.` 로 부르는 것은 배포된 판에 아직 없을 수 있어서다. 없으면 2D 만 맞고 3D 는\n 기본 시점으로 선다 — 여기서 카메라 계산을 흉내 내지 않는다.\n */\n this.scene.root?.fitCameraToModel?.()\n this.cannot = ''\n } catch (e) {\n // 원인을 지어내지 않는다 — 던진 말을 그대로 보여 준다.\n this.cannot = (e as Error).message\n }\n }\n\n /**\n * 지금 판 위에 있는 부품 컴포넌트를 이름으로 찾는다.\n *\n * **참조를 들고 있지 않는다.** 전에는 세울 때 한 번 색인해 두고 계속 그 Map 을 썼다.\n * 그런데 씬은 우리 모르게 판을 다시 세울 때가 있다 — 2D 와 3D 를 오갈 때가 그렇다.\n * 그러면 Map 안의 것들은 부모가 없는 죽은 객체가 되고, 그 뒤로는\n *\n * - 목록에서 골라도 선택이 안 된다. 루트의 `selected` 게터가 부모 없는 것을\n * 걸러 내므로 빈 배열이 되고, 테두리도 기즈모도 붙지 않는다\n * - 속성을 고쳐도 3D 가 안 따라온다. 죽은 객체에 값을 넣기 때문이다\n *\n * 둘 다 오류보고없이 일어난다. 실제로 화면에서 「기즈모가 안 잡힌다」로 나타났다.\n *\n * 부품은 많아야 20개다. 매번 순회하는 값이 참조를 갖고 있다가 틀리는 값보다 훨씬 싸다.\n */\n private partsOnCanvas(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /**\n * 보기 설정을 씬이 아는 이름으로 낸다.\n *\n * 옮겨 적는 표를 두지 않는다 — `ViewSettings` 의 이름이 곧 `model-layer` 의 상태\n * 이름이다. 표를 두면 씬이 새 설정을 받아도 이쪽이 모른다.\n */\n private sceneView(): Record<string, unknown> {\n return sceneLook(this.view ?? DEFAULT_VIEW)\n }\n\n /**\n * 설정이 바뀌면 **씬에 그대로 넣는다.** 다시 세우지 않는다.\n *\n * 씬이 이 값들에 스스로 반응한다(`three-capability` 의 `onchange`) — 조명 · 반사 ·\n * 바닥 · 좌표 바탕이 그 자리에서 바뀐다. 다시 세우면 고른 것과 카메라가 날아가서,\n * 밝기 하나 끄는 동안 화면이 계속 처음으로 돌아간다.\n */\n private applyView(): void {\n const draft = this.scene?.root\n if (!draft) return\n\n draft.set?.(this.sceneView())\n draft.invalidate?.()\n }\n\n /**\n * 부품 목록을 씬에 맞춘다. **다시 세우지 않고 바뀐 것만 고친다.**\n *\n * 다시 세우면 고른 것과 카메라가 날아가서, 숫자 하나 고칠 때마다 화면이 처음으로\n * 돌아간다.\n */\n private syncParts() {\n const root = this.scene?.root\n if (!root) return\n\n const wanted = new Map(this.parts.map(part => [part.name, part]))\n const standing = this.partsOnCanvas()\n\n // 없어진 것을 제거한다\n for (const [name, component] of standing) {\n if (!wanted.has(name)) {\n root.removeComponent?.(component)\n component.dispose?.()\n }\n }\n\n for (const part of this.parts) {\n const found = standing.get(part.name)\n if (found) {\n /*\n **달라진 값만 넣는다.**\n\n 같은 값을 다시 넣어도 씬은 변경 이벤트를 낸다. 그 이벤트가 다시 편집 데이터를\n 만들고, 그것이 다시 여기로 돌아오면 끝없이 반복된다. 기즈모를 끄는 중이라면 그\n 되먹임이 끌기와 싸운다.\n */\n const diff = changedKeys(found.state, part as unknown as Record<string, unknown>)\n if (diff) found.set(diff)\n continue\n }\n // 새 부품. 씬의 공장으로 만들어 판에 붙인다.\n const made = Model.compile({ ...part }, root.app as never) as SceneComponent | null\n if (made) root.addComponent?.(made)\n }\n\n root.invalidate?.()\n }\n\n /**\n * 씬에서 일어난 일을 밖에 알린다.\n *\n * **자리가 아니라 이름으로 알린다.** 목록의 몇 번째인지로 알리면, 알림이 오는 사이에\n * 목록이 바뀌면 엉뚱한 부품을 가리킨다.\n */\n private watchScene() {\n const draft = this.scene?.root\n const root = draft?.root\n if (!draft || !root?.on || this.viewOnly) return\n\n root.on('selected', (after: unknown) => {\n const chosen = (after as SceneComponent[] | undefined)?.[0]\n const name = chosen?.state?.name\n if (typeof name === 'string') this.tell('pick-part', { name })\n })\n\n /*\n 기즈모로 끌거나 2D 에서 옮기면 컴포넌트 상태가 바뀐다. 그것을 그대로 올려 보낸다 —\n 씬이 쓰는 용어 그대로다. 저장 형식으로 옮기는 일은 변환 함수가 한다.\n\n **바뀜은 판으로 올라오고, 바뀐 컴포넌트는 끝 인자의 `origin` 에 있다.** 자식이\n `trigger('change', after, before)` 를 하면 씬이 `{ origin, name, deliverer }` 를\n 뒤에 붙여 부모에게 넘긴다. 첫 인자를 컴포넌트로 읽으면 아무 일도 안 일어난다 —\n 실제로 그랬다.\n */\n draft.on?.('change', (...args: unknown[]) => {\n const hint = args[args.length - 1] as { origin?: SceneComponent } | undefined\n const changed = hint?.origin\n const name = changed?.state?.name\n if (typeof name !== 'string') return\n\n const was = this.parts.find(part => part.name === name)\n if (!was) return\n\n // 씬 모델 그대로 올려 보낸다. 모델러 페이지가 갖고 있는 것도 씬 모델이라 변환할 것이 없다.\n this.tell('part-changed', { name, part: { ...was, ...changed!.state } as PartModel })\n })\n }\n\n /** 목록에서 고른 것을 캔버스의 선택에 맞춘다. */\n private showPicked() {\n const draft = this.scene?.root\n const root = draft?.root\n if (!draft || !root) return\n\n const component = this.picked ? this.partsOnCanvas().get(this.picked) : undefined\n root.selected = component ? [component] : []\n draft.invalidate?.()\n }\n\n private tell(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n\n private teardown() {\n this.scene?.dispose?.()\n this.scene = undefined\n this.standing = undefined\n }\n}\n"]}
1
+ {"version":3,"file":"figure-canvas.js","sourceRoot":"","sources":["../../client/modeller/figure-canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { create, Model } from '@hatiolab/things-scene'\n\nimport { gridStep } from './part-edits.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH } from './figure-view.js'\nimport type { ViewSettings } from './figure-view.js'\nimport type { FigureDraft, PartModel } from './figure-source.js'\n\n/**\n * 판의 종이 색 — 거의 흰색.\n *\n * 2D 편집 도구(손잡이 · 안내선 · 선택 테)가 어두운 색으로 그려진다. 종이가 어두우면\n * 그것들이 안 보여 편집을 할 수 없다. 순백이 아닌 것은 판의 가장자리가 작업면과\n * 구별되게 하려는 것이다.\n */\nconst PAPER = '#f4f4f4'\n\n/**\n * 달라진 값만 골라 낸다. 같은 것뿐이면 `null`.\n *\n * 값 비교는 JSON 으로 한다 — 경로(`path`)처럼 배열인 값이 있어서 얕은 비교로는 늘\n * 다르다고 나온다.\n */\nfunction changedKeys(\n now: Record<string, unknown>,\n wanted: Record<string, unknown>\n): Record<string, unknown> | null {\n const diff: Record<string, unknown> = {}\n let any = false\n\n for (const [key, value] of Object.entries(wanted)) {\n const before = now[key]\n const same =\n before === value || (before !== undefined && JSON.stringify(before) === JSON.stringify(value))\n if (!same) {\n diff[key] = value\n any = true\n }\n }\n\n return any ? diff : null\n}\n\n/** 씬이 돌려주는 것 중 우리가 쓰는 부분. */\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n /** 모델 전체가 보이게 화면에 맞춘다. 'ratio' 는 비율을 지키며 맞춘다. */\n fit?: (type?: string) => void\n /**\n * **판**이다 — 루트가 아니다.\n *\n * `create()` 가 돌려주는 `root` 는 `model-layer` 이고, 그 위에 진짜 루트가 하나 더\n * 있다. 부품은 판에 붙고, 선택 알림은 루트가 낸다. 둘을 섞으면 알림이 안 온다 —\n * 실제로 그랬다.\n */\n root?: SceneLayer\n}\n\ninterface SceneLayer {\n invalidate?: () => void\n /** 보기 설정을 넣는 자리. 씬이 스스로 반응한다. */\n set?: (patch: Record<string, unknown>) => void\n /** 3D 카메라를 모델 전체가 보이게 맞춘다. 10.1.4 부터 있다. */\n fitCameraToModel?: (margin?: number) => void\n components?: SceneComponent[]\n selected?: SceneComponent[]\n app?: unknown\n addComponent?: (component: unknown) => void\n removeComponent?: (component: unknown) => void\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n /** 판 위의 루트. 선택 알림이 여기서 난다. */\n root?: {\n selected?: SceneComponent[]\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n }\n}\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown> | string, value?: unknown) => void\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n removeSelf?: (completely?: boolean) => void\n dispose?: () => void\n}\n\n/**\n * 편집 캔버스 — 부품들을 기준 상자 위에 세우고, 여기서 고치게 한다.\n *\n * ## 미리보기가 아니다\n *\n * 한동안 이름이 figure-preview 였다. 원본을 컴파일해 컴포넌트 하나로 세우던 시절에는\n * 정말 보기만 하는 화면이었다. 부품을 컴포넌트로 바꾸면서 편집이 여기서 일어나게 되었다.\n * 부품을 고르고, 2D 로 바꿔 편집하고, 기즈모로 끄는 일이 전부 이 안에서 일어난다.\n * 이름만 남아 있었다.\n *\n * ## 부품이 컴포넌트다\n *\n * 한동안 원본 데이터를 컴파일해 **컴포넌트 하나**로 놓고, 그 안에서 부품을 찾아 다뤘다.\n * 배포용 형태를 편집 화면에 그대로 쓴 것이다. 그래서 어느 부품이 선택됐나 · 무엇을 감쌀까 ·\n * 무엇을 끌까를 전부 따로 뚫어야 했다.\n *\n * 이제 부품 하나가 컴포넌트 하나다. 선택 · 기즈모 · 윤곽선 · 다중 선택 · 되돌리기가\n * 씬의 기본 동작으로 그냥 된다.\n *\n * ## 초안이 곧 기준 상자다\n *\n * 초안의 `width` · `height` · `depth` 가 `FigureSource.base` 다. 부품 좌표를 변환할 때\n * 상자 중심만 빼면 된다 — `figure-source.ts` 가 그 일을 한다.\n *\n * ## 다시 세우지 않는다\n *\n * 부품이 바뀌면 씬을 통째로 다시 세우지 않고 **바뀐 컴포넌트만 고친다.** 다시 세우면\n * 고른 것과 카메라가 날아가서, 숫자 하나 고칠 때마다 화면이 처음으로 돌아간다.\n * 판 크기가 바뀔 때만 다시 세운다 — 판은 씬을 만들 때 정해진다.\n */\n@customElement('figure-canvas')\nexport class FigureCanvas extends localize(i18next)(LitElement) {\n static styles = css`\n /*\n 판 바깥을 우리가 칠한다.\n\n 무대에는 셋이 있다 — 판의 면(2D 는 종이, 3D 는 바닥판), 판 바깥, 좌표 바탕.\n 어느 것도 원본 데이터에 저장되지 않는다. 보려고 만든 것이므로 편집하기 좋게 잡는다.\n\n 2D 에는 바깥을 칠하는 사람이 없다. 씬의 2D 는 캔버스를 투명으로 지우고\n 끝이라(layer.ts 의 clearRect), 뒤에 있는 것이 그대로 비친다. 테마 토큰을\n 깔아 두었더니 어두운 데서 까맣게 나왔다.\n\n 3D 하늘과 같은 값을 쓴다. 갈리면 2D 로 갔을 때 바탕색이 바뀐다.\n */\n :host {\n position: relative;\n display: flex;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n\n /*\n 씬이 이 칸을 자기 것으로 삼으면서 인라인 스타일로 position: relative 를 박는다.\n\n 그러면 우리가 CSS 로 준 position: absolute 가 밀리고, 같이 준 inset: 0 도 함께 뜻을\n 잃는다. relative 인 블록은 상위 배치에서 크기를 받는데, 자식(캔버스)이 전부 절대 배치라\n 내용 폭이 0 이다. 그래서 폭 0 짜리 칸이 되고 캔버스도 폭 0 이 된다.\n\n 화면은 그냥 회색으로 남는다. 게다가 마우스 좌표를 NDC 로 바꿀 때 폭으로 나누므로\n 값이 무한대가 되어 기즈모를 집을 수 없다 — \"기즈모가 안 잡힌다\"의 실제 원인이\n 이것이었다.\n\n 그래서 width 와 height 를 직접 준다. 인라인으로 position 이 무엇으로 바뀌든 칸은\n 부모를 채운다.\n */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n\n /* 세울 수 없는 상태를 빈 화면으로 두지 않는다 — 왜 안 보이는지 말한다 */\n div[cannot] {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-large, 12px);\n text-align: center;\n font: var(--label-font, inherit);\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n `\n\n /**\n * 편집 중인 내용. **씬 모델 그대로 받는다.**\n *\n * 한동안 `FigureSource` 로 받아 여기서 변환했다. 그런데 모델러 페이지가 갖고 있는 것이\n * 이미 씬 모델이므로, 그러면 값을 하나 고칠 때마다 왕복 변환이 한 번씩 일어난다.\n * 무손실이긴 하지만 하는 일이 없다 — 초안이 곧 판이고 부품이 곧 컴포넌트다.\n */\n @property({ type: String }) figureId?: string\n @property({ type: Object }) draft?: FigureDraft\n /** 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다(`figure-view.ts`). */\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Number }) revision = 0\n /** 지금 고른 부품 이름. 목록에서 고른 것을 캔버스에 알린다. */\n @property({ type: String }) picked?: string\n\n /**\n * 관절이 도는 자리를 그리는 부채꼴(`joint-gizmo.ts`). 저작면에만 있고 저장되지 않는다.\n *\n * 부품과 같은 길로 세운다 — 씬에 겹쳐 그리는 통로를 따로 뚫지 않는다. `locked` 라 클릭도\n * 마퀴도 집지 않는다.\n */\n @property({ type: Object }) gizmo?: PartModel\n\n /**\n * 보기 전용 — 씬에서 일어난 일을 밖에 알리지 않는다.\n *\n * AI 후보를 나란히 세울 때 쓴다. 후보는 아직 편집 대상이 아닌데, 씬은 컴포넌트를\n * 만들면서 상태를 정리하고 그때 변경 이벤트를 낸다. 그 이벤트가 그대로 올라가면\n * **후보의 값이 편집 중인 데이터를 덮어쓴다** — 실제로 그랬다. 받지 않기로 한 부품\n * 위치가 오류보고없이 반영되어 있었고, 후보를 버려도 되돌아오지 않았다.\n */\n @property({ type: Boolean }) viewOnly = false\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n /** 세울 수 없는 사유. 없으면 세운 것이다. */\n @state() private cannot = ''\n\n private scene?: SceneHandle\n\n /**\n * 지금 세워진 것을 그림으로 낸다 — 카드에 쓸 썸네일.\n *\n * 부르는 쪽은 이 부품을 **숨은 자리에 480×360 으로 하나 더 세워** 부른다\n * (`figure-thumbnail.ts`). 보이는 저작면을 찍지 않는 이유는, 저작자가 돌려 둔 각도와 확대,\n * 그리고 창 크기에 따라 달라지는 칸 비율이 그대로 카드에 박히기 때문이다 — 나사를 확대해 보던\n * 중에 저장하면 그 자산의 얼굴이 나사가 된다. 목록은 견주는 자리라 프레임이 일정해야 한다.\n *\n * 못 찍으면 `undefined` 다. 빈 그림을 만들어 「그림이 있다」고 말하지 않는다.\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n /*\n **3D 를 낼 수 있을 때만 찍는다.**\n\n 씬의 `toDataURL` 은 **2D 씬을 렌더한다.** 그것으로 찍어 보니 흰 종이 한 장이 나왔다\n (480×291 · 834바이트 · 순백 — 2D 판의 면인 `fillStyle: PAPER` 이 화면 전체였다). 3D 픽셀은\n `model-layer.renderer3d`(THREE.WebGLRenderer) 쪽에 있고, 그 렌더러는\n `preserveDrawingBuffer: false` 라 **그린 직후 같은 task 에서** 읽어야 한다. 그 절차를 아는\n 것은 씬이므로, things-scene 이 그 일을 공개 API 로 내주면 여기서 부른다.\n\n 아직 없으면 `undefined` 다. 흰 종이를 썸네일이라고 저장하면 카드가 「그림이 있다」고 말하면서\n 아무것도 보여 주지 않고, 저장할 때마다 판 번호만 올라간다 — 없는 것보다 나쁘다.\n */\n const layer = this.scene?.root as\n | (SceneLayer & {\n snapshot3d?: (\n width?: number,\n height?: number,\n type?: string,\n quality?: number\n ) => Promise<string | undefined>\n })\n | undefined\n\n if (!layer?.snapshot3d) {\n return undefined\n }\n\n try {\n /*\n 알파가 필요 없고 카드에 들어갈 그림이라 webp 로 찍는다. png 는 같은 그림이 서너 배 무겁고,\n 그 무게가 도형마다 DB 와 목록 응답에 실린다. webp 를 못 만드는 브라우저는 캔버스가 알아서\n png 를 돌려준다 — 우리가 판정하지 않는다.\n */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n /* 원인을 지어내지 않는다. 못 찍은 것이 저장을 막지는 않는다. */\n console.warn(`[thumbnail] 찍지 못했습니다 — ${(e as Error).message}`)\n return undefined\n }\n }\n /** 지금 씬이 서 있는 판. 이것이 달라지면 다시 세운다. */\n private standing?: string\n /** 칸 크기를 따라가게 지켜본다. 안 지켜보면 0x0 으로 고정된다. */\n private watcher?: ResizeObserver\n\n render() {\n return html`\n <div stage></div>\n ${this.cannot ? html`<div cannot>${this.cannot}</div>` : ''}\n `\n }\n\n willUpdate(changed: Map<string, unknown>) {\n if (!this.hasUpdated) return\n\n if (changed.has('revision')) this.standing = undefined\n if (changed.has('draft') || changed.has('parts') || changed.has('figureId') || changed.has('revision')) this.standUp()\n else if (changed.has('gizmo')) this.syncParts()\n if (changed.has('view')) this.applyView()\n if (changed.has('picked')) this.showPicked()\n }\n\n firstUpdated() {\n this.standUp()\n\n // 씬은 세울 때의 칸 크기를 읽는다. 저작면이 아직 배치되기 전이면 0x0 으로 굳어\n // **캔버스는 붙었는데 아무것도 안 보이는** 상태가 된다 — 실제로 그렇게 났다.\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * 받은 것을 씬에 반영한다.\n *\n * **판이 그대로면 다시 세우지 않는다.** 다시 세우면 고른 것과 카메라가 날아가서,\n * 숫자 하나 고칠 때마다 화면이 처음으로 돌아간다.\n */\n private standUp() {\n const draft = this.draft\n if (!draft) {\n this.teardown()\n this.cannot = i18next.t('figure.text.canvas-appears-once-a-part-exists')\n return\n }\n\n // 새 Figure의 빈 초안에서는 이전 자산의 씬을 반드시 버린다.\n if (this.parts.length === 0) {\n this.teardown()\n this.cannot = i18next.t('figure.text.canvas-appears-once-a-part-exists')\n return\n }\n\n const signature = `${this.figureId ?? ''}:${draft.width}x${draft.height}x${draft.depth}`\n if (this.scene && this.standing === signature) {\n this.syncParts()\n return\n }\n\n this.teardown()\n\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: draft.width,\n height: draft.height,\n depth: draft.depth,\n threed: true,\n environment: 'studio',\n /*\n 표본을 화면보다 더 쓴다. MSAA 만으로는 실루엣의 계단이 남는다 — 픽셀\n 비율만 올려 보니 눈에 띄게 매끄러워졌다. 저작면은 작은 칸에 자산 하나라\n 그릴 것이 적으니 표본을 더 써도 된다.\n */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n /*\n 좌표 바탕을 씬에게 맡긴다. 우리가 만들어 얹었더니 2D 로 갔다 오면\n 사라졌다 — 그때 씬이 통째로 다시 서면서 새 THREE.Scene 을 만들기 때문이다.\n 도면은 이 값을 안 주므로 안 보인다.\n */\n ...this.sceneView(),\n gridStep: gridStep(draft),\n sky: 'color',\n /*\n 판의 면 — 2D 는 밝은 종이, 3D 는 없음.\n\n 종이는 밝아야 한다: 2D 편집 손잡이와 안내선이 어두운 색이다. 바닥판은\n 없어야 한다: 좌표 모눈과 겹쳐 싸우고 원점 아래쪽을 가린다.\n */\n fillStyle: PAPER,\n components: this.standing3d().map(part => ({ ...part }))\n },\n // **편집 모드**로 만든다. 도면의 3D 모델러가 그 모드에서 카메라 시점 버튼 ·\n // 기즈모 모드 · 도움말 · 단축키를 띄운다.\n mode: 1\n }) as SceneHandle\n\n this.standing = signature\n this.watchScene()\n this.scene.resize?.()\n /*\n 열자마자 모델 전체가 보이게 맞춘다.\n\n 카메라는 2D 뷰포트에서 만들어진다. 그런데 그 뷰포트의 처음 배율은 컨테이너\n 크기에서 오지 모델 크기에서 오지 않는다. 그래서 가로로 긴 모델(컨베이어가\n 그렇다)을 열면 화면 밖으로 나가 벨트 한 조각만 보였다.\n\n **다시 세울 때만 맞춘다.** 창 크기가 바뀔 때마다 맞추면 사용자가 돌려 둔 시점과\n 확대가 매번 되돌아간다.\n */\n this.scene.fit?.('ratio')\n /*\n 3D 카메라는 2D 쪽 fit 으로 안 움직인다. 처음 3D 로 설 때 모델 크기를 보지 않기\n 때문이다. 씬이 내주는 자리로 따로 맞춘다.\n\n `?.` 로 부르는 것은 배포된 판에 아직 없을 수 있어서다. 없으면 2D 만 맞고 3D 는\n 기본 시점으로 선다 — 여기서 카메라 계산을 흉내 내지 않는다.\n */\n this.scene.root?.fitCameraToModel?.()\n this.cannot = ''\n } catch (e) {\n // 원인을 지어내지 않는다 — 던진 말을 그대로 보여 준다.\n this.cannot = (e as Error).message\n }\n }\n\n /**\n * 지금 판 위에 있는 부품 컴포넌트를 이름으로 찾는다.\n *\n * **참조를 들고 있지 않는다.** 전에는 세울 때 한 번 색인해 두고 계속 그 Map 을 썼다.\n * 그런데 씬은 우리 모르게 판을 다시 세울 때가 있다 — 2D 와 3D 를 오갈 때가 그렇다.\n * 그러면 Map 안의 것들은 부모가 없는 죽은 객체가 되고, 그 뒤로는\n *\n * - 목록에서 골라도 선택이 안 된다. 루트의 `selected` 게터가 부모 없는 것을\n * 걸러 내므로 빈 배열이 되고, 테두리도 기즈모도 붙지 않는다\n * - 속성을 고쳐도 3D 가 안 따라온다. 죽은 객체에 값을 넣기 때문이다\n *\n * 둘 다 오류보고없이 일어난다. 실제로 화면에서 「기즈모가 안 잡힌다」로 나타났다.\n *\n * 부품은 많아야 20개다. 매번 순회하는 값이 참조를 갖고 있다가 틀리는 값보다 훨씬 싸다.\n */\n private partsOnCanvas(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /**\n * 보기 설정을 씬이 아는 이름으로 낸다.\n *\n * 옮겨 적는 표를 두지 않는다 — `ViewSettings` 의 이름이 곧 `model-layer` 의 상태\n * 이름이다. 표를 두면 씬이 새 설정을 받아도 이쪽이 모른다.\n */\n private sceneView(): Record<string, unknown> {\n return sceneLook(this.view ?? DEFAULT_VIEW)\n }\n\n /**\n * 설정이 바뀌면 **씬에 그대로 넣는다.** 다시 세우지 않는다.\n *\n * 씬이 이 값들에 스스로 반응한다(`three-capability` 의 `onchange`) — 조명 · 반사 ·\n * 바닥 · 좌표 바탕이 그 자리에서 바뀐다. 다시 세우면 고른 것과 카메라가 날아가서,\n * 밝기 하나 끄는 동안 화면이 계속 처음으로 돌아간다.\n */\n private applyView(): void {\n const draft = this.scene?.root\n if (!draft) return\n\n draft.set?.(this.sceneView())\n draft.invalidate?.()\n }\n\n /**\n * 부품 목록을 씬에 맞춘다. **다시 세우지 않고 바뀐 것만 고친다.**\n *\n * 다시 세우면 고른 것과 카메라가 날아가서, 숫자 하나 고칠 때마다 화면이 처음으로\n * 돌아간다.\n */\n /** 판에 세울 것 전부 — 저작한 부품과, 있으면 관절 부채꼴. */\n private standing3d(): PartModel[] {\n return this.gizmo ? [...this.parts, this.gizmo] : this.parts\n }\n\n private syncParts() {\n const root = this.scene?.root\n if (!root) return\n\n const all = this.standing3d()\n const wanted = new Map(all.map(part => [part.name, part]))\n const standing = this.partsOnCanvas()\n\n // 없어진 것을 제거한다\n for (const [name, component] of standing) {\n if (!wanted.has(name)) {\n root.removeComponent?.(component)\n component.dispose?.()\n }\n }\n\n for (const part of all) {\n const found = standing.get(part.name)\n if (found) {\n /*\n **달라진 값만 넣는다.**\n\n 같은 값을 다시 넣어도 씬은 변경 이벤트를 낸다. 그 이벤트가 다시 편집 데이터를\n 만들고, 그것이 다시 여기로 돌아오면 끝없이 반복된다. 기즈모를 끄는 중이라면 그\n 되먹임이 끌기와 싸운다.\n */\n const diff = changedKeys(found.state, part as unknown as Record<string, unknown>)\n if (diff) found.set(diff)\n continue\n }\n // 새 부품. 씬의 공장으로 만들어 판에 붙인다.\n const made = Model.compile({ ...part }, root.app as never) as SceneComponent | null\n if (made) root.addComponent?.(made)\n }\n\n root.invalidate?.()\n }\n\n /**\n * 씬에서 일어난 일을 밖에 알린다.\n *\n * **자리가 아니라 이름으로 알린다.** 목록의 몇 번째인지로 알리면, 알림이 오는 사이에\n * 목록이 바뀌면 엉뚱한 부품을 가리킨다.\n */\n private watchScene() {\n const draft = this.scene?.root\n const root = draft?.root\n if (!draft || !root?.on || this.viewOnly) return\n\n root.on('selected', (after: unknown) => {\n const chosen = (after as SceneComponent[] | undefined)?.[0]\n const name = chosen?.state?.name\n if (typeof name === 'string') this.tell('pick-part', { name })\n })\n\n /*\n 기즈모로 끌거나 2D 에서 옮기면 컴포넌트 상태가 바뀐다. 그것을 그대로 올려 보낸다 —\n 씬이 쓰는 용어 그대로다. 저장 형식으로 옮기는 일은 변환 함수가 한다.\n\n **바뀜은 판으로 올라오고, 바뀐 컴포넌트는 끝 인자의 `origin` 에 있다.** 자식이\n `trigger('change', after, before)` 를 하면 씬이 `{ origin, name, deliverer }` 를\n 뒤에 붙여 부모에게 넘긴다. 첫 인자를 컴포넌트로 읽으면 아무 일도 안 일어난다 —\n 실제로 그랬다.\n */\n draft.on?.('change', (...args: unknown[]) => {\n const hint = args[args.length - 1] as { origin?: SceneComponent } | undefined\n const changed = hint?.origin\n const name = changed?.state?.name\n if (typeof name !== 'string') return\n\n const was = this.parts.find(part => part.name === name)\n if (!was) return\n\n // 씬 모델 그대로 올려 보낸다. 모델러 페이지가 갖고 있는 것도 씬 모델이라 변환할 것이 없다.\n this.tell('part-changed', { name, part: { ...was, ...changed!.state } as PartModel })\n })\n }\n\n /** 목록에서 고른 것을 캔버스의 선택에 맞춘다. */\n private showPicked() {\n const draft = this.scene?.root\n const root = draft?.root\n if (!draft || !root) return\n\n const component = this.picked ? this.partsOnCanvas().get(this.picked) : undefined\n root.selected = component ? [component] : []\n draft.invalidate?.()\n }\n\n private tell(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n\n private teardown() {\n this.scene?.dispose?.()\n this.scene = undefined\n this.standing = undefined\n }\n}\n"]}