@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.
- package/client/graphql/index.ts +23 -0
- package/client/modeller/base-box.ts +104 -0
- package/client/modeller/figure-animations.ts +253 -32
- package/client/modeller/figure-canvas.ts +18 -3
- package/client/modeller/figure-inspector.ts +292 -3
- package/client/modeller/figure-preview.ts +57 -2
- package/client/modeller/figure-report.ts +9 -4
- package/client/modeller/figure-side.ts +9 -1
- package/client/modeller/figure-source.ts +24 -4
- package/client/modeller/figure-view.ts +20 -0
- package/client/modeller/joint-edits.ts +262 -0
- package/client/modeller/joint-gizmo.ts +118 -0
- package/client/modeller/pose-defaults.ts +67 -0
- package/client/modeller/proposal.ts +9 -3
- package/client/modeller/scene-budget.ts +31 -40
- package/client/pages/figure-list-page.ts +4 -9
- package/client/pages/figure-modeller-page.ts +124 -41
- package/dist-client/graphql/index.d.ts +7 -0
- package/dist-client/graphql/index.js +21 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/base-box.d.ts +47 -0
- package/dist-client/modeller/base-box.js +52 -0
- package/dist-client/modeller/base-box.js.map +1 -0
- package/dist-client/modeller/figure-animations.d.ts +29 -0
- package/dist-client/modeller/figure-animations.js +228 -25
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +9 -0
- package/dist-client/modeller/figure-canvas.js +14 -3
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +37 -0
- package/dist-client/modeller/figure-inspector.js +253 -2
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +15 -0
- package/dist-client/modeller/figure-preview.js +48 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +3 -2
- package/dist-client/modeller/figure-report.js +9 -4
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +5 -1
- package/dist-client/modeller/figure-side.js +12 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +15 -0
- package/dist-client/modeller/figure-source.js +8 -4
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +16 -0
- package/dist-client/modeller/figure-view.js +15 -0
- package/dist-client/modeller/figure-view.js.map +1 -1
- package/dist-client/modeller/joint-edits.d.ts +49 -0
- package/dist-client/modeller/joint-edits.js +223 -0
- package/dist-client/modeller/joint-edits.js.map +1 -0
- package/dist-client/modeller/joint-gizmo.d.ts +31 -0
- package/dist-client/modeller/joint-gizmo.js +104 -0
- package/dist-client/modeller/joint-gizmo.js.map +1 -0
- package/dist-client/modeller/pose-defaults.d.ts +9 -0
- package/dist-client/modeller/pose-defaults.js +35 -0
- package/dist-client/modeller/pose-defaults.js.map +1 -0
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +8 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/modeller/scene-budget.d.ts +31 -34
- package/dist-client/modeller/scene-budget.js +28 -37
- package/dist-client/modeller/scene-budget.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +4 -9
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +43 -0
- package/dist-client/pages/figure-modeller-page.js +121 -40
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/test/ai-proposal-contract.test.ts +16 -11
- package/test/base-box.test.ts +154 -0
- package/test/figure-source.test.ts +32 -0
- package/test/joint-edits.test.ts +165 -0
- package/test/joint-gizmo.test.ts +174 -0
- package/test/pose-defaults.test.ts +71 -0
- package/test/preview-board-depth.test.ts +27 -0
- package/test/scene-budget.test.ts +53 -0
- package/translations/en.json +37 -2
- package/translations/ja.json +37 -2
- package/translations/ko.json +37 -2
- package/translations/ms.json +37 -2
- package/translations/zh.json +37 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-side.js","sourceRoot":"","sources":["../../client/modeller/figure-side.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport './figure-inspector.js'\nimport './figure-capabilities.js'\nimport './figure-animations.js'\nimport './figure-report.js'\nimport './figure-settings.js'\nimport './figure-history-panel.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureSource } from '@hatiolab/figure-model'\n\nimport type { FigureDraft, PartModel } from './figure-source.js'\nimport { DEFAULT_VIEW } from './figure-view.js'\nimport type { ViewSettings } from './figure-view.js'\n\ntype Tab = 'properties' | 'capability' | 'animation' | 'verdict' | 'view' | 'history'\n\n/**\n * 저작면 오른쪽 칸 — 속성과 판정을 탭으로 겹쳐 둔다.\n *\n * ## 왜 위아래로 나누지 않나\n *\n * 둘 다 세로로 긴 화면이다. 위아래로 나누면 양쪽 다 잘려서, 속성은 재질까지 못\n * 내려가고 판정은 무게까지 못 내려간다. 탭으로 겹치면 각자 칸 전체를 쓴다.\n *\n * ## 그래도 개수는 늘 보인다\n *\n * 성숙도의 축 하나 —「몇 개까지 편한가」— 는 부품을 고칠 때마다 움직이고, 그것이\n * 보여야 저작자가 고칠 수 있다. 탭 뒤로 숨으면 저장하고 나서야 알게 된다. 그래서 그\n * 숫자만 **탭 이름표에 남긴다** — 성숙도를 덮어 두고 속성을 만져도 따라 움직이는\n * 것이 보인다.\n *\n * 판정은 화면에서 감춰도 **DOM 에는 남는다.** 떼었다 붙이면 다시 세느라 값이 잠깐\n * 비고, 펼쳐 둔 추이 그림 같은 상태도 날아간다.\n */\n@customElement('figure-side')\nexport class FigureSide extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n border-left: 1px solid var(--md-sys-color-outline-variant);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\n\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n /*\n **아이콘 하나가 한 칸을 쓴다.**\n\n 전에는 이름을 적었는데, 넷이 좁은 칸을 나눠 쓰면서 접혀 「성숙 / 도」가 되었다. 접히면\n 그 탭만 두 줄이 되어 높이가 서로 달라진다. 다섯이 되면서 더는 줄여 짜 넣을 수 없어\n 아이콘으로 바꿨고, 이름은 title 과 aria-label 로 남는다.\n\n 배지는 여전히 자리를 차지하지 않고 **아이콘 위에 겹친다**(아래 span[badge]). 개수는\n 곁들이는 것이다.\n */\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n /* 고른 탭을 밑줄로 말한다 — 칸을 색으로 채우면 아래 내용과 경계가 흐려진다 */\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n font-weight: 700;\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n\n /*\n 판정을 덮어 두어도 개수는 여기 남는다.\n\n 자리를 먹지 않게 오른쪽 위에 겹쳐 둔다 — 탭 이름을 밀지 않으므로 이름이 접히지 않는다.\n 숫자가 커져도(1,200 같은 것) 이름 위로 넘칠 뿐 줄을 바꾸지 않는다.\n */\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n /* 이름 위에 겹치되 위로 붙지 않게 — 탭 가운데 글자와 눈높이를 조금 맞춘다 */\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n\n figure-inspector,\n figure-animations,\n figure-report,\n figure-settings {\n flex: 1;\n min-height: 0;\n border-left: none;\n }\n /* 감추되 떼지는 않는다 */\n [off] {\n display: none;\n }\n `\n\n /*\n 두 형태가 이 패널에서 갈린다.\n\n 속성은 **수정하는** 곳이라 편집 중인 씬 모델을 받는다. 판정은 **저장될 것을 측정하는**\n 곳이라 변환된 FigureSource 를 받는다. 변환은 모델러 페이지가 한 번만 해서 내려보낸다.\n */\n @property({ type: String }) figureName = ''\n /** 카탈로그 분류·태그. 속성 탭이 보여 주고 바꾼다. */\n @property({ type: String }) figureCategory?: string\n @property({ type: Array }) figureTags?: string[]\n /** 이력 탭이 발행하고 되돌리는 데 필요한 것들. 정본은 저작면이 든다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n @property({ type: Boolean }) dirty = false\n\n @property({ type: Object }) draft?: FigureDraft\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Object }) source?: FigureSource\n @property({ type: Number }) selected = -1\n /** 저작 화면의 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다. */\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n\n @state() private tab: Tab = 'properties'\n /** 판정이 알려 준 개수. 탭 이름표에 붙는다. */\n @state() private instances?: number\n\n /**\n * 이 도형이 든 clip 수. 탭 아이콘에 붙는다.\n *\n * 아이콘만으로는 「여기에 무언가 있다」를 알 수 없다. 대부분의 도형은 움직이지 않으므로\n * 이 탭은 늘 비어 있을 것이고, 그러면 움직이는 도형을 열었을 때 그 사실이 눈에 걸려야 한다.\n * 0 이면 아무것도 안 붙인다 — 「0」을 붙이면 없는 것이 있는 것처럼 자리를 차지한다.\n */\n private get clipCount(): number | undefined {\n const n = (this.draft?.animations?.length ?? 0) + (this.draft?.parameters?.length ?? 0)\n return n === 0 ? undefined : n\n }\n\n render() {\n return html`\n <div tabs>\n ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}\n ${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}\n ${this.renderTab('animation', 'animation', i18next.t('figure.label.animation'), this.clipCount)}\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n\n <figure-inspector\n ?off=${this.tab !== 'properties'}\n .figureId=${this.figureId}\n .figureName=${this.figureName}\n .figureCategory=${this.figureCategory}\n .figureTags=${this.figureTags}\n .draft=${this.draft}\n .parts=${this.parts}\n .selected=${this.selected}\n ></figure-inspector>\n\n <figure-capabilities\n ?off=${this.tab !== 'capability'}\n .draft=${this.draft}\n .parts=${this.parts}\n ></figure-capabilities>\n\n <figure-animations\n ?off=${this.tab !== 'animation'}\n .draft=${this.draft}\n .parts=${this.parts}\n ></figure-animations>\n\n <figure-report\n ?off=${this.tab !== 'verdict'}\n .source=${this.source}\n @verdict-changed=${(e: CustomEvent) => (this.instances = e.detail.instances)}\n ></figure-report>\n\n <figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>\n\n <!--\n 이력은 감춰도 DOM 에 남긴다 — 다른 탭들과 같은 규칙이다. 떼었다 붙이면 판본을 다시 부른다.\n -->\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n .figureId=${this.figureId}\n .figureState=${this.figureState}\n .figureVersion=${this.figureVersion}\n .dirty=${this.dirty}\n ></figure-history-panel>\n `\n }\n\n /**\n * 탭 하나 — **아이콘이 이름을 대신한다.**\n *\n * 탭이 다섯이 되면서 이름이 좁은 칸을 나눠 쓰다 접혔다. 앞으로 더 늘어날 자리이므로 이름을\n * 줄여 짜 넣는 대신 아이콘으로 바꾼다.\n *\n * **이름을 버리지는 않는다.** 아이콘만으로는 무엇인지 모르는 사람이 있고, 특히 「능력」처럼\n * 이 도구에만 있는 개념이 그렇다. 그래서 이름은 `title` 로 남아 마우스를 올리면 나오고,\n * `aria-label` 로 읽는 기계에도 그대로 간다.\n */\n private renderTab(tab: Tab, icon: string, label: string, badge?: number) {\n return html`\n <button\n tab\n ?on=${this.tab === tab}\n title=${label}\n aria-label=${label}\n @click=${() => (this.tab = tab)}\n >\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-side.js","sourceRoot":"","sources":["../../client/modeller/figure-side.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport './figure-inspector.js'\nimport './figure-capabilities.js'\nimport './figure-animations.js'\nimport './figure-report.js'\nimport './figure-settings.js'\nimport './figure-history-panel.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { ContractChange, FigureSource } from '@hatiolab/figure-model'\n\nimport type { FigureDraft, PartModel } from './figure-source.js'\nimport { DEFAULT_VIEW } from './figure-view.js'\nimport type { ViewSettings } from './figure-view.js'\n\ntype Tab = 'properties' | 'capability' | 'animation' | 'verdict' | 'view' | 'history'\n\n/**\n * 저작면 오른쪽 칸 — 속성과 판정을 탭으로 겹쳐 둔다.\n *\n * ## 왜 위아래로 나누지 않나\n *\n * 둘 다 세로로 긴 화면이다. 위아래로 나누면 양쪽 다 잘려서, 속성은 재질까지 못\n * 내려가고 판정은 무게까지 못 내려간다. 탭으로 겹치면 각자 칸 전체를 쓴다.\n *\n * ## 그래도 개수는 늘 보인다\n *\n * 성숙도의 축 하나 —「몇 개까지 편한가」— 는 부품을 고칠 때마다 움직이고, 그것이\n * 보여야 저작자가 고칠 수 있다. 탭 뒤로 숨으면 저장하고 나서야 알게 된다. 그래서 그\n * 숫자만 **탭 이름표에 남긴다** — 성숙도를 덮어 두고 속성을 만져도 따라 움직이는\n * 것이 보인다.\n *\n * 판정은 화면에서 감춰도 **DOM 에는 남는다.** 떼었다 붙이면 다시 세느라 값이 잠깐\n * 비고, 펼쳐 둔 추이 그림 같은 상태도 날아간다.\n */\n@customElement('figure-side')\nexport class FigureSide extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n border-left: 1px solid var(--md-sys-color-outline-variant);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\n\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n /*\n **아이콘 하나가 한 칸을 쓴다.**\n\n 전에는 이름을 적었는데, 넷이 좁은 칸을 나눠 쓰면서 접혀 「성숙 / 도」가 되었다. 접히면\n 그 탭만 두 줄이 되어 높이가 서로 달라진다. 다섯이 되면서 더는 줄여 짜 넣을 수 없어\n 아이콘으로 바꿨고, 이름은 title 과 aria-label 로 남는다.\n\n 배지는 여전히 자리를 차지하지 않고 **아이콘 위에 겹친다**(아래 span[badge]). 개수는\n 곁들이는 것이다.\n */\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n /* 고른 탭을 밑줄로 말한다 — 칸을 색으로 채우면 아래 내용과 경계가 흐려진다 */\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n font-weight: 700;\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n\n /*\n 판정을 덮어 두어도 개수는 여기 남는다.\n\n 자리를 먹지 않게 오른쪽 위에 겹쳐 둔다 — 탭 이름을 밀지 않으므로 이름이 접히지 않는다.\n 숫자가 커져도(1,200 같은 것) 이름 위로 넘칠 뿐 줄을 바꾸지 않는다.\n */\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n /* 이름 위에 겹치되 위로 붙지 않게 — 탭 가운데 글자와 눈높이를 조금 맞춘다 */\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n\n figure-inspector,\n figure-animations,\n figure-report,\n figure-settings {\n flex: 1;\n min-height: 0;\n border-left: none;\n }\n /* 감추되 떼지는 않는다 */\n [off] {\n display: none;\n }\n `\n\n /*\n 두 형태가 이 패널에서 갈린다.\n\n 속성은 **수정하는** 곳이라 편집 중인 씬 모델을 받는다. 판정은 **저장될 것을 측정하는**\n 곳이라 변환된 FigureSource 를 받는다. 변환은 모델러 페이지가 한 번만 해서 내려보낸다.\n */\n @property({ type: String }) figureName = ''\n /** 카탈로그 분류·태그. 속성 탭이 보여 주고 바꾼다. */\n @property({ type: String }) figureCategory?: string\n @property({ type: Array }) figureTags?: string[]\n /** 이력 탭이 발행하고 되돌리는 데 필요한 것들. 정본은 저작면이 든다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n @property({ type: Boolean }) dirty = false\n\n @property({ type: Object }) draft?: FigureDraft\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Object }) source?: FigureSource\n\n /** 이 고침이 이미 놓인 인스턴스에 무엇을 하나(ADR-0063 결정 3). 발행 전 도형에는 비어 있다. */\n @property({ type: Array }) contract: ContractChange[] = []\n\n /** 견주지 못한 까닭. 있으면 화면이 「확인하지 못했다」로 말한다. */\n @property({ type: String }) contractFailure?: string\n @property({ type: Number }) selected = -1\n /** 저작 화면의 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다. */\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n\n @state() private tab: Tab = 'properties'\n /** 판정이 알려 준 개수. 탭 이름표에 붙는다. */\n @state() private instances?: number\n\n /**\n * 이 도형이 든 clip 수. 탭 아이콘에 붙는다.\n *\n * 아이콘만으로는 「여기에 무언가 있다」를 알 수 없다. 대부분의 도형은 움직이지 않으므로\n * 이 탭은 늘 비어 있을 것이고, 그러면 움직이는 도형을 열었을 때 그 사실이 눈에 걸려야 한다.\n * 0 이면 아무것도 안 붙인다 — 「0」을 붙이면 없는 것이 있는 것처럼 자리를 차지한다.\n */\n private get clipCount(): number | undefined {\n const n = (this.draft?.animations?.length ?? 0) + (this.draft?.parameters?.length ?? 0)\n return n === 0 ? undefined : n\n }\n\n render() {\n return html`\n <div tabs>\n ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}\n ${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}\n ${this.renderTab('animation', 'animation', i18next.t('figure.label.animation'), this.clipCount)}\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n\n <figure-inspector\n ?off=${this.tab !== 'properties'}\n .figureId=${this.figureId}\n .figureName=${this.figureName}\n .figureCategory=${this.figureCategory}\n .figureTags=${this.figureTags}\n .draft=${this.draft}\n .parts=${this.parts}\n .contract=${this.contract}\n .contractFailure=${this.contractFailure}\n .selected=${this.selected}\n ></figure-inspector>\n\n <figure-capabilities\n ?off=${this.tab !== 'capability'}\n .draft=${this.draft}\n .parts=${this.parts}\n ></figure-capabilities>\n\n <figure-animations\n ?off=${this.tab !== 'animation'}\n .draft=${this.draft}\n .parts=${this.parts}\n ></figure-animations>\n\n <figure-report\n ?off=${this.tab !== 'verdict'}\n .source=${this.source}\n @verdict-changed=${(e: CustomEvent) => (this.instances = e.detail.instances)}\n ></figure-report>\n\n <figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>\n\n <!--\n 이력은 감춰도 DOM 에 남긴다 — 다른 탭들과 같은 규칙이다. 떼었다 붙이면 판본을 다시 부른다.\n -->\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n .figureId=${this.figureId}\n .figureState=${this.figureState}\n .figureVersion=${this.figureVersion}\n .dirty=${this.dirty}\n ></figure-history-panel>\n `\n }\n\n /**\n * 탭 하나 — **아이콘이 이름을 대신한다.**\n *\n * 탭이 다섯이 되면서 이름이 좁은 칸을 나눠 쓰다 접혔다. 앞으로 더 늘어날 자리이므로 이름을\n * 줄여 짜 넣는 대신 아이콘으로 바꾼다.\n *\n * **이름을 버리지는 않는다.** 아이콘만으로는 무엇인지 모르는 사람이 있고, 특히 「능력」처럼\n * 이 도구에만 있는 개념이 그렇다. 그래서 이름은 `title` 로 남아 마우스를 올리면 나오고,\n * `aria-label` 로 읽는 기계에도 그대로 간다.\n */\n private renderTab(tab: Tab, icon: string, label: string, badge?: number) {\n return html`\n <button\n tab\n ?on=${this.tab === tab}\n title=${label}\n aria-label=${label}\n @click=${() => (this.tab = tab)}\n >\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
|
|
@@ -113,6 +113,12 @@ export interface FigureDraft {
|
|
|
113
113
|
* 주나**를 말하는 다른 칸이다.
|
|
114
114
|
*/
|
|
115
115
|
capabilities?: FigureSource['capabilities'];
|
|
116
|
+
/**
|
|
117
|
+
* How parts move relative to their parents (ADR-0066). Kept in figure coordinates as stored, the same
|
|
118
|
+
* terms the inspector shows. Carried even when nothing edits them, so opening and saving never drops
|
|
119
|
+
* them.
|
|
120
|
+
*/
|
|
121
|
+
joints?: FigureSource['joints'];
|
|
116
122
|
}
|
|
117
123
|
/** 부품 컴포넌트의 상태. 씬의 용어 그대로다. */
|
|
118
124
|
export interface PartModel {
|
|
@@ -150,6 +156,13 @@ export interface PartModel {
|
|
|
150
156
|
* `toFigureSource` 가 싣지 않고, 다시 열면 전부 보이는 상태로 돌아온다.
|
|
151
157
|
*/
|
|
152
158
|
hidden?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* 씬이 이것을 **집지 않는다** — 클릭·마퀴·기즈모 어디에도 안 걸린다.
|
|
161
|
+
*
|
|
162
|
+
* 저작면에만 서는 것(관절 부채꼴)이 쓴다. 저장 형식에는 없는 칸이라 `toFigureSource` 가
|
|
163
|
+
* 싣지 않는다.
|
|
164
|
+
*/
|
|
165
|
+
locked?: boolean;
|
|
153
166
|
/** 색 토큰 이름 (`palette.primary` 등). */
|
|
154
167
|
token?: string;
|
|
155
168
|
preset?: FigurePart['material']['preset'];
|
|
@@ -178,6 +191,8 @@ export interface PartModel {
|
|
|
178
191
|
* 청사진에서는 `groups` 가 아니라 `anchors` 로 간다.
|
|
179
192
|
*/
|
|
180
193
|
capability?: FigurePart['capability'];
|
|
194
|
+
/** The part this one is attached to (ADR-0066). The one place a parent is written. */
|
|
195
|
+
parent?: FigurePart['parent'];
|
|
181
196
|
}
|
|
182
197
|
/** 소수점 넷째 자리까지. 라디안↔도를 오가며 생기는 꼬리를 자른다. */
|
|
183
198
|
export declare function round4(value: number): number;
|
|
@@ -77,7 +77,8 @@ export function toFigureSource(draft, parts) {
|
|
|
77
77
|
parts: parts.map(part => partTo(part, half)),
|
|
78
78
|
animations: draft.animations,
|
|
79
79
|
parameters: draft.parameters,
|
|
80
|
-
capabilities: draft.capabilities
|
|
80
|
+
capabilities: draft.capabilities,
|
|
81
|
+
joints: draft.joints?.length ? draft.joints : undefined
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
84
|
function partTo(part, half) {
|
|
@@ -123,7 +124,8 @@ function partTo(part, half) {
|
|
|
123
124
|
anchor: part.anchor,
|
|
124
125
|
repeat: part.repeat,
|
|
125
126
|
label: part.label,
|
|
126
|
-
capability: part.capability
|
|
127
|
+
capability: part.capability,
|
|
128
|
+
parent: part.parent
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
131
|
/** 형식을 씬 모델로 편다. */
|
|
@@ -141,7 +143,8 @@ export function fromFigureSource(source) {
|
|
|
141
143
|
/* Opened as stored. A legacy `drive: 'hold'` clip is refused by the format now; stored ones were migrated. */
|
|
142
144
|
animations: source.animations?.length ? source.animations : undefined,
|
|
143
145
|
parameters: source.parameters?.length ? source.parameters : undefined,
|
|
144
|
-
capabilities: source.capabilities
|
|
146
|
+
capabilities: source.capabilities,
|
|
147
|
+
joints: source.joints?.length ? source.joints : undefined
|
|
145
148
|
});
|
|
146
149
|
return { draft, parts: source.parts.map(part => partFrom(part, half)) };
|
|
147
150
|
}
|
|
@@ -183,7 +186,8 @@ function partFrom(part, half) {
|
|
|
183
186
|
anchor: part.anchor,
|
|
184
187
|
repeat: part.repeat,
|
|
185
188
|
label: part.label,
|
|
186
|
-
capability: part.capability
|
|
189
|
+
capability: part.capability,
|
|
190
|
+
parent: part.parent
|
|
187
191
|
});
|
|
188
192
|
}
|
|
189
193
|
//# sourceMappingURL=figure-source.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-source.js","sourceRoot":"","sources":["../../client/modeller/figure-source.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EAMtB,MAAM,wBAAwB,CAAA;AAuM/B,MAAM,UAAU,GAAG,GAAG,GAAG,IAAI,CAAC,EAAE,CAAA;AAChC,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,GAAG,GAAG,CAAA;AAEhC,+DAA+D;AAC/D,SAAS,YAAY,CAAmB,KAAQ;IAC9C,MAAM,GAAG,GAAG,EAA6B,CAAA;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,IAAI,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;IACzC,CAAC;IACD,OAAO,GAAQ,CAAA;AACjB,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,KAAkB;IACvC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAA;AACxE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;IAChC,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;IAChC,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE;CACxB,CAAA;AAIV,wBAAwB;AACxB,MAAM,UAAU,WAAW,CAAC,KAAkB,EAAE,IAAe;IAC7D,OAAO,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAA;AAC/E,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,YAAY,CAAC,IAAe,EAAE,KAAkB;IAC9D,OAAO,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AACpC,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,cAAc,CAAC,IAAgB,EAAE,KAAkB;IACjE,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AACtC,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAE1B,OAAO,YAAY,CAAC;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,qBAAqB;QAC/C,IAAI,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;QAC5B,IAAI,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE;QACzD,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5C,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAiB,CAAA;AACpB,CAAC;AAED,SAAS,MAAM,CAAC,IAAe,EAAE,IAAU;IACzC,MAAM,QAAQ,GAAG,YAAY,CAAC;QAC5B,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;QACnE,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;QAClE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;KACpE,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;IACvF;;;;;;MAME;IACF,MAAM,QAAQ,GAAG,YAAY,CAAC;QAC5B,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAA;IAEF,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,YAAY,CAAC;YACtB,QAAQ,EAAE;gBACR,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;gBAC9C,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;gBACrC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;aAC/C;YACD,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE;YACtD,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;SAClE,CAAC;QACF,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QACxD,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QACjE,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAe,CAAA;AAClB,CAAC;AAED,oBAAoB;AACpB,MAAM,UAAU,gBAAgB,CAAC,MAAoB;IACnD,MAAM,IAAI,GAAS,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAA;IAEvF,MAAM,KAAK,GAAG,YAAY,CAAC;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC,IAAI;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,8GAA8G;QAC9G,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QACrE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QACrE,YAAY,EAAE,MAAM,CAAC,YAAY;KAClC,CAAgB,CAAA;IAEjB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAA;AACzE,CAAC;AAED,SAAS,QAAQ,CAAC,IAAgB,EAAE,IAAU;IAC5C,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAA;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAA;IAEpC,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,SAAS;QAEzB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC9C,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC7C,KAAK,EAAE,IAAI,CAAC,CAAC;QACb,MAAM,EAAE,IAAI,CAAC,CAAC;QACd,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACrC,KAAK,EAAE,IAAI,CAAC,CAAC;QAEb;;;;;UAKE;QACF,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,SAAS;QAC5D,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,SAAS;QAC5D,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,SAAS;QAE5D,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK;QACxB,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI;QACtB,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM;QAE1B,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,OAAO,EAAE,QAAQ,CAAC,OAAO;QAEzB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAc,CAAA;AACjB,CAAC","sourcesContent":["import {\n FIGURE_SOURCE_VERSION,\n type DetailLevel,\n type FigurePart,\n type FigurePlacement,\n type FigureSource,\n type Vec3\n} from '@hatiolab/figure-model'\n\n/**\n * Scene model <-> `FigureSource`.\n *\n * ## Why this is the only place that converts\n *\n * While authoring, the source of truth is the **scene model** -- part\n * components sitting on a draft. `FigureSource` is produced from it when the\n * asset is saved or scored.\n *\n * The two use different vocabularies. The scene inherited 2D canvas terms\n * (`width`/`height` in plan, `depth` for height, `left`/`top`/`zPos` for\n * position); the format uses the axes 3D tools use (`x` right, `y` up, `z`\n * front, position at the part's **centre**).\n *\n * **Convert in one place.** Two places drift, and this format has already had\n * one incident caused by axes disagreeing.\n *\n * ## It was called `fold`, and that was wrong\n *\n * `fold` is an established term -- it reduces a structure to a summary, and\n * this repo already uses it correctly (`foldKpi`, `foldProbeResults`). Nothing\n * is reduced here: the same information comes out in a different shape, and it\n * goes back unchanged. A reader who learned `fold` from `foldKpi` reads this\n * one as reducing something, and the user did exactly that.\n *\n * ## The mapping\n *\n * The draft is the base box; its centre is the format's origin.\n *\n * format scene\n * ──────────────── ──────────────────────────────\n * base.x draft width\n * base.y draft depth <- height\n * base.z draft height\n *\n * size.x width\n * size.y depth\n * size.z height\n *\n * position.x left + width/2 - draft width/2\n * position.y zPos + depth/2 <- both measured from the box bottom\n * position.z top + height/2 - draft height/2\n *\n * (Source edition 2 measures y from the bottom face of the base box, as the scene's zPos does, so\n * that row needs no half-box shift. x and z are still measured from the box centre. ADR-0065.)\n *\n * rotation.x rotationX (scene radians, format degrees)\n * rotation.y -rotation\n * rotation.z rotationY\n *\n * The rotation names are crossed because of the scene: `rotationY` becomes\n * three's z (`real-object.ts`, `get rotation()`). That stays inside this table.\n *\n * ## Fields nothing reads yet still travel\n *\n * `materialSlot`, `keepRound`, `sizing`, `anchor`, `repeat` and `label` exist in the format\n * and the editor ignores them. They still have to survive a round trip. When a\n * source proposed by AI is opened, edited and written back, a field that\n * disappears **without an error** is one the author cannot know about.\n *\n * **Five have gone that way here**: `ports`, `animations`, `anchor`,\n * `shape.hollow` and `keepRound`. The last one the test caught first, which\n * is the guard below actually earning its keep. The third showed up as a conveyor motor that stayed on the\n * floor no matter what the sample said; the fourth as a hollowed tray that\n * came out a solid block.\n *\n * All four passed the round-trip test, because the fixture it uses did not\n * carry the field. So the test now reads the **field list the format declares**\n * rather than trusting the fixture.\n *\n * `shape` is unpacked into `round`, `path` and `hollow` rather than carried\n * whole, because the editor holds those as separate state. Every field added\n * under `shape` costs a line here, and forgetting that line was the fourth\n * of the four.\n */\n/** 판 컴포넌트의 상태. 씬의 용어 그대로다. */\nexport interface FigureDraft {\n /** 형식 버전. */\n version?: number\n /*\n The three sizes keep the scene component's names, and the scene assigns its axes differently\n from the format: here `depth` is the vertical one. Naming the axis alone (\"the y axis\") does\n not say which of the two conventions is meant, and this interface sits on the boundary between\n them — so each field names the `FigureSource` field it becomes. `toFigureSource` and\n `fromFigureSource` below are the only places that may disagree with these three lines.\n */\n /** 가로 너비 (mm) → `base.x`. */\n width: number\n /** 앞뒤 깊이 (mm) → `base.z`. 저작자가 위에서 내려다볼 때의 세로다. */\n height: number\n /** 높이 (mm) → `base.y`. 서 있는 방향이다. */\n depth: number\n /** 컴포넌트 타입 이름. **저장되는 식별자다.** */\n figureType?: string\n /** 3D 배치 기준면 ('floor' | 'ceiling' | 'center'). */\n placement?: FigurePlacement\n detailLevel?: DetailLevel\n styleKit?: string\n /**\n * 움직임. **부품 바깥에 있다** — clip 하나가 부품 여럿을 함께 움직이기 때문이다.\n *\n * 편집면이 아직 이것을 만지지 않는다. 그래도 여기 실어 두는 것은, 안 실으면 열었다\n * 저장하는 것만으로 **조용히 지워지기** 때문이다.\n */\n animations?: FigureSource['animations']\n /**\n * 값이 만드는 자세 — **애니메이션과 다른 칸이다.**\n *\n * 저것은 시각이 흘러 움직이는 것(도는 롤러)이고 이것은 인스턴스가 준 값이 그대로 자세가\n * 되는 것(1200mm 내려온 호이스트)이다. 한동안 애니메이션 안에 `drive: 'hold'` 로 숨어\n * 있었고, 그래서 「길이값을 변수로 노출하라」는 요청에 아무도 이것을 못 찾았다(ADR-0051).\n */\n parameters?: FigureSource['parameters']\n /**\n * 이 도형이 지는 능력. 씬의 capability mixin 이름이다.\n *\n * **부품이 아니라 도형 전체의 것이다** — 「이 컨베이어는 흐름의 마디다」는 부품 하나가\n * 아니라 도형이 지는 성질이다. 부품 쪽 `capability` 는 그 능력을 **어느 부품이 받쳐\n * 주나**를 말하는 다른 칸이다.\n */\n capabilities?: FigureSource['capabilities']\n}\n\n/** 부품 컴포넌트의 상태. 씬의 용어 그대로다. */\nexport interface PartModel {\n type: 'figure-part'\n /** 부품 이름. **저장되는 식별자다.** 씬은 이 값을 검색에도 쓴다. */\n name: string\n primitive: FigurePart['primitive']\n\n left: number\n top: number\n width: number\n height: number\n /** 부피의 **밑면** 높이. 씬의 'floor' 배치에서 그렇게 정해져 있다. */\n zPos: number\n depth: number\n\n /** 라디안. 씬이 그렇게 쓴다. */\n rotationX?: number\n rotation?: number\n rotationY?: number\n\n segments?: number\n round?: number\n path?: { x: number; y: number }[]\n /** 속을 판 단면. 편집면이 아직 안 만지지만 잃어버리면 통이 통짜 상자가 된다. */\n hollow?: NonNullable<FigurePart['shape']>['hollow']\n\n /**\n * 편집 중에만 쓰는 상태 — **저장 형식으로 넘어가지 않는다.**\n *\n * 씬이 이미 갖고 있는 속성이다. 편집 모드에서는 `faint`(반투명 + 색을 버리고 회색으로\n * 수렴)로 보이고, 뷰 모드에서는 아예 안 그려진다. 판단은 씬의 `modeling-faint` 한 곳에\n * 있고 2D 캔버스·3D 재질·DOM 세 채널이 같이 쓴다.\n *\n * 숨김은 \"지금 이 뒤를 만지려고\" 하는 것이지 그 부품의 성질이 아니다. 그래서\n * `toFigureSource` 가 싣지 않고, 다시 열면 전부 보이는 상태로 돌아온다.\n */\n hidden?: boolean\n\n /** 색 토큰 이름 (`palette.primary` 등). */\n token?: string\n /*\n `FigurePart['material']` is required in the format, so these index into it directly. The\n `NonNullable` on `shape` above is not the same case — `shape` is optional there.\n */\n preset?: FigurePart['material']['preset']\n flatShading?: boolean\n transparent?: boolean\n /**\n * 자체발광. 없으면 안 빛난다.\n *\n * 바깥의 `token` 이 **꺼진 모습**이고 이 안의 `token` 이 **켜진 모습**이다 — 꺼진\n * 전구는 빨갛지 않고 어두운 플라스틱이다.\n */\n emissive?: FigurePart['material']['emissive']\n surface?: FigurePart['material']['surface']\n\n /* 아직 아무 일도 안 하지만 잃어버리지 않는다. */\n materialSlot?: FigurePart['materialSlot']\n /** 단면을 원으로 지킬까. 곡면만 뜻이 있고 기본은 지키는 것이다. */\n keepRound?: FigurePart['keepRound']\n sizing?: FigurePart['sizing']\n /** 축마다 어느 면을 붙잡나. `sizing` 이 정한 것을 덮어쓴다. */\n anchor?: FigurePart['anchor']\n repeat?: FigurePart['repeat']\n label?: FigurePart['label']\n /**\n * 이 부품이 능력의 어느 자리인가 — 물건이 앉는 자리(slot)이거나 드나드는 문(port)이다.\n *\n * 이 칸이 있는 부품은 **그려지지 않는다.** 저작면에서는 어디인지 보여야 하니 그리지만,\n * 청사진에서는 `groups` 가 아니라 `anchors` 로 간다.\n */\n capability?: FigurePart['capability']\n}\n\nconst RAD_TO_DEG = 180 / Math.PI\nconst DEG_TO_RAD = Math.PI / 180\n\n/** 값이 있는 것만 남긴다. 없는 필드를 `undefined` 로 두면 접었다 편 것이 원본과 달라진다. */\nfunction withoutEmpty<T extends object>(value: T): T {\n const out = {} as Record<string, unknown>\n for (const [key, item] of Object.entries(value)) {\n if (item !== undefined) out[key] = item\n }\n return out as T\n}\n\n/** 소수점 넷째 자리까지. 라디안↔도를 오가며 생기는 꼬리를 자른다. */\nexport function round4(value: number): number {\n return Math.round(value * 1e4) / 1e4\n}\n\n/**\n * 판의 절반. 형식의 원점이 판 중심이라 이 값이 늘 함께 다닌다.\n *\n * 축 이름은 **형식의 것**이다 — x 가로 · y 높이 · z 세로.\n */\nexport function halfOf(draft: FigureDraft): Vec3 {\n return { x: draft.width / 2, y: draft.depth / 2, z: draft.height / 2 }\n}\n\n/*\n * ── The placement is not in these numbers ────────────────────────────────────\n *\n * `placement` says which reference surface a figure of this kind attaches to — a conveyor stands\n * on the floor, an OHT hangs from the ceiling. That is a fact about the figure *type*, and the\n * scene acts on it when an instance is placed: `floor` puts the box on the ground, `ceiling` at\n * the ceiling height less the figure's own height. Nothing about the authored shape changes.\n *\n * So the conversion below does not read it, and the editing canvas does not either. The box is\n * always `0 … base.y` there and `zPos` is always the part's underside. Two figures with identical\n * parts and different placements have identical `parts` arrays, which is what the format says.\n *\n * This used to be wired the other way: the canvas fed the figure's placement to the model layer's\n * `state.placement`, which is a scene-wide coordinate mode ('floor' | 'space' | 'inverted') that\n * flips what `zPos` names for everything in that layer. It stood in for the archetype because the\n * modeller holds exactly one figure — and it cost a whole day of defects, because every number the\n * author typed then meant a different face depending on a dropdown. That coordinate mode is now\n * where it belongs: a workbench setting in `figure-view.ts`, applied to the preview alone.\n */\n\n/**\n * 저작자가 보는 축이 씬의 어느 용어인가.\n *\n * 씬은 2D 캔버스에서 온 용어를 쓰므로 축 이름이 그대로 없다. 이 표가 **유일한** 대응이다 —\n * 편집 규칙이 저마다 자기 표를 들면 한 곳을 고칠 때 다른 곳이 남는다.\n */\nexport const AXIS_KEYS = {\n x: { at: 'left', size: 'width' },\n y: { at: 'zPos', size: 'depth' },\n z: { at: 'top', size: 'height' }\n} as const\n\nexport type PlaneAxis = keyof typeof AXIS_KEYS\n\n/** 판이 그 축으로 얼마나 넓은가. */\nexport function draftExtent(draft: FigureDraft, axis: PlaneAxis): number {\n return axis === 'x' ? draft.width : axis === 'y' ? draft.depth : draft.height\n}\n\n/** 부품 하나를 저장 형식으로 변환한다. 저작 화면이 저작자의 어휘로 보일 때 쓴다. */\nexport function partToFigure(part: PartModel, draft: FigureDraft): FigurePart {\n return partTo(part, halfOf(draft))\n}\n\n/** 부품 하나를 씬 용어로 되돌린다. */\nexport function partFromFigure(part: FigurePart, draft: FigureDraft): PartModel {\n return partFrom(part, halfOf(draft))\n}\n\n/** 씬 모델을 저장 형식으로 변환한다. */\nexport function toFigureSource(draft: FigureDraft, parts: PartModel[]): FigureSource {\n const half = halfOf(draft)\n\n return withoutEmpty({\n version: draft.version ?? FIGURE_SOURCE_VERSION,\n type: draft.figureType ?? '',\n base: { x: draft.width, y: draft.depth, z: draft.height },\n placement: draft.placement,\n detailLevel: draft.detailLevel,\n styleKit: draft.styleKit,\n parts: parts.map(part => partTo(part, half)),\n animations: draft.animations,\n parameters: draft.parameters,\n capabilities: draft.capabilities\n }) as FigureSource\n}\n\nfunction partTo(part: PartModel, half: Vec3): FigurePart {\n const rotation = withoutEmpty({\n x: part.rotationX ? round4(part.rotationX * RAD_TO_DEG) : undefined,\n y: part.rotation ? round4(-part.rotation * RAD_TO_DEG) : undefined,\n z: part.rotationY ? round4(part.rotationY * RAD_TO_DEG) : undefined\n })\n\n const shape = withoutEmpty({ round: part.round, path: part.path, hollow: part.hollow })\n /*\n 재질이 하나도 없으면 **칸 자체를 안 적는다.**\n\n 빈 객체를 적으면 형식이 「재질은 있는데 무엇으로 칠할지 모른다」로 읽어\n `material-unresolvable` 을 낸다. 그리지 않는 자리(`capability`)에는 재질이 없는 것이\n 정상이고, 그 부품이 조용히 오류를 달고 다니게 된다.\n */\n const material = withoutEmpty({\n token: part.token,\n preset: part.preset,\n flatShading: part.flatShading,\n transparent: part.transparent,\n emissive: part.emissive,\n surface: part.surface\n })\n\n return withoutEmpty({\n name: part.name,\n primitive: part.primitive,\n transform: withoutEmpty({\n position: {\n x: round4(part.left + part.width / 2 - half.x),\n y: round4(part.zPos + part.depth / 2),\n z: round4(part.top + part.height / 2 - half.z)\n },\n size: { x: part.width, y: part.depth, z: part.height },\n rotation: Object.keys(rotation).length > 0 ? rotation : undefined\n }),\n segments: part.segments,\n shape: Object.keys(shape).length > 0 ? shape : undefined,\n material: Object.keys(material).length > 0 ? material : undefined,\n materialSlot: part.materialSlot,\n keepRound: part.keepRound,\n sizing: part.sizing,\n anchor: part.anchor,\n repeat: part.repeat,\n label: part.label,\n capability: part.capability\n }) as FigurePart\n}\n\n/** 형식을 씬 모델로 편다. */\nexport function fromFigureSource(source: FigureSource): { draft: FigureDraft; parts: PartModel[] } {\n const half: Vec3 = { x: source.base.x / 2, y: source.base.y / 2, z: source.base.z / 2 }\n\n const draft = withoutEmpty({\n version: source.version,\n width: source.base.x,\n height: source.base.z,\n depth: source.base.y,\n figureType: source.type,\n placement: source.placement,\n detailLevel: source.detailLevel,\n styleKit: source.styleKit,\n /* Opened as stored. A legacy `drive: 'hold'` clip is refused by the format now; stored ones were migrated. */\n animations: source.animations?.length ? source.animations : undefined,\n parameters: source.parameters?.length ? source.parameters : undefined,\n capabilities: source.capabilities\n }) as FigureDraft\n\n return { draft, parts: source.parts.map(part => partFrom(part, half)) }\n}\n\nfunction partFrom(part: FigurePart, half: Vec3): PartModel {\n const { position, size, rotation } = part.transform\n const material = part.material ?? {}\n\n return withoutEmpty({\n type: 'figure-part',\n name: part.name,\n primitive: part.primitive,\n\n left: round4(half.x + position.x - size.x / 2),\n top: round4(half.z + position.z - size.z / 2),\n width: size.x,\n height: size.z,\n zPos: round4(position.y - size.y / 2),\n depth: size.y,\n\n /*\n 라디안은 자르지 않는다.\n\n 소수 넷째에서 잘랐더니 도로 되돌릴 때 57 배로 커져 45 도가 45.0001 이 됐다.\n 자르는 것은 **사람이 읽는 쪽**(도)에서만 한다.\n */\n rotationX: rotation?.x ? rotation.x * DEG_TO_RAD : undefined,\n rotation: rotation?.y ? -rotation.y * DEG_TO_RAD : undefined,\n rotationY: rotation?.z ? rotation.z * DEG_TO_RAD : undefined,\n\n segments: part.segments,\n round: part.shape?.round,\n path: part.shape?.path,\n hollow: part.shape?.hollow,\n\n token: material.token,\n preset: material.preset,\n flatShading: material.flatShading,\n transparent: material.transparent,\n emissive: material.emissive,\n surface: material.surface,\n\n materialSlot: part.materialSlot,\n keepRound: part.keepRound,\n sizing: part.sizing,\n anchor: part.anchor,\n repeat: part.repeat,\n label: part.label,\n capability: part.capability\n }) as PartModel\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-source.js","sourceRoot":"","sources":["../../client/modeller/figure-source.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n FIGURE_SOURCE_VERSION,\n type DetailLevel,\n type FigurePart,\n type FigurePlacement,\n type FigureSource,\n type Vec3\n} from '@hatiolab/figure-model'\n\n/**\n * Scene model <-> `FigureSource`.\n *\n * ## Why this is the only place that converts\n *\n * While authoring, the source of truth is the **scene model** -- part\n * components sitting on a draft. `FigureSource` is produced from it when the\n * asset is saved or scored.\n *\n * The two use different vocabularies. The scene inherited 2D canvas terms\n * (`width`/`height` in plan, `depth` for height, `left`/`top`/`zPos` for\n * position); the format uses the axes 3D tools use (`x` right, `y` up, `z`\n * front, position at the part's **centre**).\n *\n * **Convert in one place.** Two places drift, and this format has already had\n * one incident caused by axes disagreeing.\n *\n * ## It was called `fold`, and that was wrong\n *\n * `fold` is an established term -- it reduces a structure to a summary, and\n * this repo already uses it correctly (`foldKpi`, `foldProbeResults`). Nothing\n * is reduced here: the same information comes out in a different shape, and it\n * goes back unchanged. A reader who learned `fold` from `foldKpi` reads this\n * one as reducing something, and the user did exactly that.\n *\n * ## The mapping\n *\n * The draft is the base box; its centre is the format's origin.\n *\n * format scene\n * ──────────────── ──────────────────────────────\n * base.x draft width\n * base.y draft depth <- height\n * base.z draft height\n *\n * size.x width\n * size.y depth\n * size.z height\n *\n * position.x left + width/2 - draft width/2\n * position.y zPos + depth/2 <- both measured from the box bottom\n * position.z top + height/2 - draft height/2\n *\n * (Source edition 2 measures y from the bottom face of the base box, as the scene's zPos does, so\n * that row needs no half-box shift. x and z are still measured from the box centre. ADR-0065.)\n *\n * rotation.x rotationX (scene radians, format degrees)\n * rotation.y -rotation\n * rotation.z rotationY\n *\n * The rotation names are crossed because of the scene: `rotationY` becomes\n * three's z (`real-object.ts`, `get rotation()`). That stays inside this table.\n *\n * ## Fields nothing reads yet still travel\n *\n * `materialSlot`, `keepRound`, `sizing`, `anchor`, `repeat` and `label` exist in the format\n * and the editor ignores them. They still have to survive a round trip. When a\n * source proposed by AI is opened, edited and written back, a field that\n * disappears **without an error** is one the author cannot know about.\n *\n * **Five have gone that way here**: `ports`, `animations`, `anchor`,\n * `shape.hollow` and `keepRound`. The last one the test caught first, which\n * is the guard below actually earning its keep. The third showed up as a conveyor motor that stayed on the\n * floor no matter what the sample said; the fourth as a hollowed tray that\n * came out a solid block.\n *\n * All four passed the round-trip test, because the fixture it uses did not\n * carry the field. So the test now reads the **field list the format declares**\n * rather than trusting the fixture.\n *\n * `shape` is unpacked into `round`, `path` and `hollow` rather than carried\n * whole, because the editor holds those as separate state. Every field added\n * under `shape` costs a line here, and forgetting that line was the fourth\n * of the four.\n */\n/** 판 컴포넌트의 상태. 씬의 용어 그대로다. */\nexport interface FigureDraft {\n /** 형식 버전. */\n version?: number\n /*\n The three sizes keep the scene component's names, and the scene assigns its axes differently\n from the format: here `depth` is the vertical one. Naming the axis alone (\"the y axis\") does\n not say which of the two conventions is meant, and this interface sits on the boundary between\n them — so each field names the `FigureSource` field it becomes. `toFigureSource` and\n `fromFigureSource` below are the only places that may disagree with these three lines.\n */\n /** 가로 너비 (mm) → `base.x`. */\n width: number\n /** 앞뒤 깊이 (mm) → `base.z`. 저작자가 위에서 내려다볼 때의 세로다. */\n height: number\n /** 높이 (mm) → `base.y`. 서 있는 방향이다. */\n depth: number\n /** 컴포넌트 타입 이름. **저장되는 식별자다.** */\n figureType?: string\n /** 3D 배치 기준면 ('floor' | 'ceiling' | 'center'). */\n placement?: FigurePlacement\n detailLevel?: DetailLevel\n styleKit?: string\n /**\n * 움직임. **부품 바깥에 있다** — clip 하나가 부품 여럿을 함께 움직이기 때문이다.\n *\n * 편집면이 아직 이것을 만지지 않는다. 그래도 여기 실어 두는 것은, 안 실으면 열었다\n * 저장하는 것만으로 **조용히 지워지기** 때문이다.\n */\n animations?: FigureSource['animations']\n /**\n * 값이 만드는 자세 — **애니메이션과 다른 칸이다.**\n *\n * 저것은 시각이 흘러 움직이는 것(도는 롤러)이고 이것은 인스턴스가 준 값이 그대로 자세가\n * 되는 것(1200mm 내려온 호이스트)이다. 한동안 애니메이션 안에 `drive: 'hold'` 로 숨어\n * 있었고, 그래서 「길이값을 변수로 노출하라」는 요청에 아무도 이것을 못 찾았다(ADR-0051).\n */\n parameters?: FigureSource['parameters']\n /**\n * 이 도형이 지는 능력. 씬의 capability mixin 이름이다.\n *\n * **부품이 아니라 도형 전체의 것이다** — 「이 컨베이어는 흐름의 마디다」는 부품 하나가\n * 아니라 도형이 지는 성질이다. 부품 쪽 `capability` 는 그 능력을 **어느 부품이 받쳐\n * 주나**를 말하는 다른 칸이다.\n */\n capabilities?: FigureSource['capabilities']\n /**\n * How parts move relative to their parents (ADR-0066). Kept in figure coordinates as stored, the same\n * terms the inspector shows. Carried even when nothing edits them, so opening and saving never drops\n * them.\n */\n joints?: FigureSource['joints']\n}\n\n/** 부품 컴포넌트의 상태. 씬의 용어 그대로다. */\nexport interface PartModel {\n type: 'figure-part'\n /** 부품 이름. **저장되는 식별자다.** 씬은 이 값을 검색에도 쓴다. */\n name: string\n primitive: FigurePart['primitive']\n\n left: number\n top: number\n width: number\n height: number\n /** 부피의 **밑면** 높이. 씬의 'floor' 배치에서 그렇게 정해져 있다. */\n zPos: number\n depth: number\n\n /** 라디안. 씬이 그렇게 쓴다. */\n rotationX?: number\n rotation?: number\n rotationY?: number\n\n segments?: number\n round?: number\n path?: { x: number; y: number }[]\n /** 속을 판 단면. 편집면이 아직 안 만지지만 잃어버리면 통이 통짜 상자가 된다. */\n hollow?: NonNullable<FigurePart['shape']>['hollow']\n\n /**\n * 편집 중에만 쓰는 상태 — **저장 형식으로 넘어가지 않는다.**\n *\n * 씬이 이미 갖고 있는 속성이다. 편집 모드에서는 `faint`(반투명 + 색을 버리고 회색으로\n * 수렴)로 보이고, 뷰 모드에서는 아예 안 그려진다. 판단은 씬의 `modeling-faint` 한 곳에\n * 있고 2D 캔버스·3D 재질·DOM 세 채널이 같이 쓴다.\n *\n * 숨김은 \"지금 이 뒤를 만지려고\" 하는 것이지 그 부품의 성질이 아니다. 그래서\n * `toFigureSource` 가 싣지 않고, 다시 열면 전부 보이는 상태로 돌아온다.\n */\n hidden?: boolean\n\n /**\n * 씬이 이것을 **집지 않는다** — 클릭·마퀴·기즈모 어디에도 안 걸린다.\n *\n * 저작면에만 서는 것(관절 부채꼴)이 쓴다. 저장 형식에는 없는 칸이라 `toFigureSource` 가\n * 싣지 않는다.\n */\n locked?: boolean\n\n /** 색 토큰 이름 (`palette.primary` 등). */\n token?: string\n /*\n `FigurePart['material']` is required in the format, so these index into it directly. The\n `NonNullable` on `shape` above is not the same case — `shape` is optional there.\n */\n preset?: FigurePart['material']['preset']\n flatShading?: boolean\n transparent?: boolean\n /**\n * 자체발광. 없으면 안 빛난다.\n *\n * 바깥의 `token` 이 **꺼진 모습**이고 이 안의 `token` 이 **켜진 모습**이다 — 꺼진\n * 전구는 빨갛지 않고 어두운 플라스틱이다.\n */\n emissive?: FigurePart['material']['emissive']\n surface?: FigurePart['material']['surface']\n\n /* 아직 아무 일도 안 하지만 잃어버리지 않는다. */\n materialSlot?: FigurePart['materialSlot']\n /** 단면을 원으로 지킬까. 곡면만 뜻이 있고 기본은 지키는 것이다. */\n keepRound?: FigurePart['keepRound']\n sizing?: FigurePart['sizing']\n /** 축마다 어느 면을 붙잡나. `sizing` 이 정한 것을 덮어쓴다. */\n anchor?: FigurePart['anchor']\n repeat?: FigurePart['repeat']\n label?: FigurePart['label']\n /**\n * 이 부품이 능력의 어느 자리인가 — 물건이 앉는 자리(slot)이거나 드나드는 문(port)이다.\n *\n * 이 칸이 있는 부품은 **그려지지 않는다.** 저작면에서는 어디인지 보여야 하니 그리지만,\n * 청사진에서는 `groups` 가 아니라 `anchors` 로 간다.\n */\n capability?: FigurePart['capability']\n /** The part this one is attached to (ADR-0066). The one place a parent is written. */\n parent?: FigurePart['parent']\n}\n\nconst RAD_TO_DEG = 180 / Math.PI\nconst DEG_TO_RAD = Math.PI / 180\n\n/** 값이 있는 것만 남긴다. 없는 필드를 `undefined` 로 두면 접었다 편 것이 원본과 달라진다. */\nfunction withoutEmpty<T extends object>(value: T): T {\n const out = {} as Record<string, unknown>\n for (const [key, item] of Object.entries(value)) {\n if (item !== undefined) out[key] = item\n }\n return out as T\n}\n\n/** 소수점 넷째 자리까지. 라디안↔도를 오가며 생기는 꼬리를 자른다. */\nexport function round4(value: number): number {\n return Math.round(value * 1e4) / 1e4\n}\n\n/**\n * 판의 절반. 형식의 원점이 판 중심이라 이 값이 늘 함께 다닌다.\n *\n * 축 이름은 **형식의 것**이다 — x 가로 · y 높이 · z 세로.\n */\nexport function halfOf(draft: FigureDraft): Vec3 {\n return { x: draft.width / 2, y: draft.depth / 2, z: draft.height / 2 }\n}\n\n/*\n * ── The placement is not in these numbers ────────────────────────────────────\n *\n * `placement` says which reference surface a figure of this kind attaches to — a conveyor stands\n * on the floor, an OHT hangs from the ceiling. That is a fact about the figure *type*, and the\n * scene acts on it when an instance is placed: `floor` puts the box on the ground, `ceiling` at\n * the ceiling height less the figure's own height. Nothing about the authored shape changes.\n *\n * So the conversion below does not read it, and the editing canvas does not either. The box is\n * always `0 … base.y` there and `zPos` is always the part's underside. Two figures with identical\n * parts and different placements have identical `parts` arrays, which is what the format says.\n *\n * This used to be wired the other way: the canvas fed the figure's placement to the model layer's\n * `state.placement`, which is a scene-wide coordinate mode ('floor' | 'space' | 'inverted') that\n * flips what `zPos` names for everything in that layer. It stood in for the archetype because the\n * modeller holds exactly one figure — and it cost a whole day of defects, because every number the\n * author typed then meant a different face depending on a dropdown. That coordinate mode is now\n * where it belongs: a workbench setting in `figure-view.ts`, applied to the preview alone.\n */\n\n/**\n * 저작자가 보는 축이 씬의 어느 용어인가.\n *\n * 씬은 2D 캔버스에서 온 용어를 쓰므로 축 이름이 그대로 없다. 이 표가 **유일한** 대응이다 —\n * 편집 규칙이 저마다 자기 표를 들면 한 곳을 고칠 때 다른 곳이 남는다.\n */\nexport const AXIS_KEYS = {\n x: { at: 'left', size: 'width' },\n y: { at: 'zPos', size: 'depth' },\n z: { at: 'top', size: 'height' }\n} as const\n\nexport type PlaneAxis = keyof typeof AXIS_KEYS\n\n/** 판이 그 축으로 얼마나 넓은가. */\nexport function draftExtent(draft: FigureDraft, axis: PlaneAxis): number {\n return axis === 'x' ? draft.width : axis === 'y' ? draft.depth : draft.height\n}\n\n/** 부품 하나를 저장 형식으로 변환한다. 저작 화면이 저작자의 어휘로 보일 때 쓴다. */\nexport function partToFigure(part: PartModel, draft: FigureDraft): FigurePart {\n return partTo(part, halfOf(draft))\n}\n\n/** 부품 하나를 씬 용어로 되돌린다. */\nexport function partFromFigure(part: FigurePart, draft: FigureDraft): PartModel {\n return partFrom(part, halfOf(draft))\n}\n\n/** 씬 모델을 저장 형식으로 변환한다. */\nexport function toFigureSource(draft: FigureDraft, parts: PartModel[]): FigureSource {\n const half = halfOf(draft)\n\n return withoutEmpty({\n version: draft.version ?? FIGURE_SOURCE_VERSION,\n type: draft.figureType ?? '',\n base: { x: draft.width, y: draft.depth, z: draft.height },\n placement: draft.placement,\n detailLevel: draft.detailLevel,\n styleKit: draft.styleKit,\n parts: parts.map(part => partTo(part, half)),\n animations: draft.animations,\n parameters: draft.parameters,\n capabilities: draft.capabilities,\n joints: draft.joints?.length ? draft.joints : undefined\n }) as FigureSource\n}\n\nfunction partTo(part: PartModel, half: Vec3): FigurePart {\n const rotation = withoutEmpty({\n x: part.rotationX ? round4(part.rotationX * RAD_TO_DEG) : undefined,\n y: part.rotation ? round4(-part.rotation * RAD_TO_DEG) : undefined,\n z: part.rotationY ? round4(part.rotationY * RAD_TO_DEG) : undefined\n })\n\n const shape = withoutEmpty({ round: part.round, path: part.path, hollow: part.hollow })\n /*\n 재질이 하나도 없으면 **칸 자체를 안 적는다.**\n\n 빈 객체를 적으면 형식이 「재질은 있는데 무엇으로 칠할지 모른다」로 읽어\n `material-unresolvable` 을 낸다. 그리지 않는 자리(`capability`)에는 재질이 없는 것이\n 정상이고, 그 부품이 조용히 오류를 달고 다니게 된다.\n */\n const material = withoutEmpty({\n token: part.token,\n preset: part.preset,\n flatShading: part.flatShading,\n transparent: part.transparent,\n emissive: part.emissive,\n surface: part.surface\n })\n\n return withoutEmpty({\n name: part.name,\n primitive: part.primitive,\n transform: withoutEmpty({\n position: {\n x: round4(part.left + part.width / 2 - half.x),\n y: round4(part.zPos + part.depth / 2),\n z: round4(part.top + part.height / 2 - half.z)\n },\n size: { x: part.width, y: part.depth, z: part.height },\n rotation: Object.keys(rotation).length > 0 ? rotation : undefined\n }),\n segments: part.segments,\n shape: Object.keys(shape).length > 0 ? shape : undefined,\n material: Object.keys(material).length > 0 ? material : undefined,\n materialSlot: part.materialSlot,\n keepRound: part.keepRound,\n sizing: part.sizing,\n anchor: part.anchor,\n repeat: part.repeat,\n label: part.label,\n capability: part.capability,\n parent: part.parent\n }) as FigurePart\n}\n\n/** 형식을 씬 모델로 편다. */\nexport function fromFigureSource(source: FigureSource): { draft: FigureDraft; parts: PartModel[] } {\n const half: Vec3 = { x: source.base.x / 2, y: source.base.y / 2, z: source.base.z / 2 }\n\n const draft = withoutEmpty({\n version: source.version,\n width: source.base.x,\n height: source.base.z,\n depth: source.base.y,\n figureType: source.type,\n placement: source.placement,\n detailLevel: source.detailLevel,\n styleKit: source.styleKit,\n /* Opened as stored. A legacy `drive: 'hold'` clip is refused by the format now; stored ones were migrated. */\n animations: source.animations?.length ? source.animations : undefined,\n parameters: source.parameters?.length ? source.parameters : undefined,\n capabilities: source.capabilities,\n joints: source.joints?.length ? source.joints : undefined\n }) as FigureDraft\n\n return { draft, parts: source.parts.map(part => partFrom(part, half)) }\n}\n\nfunction partFrom(part: FigurePart, half: Vec3): PartModel {\n const { position, size, rotation } = part.transform\n const material = part.material ?? {}\n\n return withoutEmpty({\n type: 'figure-part',\n name: part.name,\n primitive: part.primitive,\n\n left: round4(half.x + position.x - size.x / 2),\n top: round4(half.z + position.z - size.z / 2),\n width: size.x,\n height: size.z,\n zPos: round4(position.y - size.y / 2),\n depth: size.y,\n\n /*\n 라디안은 자르지 않는다.\n\n 소수 넷째에서 잘랐더니 도로 되돌릴 때 57 배로 커져 45 도가 45.0001 이 됐다.\n 자르는 것은 **사람이 읽는 쪽**(도)에서만 한다.\n */\n rotationX: rotation?.x ? rotation.x * DEG_TO_RAD : undefined,\n rotation: rotation?.y ? -rotation.y * DEG_TO_RAD : undefined,\n rotationY: rotation?.z ? rotation.z * DEG_TO_RAD : undefined,\n\n segments: part.segments,\n round: part.shape?.round,\n path: part.shape?.path,\n hollow: part.shape?.hollow,\n\n token: material.token,\n preset: material.preset,\n flatShading: material.flatShading,\n transparent: material.transparent,\n emissive: material.emissive,\n surface: material.surface,\n\n materialSlot: part.materialSlot,\n keepRound: part.keepRound,\n sizing: part.sizing,\n anchor: part.anchor,\n repeat: part.repeat,\n label: part.label,\n capability: part.capability,\n parent: part.parent\n }) as PartModel\n}\n"]}
|
|
@@ -100,3 +100,19 @@ export declare const WORKBENCH = "#7c7c7c";
|
|
|
100
100
|
* 무엇이 설정 때문이고 무엇이 원래 그랬는지 갈리지 않는다.
|
|
101
101
|
*/
|
|
102
102
|
export declare const DEFAULT_VIEW: ViewSettings;
|
|
103
|
+
/**
|
|
104
|
+
* How tall the preview board is: enough room for the tallest instance where the figure actually stands.
|
|
105
|
+
*
|
|
106
|
+
* A floor or center figure stands on the floor, so the tallest instance is the whole height. Only a
|
|
107
|
+
* ceiling figure needs the room up to the ceiling. The board used to reach the ceiling for every figure,
|
|
108
|
+
* which made a 920 tall AGV preview 4400 tall.
|
|
109
|
+
*
|
|
110
|
+
* This is an envelope, not a placement rule: where each instance goes is still the scene's
|
|
111
|
+
* (`FigureRealObject.effectiveZPos`), and the camera fits what the scene drew.
|
|
112
|
+
*/
|
|
113
|
+
export declare function previewBoardDepth(figure: {
|
|
114
|
+
placement?: 'floor' | 'ceiling' | 'center';
|
|
115
|
+
base: {
|
|
116
|
+
y: number;
|
|
117
|
+
};
|
|
118
|
+
}, ceilingHeight: number, factor: number): number;
|
|
@@ -60,4 +60,19 @@ export const DEFAULT_VIEW = {
|
|
|
60
60
|
dirShadowEnabled: true,
|
|
61
61
|
hemiIntensity: 4.0
|
|
62
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* How tall the preview board is: enough room for the tallest instance where the figure actually stands.
|
|
65
|
+
*
|
|
66
|
+
* A floor or center figure stands on the floor, so the tallest instance is the whole height. Only a
|
|
67
|
+
* ceiling figure needs the room up to the ceiling. The board used to reach the ceiling for every figure,
|
|
68
|
+
* which made a 920 tall AGV preview 4400 tall.
|
|
69
|
+
*
|
|
70
|
+
* This is an envelope, not a placement rule: where each instance goes is still the scene's
|
|
71
|
+
* (`FigureRealObject.effectiveZPos`), and the camera fits what the scene drew.
|
|
72
|
+
*/
|
|
73
|
+
export function previewBoardDepth(figure, ceilingHeight, factor) {
|
|
74
|
+
const tallest = figure.base.y * factor;
|
|
75
|
+
const reach = figure.placement === 'ceiling' ? Math.max(ceilingHeight, tallest) : tallest;
|
|
76
|
+
return Math.round(reach * 1.1);
|
|
77
|
+
}
|
|
63
78
|
//# sourceMappingURL=figure-view.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-view.js","sourceRoot":"","sources":["../../client/modeller/figure-view.ts"],"names":[],"mappings":"AAiEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ;IACR,WAAW;IACX,SAAS;IACT,QAAQ;IACR,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;CACC,CAAA;AAGV;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,IAAkB;IAC1C,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;IAClD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAU,CAAA;AAEvE,qCAAqC;AACrC,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAA;AAElC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,SAAS,EAAE,OAAO;IAClB,iDAAiD;IACjD,aAAa,EAAE,IAAI;IACnB,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,QAAQ;IACrB,QAAQ,EAAE,SAAS;IACnB,iBAAiB,EAAE,GAAG;IACtB,oBAAoB,EAAE,IAAI;IAC1B,gBAAgB,EAAE,IAAI;IACtB,aAAa,EAAE,GAAG;CACnB,CAAA","sourcesContent":["/**\n * 저작 화면의 **보기 설정**.\n *\n * ## 저장되지 않는다\n *\n * 이것은 자산이 아니라 **작업대**다. 조명을 밝게 하고 바닥을 켠 것은 그 사람이 지금\n * 보기 편한 상태일 뿐, 그 자산의 성질이 아니다. 그래서 `FigureSource` 에 안 들어간다 —\n * `toFigureSource` 는 판에서 `width` · `height` · `depth` 와 몇 가지만 집어 가고 나머지는\n * 버린다.\n *\n * 다른 사람이 같은 자산을 열면 자기 설정으로 본다. 그것이 맞다.\n *\n * ## 씬이 아는 이름을 그대로 쓴다\n *\n * 여기 이름은 전부 `model-layer` 의 상태 이름이다. 옮겨 적는 표를 두지 않는다 — 표를\n * 두면 씬이 새 설정을 받아도 이쪽이 모른다.\n *\n * 값이 바뀌면 씬이 스스로 반영한다(`three-capability` 의 `onchange`). 다만 `grid` 는\n * 한동안 3D 를 세울 때만 읽혔다 — things-scene 10.1.5 에서 고쳤다.\n */\nexport interface ViewSettings {\n /**\n * 어떤 도면에 놓인 것으로 보나 — 씬 전체의 좌표 모드.\n *\n * **도형의 `placement` 와 다른 것이다.** 도형의 것은 「이 종류는 바닥에 서나, 천장에\n * 매달리나」이고, 이것은 「지금 보고 있는 도면이 어느 쪽을 기준으로 재나」다. 바닥\n * 기준 도면에 천정 기반 OHT 를 놓으면 도면은 안 뒤집히고 OHT 가 천장 높이에 매달린다.\n *\n * 한동안 도형의 `placement` 를 이 자리에 흘려 넣었다. 모델러에 도형이 하나뿐이라\n * 그럴듯해 보였지만, 그러면 도형의 성질이 씬 전체를 뒤집는다 — 도면에서는 성립할 수가\n * 없고(도면에는 도형이 여럿이다), 저작 중에는 배치를 고를 때마다 저작자가 적어 둔\n * 높이가 전부 다른 숫자로 보였다.\n *\n * 씬의 낱말 그대로다: `floor` 바닥 기준 · `space` 중심 기준(바닥 없음) · `inverted`\n * 천장 기준. **미리보기에만 먹인다** — 편집 캔버스의 `zPos` 는 저작자가 직접 적는\n * 숫자라 그 뜻이 보기 설정에 따라 바뀌면 안 된다.\n */\n placement: 'floor' | 'space' | 'inverted'\n /**\n * 천장 높이 (mm). 천정 기반 도형을 미리보기에서 어디에 매달 것인가.\n *\n * 도형은 제가 놓일 방의 높이를 모른다. 도면은 알지만 미리보기에는 도면이 없으므로,\n * 저작자가 가정할 값을 여기서 정한다.\n */\n ceilingHeight: number\n /** 좌표 바탕(축 가이드). 원점과 눈금을 보인다. */\n grid: boolean\n /** 축 이름표(X · Y · Z). 눈금만 두고 글자를 끌 수 있다. */\n gridLabels: boolean\n /** 판의 바닥면. 좌표 바탕과 겹치므로 기본은 끈다. */\n floor: boolean\n /** Environment — 반사(IBL). 금속 재질이 무엇을 비출지 정한다. */\n environment: EnvironmentName\n /** Sky Color — 배경색. */\n skyColor: string\n /** Key Light Intensity. */\n dirLightIntensity: number\n /** Key Light — Follow Camera. 끄면 그림자 방향이 고정된다. */\n dirLightFollowCamera: boolean\n /** Key Light — Cast Shadow. */\n dirShadowEnabled: boolean\n /** Hemisphere Light Intensity — 그늘진 면을 들어올린다. */\n hemiIntensity: number\n}\n\n/**\n * 환경 프리셋 — **`scene-manager` 의 `SkyPreset` 과 같은 여덟이다.**\n *\n * 한동안 다섯만 적었다. 씬은 여덟을 다 받는데 이쪽이 셋을 몰라서 고를 수 없었다.\n * operato-board 의 3D 씬 속성 패널도 같은 여덟을 보인다.\n */\nexport const ENVIRONMENTS = [\n 'studio',\n 'warehouse',\n 'factory',\n 'office',\n 'home',\n 'sunny',\n 'cloudy',\n 'rainy'\n] as const\nexport type EnvironmentName = (typeof ENVIRONMENTS)[number]\n\n/**\n * 씬에 그대로 넣는 보기 설정 — **좌표 기준과 천장 높이는 빼고.**\n *\n * 나머지는 전부 `model-layer` 의 상태 이름이라 그대로 넣으면 씬이 알아서 반응한다.\n * 그런데 `placement` 는 편집 캔버스에 들어가면 안 되고(거기는 늘 상자 기준이다),\n * `ceilingHeight` 는 씬이 모르는 이름이다.\n *\n * **rest spread 로 넘기지 않으려고 이 함수가 있다.** 캔버스가 한동안\n * `const { grid, ..., ...light } = view` 로 나머지를 통째로 넘겼는데, 그러면 설정을\n * 하나 더할 때마다 그것이 두 화면에 **말없이** 실린다. 새로 더한 좌표 기준이 바로\n * 그 길로 캔버스에 들어갈 뻔했다.\n */\nexport function sceneLook(view: ViewSettings): Record<string, unknown> {\n const { placement, ceilingHeight, ...look } = view\n return look\n}\n\n/**\n * 씬의 좌표 기준 — **씬의 낱말 그대로 셋이다.**\n *\n * 형식의 `floor` · `center` · `ceiling` 과 짝이 맞지만 철자가 다르다. 옮겨 적는 표를 두지\n * 않는 것이 이 파일의 규칙이므로 씬 쪽을 쓴다. 화면에는 둘 다 보인다 — 「천장 기준\n * (inverted)」.\n */\nexport const SCENE_REFERENCES = ['floor', 'space', 'inverted'] as const\n\n/** 배경색 기본값. 회색이라 부품 색이 제 색으로 보인다. */\nexport const WORKBENCH = '#7c7c7c'\n\n/**\n * 처음 값.\n *\n * **지금 저작 화면이 실제로 쓰던 값 그대로다.** 설정을 만들면서 화면이 달라지면\n * 무엇이 설정 때문이고 무엇이 원래 그랬는지 갈리지 않는다.\n */\nexport const DEFAULT_VIEW: ViewSettings = {\n placement: 'floor',\n /* 공장 천장의 흔한 높이. 반도체 fab 의 OHT 레일이 대체로 이 언저리다. */\n ceilingHeight: 4000,\n grid: true,\n gridLabels: true,\n floor: false,\n environment: 'studio',\n skyColor: WORKBENCH,\n dirLightIntensity: 2.0,\n dirLightFollowCamera: true,\n dirShadowEnabled: true,\n hemiIntensity: 4.0\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-view.js","sourceRoot":"","sources":["../../client/modeller/figure-view.ts"],"names":[],"mappings":"AAiEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ;IACR,WAAW;IACX,SAAS;IACT,QAAQ;IACR,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;CACC,CAAA;AAGV;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,IAAkB;IAC1C,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;IAClD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAU,CAAA;AAEvE,qCAAqC;AACrC,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAA;AAElC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,SAAS,EAAE,OAAO;IAClB,iDAAiD;IACjD,aAAa,EAAE,IAAI;IACnB,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,QAAQ;IACrB,QAAQ,EAAE,SAAS;IACnB,iBAAiB,EAAE,GAAG;IACtB,oBAAoB,EAAE,IAAI;IAC1B,gBAAgB,EAAE,IAAI;IACtB,aAAa,EAAE,GAAG;CACnB,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA2E,EAC3E,aAAqB,EACrB,MAAc;IAEd,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAA;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IACzF,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAA;AAChC,CAAC","sourcesContent":["/**\n * 저작 화면의 **보기 설정**.\n *\n * ## 저장되지 않는다\n *\n * 이것은 자산이 아니라 **작업대**다. 조명을 밝게 하고 바닥을 켠 것은 그 사람이 지금\n * 보기 편한 상태일 뿐, 그 자산의 성질이 아니다. 그래서 `FigureSource` 에 안 들어간다 —\n * `toFigureSource` 는 판에서 `width` · `height` · `depth` 와 몇 가지만 집어 가고 나머지는\n * 버린다.\n *\n * 다른 사람이 같은 자산을 열면 자기 설정으로 본다. 그것이 맞다.\n *\n * ## 씬이 아는 이름을 그대로 쓴다\n *\n * 여기 이름은 전부 `model-layer` 의 상태 이름이다. 옮겨 적는 표를 두지 않는다 — 표를\n * 두면 씬이 새 설정을 받아도 이쪽이 모른다.\n *\n * 값이 바뀌면 씬이 스스로 반영한다(`three-capability` 의 `onchange`). 다만 `grid` 는\n * 한동안 3D 를 세울 때만 읽혔다 — things-scene 10.1.5 에서 고쳤다.\n */\nexport interface ViewSettings {\n /**\n * 어떤 도면에 놓인 것으로 보나 — 씬 전체의 좌표 모드.\n *\n * **도형의 `placement` 와 다른 것이다.** 도형의 것은 「이 종류는 바닥에 서나, 천장에\n * 매달리나」이고, 이것은 「지금 보고 있는 도면이 어느 쪽을 기준으로 재나」다. 바닥\n * 기준 도면에 천정 기반 OHT 를 놓으면 도면은 안 뒤집히고 OHT 가 천장 높이에 매달린다.\n *\n * 한동안 도형의 `placement` 를 이 자리에 흘려 넣었다. 모델러에 도형이 하나뿐이라\n * 그럴듯해 보였지만, 그러면 도형의 성질이 씬 전체를 뒤집는다 — 도면에서는 성립할 수가\n * 없고(도면에는 도형이 여럿이다), 저작 중에는 배치를 고를 때마다 저작자가 적어 둔\n * 높이가 전부 다른 숫자로 보였다.\n *\n * 씬의 낱말 그대로다: `floor` 바닥 기준 · `space` 중심 기준(바닥 없음) · `inverted`\n * 천장 기준. **미리보기에만 먹인다** — 편집 캔버스의 `zPos` 는 저작자가 직접 적는\n * 숫자라 그 뜻이 보기 설정에 따라 바뀌면 안 된다.\n */\n placement: 'floor' | 'space' | 'inverted'\n /**\n * 천장 높이 (mm). 천정 기반 도형을 미리보기에서 어디에 매달 것인가.\n *\n * 도형은 제가 놓일 방의 높이를 모른다. 도면은 알지만 미리보기에는 도면이 없으므로,\n * 저작자가 가정할 값을 여기서 정한다.\n */\n ceilingHeight: number\n /** 좌표 바탕(축 가이드). 원점과 눈금을 보인다. */\n grid: boolean\n /** 축 이름표(X · Y · Z). 눈금만 두고 글자를 끌 수 있다. */\n gridLabels: boolean\n /** 판의 바닥면. 좌표 바탕과 겹치므로 기본은 끈다. */\n floor: boolean\n /** Environment — 반사(IBL). 금속 재질이 무엇을 비출지 정한다. */\n environment: EnvironmentName\n /** Sky Color — 배경색. */\n skyColor: string\n /** Key Light Intensity. */\n dirLightIntensity: number\n /** Key Light — Follow Camera. 끄면 그림자 방향이 고정된다. */\n dirLightFollowCamera: boolean\n /** Key Light — Cast Shadow. */\n dirShadowEnabled: boolean\n /** Hemisphere Light Intensity — 그늘진 면을 들어올린다. */\n hemiIntensity: number\n}\n\n/**\n * 환경 프리셋 — **`scene-manager` 의 `SkyPreset` 과 같은 여덟이다.**\n *\n * 한동안 다섯만 적었다. 씬은 여덟을 다 받는데 이쪽이 셋을 몰라서 고를 수 없었다.\n * operato-board 의 3D 씬 속성 패널도 같은 여덟을 보인다.\n */\nexport const ENVIRONMENTS = [\n 'studio',\n 'warehouse',\n 'factory',\n 'office',\n 'home',\n 'sunny',\n 'cloudy',\n 'rainy'\n] as const\nexport type EnvironmentName = (typeof ENVIRONMENTS)[number]\n\n/**\n * 씬에 그대로 넣는 보기 설정 — **좌표 기준과 천장 높이는 빼고.**\n *\n * 나머지는 전부 `model-layer` 의 상태 이름이라 그대로 넣으면 씬이 알아서 반응한다.\n * 그런데 `placement` 는 편집 캔버스에 들어가면 안 되고(거기는 늘 상자 기준이다),\n * `ceilingHeight` 는 씬이 모르는 이름이다.\n *\n * **rest spread 로 넘기지 않으려고 이 함수가 있다.** 캔버스가 한동안\n * `const { grid, ..., ...light } = view` 로 나머지를 통째로 넘겼는데, 그러면 설정을\n * 하나 더할 때마다 그것이 두 화면에 **말없이** 실린다. 새로 더한 좌표 기준이 바로\n * 그 길로 캔버스에 들어갈 뻔했다.\n */\nexport function sceneLook(view: ViewSettings): Record<string, unknown> {\n const { placement, ceilingHeight, ...look } = view\n return look\n}\n\n/**\n * 씬의 좌표 기준 — **씬의 낱말 그대로 셋이다.**\n *\n * 형식의 `floor` · `center` · `ceiling` 과 짝이 맞지만 철자가 다르다. 옮겨 적는 표를 두지\n * 않는 것이 이 파일의 규칙이므로 씬 쪽을 쓴다. 화면에는 둘 다 보인다 — 「천장 기준\n * (inverted)」.\n */\nexport const SCENE_REFERENCES = ['floor', 'space', 'inverted'] as const\n\n/** 배경색 기본값. 회색이라 부품 색이 제 색으로 보인다. */\nexport const WORKBENCH = '#7c7c7c'\n\n/**\n * 처음 값.\n *\n * **지금 저작 화면이 실제로 쓰던 값 그대로다.** 설정을 만들면서 화면이 달라지면\n * 무엇이 설정 때문이고 무엇이 원래 그랬는지 갈리지 않는다.\n */\nexport const DEFAULT_VIEW: ViewSettings = {\n placement: 'floor',\n /* 공장 천장의 흔한 높이. 반도체 fab 의 OHT 레일이 대체로 이 언저리다. */\n ceilingHeight: 4000,\n grid: true,\n gridLabels: true,\n floor: false,\n environment: 'studio',\n skyColor: WORKBENCH,\n dirLightIntensity: 2.0,\n dirLightFollowCamera: true,\n dirShadowEnabled: true,\n hemiIntensity: 4.0\n}\n\n/**\n * How tall the preview board is: enough room for the tallest instance where the figure actually stands.\n *\n * A floor or center figure stands on the floor, so the tallest instance is the whole height. Only a\n * ceiling figure needs the room up to the ceiling. The board used to reach the ceiling for every figure,\n * which made a 920 tall AGV preview 4400 tall.\n *\n * This is an envelope, not a placement rule: where each instance goes is still the scene's\n * (`FigureRealObject.effectiveZPos`), and the camera fits what the scene drew.\n */\nexport function previewBoardDepth(\n figure: { placement?: 'floor' | 'ceiling' | 'center'; base: { y: number } },\n ceilingHeight: number,\n factor: number\n): number {\n const tallest = figure.base.y * factor\n const reach = figure.placement === 'ceiling' ? Math.max(ceilingHeight, tallest) : tallest\n return Math.round(reach * 1.1)\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FigureJoint, FigurePart, JointType, Vec3 } from '@hatiolab/figure-model';
|
|
2
|
+
import type { FigureDraft, PartModel } from './figure-source.js';
|
|
3
|
+
/** The joint whose child is this part, if any. */
|
|
4
|
+
export declare function jointOf(draft: FigureDraft | undefined, part: string): FigureJoint | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Parts a part may be attached to: every other part except itself and anything attached below it, since
|
|
7
|
+
* either would close a loop the format refuses (`parent-cycle`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function parentChoices(parts: readonly PartModel[], part: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* 새 관절이 설 자리 — **부품이 붙은 부품과 맞닿는 면의 한가운데.**
|
|
12
|
+
*
|
|
13
|
+
* 경첩은 두 부품이 닿는 자리에 있다. 팔꿈치는 윗팔과 앞팔이 만나는 곳이고, 문은 문틀에 닿는
|
|
14
|
+
* 모서리에 있다. 그래서 처음 값은 그 자리여야 한다.
|
|
15
|
+
*
|
|
16
|
+
* 한동안 **부품 아랫면 한가운데**를 썼다. 위로 쌓는 부품에는 맞는 자리인데(로봇 팔이 그렇다),
|
|
17
|
+
* 옆에 달린 부품에는 틀린다 — 캐비닛 옆에 달린 문의 축이 문 바닥에 생긴다. 저작자는 그것을
|
|
18
|
+
* 화면에서 보고 손으로 옮겨야 했고, 어디로 옮길지는 두 부품의 치수를 머릿속에서 빼야 나왔다.
|
|
19
|
+
*
|
|
20
|
+
* **가장 벌어진 축이 맞닿는 축이다.** 두 상자가 겹치는 축에서는 뺀 값이 음수이고, 마주 보는
|
|
21
|
+
* 축에서만 0 이나 양수가 된다. 나머지 두 축은 겹치는 구간의 한가운데를 쓴다 — 문의 축은 문
|
|
22
|
+
* 높이의 한가운데에 있지 위아래 끝에 있지 않다.
|
|
23
|
+
*
|
|
24
|
+
* 붙은 부품이 없으면(Figure 틀에 직접 붙는다) 아랫면 한가운데다. 그때 맞닿는 것은 바닥이다.
|
|
25
|
+
*
|
|
26
|
+
* ⚠ 부품의 제 회전은 보지 않는다. 비스듬히 세운 부품에서는 축에 나란한 상자로 셈한 자리가
|
|
27
|
+
* 나오고, 그것은 **처음 값**이다 — 저작자가 옮길 수 있고, 화면의 부채꼴이 어디인지 보여 준다.
|
|
28
|
+
*/
|
|
29
|
+
export declare function seamOrigin(part: FigurePart, parent?: FigurePart): Vec3;
|
|
30
|
+
/**
|
|
31
|
+
* Sets how a part moves relative to its parent, or clears the joint with `undefined`.
|
|
32
|
+
*
|
|
33
|
+
* - From none: adds a joint at the seam with the parent (`seamOrigin`), with a vertical axis and starting
|
|
34
|
+
* limits, and a parameter that drives it over those limits.
|
|
35
|
+
* - To none: removes the joint and every channel that drove it; a parameter or clip left with no channel
|
|
36
|
+
* is removed too, because the format refuses an empty one.
|
|
37
|
+
* - Between types: keeps the origin and axis, sets limits for the new type (none for continuous), and
|
|
38
|
+
* moves a plain driving parameter along with them.
|
|
39
|
+
*/
|
|
40
|
+
export declare function setJointType(draft: FigureDraft, part: FigurePart, type: JointType | undefined, parent?: FigurePart): FigureDraft;
|
|
41
|
+
/** Changes a joint's origin, axis or limits. A plain driving parameter follows new limits. */
|
|
42
|
+
export declare function patchJoint(draft: FigureDraft, name: string, patch: {
|
|
43
|
+
origin?: Vec3;
|
|
44
|
+
axis?: Vec3;
|
|
45
|
+
limits?: {
|
|
46
|
+
min: number;
|
|
47
|
+
max: number;
|
|
48
|
+
};
|
|
49
|
+
}): FigureDraft;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/** The limits a new joint starts with. Revolute in degrees, prismatic in millimetres. */
|
|
5
|
+
const NEW_LIMITS = {
|
|
6
|
+
revolute: { min: -90, max: 90 },
|
|
7
|
+
prismatic: { min: 0, max: 500 }
|
|
8
|
+
};
|
|
9
|
+
/** The span a parameter driving a continuous joint offers: one turn either way. */
|
|
10
|
+
const CONTINUOUS_SPAN = { min: -360, max: 360 };
|
|
11
|
+
/** The joint whose child is this part, if any. */
|
|
12
|
+
export function jointOf(draft, part) {
|
|
13
|
+
return draft?.joints?.find(joint => joint.child === part);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parts a part may be attached to: every other part except itself and anything attached below it, since
|
|
17
|
+
* either would close a loop the format refuses (`parent-cycle`).
|
|
18
|
+
*/
|
|
19
|
+
export function parentChoices(parts, part) {
|
|
20
|
+
const below = new Set([part]);
|
|
21
|
+
let grew = true;
|
|
22
|
+
while (grew) {
|
|
23
|
+
grew = false;
|
|
24
|
+
for (const one of parts) {
|
|
25
|
+
if (one.parent !== undefined && below.has(one.parent) && !below.has(one.name)) {
|
|
26
|
+
below.add(one.name);
|
|
27
|
+
grew = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return parts.map(one => one.name).filter(name => !!name && !below.has(name));
|
|
32
|
+
}
|
|
33
|
+
/** 한 축에서 부품이 차지하는 구간. */
|
|
34
|
+
const reachOf = (part, axis) => ({
|
|
35
|
+
min: part.transform.position[axis] - part.transform.size[axis] / 2,
|
|
36
|
+
max: part.transform.position[axis] + part.transform.size[axis] / 2
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* 새 관절이 설 자리 — **부품이 붙은 부품과 맞닿는 면의 한가운데.**
|
|
40
|
+
*
|
|
41
|
+
* 경첩은 두 부품이 닿는 자리에 있다. 팔꿈치는 윗팔과 앞팔이 만나는 곳이고, 문은 문틀에 닿는
|
|
42
|
+
* 모서리에 있다. 그래서 처음 값은 그 자리여야 한다.
|
|
43
|
+
*
|
|
44
|
+
* 한동안 **부품 아랫면 한가운데**를 썼다. 위로 쌓는 부품에는 맞는 자리인데(로봇 팔이 그렇다),
|
|
45
|
+
* 옆에 달린 부품에는 틀린다 — 캐비닛 옆에 달린 문의 축이 문 바닥에 생긴다. 저작자는 그것을
|
|
46
|
+
* 화면에서 보고 손으로 옮겨야 했고, 어디로 옮길지는 두 부품의 치수를 머릿속에서 빼야 나왔다.
|
|
47
|
+
*
|
|
48
|
+
* **가장 벌어진 축이 맞닿는 축이다.** 두 상자가 겹치는 축에서는 뺀 값이 음수이고, 마주 보는
|
|
49
|
+
* 축에서만 0 이나 양수가 된다. 나머지 두 축은 겹치는 구간의 한가운데를 쓴다 — 문의 축은 문
|
|
50
|
+
* 높이의 한가운데에 있지 위아래 끝에 있지 않다.
|
|
51
|
+
*
|
|
52
|
+
* 붙은 부품이 없으면(Figure 틀에 직접 붙는다) 아랫면 한가운데다. 그때 맞닿는 것은 바닥이다.
|
|
53
|
+
*
|
|
54
|
+
* ⚠ 부품의 제 회전은 보지 않는다. 비스듬히 세운 부품에서는 축에 나란한 상자로 셈한 자리가
|
|
55
|
+
* 나오고, 그것은 **처음 값**이다 — 저작자가 옮길 수 있고, 화면의 부채꼴이 어디인지 보여 준다.
|
|
56
|
+
*/
|
|
57
|
+
export function seamOrigin(part, parent) {
|
|
58
|
+
const { position, size } = part.transform;
|
|
59
|
+
if (!parent)
|
|
60
|
+
return { x: position.x, y: position.y - size.y / 2, z: position.z };
|
|
61
|
+
const axes = ['x', 'y', 'z'];
|
|
62
|
+
const gaps = axes.map(axis => {
|
|
63
|
+
const mine = reachOf(part, axis);
|
|
64
|
+
const theirs = reachOf(parent, axis);
|
|
65
|
+
return { axis, gap: Math.max(mine.min - theirs.max, theirs.min - mine.max), mine, theirs };
|
|
66
|
+
});
|
|
67
|
+
const meeting = gaps.reduce((widest, one) => (one.gap > widest.gap ? one : widest));
|
|
68
|
+
const origin = { x: position.x, y: position.y, z: position.z };
|
|
69
|
+
for (const { axis, mine, theirs } of gaps) {
|
|
70
|
+
if (axis === meeting.axis) {
|
|
71
|
+
/* 맞닿는 면은 **내 면**이다. 부모 쪽 면을 쓰면 둘 사이가 벌어져 있을 때 축이 허공에 선다. */
|
|
72
|
+
origin[axis] = mine.min >= theirs.max ? mine.min : mine.max;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const from = Math.max(mine.min, theirs.min);
|
|
76
|
+
const to = Math.min(mine.max, theirs.max);
|
|
77
|
+
origin[axis] = from <= to ? (from + to) / 2 : position[axis];
|
|
78
|
+
}
|
|
79
|
+
return { x: round4(origin.x), y: round4(origin.y), z: round4(origin.z) };
|
|
80
|
+
}
|
|
81
|
+
const round4 = (value) => Math.round(value * 10000) / 10000;
|
|
82
|
+
function freeName(taken, stem) {
|
|
83
|
+
const used = new Set(taken);
|
|
84
|
+
if (!used.has(stem))
|
|
85
|
+
return stem;
|
|
86
|
+
for (let n = 2;; n++)
|
|
87
|
+
if (!used.has(`${stem}-${n}`))
|
|
88
|
+
return `${stem}-${n}`;
|
|
89
|
+
}
|
|
90
|
+
/** The span the driving parameter offers for a joint: its limits, or one turn either way when it has none. */
|
|
91
|
+
function spanOf(joint) {
|
|
92
|
+
return joint.limits ?? CONTINUOUS_SPAN;
|
|
93
|
+
}
|
|
94
|
+
function drivingParameter(joint) {
|
|
95
|
+
const span = spanOf(joint);
|
|
96
|
+
return {
|
|
97
|
+
name: joint.name,
|
|
98
|
+
range: { unit: joint.type === 'prismatic' ? 'mm' : 'deg', min: span.min, max: span.max },
|
|
99
|
+
default: 0,
|
|
100
|
+
clip: {
|
|
101
|
+
channels: [
|
|
102
|
+
{
|
|
103
|
+
target: joint.name,
|
|
104
|
+
keys: [
|
|
105
|
+
{ at: 0, value: span.min },
|
|
106
|
+
{ at: 1, value: span.max }
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Whether a parameter is the plain one `setJointType` made for a joint: one channel on that joint, keys at
|
|
115
|
+
* 0 and 1 spanning the given limits. Only such a parameter follows the joint when its limits or type change;
|
|
116
|
+
* one the author reshaped is left as written.
|
|
117
|
+
*/
|
|
118
|
+
function isPlainDriver(parameter, joint, span) {
|
|
119
|
+
const channels = parameter.clip?.channels ?? [];
|
|
120
|
+
if (channels.length !== 1)
|
|
121
|
+
return false;
|
|
122
|
+
const [channel] = channels;
|
|
123
|
+
if (channel.target !== joint.name || channel.path !== undefined)
|
|
124
|
+
return false;
|
|
125
|
+
const keys = channel.keys;
|
|
126
|
+
return (keys.length === 2 &&
|
|
127
|
+
keys[0].at === 0 &&
|
|
128
|
+
keys[1].at === 1 &&
|
|
129
|
+
keys[0].value === span.min &&
|
|
130
|
+
keys[1].value === span.max &&
|
|
131
|
+
parameter.range?.min === span.min &&
|
|
132
|
+
parameter.range?.max === span.max);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Sets how a part moves relative to its parent, or clears the joint with `undefined`.
|
|
136
|
+
*
|
|
137
|
+
* - From none: adds a joint at the seam with the parent (`seamOrigin`), with a vertical axis and starting
|
|
138
|
+
* limits, and a parameter that drives it over those limits.
|
|
139
|
+
* - To none: removes the joint and every channel that drove it; a parameter or clip left with no channel
|
|
140
|
+
* is removed too, because the format refuses an empty one.
|
|
141
|
+
* - Between types: keeps the origin and axis, sets limits for the new type (none for continuous), and
|
|
142
|
+
* moves a plain driving parameter along with them.
|
|
143
|
+
*/
|
|
144
|
+
export function setJointType(draft, part, type, parent) {
|
|
145
|
+
const joints = draft.joints ?? [];
|
|
146
|
+
const existing = joints.find(joint => joint.child === part.name);
|
|
147
|
+
if (type === undefined) {
|
|
148
|
+
if (!existing)
|
|
149
|
+
return draft;
|
|
150
|
+
return withoutJoint(draft, existing.name);
|
|
151
|
+
}
|
|
152
|
+
if (!existing) {
|
|
153
|
+
const joint = {
|
|
154
|
+
name: freeName([...joints.map(one => one.name)], `${part.name}-joint`),
|
|
155
|
+
child: part.name,
|
|
156
|
+
type,
|
|
157
|
+
origin: seamOrigin(part, parent),
|
|
158
|
+
axis: { x: 0, y: 1, z: 0 }
|
|
159
|
+
};
|
|
160
|
+
if (type !== 'continuous')
|
|
161
|
+
joint.limits = { ...NEW_LIMITS[type] };
|
|
162
|
+
const parameters = draft.parameters ?? [];
|
|
163
|
+
const taken = [...parameters.map(one => one.name), ...(draft.animations ?? []).map(one => one.name)];
|
|
164
|
+
const driver = drivingParameter(joint);
|
|
165
|
+
driver.name = freeName(taken, joint.name);
|
|
166
|
+
return { ...draft, joints: [...joints, joint], parameters: [...parameters, driver] };
|
|
167
|
+
}
|
|
168
|
+
if (existing.type === type)
|
|
169
|
+
return draft;
|
|
170
|
+
const next = { ...existing, type };
|
|
171
|
+
if (type === 'continuous')
|
|
172
|
+
delete next.limits;
|
|
173
|
+
else if (existing.type === 'continuous' || (existing.type === 'prismatic') !== (type === 'prismatic')) {
|
|
174
|
+
next.limits = { ...NEW_LIMITS[type] };
|
|
175
|
+
}
|
|
176
|
+
return replaceJoint(draft, existing, next);
|
|
177
|
+
}
|
|
178
|
+
/** Changes a joint's origin, axis or limits. A plain driving parameter follows new limits. */
|
|
179
|
+
export function patchJoint(draft, name, patch) {
|
|
180
|
+
const existing = draft.joints?.find(joint => joint.name === name);
|
|
181
|
+
if (!existing)
|
|
182
|
+
return draft;
|
|
183
|
+
const next = { ...existing, ...patch };
|
|
184
|
+
if (existing.type === 'continuous')
|
|
185
|
+
delete next.limits;
|
|
186
|
+
return replaceJoint(draft, existing, next);
|
|
187
|
+
}
|
|
188
|
+
function replaceJoint(draft, before, after) {
|
|
189
|
+
const joints = (draft.joints ?? []).map(joint => (joint === before ? after : joint));
|
|
190
|
+
const was = spanOf(before);
|
|
191
|
+
const unitChanged = (before.type === 'prismatic') !== (after.type === 'prismatic');
|
|
192
|
+
const spanChanged = was.min !== spanOf(after).min || was.max !== spanOf(after).max;
|
|
193
|
+
if (!unitChanged && !spanChanged)
|
|
194
|
+
return { ...draft, joints };
|
|
195
|
+
const parameters = (draft.parameters ?? []).map(parameter => {
|
|
196
|
+
if (!isPlainDriver(parameter, before, was))
|
|
197
|
+
return parameter;
|
|
198
|
+
const driver = drivingParameter(after);
|
|
199
|
+
return { ...parameter, range: driver.range, clip: { ...parameter.clip, channels: driver.clip.channels } };
|
|
200
|
+
});
|
|
201
|
+
return { ...draft, joints, parameters };
|
|
202
|
+
}
|
|
203
|
+
function withoutJoint(draft, name) {
|
|
204
|
+
const keep = (one) => (one.channels ?? []).filter(channel => channel.target !== name);
|
|
205
|
+
const parameters = (draft.parameters ?? [])
|
|
206
|
+
.map(parameter => ({ ...parameter, clip: { ...parameter.clip, channels: keep(parameter.clip) } }))
|
|
207
|
+
.filter(parameter => parameter.clip.channels.length > 0);
|
|
208
|
+
const animations = (draft.animations ?? [])
|
|
209
|
+
.map(clip => ({ ...clip, channels: keep(clip) }))
|
|
210
|
+
.filter(clip => clip.channels.length > 0);
|
|
211
|
+
const joints = (draft.joints ?? []).filter(joint => joint.name !== name);
|
|
212
|
+
const next = { ...draft };
|
|
213
|
+
if (joints.length > 0)
|
|
214
|
+
next.joints = joints;
|
|
215
|
+
else
|
|
216
|
+
delete next.joints;
|
|
217
|
+
if (draft.parameters !== undefined)
|
|
218
|
+
next.parameters = parameters;
|
|
219
|
+
if (draft.animations !== undefined)
|
|
220
|
+
next.animations = animations;
|
|
221
|
+
return next;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=joint-edits.js.map
|