@operato/scene-visualizer 10.0.0-beta.4 → 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.
Files changed (49) hide show
  1. package/dist/editors/index.d.ts +1 -0
  2. package/dist/editors/index.js +5 -0
  3. package/dist/editors/index.js.map +1 -1
  4. package/dist/editors/property-editor-gltf-fill-targets.d.ts +4 -1
  5. package/dist/editors/property-editor-gltf-fill-targets.js +178 -74
  6. package/dist/editors/property-editor-gltf-fill-targets.js.map +1 -1
  7. package/dist/editors/property-editor-gltf-info.d.ts +19 -4
  8. package/dist/editors/property-editor-gltf-info.js +279 -84
  9. package/dist/editors/property-editor-gltf-info.js.map +1 -1
  10. package/dist/editors/property-editor-gltf-play-targets.d.ts +25 -0
  11. package/dist/editors/property-editor-gltf-play-targets.js +388 -0
  12. package/dist/editors/property-editor-gltf-play-targets.js.map +1 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/rack-table-3d.js +1 -0
  17. package/dist/rack-table-3d.js.map +1 -1
  18. package/dist/rack-table.js +13 -0
  19. package/dist/rack-table.js.map +1 -1
  20. package/dist/rack.d.ts +6 -0
  21. package/dist/rack.js +10 -1
  22. package/dist/rack.js.map +1 -1
  23. package/dist/stock-hub.d.ts +25 -0
  24. package/dist/stock-hub.js +147 -0
  25. package/dist/stock-hub.js.map +1 -0
  26. package/dist/stock.d.ts +2 -0
  27. package/dist/stock.js +22 -33
  28. package/dist/stock.js.map +1 -1
  29. package/dist/templates/index.d.ts +0 -41
  30. package/dist/templates/index.js +2 -0
  31. package/dist/templates/index.js.map +1 -1
  32. package/dist/templates/rack-table.d.ts +2 -0
  33. package/dist/templates/rack-table.js +4 -2
  34. package/dist/templates/rack-table.js.map +1 -1
  35. package/dist/templates/stock-hub.d.ts +14 -0
  36. package/dist/templates/stock-hub.js +15 -0
  37. package/dist/templates/stock-hub.js.map +1 -0
  38. package/dist/templates/visualizer.js +1 -1
  39. package/dist/templates/visualizer.js.map +1 -1
  40. package/dist/visualizer.d.ts +2 -1
  41. package/dist/visualizer.js +47 -48
  42. package/dist/visualizer.js.map +1 -1
  43. package/icons/stock-hub.png +0 -0
  44. package/package.json +2 -2
  45. package/translations/en.json +6 -0
  46. package/translations/ja.json +5 -0
  47. package/translations/ko.json +6 -1
  48. package/translations/ms.json +5 -0
  49. 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 * as THREE from 'three';
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
- div[info] {
13
+ [info-panel] {
14
+ font-size: 12px;
15
+ padding: 4px 0;
16
+ }
17
+
18
+ .dim-row {
25
19
  display: flex;
26
- flex-direction: row;
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
- div[info] * {
31
- align-self: center;
73
+ .action-btn:hover {
74
+ background: var(--md-sys-color-surface-container-highest, #e6e0e9);
32
75
  }
33
76
 
34
- div[info] title {
35
- font-weight: bold;
77
+ .action-btn md-icon {
78
+ --md-icon-size: 16px;
36
79
  }
37
80
 
38
- .buttons {
81
+ .ratio-lock-toggle {
39
82
  display: flex;
83
+ align-items: center;
40
84
  gap: 4px;
41
- margin-left: auto;
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
- .buttons md-elevated-button {
45
- --md-icon-size: 24px;
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
- <legend><ox-i18n msgid="label.gltf-info">GLTF info.</ox-i18n></legend>
62
-
63
- <div>
64
- <div info>
65
- <div label>[W x H x D] :</div>
66
- ${valid
67
- ? html ` <div value>${this.width} x ${this.height} x ${this.depth}</div>
68
- <div class="buttons">
69
- <md-elevated-button
70
- title="원래 크기로 설정"
71
- @click=${() => {
72
- this._applyAction(action, {
73
- width: this.width,
74
- height: this.height,
75
- depth: this.depth
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
- <md-icon>autorenew</md-icon>
80
- </md-elevated-button>
81
- <md-elevated-button
82
- title="현재 크기에 비율 맞춤"
83
- @click=${() => this._applyProportional(action)}
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
- * 현재 컴포넌트의 W/H/D가장 값을 기준으로,
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
- // 현재 치수 가장 큰 값과, 그에 대응하는 원래 모델 치수로 scale 계산
123
- const maxCurrent = Math.max(cw, ch, cd);
124
- let scale;
125
- if (maxCurrent === cw) {
126
- scale = cw / ow;
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
- await this.fetchSourceInfo(selected[0], this.src);
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
- try {
161
- const path = component.app.url(src);
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
+ }