@things-factory/figure-ui 10.1.3
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/bootstrap.ts +16 -0
- package/client/graphql/index.ts +209 -0
- package/client/index.ts +2 -0
- package/client/modeller/figure-ask.ts +247 -0
- package/client/modeller/figure-canvas.ts +482 -0
- package/client/modeller/figure-inspector.ts +1054 -0
- package/client/modeller/figure-parts.ts +479 -0
- package/client/modeller/figure-preview.ts +381 -0
- package/client/modeller/figure-report.ts +895 -0
- package/client/modeller/figure-settings.ts +285 -0
- package/client/modeller/figure-side.ts +162 -0
- package/client/modeller/figure-source.test.ts +336 -0
- package/client/modeller/figure-source.ts +338 -0
- package/client/modeller/figure-view.ts +79 -0
- package/client/modeller/maturity.ts +288 -0
- package/client/modeller/part-edits.test.ts +252 -0
- package/client/modeller/part-edits.ts +246 -0
- package/client/modeller/proposal.ts +142 -0
- package/client/modeller/scene-budget.ts +102 -0
- package/client/pages/figure-list-page.ts +274 -0
- package/client/pages/figure-modeller-page.ts +1067 -0
- package/client/route.ts +11 -0
- package/client/themes/brand.css +51 -0
- package/client/themes/dark.css +64 -0
- package/client/themes/light.css +95 -0
- package/client/tsconfig.json +19 -0
- package/client/types.ts +96 -0
- package/client/viewparts/figure-card.ts +226 -0
- package/dist-client/bootstrap.d.ts +14 -0
- package/dist-client/bootstrap.js +17 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/graphql/index.d.ts +41 -0
- package/dist-client/graphql/index.js +173 -0
- package/dist-client/graphql/index.js.map +1 -0
- package/dist-client/index.d.ts +2 -0
- package/dist-client/index.js +3 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/modeller/figure-ask.d.ts +36 -0
- package/dist-client/modeller/figure-ask.js +256 -0
- package/dist-client/modeller/figure-ask.js.map +1 -0
- package/dist-client/modeller/figure-canvas.d.ts +128 -0
- package/dist-client/modeller/figure-canvas.js +431 -0
- package/dist-client/modeller/figure-canvas.js.map +1 -0
- package/dist-client/modeller/figure-inspector.d.ts +149 -0
- package/dist-client/modeller/figure-inspector.js +991 -0
- package/dist-client/modeller/figure-inspector.js.map +1 -0
- package/dist-client/modeller/figure-parts.d.ts +64 -0
- package/dist-client/modeller/figure-parts.js +478 -0
- package/dist-client/modeller/figure-parts.js.map +1 -0
- package/dist-client/modeller/figure-preview.d.ts +62 -0
- package/dist-client/modeller/figure-preview.js +360 -0
- package/dist-client/modeller/figure-preview.js.map +1 -0
- package/dist-client/modeller/figure-report.d.ts +134 -0
- package/dist-client/modeller/figure-report.js +881 -0
- package/dist-client/modeller/figure-report.js.map +1 -0
- package/dist-client/modeller/figure-settings.d.ts +39 -0
- package/dist-client/modeller/figure-settings.js +277 -0
- package/dist-client/modeller/figure-settings.js.map +1 -0
- package/dist-client/modeller/figure-side.d.ts +41 -0
- package/dist-client/modeller/figure-side.js +175 -0
- package/dist-client/modeller/figure-side.js.map +1 -0
- package/dist-client/modeller/figure-source.d.ts +196 -0
- package/dist-client/modeller/figure-source.js +153 -0
- package/dist-client/modeller/figure-source.js.map +1 -0
- package/dist-client/modeller/figure-view.d.ts +57 -0
- package/dist-client/modeller/figure-view.js +36 -0
- package/dist-client/modeller/figure-view.js.map +1 -0
- package/dist-client/modeller/maturity.d.ts +55 -0
- package/dist-client/modeller/maturity.js +212 -0
- package/dist-client/modeller/maturity.js.map +1 -0
- package/dist-client/modeller/part-edits.d.ts +64 -0
- package/dist-client/modeller/part-edits.js +218 -0
- package/dist-client/modeller/part-edits.js.map +1 -0
- package/dist-client/modeller/proposal.d.ts +45 -0
- package/dist-client/modeller/proposal.js +96 -0
- package/dist-client/modeller/proposal.js.map +1 -0
- package/dist-client/modeller/scene-budget.d.ts +78 -0
- package/dist-client/modeller/scene-budget.js +80 -0
- package/dist-client/modeller/scene-budget.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +40 -0
- package/dist-client/pages/figure-list-page.js +280 -0
- package/dist-client/pages/figure-list-page.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.d.ts +219 -0
- package/dist-client/pages/figure-modeller-page.js +1045 -0
- package/dist-client/pages/figure-modeller-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +11 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/themes/brand.css +51 -0
- package/dist-client/themes/dark.css +64 -0
- package/dist-client/themes/light.css +95 -0
- package/dist-client/types.d.ts +93 -0
- package/dist-client/types.js +2 -0
- package/dist-client/types.js.map +1 -0
- package/dist-client/viewparts/figure-card.d.ts +24 -0
- package/dist-client/viewparts/figure-card.js +221 -0
- package/dist-client/viewparts/figure-card.js.map +1 -0
- package/dist-server/index.d.ts +7 -0
- package/dist-server/index.js +3 -0
- package/dist-server/index.js.map +1 -0
- package/package.json +41 -0
- package/server/index.ts +7 -0
- package/server/tsconfig.json +18 -0
- package/test/brand-palette.test.ts +188 -0
- package/test/heading-scale-guard.test.ts +144 -0
- package/test/resolve-ts-siblings.mjs +67 -0
- package/things-factory.config.js +11 -0
- package/translations/en.json +247 -0
- package/translations/ja.json +247 -0
- package/translations/ko.json +247 -0
- package/translations/ms.json +247 -0
- package/translations/zh.json +247 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import './figure-inspector.js';
|
|
3
|
+
import './figure-report.js';
|
|
4
|
+
import './figure-settings.js';
|
|
5
|
+
import { css, html, LitElement } from 'lit';
|
|
6
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
7
|
+
import { i18next, localize } from '@operato/i18n';
|
|
8
|
+
import { DEFAULT_VIEW } from './figure-view.js';
|
|
9
|
+
/**
|
|
10
|
+
* 저작면 오른쪽 칸 — 속성과 판정을 탭으로 겹쳐 둔다.
|
|
11
|
+
*
|
|
12
|
+
* ## 왜 위아래로 나누지 않나
|
|
13
|
+
*
|
|
14
|
+
* 둘 다 세로로 긴 화면이다. 위아래로 나누면 양쪽 다 잘려서, 속성은 재질까지 못
|
|
15
|
+
* 내려가고 판정은 무게까지 못 내려간다. 탭으로 겹치면 각자 칸 전체를 쓴다.
|
|
16
|
+
*
|
|
17
|
+
* ## 그래도 개수는 늘 보인다
|
|
18
|
+
*
|
|
19
|
+
* 성숙도의 축 하나 —「몇 개까지 편한가」— 는 부품을 고칠 때마다 움직이고, 그것이
|
|
20
|
+
* 보여야 저작자가 고칠 수 있다. 탭 뒤로 숨으면 저장하고 나서야 알게 된다. 그래서 그
|
|
21
|
+
* 숫자만 **탭 이름표에 남긴다** — 성숙도를 덮어 두고 속성을 만져도 따라 움직이는
|
|
22
|
+
* 것이 보인다.
|
|
23
|
+
*
|
|
24
|
+
* 판정은 화면에서 감춰도 **DOM 에는 남는다.** 떼었다 붙이면 다시 세느라 값이 잠깐
|
|
25
|
+
* 비고, 펼쳐 둔 추이 그림 같은 상태도 날아간다.
|
|
26
|
+
*/
|
|
27
|
+
let FigureSide = class FigureSide extends localize(i18next)(LitElement) {
|
|
28
|
+
constructor() {
|
|
29
|
+
super(...arguments);
|
|
30
|
+
this.parts = [];
|
|
31
|
+
this.selected = -1;
|
|
32
|
+
/** 저작 화면의 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다. */
|
|
33
|
+
this.view = DEFAULT_VIEW;
|
|
34
|
+
this.tab = 'properties';
|
|
35
|
+
}
|
|
36
|
+
static { this.styles = css `
|
|
37
|
+
:host {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
min-height: 0;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
background-color: var(--md-sys-color-surface-container-lowest);
|
|
43
|
+
border-left: 1px solid var(--md-sys-color-outline-variant);
|
|
44
|
+
color: var(--md-sys-color-on-surface);
|
|
45
|
+
font: var(--label-font, inherit);
|
|
46
|
+
/*
|
|
47
|
+
이 패널의 본문 크기를 여기서 한 번 정한다.
|
|
48
|
+
|
|
49
|
+
전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를
|
|
50
|
+
물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.
|
|
51
|
+
이 파일에서만 두 번 났다.
|
|
52
|
+
|
|
53
|
+
form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.
|
|
54
|
+
그것 말고는 여기 값을 따른다.
|
|
55
|
+
*/
|
|
56
|
+
font-size: 0.76rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
div[tabs] {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex: none;
|
|
62
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
63
|
+
}
|
|
64
|
+
button[tab] {
|
|
65
|
+
flex: 1;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
gap: 6px;
|
|
70
|
+
padding: 8px 6px;
|
|
71
|
+
font: var(--label-font, inherit);
|
|
72
|
+
font-size: 0.76rem;
|
|
73
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
74
|
+
background: none;
|
|
75
|
+
border: none;
|
|
76
|
+
/* 고른 탭을 밑줄로 말한다 — 칸을 색으로 채우면 아래 내용과 경계가 흐려진다 */
|
|
77
|
+
border-bottom: 2px solid transparent;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
}
|
|
80
|
+
button[tab]:hover:not([on]) {
|
|
81
|
+
background-color: var(--md-sys-color-surface-container);
|
|
82
|
+
}
|
|
83
|
+
button[tab][on] {
|
|
84
|
+
font-weight: 700;
|
|
85
|
+
color: var(--md-sys-color-primary);
|
|
86
|
+
border-bottom-color: var(--md-sys-color-primary);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* 판정을 덮어 두어도 개수는 여기 남는다 */
|
|
90
|
+
span[badge] {
|
|
91
|
+
padding: 1px 7px;
|
|
92
|
+
font-size: 0.72rem;
|
|
93
|
+
font-weight: 700;
|
|
94
|
+
color: var(--md-sys-color-on-primary-container);
|
|
95
|
+
background-color: var(--md-sys-color-primary-container);
|
|
96
|
+
border-radius: 999em;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
figure-inspector,
|
|
100
|
+
figure-report,
|
|
101
|
+
figure-settings {
|
|
102
|
+
flex: 1;
|
|
103
|
+
min-height: 0;
|
|
104
|
+
border-left: none;
|
|
105
|
+
}
|
|
106
|
+
/* 감추되 떼지는 않는다 */
|
|
107
|
+
[off] {
|
|
108
|
+
display: none;
|
|
109
|
+
}
|
|
110
|
+
`; }
|
|
111
|
+
render() {
|
|
112
|
+
return html `
|
|
113
|
+
<div tabs>
|
|
114
|
+
${this.renderTab('properties', i18next.t('label.properties'))}
|
|
115
|
+
${this.renderTab('verdict', i18next.t('label.maturity'), this.instances)}
|
|
116
|
+
${this.renderTab('view', i18next.t('label.view'))}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<figure-inspector
|
|
120
|
+
?off=${this.tab !== 'properties'}
|
|
121
|
+
.board=${this.board}
|
|
122
|
+
.parts=${this.parts}
|
|
123
|
+
.selected=${this.selected}
|
|
124
|
+
></figure-inspector>
|
|
125
|
+
|
|
126
|
+
<figure-report
|
|
127
|
+
?off=${this.tab !== 'verdict'}
|
|
128
|
+
.source=${this.source}
|
|
129
|
+
@verdict-changed=${(e) => (this.instances = e.detail.instances)}
|
|
130
|
+
></figure-report>
|
|
131
|
+
|
|
132
|
+
<figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
renderTab(tab, label, badge) {
|
|
136
|
+
return html `
|
|
137
|
+
<button tab ?on=${this.tab === tab} @click=${() => (this.tab = tab)}>
|
|
138
|
+
${label}${badge == null ? '' : html `<span badge>${badge.toLocaleString()}</span>`}
|
|
139
|
+
</button>
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
__decorate([
|
|
144
|
+
property({ type: Object }),
|
|
145
|
+
__metadata("design:type", Object)
|
|
146
|
+
], FigureSide.prototype, "board", void 0);
|
|
147
|
+
__decorate([
|
|
148
|
+
property({ type: Array }),
|
|
149
|
+
__metadata("design:type", Array)
|
|
150
|
+
], FigureSide.prototype, "parts", void 0);
|
|
151
|
+
__decorate([
|
|
152
|
+
property({ type: Object }),
|
|
153
|
+
__metadata("design:type", Object)
|
|
154
|
+
], FigureSide.prototype, "source", void 0);
|
|
155
|
+
__decorate([
|
|
156
|
+
property({ type: Number }),
|
|
157
|
+
__metadata("design:type", Object)
|
|
158
|
+
], FigureSide.prototype, "selected", void 0);
|
|
159
|
+
__decorate([
|
|
160
|
+
property({ type: Object }),
|
|
161
|
+
__metadata("design:type", Object)
|
|
162
|
+
], FigureSide.prototype, "view", void 0);
|
|
163
|
+
__decorate([
|
|
164
|
+
state(),
|
|
165
|
+
__metadata("design:type", String)
|
|
166
|
+
], FigureSide.prototype, "tab", void 0);
|
|
167
|
+
__decorate([
|
|
168
|
+
state(),
|
|
169
|
+
__metadata("design:type", Number)
|
|
170
|
+
], FigureSide.prototype, "instances", void 0);
|
|
171
|
+
FigureSide = __decorate([
|
|
172
|
+
customElement('figure-side')
|
|
173
|
+
], FigureSide);
|
|
174
|
+
export { FigureSide };
|
|
175
|
+
//# sourceMappingURL=figure-side.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-side.js","sourceRoot":"","sources":["../../client/modeller/figure-side.ts"],"names":[],"mappings":";AAAA,OAAO,uBAAuB,CAAA;AAC9B,OAAO,oBAAoB,CAAA;AAC3B,OAAO,sBAAsB,CAAA;AAE7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAIjD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAK/C;;;;;;;;;;;;;;;;;GAiBG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAAtD;;QAoFsB,UAAK,GAAgB,EAAE,CAAA;QAEtB,aAAQ,GAAG,CAAC,CAAC,CAAA;QACzC,6CAA6C;QACjB,SAAI,GAAiB,YAAY,CAAA;QAE5C,QAAG,GAAQ,YAAY,CAAA;IAoC1C,CAAC;aA7HQ,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0ElB,AA1EY,CA0EZ;IAmBD,MAAM;QACJ,OAAO,IAAI,CAAA;;UAEL,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;UAC3D,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;UACtE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;;;;eAI1C,IAAI,CAAC,GAAG,KAAK,YAAY;iBACvB,IAAI,CAAC,KAAK;iBACV,IAAI,CAAC,KAAK;oBACP,IAAI,CAAC,QAAQ;;;;eAIlB,IAAI,CAAC,GAAG,KAAK,SAAS;kBACnB,IAAI,CAAC,MAAM;2BACF,CAAC,CAAc,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;;;8BAGtD,IAAI,CAAC,GAAG,KAAK,MAAM,UAAU,IAAI,CAAC,IAAI;KAC/D,CAAA;IACH,CAAC;IAEO,SAAS,CAAC,GAAQ,EAAE,KAAa,EAAE,KAAc;QACvD,OAAO,IAAI,CAAA;wBACS,IAAI,CAAC,GAAG,KAAK,GAAG,WAAW,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;UAC/D,KAAK,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA,eAAe,KAAK,CAAC,cAAc,EAAE,SAAS;;KAEpF,CAAA;IACH,CAAC;;AA1C2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;yCAAmB;AACnB;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;yCAAwB;AACtB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0CAAsB;AACrB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;4CAAc;AAEb;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;wCAAkC;AAE5C;IAAhB,KAAK,EAAE;;uCAAgC;AAEvB;IAAhB,KAAK,EAAE;;6CAA2B;AA5FxB,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CA8HtB","sourcesContent":["import './figure-inspector.js'\nimport './figure-report.js'\nimport './figure-settings.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 { BoardModel, PartModel } from './figure-source.js'\nimport { DEFAULT_VIEW } from './figure-view.js'\nimport type { ViewSettings } from './figure-view.js'\n\ntype Tab = 'properties' | 'verdict' | 'view'\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 display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 8px 6px;\n font: var(--label-font, inherit);\n font-size: 0.76rem;\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 span[badge] {\n padding: 1px 7px;\n font-size: 0.72rem;\n font-weight: 700;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n }\n\n figure-inspector,\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: Object }) board?: BoardModel\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 render() {\n return html`\n <div tabs>\n ${this.renderTab('properties', i18next.t('label.properties'))}\n ${this.renderTab('verdict', i18next.t('label.maturity'), this.instances)}\n ${this.renderTab('view', i18next.t('label.view'))}\n </div>\n\n <figure-inspector\n ?off=${this.tab !== 'properties'}\n .board=${this.board}\n .parts=${this.parts}\n .selected=${this.selected}\n ></figure-inspector>\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\n private renderTab(tab: Tab, label: string, badge?: number) {\n return html`\n <button tab ?on=${this.tab === tab} @click=${() => (this.tab = tab)}>\n ${label}${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { DetailLevel, FigurePart, FigureSource, Vec3 } from '@hatiolab/figure-model';
|
|
2
|
+
/**
|
|
3
|
+
* Scene model <-> `FigureSource`.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is the only place that converts
|
|
6
|
+
*
|
|
7
|
+
* While authoring, the source of truth is the **scene model** -- part
|
|
8
|
+
* components sitting on a board. `FigureSource` is produced from it when the
|
|
9
|
+
* asset is saved or scored.
|
|
10
|
+
*
|
|
11
|
+
* The two use different vocabularies. The scene inherited 2D canvas terms
|
|
12
|
+
* (`width`/`height` in plan, `depth` for height, `left`/`top`/`zPos` for
|
|
13
|
+
* position); the format uses the axes 3D tools use (`x` right, `y` up, `z`
|
|
14
|
+
* front, position at the part's **centre**).
|
|
15
|
+
*
|
|
16
|
+
* **Convert in one place.** Two places drift, and this format has already had
|
|
17
|
+
* one incident caused by axes disagreeing.
|
|
18
|
+
*
|
|
19
|
+
* ## It was called `fold`, and that was wrong
|
|
20
|
+
*
|
|
21
|
+
* `fold` is an established term -- it reduces a structure to a summary, and
|
|
22
|
+
* this repo already uses it correctly (`foldKpi`, `foldProbeResults`). Nothing
|
|
23
|
+
* is reduced here: the same information comes out in a different shape, and it
|
|
24
|
+
* goes back unchanged. A reader who learned `fold` from `foldKpi` reads this
|
|
25
|
+
* one as reducing something, and the user did exactly that.
|
|
26
|
+
*
|
|
27
|
+
* ## The mapping
|
|
28
|
+
*
|
|
29
|
+
* The board is the base box; its centre is the format's origin.
|
|
30
|
+
*
|
|
31
|
+
* format scene
|
|
32
|
+
* ──────────────── ──────────────────────────────
|
|
33
|
+
* base.x board width
|
|
34
|
+
* base.y board depth <- height
|
|
35
|
+
* base.z board height
|
|
36
|
+
*
|
|
37
|
+
* size.x width
|
|
38
|
+
* size.y depth
|
|
39
|
+
* size.z height
|
|
40
|
+
*
|
|
41
|
+
* position.x left + width/2 - board width/2
|
|
42
|
+
* position.y zPos + depth/2 - board depth/2 <- zPos is the underside
|
|
43
|
+
* position.z top + height/2 - board height/2
|
|
44
|
+
*
|
|
45
|
+
* rotation.x rotationX (scene radians, format degrees)
|
|
46
|
+
* rotation.y -rotation
|
|
47
|
+
* rotation.z rotationY
|
|
48
|
+
*
|
|
49
|
+
* The rotation names are crossed because of the scene: `rotationY` becomes
|
|
50
|
+
* three's z (`real-object.ts`, `get rotation()`). That stays inside this table.
|
|
51
|
+
*
|
|
52
|
+
* ## Fields nothing reads yet still travel
|
|
53
|
+
*
|
|
54
|
+
* `materialSlot`, `keepRound`, `sizing`, `anchor`, `repeat` and `label` exist in the format
|
|
55
|
+
* and the editor ignores them. They still have to survive a round trip. When a
|
|
56
|
+
* source proposed by AI is opened, edited and written back, a field that
|
|
57
|
+
* disappears **without an error** is one the author cannot know about.
|
|
58
|
+
*
|
|
59
|
+
* **Five have gone that way here**: `ports`, `animations`, `anchor`,
|
|
60
|
+
* `shape.hollow` and `keepRound`. The last one the test caught first, which
|
|
61
|
+
* is the guard below actually earning its keep. The third showed up as a conveyor motor that stayed on the
|
|
62
|
+
* floor no matter what the sample said; the fourth as a hollowed tray that
|
|
63
|
+
* came out a solid block.
|
|
64
|
+
*
|
|
65
|
+
* All four passed the round-trip test, because the fixture it uses did not
|
|
66
|
+
* carry the field. So the test now reads the **field list the format declares**
|
|
67
|
+
* rather than trusting the fixture.
|
|
68
|
+
*
|
|
69
|
+
* `shape` is unpacked into `round`, `path` and `hollow` rather than carried
|
|
70
|
+
* whole, because the editor holds those as separate state. Every field added
|
|
71
|
+
* under `shape` costs a line here, and forgetting that line was the fourth
|
|
72
|
+
* of the four.
|
|
73
|
+
*/
|
|
74
|
+
/** 보드의 상태. 기준 상자이자 원본 데이터의 뼈대다. */
|
|
75
|
+
export interface BoardModel {
|
|
76
|
+
/** 기준 상자. 씬의 용어 그대로다. */
|
|
77
|
+
width: number;
|
|
78
|
+
height: number;
|
|
79
|
+
depth: number;
|
|
80
|
+
/** 컴포넌트 타입 이름. **저장되는 식별자다.** */
|
|
81
|
+
figureType?: string;
|
|
82
|
+
detailLevel?: DetailLevel;
|
|
83
|
+
styleKit?: string;
|
|
84
|
+
/**
|
|
85
|
+
* 형상 전체의 기준점.
|
|
86
|
+
*
|
|
87
|
+
* **부품의 `anchor` 와 다른 것이다.** 이쪽은 점(`Vec3`)이고 저쪽은 축별로 어느 면을
|
|
88
|
+
* 붙잡느냐다. 한 형식 안에 같은 낱말이 둘인 것은 좋지 않으나, 이름을 바꾸면 저장된
|
|
89
|
+
* 원본이 따라와야 하므로 여기서 정하지 않는다.
|
|
90
|
+
*/
|
|
91
|
+
anchor?: Vec3;
|
|
92
|
+
/**
|
|
93
|
+
* 움직임. **부품 바깥에 있다** — clip 하나가 부품 여럿을 함께 움직이기 때문이다.
|
|
94
|
+
*
|
|
95
|
+
* 편집면이 아직 이것을 만지지 않는다. 그래도 여기 실어 두는 것은, 안 실으면 열었다
|
|
96
|
+
* 저장하는 것만으로 **조용히 지워지기** 때문이다.
|
|
97
|
+
*/
|
|
98
|
+
animations?: FigureSource['animations'];
|
|
99
|
+
}
|
|
100
|
+
/** 부품 컴포넌트의 상태. 씬의 용어 그대로다. */
|
|
101
|
+
export interface PartModel {
|
|
102
|
+
type: 'figure-part';
|
|
103
|
+
/** 부품 이름. **저장되는 식별자다.** 씬은 이 값을 검색에도 쓴다. */
|
|
104
|
+
name: string;
|
|
105
|
+
primitive: FigurePart['primitive'];
|
|
106
|
+
left: number;
|
|
107
|
+
top: number;
|
|
108
|
+
width: number;
|
|
109
|
+
height: number;
|
|
110
|
+
/** 부피의 **밑면** 높이. 씬의 'floor' 배치에서 그렇게 정해져 있다. */
|
|
111
|
+
zPos: number;
|
|
112
|
+
depth: number;
|
|
113
|
+
/** 라디안. 씬이 그렇게 쓴다. */
|
|
114
|
+
rotationX?: number;
|
|
115
|
+
rotation?: number;
|
|
116
|
+
rotationY?: number;
|
|
117
|
+
segments?: number;
|
|
118
|
+
round?: number;
|
|
119
|
+
path?: {
|
|
120
|
+
x: number;
|
|
121
|
+
y: number;
|
|
122
|
+
}[];
|
|
123
|
+
/** 속을 판 단면. 편집면이 아직 안 만지지만 잃어버리면 통이 통짜 상자가 된다. */
|
|
124
|
+
hollow?: NonNullable<FigurePart['shape']>['hollow'];
|
|
125
|
+
/**
|
|
126
|
+
* 편집 중에만 쓰는 상태 — **저장 형식으로 넘어가지 않는다.**
|
|
127
|
+
*
|
|
128
|
+
* 씬이 이미 갖고 있는 속성이다. 편집 모드에서는 `faint`(반투명 + 색을 버리고 회색으로
|
|
129
|
+
* 수렴)로 보이고, 뷰 모드에서는 아예 안 그려진다. 판단은 씬의 `modeling-faint` 한 곳에
|
|
130
|
+
* 있고 2D 캔버스·3D 재질·DOM 세 채널이 같이 쓴다.
|
|
131
|
+
*
|
|
132
|
+
* 숨김은 "지금 이 뒤를 만지려고" 하는 것이지 그 부품의 성질이 아니다. 그래서
|
|
133
|
+
* `toFigureSource` 가 싣지 않고, 다시 열면 전부 보이는 상태로 돌아온다.
|
|
134
|
+
*/
|
|
135
|
+
hidden?: boolean;
|
|
136
|
+
token?: string;
|
|
137
|
+
preset?: FigurePart['material']['preset'];
|
|
138
|
+
flatShading?: boolean;
|
|
139
|
+
transparent?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* 자체발광. 없으면 안 빛난다.
|
|
142
|
+
*
|
|
143
|
+
* 바깥의 `token` 이 **꺼진 모습**이고 이 안의 `token` 이 **켜진 모습**이다 — 꺼진
|
|
144
|
+
* 전구는 빨갛지 않고 어두운 플라스틱이다.
|
|
145
|
+
*/
|
|
146
|
+
emissive?: FigurePart['material']['emissive'];
|
|
147
|
+
surface?: FigurePart['material']['surface'];
|
|
148
|
+
materialSlot?: FigurePart['materialSlot'];
|
|
149
|
+
/** 단면을 원으로 지킬까. 곡면만 뜻이 있고 기본은 지키는 것이다. */
|
|
150
|
+
keepRound?: FigurePart['keepRound'];
|
|
151
|
+
sizing?: FigurePart['sizing'];
|
|
152
|
+
/** 축마다 어느 면을 붙잡나. `sizing` 이 정한 것을 덮어쓴다. */
|
|
153
|
+
anchor?: FigurePart['anchor'];
|
|
154
|
+
repeat?: FigurePart['repeat'];
|
|
155
|
+
label?: FigurePart['label'];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 판의 절반. 형식의 원점이 판 중심이라 이 값이 늘 함께 다닌다.
|
|
159
|
+
*
|
|
160
|
+
* 축 이름은 **형식의 것**이다 — x 가로 · y 높이 · z 세로.
|
|
161
|
+
*/
|
|
162
|
+
export declare function halfOf(board: BoardModel): Vec3;
|
|
163
|
+
/**
|
|
164
|
+
* 저작자가 보는 축이 씬의 어느 용어인가.
|
|
165
|
+
*
|
|
166
|
+
* 씬은 2D 캔버스에서 온 용어를 쓰므로 축 이름이 그대로 없다. 이 표가 **유일한** 대응이다 —
|
|
167
|
+
* 편집 규칙이 저마다 자기 표를 들면 한 곳을 고칠 때 다른 곳이 남는다.
|
|
168
|
+
*/
|
|
169
|
+
export declare const AXIS_KEYS: {
|
|
170
|
+
readonly x: {
|
|
171
|
+
readonly at: "left";
|
|
172
|
+
readonly size: "width";
|
|
173
|
+
};
|
|
174
|
+
readonly y: {
|
|
175
|
+
readonly at: "zPos";
|
|
176
|
+
readonly size: "depth";
|
|
177
|
+
};
|
|
178
|
+
readonly z: {
|
|
179
|
+
readonly at: "top";
|
|
180
|
+
readonly size: "height";
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
export type PlaneAxis = keyof typeof AXIS_KEYS;
|
|
184
|
+
/** 판이 그 축으로 얼마나 넓은가. */
|
|
185
|
+
export declare function boardExtent(board: BoardModel, axis: PlaneAxis): number;
|
|
186
|
+
/** 부품 하나를 저장 형식으로 변환한다. 저작 화면이 저작자의 어휘로 보일 때 쓴다. */
|
|
187
|
+
export declare function partToFigure(part: PartModel, board: BoardModel): FigurePart;
|
|
188
|
+
/** 부품 하나를 씬 용어로 되돌린다. */
|
|
189
|
+
export declare function partFromFigure(part: FigurePart, board: BoardModel): PartModel;
|
|
190
|
+
/** 씬 모델을 저장 형식으로 변환한다. */
|
|
191
|
+
export declare function toFigureSource(board: BoardModel, parts: PartModel[]): FigureSource;
|
|
192
|
+
/** 형식을 씬 모델로 편다. */
|
|
193
|
+
export declare function fromFigureSource(source: FigureSource): {
|
|
194
|
+
board: BoardModel;
|
|
195
|
+
parts: PartModel[];
|
|
196
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const RAD_TO_DEG = 180 / Math.PI;
|
|
2
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
3
|
+
/** 값이 있는 것만 남긴다. 없는 필드를 `undefined` 로 두면 접었다 편 것이 원본과 달라진다. */
|
|
4
|
+
function withoutEmpty(value) {
|
|
5
|
+
const out = {};
|
|
6
|
+
for (const [key, item] of Object.entries(value)) {
|
|
7
|
+
if (item !== undefined)
|
|
8
|
+
out[key] = item;
|
|
9
|
+
}
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
/** 소수점 넷째 자리까지. 라디안↔도를 오가며 생기는 꼬리를 자른다. */
|
|
13
|
+
function round4(value) {
|
|
14
|
+
return Math.round(value * 1e4) / 1e4;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 판의 절반. 형식의 원점이 판 중심이라 이 값이 늘 함께 다닌다.
|
|
18
|
+
*
|
|
19
|
+
* 축 이름은 **형식의 것**이다 — x 가로 · y 높이 · z 세로.
|
|
20
|
+
*/
|
|
21
|
+
export function halfOf(board) {
|
|
22
|
+
return { x: board.width / 2, y: board.depth / 2, z: board.height / 2 };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 저작자가 보는 축이 씬의 어느 용어인가.
|
|
26
|
+
*
|
|
27
|
+
* 씬은 2D 캔버스에서 온 용어를 쓰므로 축 이름이 그대로 없다. 이 표가 **유일한** 대응이다 —
|
|
28
|
+
* 편집 규칙이 저마다 자기 표를 들면 한 곳을 고칠 때 다른 곳이 남는다.
|
|
29
|
+
*/
|
|
30
|
+
export const AXIS_KEYS = {
|
|
31
|
+
x: { at: 'left', size: 'width' },
|
|
32
|
+
y: { at: 'zPos', size: 'depth' },
|
|
33
|
+
z: { at: 'top', size: 'height' }
|
|
34
|
+
};
|
|
35
|
+
/** 판이 그 축으로 얼마나 넓은가. */
|
|
36
|
+
export function boardExtent(board, axis) {
|
|
37
|
+
return axis === 'x' ? board.width : axis === 'y' ? board.depth : board.height;
|
|
38
|
+
}
|
|
39
|
+
/** 부품 하나를 저장 형식으로 변환한다. 저작 화면이 저작자의 어휘로 보일 때 쓴다. */
|
|
40
|
+
export function partToFigure(part, board) {
|
|
41
|
+
return partTo(part, halfOf(board));
|
|
42
|
+
}
|
|
43
|
+
/** 부품 하나를 씬 용어로 되돌린다. */
|
|
44
|
+
export function partFromFigure(part, board) {
|
|
45
|
+
return partFrom(part, halfOf(board));
|
|
46
|
+
}
|
|
47
|
+
/** 씬 모델을 저장 형식으로 변환한다. */
|
|
48
|
+
export function toFigureSource(board, parts) {
|
|
49
|
+
const half = halfOf(board);
|
|
50
|
+
return withoutEmpty({
|
|
51
|
+
type: board.figureType ?? '',
|
|
52
|
+
base: { x: board.width, y: board.depth, z: board.height },
|
|
53
|
+
anchor: board.anchor,
|
|
54
|
+
detailLevel: board.detailLevel,
|
|
55
|
+
styleKit: board.styleKit,
|
|
56
|
+
parts: parts.map(part => partTo(part, half)),
|
|
57
|
+
animations: board.animations
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function partTo(part, half) {
|
|
61
|
+
const rotation = withoutEmpty({
|
|
62
|
+
x: part.rotationX ? round4(part.rotationX * RAD_TO_DEG) : undefined,
|
|
63
|
+
y: part.rotation ? round4(-part.rotation * RAD_TO_DEG) : undefined,
|
|
64
|
+
z: part.rotationY ? round4(part.rotationY * RAD_TO_DEG) : undefined
|
|
65
|
+
});
|
|
66
|
+
const shape = withoutEmpty({ round: part.round, path: part.path, hollow: part.hollow });
|
|
67
|
+
const material = withoutEmpty({
|
|
68
|
+
token: part.token,
|
|
69
|
+
preset: part.preset,
|
|
70
|
+
flatShading: part.flatShading,
|
|
71
|
+
transparent: part.transparent,
|
|
72
|
+
emissive: part.emissive,
|
|
73
|
+
surface: part.surface
|
|
74
|
+
});
|
|
75
|
+
return withoutEmpty({
|
|
76
|
+
name: part.name,
|
|
77
|
+
primitive: part.primitive,
|
|
78
|
+
transform: withoutEmpty({
|
|
79
|
+
position: {
|
|
80
|
+
x: round4(part.left + part.width / 2 - half.x),
|
|
81
|
+
y: round4(part.zPos + part.depth / 2 - half.y),
|
|
82
|
+
z: round4(part.top + part.height / 2 - half.z)
|
|
83
|
+
},
|
|
84
|
+
size: { x: part.width, y: part.depth, z: part.height },
|
|
85
|
+
rotation: Object.keys(rotation).length > 0 ? rotation : undefined
|
|
86
|
+
}),
|
|
87
|
+
segments: part.segments,
|
|
88
|
+
shape: Object.keys(shape).length > 0 ? shape : undefined,
|
|
89
|
+
material,
|
|
90
|
+
materialSlot: part.materialSlot,
|
|
91
|
+
keepRound: part.keepRound,
|
|
92
|
+
sizing: part.sizing,
|
|
93
|
+
anchor: part.anchor,
|
|
94
|
+
repeat: part.repeat,
|
|
95
|
+
label: part.label
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** 형식을 씬 모델로 편다. */
|
|
99
|
+
export function fromFigureSource(source) {
|
|
100
|
+
const half = { x: source.base.x / 2, y: source.base.y / 2, z: source.base.z / 2 };
|
|
101
|
+
const board = withoutEmpty({
|
|
102
|
+
width: source.base.x,
|
|
103
|
+
height: source.base.z,
|
|
104
|
+
depth: source.base.y,
|
|
105
|
+
figureType: source.type,
|
|
106
|
+
detailLevel: source.detailLevel,
|
|
107
|
+
styleKit: source.styleKit,
|
|
108
|
+
anchor: source.anchor,
|
|
109
|
+
animations: source.animations
|
|
110
|
+
});
|
|
111
|
+
return { board, parts: source.parts.map(part => partFrom(part, half)) };
|
|
112
|
+
}
|
|
113
|
+
function partFrom(part, half) {
|
|
114
|
+
const { position, size, rotation } = part.transform;
|
|
115
|
+
const material = part.material ?? {};
|
|
116
|
+
return withoutEmpty({
|
|
117
|
+
type: 'figure-part',
|
|
118
|
+
name: part.name,
|
|
119
|
+
primitive: part.primitive,
|
|
120
|
+
left: round4(half.x + position.x - size.x / 2),
|
|
121
|
+
top: round4(half.z + position.z - size.z / 2),
|
|
122
|
+
width: size.x,
|
|
123
|
+
height: size.z,
|
|
124
|
+
zPos: round4(half.y + position.y - size.y / 2),
|
|
125
|
+
depth: size.y,
|
|
126
|
+
/*
|
|
127
|
+
라디안은 자르지 않는다.
|
|
128
|
+
|
|
129
|
+
소수 넷째에서 잘랐더니 도로 되돌릴 때 57 배로 커져 45 도가 45.0001 이 됐다.
|
|
130
|
+
자르는 것은 **사람이 읽는 쪽**(도)에서만 한다.
|
|
131
|
+
*/
|
|
132
|
+
rotationX: rotation?.x ? rotation.x * DEG_TO_RAD : undefined,
|
|
133
|
+
rotation: rotation?.y ? -rotation.y * DEG_TO_RAD : undefined,
|
|
134
|
+
rotationY: rotation?.z ? rotation.z * DEG_TO_RAD : undefined,
|
|
135
|
+
segments: part.segments,
|
|
136
|
+
round: part.shape?.round,
|
|
137
|
+
path: part.shape?.path,
|
|
138
|
+
hollow: part.shape?.hollow,
|
|
139
|
+
token: material.token,
|
|
140
|
+
preset: material.preset,
|
|
141
|
+
flatShading: material.flatShading,
|
|
142
|
+
transparent: material.transparent,
|
|
143
|
+
emissive: material.emissive,
|
|
144
|
+
surface: material.surface,
|
|
145
|
+
materialSlot: part.materialSlot,
|
|
146
|
+
keepRound: part.keepRound,
|
|
147
|
+
sizing: part.sizing,
|
|
148
|
+
anchor: part.anchor,
|
|
149
|
+
repeat: part.repeat,
|
|
150
|
+
label: part.label
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=figure-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-source.js","sourceRoot":"","sources":["../../client/modeller/figure-source.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DetailLevel, FigurePart, FigureSource, Vec3 } 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 board. `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 board is the base box; its centre is the format's origin.\n *\n * format scene\n * ──────────────── ──────────────────────────────\n * base.x board width\n * base.y board depth <- height\n * base.z board height\n *\n * size.x width\n * size.y depth\n * size.z height\n *\n * position.x left + width/2 - board width/2\n * position.y zPos + depth/2 - board depth/2 <- zPos is the underside\n * position.z top + height/2 - board height/2\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 BoardModel {\n /** 기준 상자. 씬의 용어 그대로다. */\n width: number\n height: number\n depth: number\n /** 컴포넌트 타입 이름. **저장되는 식별자다.** */\n figureType?: string\n detailLevel?: DetailLevel\n styleKit?: string\n /**\n * 형상 전체의 기준점.\n *\n * **부품의 `anchor` 와 다른 것이다.** 이쪽은 점(`Vec3`)이고 저쪽은 축별로 어느 면을\n * 붙잡느냐다. 한 형식 안에 같은 낱말이 둘인 것은 좋지 않으나, 이름을 바꾸면 저장된\n * 원본이 따라와야 하므로 여기서 정하지 않는다.\n */\n anchor?: Vec3\n /**\n * 움직임. **부품 바깥에 있다** — clip 하나가 부품 여럿을 함께 움직이기 때문이다.\n *\n * 편집면이 아직 이것을 만지지 않는다. 그래도 여기 실어 두는 것은, 안 실으면 열었다\n * 저장하는 것만으로 **조용히 지워지기** 때문이다.\n */\n animations?: FigureSource['animations']\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 token?: string\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\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/** 소수점 넷째 자리까지. 라디안↔도를 오가며 생기는 꼬리를 자른다. */\nfunction round4(value: number): number {\n return Math.round(value * 1e4) / 1e4\n}\n\n/**\n * 판의 절반. 형식의 원점이 판 중심이라 이 값이 늘 함께 다닌다.\n *\n * 축 이름은 **형식의 것**이다 — x 가로 · y 높이 · z 세로.\n */\nexport function halfOf(board: BoardModel): Vec3 {\n return { x: board.width / 2, y: board.depth / 2, z: board.height / 2 }\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 boardExtent(board: BoardModel, axis: PlaneAxis): number {\n return axis === 'x' ? board.width : axis === 'y' ? board.depth : board.height\n}\n\n/** 부품 하나를 저장 형식으로 변환한다. 저작 화면이 저작자의 어휘로 보일 때 쓴다. */\nexport function partToFigure(part: PartModel, board: BoardModel): FigurePart {\n return partTo(part, halfOf(board))\n}\n\n/** 부품 하나를 씬 용어로 되돌린다. */\nexport function partFromFigure(part: FigurePart, board: BoardModel): PartModel {\n return partFrom(part, halfOf(board))\n}\n\n/** 씬 모델을 저장 형식으로 변환한다. */\nexport function toFigureSource(board: BoardModel, parts: PartModel[]): FigureSource {\n const half = halfOf(board)\n\n return withoutEmpty({\n type: board.figureType ?? '',\n base: { x: board.width, y: board.depth, z: board.height },\n anchor: board.anchor,\n detailLevel: board.detailLevel,\n styleKit: board.styleKit,\n parts: parts.map(part => partTo(part, half)),\n animations: board.animations\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 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 - half.y),\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,\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 }) as FigurePart\n}\n\n/** 형식을 씬 모델로 편다. */\nexport function fromFigureSource(source: FigureSource): { board: BoardModel; parts: PartModel[] } {\n const half: Vec3 = { x: source.base.x / 2, y: source.base.y / 2, z: source.base.z / 2 }\n\n const board = withoutEmpty({\n width: source.base.x,\n height: source.base.z,\n depth: source.base.y,\n figureType: source.type,\n detailLevel: source.detailLevel,\n styleKit: source.styleKit,\n anchor: source.anchor,\n animations: source.animations\n }) as BoardModel\n\n return { board, 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(half.y + 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 }) as PartModel\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 저작 화면의 **보기 설정**.
|
|
3
|
+
*
|
|
4
|
+
* ## 저장되지 않는다
|
|
5
|
+
*
|
|
6
|
+
* 이것은 자산이 아니라 **작업대**다. 조명을 밝게 하고 바닥을 켠 것은 그 사람이 지금
|
|
7
|
+
* 보기 편한 상태일 뿐, 그 자산의 성질이 아니다. 그래서 `FigureSource` 에 안 들어간다 —
|
|
8
|
+
* `toFigureSource` 는 판에서 `width` · `height` · `depth` 와 몇 가지만 집어 가고 나머지는
|
|
9
|
+
* 버린다.
|
|
10
|
+
*
|
|
11
|
+
* 다른 사람이 같은 자산을 열면 자기 설정으로 본다. 그것이 맞다.
|
|
12
|
+
*
|
|
13
|
+
* ## 씬이 아는 이름을 그대로 쓴다
|
|
14
|
+
*
|
|
15
|
+
* 여기 이름은 전부 `model-layer` 의 상태 이름이다. 옮겨 적는 표를 두지 않는다 — 표를
|
|
16
|
+
* 두면 씬이 새 설정을 받아도 이쪽이 모른다.
|
|
17
|
+
*
|
|
18
|
+
* 값이 바뀌면 씬이 스스로 반영한다(`three-capability` 의 `onchange`). 다만 `grid` 는
|
|
19
|
+
* 한동안 3D 를 세울 때만 읽혔다 — things-scene 10.1.5 에서 고쳤다.
|
|
20
|
+
*/
|
|
21
|
+
export interface ViewSettings {
|
|
22
|
+
/** 좌표 바탕(축 가이드). 원점과 눈금을 보인다. */
|
|
23
|
+
grid: boolean;
|
|
24
|
+
/** 축 이름표(X · Y · Z). 눈금만 두고 글자를 끌 수 있다. */
|
|
25
|
+
gridLabels: boolean;
|
|
26
|
+
/** 판의 바닥면. 좌표 바탕과 겹치므로 기본은 끈다. */
|
|
27
|
+
floor: boolean;
|
|
28
|
+
/** Environment — 반사(IBL). 금속 재질이 무엇을 비출지 정한다. */
|
|
29
|
+
environment: EnvironmentName;
|
|
30
|
+
/** Sky Color — 배경색. */
|
|
31
|
+
skyColor: string;
|
|
32
|
+
/** Key Light Intensity. */
|
|
33
|
+
dirLightIntensity: number;
|
|
34
|
+
/** Key Light — Follow Camera. 끄면 그림자 방향이 고정된다. */
|
|
35
|
+
dirLightFollowCamera: boolean;
|
|
36
|
+
/** Key Light — Cast Shadow. */
|
|
37
|
+
dirShadowEnabled: boolean;
|
|
38
|
+
/** Hemisphere Light Intensity — 그늘진 면을 들어올린다. */
|
|
39
|
+
hemiIntensity: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 환경 프리셋 — **`scene-manager` 의 `SkyPreset` 과 같은 여덟이다.**
|
|
43
|
+
*
|
|
44
|
+
* 한동안 다섯만 적었다. 씬은 여덟을 다 받는데 이쪽이 셋을 몰라서 고를 수 없었다.
|
|
45
|
+
* operato-board 의 3D 씬 속성 패널도 같은 여덟을 보인다.
|
|
46
|
+
*/
|
|
47
|
+
export declare const ENVIRONMENTS: readonly ["studio", "warehouse", "factory", "office", "home", "sunny", "cloudy", "rainy"];
|
|
48
|
+
export type EnvironmentName = (typeof ENVIRONMENTS)[number];
|
|
49
|
+
/** 배경색 기본값. 회색이라 부품 색이 제 색으로 보인다. */
|
|
50
|
+
export declare const WORKBENCH = "#7c7c7c";
|
|
51
|
+
/**
|
|
52
|
+
* 처음 값.
|
|
53
|
+
*
|
|
54
|
+
* **지금 저작 화면이 실제로 쓰던 값 그대로다.** 설정을 만들면서 화면이 달라지면
|
|
55
|
+
* 무엇이 설정 때문이고 무엇이 원래 그랬는지 갈리지 않는다.
|
|
56
|
+
*/
|
|
57
|
+
export declare const DEFAULT_VIEW: ViewSettings;
|