@operato/scene-urdf 10.0.0-beta.2 → 10.0.0-beta.22
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/README.md +22 -25
- package/TODO.md +58 -0
- package/dist/editors/index.d.ts +7 -0
- package/dist/editors/index.js +12 -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 +246 -0
- package/dist/editors/property-editor-urdf-joints.js.map +1 -0
- package/dist/editors/property-editor-urdf-preset.d.ts +8 -0
- package/dist/editors/property-editor-urdf-preset.js +114 -0
- package/dist/editors/property-editor-urdf-preset.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/joint-controller.d.ts +35 -0
- package/dist/joint-controller.js +34 -0
- package/dist/joint-controller.js.map +1 -0
- package/dist/real-object-urdf.d.ts +106 -0
- package/dist/real-object-urdf.js +527 -0
- package/dist/real-object-urdf.js.map +1 -0
- package/dist/smoothing-controller.d.ts +15 -0
- package/dist/smoothing-controller.js +88 -0
- package/dist/smoothing-controller.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 +190 -0
- package/dist/urdf-object.js.map +1 -0
- package/dist/urdf-presets.d.ts +22 -0
- package/dist/urdf-presets.js +176 -0
- package/dist/urdf-presets.js.map +1 -0
- package/icons/urdf.png +0 -0
- package/package.json +5 -4
- package/translations/en.json +10 -16
- package/translations/ja.json +10 -16
- package/translations/ko.json +10 -16
- package/translations/ms.json +10 -16
- package/translations/zh.json +10 -16
- 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 -197
- 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 -112
- 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 -414
- 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 -70
- 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,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Level 1 관절 스무딩 — 스크립트된 애니메이션이 아닌 "타겟 추적 감쇠".
|
|
5
|
+
*
|
|
6
|
+
* 구현: critically damped 2차 spring-damper ODE.
|
|
7
|
+
* acc = error × ω² - 2 × ω × vel
|
|
8
|
+
* vel += acc × dt
|
|
9
|
+
* pos += vel × dt
|
|
10
|
+
* 여기서 ω는 응답 주파수(rad/s). 기본 ω=10이면 시상수 ~0.1s 수준으로 빠르게
|
|
11
|
+
* 수렴하면서도 overshoot 없음 (ζ=1).
|
|
12
|
+
*
|
|
13
|
+
* 사용자가 state.joints를 바꾸면 setTargets로 새 목표가 전달되고, tick에서
|
|
14
|
+
* 매 프레임 current가 target으로 수렴한다. 바뀐 값은 joint.setJointValue로
|
|
15
|
+
* 직접 적용.
|
|
16
|
+
*/
|
|
17
|
+
import { registerJointController } from './joint-controller.js';
|
|
18
|
+
export class SmoothingController {
|
|
19
|
+
_joints;
|
|
20
|
+
_current = new Map();
|
|
21
|
+
_target = new Map();
|
|
22
|
+
_velocity = new Map();
|
|
23
|
+
/** 응답 주파수 (rad/s). 클수록 빠른 수렴. */
|
|
24
|
+
omega = 10;
|
|
25
|
+
/** 수렴 판정 임계값. error와 velocity가 모두 이 값 미만이면 그 조인트는 skip. */
|
|
26
|
+
epsilon = 1e-5;
|
|
27
|
+
setup(ctx) {
|
|
28
|
+
this._joints = ctx.joints;
|
|
29
|
+
this._current.clear();
|
|
30
|
+
this._target.clear();
|
|
31
|
+
this._velocity.clear();
|
|
32
|
+
for (const [name, joint] of ctx.joints) {
|
|
33
|
+
const v = joint.angle ?? 0;
|
|
34
|
+
this._current.set(name, v);
|
|
35
|
+
this._target.set(name, v);
|
|
36
|
+
this._velocity.set(name, 0);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
setTargets(targets) {
|
|
40
|
+
for (const [name, raw] of Object.entries(targets)) {
|
|
41
|
+
const v = typeof raw === 'number' ? raw : Number(raw?.value);
|
|
42
|
+
if (Number.isFinite(v)) {
|
|
43
|
+
this._target.set(name, v);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
tick(dt) {
|
|
48
|
+
if (!this._joints)
|
|
49
|
+
return false;
|
|
50
|
+
// dt 보정: 0 이하거나 비정상적으로 크면 (탭 전환 후 복귀 등) 스킵/클램프
|
|
51
|
+
if (dt <= 0)
|
|
52
|
+
return false;
|
|
53
|
+
if (dt > 0.1)
|
|
54
|
+
dt = 0.016;
|
|
55
|
+
const w = this.omega;
|
|
56
|
+
const w2 = w * w;
|
|
57
|
+
const twoW = 2 * w;
|
|
58
|
+
const eps = this.epsilon;
|
|
59
|
+
let changed = false;
|
|
60
|
+
for (const [name, joint] of this._joints) {
|
|
61
|
+
if (joint.jointType === 'fixed')
|
|
62
|
+
continue;
|
|
63
|
+
const target = this._target.get(name) ?? 0;
|
|
64
|
+
const cur = this._current.get(name) ?? target;
|
|
65
|
+
const vel = this._velocity.get(name) ?? 0;
|
|
66
|
+
const error = target - cur;
|
|
67
|
+
if (Math.abs(error) < eps && Math.abs(vel) < eps)
|
|
68
|
+
continue;
|
|
69
|
+
const acc = error * w2 - twoW * vel;
|
|
70
|
+
const newVel = vel + acc * dt;
|
|
71
|
+
const newCur = cur + newVel * dt;
|
|
72
|
+
this._current.set(name, newCur);
|
|
73
|
+
this._velocity.set(name, newVel);
|
|
74
|
+
joint.setJointValue(newCur);
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
return changed;
|
|
78
|
+
}
|
|
79
|
+
dispose() {
|
|
80
|
+
this._joints = undefined;
|
|
81
|
+
this._current.clear();
|
|
82
|
+
this._target.clear();
|
|
83
|
+
this._velocity.clear();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// 기본 등록. 'smooth' 이름으로 state.physics에서 선택 가능.
|
|
87
|
+
registerJointController('smooth', () => new SmoothingController());
|
|
88
|
+
//# sourceMappingURL=smoothing-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoothing-controller.js","sourceRoot":"","sources":["../src/smoothing-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Level 1 관절 스무딩 — 스크립트된 애니메이션이 아닌 \"타겟 추적 감쇠\".\n *\n * 구현: critically damped 2차 spring-damper ODE.\n * acc = error × ω² - 2 × ω × vel\n * vel += acc × dt\n * pos += vel × dt\n * 여기서 ω는 응답 주파수(rad/s). 기본 ω=10이면 시상수 ~0.1s 수준으로 빠르게\n * 수렴하면서도 overshoot 없음 (ζ=1).\n *\n * 사용자가 state.joints를 바꾸면 setTargets로 새 목표가 전달되고, tick에서\n * 매 프레임 current가 target으로 수렴한다. 바뀐 값은 joint.setJointValue로\n * 직접 적용.\n */\n\nimport type { URDFJoint } from 'urdf-loader'\nimport type { JointController, JointControllerContext } from './joint-controller.js'\nimport { registerJointController } from './joint-controller.js'\n\nexport class SmoothingController implements JointController {\n private _joints?: Map<string, URDFJoint>\n private _current = new Map<string, number>()\n private _target = new Map<string, number>()\n private _velocity = new Map<string, number>()\n\n /** 응답 주파수 (rad/s). 클수록 빠른 수렴. */\n omega: number = 10\n\n /** 수렴 판정 임계값. error와 velocity가 모두 이 값 미만이면 그 조인트는 skip. */\n epsilon: number = 1e-5\n\n setup(ctx: JointControllerContext): void {\n this._joints = ctx.joints\n this._current.clear()\n this._target.clear()\n this._velocity.clear()\n\n for (const [name, joint] of ctx.joints) {\n const v = joint.angle ?? 0\n this._current.set(name, v)\n this._target.set(name, v)\n this._velocity.set(name, 0)\n }\n }\n\n setTargets(targets: Record<string, number>): void {\n for (const [name, raw] of Object.entries(targets)) {\n const v = typeof raw === 'number' ? raw : Number((raw as { value?: number } | null)?.value)\n if (Number.isFinite(v)) {\n this._target.set(name, v)\n }\n }\n }\n\n tick(dt: number): boolean {\n if (!this._joints) return false\n // dt 보정: 0 이하거나 비정상적으로 크면 (탭 전환 후 복귀 등) 스킵/클램프\n if (dt <= 0) return false\n if (dt > 0.1) dt = 0.016\n\n const w = this.omega\n const w2 = w * w\n const twoW = 2 * w\n const eps = this.epsilon\n let changed = false\n\n for (const [name, joint] of this._joints) {\n if (joint.jointType === 'fixed') continue\n\n const target = this._target.get(name) ?? 0\n const cur = this._current.get(name) ?? target\n const vel = this._velocity.get(name) ?? 0\n const error = target - cur\n\n if (Math.abs(error) < eps && Math.abs(vel) < eps) continue\n\n const acc = error * w2 - twoW * vel\n const newVel = vel + acc * dt\n const newCur = cur + newVel * dt\n\n this._current.set(name, newCur)\n this._velocity.set(name, newVel)\n joint.setJointValue(newCur)\n changed = true\n }\n\n return changed\n }\n\n dispose(): void {\n this._joints = undefined\n this._current.clear()\n this._target.clear()\n this._velocity.clear()\n }\n}\n\n// 기본 등록. 'smooth' 이름으로 state.physics에서 선택 가능.\nregisterJointController('smooth', () => new SmoothingController())\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|3D|facility|storage|conveyance|transport|form|etc */
|
|
7
|
+
icon,
|
|
8
|
+
model: {
|
|
9
|
+
type: 'urdf',
|
|
10
|
+
left: 10,
|
|
11
|
+
top: 10,
|
|
12
|
+
width: 200,
|
|
13
|
+
height: 200,
|
|
14
|
+
depth: 200,
|
|
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,wHAAwH;IACxH,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|3D|facility|storage|conveyance|transport|form|etc */\n icon,\n model: {\n type: 'urdf',\n left: 10,\n top: 10,\n width: 200,\n height: 200,\n depth: 200,\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 {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* URDF 로봇을 things-scene 3D 공간에 네이티브로 임베드한다.
|
|
5
|
+
* GLTFObject와 대칭적인 구조 — 2D 탑뷰는 스냅샷 렌더로 표현한다.
|
|
6
|
+
*/
|
|
7
|
+
import { __decorate } from "tslib";
|
|
8
|
+
import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
|
|
9
|
+
import { RealObjectURDF } from './real-object-urdf.js';
|
|
10
|
+
const BASE_URL_ALIAS = '$base_url';
|
|
11
|
+
const NATURE = {
|
|
12
|
+
mutable: false,
|
|
13
|
+
resizable: true,
|
|
14
|
+
rotatable: true,
|
|
15
|
+
properties: [
|
|
16
|
+
{
|
|
17
|
+
type: 'urdf-preset',
|
|
18
|
+
label: 'preset',
|
|
19
|
+
name: 'preset'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: 'string',
|
|
23
|
+
label: 'url',
|
|
24
|
+
name: 'src',
|
|
25
|
+
property: {
|
|
26
|
+
displayField: 'id',
|
|
27
|
+
displayFullUrl: true,
|
|
28
|
+
baseUrlAlias: BASE_URL_ALIAS,
|
|
29
|
+
defaultStorage: '3d-model',
|
|
30
|
+
storageFilters: {
|
|
31
|
+
type: Array,
|
|
32
|
+
value: [
|
|
33
|
+
{
|
|
34
|
+
name: 'category',
|
|
35
|
+
value: 'urdf'
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
useUpload: true,
|
|
40
|
+
category: 'application'
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'select',
|
|
45
|
+
label: 'up-axis',
|
|
46
|
+
name: 'upAxis',
|
|
47
|
+
property: {
|
|
48
|
+
options: [
|
|
49
|
+
{ display: 'Z (URDF default)', value: 'z' },
|
|
50
|
+
{ display: 'Y (Three.js default)', value: 'y' }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'number',
|
|
56
|
+
label: 'unit-scale',
|
|
57
|
+
name: 'unitScale',
|
|
58
|
+
placeholder: '1000'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'select',
|
|
62
|
+
label: 'auto-animate',
|
|
63
|
+
name: 'autoAnimate',
|
|
64
|
+
property: {
|
|
65
|
+
options: [
|
|
66
|
+
{ display: 'Off', value: 'none' },
|
|
67
|
+
{ display: 'Sine swing', value: 'sine' }
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'select',
|
|
73
|
+
label: 'physics',
|
|
74
|
+
name: 'physics',
|
|
75
|
+
property: {
|
|
76
|
+
options: [
|
|
77
|
+
{ display: 'Direct (immediate)', value: 'none' },
|
|
78
|
+
{ display: 'Smooth (damped)', value: 'smooth' }
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'urdf-joints',
|
|
84
|
+
label: '',
|
|
85
|
+
name: 'joints',
|
|
86
|
+
editor: {
|
|
87
|
+
fullwidth: true
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
'value-property': 'source',
|
|
92
|
+
help: 'scene/component/urdf'
|
|
93
|
+
};
|
|
94
|
+
let URDFObject = class URDFObject extends RectPath(Shape) {
|
|
95
|
+
get hasTextProperty() {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
is3dish() {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
get controls() {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
render(context) {
|
|
105
|
+
const { left, top, width, height } = this.bounds;
|
|
106
|
+
context.beginPath();
|
|
107
|
+
const snapshot = this._topViewSnapshot;
|
|
108
|
+
if (snapshot) {
|
|
109
|
+
context.drawImage(snapshot, left, top, width, height);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// 폴백: 로봇 아이콘 박스 + 중앙 라벨
|
|
113
|
+
context.rect(left, top, width, height);
|
|
114
|
+
context.fillStyle = '#e3f2fd';
|
|
115
|
+
context.fill();
|
|
116
|
+
context.strokeStyle = '#1976d2';
|
|
117
|
+
context.lineWidth = 1;
|
|
118
|
+
context.stroke();
|
|
119
|
+
const src = this.getState('src');
|
|
120
|
+
const label = src ? src.split('/').pop()?.replace(/\.[^.]+$/, '') || 'URDF' : 'URDF';
|
|
121
|
+
const fontSize = Math.max(10, Math.min(width, height) * 0.12);
|
|
122
|
+
context.fillStyle = '#1976d2';
|
|
123
|
+
context.font = `${fontSize}px sans-serif`;
|
|
124
|
+
context.textAlign = 'center';
|
|
125
|
+
context.textBaseline = 'middle';
|
|
126
|
+
context.fillText(label, left + width / 2, top + height / 2, width * 0.9);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async ready() {
|
|
130
|
+
await super.ready();
|
|
131
|
+
this._ensureTopViewSnapshot();
|
|
132
|
+
}
|
|
133
|
+
onchange(after, before) {
|
|
134
|
+
super.onchange(after, before);
|
|
135
|
+
if ('src' in after) {
|
|
136
|
+
this._topViewSnapshot = undefined;
|
|
137
|
+
this._ensureTopViewSnapshot();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
_ensureTopViewSnapshot() {
|
|
141
|
+
const src = this.getState('src');
|
|
142
|
+
if (!src)
|
|
143
|
+
return;
|
|
144
|
+
const cached = RealObjectURDF.getTopViewCache(src);
|
|
145
|
+
if (cached) {
|
|
146
|
+
this._topViewSnapshot = cached;
|
|
147
|
+
this.invalidate();
|
|
148
|
+
}
|
|
149
|
+
// 캐시 miss인 경우 3D 씬이 활성화되면 _onLoaded에서 자동 생성한다.
|
|
150
|
+
}
|
|
151
|
+
buildRealObject() {
|
|
152
|
+
return new RealObjectURDF(this);
|
|
153
|
+
}
|
|
154
|
+
get nature() {
|
|
155
|
+
return NATURE;
|
|
156
|
+
}
|
|
157
|
+
get source() {
|
|
158
|
+
return this.getState('src');
|
|
159
|
+
}
|
|
160
|
+
set source(source) {
|
|
161
|
+
this.setState('src', source);
|
|
162
|
+
}
|
|
163
|
+
set dimension(dimension) {
|
|
164
|
+
const { width = 1, height = 1, depth = 1 } = dimension;
|
|
165
|
+
this.setState({ width, height, depth });
|
|
166
|
+
this.realObject?.updateDimension();
|
|
167
|
+
}
|
|
168
|
+
// --- URDF 바인딩 API ---
|
|
169
|
+
/** 조인트 상태 맵. 값은 숫자(라디안/거리) 또는 { value } 형식 허용. */
|
|
170
|
+
get joints() {
|
|
171
|
+
return this.getState('joints');
|
|
172
|
+
}
|
|
173
|
+
set joints(value) {
|
|
174
|
+
this.setState('joints', value);
|
|
175
|
+
}
|
|
176
|
+
/** 로드된 URDF의 조인트 메타 정보. property editor가 슬라이더 UI 생성에 사용. */
|
|
177
|
+
get jointMeta() {
|
|
178
|
+
const ro = this.realObject;
|
|
179
|
+
return ro?.jointMeta ?? [];
|
|
180
|
+
}
|
|
181
|
+
get jointNames() {
|
|
182
|
+
const ro = this.realObject;
|
|
183
|
+
return ro?.jointNames ?? [];
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
URDFObject = __decorate([
|
|
187
|
+
sceneComponent('urdf')
|
|
188
|
+
], URDFObject);
|
|
189
|
+
export { URDFObject };
|
|
190
|
+
//# sourceMappingURL=urdf-object.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urdf-object.js","sourceRoot":"","sources":["../src/urdf-object.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * URDF 로봇을 things-scene 3D 공간에 네이티브로 임베드한다.\n * GLTFObject와 대칭적인 구조 — 2D 탑뷰는 스냅샷 렌더로 표현한다.\n */\n\nimport {\n RectPath,\n Shape,\n sceneComponent,\n type ComponentNature,\n type Control,\n type IRealObject\n} from '@hatiolab/things-scene'\nimport { RealObjectURDF } from './real-object-urdf.js'\nimport type { URDFJointState, URDFJointMeta } from './real-object-urdf.js'\n\nconst BASE_URL_ALIAS = '$base_url'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'urdf-preset',\n label: 'preset',\n name: 'preset'\n },\n {\n type: 'string',\n label: 'url',\n name: 'src',\n property: {\n displayField: 'id',\n displayFullUrl: true,\n baseUrlAlias: BASE_URL_ALIAS,\n defaultStorage: '3d-model',\n storageFilters: {\n type: Array,\n value: [\n {\n name: 'category',\n value: 'urdf'\n }\n ]\n },\n useUpload: true,\n category: 'application'\n }\n },\n {\n type: 'select',\n label: 'up-axis',\n name: 'upAxis',\n property: {\n options: [\n { display: 'Z (URDF default)', value: 'z' },\n { display: 'Y (Three.js default)', value: 'y' }\n ]\n }\n },\n {\n type: 'number',\n label: 'unit-scale',\n name: 'unitScale',\n placeholder: '1000'\n },\n {\n type: 'select',\n label: 'auto-animate',\n name: 'autoAnimate',\n property: {\n options: [\n { display: 'Off', value: 'none' },\n { display: 'Sine swing', value: 'sine' }\n ]\n }\n },\n {\n type: 'select',\n label: 'physics',\n name: 'physics',\n property: {\n options: [\n { display: 'Direct (immediate)', value: 'none' },\n { display: 'Smooth (damped)', value: 'smooth' }\n ]\n }\n },\n {\n type: 'urdf-joints',\n label: '',\n name: 'joints',\n editor: {\n fullwidth: true\n }\n }\n ],\n 'value-property': 'source',\n help: 'scene/component/urdf'\n}\n\n@sceneComponent('urdf')\nexport class URDFObject extends RectPath(Shape) {\n get hasTextProperty() {\n return false\n }\n\n is3dish() {\n return true\n }\n\n get controls(): Array<Control> | undefined {\n return []\n }\n\n render(context: CanvasRenderingContext2D) {\n const { left, top, width, height } = this.bounds\n\n context.beginPath()\n\n const snapshot = (this as any)._topViewSnapshot as HTMLCanvasElement | undefined\n if (snapshot) {\n context.drawImage(snapshot, left, top, width, height)\n } else {\n // 폴백: 로봇 아이콘 박스 + 중앙 라벨\n context.rect(left, top, width, height)\n context.fillStyle = '#e3f2fd'\n context.fill()\n context.strokeStyle = '#1976d2'\n context.lineWidth = 1\n context.stroke()\n\n const src = this.getState('src') as string | undefined\n const label = src ? src.split('/').pop()?.replace(/\\.[^.]+$/, '') || 'URDF' : 'URDF'\n const fontSize = Math.max(10, Math.min(width, height) * 0.12)\n context.fillStyle = '#1976d2'\n context.font = `${fontSize}px sans-serif`\n context.textAlign = 'center'\n context.textBaseline = 'middle'\n context.fillText(label, left + width / 2, top + height / 2, width * 0.9)\n }\n }\n\n async ready() {\n await super.ready()\n this._ensureTopViewSnapshot()\n }\n\n onchange(after: Record<string, any>, before: Record<string, any>) {\n super.onchange(after, before)\n if ('src' in after) {\n (this as any)._topViewSnapshot = undefined\n this._ensureTopViewSnapshot()\n }\n }\n\n private _ensureTopViewSnapshot() {\n const src = this.getState('src') as string | undefined\n if (!src) return\n\n const cached = RealObjectURDF.getTopViewCache(src)\n if (cached) {\n (this as any)._topViewSnapshot = cached\n this.invalidate()\n }\n // 캐시 miss인 경우 3D 씬이 활성화되면 _onLoaded에서 자동 생성한다.\n }\n\n buildRealObject(): IRealObject | undefined {\n return new RealObjectURDF(this as any)\n }\n\n get nature() {\n return NATURE\n }\n\n get source() {\n return this.getState('src')\n }\n\n set source(source) {\n this.setState('src', source)\n }\n\n set dimension(dimension: { width: number; height: number; depth: number }) {\n const { width = 1, height = 1, depth = 1 } = dimension\n this.setState({ width, height, depth })\n this.realObject?.updateDimension()\n }\n\n // --- URDF 바인딩 API ---\n\n /** 조인트 상태 맵. 값은 숫자(라디안/거리) 또는 { value } 형식 허용. */\n get joints(): Record<string, URDFJointState | number> | undefined {\n return this.getState('joints')\n }\n\n set joints(value: Record<string, URDFJointState | number> | undefined) {\n this.setState('joints', value)\n }\n\n /** 로드된 URDF의 조인트 메타 정보. property editor가 슬라이더 UI 생성에 사용. */\n get jointMeta(): URDFJointMeta[] {\n const ro = this.realObject as RealObjectURDF | undefined\n return ro?.jointMeta ?? []\n }\n\n get jointNames(): string[] {\n const ro = this.realObject as RealObjectURDF | undefined\n return ro?.jointNames ?? []\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface UrdfPreset {
|
|
2
|
+
/** 고유 식별자 (select value) */
|
|
3
|
+
id: string;
|
|
4
|
+
/** 표시 이름 */
|
|
5
|
+
display: string;
|
|
6
|
+
/** 로봇 분류 (arm / mobile / aerial / humanoid / quadruped) */
|
|
7
|
+
category: 'arm' | 'mobile' | 'aerial' | 'humanoid' | 'quadruped';
|
|
8
|
+
/** URDF 파일 URL */
|
|
9
|
+
src: string;
|
|
10
|
+
/** 컴포넌트 기본 dimension (mm). 로드 시 uniform best-fit scale 적용. */
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
depth: number;
|
|
14
|
+
/** 좌표계 up 축 (URDF는 보통 'z') */
|
|
15
|
+
upAxis: 'z' | 'y';
|
|
16
|
+
/** URDF meter → 에디터 mm 스케일 (보통 1000) */
|
|
17
|
+
unitScale: number;
|
|
18
|
+
/** ROS `package://<pkg>/...` prefix 해석 맵. 없으면 URDF 상대 경로만 지원. */
|
|
19
|
+
packages?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export declare const URDF_PRESETS: UrdfPreset[];
|
|
22
|
+
export declare function findPreset(id: string): UrdfPreset | undefined;
|