@operato/scene-urdf 9.1.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.
- package/dist/editors/index.d.ts +6 -0
- package/dist/editors/index.js +7 -1
- package/dist/editors/index.js.map +1 -1
- package/dist/editors/property-editor-urdf-joints.d.ts +54 -0
- package/dist/editors/property-editor-urdf-joints.js +242 -0
- package/dist/editors/property-editor-urdf-joints.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/real-object-urdf.d.ts +75 -0
- package/dist/real-object-urdf.js +284 -0
- package/dist/real-object-urdf.js.map +1 -0
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +2 -3
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/{urdf-controller.d.ts → urdf.d.ts} +3 -0
- package/dist/templates/urdf.js +19 -0
- package/dist/templates/urdf.js.map +1 -0
- package/dist/urdf-object.d.ts +264 -0
- package/dist/urdf-object.js +163 -0
- package/dist/urdf-object.js.map +1 -0
- package/icons/urdf.png +0 -0
- package/package.json +19 -18
- package/dist/elements/drag-n-drop.d.ts +0 -2
- package/dist/elements/drag-n-drop.js +0 -126
- package/dist/elements/drag-n-drop.js.map +0 -1
- package/dist/elements/urdf-controller-element.d.ts +0 -12
- package/dist/elements/urdf-controller-element.js +0 -283
- package/dist/elements/urdf-controller-element.js.map +0 -1
- package/dist/elements/urdf-drag-controls.d.ts +0 -32
- package/dist/elements/urdf-drag-controls.js +0 -185
- package/dist/elements/urdf-drag-controls.js.map +0 -1
- package/dist/elements/urdf-manipulator-element.d.ts +0 -15
- package/dist/elements/urdf-manipulator-element.js +0 -110
- package/dist/elements/urdf-manipulator-element.js.map +0 -1
- package/dist/elements/urdf-viewer-element.d.ts +0 -53
- package/dist/elements/urdf-viewer-element.js +0 -402
- package/dist/elements/urdf-viewer-element.js.map +0 -1
- package/dist/templates/urdf-controller.js +0 -16
- package/dist/templates/urdf-controller.js.map +0 -1
- package/dist/templates/urdf-viewer.d.ts +0 -14
- package/dist/templates/urdf-viewer.js +0 -16
- package/dist/templates/urdf-viewer.js.map +0 -1
- package/dist/urdf-controller.d.ts +0 -15
- package/dist/urdf-controller.js +0 -69
- package/dist/urdf-controller.js.map +0 -1
- package/dist/urdf-viewer.d.ts +0 -16
- package/dist/urdf-viewer.js +0 -202
- package/dist/urdf-viewer.js.map +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* URDF (Unified Robot Description Format) 지원 RealObject.
|
|
5
|
+
* RealObjectGLTF와 동일한 캐시/레이스/placeholder 패턴을 따른다.
|
|
6
|
+
*
|
|
7
|
+
* 차이점: GLTF는 노드/애니메이션이 주된 바인딩 대상이지만, URDF는 "joint"가
|
|
8
|
+
* 1급 시민이다. state.joints에 이름→라디안(또는 prismatic의 경우 거리) 맵을
|
|
9
|
+
* 두어 외부 센서값 등을 연결할 수 있도록 한다.
|
|
10
|
+
*
|
|
11
|
+
* 좌표계: URDF는 Z-up, things-scene의 3D는 Y-up. 로드 후 루트 그룹에 1회만
|
|
12
|
+
* -PI/2 X축 회전을 적용하여 변환한다. 내부 조인트 축은 원본 그대로 유지.
|
|
13
|
+
*/
|
|
14
|
+
import * as THREE from 'three';
|
|
15
|
+
import URDFLoader from 'urdf-loader';
|
|
16
|
+
import { warn } from '@hatiolab/things-scene';
|
|
17
|
+
import { RealObjectExternalModel } from '@hatiolab/things-scene';
|
|
18
|
+
import { RealObjectGLTF } from '@hatiolab/things-scene';
|
|
19
|
+
const X_NEG_PI_HALF = -Math.PI / 2;
|
|
20
|
+
export class RealObjectURDF extends RealObjectExternalModel {
|
|
21
|
+
get _formatLabel() { return 'urdf'; }
|
|
22
|
+
get _placeholderColor() { return 0x1976d2; }
|
|
23
|
+
// URDF 파싱 결과 캐시 — 동일 URL 재파싱 방지. clone()으로 복제 사용.
|
|
24
|
+
static _urdfCache = new Map();
|
|
25
|
+
// 탑뷰 스냅샷 캐시 (2D 렌더 폴백)
|
|
26
|
+
static _topViewCache = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* URDF를 로드한다 (동일 URL 캐시).
|
|
29
|
+
* mesh 로더는 STL/DAE(내장) + GLB(확장)를 지원한다.
|
|
30
|
+
*
|
|
31
|
+
* URDFLoader.loadAsync는 URDF XML 파싱 직후 resolve하지만 mesh(STL/DAE/GLB)는
|
|
32
|
+
* 비동기로 뒤늦게 attach된다. robot.clone() 시점에 mesh가 아직 없으면 클론된
|
|
33
|
+
* 트리엔 visual이 비게 되고 원본에만 mesh가 붙는다. 전용 LoadingManager의
|
|
34
|
+
* onLoad를 사용해 URDF + 모든 mesh가 완료된 후 resolve한다.
|
|
35
|
+
*/
|
|
36
|
+
static loadURDF(url) {
|
|
37
|
+
let cached = RealObjectURDF._urdfCache.get(url);
|
|
38
|
+
if (!cached) {
|
|
39
|
+
cached = new Promise((resolve, reject) => {
|
|
40
|
+
const manager = new THREE.LoadingManager();
|
|
41
|
+
const loader = new URDFLoader(manager);
|
|
42
|
+
loader.loadMeshCb = ((path, mgr, done) => {
|
|
43
|
+
if (/\.(glb|gltf)$/i.test(path)) {
|
|
44
|
+
// GLB는 별도 시스템 — manager 추적에서 벗어나므로 명시적으로 track.
|
|
45
|
+
mgr.itemStart(path);
|
|
46
|
+
RealObjectGLTF.loadGLTF(path).then(gltf => { done(gltf.scene.clone()); mgr.itemEnd(path); }, err => { done(null, err); mgr.itemError(path); mgr.itemEnd(path); });
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// STL/DAE는 urdf-loader 내장 디폴트로 폴백 (manager 자동 추적)
|
|
50
|
+
;
|
|
51
|
+
loader.defaultMeshLoader(path, mgr, done);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
let robotResult;
|
|
55
|
+
manager.onLoad = () => {
|
|
56
|
+
if (robotResult)
|
|
57
|
+
resolve(robotResult);
|
|
58
|
+
else
|
|
59
|
+
reject(new Error('URDF load completed but no robot result'));
|
|
60
|
+
};
|
|
61
|
+
manager.onError = (failedUrl) => {
|
|
62
|
+
// 개별 에러는 로그로만 남김 — 전체 로드는 그래도 완료되도록
|
|
63
|
+
// (일부 mesh 실패 시 나머지라도 보이게)
|
|
64
|
+
warn('URDFLoader: resource load error', failedUrl);
|
|
65
|
+
};
|
|
66
|
+
loader.load(url, robot => { robotResult = robot; }, undefined, err => reject(err));
|
|
67
|
+
}).catch(err => {
|
|
68
|
+
RealObjectURDF._urdfCache.delete(url);
|
|
69
|
+
throw err;
|
|
70
|
+
});
|
|
71
|
+
RealObjectURDF._urdfCache.set(url, cached);
|
|
72
|
+
}
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
/** 모든 캐시 비움 (보드 전환 시 호출) */
|
|
76
|
+
static flushCache() {
|
|
77
|
+
RealObjectURDF._urdfCache.clear();
|
|
78
|
+
RealObjectURDF._topViewCache.clear();
|
|
79
|
+
}
|
|
80
|
+
static getTopViewCache(source) {
|
|
81
|
+
return RealObjectURDF._topViewCache.get(source);
|
|
82
|
+
}
|
|
83
|
+
static setTopViewCache(source, canvas) {
|
|
84
|
+
RealObjectURDF._topViewCache.set(source, canvas);
|
|
85
|
+
}
|
|
86
|
+
// --- 인스턴스 상태 ---
|
|
87
|
+
_robot;
|
|
88
|
+
// 조인트 인덱스 + 메타 (로드 후 외부에서 구독 가능)
|
|
89
|
+
_jointIndex = new Map();
|
|
90
|
+
_jointMeta = new Map();
|
|
91
|
+
// 원본 조인트 값 — reset을 위해 보존
|
|
92
|
+
_jointOriginals = new Map();
|
|
93
|
+
get robot() {
|
|
94
|
+
return this._robot;
|
|
95
|
+
}
|
|
96
|
+
/** 로드된 조인트 메타 리스트 (property panel 등이 참조) */
|
|
97
|
+
get jointMeta() {
|
|
98
|
+
return Array.from(this._jointMeta.values());
|
|
99
|
+
}
|
|
100
|
+
get jointNames() {
|
|
101
|
+
return Array.from(this._jointIndex.keys());
|
|
102
|
+
}
|
|
103
|
+
_loadExternal(url) {
|
|
104
|
+
return RealObjectURDF.loadURDF(url);
|
|
105
|
+
}
|
|
106
|
+
_onLoaded(robot) {
|
|
107
|
+
if (this.component.state.loadError) {
|
|
108
|
+
this.component.setState({ loadError: undefined });
|
|
109
|
+
}
|
|
110
|
+
// 캐시의 robot은 공유 참조이므로 clone하여 독립 인스턴스 구성.
|
|
111
|
+
// urdf-loader의 URDFRobot는 Object3D를 상속하므로 Object3D.clone()이 안전하게 동작.
|
|
112
|
+
const cloned = robot.clone();
|
|
113
|
+
// 섀도우 캐스팅
|
|
114
|
+
cloned.traverse(child => {
|
|
115
|
+
if (child.isMesh) {
|
|
116
|
+
child.castShadow = true;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// clear()는 object3d.userData를 리셋하므로 pending 후 재설정.
|
|
120
|
+
this.clear();
|
|
121
|
+
this.object3d.userData.context = this;
|
|
122
|
+
this._robot = cloned;
|
|
123
|
+
// 좌표계 변환: URDF(Z-up) → three(Y-up).
|
|
124
|
+
// 사용자가 state.upAxis = 'y' 로 지정하면 변환 생략.
|
|
125
|
+
this.pivot = new THREE.Object3D();
|
|
126
|
+
const upAxis = this.component.state.upAxis;
|
|
127
|
+
if (upAxis !== 'y') {
|
|
128
|
+
this.pivot.rotation.x = X_NEG_PI_HALF;
|
|
129
|
+
}
|
|
130
|
+
// URDF는 기본 meter 단위. state.unitScale로 에디터 단위로 환산.
|
|
131
|
+
const unitScale = this._resolveUnitScale();
|
|
132
|
+
if (unitScale !== 1) {
|
|
133
|
+
this.pivot.scale.setScalar(unitScale);
|
|
134
|
+
}
|
|
135
|
+
this.pivot.add(cloned);
|
|
136
|
+
// URDF의 base_link는 관행상 z=0이 바닥(floor origin). things-scene는 center
|
|
137
|
+
// origin이므로, components3D 그룹(= -geometricOffsetY만큼 shift된 바닥 기준
|
|
138
|
+
// subgroup)에 pivot을 넣어 로봇이 컴포넌트 바닥면에 서도록 한다.
|
|
139
|
+
this.components3D.add(this.pivot);
|
|
140
|
+
// 조인트 인덱스/메타 구축
|
|
141
|
+
this._buildJointIndex(cloned);
|
|
142
|
+
// bounding box 계산 후 updateDimension으로 크기 반영
|
|
143
|
+
const box = new THREE.Box3().setFromObject(this.pivot);
|
|
144
|
+
this._objectSize = box.getSize(new THREE.Vector3());
|
|
145
|
+
this.updateDimension();
|
|
146
|
+
// 초기 joint 상태 적용
|
|
147
|
+
const jointStates = this.component.state.joints;
|
|
148
|
+
if (jointStates) {
|
|
149
|
+
this._applyJointStates(jointStates);
|
|
150
|
+
}
|
|
151
|
+
// 탑뷰 스냅샷
|
|
152
|
+
const source = this.component.state.src;
|
|
153
|
+
if (source && !RealObjectURDF._topViewCache.has(source)) {
|
|
154
|
+
try {
|
|
155
|
+
const canvas = RealObjectGLTF.renderTopView(this.pivot);
|
|
156
|
+
RealObjectURDF._topViewCache.set(source, canvas);
|
|
157
|
+
this.component._topViewSnapshot = canvas;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// 스냅샷 실패는 치명적이지 않음
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (source) {
|
|
164
|
+
;
|
|
165
|
+
this.component._topViewSnapshot = RealObjectURDF._topViewCache.get(source);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
_resolveUnitScale() {
|
|
169
|
+
const s = this.component.state.unitScale;
|
|
170
|
+
if (typeof s === 'number' && s > 0)
|
|
171
|
+
return s;
|
|
172
|
+
// 기본: URDF meter → 에디터 mm 스케일 1000
|
|
173
|
+
return 1000;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 조인트 트리를 순회하며 인덱스와 메타를 구축한다.
|
|
177
|
+
* fixed 조인트는 조작 대상이 아니므로 메타에서 제외하되 인덱스에는 남긴다.
|
|
178
|
+
*/
|
|
179
|
+
_buildJointIndex(robot) {
|
|
180
|
+
this._jointIndex.clear();
|
|
181
|
+
this._jointMeta.clear();
|
|
182
|
+
this._jointOriginals.clear();
|
|
183
|
+
const joints = robot.joints || {};
|
|
184
|
+
for (const [name, joint] of Object.entries(joints)) {
|
|
185
|
+
this._jointIndex.set(name, joint);
|
|
186
|
+
this._jointOriginals.set(name, joint.angle || 0);
|
|
187
|
+
if (joint.jointType === 'fixed')
|
|
188
|
+
continue;
|
|
189
|
+
this._jointMeta.set(name, {
|
|
190
|
+
name,
|
|
191
|
+
type: joint.jointType,
|
|
192
|
+
axis: { x: joint.axis.x, y: joint.axis.y, z: joint.axis.z },
|
|
193
|
+
limit: { ...joint.limit }
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* state.joints 맵을 순회하며 각 조인트 값을 적용한다.
|
|
199
|
+
* 값은 숫자 또는 { value: number } 형식 모두 허용 (유연성).
|
|
200
|
+
*
|
|
201
|
+
* joint transform 변경 후 component.invalidate()로 재렌더를 요청한다.
|
|
202
|
+
* 표준 경로: invalidate → Layer.throttle_render → rAF __draw__ → trigger('redraw')
|
|
203
|
+
* → ThreeCapability.animate → renderThreeD.
|
|
204
|
+
*/
|
|
205
|
+
_applyJointStates(states) {
|
|
206
|
+
if (!this._robot)
|
|
207
|
+
return;
|
|
208
|
+
let changed = false;
|
|
209
|
+
for (const [name, raw] of Object.entries(states)) {
|
|
210
|
+
const joint = this._jointIndex.get(name);
|
|
211
|
+
if (!joint || joint.jointType === 'fixed')
|
|
212
|
+
continue;
|
|
213
|
+
const value = typeof raw === 'number' ? raw : raw?.value;
|
|
214
|
+
if (typeof value !== 'number' || !isFinite(value))
|
|
215
|
+
continue;
|
|
216
|
+
joint.setJointValue(value);
|
|
217
|
+
changed = true;
|
|
218
|
+
}
|
|
219
|
+
if (changed) {
|
|
220
|
+
this.component?.invalidate?.();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 모든 조인트를 원본 값으로 복원한다.
|
|
225
|
+
*/
|
|
226
|
+
_resetAllJoints() {
|
|
227
|
+
if (!this._robot)
|
|
228
|
+
return;
|
|
229
|
+
for (const [name, orig] of this._jointOriginals) {
|
|
230
|
+
const joint = this._jointIndex.get(name);
|
|
231
|
+
if (joint && joint.jointType !== 'fixed') {
|
|
232
|
+
joint.setJointValue(orig);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// --- 라이프사이클 ---
|
|
237
|
+
clear() {
|
|
238
|
+
this._jointIndex.clear();
|
|
239
|
+
this._jointMeta.clear();
|
|
240
|
+
this._jointOriginals.clear();
|
|
241
|
+
this._robot = undefined;
|
|
242
|
+
return super.clear();
|
|
243
|
+
}
|
|
244
|
+
updateDimension() {
|
|
245
|
+
if (!this.pivot)
|
|
246
|
+
return;
|
|
247
|
+
const { width = 1, height = 1, depth = 1 } = this.component.state;
|
|
248
|
+
const { x = 1, y = 1, z = 1 } = this._objectSize || {};
|
|
249
|
+
// URDF는 관절 기반 로봇의 비율을 유지해야 하므로 uniform scale을 적용한다.
|
|
250
|
+
// 세 축 각각의 비율 중 가장 작은 값(best-fit)을 사용해 로봇이 컴포넌트의
|
|
251
|
+
// bounding box 안에 왜곡 없이 맞춰지도록 한다.
|
|
252
|
+
const unit = this._resolveUnitScale();
|
|
253
|
+
const baseX = x || 1;
|
|
254
|
+
const baseY = y || 1;
|
|
255
|
+
const baseZ = z || 1;
|
|
256
|
+
const upAxis = this.component.state.upAxis;
|
|
257
|
+
const rotated = upAxis !== 'y';
|
|
258
|
+
const rX = width / baseX;
|
|
259
|
+
const rY = (rotated ? depth : height) / baseY;
|
|
260
|
+
const rZ = (rotated ? height : depth) / baseZ;
|
|
261
|
+
const ratio = Math.min(rX, rY, rZ);
|
|
262
|
+
this.pivot.scale.setScalar(ratio * unit);
|
|
263
|
+
this.component.invalidate();
|
|
264
|
+
}
|
|
265
|
+
onchange(after, before) {
|
|
266
|
+
super.onchange(after, before);
|
|
267
|
+
if ('src' in after) {
|
|
268
|
+
this.updateSource();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if ('joints' in after) {
|
|
272
|
+
this._resetAllJoints();
|
|
273
|
+
const jointStates = after.joints;
|
|
274
|
+
if (jointStates && Object.keys(jointStates).length > 0) {
|
|
275
|
+
this._applyJointStates(jointStates);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if ('upAxis' in after || 'unitScale' in after) {
|
|
279
|
+
// 좌표/스케일 변경은 pivot 재구성이 필요하므로 전체 재빌드
|
|
280
|
+
this.build();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=real-object-urdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"real-object-urdf.js","sourceRoot":"","sources":["../src/real-object-urdf.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * URDF (Unified Robot Description Format) 지원 RealObject.\n * RealObjectGLTF와 동일한 캐시/레이스/placeholder 패턴을 따른다.\n *\n * 차이점: GLTF는 노드/애니메이션이 주된 바인딩 대상이지만, URDF는 \"joint\"가\n * 1급 시민이다. state.joints에 이름→라디안(또는 prismatic의 경우 거리) 맵을\n * 두어 외부 센서값 등을 연결할 수 있도록 한다.\n *\n * 좌표계: URDF는 Z-up, things-scene의 3D는 Y-up. 로드 후 루트 그룹에 1회만\n * -PI/2 X축 회전을 적용하여 변환한다. 내부 조인트 축은 원본 그대로 유지.\n */\n\nimport * as THREE from 'three'\nimport URDFLoader from 'urdf-loader'\nimport type { URDFRobot, URDFJoint } from 'urdf-loader'\n\nimport { warn } from '@hatiolab/things-scene'\nimport type { Properties } from '@hatiolab/things-scene'\n\nimport { RealObjectExternalModel } from '@hatiolab/things-scene'\nimport { RealObjectGLTF } from '@hatiolab/things-scene'\n\nexport interface URDFJointState {\n /** revolute/continuous: 라디안. prismatic: 거리(URDF 단위). */\n value?: number\n}\n\nexport interface URDFJointMeta {\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 X_NEG_PI_HALF = -Math.PI / 2\n\nexport class RealObjectURDF extends RealObjectExternalModel<URDFRobot> {\n protected get _formatLabel() { return 'urdf' }\n protected get _placeholderColor() { return 0x1976d2 }\n // URDF 파싱 결과 캐시 — 동일 URL 재파싱 방지. clone()으로 복제 사용.\n private static _urdfCache = new Map<string, Promise<URDFRobot>>()\n\n // 탑뷰 스냅샷 캐시 (2D 렌더 폴백)\n private static _topViewCache = new Map<string, HTMLCanvasElement>()\n\n /**\n * URDF를 로드한다 (동일 URL 캐시).\n * mesh 로더는 STL/DAE(내장) + GLB(확장)를 지원한다.\n *\n * URDFLoader.loadAsync는 URDF XML 파싱 직후 resolve하지만 mesh(STL/DAE/GLB)는\n * 비동기로 뒤늦게 attach된다. robot.clone() 시점에 mesh가 아직 없으면 클론된\n * 트리엔 visual이 비게 되고 원본에만 mesh가 붙는다. 전용 LoadingManager의\n * onLoad를 사용해 URDF + 모든 mesh가 완료된 후 resolve한다.\n */\n static loadURDF(url: string): Promise<URDFRobot> {\n let cached = RealObjectURDF._urdfCache.get(url)\n if (!cached) {\n cached = new Promise<URDFRobot>((resolve, reject) => {\n const manager = new THREE.LoadingManager()\n const loader = new URDFLoader(manager)\n\n loader.loadMeshCb = ((path: string, mgr: THREE.LoadingManager, done: (mesh: any, err?: Error) => void) => {\n if (/\\.(glb|gltf)$/i.test(path)) {\n // GLB는 별도 시스템 — manager 추적에서 벗어나므로 명시적으로 track.\n mgr.itemStart(path)\n RealObjectGLTF.loadGLTF(path).then(\n gltf => { done(gltf.scene.clone()); mgr.itemEnd(path) },\n err => { done(null, err); mgr.itemError(path); mgr.itemEnd(path) }\n )\n } else {\n // STL/DAE는 urdf-loader 내장 디폴트로 폴백 (manager 자동 추적)\n ;(loader as any).defaultMeshLoader(path, mgr, done)\n }\n }) as any\n\n let robotResult: URDFRobot | undefined\n\n manager.onLoad = () => {\n if (robotResult) resolve(robotResult)\n else reject(new Error('URDF load completed but no robot result'))\n }\n manager.onError = (failedUrl: string) => {\n // 개별 에러는 로그로만 남김 — 전체 로드는 그래도 완료되도록\n // (일부 mesh 실패 시 나머지라도 보이게)\n warn('URDFLoader: resource load error', failedUrl)\n }\n\n loader.load(\n url,\n robot => { robotResult = robot },\n undefined,\n err => reject(err)\n )\n }).catch(err => {\n RealObjectURDF._urdfCache.delete(url)\n throw err\n })\n RealObjectURDF._urdfCache.set(url, cached)\n }\n return cached\n }\n\n /** 모든 캐시 비움 (보드 전환 시 호출) */\n static flushCache() {\n RealObjectURDF._urdfCache.clear()\n RealObjectURDF._topViewCache.clear()\n }\n\n static getTopViewCache(source: string): HTMLCanvasElement | undefined {\n return RealObjectURDF._topViewCache.get(source)\n }\n\n static setTopViewCache(source: string, canvas: HTMLCanvasElement) {\n RealObjectURDF._topViewCache.set(source, canvas)\n }\n\n // --- 인스턴스 상태 ---\n\n private _robot?: URDFRobot\n\n // 조인트 인덱스 + 메타 (로드 후 외부에서 구독 가능)\n private _jointIndex = new Map<string, URDFJoint>()\n private _jointMeta = new Map<string, URDFJointMeta>()\n\n // 원본 조인트 값 — reset을 위해 보존\n private _jointOriginals = new Map<string, number>()\n\n get robot(): URDFRobot | undefined {\n return this._robot\n }\n\n /** 로드된 조인트 메타 리스트 (property panel 등이 참조) */\n get jointMeta(): URDFJointMeta[] {\n return Array.from(this._jointMeta.values())\n }\n\n get jointNames(): string[] {\n return Array.from(this._jointIndex.keys())\n }\n\n protected _loadExternal(url: string): Promise<URDFRobot> {\n return RealObjectURDF.loadURDF(url)\n }\n\n protected _onLoaded(robot: URDFRobot) {\n if (this.component.state.loadError) {\n this.component.setState({ loadError: undefined })\n }\n\n // 캐시의 robot은 공유 참조이므로 clone하여 독립 인스턴스 구성.\n // urdf-loader의 URDFRobot는 Object3D를 상속하므로 Object3D.clone()이 안전하게 동작.\n const cloned = robot.clone() as URDFRobot\n\n // 섀도우 캐스팅\n cloned.traverse(child => {\n if ((child as THREE.Mesh).isMesh) {\n child.castShadow = true\n }\n })\n\n // clear()는 object3d.userData를 리셋하므로 pending 후 재설정.\n this.clear()\n this.object3d.userData.context = this\n\n this._robot = cloned\n\n // 좌표계 변환: URDF(Z-up) → three(Y-up).\n // 사용자가 state.upAxis = 'y' 로 지정하면 변환 생략.\n this.pivot = new THREE.Object3D()\n const upAxis = this.component.state.upAxis\n if (upAxis !== 'y') {\n this.pivot.rotation.x = X_NEG_PI_HALF\n }\n\n // URDF는 기본 meter 단위. state.unitScale로 에디터 단위로 환산.\n const unitScale = this._resolveUnitScale()\n if (unitScale !== 1) {\n this.pivot.scale.setScalar(unitScale)\n }\n\n this.pivot.add(cloned)\n // URDF의 base_link는 관행상 z=0이 바닥(floor origin). things-scene는 center\n // origin이므로, components3D 그룹(= -geometricOffsetY만큼 shift된 바닥 기준\n // subgroup)에 pivot을 넣어 로봇이 컴포넌트 바닥면에 서도록 한다.\n this.components3D.add(this.pivot)\n\n // 조인트 인덱스/메타 구축\n this._buildJointIndex(cloned)\n\n // bounding box 계산 후 updateDimension으로 크기 반영\n const box = new THREE.Box3().setFromObject(this.pivot)\n this._objectSize = box.getSize(new THREE.Vector3())\n\n this.updateDimension()\n\n // 초기 joint 상태 적용\n const jointStates = this.component.state.joints as Record<string, URDFJointState | number> | undefined\n if (jointStates) {\n this._applyJointStates(jointStates)\n }\n\n // 탑뷰 스냅샷\n const source = this.component.state.src as string\n if (source && !RealObjectURDF._topViewCache.has(source)) {\n try {\n const canvas = RealObjectGLTF.renderTopView(this.pivot)\n RealObjectURDF._topViewCache.set(source, canvas)\n ;(this.component as any)._topViewSnapshot = canvas\n } catch {\n // 스냅샷 실패는 치명적이지 않음\n }\n } else if (source) {\n ;(this.component as any)._topViewSnapshot = RealObjectURDF._topViewCache.get(source)\n }\n }\n\n private _resolveUnitScale(): number {\n const s = this.component.state.unitScale\n if (typeof s === 'number' && s > 0) return s\n // 기본: URDF meter → 에디터 mm 스케일 1000\n return 1000\n }\n\n /**\n * 조인트 트리를 순회하며 인덱스와 메타를 구축한다.\n * fixed 조인트는 조작 대상이 아니므로 메타에서 제외하되 인덱스에는 남긴다.\n */\n private _buildJointIndex(robot: URDFRobot) {\n this._jointIndex.clear()\n this._jointMeta.clear()\n this._jointOriginals.clear()\n\n const joints = robot.joints || {}\n for (const [name, joint] of Object.entries(joints)) {\n this._jointIndex.set(name, joint)\n this._jointOriginals.set(name, joint.angle || 0)\n\n if (joint.jointType === 'fixed') continue\n\n this._jointMeta.set(name, {\n name,\n type: joint.jointType,\n axis: { x: joint.axis.x, y: joint.axis.y, z: joint.axis.z },\n limit: { ...joint.limit }\n })\n }\n }\n\n /**\n * state.joints 맵을 순회하며 각 조인트 값을 적용한다.\n * 값은 숫자 또는 { value: number } 형식 모두 허용 (유연성).\n *\n * joint transform 변경 후 component.invalidate()로 재렌더를 요청한다.\n * 표준 경로: invalidate → Layer.throttle_render → rAF __draw__ → trigger('redraw')\n * → ThreeCapability.animate → renderThreeD.\n */\n private _applyJointStates(states: Record<string, URDFJointState | number>) {\n if (!this._robot) return\n\n let changed = false\n for (const [name, raw] of Object.entries(states)) {\n const joint = this._jointIndex.get(name)\n if (!joint || joint.jointType === 'fixed') continue\n\n const value = typeof raw === 'number' ? raw : raw?.value\n if (typeof value !== 'number' || !isFinite(value)) continue\n\n joint.setJointValue(value)\n changed = true\n }\n\n if (changed) {\n this.component?.invalidate?.()\n }\n }\n\n /**\n * 모든 조인트를 원본 값으로 복원한다.\n */\n private _resetAllJoints() {\n if (!this._robot) return\n for (const [name, orig] of this._jointOriginals) {\n const joint = this._jointIndex.get(name)\n if (joint && joint.jointType !== 'fixed') {\n joint.setJointValue(orig)\n }\n }\n }\n\n // --- 라이프사이클 ---\n\n clear() {\n this._jointIndex.clear()\n this._jointMeta.clear()\n this._jointOriginals.clear()\n this._robot = undefined\n\n return super.clear()\n }\n\n updateDimension() {\n if (!this.pivot) return\n\n const { width = 1, height = 1, depth = 1 } = this.component.state\n const { x = 1, y = 1, z = 1 } = this._objectSize || {}\n\n // URDF는 관절 기반 로봇의 비율을 유지해야 하므로 uniform scale을 적용한다.\n // 세 축 각각의 비율 중 가장 작은 값(best-fit)을 사용해 로봇이 컴포넌트의\n // bounding box 안에 왜곡 없이 맞춰지도록 한다.\n const unit = this._resolveUnitScale()\n const baseX = x || 1\n const baseY = y || 1\n const baseZ = z || 1\n const upAxis = this.component.state.upAxis\n const rotated = upAxis !== 'y'\n\n const rX = width / baseX\n const rY = (rotated ? depth : height) / baseY\n const rZ = (rotated ? height : depth) / baseZ\n const ratio = Math.min(rX, rY, rZ)\n\n this.pivot.scale.setScalar(ratio * unit)\n\n this.component.invalidate()\n }\n\n onchange(after: Properties, before: Properties) {\n super.onchange(after, before)\n\n if ('src' in after) {\n this.updateSource()\n return\n }\n\n if ('joints' in after) {\n this._resetAllJoints()\n const jointStates = after.joints as Record<string, URDFJointState | number> | undefined\n if (jointStates && Object.keys(jointStates).length > 0) {\n this._applyJointStates(jointStates)\n }\n }\n\n if ('upAxis' in after || 'unitScale' in after) {\n // 좌표/스케일 변경은 pivot 재구성이 필요하므로 전체 재빌드\n this.build()\n }\n }\n}\n"]}
|
package/dist/templates/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,eAAe,CAAC,IAAI,CAAC,CAAA","sourcesContent":["import urdf from './urdf.js'\n\nexport default [urdf]\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const icon = new URL('../../icons/urdf.png', import.meta.url).href;
|
|
2
|
+
export default {
|
|
3
|
+
type: 'urdf',
|
|
4
|
+
description: 'urdf',
|
|
5
|
+
group: '3D',
|
|
6
|
+
/* line|shape|textAndMedia|chartAndGauge|table|container|dataSource|IoT|3D|warehouse|form|etc */
|
|
7
|
+
icon,
|
|
8
|
+
model: {
|
|
9
|
+
type: 'urdf',
|
|
10
|
+
left: 10,
|
|
11
|
+
top: 10,
|
|
12
|
+
width: 200,
|
|
13
|
+
height: 200,
|
|
14
|
+
depth: 500,
|
|
15
|
+
upAxis: 'z',
|
|
16
|
+
unitScale: 1000
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=urdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urdf.js","sourceRoot":"","sources":["../../src/templates/urdf.ts"],"names":[],"mappings":"AAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AAElE,eAAe;IACb,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,MAAM;IACnB,KAAK,EAAE,IAAI;IACX,gGAAgG;IAChG,IAAI;IACJ,KAAK,EAAE;QACL,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,EAAE;QACR,GAAG,EAAE,EAAE;QACP,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,GAAG;QACX,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,GAAG;QACX,SAAS,EAAE,IAAI;KAChB;CACF,CAAA","sourcesContent":["const icon = new URL('../../icons/urdf.png', import.meta.url).href\n\nexport default {\n type: 'urdf',\n description: 'urdf',\n group: '3D',\n /* line|shape|textAndMedia|chartAndGauge|table|container|dataSource|IoT|3D|warehouse|form|etc */\n icon,\n model: {\n type: 'urdf',\n left: 10,\n top: 10,\n width: 200,\n height: 200,\n depth: 500,\n upAxis: 'z',\n unitScale: 1000\n }\n}\n"]}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Shape, type ComponentNature, type Control, type IRealObject } from '@hatiolab/things-scene';
|
|
2
|
+
import type { URDFJointState, URDFJointMeta } from './real-object-urdf.js';
|
|
3
|
+
declare const URDFObject_base: (new (...args: any[]) => {
|
|
4
|
+
contains(x: number, y: number): boolean;
|
|
5
|
+
get path(): {
|
|
6
|
+
x: any;
|
|
7
|
+
y: any;
|
|
8
|
+
}[];
|
|
9
|
+
set path(path: {
|
|
10
|
+
x: any;
|
|
11
|
+
y: any;
|
|
12
|
+
}[]): any;
|
|
13
|
+
get anchors(): {
|
|
14
|
+
name: string;
|
|
15
|
+
position: {
|
|
16
|
+
x: any;
|
|
17
|
+
y: any;
|
|
18
|
+
};
|
|
19
|
+
}[];
|
|
20
|
+
get bounds(): any;
|
|
21
|
+
set bounds(bounds: any): any;
|
|
22
|
+
render(ctx: CanvasRenderingContext2D): void;
|
|
23
|
+
_app: any;
|
|
24
|
+
_model: any;
|
|
25
|
+
_state: any;
|
|
26
|
+
_delta: any;
|
|
27
|
+
_animation: any;
|
|
28
|
+
_animate: any;
|
|
29
|
+
_parent: any;
|
|
30
|
+
_disposed: any;
|
|
31
|
+
_textHidden: any;
|
|
32
|
+
_text_substitutor: any;
|
|
33
|
+
_value_substitutor: any;
|
|
34
|
+
_mappings: any;
|
|
35
|
+
_realObject: import("@hatiolab/things-scene").IRealObject | undefined;
|
|
36
|
+
_cachedState: any;
|
|
37
|
+
updatedAt: any;
|
|
38
|
+
fontSize: any;
|
|
39
|
+
__cache__: any;
|
|
40
|
+
created(): void;
|
|
41
|
+
added(parent: any): void;
|
|
42
|
+
removed(parent: any): void;
|
|
43
|
+
ready(): Promise<void>;
|
|
44
|
+
touch(): void;
|
|
45
|
+
clearCache(...attrs: any[]): void;
|
|
46
|
+
removeSelf(completely: any): void;
|
|
47
|
+
resetAnimation(): void;
|
|
48
|
+
dispose(): void;
|
|
49
|
+
get nature(): import("@hatiolab/things-scene").ComponentNature;
|
|
50
|
+
get disposed(): boolean;
|
|
51
|
+
isLayer(): boolean;
|
|
52
|
+
isGroup(): boolean;
|
|
53
|
+
isContainer(): this is import("@hatiolab/things-scene/dist-types/types/component.js").Container;
|
|
54
|
+
isLine(): boolean;
|
|
55
|
+
isRoot(): boolean;
|
|
56
|
+
isRootModel(): boolean;
|
|
57
|
+
is3dish(): boolean;
|
|
58
|
+
get is3dMode(): boolean;
|
|
59
|
+
isIn3DSpace(): boolean;
|
|
60
|
+
isTemplate(): boolean;
|
|
61
|
+
isHTMLElement(): boolean;
|
|
62
|
+
isConnectable(): boolean;
|
|
63
|
+
isIdentifiable(): boolean;
|
|
64
|
+
isPositionable(): boolean;
|
|
65
|
+
replaceRefids(replaceMap: any): void;
|
|
66
|
+
get(property: any): any;
|
|
67
|
+
set(props: any, propval?: any): any;
|
|
68
|
+
getState(property: any): any;
|
|
69
|
+
setState(props: any, propval?: any): any;
|
|
70
|
+
get model(): any;
|
|
71
|
+
get state(): any;
|
|
72
|
+
get hierarchy(): any;
|
|
73
|
+
get volatile(): never[];
|
|
74
|
+
_applyProps(target: any, props: any, options: any): any;
|
|
75
|
+
move(offset: {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
}, ...args: boolean[]): void;
|
|
79
|
+
symmetryX(x?: number): void;
|
|
80
|
+
symmetryY(y: number): void;
|
|
81
|
+
adjustResize(bounds: import("@hatiolab/things-scene").BOUNDS, origin_bounds: import("@hatiolab/things-scene").BOUNDS, diagonal: boolean): {
|
|
82
|
+
left: any;
|
|
83
|
+
top: any;
|
|
84
|
+
width: any;
|
|
85
|
+
height: any;
|
|
86
|
+
};
|
|
87
|
+
adjustRotation(rotation: number, step: boolean): number;
|
|
88
|
+
outline(progress: number): any;
|
|
89
|
+
get center(): import("@hatiolab/things-scene").POINT;
|
|
90
|
+
set center(p: import("@hatiolab/things-scene").POINT): any;
|
|
91
|
+
get location(): import("@hatiolab/things-scene").POINT;
|
|
92
|
+
set location(l: import("@hatiolab/things-scene").POINT): any;
|
|
93
|
+
get rotate(): import("@hatiolab/things-scene").POINT;
|
|
94
|
+
set rotate(r: import("@hatiolab/things-scene").POINT): any;
|
|
95
|
+
get dimension(): import("@hatiolab/things-scene").DIMENSION;
|
|
96
|
+
set dimension(d: import("@hatiolab/things-scene").DIMENSION): any;
|
|
97
|
+
get drawPath(): import("@hatiolab/things-scene").POINT[];
|
|
98
|
+
get rotatePoint(): import("@hatiolab/things-scene").POINT;
|
|
99
|
+
get mutable(): boolean;
|
|
100
|
+
get resizable(): boolean;
|
|
101
|
+
get rotatable(): boolean;
|
|
102
|
+
buildRealObject(): import("@hatiolab/things-scene").IRealObject | undefined;
|
|
103
|
+
get realObject(): import("@hatiolab/things-scene").IRealObject | undefined;
|
|
104
|
+
draw(context?: import("@hatiolab/things-scene").SceneRenderContext): void;
|
|
105
|
+
prerender(context: import("@hatiolab/things-scene").SceneRenderContext): void;
|
|
106
|
+
postrender(context: import("@hatiolab/things-scene").SceneRenderContext): void;
|
|
107
|
+
prepare(resolve: (component: import("@hatiolab/things-scene").Component) => void, reject: (reason: any) => void): void;
|
|
108
|
+
prepareIf(condition: boolean): void;
|
|
109
|
+
drawText(context: import("@hatiolab/things-scene").SceneRenderContext): void;
|
|
110
|
+
drawStroke(context: import("@hatiolab/things-scene").SceneRenderContext, override?: Record<string, unknown>): void;
|
|
111
|
+
drawFill(context: import("@hatiolab/things-scene").SceneRenderContext, override?: Record<string, unknown>): void;
|
|
112
|
+
get strokeStyle(): any;
|
|
113
|
+
set strokeStyle(v: any): any;
|
|
114
|
+
get fillStyle(): any;
|
|
115
|
+
set fillStyle(v: any): any;
|
|
116
|
+
get fontColor(): string;
|
|
117
|
+
set fontColor(v: string): any;
|
|
118
|
+
get rotation(): number;
|
|
119
|
+
set rotation(v: number): any;
|
|
120
|
+
get decorators(): string[];
|
|
121
|
+
get decotag(): string;
|
|
122
|
+
get hidden(): boolean;
|
|
123
|
+
set hidden(v: boolean): any;
|
|
124
|
+
get tag(): string;
|
|
125
|
+
set tag(v: string): any;
|
|
126
|
+
get appendum(): any;
|
|
127
|
+
set appendum(v: any): any;
|
|
128
|
+
defaultTextSubstitutor(): string;
|
|
129
|
+
textLines(context?: import("@hatiolab/things-scene").SceneRenderContext): any[][];
|
|
130
|
+
get font(): string;
|
|
131
|
+
get lineHeight(): number;
|
|
132
|
+
get textSubstitutor(): () => string;
|
|
133
|
+
get text(): string;
|
|
134
|
+
set text(v: string): any;
|
|
135
|
+
get textBounds(): import("@hatiolab/things-scene").BOUNDS;
|
|
136
|
+
get textRotation(): number;
|
|
137
|
+
get textHidden(): boolean;
|
|
138
|
+
set textHidden(v: boolean): any;
|
|
139
|
+
get hasTextProperty(): boolean;
|
|
140
|
+
animate(opts: import("@hatiolab/things-scene").AnimationConfig): any;
|
|
141
|
+
effect(context: import("@hatiolab/things-scene").SceneRenderContext, model: any): void;
|
|
142
|
+
serialize(...others: any[]): string;
|
|
143
|
+
trim(): void;
|
|
144
|
+
closeScene(data: any): void;
|
|
145
|
+
delta(attr?: string | object, value?: any): any;
|
|
146
|
+
invalidate(): void;
|
|
147
|
+
get value(): any;
|
|
148
|
+
set value(v: any): any;
|
|
149
|
+
get data(): any;
|
|
150
|
+
set data(v: any): any;
|
|
151
|
+
set tap(v: any): any;
|
|
152
|
+
get mappings(): any[];
|
|
153
|
+
get retention(): number;
|
|
154
|
+
get animation(): import("@hatiolab/things-scene").AnimationController | undefined;
|
|
155
|
+
get started(): boolean;
|
|
156
|
+
set started(v: boolean): any;
|
|
157
|
+
get controls(): import("@hatiolab/things-scene").Control[] | undefined;
|
|
158
|
+
findFirst(finder: string | ((c: import("@hatiolab/things-scene").Component) => boolean), ...others: any[]): import("@hatiolab/things-scene").Component | undefined;
|
|
159
|
+
findAll(s: string | ((c: import("@hatiolab/things-scene").Component) => boolean), ...others: any[]): any[] | undefined;
|
|
160
|
+
capture(x: number, y: number, except?: (c: import("@hatiolab/things-scene").Component) => boolean): any;
|
|
161
|
+
findAnchor(name: string): any;
|
|
162
|
+
isDescendible(container: import("@hatiolab/things-scene").Component): boolean;
|
|
163
|
+
getContext(component?: unknown): any;
|
|
164
|
+
get root(): import("@hatiolab/things-scene").Component;
|
|
165
|
+
get rootModel(): import("@hatiolab/things-scene").Component;
|
|
166
|
+
get parent(): import("@hatiolab/things-scene").Component;
|
|
167
|
+
set parent(v: import("@hatiolab/things-scene").Component): any;
|
|
168
|
+
get scalable(): boolean;
|
|
169
|
+
get stuck(): boolean;
|
|
170
|
+
get capturable(): boolean;
|
|
171
|
+
get position(): string;
|
|
172
|
+
get origin(): string;
|
|
173
|
+
get offset(): import("@hatiolab/things-scene").POINT;
|
|
174
|
+
get app(): import("@hatiolab/things-scene").ApplicationContext;
|
|
175
|
+
drawEffect(context: import("@hatiolab/things-scene").SceneRenderContext): void;
|
|
176
|
+
prepareFill(resolve: Function, reject: Function): void;
|
|
177
|
+
prepareFillIf(condition: boolean): void;
|
|
178
|
+
onchangeFill(after: Record<string, any>, before: Record<string, any>): void;
|
|
179
|
+
drawImage(context: import("@hatiolab/things-scene").SceneRenderContext, image: HTMLImageElement, left: number, top: number, width: number, height: number): void;
|
|
180
|
+
mutateBounds(logic: ((bounds: import("@hatiolab/things-scene").BOUNDS) => import("@hatiolab/things-scene").BOUNDS | void) | null, context?: any): void;
|
|
181
|
+
mutatePath(beforeLogic: ((path: import("@hatiolab/things-scene").POINT[]) => import("@hatiolab/things-scene").POINT[] | void) | null, afterLogic: ((path: import("@hatiolab/things-scene").POINT[]) => import("@hatiolab/things-scene").POINT[] | void) | null, context?: any): void;
|
|
182
|
+
access(accessor: string): any;
|
|
183
|
+
substitute(template: string, data: any): string | undefined;
|
|
184
|
+
onchangeMappings(after: Record<string, any>, before: Record<string, any>): void;
|
|
185
|
+
onchangeData(after: Record<string, any>, before: Record<string, any>): void;
|
|
186
|
+
buildMappings(): void;
|
|
187
|
+
executeMappings(force?: boolean): void;
|
|
188
|
+
disposeMappings(): void;
|
|
189
|
+
ondropfile(transfered: FileList, files: string[]): void;
|
|
190
|
+
transcoordS2P(x: number, y: number, rp?: import("@hatiolab/things-scene").POINT): import("@hatiolab/things-scene").POINT;
|
|
191
|
+
transcoordP2S(x: number, y: number, rp?: import("@hatiolab/things-scene").POINT): import("@hatiolab/things-scene").POINT;
|
|
192
|
+
transcoordS2T(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
193
|
+
transcoordT2P(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
194
|
+
transcoordT2S(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
195
|
+
transcoordS2TR(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
196
|
+
transcoordS2O(x: number, y: number, target: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
197
|
+
transcoordC2S(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
198
|
+
transcoordS2C(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
199
|
+
toParent(x: number, y: number, rp?: import("@hatiolab/things-scene").POINT): import("@hatiolab/things-scene").POINT;
|
|
200
|
+
fromParent(x: number, y: number, rp?: import("@hatiolab/things-scene").POINT): import("@hatiolab/things-scene").POINT;
|
|
201
|
+
toScene(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
202
|
+
fromScene(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
203
|
+
toLocal(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
204
|
+
toGlobal(x: number, y: number, top?: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
205
|
+
toOther(x: number, y: number, target: import("@hatiolab/things-scene").Component): import("@hatiolab/things-scene").POINT;
|
|
206
|
+
on(name: string | object, callback: Function, context?: any): any;
|
|
207
|
+
off(name?: string | object, callback?: Function, context?: any): any;
|
|
208
|
+
once(name: string | object, callback: Function, context?: any): any;
|
|
209
|
+
trigger(name: string, ...args: any[]): any;
|
|
210
|
+
delegate_on(delegator: any): any;
|
|
211
|
+
delegate_off(delegator: any): any;
|
|
212
|
+
onchange(after: Record<string, any>, before: Record<string, any>): void;
|
|
213
|
+
calculateBounds?(): void;
|
|
214
|
+
oncreate_element?(element: HTMLElement): void;
|
|
215
|
+
removeComponent(component: import("@hatiolab/things-scene").Component, ghost?: boolean): void;
|
|
216
|
+
addComponent(component: import("@hatiolab/things-scene").Component, ghost?: boolean): void;
|
|
217
|
+
insertComponentAt(component: import("@hatiolab/things-scene").Component, index: number, ghost?: boolean): void;
|
|
218
|
+
getOverlay(component: import("@hatiolab/things-scene").Component): HTMLElement | undefined;
|
|
219
|
+
findById(id: string): import("@hatiolab/things-scene").Component | undefined;
|
|
220
|
+
findByRefid(ref: string | number): import("@hatiolab/things-scene").Component | undefined;
|
|
221
|
+
findAllById(id: string): import("@hatiolab/things-scene").Component[];
|
|
222
|
+
resize(): void;
|
|
223
|
+
fit(type?: string): void;
|
|
224
|
+
get components(): import("@hatiolab/things-scene").Component[] | undefined;
|
|
225
|
+
get layout(): any;
|
|
226
|
+
get auxOverlay(): HTMLElement | undefined;
|
|
227
|
+
get isReady(): boolean;
|
|
228
|
+
get unitScale(): number;
|
|
229
|
+
get selected(): import("@hatiolab/things-scene").Component[];
|
|
230
|
+
set selected(_v: import("@hatiolab/things-scene").Component[]): any;
|
|
231
|
+
get focused(): import("@hatiolab/things-scene").Component | null;
|
|
232
|
+
set focused(_v: import("@hatiolab/things-scene").Component | null): any;
|
|
233
|
+
get hasSameParentForAllSelected(): boolean;
|
|
234
|
+
set hasSameParentForAllSelected(_v: boolean): any;
|
|
235
|
+
get fitMode(): string | undefined;
|
|
236
|
+
set fitMode(_v: string | undefined): any;
|
|
237
|
+
get element(): HTMLElement | null;
|
|
238
|
+
set element(_v: HTMLElement | null): any;
|
|
239
|
+
}) & typeof Shape;
|
|
240
|
+
export declare class URDFObject extends URDFObject_base {
|
|
241
|
+
get hasTextProperty(): boolean;
|
|
242
|
+
is3dish(): boolean;
|
|
243
|
+
get controls(): Array<Control> | undefined;
|
|
244
|
+
render(context: CanvasRenderingContext2D): void;
|
|
245
|
+
ready(): Promise<void>;
|
|
246
|
+
onchange(after: Record<string, any>, before: Record<string, any>): void;
|
|
247
|
+
private _ensureTopViewSnapshot;
|
|
248
|
+
buildRealObject(): IRealObject | undefined;
|
|
249
|
+
get nature(): ComponentNature;
|
|
250
|
+
get source(): any;
|
|
251
|
+
set source(source: any);
|
|
252
|
+
set dimension(dimension: {
|
|
253
|
+
width: number;
|
|
254
|
+
height: number;
|
|
255
|
+
depth: number;
|
|
256
|
+
});
|
|
257
|
+
/** 조인트 상태 맵. 값은 숫자(라디안/거리) 또는 { value } 형식 허용. */
|
|
258
|
+
get joints(): Record<string, URDFJointState | number> | undefined;
|
|
259
|
+
set joints(value: Record<string, URDFJointState | number> | undefined);
|
|
260
|
+
/** 로드된 URDF의 조인트 메타 정보. property editor가 슬라이더 UI 생성에 사용. */
|
|
261
|
+
get jointMeta(): URDFJointMeta[];
|
|
262
|
+
get jointNames(): string[];
|
|
263
|
+
}
|
|
264
|
+
export {};
|