@operato/scene-visualizer 10.0.0-beta.7 → 10.0.0-beta.8
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/dist/editors/index.d.ts +1 -0
- package/dist/editors/index.js +5 -0
- package/dist/editors/index.js.map +1 -1
- package/dist/editors/property-editor-gltf-fill-targets.d.ts +4 -1
- package/dist/editors/property-editor-gltf-fill-targets.js +178 -74
- package/dist/editors/property-editor-gltf-fill-targets.js.map +1 -1
- package/dist/editors/property-editor-gltf-info.d.ts +19 -4
- package/dist/editors/property-editor-gltf-info.js +279 -84
- package/dist/editors/property-editor-gltf-info.js.map +1 -1
- package/dist/editors/property-editor-gltf-play-targets.d.ts +25 -0
- package/dist/editors/property-editor-gltf-play-targets.js +388 -0
- package/dist/editors/property-editor-gltf-play-targets.js.map +1 -0
- package/package.json +2 -2
- package/translations/en.json +6 -0
- package/translations/ja.json +5 -0
- package/translations/ko.json +6 -1
- package/translations/ms.json +5 -0
- package/translations/zh.json +5 -0
|
@@ -1,96 +1,214 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import '@material/web/icon/icon.js';
|
|
3
|
-
import '@material/web/button/elevated-button.js';
|
|
4
3
|
import '@operato/i18n/ox-i18n.js';
|
|
5
4
|
import { css, html } from 'lit';
|
|
6
5
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
7
6
|
import { OxPropertyEditor } from '@operato/property-editor';
|
|
8
|
-
import
|
|
9
|
-
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
10
|
-
const EXT_SPECGLOSS = 'KHR_materials_pbrSpecularGlossiness';
|
|
11
|
-
class SpecGlossCompat {
|
|
12
|
-
name = EXT_SPECGLOSS;
|
|
13
|
-
extendMaterialParams() {
|
|
14
|
-
return Promise.resolve();
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function registerSpecGlossCompat(loader) {
|
|
18
|
-
loader.register(() => new SpecGlossCompat());
|
|
19
|
-
}
|
|
7
|
+
import { ScrollbarStyles } from '@operato/styles/scrollbar-styles.js';
|
|
20
8
|
let GLTFInfoEditor = class GLTFInfoEditor extends OxPropertyEditor {
|
|
21
9
|
static styles = [
|
|
22
10
|
...OxPropertyEditor.styles,
|
|
11
|
+
ScrollbarStyles,
|
|
23
12
|
css `
|
|
24
|
-
|
|
13
|
+
[info-panel] {
|
|
14
|
+
font-size: 12px;
|
|
15
|
+
padding: 4px 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.dim-row {
|
|
25
19
|
display: flex;
|
|
26
|
-
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 6px;
|
|
22
|
+
padding: 3px 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dim-label {
|
|
26
|
+
min-width: 65px;
|
|
27
|
+
font-size: 11px;
|
|
28
|
+
color: var(--md-sys-color-on-surface-variant, #666);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.dim-value {
|
|
32
|
+
font-family: monospace;
|
|
27
33
|
font-size: 12px;
|
|
34
|
+
font-weight: 500;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.dim-value.mismatch {
|
|
38
|
+
color: var(--md-sys-color-error, #b3261e);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.dim-value.match {
|
|
42
|
+
color: var(--md-sys-color-primary, #6750a4);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.ratio-info {
|
|
46
|
+
font-size: 10px;
|
|
47
|
+
color: var(--md-sys-color-on-surface-variant, #888);
|
|
48
|
+
padding: 2px 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.button-row {
|
|
52
|
+
display: flex;
|
|
53
|
+
gap: 4px;
|
|
54
|
+
margin-top: 6px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.action-btn {
|
|
58
|
+
flex: 1;
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
gap: 4px;
|
|
63
|
+
padding: 6px 8px;
|
|
64
|
+
border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.2));
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
background: var(--md-sys-color-surface-container, #f3edf7);
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
font-size: 11px;
|
|
69
|
+
color: var(--md-sys-color-on-surface, #1c1b1f);
|
|
70
|
+
transition: background 0.15s;
|
|
28
71
|
}
|
|
29
72
|
|
|
30
|
-
|
|
31
|
-
|
|
73
|
+
.action-btn:hover {
|
|
74
|
+
background: var(--md-sys-color-surface-container-highest, #e6e0e9);
|
|
32
75
|
}
|
|
33
76
|
|
|
34
|
-
|
|
35
|
-
|
|
77
|
+
.action-btn md-icon {
|
|
78
|
+
--md-icon-size: 16px;
|
|
36
79
|
}
|
|
37
80
|
|
|
38
|
-
.
|
|
81
|
+
.ratio-lock-toggle {
|
|
39
82
|
display: flex;
|
|
83
|
+
align-items: center;
|
|
40
84
|
gap: 4px;
|
|
41
|
-
|
|
85
|
+
font-size: 11px;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
color: var(--md-sys-color-on-surface, #1c1b1f);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.ratio-lock-toggle input[type='checkbox'] {
|
|
91
|
+
accent-color: var(--md-sys-color-primary, #6750a4);
|
|
92
|
+
margin: 0;
|
|
42
93
|
}
|
|
43
94
|
|
|
44
|
-
.
|
|
45
|
-
|
|
95
|
+
.stats-row {
|
|
96
|
+
display: flex;
|
|
97
|
+
gap: 12px;
|
|
98
|
+
padding: 4px 0;
|
|
99
|
+
margin-top: 4px;
|
|
100
|
+
border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.1));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.stat-item {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 3px;
|
|
107
|
+
font-size: 11px;
|
|
108
|
+
color: var(--md-sys-color-on-surface-variant, #666);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.stat-item md-icon {
|
|
112
|
+
--md-icon-size: 13px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.stat-value {
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
color: var(--md-sys-color-on-surface, #1c1b1f);
|
|
46
118
|
}
|
|
47
119
|
`
|
|
48
120
|
];
|
|
121
|
+
_component = null;
|
|
49
122
|
constructor() {
|
|
50
123
|
super();
|
|
51
124
|
this.width = 0;
|
|
52
125
|
this.height = 0;
|
|
53
126
|
this.depth = 0;
|
|
127
|
+
this.currentWidth = 0;
|
|
128
|
+
this.currentHeight = 0;
|
|
129
|
+
this.currentDepth = 0;
|
|
130
|
+
this.meshCount = 0;
|
|
131
|
+
this.vertexCount = 0;
|
|
132
|
+
this.animationCount = 0;
|
|
133
|
+
this.materialCount = 0;
|
|
54
134
|
}
|
|
55
135
|
editorTemplate(value, spec) {
|
|
56
136
|
const valid = this.width && this.height && this.depth;
|
|
57
137
|
var property = spec.property || {};
|
|
58
138
|
var { action } = property;
|
|
139
|
+
const isProportional = valid && this._isProportional();
|
|
59
140
|
return html `
|
|
60
141
|
<fieldset fullwidth>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
142
|
+
<div info-panel>
|
|
143
|
+
${valid
|
|
144
|
+
? html `
|
|
145
|
+
<div class="dim-row">
|
|
146
|
+
<span class="dim-label">Model</span>
|
|
147
|
+
<span class="dim-value">${this.width} × ${this.height} × ${this.depth}</span>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="dim-row">
|
|
150
|
+
<span class="dim-label">Current</span>
|
|
151
|
+
<span class="dim-value ${isProportional ? 'match' : 'mismatch'}">
|
|
152
|
+
${this.currentWidth} × ${this.currentHeight} × ${this.currentDepth}
|
|
153
|
+
</span>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div class="stats-row">
|
|
157
|
+
<span class="stat-item"><md-icon>view_in_ar</md-icon> <span class="stat-value">${this.meshCount}</span> meshes</span>
|
|
158
|
+
<span class="stat-item"><md-icon>scatter_plot</md-icon> <span class="stat-value">${this._formatNumber(this.vertexCount)}</span> verts</span>
|
|
159
|
+
<span class="stat-item"><md-icon>palette</md-icon> <span class="stat-value">${this.materialCount}</span> mats</span>
|
|
160
|
+
${this.animationCount > 0
|
|
161
|
+
? html `<span class="stat-item"><md-icon>animation</md-icon> <span class="stat-value">${this.animationCount}</span> anims</span>`
|
|
162
|
+
: ''}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div class="button-row">
|
|
166
|
+
<div
|
|
167
|
+
class="action-btn"
|
|
168
|
+
title="W,H 중 짧은 쪽 기준으로 원본 비율 적용"
|
|
169
|
+
@click=${() => this._applyProportional(action)}
|
|
170
|
+
>
|
|
171
|
+
<md-icon>aspect_ratio</md-icon>
|
|
172
|
+
<ox-i18n msgid="label.fit-ratio">Fit Ratio</ox-i18n>
|
|
173
|
+
</div>
|
|
174
|
+
<label class="ratio-lock-toggle">
|
|
175
|
+
<input
|
|
176
|
+
type="checkbox"
|
|
177
|
+
.checked=${this._getRatioLock()}
|
|
178
|
+
@change=${(e) => {
|
|
179
|
+
e.stopPropagation();
|
|
180
|
+
this._setRatioLock(e.target.checked);
|
|
77
181
|
}}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<md-icon>aspect_ratio</md-icon>
|
|
86
|
-
</md-elevated-button>
|
|
87
|
-
</div>`
|
|
88
|
-
: html ` <div></div> `}
|
|
89
|
-
</div>
|
|
182
|
+
/>
|
|
183
|
+
<md-icon style="--md-icon-size:14px">lock</md-icon>
|
|
184
|
+
Ratio Lock
|
|
185
|
+
</label>
|
|
186
|
+
</div>
|
|
187
|
+
`
|
|
188
|
+
: html `<div class="dim-row"><span class="dim-label">Loading...</span></div>`}
|
|
90
189
|
</div>
|
|
91
190
|
</fieldset>
|
|
92
191
|
`;
|
|
93
192
|
}
|
|
193
|
+
_formatNumber(n) {
|
|
194
|
+
if (n >= 1000000)
|
|
195
|
+
return (n / 1000000).toFixed(1) + 'M';
|
|
196
|
+
if (n >= 1000)
|
|
197
|
+
return (n / 1000).toFixed(1) + 'K';
|
|
198
|
+
return String(n);
|
|
199
|
+
}
|
|
200
|
+
_isProportional() {
|
|
201
|
+
if (!this.width || !this.height || !this.depth)
|
|
202
|
+
return true;
|
|
203
|
+
if (!this.currentWidth || !this.currentHeight || !this.currentDepth)
|
|
204
|
+
return true;
|
|
205
|
+
const rw = this.currentWidth / this.width;
|
|
206
|
+
const rh = this.currentHeight / this.height;
|
|
207
|
+
const rd = this.currentDepth / this.depth;
|
|
208
|
+
const avg = (rw + rh + rd) / 3;
|
|
209
|
+
const tolerance = 0.05;
|
|
210
|
+
return Math.abs(rw - avg) / avg < tolerance && Math.abs(rh - avg) / avg < tolerance && Math.abs(rd - avg) / avg < tolerance;
|
|
211
|
+
}
|
|
94
212
|
_applyAction(action, dimension) {
|
|
95
213
|
this.dispatchEvent(new CustomEvent('i-need-selected', {
|
|
96
214
|
bubbles: true,
|
|
@@ -98,13 +216,14 @@ let GLTFInfoEditor = class GLTFInfoEditor extends OxPropertyEditor {
|
|
|
98
216
|
detail: {
|
|
99
217
|
callback: (selected) => {
|
|
100
218
|
typeof action === 'function' && action(selected[0], dimension);
|
|
219
|
+
this._refreshCurrentSize(selected[0]);
|
|
101
220
|
}
|
|
102
221
|
}
|
|
103
222
|
}));
|
|
104
223
|
}
|
|
105
224
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
225
|
+
* W, H 중 짧은 쪽을 기준으로 원래 모델의 비율에 맞게 치수를 조절한다.
|
|
226
|
+
* (contain 방식 — 모델이 컴포넌트 영역 안에 들어감)
|
|
108
227
|
*/
|
|
109
228
|
_applyProportional(action) {
|
|
110
229
|
this.dispatchEvent(new CustomEvent('i-need-selected', {
|
|
@@ -119,27 +238,44 @@ let GLTFInfoEditor = class GLTFInfoEditor extends OxPropertyEditor {
|
|
|
119
238
|
const { width: ow, height: oh, depth: od } = this; // 원래 모델 치수
|
|
120
239
|
if (!ow || !oh || !od)
|
|
121
240
|
return;
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
else if (maxCurrent === ch) {
|
|
129
|
-
scale = ch / oh;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
scale = cd / od;
|
|
133
|
-
}
|
|
241
|
+
// 이미 비율이 맞으면 스킵
|
|
242
|
+
if (this._isProportional())
|
|
243
|
+
return;
|
|
244
|
+
// W, H 중 ratio가 작은 축을 기준
|
|
245
|
+
const scale = Math.min(cw / ow, ch / oh);
|
|
134
246
|
action(component, {
|
|
135
247
|
width: Math.round(ow * scale),
|
|
136
248
|
height: Math.round(oh * scale),
|
|
137
249
|
depth: Math.round(od * scale)
|
|
138
250
|
});
|
|
251
|
+
this._refreshCurrentSize(component);
|
|
139
252
|
}
|
|
140
253
|
}
|
|
141
254
|
}));
|
|
142
255
|
}
|
|
256
|
+
_getRatioLock() {
|
|
257
|
+
return !!this._component?.state?.ratioLock;
|
|
258
|
+
}
|
|
259
|
+
_setRatioLock(value) {
|
|
260
|
+
this.dispatchEvent(new CustomEvent('i-need-selected', {
|
|
261
|
+
bubbles: true,
|
|
262
|
+
composed: true,
|
|
263
|
+
detail: {
|
|
264
|
+
callback: (selected) => {
|
|
265
|
+
selected[0].set('ratioLock', value);
|
|
266
|
+
this.requestUpdate();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
_refreshCurrentSize(component) {
|
|
272
|
+
if (!component)
|
|
273
|
+
return;
|
|
274
|
+
const state = component.state || {};
|
|
275
|
+
this.currentWidth = Math.round(state.width || 0);
|
|
276
|
+
this.currentHeight = Math.round(state.height || 0);
|
|
277
|
+
this.currentDepth = Math.round(state.depth || 0);
|
|
278
|
+
}
|
|
143
279
|
updated(changes) {
|
|
144
280
|
if (changes.has('src')) {
|
|
145
281
|
this.dispatchEvent(new CustomEvent('i-need-selected', {
|
|
@@ -147,36 +283,74 @@ let GLTFInfoEditor = class GLTFInfoEditor extends OxPropertyEditor {
|
|
|
147
283
|
composed: true,
|
|
148
284
|
detail: {
|
|
149
285
|
callback: async (selected) => {
|
|
150
|
-
|
|
286
|
+
const component = selected[0];
|
|
287
|
+
this._component = component;
|
|
288
|
+
this._refreshCurrentSize(component);
|
|
289
|
+
await this.fetchSourceInfo(component, this.src);
|
|
151
290
|
}
|
|
152
291
|
}
|
|
153
292
|
}));
|
|
154
293
|
}
|
|
155
294
|
}
|
|
295
|
+
_pollTimer;
|
|
156
296
|
async fetchSourceInfo(component, src) {
|
|
157
297
|
if (!src || !src.trim()) {
|
|
158
298
|
return;
|
|
159
299
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
let gltfLoader = new GLTFLoader();
|
|
163
|
-
registerSpecGlossCompat(gltfLoader);
|
|
164
|
-
gltfLoader.setCrossOrigin('use-credentials');
|
|
165
|
-
return new Promise((resolve) => {
|
|
166
|
-
gltfLoader.load(path, gltf => {
|
|
167
|
-
var box = new THREE.Box3().setFromObject(gltf.scene);
|
|
168
|
-
var { x, y, z } = box.getSize(new THREE.Vector3());
|
|
169
|
-
this.width = Math.floor(x);
|
|
170
|
-
this.depth = Math.floor(y);
|
|
171
|
-
this.height = Math.floor(z);
|
|
172
|
-
resolve();
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
catch (e) {
|
|
177
|
-
console.error(e);
|
|
300
|
+
// 이미 로드된 realObject에서 직접 정보를 읽는다
|
|
301
|
+
if (this._tryReadFromRealObject(component)) {
|
|
178
302
|
return;
|
|
179
303
|
}
|
|
304
|
+
// GLTF 비동기 로드 중이면 폴링 (최대 20회 = 10초)
|
|
305
|
+
if (this._pollTimer)
|
|
306
|
+
clearInterval(this._pollTimer);
|
|
307
|
+
let retries = 0;
|
|
308
|
+
this._pollTimer = window.setInterval(() => {
|
|
309
|
+
if (this._tryReadFromRealObject(component) || ++retries >= 20) {
|
|
310
|
+
clearInterval(this._pollTimer);
|
|
311
|
+
this._pollTimer = undefined;
|
|
312
|
+
}
|
|
313
|
+
}, 500);
|
|
314
|
+
}
|
|
315
|
+
_tryReadFromRealObject(component) {
|
|
316
|
+
const ro = component?.realObject;
|
|
317
|
+
if (!ro?.objectSize)
|
|
318
|
+
return false;
|
|
319
|
+
const { x = 0, y = 0, z = 0 } = ro.objectSize;
|
|
320
|
+
this.width = Math.round(x * 100) / 100;
|
|
321
|
+
this.depth = Math.round(y * 100) / 100;
|
|
322
|
+
this.height = Math.round(z * 100) / 100;
|
|
323
|
+
// 통계: pivot 또는 object3d에서 수집
|
|
324
|
+
const root = ro.pivot || ro.object3d;
|
|
325
|
+
if (root) {
|
|
326
|
+
let meshes = 0;
|
|
327
|
+
let vertices = 0;
|
|
328
|
+
const materials = new Set();
|
|
329
|
+
root.traverse((node) => {
|
|
330
|
+
if (node.isMesh) {
|
|
331
|
+
meshes++;
|
|
332
|
+
if (node.geometry) {
|
|
333
|
+
const pos = node.geometry.getAttribute('position');
|
|
334
|
+
if (pos)
|
|
335
|
+
vertices += pos.count;
|
|
336
|
+
}
|
|
337
|
+
if (node.material) {
|
|
338
|
+
const mats = Array.isArray(node.material) ? node.material : [node.material];
|
|
339
|
+
mats.forEach((m) => materials.add(m.uuid));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
this.meshCount = meshes;
|
|
344
|
+
this.vertexCount = vertices;
|
|
345
|
+
this.materialCount = materials.size;
|
|
346
|
+
}
|
|
347
|
+
this.animationCount = ro._animationActions?.size || 0;
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
disconnectedCallback() {
|
|
351
|
+
super.disconnectedCallback();
|
|
352
|
+
if (this._pollTimer)
|
|
353
|
+
clearInterval(this._pollTimer);
|
|
180
354
|
}
|
|
181
355
|
};
|
|
182
356
|
__decorate([
|
|
@@ -191,6 +365,27 @@ __decorate([
|
|
|
191
365
|
__decorate([
|
|
192
366
|
state()
|
|
193
367
|
], GLTFInfoEditor.prototype, "depth", void 0);
|
|
368
|
+
__decorate([
|
|
369
|
+
state()
|
|
370
|
+
], GLTFInfoEditor.prototype, "currentWidth", void 0);
|
|
371
|
+
__decorate([
|
|
372
|
+
state()
|
|
373
|
+
], GLTFInfoEditor.prototype, "currentHeight", void 0);
|
|
374
|
+
__decorate([
|
|
375
|
+
state()
|
|
376
|
+
], GLTFInfoEditor.prototype, "currentDepth", void 0);
|
|
377
|
+
__decorate([
|
|
378
|
+
state()
|
|
379
|
+
], GLTFInfoEditor.prototype, "meshCount", void 0);
|
|
380
|
+
__decorate([
|
|
381
|
+
state()
|
|
382
|
+
], GLTFInfoEditor.prototype, "vertexCount", void 0);
|
|
383
|
+
__decorate([
|
|
384
|
+
state()
|
|
385
|
+
], GLTFInfoEditor.prototype, "animationCount", void 0);
|
|
386
|
+
__decorate([
|
|
387
|
+
state()
|
|
388
|
+
], GLTFInfoEditor.prototype, "materialCount", void 0);
|
|
194
389
|
GLTFInfoEditor = __decorate([
|
|
195
390
|
customElement('property-editor-gltf-info')
|
|
196
391
|
], GLTFInfoEditor);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"property-editor-gltf-info.js","sourceRoot":"","sources":["../../src/editors/property-editor-gltf-info.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, PropertyValues, TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { OxPropertyEditor, PropertySpec } from '@operato/property-editor'\nimport { Component } from '@hatiolab/things-scene'\n\nimport * as THREE from 'three'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\n\nconst EXT_SPECGLOSS = 'KHR_materials_pbrSpecularGlossiness'\n\nclass SpecGlossCompat {\n name = EXT_SPECGLOSS\n extendMaterialParams() {\n return Promise.resolve()\n }\n}\n\nfunction registerSpecGlossCompat(loader: GLTFLoader) {\n loader.register(() => new SpecGlossCompat())\n}\n\n@customElement('property-editor-gltf-info')\nexport default class GLTFInfoEditor extends OxPropertyEditor {\n static styles = [\n ...OxPropertyEditor.styles,\n css`\n div[info] {\n display: flex;\n flex-direction: row;\n font-size: 12px;\n }\n\n div[info] * {\n align-self: center;\n }\n\n div[info] title {\n font-weight: bold;\n }\n\n .buttons {\n display: flex;\n gap: 4px;\n margin-left: auto;\n }\n\n .buttons md-elevated-button {\n --md-icon-size: 24px;\n }\n `\n ]\n\n @property({ type: String }) declare src: string | undefined\n\n @state() declare width: number\n @state() declare height: number\n @state() declare depth: number\n\n constructor() {\n super()\n this.width = 0\n this.height = 0\n this.depth = 0\n }\n\n editorTemplate(value: any, spec: PropertySpec): TemplateResult {\n const valid = this.width && this.height && this.depth\n\n var property = spec.property || {}\n var { action } = property\n\n return html`\n <fieldset fullwidth>\n <legend><ox-i18n msgid=\"label.gltf-info\">GLTF info.</ox-i18n></legend>\n\n <div>\n <div info>\n <div label>[W x H x D] :</div>\n ${valid\n ? html` <div value>${this.width} x ${this.height} x ${this.depth}</div>\n <div class=\"buttons\">\n <md-elevated-button\n title=\"원래 크기로 설정\"\n @click=${() => {\n this._applyAction(action, {\n width: this.width,\n height: this.height,\n depth: this.depth\n })\n }}\n >\n <md-icon>autorenew</md-icon>\n </md-elevated-button>\n <md-elevated-button\n title=\"현재 크기에 비율 맞춤\"\n @click=${() => this._applyProportional(action)}\n >\n <md-icon>aspect_ratio</md-icon>\n </md-elevated-button>\n </div>`\n : html` <div></div> `}\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _applyAction(action: any, dimension: { width: number; height: number; depth: number }) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: (selected: any[]) => {\n typeof action === 'function' && action(selected[0], dimension)\n }\n }\n })\n )\n }\n\n /**\n * 현재 컴포넌트의 W/H/D 중 가장 큰 값을 기준으로,\n * 원래 모델의 비율에 맞게 나머지 치수를 조절한다.\n */\n private _applyProportional(action: any) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: (selected: any[]) => {\n const component = selected[0]\n if (!component || typeof action !== 'function') return\n\n const { width: cw = 1, height: ch = 1, depth: cd = 1 } = component.state\n const { width: ow, height: oh, depth: od } = this // 원래 모델 치수\n\n if (!ow || !oh || !od) return\n\n // 현재 치수 중 가장 큰 값과, 그에 대응하는 원래 모델 치수로 scale 계산\n const maxCurrent = Math.max(cw, ch, cd)\n let scale: number\n\n if (maxCurrent === cw) {\n scale = cw / ow\n } else if (maxCurrent === ch) {\n scale = ch / oh\n } else {\n scale = cd / od\n }\n\n action(component, {\n width: Math.round(ow * scale),\n height: Math.round(oh * scale),\n depth: Math.round(od * scale)\n })\n }\n }\n })\n )\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('src')) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: async (selected: any[]) => {\n await this.fetchSourceInfo(selected[0], this.src!)\n }\n }\n })\n )\n }\n }\n\n async fetchSourceInfo(component: Component, src: string) {\n if (!src || !src.trim()) {\n return\n }\n\n try {\n const path = component.app.url(src)\n\n let gltfLoader = new GLTFLoader()\n registerSpecGlossCompat(gltfLoader)\n\n gltfLoader.setCrossOrigin('use-credentials')\n\n return new Promise((resolve: any) => {\n gltfLoader.load(path, gltf => {\n var box = new THREE.Box3().setFromObject(gltf.scene)\n var { x, y, z } = box.getSize(new THREE.Vector3())\n\n this.width = Math.floor(x)\n this.depth = Math.floor(y)\n this.height = Math.floor(z)\n\n resolve()\n })\n })\n } catch (e) {\n console.error(e)\n return\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"property-editor-gltf-info.js","sourceRoot":"","sources":["../../src/editors/property-editor-gltf-info.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, PropertyValues, TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { OxPropertyEditor, PropertySpec } from '@operato/property-editor'\nimport { ScrollbarStyles } from '@operato/styles/scrollbar-styles.js'\nimport { Component } from '@hatiolab/things-scene'\n\n@customElement('property-editor-gltf-info')\nexport default class GLTFInfoEditor extends OxPropertyEditor {\n static styles = [\n ...OxPropertyEditor.styles,\n ScrollbarStyles,\n css`\n [info-panel] {\n font-size: 12px;\n padding: 4px 0;\n }\n\n .dim-row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 3px 0;\n }\n\n .dim-label {\n min-width: 65px;\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant, #666);\n }\n\n .dim-value {\n font-family: monospace;\n font-size: 12px;\n font-weight: 500;\n }\n\n .dim-value.mismatch {\n color: var(--md-sys-color-error, #b3261e);\n }\n\n .dim-value.match {\n color: var(--md-sys-color-primary, #6750a4);\n }\n\n .ratio-info {\n font-size: 10px;\n color: var(--md-sys-color-on-surface-variant, #888);\n padding: 2px 0;\n }\n\n .button-row {\n display: flex;\n gap: 4px;\n margin-top: 6px;\n }\n\n .action-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.2));\n border-radius: 6px;\n background: var(--md-sys-color-surface-container, #f3edf7);\n cursor: pointer;\n font-size: 11px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n transition: background 0.15s;\n }\n\n .action-btn:hover {\n background: var(--md-sys-color-surface-container-highest, #e6e0e9);\n }\n\n .action-btn md-icon {\n --md-icon-size: 16px;\n }\n\n .ratio-lock-toggle {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n cursor: pointer;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n }\n\n .ratio-lock-toggle input[type='checkbox'] {\n accent-color: var(--md-sys-color-primary, #6750a4);\n margin: 0;\n }\n\n .stats-row {\n display: flex;\n gap: 12px;\n padding: 4px 0;\n margin-top: 4px;\n border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.1));\n }\n\n .stat-item {\n display: flex;\n align-items: center;\n gap: 3px;\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant, #666);\n }\n\n .stat-item md-icon {\n --md-icon-size: 13px;\n }\n\n .stat-value {\n font-weight: 600;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n }\n `\n ]\n\n @property({ type: String }) declare src: string | undefined\n\n private _component: any = null\n\n @state() declare width: number\n @state() declare height: number\n @state() declare depth: number\n @state() declare currentWidth: number\n @state() declare currentHeight: number\n @state() declare currentDepth: number\n @state() declare meshCount: number\n @state() declare vertexCount: number\n @state() declare animationCount: number\n @state() declare materialCount: number\n\n constructor() {\n super()\n this.width = 0\n this.height = 0\n this.depth = 0\n this.currentWidth = 0\n this.currentHeight = 0\n this.currentDepth = 0\n this.meshCount = 0\n this.vertexCount = 0\n this.animationCount = 0\n this.materialCount = 0\n }\n\n editorTemplate(value: any, spec: PropertySpec): TemplateResult {\n const valid = this.width && this.height && this.depth\n\n var property = spec.property || {}\n var { action } = property\n\n const isProportional = valid && this._isProportional()\n\n return html`\n <fieldset fullwidth>\n <div info-panel>\n ${valid\n ? html`\n <div class=\"dim-row\">\n <span class=\"dim-label\">Model</span>\n <span class=\"dim-value\">${this.width} × ${this.height} × ${this.depth}</span>\n </div>\n <div class=\"dim-row\">\n <span class=\"dim-label\">Current</span>\n <span class=\"dim-value ${isProportional ? 'match' : 'mismatch'}\">\n ${this.currentWidth} × ${this.currentHeight} × ${this.currentDepth}\n </span>\n </div>\n\n <div class=\"stats-row\">\n <span class=\"stat-item\"><md-icon>view_in_ar</md-icon> <span class=\"stat-value\">${this.meshCount}</span> meshes</span>\n <span class=\"stat-item\"><md-icon>scatter_plot</md-icon> <span class=\"stat-value\">${this._formatNumber(this.vertexCount)}</span> verts</span>\n <span class=\"stat-item\"><md-icon>palette</md-icon> <span class=\"stat-value\">${this.materialCount}</span> mats</span>\n ${this.animationCount > 0\n ? html`<span class=\"stat-item\"><md-icon>animation</md-icon> <span class=\"stat-value\">${this.animationCount}</span> anims</span>`\n : ''}\n </div>\n\n <div class=\"button-row\">\n <div\n class=\"action-btn\"\n title=\"W,H 중 짧은 쪽 기준으로 원본 비율 적용\"\n @click=${() => this._applyProportional(action)}\n >\n <md-icon>aspect_ratio</md-icon>\n <ox-i18n msgid=\"label.fit-ratio\">Fit Ratio</ox-i18n>\n </div>\n <label class=\"ratio-lock-toggle\">\n <input\n type=\"checkbox\"\n .checked=${this._getRatioLock()}\n @change=${(e: Event) => {\n e.stopPropagation()\n this._setRatioLock((e.target as HTMLInputElement).checked)\n }}\n />\n <md-icon style=\"--md-icon-size:14px\">lock</md-icon>\n Ratio Lock\n </label>\n </div>\n `\n : html`<div class=\"dim-row\"><span class=\"dim-label\">Loading...</span></div>`}\n </div>\n </fieldset>\n `\n }\n\n private _formatNumber(n: number): string {\n if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M'\n if (n >= 1000) return (n / 1000).toFixed(1) + 'K'\n return String(n)\n }\n\n private _isProportional(): boolean {\n if (!this.width || !this.height || !this.depth) return true\n if (!this.currentWidth || !this.currentHeight || !this.currentDepth) return true\n\n const rw = this.currentWidth / this.width\n const rh = this.currentHeight / this.height\n const rd = this.currentDepth / this.depth\n const avg = (rw + rh + rd) / 3\n const tolerance = 0.05\n\n return Math.abs(rw - avg) / avg < tolerance && Math.abs(rh - avg) / avg < tolerance && Math.abs(rd - avg) / avg < tolerance\n }\n\n private _applyAction(action: any, dimension: { width: number; height: number; depth: number }) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: (selected: any[]) => {\n typeof action === 'function' && action(selected[0], dimension)\n this._refreshCurrentSize(selected[0])\n }\n }\n })\n )\n }\n\n /**\n * W, H 중 짧은 쪽을 기준으로 원래 모델의 비율에 맞게 치수를 조절한다.\n * (contain 방식 — 모델이 컴포넌트 영역 안에 들어감)\n */\n private _applyProportional(action: any) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: (selected: any[]) => {\n const component = selected[0]\n if (!component || typeof action !== 'function') return\n\n const { width: cw = 1, height: ch = 1, depth: cd = 1 } = component.state\n const { width: ow, height: oh, depth: od } = this // 원래 모델 치수\n\n if (!ow || !oh || !od) return\n\n // 이미 비율이 맞으면 스킵\n if (this._isProportional()) return\n\n // W, H 중 ratio가 작은 축을 기준\n const scale = Math.min(cw / ow, ch / oh)\n\n action(component, {\n width: Math.round(ow * scale),\n height: Math.round(oh * scale),\n depth: Math.round(od * scale)\n })\n\n this._refreshCurrentSize(component)\n }\n }\n })\n )\n }\n\n private _getRatioLock(): boolean {\n return !!this._component?.state?.ratioLock\n }\n\n private _setRatioLock(value: boolean) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: (selected: any[]) => {\n selected[0].set('ratioLock', value)\n this.requestUpdate()\n }\n }\n })\n )\n }\n\n private _refreshCurrentSize(component: any) {\n if (!component) return\n const state = component.state || {}\n this.currentWidth = Math.round(state.width || 0)\n this.currentHeight = Math.round(state.height || 0)\n this.currentDepth = Math.round(state.depth || 0)\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('src')) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: async (selected: any[]) => {\n const component = selected[0]\n this._component = component\n this._refreshCurrentSize(component)\n await this.fetchSourceInfo(component, this.src!)\n }\n }\n })\n )\n }\n }\n\n private _pollTimer?: number\n\n async fetchSourceInfo(component: Component, src: string) {\n if (!src || !src.trim()) {\n return\n }\n\n // 이미 로드된 realObject에서 직접 정보를 읽는다\n if (this._tryReadFromRealObject(component)) {\n return\n }\n\n // GLTF 비동기 로드 중이면 폴링 (최대 20회 = 10초)\n if (this._pollTimer) clearInterval(this._pollTimer)\n let retries = 0\n this._pollTimer = window.setInterval(() => {\n if (this._tryReadFromRealObject(component) || ++retries >= 20) {\n clearInterval(this._pollTimer)\n this._pollTimer = undefined\n }\n }, 500)\n }\n\n private _tryReadFromRealObject(component: any): boolean {\n const ro = component?.realObject\n if (!ro?.objectSize) return false\n\n const { x = 0, y = 0, z = 0 } = ro.objectSize\n this.width = Math.round(x * 100) / 100\n this.depth = Math.round(y * 100) / 100\n this.height = Math.round(z * 100) / 100\n\n // 통계: pivot 또는 object3d에서 수집\n const root = ro.pivot || ro.object3d\n if (root) {\n let meshes = 0\n let vertices = 0\n const materials = new Set()\n\n root.traverse((node: any) => {\n if (node.isMesh) {\n meshes++\n if (node.geometry) {\n const pos = node.geometry.getAttribute('position')\n if (pos) vertices += pos.count\n }\n if (node.material) {\n const mats = Array.isArray(node.material) ? node.material : [node.material]\n mats.forEach((m: any) => materials.add(m.uuid))\n }\n }\n })\n\n this.meshCount = meshes\n this.vertexCount = vertices\n this.materialCount = materials.size\n }\n\n this.animationCount = ro._animationActions?.size || 0\n\n return true\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n if (this._pollTimer) clearInterval(this._pollTimer)\n }\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import '@operato/i18n/ox-i18n.js';
|
|
3
|
+
import { PropertyValues, TemplateResult } from 'lit';
|
|
4
|
+
import { OxPropertyEditor, PropertySpec } from '@operato/property-editor';
|
|
5
|
+
export default class GLTFPlayTargetsEditor extends OxPropertyEditor {
|
|
6
|
+
static styles: import("lit").CSSResult[];
|
|
7
|
+
src: string | undefined;
|
|
8
|
+
private _animNames;
|
|
9
|
+
private _mode;
|
|
10
|
+
private _playingAnim;
|
|
11
|
+
private _component;
|
|
12
|
+
private _lastExternalValue;
|
|
13
|
+
updated(changes: PropertyValues<this>): void;
|
|
14
|
+
private _refreshAnimNames;
|
|
15
|
+
private _pollTimer?;
|
|
16
|
+
connectedCallback(): void;
|
|
17
|
+
disconnectedCallback(): void;
|
|
18
|
+
editorTemplate(value: any, _spec: PropertySpec): TemplateResult;
|
|
19
|
+
private _setMode;
|
|
20
|
+
private _onToggle;
|
|
21
|
+
private _applyValue;
|
|
22
|
+
private _getPlayState;
|
|
23
|
+
private _setPlay;
|
|
24
|
+
private _togglePreview;
|
|
25
|
+
}
|