@operato/scene-visualizer 10.0.0-beta.7 → 10.0.0-beta.9

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 (48) hide show
  1. package/dist/editors/index.d.ts +2 -0
  2. package/dist/editors/index.js +10 -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/editors/property-editor-stocker-location.d.ts +13 -0
  14. package/dist/editors/property-editor-stocker-location.js +151 -0
  15. package/dist/editors/property-editor-stocker-location.js.map +1 -0
  16. package/dist/editors/property-editor-stocker-ports.d.ts +8 -0
  17. package/dist/editors/property-editor-stocker-ports.js +112 -0
  18. package/dist/editors/property-editor-stocker-ports.js.map +1 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +2 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/stocker-3d.d.ts +23 -0
  23. package/dist/stocker-3d.js +352 -0
  24. package/dist/stocker-3d.js.map +1 -0
  25. package/dist/stocker-port-3d.d.ts +14 -0
  26. package/dist/stocker-port-3d.js +80 -0
  27. package/dist/stocker-port-3d.js.map +1 -0
  28. package/dist/stocker-port.d.ts +254 -0
  29. package/dist/stocker-port.js +123 -0
  30. package/dist/stocker-port.js.map +1 -0
  31. package/dist/stocker.d.ts +340 -0
  32. package/dist/stocker.js +370 -0
  33. package/dist/stocker.js.map +1 -0
  34. package/dist/templates/index.d.ts +40 -0
  35. package/dist/templates/index.js +5 -1
  36. package/dist/templates/index.js.map +1 -1
  37. package/dist/templates/stocker-port.d.ts +17 -0
  38. package/dist/templates/stocker-port.js +17 -0
  39. package/dist/templates/stocker-port.js.map +1 -0
  40. package/dist/templates/stocker.d.ts +27 -0
  41. package/dist/templates/stocker.js +38 -0
  42. package/dist/templates/stocker.js.map +1 -0
  43. package/package.json +2 -2
  44. package/translations/en.json +6 -0
  45. package/translations/ja.json +5 -0
  46. package/translations/ko.json +6 -1
  47. package/translations/ms.json +5 -0
  48. package/translations/zh.json +5 -0
@@ -1,6 +1,8 @@
1
1
  import './property-editor-location-increase-pattern.js';
2
2
  import './property-editor-gltf-info.js';
3
3
  import './property-editor-gltf-fill-targets.js';
4
+ import './property-editor-gltf-play-targets.js';
5
+ import './property-editor-stocker-location.js';
4
6
  declare const _default: {
5
7
  type: string;
6
8
  element: string;
@@ -1,6 +1,8 @@
1
1
  import './property-editor-location-increase-pattern.js';
2
2
  import './property-editor-gltf-info.js';
3
3
  import './property-editor-gltf-fill-targets.js';
4
+ import './property-editor-gltf-play-targets.js';
5
+ import './property-editor-stocker-location.js';
4
6
  export default [
5
7
  {
6
8
  type: 'location-increase-pattern',
@@ -13,6 +15,14 @@ export default [
13
15
  {
14
16
  type: 'gltf-fill-targets',
15
17
  element: 'property-editor-gltf-fill-targets'
18
+ },
19
+ {
20
+ type: 'gltf-play-targets',
21
+ element: 'property-editor-gltf-play-targets'
22
+ },
23
+ {
24
+ type: 'stocker-location',
25
+ element: 'property-editor-stocker-location'
16
26
  }
17
27
  ];
18
28
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/editors/index.ts"],"names":[],"mappings":"AAAA,OAAO,gDAAgD,CAAA;AACvD,OAAO,gCAAgC,CAAA;AACvC,OAAO,wCAAwC,CAAA;AAE/C,eAAe;IACb;QACE,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,2CAA2C;KACrD;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,2BAA2B;KACrC;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,mCAAmC;KAC7C;CACF,CAAA","sourcesContent":["import './property-editor-location-increase-pattern.js'\nimport './property-editor-gltf-info.js'\nimport './property-editor-gltf-fill-targets.js'\n\nexport default [\n {\n type: 'location-increase-pattern',\n element: 'property-editor-location-increase-pattern'\n },\n {\n type: 'gltf-info',\n element: 'property-editor-gltf-info'\n },\n {\n type: 'gltf-fill-targets',\n element: 'property-editor-gltf-fill-targets'\n }\n]\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/editors/index.ts"],"names":[],"mappings":"AAAA,OAAO,gDAAgD,CAAA;AACvD,OAAO,gCAAgC,CAAA;AACvC,OAAO,wCAAwC,CAAA;AAC/C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,uCAAuC,CAAA;AAE9C,eAAe;IACb;QACE,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,2CAA2C;KACrD;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,2BAA2B;KACrC;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,mCAAmC;KAC7C;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,mCAAmC;KAC7C;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,OAAO,EAAE,kCAAkC;KAC5C;CACF,CAAA","sourcesContent":["import './property-editor-location-increase-pattern.js'\nimport './property-editor-gltf-info.js'\nimport './property-editor-gltf-fill-targets.js'\nimport './property-editor-gltf-play-targets.js'\nimport './property-editor-stocker-location.js'\n\nexport default [\n {\n type: 'location-increase-pattern',\n element: 'property-editor-location-increase-pattern'\n },\n {\n type: 'gltf-info',\n element: 'property-editor-gltf-info'\n },\n {\n type: 'gltf-fill-targets',\n element: 'property-editor-gltf-fill-targets'\n },\n {\n type: 'gltf-play-targets',\n element: 'property-editor-gltf-play-targets'\n },\n {\n type: 'stocker-location',\n element: 'property-editor-stocker-location'\n }\n]\n"]}
@@ -5,12 +5,15 @@ export default class GLTFFillTargetsEditor extends OxPropertyEditor {
5
5
  static styles: import("lit").CSSResult[];
6
6
  src: string | undefined;
7
7
  private _meshNames;
8
+ private _mode;
8
9
  private _component;
10
+ private _lastExternalValue;
9
11
  updated(changes: PropertyValues<this>): void;
10
12
  private _refreshMeshNames;
11
13
  editorTemplate(value: any, _spec: PropertySpec): TemplateResult;
12
- private _setAll;
14
+ private _setMode;
13
15
  private _onToggle;
16
+ private _applyValue;
14
17
  private _highlightNode;
15
18
  private _clearHighlight;
16
19
  private _getRendererManager;
@@ -2,19 +2,68 @@
2
2
  * Copyright © HatioLab Inc. All rights reserved.
3
3
  *
4
4
  * GLTF fillStyle 타겟 노드 선택 에디터.
5
- * Specific 탭에서 GLTF 모델의 Mesh 노드 목록을 체크박스로 표시하고,
6
- * 선택된 노드 이름을 fillStyleTargets 배열로 저장한다.
7
- * 마우스 오버 시 해당 노드를 OutlinePass로 강조한다.
5
+ * All / None / Custom 모드로 명시적 설정.
6
+ * Custom 모드에서 개별 Mesh 체크박스 선택.
8
7
  */
9
8
  import { __decorate } from "tslib";
10
9
  import '@operato/i18n/ox-i18n.js';
11
- import { css, html } from 'lit';
10
+ import { css, html, nothing } from 'lit';
12
11
  import { customElement, property, state } from 'lit/decorators.js';
13
12
  import { OxPropertyEditor } from '@operato/property-editor';
13
+ import { ScrollbarStyles } from '@operato/styles/scrollbar-styles.js';
14
+ function getMode(value) {
15
+ if (value === '*')
16
+ return 'all';
17
+ if (value === 'none' || !value)
18
+ return 'none';
19
+ if (Array.isArray(value) && value.length > 0)
20
+ return 'custom';
21
+ // [] (빈 배열) = custom with nothing selected
22
+ if (Array.isArray(value))
23
+ return 'custom';
24
+ return 'none';
25
+ }
14
26
  let GLTFFillTargetsEditor = class GLTFFillTargetsEditor extends OxPropertyEditor {
15
27
  static styles = [
16
28
  ...OxPropertyEditor.styles,
29
+ ScrollbarStyles,
17
30
  css `
31
+ .mode-selector {
32
+ display: flex;
33
+ gap: 0;
34
+ margin-bottom: 6px;
35
+ border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));
36
+ border-radius: 6px;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .mode-btn {
41
+ flex: 1;
42
+ padding: 5px 0;
43
+ font-size: 11px;
44
+ font-weight: 500;
45
+ text-align: center;
46
+ cursor: pointer;
47
+ background: var(--md-sys-color-surface-container, #f3edf7);
48
+ color: var(--md-sys-color-on-surface, #1c1b1f);
49
+ border: none;
50
+ border-right: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.1));
51
+ transition: all 0.15s;
52
+ }
53
+
54
+ .mode-btn:last-child {
55
+ border-right: none;
56
+ }
57
+
58
+ .mode-btn:hover {
59
+ background: var(--md-sys-color-surface-container-highest, #e6e0e9);
60
+ }
61
+
62
+ .mode-btn[active] {
63
+ background: var(--md-sys-color-primary, #6750a4);
64
+ color: var(--md-sys-color-on-primary, #fff);
65
+ }
66
+
18
67
  .node-list {
19
68
  display: flex;
20
69
  flex-direction: column;
@@ -27,44 +76,57 @@ let GLTFFillTargetsEditor = class GLTFFillTargetsEditor extends OxPropertyEditor
27
76
  .node-item {
28
77
  display: flex;
29
78
  align-items: center;
30
- gap: 4px;
31
- padding: 2px 4px;
79
+ gap: 6px;
80
+ padding: 4px 8px;
32
81
  cursor: pointer;
33
- border-radius: 2px;
82
+ border-radius: 4px;
83
+ transition: background 0.1s;
34
84
  }
35
85
 
36
86
  .node-item:hover {
37
- background: rgba(242, 101, 34, 0.15);
87
+ background: var(--md-sys-color-primary-container, rgba(103, 80, 164, 0.12));
38
88
  }
39
89
 
40
- .toolbar {
41
- display: flex;
42
- gap: 2px;
43
- margin-bottom: 2px;
90
+ .node-item input[type='checkbox'] {
91
+ margin: 0;
92
+ accent-color: var(--md-sys-color-primary, #6750a4);
44
93
  }
45
94
 
46
- .toolbar button {
95
+ .node-item span {
96
+ font-size: 12px;
97
+ color: var(--md-sys-color-on-surface, #1c1b1f);
98
+ }
99
+
100
+ .mode-desc {
47
101
  font-size: 11px;
48
- padding: 1px 6px;
49
- border: 1px solid var(--md-sys-color-outline, #ccc);
50
- border-radius: 2px;
51
- background: transparent;
52
- color: var(--md-sys-color-on-surface, #333);
102
+ color: var(--md-sys-color-on-surface-variant, #666);
103
+ padding: 4px 0;
104
+ }
105
+
106
+ .mini-btn {
107
+ font-size: 10px;
108
+ padding: 2px 8px;
109
+ border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));
110
+ border-radius: 4px;
111
+ background: var(--md-sys-color-surface-container, #f3edf7);
112
+ color: var(--md-sys-color-on-surface, #1c1b1f);
53
113
  cursor: pointer;
54
114
  }
55
115
 
56
- .toolbar button:hover {
57
- background: rgba(0, 0, 0, 0.06);
116
+ .mini-btn:hover {
117
+ background: var(--md-sys-color-surface-container-highest, #e6e0e9);
58
118
  }
59
119
 
60
120
  .empty {
61
- color: var(--md-sys-color-outline, #999);
121
+ color: var(--md-sys-color-on-surface-variant, #999);
62
122
  font-size: 11px;
63
- padding: 4px;
123
+ padding: 8px;
124
+ text-align: center;
64
125
  }
65
126
  `
66
127
  ];
67
128
  _component = null;
129
+ _lastExternalValue = undefined;
68
130
  updated(changes) {
69
131
  if (changes.has('src')) {
70
132
  this.dispatchEvent(new CustomEvent('i-need-selected', {
@@ -89,77 +151,118 @@ let GLTFFillTargetsEditor = class GLTFFillTargetsEditor extends OxPropertyEditor
89
151
  this._meshNames = [];
90
152
  return;
91
153
  }
92
- // Mesh 노드만 필터링 — 그룹/본 등은 fillStyle 적용 대상이 아님
93
154
  this._meshNames = (this._component.nodeNames ?? []).filter((name) => {
94
155
  const node = ro.getNode(name);
95
156
  return node?.isMesh;
96
157
  });
97
158
  }
98
159
  editorTemplate(value, _spec) {
99
- const targets = Array.isArray(value) ? value : [];
100
- // GLTF 로드 완료 후 nodeNames가 채워지므로, 비어있으면 재시도
101
160
  if (!this._meshNames?.length) {
102
161
  this._refreshMeshNames();
103
162
  }
163
+ // 외부 value가 변경되면 모드 동기화
164
+ if (value !== this._lastExternalValue) {
165
+ this._lastExternalValue = value;
166
+ this._mode = getMode(value);
167
+ }
168
+ const mode = this._mode;
169
+ const meshCount = this._meshNames?.length ?? 0;
170
+ const targets = Array.isArray(this.value) ? this.value : (Array.isArray(value) ? value : []);
104
171
  return html `
105
172
  <fieldset fullwidth>
106
- <legend><ox-i18n msgid="label.fill-targets">fillStyle Targets</ox-i18n></legend>
107
- ${(this._meshNames?.length ?? 0) > 0
108
- ? html `<div class="toolbar">
109
- <button type="button" @click=${() => this._setAll(true)}>All</button>
110
- <button type="button" @click=${() => this._setAll(false)}>None</button>
111
- </div>`
112
- : ''}
113
- <div class="node-list">
114
- ${(this._meshNames?.length ?? 0) === 0
115
- ? html `<div class="empty">
116
- <ox-i18n msgid="label.no-gltf-meshes">No meshes available</ox-i18n>
117
- </div>`
118
- : (this._meshNames ?? []).map(name => html `
119
- <label
120
- class="node-item"
121
- @mouseenter=${() => this._highlightNode(name)}
122
- @mouseleave=${() => this._clearHighlight()}
123
- >
124
- <input
125
- type="checkbox"
126
- .checked=${targets.includes(name)}
127
- @change=${(e) => {
128
- e.stopPropagation();
129
- this._onToggle(name, e.target.checked);
130
- }}
131
- />
132
- <span>${name}</span>
133
- </label>
134
- `)}
135
- </div>
173
+ ${meshCount > 0
174
+ ? html `
175
+ <div class="mode-selector">
176
+ <div class="mode-btn" ?active=${mode === 'all'} @click=${() => this._setMode('all')}>All</div>
177
+ <div class="mode-btn" ?active=${mode === 'none'} @click=${() => this._setMode('none')}>None</div>
178
+ <div class="mode-btn" ?active=${mode === 'custom'} @click=${() => this._setMode('custom')}>Custom</div>
179
+ </div>
180
+
181
+ ${mode === 'all'
182
+ ? html `<div class="mode-desc">${meshCount} meshes — fillStyle applies to all</div>`
183
+ : nothing}
184
+ ${mode === 'none'
185
+ ? html `<div class="mode-desc">fillStyle not applied to any mesh</div>`
186
+ : nothing}
187
+ ${mode === 'custom'
188
+ ? html `
189
+ <div style="display:flex;align-items:center;gap:6px">
190
+ <div class="mode-desc" style="flex:1">${targets.length} / ${meshCount} selected</div>
191
+ <button class="mini-btn" @click=${() => this._applyValue([...(this._meshNames ?? [])])}>All</button>
192
+ <button class="mini-btn" @click=${() => this._applyValue([])}>Clear</button>
193
+ </div>
194
+ <div class="node-list">
195
+ ${(this._meshNames ?? []).map(name => html `
196
+ <label
197
+ class="node-item"
198
+ @mouseenter=${() => this._highlightNode(name)}
199
+ @mouseleave=${() => this._clearHighlight()}
200
+ >
201
+ <input
202
+ type="checkbox"
203
+ .checked=${targets.includes(name)}
204
+ @change=${(e) => {
205
+ e.stopPropagation();
206
+ this._onToggle(name, e.target.checked, targets);
207
+ }}
208
+ />
209
+ <span>${name}</span>
210
+ </label>
211
+ `)}
212
+ </div>
213
+ `
214
+ : nothing}
215
+ `
216
+ : html `<div class="empty">
217
+ <ox-i18n msgid="label.no-gltf-meshes">No meshes available</ox-i18n>
218
+ </div>`}
136
219
  </fieldset>
137
220
  `;
138
221
  }
139
- _setAll(select) {
140
- const newValue = select ? [...(this._meshNames ?? [])] : undefined;
141
- this.dispatchEvent(new CustomEvent('i-need-selected', {
142
- bubbles: true,
143
- composed: true,
144
- detail: {
145
- callback: (selected) => {
146
- selected[0].set('fillStyleTargets', newValue);
222
+ _setMode(mode) {
223
+ const prevMode = this._mode;
224
+ this._mode = mode;
225
+ let newValue;
226
+ switch (mode) {
227
+ case 'all':
228
+ newValue = '*';
229
+ break;
230
+ case 'none':
231
+ newValue = 'none';
232
+ break;
233
+ case 'custom':
234
+ // 이전 상태에 따라: All → 전체 선택, None → 전체 해제
235
+ if (prevMode === 'all') {
236
+ newValue = [...(this._meshNames ?? [])];
147
237
  }
148
- }
149
- }));
238
+ else if (prevMode === 'none') {
239
+ newValue = [];
240
+ }
241
+ else {
242
+ // 이미 custom이면 현재 값 유지
243
+ newValue = Array.isArray(this.value) ? this.value : [];
244
+ }
245
+ break;
246
+ }
247
+ this._applyValue(newValue);
150
248
  }
151
- _onToggle(name, checked) {
152
- const current = [...(Array.isArray(this.value) ? this.value : [])];
249
+ _onToggle(name, checked, current) {
250
+ const list = [...current];
153
251
  if (checked) {
154
- if (!current.includes(name))
155
- current.push(name);
252
+ if (!list.includes(name))
253
+ list.push(name);
156
254
  }
157
255
  else {
158
- const idx = current.indexOf(name);
256
+ const idx = list.indexOf(name);
159
257
  if (idx >= 0)
160
- current.splice(idx, 1);
258
+ list.splice(idx, 1);
161
259
  }
162
- const newValue = current.length > 0 ? current : undefined;
260
+ this._applyValue(list);
261
+ }
262
+ _applyValue(newValue) {
263
+ // 로컬 value 즉시 반영 (re-render)
264
+ this.value = newValue;
265
+ this.requestUpdate();
163
266
  this.dispatchEvent(new CustomEvent('i-need-selected', {
164
267
  bubbles: true,
165
268
  composed: true,
@@ -185,14 +288,12 @@ let GLTFFillTargetsEditor = class GLTFFillTargetsEditor extends OxPropertyEditor
185
288
  const ro = this._component?.realObject;
186
289
  if (!ro)
187
290
  return;
188
- // 선택 상태의 아웃라인 복원 (전체 GLTF 오브젝트)
189
291
  this._getRendererManager()?.setOutlineObjects(ro.object3d ? [ro.object3d] : []);
190
292
  this._component?.invalidate();
191
293
  }
192
294
  _getRendererManager() {
193
295
  const ro = this._component?.realObject;
194
296
  const container = ro?.threeContainer;
195
- // ThreeContainer._capability 또는 ModelLayer._threeCapability
196
297
  return container?._capability?.rendererManager ?? container?._threeCapability?.rendererManager ?? null;
197
298
  }
198
299
  };
@@ -202,6 +303,9 @@ __decorate([
202
303
  __decorate([
203
304
  state()
204
305
  ], GLTFFillTargetsEditor.prototype, "_meshNames", void 0);
306
+ __decorate([
307
+ state()
308
+ ], GLTFFillTargetsEditor.prototype, "_mode", void 0);
205
309
  GLTFFillTargetsEditor = __decorate([
206
310
  customElement('property-editor-gltf-fill-targets')
207
311
  ], GLTFFillTargetsEditor);
@@ -1 +1 @@
1
- {"version":3,"file":"property-editor-gltf-fill-targets.js","sourceRoot":"","sources":["../../src/editors/property-editor-gltf-fill-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;;AAEH,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkC,MAAM,KAAK,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,gBAAgB,EAAgB,MAAM,0BAA0B,CAAA;AAG1D,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,gBAAgB;IACjE,MAAM,CAAC,MAAM,GAAG;QACd,GAAG,gBAAgB,CAAC,MAAM;QAC1B,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgDF;KACF,CAAA;IAMO,UAAU,GAAQ,IAAI,CAAA;IAE9B,OAAO,CAAC,OAA6B;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;gBACjC,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE;oBACN,QAAQ,EAAE,CAAC,QAAe,EAAE,EAAE;wBAC5B,IAAI,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;wBAC/B,IAAI,CAAC,iBAAiB,EAAE,CAAA;oBAC1B,CAAC;iBACF;aACF,CAAC,CACH,CAAA;QACH,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;YACpB,OAAM;QACR,CAAC;QAED,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,UAAiB,CAAA;QAC5C,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;YACpB,OAAM;QACR,CAAC;QAED,6CAA6C;QAC7C,IAAI,CAAC,UAAU,GAAG,CAAE,IAAI,CAAC,UAAU,CAAC,SAAsB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAY,EAAE,EAAE;YACxF,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YAC7B,OAAO,IAAI,EAAE,MAAM,CAAA;QACrB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,cAAc,CAAC,KAAU,EAAE,KAAmB;QAC5C,MAAM,OAAO,GAAa,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAE3D,2CAA2C;QAC3C,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;YAC7B,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAC1B,CAAC;QAED,OAAO,IAAI,CAAA;;;UAGL,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;YAChC,CAAC,CAAC,IAAI,CAAA;+CAC6B,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;+CACxB,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;qBACnD;YACT,CAAC,CAAC,EAAE;;YAEJ,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC;YACpC,CAAC,CAAC,IAAI,CAAA;;qBAEG;YACT,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CACzB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAA;;;kCAGM,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;kCAC/B,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE;;;;iCAI7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;gCACvB,CAAC,CAAQ,EAAE,EAAE;gBACrB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAG,CAAC,CAAC,MAA2B,CAAC,OAAO,CAAC,CAAA;YAC9D,CAAC;;4BAEK,IAAI;;iBAEf,CACF;;;KAGV,CAAA;IACH,CAAC;IAEO,OAAO,CAAC,MAAe;QAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAElE,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;YACjC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE;gBACN,QAAQ,EAAE,CAAC,QAAe,EAAE,EAAE;oBAC5B,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAA;gBAC/C,CAAC;aACF;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAEO,SAAS,CAAC,IAAY,EAAE,OAAgB;QAC9C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QAClE,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjD,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YACjC,IAAI,GAAG,IAAI,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtC,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;QAEzD,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;YACjC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE;gBACN,QAAQ,EAAE,CAAC,QAAe,EAAE,EAAE;oBAC5B,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAA;gBAC/C,CAAC;aACF;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAED,qBAAqB;IAEb,cAAc,CAAC,IAAY;QACjC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,UAAiB,CAAA;QAC7C,IAAI,CAAC,EAAE,EAAE,OAAO;YAAE,OAAM;QAExB,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC,IAAI;YAAE,OAAM;QAEjB,IAAI,CAAC,mBAAmB,EAAE,EAAE,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QACrD,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,CAAA;IAC/B,CAAC;IAEO,eAAe;QACrB,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,UAAiB,CAAA;QAC7C,IAAI,CAAC,EAAE;YAAE,OAAM;QAEf,gCAAgC;QAChC,IAAI,CAAC,mBAAmB,EAAE,EAAE,iBAAiB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC/E,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,CAAA;IAC/B,CAAC;IAEO,mBAAmB;QACzB,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,UAAiB,CAAA;QAC7C,MAAM,SAAS,GAAG,EAAE,EAAE,cAAqB,CAAA;QAC3C,4DAA4D;QAC5D,OAAO,SAAS,EAAE,WAAW,EAAE,eAAe,IAAI,SAAS,EAAE,gBAAgB,EAAE,eAAe,IAAI,IAAI,CAAA;IACxG,CAAC;;AA3JmC;IAAnC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;kDAAgC;AAElC;IAAxB,KAAK,EAAE;yDAAqC;AAxD1B,qBAAqB;IADzC,aAAa,CAAC,mCAAmC,CAAC;GAC9B,qBAAqB,CAkNzC;eAlNoB,qBAAqB","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * GLTF fillStyle 타겟 노드 선택 에디터.\n * Specific 탭에서 GLTF 모델의 Mesh 노드 목록을 체크박스로 표시하고,\n * 선택된 노드 이름을 fillStyleTargets 배열로 저장한다.\n * 마우스 오버 시 해당 노드를 OutlinePass로 강조한다.\n */\n\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'\n\n@customElement('property-editor-gltf-fill-targets')\nexport default class GLTFFillTargetsEditor extends OxPropertyEditor {\n static styles = [\n ...OxPropertyEditor.styles,\n css`\n .node-list {\n display: flex;\n flex-direction: column;\n gap: 1px;\n max-height: 200px;\n overflow-y: auto;\n font-size: 12px;\n }\n\n .node-item {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px 4px;\n cursor: pointer;\n border-radius: 2px;\n }\n\n .node-item:hover {\n background: rgba(242, 101, 34, 0.15);\n }\n\n .toolbar {\n display: flex;\n gap: 2px;\n margin-bottom: 2px;\n }\n\n .toolbar button {\n font-size: 11px;\n padding: 1px 6px;\n border: 1px solid var(--md-sys-color-outline, #ccc);\n border-radius: 2px;\n background: transparent;\n color: var(--md-sys-color-on-surface, #333);\n cursor: pointer;\n }\n\n .toolbar button:hover {\n background: rgba(0, 0, 0, 0.06);\n }\n\n .empty {\n color: var(--md-sys-color-outline, #999);\n font-size: 11px;\n padding: 4px;\n }\n `\n ]\n\n @property({ type: String }) declare src: string | undefined\n\n @state() declare private _meshNames: string[]\n\n private _component: any = null\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: (selected: any[]) => {\n this._component = selected?.[0]\n this._refreshMeshNames()\n }\n }\n })\n )\n }\n }\n\n private _refreshMeshNames() {\n if (!this._component) {\n this._meshNames = []\n return\n }\n\n const ro = this._component.realObject as any\n if (!ro?.getNode) {\n this._meshNames = []\n return\n }\n\n // Mesh 노드만 필터링 — 그룹/본 등은 fillStyle 적용 대상이 아님\n this._meshNames = ((this._component.nodeNames as string[]) ?? []).filter((name: string) => {\n const node = ro.getNode(name)\n return node?.isMesh\n })\n }\n\n editorTemplate(value: any, _spec: PropertySpec): TemplateResult {\n const targets: string[] = Array.isArray(value) ? value : []\n\n // GLTF 로드 완료 후 nodeNames가 채워지므로, 비어있으면 재시도\n if (!this._meshNames?.length) {\n this._refreshMeshNames()\n }\n\n return html`\n <fieldset fullwidth>\n <legend><ox-i18n msgid=\"label.fill-targets\">fillStyle Targets</ox-i18n></legend>\n ${(this._meshNames?.length ?? 0) > 0\n ? html`<div class=\"toolbar\">\n <button type=\"button\" @click=${() => this._setAll(true)}>All</button>\n <button type=\"button\" @click=${() => this._setAll(false)}>None</button>\n </div>`\n : ''}\n <div class=\"node-list\">\n ${(this._meshNames?.length ?? 0) === 0\n ? html`<div class=\"empty\">\n <ox-i18n msgid=\"label.no-gltf-meshes\">No meshes available</ox-i18n>\n </div>`\n : (this._meshNames ?? []).map(\n name => html`\n <label\n class=\"node-item\"\n @mouseenter=${() => this._highlightNode(name)}\n @mouseleave=${() => this._clearHighlight()}\n >\n <input\n type=\"checkbox\"\n .checked=${targets.includes(name)}\n @change=${(e: Event) => {\n e.stopPropagation()\n this._onToggle(name, (e.target as HTMLInputElement).checked)\n }}\n />\n <span>${name}</span>\n </label>\n `\n )}\n </div>\n </fieldset>\n `\n }\n\n private _setAll(select: boolean) {\n const newValue = select ? [...(this._meshNames ?? [])] : undefined\n\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('fillStyleTargets', newValue)\n }\n }\n })\n )\n }\n\n private _onToggle(name: string, checked: boolean) {\n const current = [...(Array.isArray(this.value) ? this.value : [])]\n if (checked) {\n if (!current.includes(name)) current.push(name)\n } else {\n const idx = current.indexOf(name)\n if (idx >= 0) current.splice(idx, 1)\n }\n\n const newValue = current.length > 0 ? current : undefined\n\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('fillStyleTargets', newValue)\n }\n }\n })\n )\n }\n\n // --- Outline 강조 ---\n\n private _highlightNode(name: string) {\n const ro = this._component?.realObject as any\n if (!ro?.getNode) return\n\n const node = ro.getNode(name)\n if (!node) return\n\n this._getRendererManager()?.setOutlineObjects([node])\n this._component?.invalidate()\n }\n\n private _clearHighlight() {\n const ro = this._component?.realObject as any\n if (!ro) return\n\n // 선택 상태의 아웃라인 복원 (전체 GLTF 오브젝트)\n this._getRendererManager()?.setOutlineObjects(ro.object3d ? [ro.object3d] : [])\n this._component?.invalidate()\n }\n\n private _getRendererManager(): any {\n const ro = this._component?.realObject as any\n const container = ro?.threeContainer as any\n // ThreeContainer._capability 또는 ModelLayer._threeCapability\n return container?._capability?.rendererManager ?? container?._threeCapability?.rendererManager ?? null\n }\n}\n"]}
1
+ {"version":3,"file":"property-editor-gltf-fill-targets.js","sourceRoot":"","sources":["../../src/editors/property-editor-gltf-fill-targets.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * GLTF fillStyle 타겟 노드 선택 에디터.\n * All / None / Custom 모드로 명시적 설정.\n * Custom 모드에서 개별 Mesh 체크박스 선택.\n */\n\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, PropertyValues, TemplateResult, nothing } 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'\n\n// fillStyleTargets 값 규칙:\n// undefined 또는 미설정 → None (적용 안 함, 기본값)\n// '*' → All (전체 메시 적용)\n// ['mesh_0', ...] → Custom (선택 메시만 적용)\n\ntype FillMode = 'all' | 'none' | 'custom'\n\nfunction getMode(value: any): FillMode {\n if (value === '*') return 'all'\n if (value === 'none' || !value) return 'none'\n if (Array.isArray(value) && value.length > 0) return 'custom'\n // [] (빈 배열) = custom with nothing selected\n if (Array.isArray(value)) return 'custom'\n return 'none'\n}\n\n@customElement('property-editor-gltf-fill-targets')\nexport default class GLTFFillTargetsEditor extends OxPropertyEditor {\n static styles = [\n ...OxPropertyEditor.styles,\n ScrollbarStyles,\n css`\n .mode-selector {\n display: flex;\n gap: 0;\n margin-bottom: 6px;\n border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));\n border-radius: 6px;\n overflow: hidden;\n }\n\n .mode-btn {\n flex: 1;\n padding: 5px 0;\n font-size: 11px;\n font-weight: 500;\n text-align: center;\n cursor: pointer;\n background: var(--md-sys-color-surface-container, #f3edf7);\n color: var(--md-sys-color-on-surface, #1c1b1f);\n border: none;\n border-right: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.1));\n transition: all 0.15s;\n }\n\n .mode-btn:last-child {\n border-right: none;\n }\n\n .mode-btn:hover {\n background: var(--md-sys-color-surface-container-highest, #e6e0e9);\n }\n\n .mode-btn[active] {\n background: var(--md-sys-color-primary, #6750a4);\n color: var(--md-sys-color-on-primary, #fff);\n }\n\n .node-list {\n display: flex;\n flex-direction: column;\n gap: 1px;\n max-height: 200px;\n overflow-y: auto;\n font-size: 12px;\n }\n\n .node-item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n cursor: pointer;\n border-radius: 4px;\n transition: background 0.1s;\n }\n\n .node-item:hover {\n background: var(--md-sys-color-primary-container, rgba(103, 80, 164, 0.12));\n }\n\n .node-item input[type='checkbox'] {\n margin: 0;\n accent-color: var(--md-sys-color-primary, #6750a4);\n }\n\n .node-item span {\n font-size: 12px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n }\n\n .mode-desc {\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant, #666);\n padding: 4px 0;\n }\n\n .mini-btn {\n font-size: 10px;\n padding: 2px 8px;\n border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));\n border-radius: 4px;\n background: var(--md-sys-color-surface-container, #f3edf7);\n color: var(--md-sys-color-on-surface, #1c1b1f);\n cursor: pointer;\n }\n\n .mini-btn:hover {\n background: var(--md-sys-color-surface-container-highest, #e6e0e9);\n }\n\n .empty {\n color: var(--md-sys-color-on-surface-variant, #999);\n font-size: 11px;\n padding: 8px;\n text-align: center;\n }\n `\n ]\n\n @property({ type: String }) declare src: string | undefined\n\n @state() declare private _meshNames: string[]\n @state() declare private _mode: FillMode\n\n private _component: any = null\n private _lastExternalValue: any = undefined\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: (selected: any[]) => {\n this._component = selected?.[0]\n this._refreshMeshNames()\n }\n }\n })\n )\n }\n }\n\n private _refreshMeshNames() {\n if (!this._component) {\n this._meshNames = []\n return\n }\n\n const ro = this._component.realObject as any\n if (!ro?.getNode) {\n this._meshNames = []\n return\n }\n\n this._meshNames = ((this._component.nodeNames as string[]) ?? []).filter((name: string) => {\n const node = ro.getNode(name)\n return node?.isMesh\n })\n }\n\n editorTemplate(value: any, _spec: PropertySpec): TemplateResult {\n if (!this._meshNames?.length) {\n this._refreshMeshNames()\n }\n\n // 외부 value가 변경되면 모드 동기화\n if (value !== this._lastExternalValue) {\n this._lastExternalValue = value\n this._mode = getMode(value)\n }\n\n const mode = this._mode\n const meshCount = this._meshNames?.length ?? 0\n const targets: string[] = Array.isArray(this.value) ? this.value : (Array.isArray(value) ? value : [])\n\n return html`\n <fieldset fullwidth>\n ${meshCount > 0\n ? html`\n <div class=\"mode-selector\">\n <div class=\"mode-btn\" ?active=${mode === 'all'} @click=${() => this._setMode('all')}>All</div>\n <div class=\"mode-btn\" ?active=${mode === 'none'} @click=${() => this._setMode('none')}>None</div>\n <div class=\"mode-btn\" ?active=${mode === 'custom'} @click=${() => this._setMode('custom')}>Custom</div>\n </div>\n\n ${mode === 'all'\n ? html`<div class=\"mode-desc\">${meshCount} meshes — fillStyle applies to all</div>`\n : nothing}\n ${mode === 'none'\n ? html`<div class=\"mode-desc\">fillStyle not applied to any mesh</div>`\n : nothing}\n ${mode === 'custom'\n ? html`\n <div style=\"display:flex;align-items:center;gap:6px\">\n <div class=\"mode-desc\" style=\"flex:1\">${targets.length} / ${meshCount} selected</div>\n <button class=\"mini-btn\" @click=${() => this._applyValue([...(this._meshNames ?? [])])}>All</button>\n <button class=\"mini-btn\" @click=${() => this._applyValue([])}>Clear</button>\n </div>\n <div class=\"node-list\">\n ${(this._meshNames ?? []).map(\n name => html`\n <label\n class=\"node-item\"\n @mouseenter=${() => this._highlightNode(name)}\n @mouseleave=${() => this._clearHighlight()}\n >\n <input\n type=\"checkbox\"\n .checked=${targets.includes(name)}\n @change=${(e: Event) => {\n e.stopPropagation()\n this._onToggle(name, (e.target as HTMLInputElement).checked, targets)\n }}\n />\n <span>${name}</span>\n </label>\n `\n )}\n </div>\n `\n : nothing}\n `\n : html`<div class=\"empty\">\n <ox-i18n msgid=\"label.no-gltf-meshes\">No meshes available</ox-i18n>\n </div>`}\n </fieldset>\n `\n }\n\n private _setMode(mode: FillMode) {\n const prevMode = this._mode\n this._mode = mode\n\n let newValue: any\n switch (mode) {\n case 'all':\n newValue = '*'\n break\n case 'none':\n newValue = 'none'\n break\n case 'custom':\n // 이전 상태에 따라: All → 전체 선택, None → 전체 해제\n if (prevMode === 'all') {\n newValue = [...(this._meshNames ?? [])]\n } else if (prevMode === 'none') {\n newValue = []\n } else {\n // 이미 custom이면 현재 값 유지\n newValue = Array.isArray(this.value) ? this.value : []\n }\n break\n }\n\n this._applyValue(newValue)\n }\n\n private _onToggle(name: string, checked: boolean, current: string[]) {\n const list = [...current]\n if (checked) {\n if (!list.includes(name)) list.push(name)\n } else {\n const idx = list.indexOf(name)\n if (idx >= 0) list.splice(idx, 1)\n }\n\n this._applyValue(list)\n }\n\n private _applyValue(newValue: any) {\n // 로컬 value 즉시 반영 (re-render)\n this.value = newValue\n this.requestUpdate()\n\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('fillStyleTargets', newValue)\n }\n }\n })\n )\n }\n\n // --- Outline 강조 ---\n\n private _highlightNode(name: string) {\n const ro = this._component?.realObject as any\n if (!ro?.getNode) return\n\n const node = ro.getNode(name)\n if (!node) return\n\n this._getRendererManager()?.setOutlineObjects([node])\n this._component?.invalidate()\n }\n\n private _clearHighlight() {\n const ro = this._component?.realObject as any\n if (!ro) return\n\n this._getRendererManager()?.setOutlineObjects(ro.object3d ? [ro.object3d] : [])\n this._component?.invalidate()\n }\n\n private _getRendererManager(): any {\n const ro = this._component?.realObject as any\n const container = ro?.threeContainer as any\n return container?._capability?.rendererManager ?? container?._threeCapability?.rendererManager ?? null\n }\n}\n"]}
@@ -1,5 +1,4 @@
1
1
  import '@material/web/icon/icon.js';
2
- import '@material/web/button/elevated-button.js';
3
2
  import '@operato/i18n/ox-i18n.js';
4
3
  import { PropertyValues, TemplateResult } from 'lit';
5
4
  import { OxPropertyEditor, PropertySpec } from '@operato/property-editor';
@@ -7,17 +6,33 @@ import { Component } from '@hatiolab/things-scene';
7
6
  export default class GLTFInfoEditor extends OxPropertyEditor {
8
7
  static styles: import("lit").CSSResult[];
9
8
  src: string | undefined;
9
+ private _component;
10
10
  width: number;
11
11
  height: number;
12
12
  depth: number;
13
+ currentWidth: number;
14
+ currentHeight: number;
15
+ currentDepth: number;
16
+ meshCount: number;
17
+ vertexCount: number;
18
+ animationCount: number;
19
+ materialCount: number;
13
20
  constructor();
14
21
  editorTemplate(value: any, spec: PropertySpec): TemplateResult;
22
+ private _formatNumber;
23
+ private _isProportional;
15
24
  private _applyAction;
16
25
  /**
17
- * 현재 컴포넌트의 W/H/D가장 값을 기준으로,
18
- * 원래 모델의 비율에 맞게 나머지 치수를 조절한다.
26
+ * W, H 중 짧은 쪽을 기준으로 원래 모델의 비율에 맞게 치수를 조절한다.
27
+ * (contain 방식 모델이 컴포넌트 영역 안에 들어감)
19
28
  */
20
29
  private _applyProportional;
30
+ private _getRatioLock;
31
+ private _setRatioLock;
32
+ private _refreshCurrentSize;
21
33
  updated(changes: PropertyValues<this>): void;
22
- fetchSourceInfo(component: Component, src: string): Promise<unknown>;
34
+ private _pollTimer?;
35
+ fetchSourceInfo(component: Component, src: string): Promise<void>;
36
+ private _tryReadFromRealObject;
37
+ disconnectedCallback(): void;
23
38
  }