@operato/scene-urdf 10.0.0-beta.1 → 10.0.0-beta.18

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 +6 -0
  2. package/dist/editors/index.js +7 -1
  3. package/dist/editors/index.js.map +1 -1
  4. package/dist/editors/property-editor-urdf-joints.d.ts +54 -0
  5. package/dist/editors/property-editor-urdf-joints.js +242 -0
  6. package/dist/editors/property-editor-urdf-joints.js.map +1 -0
  7. package/dist/index.d.ts +4 -2
  8. package/dist/index.js +16 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/real-object-urdf.d.ts +75 -0
  11. package/dist/real-object-urdf.js +284 -0
  12. package/dist/real-object-urdf.js.map +1 -0
  13. package/dist/templates/index.d.ts +3 -0
  14. package/dist/templates/index.js +2 -3
  15. package/dist/templates/index.js.map +1 -1
  16. package/dist/templates/{urdf-controller.d.ts → urdf.d.ts} +3 -0
  17. package/dist/templates/urdf.js +19 -0
  18. package/dist/templates/urdf.js.map +1 -0
  19. package/dist/urdf-object.d.ts +264 -0
  20. package/dist/urdf-object.js +163 -0
  21. package/dist/urdf-object.js.map +1 -0
  22. package/icons/urdf.png +0 -0
  23. package/package.json +5 -4
  24. package/dist/elements/drag-n-drop.d.ts +0 -2
  25. package/dist/elements/drag-n-drop.js +0 -126
  26. package/dist/elements/drag-n-drop.js.map +0 -1
  27. package/dist/elements/urdf-controller-element.d.ts +0 -12
  28. package/dist/elements/urdf-controller-element.js +0 -283
  29. package/dist/elements/urdf-controller-element.js.map +0 -1
  30. package/dist/elements/urdf-drag-controls.d.ts +0 -32
  31. package/dist/elements/urdf-drag-controls.js +0 -197
  32. package/dist/elements/urdf-drag-controls.js.map +0 -1
  33. package/dist/elements/urdf-manipulator-element.d.ts +0 -15
  34. package/dist/elements/urdf-manipulator-element.js +0 -112
  35. package/dist/elements/urdf-manipulator-element.js.map +0 -1
  36. package/dist/elements/urdf-viewer-element.d.ts +0 -53
  37. package/dist/elements/urdf-viewer-element.js +0 -414
  38. package/dist/elements/urdf-viewer-element.js.map +0 -1
  39. package/dist/templates/urdf-controller.js +0 -16
  40. package/dist/templates/urdf-controller.js.map +0 -1
  41. package/dist/templates/urdf-viewer.d.ts +0 -14
  42. package/dist/templates/urdf-viewer.js +0 -16
  43. package/dist/templates/urdf-viewer.js.map +0 -1
  44. package/dist/urdf-controller.d.ts +0 -15
  45. package/dist/urdf-controller.js +0 -70
  46. package/dist/urdf-controller.js.map +0 -1
  47. package/dist/urdf-viewer.d.ts +0 -16
  48. package/dist/urdf-viewer.js +0 -202
  49. package/dist/urdf-viewer.js.map +0 -1
@@ -0,0 +1,6 @@
1
+ import './property-editor-urdf-joints.js';
2
+ declare const _default: {
3
+ type: string;
4
+ element: string;
5
+ }[];
6
+ export default _default;
@@ -1,2 +1,8 @@
1
- "use strict";
1
+ import './property-editor-urdf-joints.js';
2
+ export default [
3
+ {
4
+ type: 'urdf-joints',
5
+ element: 'property-editor-urdf-joints'
6
+ }
7
+ ];
2
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/editors/index.ts"],"names":[],"mappings":"","sourcesContent":[""]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/editors/index.ts"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAA;AAEzC,eAAe;IACb;QACE,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,6BAA6B;KACvC;CACF,CAAA","sourcesContent":["import './property-editor-urdf-joints.js'\n\nexport default [\n {\n type: 'urdf-joints',\n element: 'property-editor-urdf-joints'\n }\n]\n"]}
@@ -0,0 +1,54 @@
1
+ import '@operato/i18n/ox-i18n.js';
2
+ import { TemplateResult } from 'lit';
3
+ import { OxPropertyEditor, PropertySpec } from '@operato/property-editor';
4
+ type JointMeta = {
5
+ name: string;
6
+ type: 'fixed' | 'continuous' | 'revolute' | 'planar' | 'prismatic' | 'floating';
7
+ axis: {
8
+ x: number;
9
+ y: number;
10
+ z: number;
11
+ };
12
+ limit: {
13
+ lower: number;
14
+ upper: number;
15
+ effort: number;
16
+ velocity: number;
17
+ };
18
+ };
19
+ /**
20
+ * URDF 조인트 슬라이더 에디터.
21
+ *
22
+ * 값(value)은 state.joints 객체: `{ [jointName]: number | { value: number } }`.
23
+ * 조인트 메타(이름/타입/axis/limit)는 로드된 RealObjectURDF.jointMeta에서 읽으며,
24
+ * 선택된 컴포넌트 참조를 얻기 위해 `i-need-selected` 커스텀 이벤트를 사용한다.
25
+ *
26
+ * 슬라이더 조작 시 state.joints를 immutable하게 업데이트한 새 객체를 value로
27
+ * 설정 — things-scene setState의 deep-equality 변화 감지를 통과시키기 위함.
28
+ */
29
+ export default class URDFJointsEditor extends OxPropertyEditor {
30
+ static styles: import("lit").CSSResult[];
31
+ private _component;
32
+ private _pollTimer?;
33
+ meta: JointMeta[];
34
+ constructor();
35
+ editorTemplate(value: any, _spec: PropertySpec): TemplateResult;
36
+ private _renderJointSlider;
37
+ private _onSlide;
38
+ /**
39
+ * 슬라이더 드래그 종료 시 브라우저가 네이티브 'change' 이벤트를 발행한다.
40
+ * 이 이벤트가 OxPropertyEditor 베이스의 _valueChanged(renderRoot 리스너)에
41
+ * 도달하면 input.value(문자열)를 this.value에 assign해버려서 joints 객체가
42
+ * "45" 같은 문자열로 덮어쓰여진다. shadow DOM 내부에서 바로 stopPropagation.
43
+ */
44
+ private _stopChange;
45
+ private _onReset;
46
+ connectedCallback(): void;
47
+ disconnectedCallback(): void;
48
+ /**
49
+ * 선택된 URDF 컴포넌트를 얻어 jointMeta를 읽는다. 로드가 아직이면 주기적 폴링.
50
+ */
51
+ private _requestSelectedComponent;
52
+ private _refreshJointMeta;
53
+ }
54
+ export {};
@@ -0,0 +1,242 @@
1
+ import { __decorate } from "tslib";
2
+ import '@operato/i18n/ox-i18n.js';
3
+ import { css, html } from 'lit';
4
+ import { customElement, state } from 'lit/decorators.js';
5
+ import { OxPropertyEditor } from '@operato/property-editor';
6
+ import { ScrollbarStyles } from '@operato/styles/scrollbar-styles.js';
7
+ const RAD2DEG = 180 / Math.PI;
8
+ /**
9
+ * URDF 조인트 슬라이더 에디터.
10
+ *
11
+ * 값(value)은 state.joints 객체: `{ [jointName]: number | { value: number } }`.
12
+ * 조인트 메타(이름/타입/axis/limit)는 로드된 RealObjectURDF.jointMeta에서 읽으며,
13
+ * 선택된 컴포넌트 참조를 얻기 위해 `i-need-selected` 커스텀 이벤트를 사용한다.
14
+ *
15
+ * 슬라이더 조작 시 state.joints를 immutable하게 업데이트한 새 객체를 value로
16
+ * 설정 — things-scene setState의 deep-equality 변화 감지를 통과시키기 위함.
17
+ */
18
+ let URDFJointsEditor = class URDFJointsEditor extends OxPropertyEditor {
19
+ static styles = [
20
+ ...OxPropertyEditor.styles,
21
+ ScrollbarStyles,
22
+ css `
23
+ [joints-panel] {
24
+ max-height: 320px;
25
+ overflow-y: auto;
26
+ padding: 4px 0;
27
+ }
28
+
29
+ .joint-row {
30
+ display: grid;
31
+ grid-template-columns: 80px 1fr 60px;
32
+ gap: 6px;
33
+ align-items: center;
34
+ margin: 3px 0;
35
+ }
36
+
37
+ .joint-row > .joint-name {
38
+ font-size: 11px;
39
+ color: var(--md-sys-color-on-surface, #1c1b1f);
40
+ text-align: right;
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ white-space: nowrap;
44
+ }
45
+
46
+ .joint-row > .joint-name[data-fixed] {
47
+ color: var(--md-sys-color-on-surface-variant, #888);
48
+ font-style: italic;
49
+ }
50
+
51
+ .joint-row > input[type='range'] {
52
+ width: 100%;
53
+ accent-color: var(--md-sys-color-primary, #6750a4);
54
+ }
55
+
56
+ .joint-row > .joint-value {
57
+ font: 11px monospace;
58
+ color: var(--md-sys-color-on-surface-variant, #666);
59
+ text-align: right;
60
+ }
61
+
62
+ .joint-empty,
63
+ .joint-waiting {
64
+ font-size: 11px;
65
+ color: var(--md-sys-color-on-surface-variant, #888);
66
+ padding: 8px 10px;
67
+ font-style: italic;
68
+ }
69
+
70
+ .joint-type-badge {
71
+ display: inline-block;
72
+ font-size: 9px;
73
+ padding: 1px 5px;
74
+ margin-left: 4px;
75
+ border-radius: 6px;
76
+ background: var(--md-sys-color-secondary-container, #e8def8);
77
+ color: var(--md-sys-color-on-secondary-container, #1d192b);
78
+ text-transform: uppercase;
79
+ }
80
+
81
+ .reset-btn {
82
+ font-size: 10px;
83
+ padding: 3px 8px;
84
+ margin-top: 4px;
85
+ border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.2));
86
+ border-radius: 6px;
87
+ background: var(--md-sys-color-surface-container, #f3edf7);
88
+ cursor: pointer;
89
+ color: var(--md-sys-color-on-surface, #1c1b1f);
90
+ }
91
+ .reset-btn:hover {
92
+ background: var(--md-sys-color-surface-container-highest, #e6e0e9);
93
+ }
94
+ `
95
+ ];
96
+ _component = null;
97
+ _pollTimer;
98
+ constructor() {
99
+ super();
100
+ this.meta = [];
101
+ }
102
+ editorTemplate(value, _spec) {
103
+ const joints = (value || {});
104
+ return html `
105
+ <fieldset fullwidth>
106
+ <legend>Joints</legend>
107
+ <div joints-panel>
108
+ ${this.meta.length === 0
109
+ ? html `<div class="joint-waiting">Waiting for URDF to load…</div>`
110
+ : this.meta.map(m => this._renderJointSlider(m, joints))}
111
+ </div>
112
+ ${this.meta.length > 0
113
+ ? html `<button class="reset-btn" @click=${this._onReset}>Reset all</button>`
114
+ : null}
115
+ </fieldset>
116
+ `;
117
+ }
118
+ _renderJointSlider(m, joints) {
119
+ const raw = joints[m.name];
120
+ const internal = typeof raw === 'number' ? raw : (raw?.value ?? 0);
121
+ const isRad = m.type === 'revolute' || m.type === 'continuous';
122
+ const scale = isRad ? RAD2DEG : 1;
123
+ const unit = isRad ? '°' : 'm';
124
+ const minDisp = m.limit.lower * scale;
125
+ const maxDisp = m.limit.upper * scale;
126
+ const dispVal = internal * scale;
127
+ const step = isRad ? 1 : Math.max((m.limit.upper - m.limit.lower) / 100, 0.001);
128
+ return html `
129
+ <div class="joint-row">
130
+ <span class="joint-name" title="${m.name} (${m.type})">
131
+ ${m.name}
132
+ <span class="joint-type-badge">${m.type.substring(0, 3)}</span>
133
+ </span>
134
+ <input
135
+ type="range"
136
+ min=${minDisp}
137
+ max=${maxDisp}
138
+ step=${step}
139
+ .value=${String(dispVal)}
140
+ data-joint=${m.name}
141
+ data-scale=${scale}
142
+ @input=${this._onSlide}
143
+ @change=${this._stopChange}
144
+ />
145
+ <span class="joint-value">${dispVal.toFixed(isRad ? 1 : 3)}${unit}</span>
146
+ </div>
147
+ `;
148
+ }
149
+ _onSlide = (e) => {
150
+ e.stopPropagation();
151
+ const input = e.target;
152
+ const jointName = input.dataset.joint;
153
+ const scale = parseFloat(input.dataset.scale || '1');
154
+ const internal = parseFloat(input.value) / scale;
155
+ // 이전 값을 immutable하게 새 객체로 머지. state.joints를 mutate하면
156
+ // setState의 deep-equality 체크에서 before=after로 판정되어 change 감지
157
+ // 실패 → onchange가 fire되지 않음. Phase 1에서 확인된 규칙.
158
+ const prev = (this.value || {});
159
+ const next = { ...prev, [jointName]: internal };
160
+ this.value = next;
161
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
162
+ };
163
+ /**
164
+ * 슬라이더 드래그 종료 시 브라우저가 네이티브 'change' 이벤트를 발행한다.
165
+ * 이 이벤트가 OxPropertyEditor 베이스의 _valueChanged(renderRoot 리스너)에
166
+ * 도달하면 input.value(문자열)를 this.value에 assign해버려서 joints 객체가
167
+ * "45" 같은 문자열로 덮어쓰여진다. shadow DOM 내부에서 바로 stopPropagation.
168
+ */
169
+ _stopChange = (e) => {
170
+ e.stopPropagation();
171
+ };
172
+ _onReset = (e) => {
173
+ e.stopPropagation();
174
+ this.value = {};
175
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
176
+ };
177
+ connectedCallback() {
178
+ super.connectedCallback();
179
+ this._requestSelectedComponent();
180
+ }
181
+ disconnectedCallback() {
182
+ super.disconnectedCallback();
183
+ if (this._pollTimer) {
184
+ clearInterval(this._pollTimer);
185
+ this._pollTimer = undefined;
186
+ }
187
+ }
188
+ /**
189
+ * 선택된 URDF 컴포넌트를 얻어 jointMeta를 읽는다. 로드가 아직이면 주기적 폴링.
190
+ */
191
+ _requestSelectedComponent() {
192
+ this.dispatchEvent(new CustomEvent('i-need-selected', {
193
+ bubbles: true,
194
+ composed: true,
195
+ detail: {
196
+ callback: (selected) => {
197
+ const component = selected?.[0];
198
+ if (!component)
199
+ return;
200
+ this._component = component;
201
+ this._refreshJointMeta();
202
+ }
203
+ }
204
+ }));
205
+ }
206
+ _refreshJointMeta() {
207
+ const ro = this._component?.realObject;
208
+ const meta = ro?.jointMeta;
209
+ if (meta && meta.length > 0) {
210
+ this.meta = meta;
211
+ if (this._pollTimer) {
212
+ clearInterval(this._pollTimer);
213
+ this._pollTimer = undefined;
214
+ }
215
+ return;
216
+ }
217
+ // 아직 로드 전이면 폴링 (최대 20회 = 10초)
218
+ if (this._pollTimer)
219
+ return;
220
+ let retries = 0;
221
+ this._pollTimer = window.setInterval(() => {
222
+ const m = this._component?.realObject?.jointMeta;
223
+ if (m && m.length > 0) {
224
+ this.meta = m;
225
+ clearInterval(this._pollTimer);
226
+ this._pollTimer = undefined;
227
+ }
228
+ else if (++retries >= 20) {
229
+ clearInterval(this._pollTimer);
230
+ this._pollTimer = undefined;
231
+ }
232
+ }, 500);
233
+ }
234
+ };
235
+ __decorate([
236
+ state()
237
+ ], URDFJointsEditor.prototype, "meta", void 0);
238
+ URDFJointsEditor = __decorate([
239
+ customElement('property-editor-urdf-joints')
240
+ ], URDFJointsEditor);
241
+ export default URDFJointsEditor;
242
+ //# sourceMappingURL=property-editor-urdf-joints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property-editor-urdf-joints.js","sourceRoot":"","sources":["../../src/editors/property-editor-urdf-joints.ts"],"names":[],"mappings":";AAAA,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,EAAE,gBAAgB,EAAgB,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AASrE,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,EAAE,CAAA;AAE7B;;;;;;;;;GASG;AAEY,IAAM,gBAAgB,GAAtB,MAAM,gBAAiB,SAAQ,gBAAgB;IAC5D,MAAM,CAAC,MAAM,GAAG;QACd,GAAG,gBAAgB,CAAC,MAAM;QAC1B,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAwEF;KACF,CAAA;IAEO,UAAU,GAAQ,IAAI,CAAA;IACtB,UAAU,CAAS;IAI3B;QACE,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,EAAE,CAAA;IAChB,CAAC;IAED,cAAc,CAAC,KAAU,EAAE,KAAmB;QAC5C,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE,CAAgD,CAAA;QAE3E,OAAO,IAAI,CAAA;;;;YAIH,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YACtB,CAAC,CAAC,IAAI,CAAA,4DAA4D;YAClE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;;UAE1D,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;YACpB,CAAC,CAAC,IAAI,CAAA,oCAAoC,IAAI,CAAC,QAAQ,qBAAqB;YAC5E,CAAC,CAAC,IAAI;;KAEX,CAAA;IACH,CAAC;IAEO,kBAAkB,CAAC,CAAY,EAAE,MAAmD;QAC1F,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC1B,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAA;QAClE,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAA;QAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;QAC9B,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAA;QACrC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAA;QACrC,MAAM,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;QAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,CAAA;QAE/E,OAAO,IAAI,CAAA;;0CAE2B,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;YAC/C,CAAC,CAAC,IAAI;2CACyB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;;;;gBAIjD,OAAO;gBACP,OAAO;iBACN,IAAI;mBACF,MAAM,CAAC,OAAO,CAAC;uBACX,CAAC,CAAC,IAAI;uBACN,KAAK;mBACT,IAAI,CAAC,QAAQ;oBACZ,IAAI,CAAC,WAAW;;oCAEA,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;;KAEpE,CAAA;IACH,CAAC;IAEO,QAAQ,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC9B,CAAC,CAAC,eAAe,EAAE,CAAA;QACnB,MAAM,KAAK,GAAG,CAAC,CAAC,MAA0B,CAAA;QAC1C,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAM,CAAA;QACtC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC,CAAA;QACpD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;QAEhD,qDAAqD;QACrD,4DAA4D;QAC5D,8CAA8C;QAC9C,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAwB,CAAA;QACtD,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAA;QAC/C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QAEjB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAClF,CAAC,CAAA;IAED;;;;;OAKG;IACK,WAAW,GAAG,CAAC,CAAQ,EAAE,EAAE;QACjC,CAAC,CAAC,eAAe,EAAE,CAAA;IACrB,CAAC,CAAA;IAEO,QAAQ,GAAG,CAAC,CAAa,EAAE,EAAE;QACnC,CAAC,CAAC,eAAe,EAAE,CAAA;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;QACf,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAClF,CAAC,CAAA;IAED,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,IAAI,CAAC,yBAAyB,EAAE,CAAA;IAClC,CAAC;IAED,oBAAoB;QAClB,KAAK,CAAC,oBAAoB,EAAE,CAAA;QAC5B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;QAC7B,CAAC;IACH,CAAC;IAED;;OAEG;IACK,yBAAyB;QAC/B,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,MAAM,SAAS,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;oBAC/B,IAAI,CAAC,SAAS;wBAAE,OAAM;oBACtB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;oBAC3B,IAAI,CAAC,iBAAiB,EAAE,CAAA;gBAC1B,CAAC;aACF;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAEO,iBAAiB;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,CAAA;QACtC,MAAM,IAAI,GAA4B,EAAE,EAAE,SAAS,CAAA;QACnD,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;YAChB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;gBAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;YAC7B,CAAC;YACD,OAAM;QACR,CAAC;QAED,8BAA8B;QAC9B,IAAI,IAAI,CAAC,UAAU;YAAE,OAAM;QAC3B,IAAI,OAAO,GAAG,CAAC,CAAA;QACf,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;YACxC,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,CAAA;YAChD,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;gBACb,aAAa,CAAC,IAAI,CAAC,UAAW,CAAC,CAAA;gBAC/B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;YAC7B,CAAC;iBAAM,IAAI,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;gBAC3B,aAAa,CAAC,IAAI,CAAC,UAAW,CAAC,CAAA;gBAC/B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;YAC7B,CAAC;QACH,CAAC,EAAE,GAAG,CAAC,CAAA;IACT,CAAC;;AAtJgB;IAAhB,KAAK,EAAE;8CAA0B;AAlFf,gBAAgB;IADpC,aAAa,CAAC,6BAA6B,CAAC;GACxB,gBAAgB,CAyOpC;eAzOoB,gBAAgB","sourcesContent":["import '@operato/i18n/ox-i18n.js'\n\nimport { css, html, TemplateResult } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { OxPropertyEditor, PropertySpec } from '@operato/property-editor'\nimport { ScrollbarStyles } from '@operato/styles/scrollbar-styles.js'\n\ntype JointMeta = {\n name: string\n type: 'fixed' | 'continuous' | 'revolute' | 'planar' | 'prismatic' | 'floating'\n axis: { x: number; y: number; z: number }\n limit: { lower: number; upper: number; effort: number; velocity: number }\n}\n\nconst RAD2DEG = 180 / Math.PI\n\n/**\n * URDF 조인트 슬라이더 에디터.\n *\n * 값(value)은 state.joints 객체: `{ [jointName]: number | { value: number } }`.\n * 조인트 메타(이름/타입/axis/limit)는 로드된 RealObjectURDF.jointMeta에서 읽으며,\n * 선택된 컴포넌트 참조를 얻기 위해 `i-need-selected` 커스텀 이벤트를 사용한다.\n *\n * 슬라이더 조작 시 state.joints를 immutable하게 업데이트한 새 객체를 value로\n * 설정 — things-scene setState의 deep-equality 변화 감지를 통과시키기 위함.\n */\n@customElement('property-editor-urdf-joints')\nexport default class URDFJointsEditor extends OxPropertyEditor {\n static styles = [\n ...OxPropertyEditor.styles,\n ScrollbarStyles,\n css`\n [joints-panel] {\n max-height: 320px;\n overflow-y: auto;\n padding: 4px 0;\n }\n\n .joint-row {\n display: grid;\n grid-template-columns: 80px 1fr 60px;\n gap: 6px;\n align-items: center;\n margin: 3px 0;\n }\n\n .joint-row > .joint-name {\n font-size: 11px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .joint-row > .joint-name[data-fixed] {\n color: var(--md-sys-color-on-surface-variant, #888);\n font-style: italic;\n }\n\n .joint-row > input[type='range'] {\n width: 100%;\n accent-color: var(--md-sys-color-primary, #6750a4);\n }\n\n .joint-row > .joint-value {\n font: 11px monospace;\n color: var(--md-sys-color-on-surface-variant, #666);\n text-align: right;\n }\n\n .joint-empty,\n .joint-waiting {\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant, #888);\n padding: 8px 10px;\n font-style: italic;\n }\n\n .joint-type-badge {\n display: inline-block;\n font-size: 9px;\n padding: 1px 5px;\n margin-left: 4px;\n border-radius: 6px;\n background: var(--md-sys-color-secondary-container, #e8def8);\n color: var(--md-sys-color-on-secondary-container, #1d192b);\n text-transform: uppercase;\n }\n\n .reset-btn {\n font-size: 10px;\n padding: 3px 8px;\n margin-top: 4px;\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 color: var(--md-sys-color-on-surface, #1c1b1f);\n }\n .reset-btn:hover {\n background: var(--md-sys-color-surface-container-highest, #e6e0e9);\n }\n `\n ]\n\n private _component: any = null\n private _pollTimer?: number\n\n @state() declare meta: JointMeta[]\n\n constructor() {\n super()\n this.meta = []\n }\n\n editorTemplate(value: any, _spec: PropertySpec): TemplateResult {\n const joints = (value || {}) as Record<string, number | { value?: number }>\n\n return html`\n <fieldset fullwidth>\n <legend>Joints</legend>\n <div joints-panel>\n ${this.meta.length === 0\n ? html`<div class=\"joint-waiting\">Waiting for URDF to load…</div>`\n : this.meta.map(m => this._renderJointSlider(m, joints))}\n </div>\n ${this.meta.length > 0\n ? html`<button class=\"reset-btn\" @click=${this._onReset}>Reset all</button>`\n : null}\n </fieldset>\n `\n }\n\n private _renderJointSlider(m: JointMeta, joints: Record<string, number | { value?: number }>) {\n const raw = joints[m.name]\n const internal = typeof raw === 'number' ? raw : (raw?.value ?? 0)\n const isRad = m.type === 'revolute' || m.type === 'continuous'\n const scale = isRad ? RAD2DEG : 1\n const unit = isRad ? '°' : 'm'\n const minDisp = m.limit.lower * scale\n const maxDisp = m.limit.upper * scale\n const dispVal = internal * scale\n const step = isRad ? 1 : Math.max((m.limit.upper - m.limit.lower) / 100, 0.001)\n\n return html`\n <div class=\"joint-row\">\n <span class=\"joint-name\" title=\"${m.name} (${m.type})\">\n ${m.name}\n <span class=\"joint-type-badge\">${m.type.substring(0, 3)}</span>\n </span>\n <input\n type=\"range\"\n min=${minDisp}\n max=${maxDisp}\n step=${step}\n .value=${String(dispVal)}\n data-joint=${m.name}\n data-scale=${scale}\n @input=${this._onSlide}\n @change=${this._stopChange}\n />\n <span class=\"joint-value\">${dispVal.toFixed(isRad ? 1 : 3)}${unit}</span>\n </div>\n `\n }\n\n private _onSlide = (e: Event) => {\n e.stopPropagation()\n const input = e.target as HTMLInputElement\n const jointName = input.dataset.joint!\n const scale = parseFloat(input.dataset.scale || '1')\n const internal = parseFloat(input.value) / scale\n\n // 이전 값을 immutable하게 새 객체로 머지. state.joints를 mutate하면\n // setState의 deep-equality 체크에서 before=after로 판정되어 change 감지\n // 실패 → onchange가 fire되지 않음. Phase 1에서 확인된 규칙.\n const prev = (this.value || {}) as Record<string, any>\n const next = { ...prev, [jointName]: internal }\n this.value = next\n\n this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }))\n }\n\n /**\n * 슬라이더 드래그 종료 시 브라우저가 네이티브 'change' 이벤트를 발행한다.\n * 이 이벤트가 OxPropertyEditor 베이스의 _valueChanged(renderRoot 리스너)에\n * 도달하면 input.value(문자열)를 this.value에 assign해버려서 joints 객체가\n * \"45\" 같은 문자열로 덮어쓰여진다. shadow DOM 내부에서 바로 stopPropagation.\n */\n private _stopChange = (e: Event) => {\n e.stopPropagation()\n }\n\n private _onReset = (e: MouseEvent) => {\n e.stopPropagation()\n this.value = {}\n this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }))\n }\n\n connectedCallback() {\n super.connectedCallback()\n this._requestSelectedComponent()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n if (this._pollTimer) {\n clearInterval(this._pollTimer)\n this._pollTimer = undefined\n }\n }\n\n /**\n * 선택된 URDF 컴포넌트를 얻어 jointMeta를 읽는다. 로드가 아직이면 주기적 폴링.\n */\n private _requestSelectedComponent() {\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) return\n this._component = component\n this._refreshJointMeta()\n }\n }\n })\n )\n }\n\n private _refreshJointMeta() {\n const ro = this._component?.realObject\n const meta: JointMeta[] | undefined = ro?.jointMeta\n if (meta && meta.length > 0) {\n this.meta = meta\n if (this._pollTimer) {\n clearInterval(this._pollTimer)\n this._pollTimer = undefined\n }\n return\n }\n\n // 아직 로드 전이면 폴링 (최대 20회 = 10초)\n if (this._pollTimer) return\n let retries = 0\n this._pollTimer = window.setInterval(() => {\n const m = this._component?.realObject?.jointMeta\n if (m && m.length > 0) {\n this.meta = m\n clearInterval(this._pollTimer!)\n this._pollTimer = undefined\n } else if (++retries >= 20) {\n clearInterval(this._pollTimer!)\n this._pollTimer = undefined\n }\n }, 500)\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- export { default as UrdfViewer } from './urdf-viewer.js';
2
- export { default as UrdfController } from './urdf-controller.js';
1
+ import './urdf-object.js';
2
+ export { RealObjectURDF } from './real-object-urdf.js';
3
+ export type { URDFJointState, URDFJointMeta } from './real-object-urdf.js';
4
+ export { URDFObject } from './urdf-object.js';
package/dist/index.js CHANGED
@@ -1,3 +1,17 @@
1
- export { default as UrdfViewer } from './urdf-viewer.js';
2
- export { default as UrdfController } from './urdf-controller.js';
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * URDF 플러그인 — things-scene의 RealObjectExternalModel 베이스를 상속받아
5
+ * URDF(Unified Robot Description Format) 로봇을 네이티브 3D 씬 오브젝트로
6
+ * 렌더링한다. @sceneComponent('urdf')로 등록되어 things-factory 보드에서
7
+ * 일반 컴포넌트처럼 사용 가능.
8
+ *
9
+ * 기본 loader는 STL/DAE/GLB를 지원하며, GLB는 things-scene의 RealObjectGLTF
10
+ * 캐시와 공유한다. xacro 전처리는 현재 미지원(ros-industrial 로봇 등은 사전
11
+ * 변환된 URDF 필요).
12
+ */
13
+ // @sceneComponent decorator side-effect를 위해 import
14
+ import './urdf-object.js';
15
+ export { RealObjectURDF } from './real-object-urdf.js';
16
+ export { URDFObject } from './urdf-object.js';
3
17
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAA","sourcesContent":["export { default as UrdfViewer } from './urdf-viewer.js'\nexport { default as UrdfController } from './urdf-controller.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,mDAAmD;AACnD,OAAO,kBAAkB,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * URDF 플러그인 — things-scene의 RealObjectExternalModel 베이스를 상속받아\n * URDF(Unified Robot Description Format) 로봇을 네이티브 3D 씬 오브젝트로\n * 렌더링한다. @sceneComponent('urdf')로 등록되어 things-factory 보드에서\n * 일반 컴포넌트처럼 사용 가능.\n *\n * 기본 loader는 STL/DAE/GLB를 지원하며, GLB는 things-scene의 RealObjectGLTF\n * 캐시와 공유한다. xacro 전처리는 현재 미지원(ros-industrial 로봇 등은 사전\n * 변환된 URDF 필요).\n */\n\n// @sceneComponent decorator side-effect를 위해 import\nimport './urdf-object.js'\n\nexport { RealObjectURDF } from './real-object-urdf.js'\nexport type { URDFJointState, URDFJointMeta } from './real-object-urdf.js'\nexport { URDFObject } from './urdf-object.js'\n"]}
@@ -0,0 +1,75 @@
1
+ import * as THREE from 'three';
2
+ import type { URDFRobot } from 'urdf-loader';
3
+ import type { Properties } from '@hatiolab/things-scene';
4
+ import { RealObjectExternalModel } from '@hatiolab/things-scene';
5
+ export interface URDFJointState {
6
+ /** revolute/continuous: 라디안. prismatic: 거리(URDF 단위). */
7
+ value?: number;
8
+ }
9
+ export interface URDFJointMeta {
10
+ name: string;
11
+ type: 'fixed' | 'continuous' | 'revolute' | 'planar' | 'prismatic' | 'floating';
12
+ axis: {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ };
17
+ limit: {
18
+ lower: number;
19
+ upper: number;
20
+ effort: number;
21
+ velocity: number;
22
+ };
23
+ }
24
+ export declare class RealObjectURDF extends RealObjectExternalModel<URDFRobot> {
25
+ protected get _formatLabel(): string;
26
+ protected get _placeholderColor(): number;
27
+ private static _urdfCache;
28
+ private static _topViewCache;
29
+ /**
30
+ * URDF를 로드한다 (동일 URL 캐시).
31
+ * mesh 로더는 STL/DAE(내장) + GLB(확장)를 지원한다.
32
+ *
33
+ * URDFLoader.loadAsync는 URDF XML 파싱 직후 resolve하지만 mesh(STL/DAE/GLB)는
34
+ * 비동기로 뒤늦게 attach된다. robot.clone() 시점에 mesh가 아직 없으면 클론된
35
+ * 트리엔 visual이 비게 되고 원본에만 mesh가 붙는다. 전용 LoadingManager의
36
+ * onLoad를 사용해 URDF + 모든 mesh가 완료된 후 resolve한다.
37
+ */
38
+ static loadURDF(url: string): Promise<URDFRobot>;
39
+ /** 모든 캐시 비움 (보드 전환 시 호출) */
40
+ static flushCache(): void;
41
+ static getTopViewCache(source: string): HTMLCanvasElement | undefined;
42
+ static setTopViewCache(source: string, canvas: HTMLCanvasElement): void;
43
+ private _robot?;
44
+ private _jointIndex;
45
+ private _jointMeta;
46
+ private _jointOriginals;
47
+ get robot(): URDFRobot | undefined;
48
+ /** 로드된 조인트 메타 리스트 (property panel 등이 참조) */
49
+ get jointMeta(): URDFJointMeta[];
50
+ get jointNames(): string[];
51
+ protected _loadExternal(url: string): Promise<URDFRobot>;
52
+ protected _onLoaded(robot: URDFRobot): void;
53
+ private _resolveUnitScale;
54
+ /**
55
+ * 조인트 트리를 순회하며 인덱스와 메타를 구축한다.
56
+ * fixed 조인트는 조작 대상이 아니므로 메타에서 제외하되 인덱스에는 남긴다.
57
+ */
58
+ private _buildJointIndex;
59
+ /**
60
+ * state.joints 맵을 순회하며 각 조인트 값을 적용한다.
61
+ * 값은 숫자 또는 { value: number } 형식 모두 허용 (유연성).
62
+ *
63
+ * joint transform 변경 후 component.invalidate()로 재렌더를 요청한다.
64
+ * 표준 경로: invalidate → Layer.throttle_render → rAF __draw__ → trigger('redraw')
65
+ * → ThreeCapability.animate → renderThreeD.
66
+ */
67
+ private _applyJointStates;
68
+ /**
69
+ * 모든 조인트를 원본 값으로 복원한다.
70
+ */
71
+ private _resetAllJoints;
72
+ clear(): THREE.Object3D<THREE.Object3DEventMap>;
73
+ updateDimension(): void;
74
+ onchange(after: Properties, before: Properties): void;
75
+ }