@holoscript/robotics-plugin 2.0.1 → 2.0.2
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/package.json +9 -9
- package/src/__tests__/isaac-lab-interop.test.ts +13 -10
- package/src/ast.ts +6 -1
- package/src/index.ts +32 -22
- package/src/parser.ts +19 -7
- package/src/traits/ROS2HardwareLoopTrait.ts +29 -14
- package/src/usd-codegen.ts +43 -14
- package/LICENSE +0 -21
- package/dist/ast.d.ts +0 -69
- package/dist/ast.js +0 -9
- package/dist/index.d.ts +0 -127
- package/dist/index.js +0 -349
- package/dist/lexer.d.ts +0 -52
- package/dist/lexer.js +0 -251
- package/dist/parser.d.ts +0 -30
- package/dist/parser.js +0 -376
- package/dist/traits/ROS2HardwareLoopTrait.d.ts +0 -15
- package/dist/traits/ROS2HardwareLoopTrait.js +0 -49
- package/dist/traits/types.d.ts +0 -4
- package/dist/traits/types.js +0 -1
- package/dist/usd-codegen.d.ts +0 -45
- package/dist/usd-codegen.js +0 -504
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export function createROS2HardwareLoopHandler() {
|
|
2
|
-
return {
|
|
3
|
-
name: 'ros2_hardware_loop',
|
|
4
|
-
defaultConfig: { nodeName: 'holo_rig_01', topicPrefix: '/holo', updateFrequencyHz: 60, bidirectional: true },
|
|
5
|
-
onAttach(n, c, ctx) {
|
|
6
|
-
n.__ros2State = { connected: false, lastPingMs: 0, activeTopics: [], hardwareSyncDriftMs: 0 };
|
|
7
|
-
ctx.emit?.('ros2:init', { node: c.nodeName });
|
|
8
|
-
},
|
|
9
|
-
onDetach(n, _c, ctx) {
|
|
10
|
-
delete n.__ros2State;
|
|
11
|
-
ctx.emit?.('ros2:disconnect');
|
|
12
|
-
},
|
|
13
|
-
onUpdate() { },
|
|
14
|
-
onEvent(n, c, ctx, e) {
|
|
15
|
-
const s = n.__ros2State;
|
|
16
|
-
if (!s)
|
|
17
|
-
return;
|
|
18
|
-
const evt = e;
|
|
19
|
-
switch (evt.type) {
|
|
20
|
-
case 'ros2:connect':
|
|
21
|
-
s.connected = true;
|
|
22
|
-
// In real usage, initializes rclnodejs bridge for pub/sub physics
|
|
23
|
-
ctx.emit?.('ros2:connected', { latencyMs: 12 });
|
|
24
|
-
break;
|
|
25
|
-
case 'scene:transform_change':
|
|
26
|
-
if (c.bidirectional && s.connected) {
|
|
27
|
-
// Push virtual set transform to physical robot joint
|
|
28
|
-
ctx.emit?.('ros2:publish', { topic: `${c.topicPrefix}/joint_cmd`, payload: evt.payload });
|
|
29
|
-
}
|
|
30
|
-
break;
|
|
31
|
-
case 'ros2:telemetry':
|
|
32
|
-
// Receive physical robot telemetry to drive virtual set
|
|
33
|
-
s.hardwareSyncDriftMs = Math.abs(Date.now() - (evt.payload.timestamp || Date.now()));
|
|
34
|
-
n.transform = evt.payload.transform;
|
|
35
|
-
break;
|
|
36
|
-
case 'ttu:manifested':
|
|
37
|
-
// Dynamically bind to Text-To-Universe AST streams
|
|
38
|
-
const crdtRoot = evt.payload?.crdtRoot;
|
|
39
|
-
if (crdtRoot && s.connected) {
|
|
40
|
-
const dynamicTopic = `${c.topicPrefix}/ttu_sync/${crdtRoot}`;
|
|
41
|
-
s.activeTopics.push(dynamicTopic);
|
|
42
|
-
ctx.emit?.('ros2:subscribe', { topic: dynamicTopic });
|
|
43
|
-
ctx.emit?.('ros2:log', { message: `Bridged digital twin for TTU root: ${crdtRoot}` });
|
|
44
|
-
}
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
}
|
package/dist/traits/types.d.ts
DELETED
package/dist/traits/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/usd-codegen.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HoloScript → USD Code Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates Universal Scene Description (USD) files from HoloScript AST.
|
|
5
|
-
* Targets NVIDIA Isaac Sim with UsdPhysics and PhysX schemas.
|
|
6
|
-
*
|
|
7
|
-
* Isaac Lab Interop (Path A):
|
|
8
|
-
* - Emits PhysicsDriveAPI for PD actuator control
|
|
9
|
-
* - Emits PhysxJointAxisAPI for per-axis joint friction assumptions
|
|
10
|
-
* - Supports domain randomization metadata for sim-to-real transfer
|
|
11
|
-
*/
|
|
12
|
-
import { CompositionNode } from './ast';
|
|
13
|
-
export interface IsaacLabConfig {
|
|
14
|
-
/** Isaac Lab version target for generated code */
|
|
15
|
-
isaacLabVersion?: string;
|
|
16
|
-
/** Enable PhysX articulation and per-axis joint friction schemas (default: true) */
|
|
17
|
-
enableJointFriction?: boolean;
|
|
18
|
-
/** Enable drive attributes for PD control (default: true) */
|
|
19
|
-
enableDriveAttributes?: boolean;
|
|
20
|
-
}
|
|
21
|
-
export declare class USDCodeGen {
|
|
22
|
-
private output;
|
|
23
|
-
private indentLevel;
|
|
24
|
-
private config;
|
|
25
|
-
constructor(config?: IsaacLabConfig);
|
|
26
|
-
generate(ast: CompositionNode): string;
|
|
27
|
-
private generateLink;
|
|
28
|
-
private generateJoint;
|
|
29
|
-
private indent;
|
|
30
|
-
private emit;
|
|
31
|
-
private geometryToUSD;
|
|
32
|
-
private isJointObject;
|
|
33
|
-
private getJointKind;
|
|
34
|
-
private getJointApiSchemas;
|
|
35
|
-
private hasDriveProperties;
|
|
36
|
-
private hasPhysxJointAxisProperties;
|
|
37
|
-
private emitPhysxJointAxisProperties;
|
|
38
|
-
private emitActuatorGroupComment;
|
|
39
|
-
private numberProp;
|
|
40
|
-
private convertJointScalar;
|
|
41
|
-
private formatNumber;
|
|
42
|
-
private formatTokenArray;
|
|
43
|
-
private vectorToAxisName;
|
|
44
|
-
private emitDomainRandomizationAsComment;
|
|
45
|
-
}
|
package/dist/usd-codegen.js
DELETED
|
@@ -1,504 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HoloScript → USD Code Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates Universal Scene Description (USD) files from HoloScript AST.
|
|
5
|
-
* Targets NVIDIA Isaac Sim with UsdPhysics and PhysX schemas.
|
|
6
|
-
*
|
|
7
|
-
* Isaac Lab Interop (Path A):
|
|
8
|
-
* - Emits PhysicsDriveAPI for PD actuator control
|
|
9
|
-
* - Emits PhysxJointAxisAPI for per-axis joint friction assumptions
|
|
10
|
-
* - Supports domain randomization metadata for sim-to-real transfer
|
|
11
|
-
*/
|
|
12
|
-
export class USDCodeGen {
|
|
13
|
-
constructor(config) {
|
|
14
|
-
this.output = [];
|
|
15
|
-
this.indentLevel = 0;
|
|
16
|
-
this.config = {
|
|
17
|
-
isaacLabVersion: config?.isaacLabVersion || '2.3',
|
|
18
|
-
enableJointFriction: config?.enableJointFriction ?? true,
|
|
19
|
-
enableDriveAttributes: config?.enableDriveAttributes ?? true,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
generate(ast) {
|
|
23
|
-
this.output = [];
|
|
24
|
-
this.indentLevel = 0;
|
|
25
|
-
// USD header
|
|
26
|
-
this.emit('#usda 1.0');
|
|
27
|
-
this.emit('(');
|
|
28
|
-
this.indentLevel++;
|
|
29
|
-
this.emit(`defaultPrim = "${ast.name}"`);
|
|
30
|
-
this.emit('upAxis = "Z"');
|
|
31
|
-
this.emit('metersPerUnit = 1.0');
|
|
32
|
-
this.emit('kilogramsPerMass = 1.0');
|
|
33
|
-
this.indentLevel--;
|
|
34
|
-
this.emit(')');
|
|
35
|
-
this.emit('');
|
|
36
|
-
// Isaac Lab header comment
|
|
37
|
-
this.emit(`# Generated for Isaac Lab ${this.config.isaacLabVersion}`);
|
|
38
|
-
this.emit('# Units: meters, kilograms, seconds; HoloScript angular inputs are radians.');
|
|
39
|
-
this.emit('# USD angular joint limits and velocities are exported in degrees per OpenUSD/PhysX.');
|
|
40
|
-
// Emit domain randomization config as USD comments (for Isaac Lab Python codegen)
|
|
41
|
-
if (ast.domainRandomization) {
|
|
42
|
-
this.emit('');
|
|
43
|
-
this.emit('# Domain Randomization Configuration');
|
|
44
|
-
this.emitDomainRandomizationAsComment(ast.domainRandomization);
|
|
45
|
-
this.emit('');
|
|
46
|
-
}
|
|
47
|
-
// Root articulation.
|
|
48
|
-
const rootSchemas = ['PhysicsArticulationRootAPI'];
|
|
49
|
-
if (this.config.enableJointFriction) {
|
|
50
|
-
rootSchemas.push('PhysxArticulationAPI');
|
|
51
|
-
}
|
|
52
|
-
this.emit(`def Xform "${ast.name}" (`);
|
|
53
|
-
this.indentLevel++;
|
|
54
|
-
this.emit(`prepend apiSchemas = ${this.formatTokenArray(rootSchemas)}`);
|
|
55
|
-
this.indentLevel--;
|
|
56
|
-
this.emit(')');
|
|
57
|
-
this.emit('{');
|
|
58
|
-
this.indentLevel++;
|
|
59
|
-
if (this.config.enableJointFriction) {
|
|
60
|
-
this.emit('');
|
|
61
|
-
this.emit('# PhysxArticulationAPI root settings');
|
|
62
|
-
this.emit('bool physxArticulation:articulationEnabled = true');
|
|
63
|
-
this.emit('bool physxArticulation:enabledSelfCollisions = false');
|
|
64
|
-
this.emit('int physxArticulation:solverPositionIterationCount = 32');
|
|
65
|
-
this.emit('int physxArticulation:solverVelocityIterationCount = 1');
|
|
66
|
-
}
|
|
67
|
-
// Collect joints for later generation
|
|
68
|
-
const joints = [];
|
|
69
|
-
// Separate joint and link objects
|
|
70
|
-
const jointObjects = [];
|
|
71
|
-
const linkObjects = [];
|
|
72
|
-
for (const obj of ast.objects) {
|
|
73
|
-
if (this.isJointObject(obj)) {
|
|
74
|
-
jointObjects.push(obj);
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
linkObjects.push(obj);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// Generate links
|
|
81
|
-
for (const obj of ast.objects) {
|
|
82
|
-
if (this.isJointObject(obj)) {
|
|
83
|
-
// New template format: Joint is separate object
|
|
84
|
-
// Check if there's a corresponding link (child of this joint)
|
|
85
|
-
const childLink = linkObjects.find((link) => link.properties.parent === obj.name);
|
|
86
|
-
const kind = this.getJointKind(obj);
|
|
87
|
-
if (childLink) {
|
|
88
|
-
// Generate the child link
|
|
89
|
-
this.generateLink(childLink);
|
|
90
|
-
// Collect joint info
|
|
91
|
-
const parent = obj.properties.parent || 'World';
|
|
92
|
-
joints.push({
|
|
93
|
-
name: obj.name,
|
|
94
|
-
kind,
|
|
95
|
-
parent,
|
|
96
|
-
child: childLink.name,
|
|
97
|
-
props: obj.properties,
|
|
98
|
-
actuatorGroups: obj.actuatorGroups,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
// Old format: joint_parent property (backward compatibility)
|
|
103
|
-
this.generateLink(obj);
|
|
104
|
-
const parent = obj.properties.joint_parent || 'World';
|
|
105
|
-
joints.push({
|
|
106
|
-
name: `${obj.name}Joint`,
|
|
107
|
-
kind,
|
|
108
|
-
parent,
|
|
109
|
-
child: obj.name,
|
|
110
|
-
props: obj.properties,
|
|
111
|
-
actuatorGroups: obj.actuatorGroups,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
// Link object without joint (base, end effector, etc.)
|
|
117
|
-
// Only generate if not already generated as child of a joint
|
|
118
|
-
const isChildOfJoint = jointObjects.some((j) => linkObjects.find((l) => l.properties.parent === j.name && l.name === obj.name));
|
|
119
|
-
if (!isChildOfJoint) {
|
|
120
|
-
this.generateLink(obj);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
// Generate joints after all links
|
|
125
|
-
this.emit('');
|
|
126
|
-
this.emit('# Joints');
|
|
127
|
-
for (const joint of joints) {
|
|
128
|
-
this.generateJoint(joint);
|
|
129
|
-
}
|
|
130
|
-
this.indentLevel--;
|
|
131
|
-
this.emit('}');
|
|
132
|
-
return this.output.join('\n');
|
|
133
|
-
}
|
|
134
|
-
generateLink(obj) {
|
|
135
|
-
const geometry = obj.properties.geometry || 'box';
|
|
136
|
-
const usdType = this.geometryToUSD(geometry);
|
|
137
|
-
const isStatic = obj.traits.includes('static');
|
|
138
|
-
this.emit('');
|
|
139
|
-
this.emit(`# ${obj.name}`);
|
|
140
|
-
if (obj.domainRandomization) {
|
|
141
|
-
this.emit('# Object Domain Randomization Configuration');
|
|
142
|
-
this.emitDomainRandomizationAsComment(obj.domainRandomization);
|
|
143
|
-
}
|
|
144
|
-
this.emit(`def ${usdType} "${obj.name}" (`);
|
|
145
|
-
this.indentLevel++;
|
|
146
|
-
// Add physics APIs (unless static)
|
|
147
|
-
if (!isStatic) {
|
|
148
|
-
this.emit('prepend apiSchemas = ["PhysicsCollisionAPI", "PhysicsRigidBodyAPI", "PhysicsMassAPI"]');
|
|
149
|
-
}
|
|
150
|
-
this.indentLevel--;
|
|
151
|
-
this.emit(')');
|
|
152
|
-
this.emit('{');
|
|
153
|
-
this.indentLevel++;
|
|
154
|
-
// Geometry dimensions (support both array and individual properties)
|
|
155
|
-
if (geometry === 'cylinder') {
|
|
156
|
-
let radius, height;
|
|
157
|
-
if (obj.properties.dimensions) {
|
|
158
|
-
// Old format: dimensions: [radius, height]
|
|
159
|
-
const dims = obj.properties.dimensions;
|
|
160
|
-
[radius, height] = dims;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
// New format: radius: 0.2, height/length: 0.2
|
|
164
|
-
radius = obj.properties.radius || 0.1;
|
|
165
|
-
height = (obj.properties.height || obj.properties.length) || 0.1;
|
|
166
|
-
}
|
|
167
|
-
this.emit(`float radius = ${radius}`);
|
|
168
|
-
this.emit(`float height = ${height}`);
|
|
169
|
-
}
|
|
170
|
-
else if (geometry === 'box') {
|
|
171
|
-
let length, width, height;
|
|
172
|
-
if (obj.properties.dimensions) {
|
|
173
|
-
// Old format: dimensions: [length, width, height]
|
|
174
|
-
const dims = obj.properties.dimensions;
|
|
175
|
-
[length, width, height] = dims;
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
// New format: length: 1.0, width: 0.1, height: 0.1
|
|
179
|
-
length = obj.properties.length || 1.0;
|
|
180
|
-
width = obj.properties.width || 1.0;
|
|
181
|
-
height = obj.properties.height || 1.0;
|
|
182
|
-
}
|
|
183
|
-
this.emit('double size = 1.0');
|
|
184
|
-
this.emit(`float3 xformOp:scale = (${length}, ${width}, ${height})`);
|
|
185
|
-
this.emit('uniform token[] xformOpOrder = ["xformOp:scale"]');
|
|
186
|
-
}
|
|
187
|
-
else if (geometry === 'sphere') {
|
|
188
|
-
let radius;
|
|
189
|
-
if (obj.properties.dimensions) {
|
|
190
|
-
// Old format: dimensions: [radius]
|
|
191
|
-
const dims = obj.properties.dimensions;
|
|
192
|
-
[radius] = dims;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
// New format: radius: 0.08
|
|
196
|
-
radius = obj.properties.radius || 0.1;
|
|
197
|
-
}
|
|
198
|
-
this.emit(`float radius = ${radius}`);
|
|
199
|
-
}
|
|
200
|
-
// Color/Material
|
|
201
|
-
if (obj.properties.color) {
|
|
202
|
-
const color = obj.properties.color;
|
|
203
|
-
const [r, g, b] = color;
|
|
204
|
-
this.emit(`color3f[] primvars:displayColor = [(${r}, ${g}, ${b})]`);
|
|
205
|
-
}
|
|
206
|
-
else if (obj.properties.material) {
|
|
207
|
-
// Default colors based on material
|
|
208
|
-
const materialColors = {
|
|
209
|
-
metal: '0.6, 0.6, 0.65',
|
|
210
|
-
plastic: '0.8, 0.8, 0.8',
|
|
211
|
-
wood: '0.6, 0.4, 0.2',
|
|
212
|
-
glass: '0.9, 0.9, 1.0',
|
|
213
|
-
};
|
|
214
|
-
const material = obj.properties.material;
|
|
215
|
-
const color = materialColors[material] || '0.8, 0.8, 0.8';
|
|
216
|
-
this.emit(`color3f[] primvars:displayColor = [(${color})]`);
|
|
217
|
-
}
|
|
218
|
-
// Physics properties
|
|
219
|
-
if (!isStatic) {
|
|
220
|
-
this.emit('');
|
|
221
|
-
this.emit('# Physics properties');
|
|
222
|
-
const mass = obj.properties.mass || 1.0;
|
|
223
|
-
this.emit(`float physics:mass = ${mass}`);
|
|
224
|
-
if (obj.properties.inertia) {
|
|
225
|
-
const inertia = obj.properties.inertia;
|
|
226
|
-
this.emit(`float3 physics:diagonalInertia = (${inertia[0]}, ${inertia[1]}, ${inertia[2]})`);
|
|
227
|
-
}
|
|
228
|
-
this.emit('uniform token physics:approximation = "convexHull"');
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
this.emit('');
|
|
232
|
-
this.emit('# Static object');
|
|
233
|
-
this.emit('uniform token physics:rigidBodyEnabled = false');
|
|
234
|
-
}
|
|
235
|
-
// Position (if specified)
|
|
236
|
-
if (obj.properties.position) {
|
|
237
|
-
const pos = obj.properties.position;
|
|
238
|
-
this.emit('');
|
|
239
|
-
this.emit(`double3 xformOp:translate = (${pos[0]}, ${pos[1]}, ${pos[2]})`);
|
|
240
|
-
if (!obj.properties.dimensions) {
|
|
241
|
-
this.emit('uniform token[] xformOpOrder = ["xformOp:translate"]');
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
this.indentLevel--;
|
|
245
|
-
this.emit('}');
|
|
246
|
-
}
|
|
247
|
-
generateJoint(joint) {
|
|
248
|
-
const axisToken = joint.kind === 'prismatic' ? 'linear' : 'angular';
|
|
249
|
-
const schemas = this.getJointApiSchemas(joint, axisToken);
|
|
250
|
-
this.emit('');
|
|
251
|
-
this.emit(`def ${joint.kind === 'prismatic' ? 'PhysicsPrismaticJoint' : 'PhysicsRevoluteJoint'} "${joint.name}"`);
|
|
252
|
-
if (schemas.length > 0) {
|
|
253
|
-
this.emit('(');
|
|
254
|
-
this.indentLevel++;
|
|
255
|
-
this.emit(`prepend apiSchemas = ${this.formatTokenArray(schemas)}`);
|
|
256
|
-
this.indentLevel--;
|
|
257
|
-
this.emit(')');
|
|
258
|
-
}
|
|
259
|
-
this.emit('{');
|
|
260
|
-
this.indentLevel++;
|
|
261
|
-
// Body references
|
|
262
|
-
this.emit(`rel physics:body0 = <../${joint.parent}>`);
|
|
263
|
-
this.emit(`rel physics:body1 = <../${joint.child}>`);
|
|
264
|
-
this.emit('');
|
|
265
|
-
// Joint frames
|
|
266
|
-
const origin = joint.props.joint_origin || joint.props.position;
|
|
267
|
-
if (origin) {
|
|
268
|
-
const pos = origin;
|
|
269
|
-
this.emit(`point3f physics:localPos0 = (${pos[0]}, ${pos[1]}, ${pos[2]})`);
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
this.emit('point3f physics:localPos0 = (0, 0, 0)');
|
|
273
|
-
}
|
|
274
|
-
this.emit('quatf physics:localRot0 = (1, 0, 0, 0)');
|
|
275
|
-
this.emit('');
|
|
276
|
-
this.emit('point3f physics:localPos1 = (0, 0, 0)');
|
|
277
|
-
this.emit('quatf physics:localRot1 = (1, 0, 0, 0)');
|
|
278
|
-
this.emit('');
|
|
279
|
-
// Joint axis (support both old and new property names)
|
|
280
|
-
const axis = joint.props.joint_axis || joint.props.axis;
|
|
281
|
-
if (axis) {
|
|
282
|
-
const axisVec = axis;
|
|
283
|
-
const axisName = this.vectorToAxisName(axisVec);
|
|
284
|
-
this.emit(`uniform token physics:axis = "${axisName}"`);
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
this.emit('uniform token physics:axis = "Y"');
|
|
288
|
-
}
|
|
289
|
-
this.emit('');
|
|
290
|
-
// Joint limits (support both old and new property names)
|
|
291
|
-
const limits = joint.props.joint_limits || joint.props.limits;
|
|
292
|
-
if (limits) {
|
|
293
|
-
const limitsVec = limits;
|
|
294
|
-
this.emit(`float physics:lowerLimit = ${this.formatNumber(this.convertJointScalar(limitsVec[0], joint.kind))}`);
|
|
295
|
-
this.emit(`float physics:upperLimit = ${this.formatNumber(this.convertJointScalar(limitsVec[1], joint.kind))}`);
|
|
296
|
-
}
|
|
297
|
-
this.emit('');
|
|
298
|
-
// Joint dynamics (support both old and new property names)
|
|
299
|
-
const effort = this.numberProp(joint.props, 'joint_effort', 'max_effort');
|
|
300
|
-
if (effort !== undefined) {
|
|
301
|
-
this.emit(`float physics:maxForce = ${effort}`);
|
|
302
|
-
}
|
|
303
|
-
const velocity = this.numberProp(joint.props, 'max_velocity');
|
|
304
|
-
if (velocity !== undefined) {
|
|
305
|
-
this.emit(`float physics:maxVelocity = ${this.formatNumber(this.convertJointScalar(velocity, joint.kind))}`);
|
|
306
|
-
}
|
|
307
|
-
// OpenUSD PhysicsDriveAPI attributes for PD actuator control.
|
|
308
|
-
if (this.config.enableDriveAttributes && this.hasDriveProperties(joint.props)) {
|
|
309
|
-
this.emit('');
|
|
310
|
-
this.emit('# PhysicsDriveAPI attributes for PD control');
|
|
311
|
-
const kp = this.numberProp(joint.props, 'kp', 'stiffness');
|
|
312
|
-
const kd = this.numberProp(joint.props, 'kd', 'damping');
|
|
313
|
-
if (kp !== undefined) {
|
|
314
|
-
this.emit(`float drive:${axisToken}:physics:stiffness = ${kp}`);
|
|
315
|
-
}
|
|
316
|
-
if (kd !== undefined) {
|
|
317
|
-
this.emit(`float drive:${axisToken}:physics:damping = ${kd}`);
|
|
318
|
-
}
|
|
319
|
-
if (effort !== undefined) {
|
|
320
|
-
this.emit(`float drive:${axisToken}:physics:maxForce = ${effort}`);
|
|
321
|
-
}
|
|
322
|
-
this.emit(`uniform token drive:${axisToken}:physics:type = "force"`);
|
|
323
|
-
}
|
|
324
|
-
if (this.config.enableJointFriction && this.hasPhysxJointAxisProperties(joint.props)) {
|
|
325
|
-
this.emit('');
|
|
326
|
-
this.emit('# PhysxJointAxisAPI per-axis friction and velocity assumptions');
|
|
327
|
-
this.emitPhysxJointAxisProperties(joint.props, joint.kind, axisToken);
|
|
328
|
-
}
|
|
329
|
-
const latency = this.numberProp(joint.props, 'actuator_latency', 'latency');
|
|
330
|
-
if (latency !== undefined) {
|
|
331
|
-
this.emit('');
|
|
332
|
-
this.emit('# Isaac Lab delayed actuator hint; convert seconds to delay steps in task config.');
|
|
333
|
-
this.emit(`custom float holoscript:isaacLab:actuatorLatencySeconds = ${latency}`);
|
|
334
|
-
}
|
|
335
|
-
if (joint.actuatorGroups?.length) {
|
|
336
|
-
this.emit('');
|
|
337
|
-
this.emit('# Isaac Lab actuator group hints');
|
|
338
|
-
for (const group of joint.actuatorGroups) {
|
|
339
|
-
this.emitActuatorGroupComment(group);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
this.indentLevel--;
|
|
343
|
-
this.emit('}');
|
|
344
|
-
}
|
|
345
|
-
indent() {
|
|
346
|
-
return ' '.repeat(this.indentLevel);
|
|
347
|
-
}
|
|
348
|
-
emit(line) {
|
|
349
|
-
this.output.push(this.indent() + line);
|
|
350
|
-
}
|
|
351
|
-
geometryToUSD(geometry) {
|
|
352
|
-
const mapping = {
|
|
353
|
-
cylinder: 'Cylinder',
|
|
354
|
-
box: 'Cube',
|
|
355
|
-
sphere: 'Sphere',
|
|
356
|
-
cone: 'Cone',
|
|
357
|
-
plane: 'Plane',
|
|
358
|
-
torus: 'Torus',
|
|
359
|
-
};
|
|
360
|
-
return mapping[geometry] || 'Cube';
|
|
361
|
-
}
|
|
362
|
-
isJointObject(obj) {
|
|
363
|
-
return obj.traits.includes('joint_revolute') || obj.traits.includes('joint_prismatic');
|
|
364
|
-
}
|
|
365
|
-
getJointKind(obj) {
|
|
366
|
-
return obj.traits.includes('joint_prismatic') ? 'prismatic' : 'revolute';
|
|
367
|
-
}
|
|
368
|
-
getJointApiSchemas(joint, axisToken) {
|
|
369
|
-
const schemas = [];
|
|
370
|
-
if (this.config.enableDriveAttributes && this.hasDriveProperties(joint.props)) {
|
|
371
|
-
schemas.push(`PhysicsDriveAPI:${axisToken}`);
|
|
372
|
-
}
|
|
373
|
-
if (this.config.enableJointFriction && this.hasPhysxJointAxisProperties(joint.props)) {
|
|
374
|
-
schemas.push(`PhysxJointAxisAPI:${axisToken}`);
|
|
375
|
-
}
|
|
376
|
-
return schemas;
|
|
377
|
-
}
|
|
378
|
-
hasDriveProperties(props) {
|
|
379
|
-
return this.numberProp(props, 'kp', 'stiffness', 'kd', 'damping', 'joint_effort', 'max_effort') !== undefined;
|
|
380
|
-
}
|
|
381
|
-
hasPhysxJointAxisProperties(props) {
|
|
382
|
-
return (this.numberProp(props, 'joint_friction', 'friction', 'joint_static_friction', 'joint_dynamic_friction', 'joint_viscous_friction', 'armature', 'max_velocity') !== undefined);
|
|
383
|
-
}
|
|
384
|
-
emitPhysxJointAxisProperties(props, kind, axisToken) {
|
|
385
|
-
const friction = this.numberProp(props, 'joint_friction', 'friction');
|
|
386
|
-
const staticFriction = this.numberProp(props, 'joint_static_friction') ?? friction;
|
|
387
|
-
const dynamicFriction = this.numberProp(props, 'joint_dynamic_friction') ?? friction;
|
|
388
|
-
const viscousFriction = this.numberProp(props, 'joint_viscous_friction');
|
|
389
|
-
const armature = this.numberProp(props, 'armature');
|
|
390
|
-
const velocity = this.numberProp(props, 'max_velocity');
|
|
391
|
-
if (staticFriction !== undefined) {
|
|
392
|
-
this.emit(`float physxJointAxis:${axisToken}:staticFrictionEffort = ${staticFriction}`);
|
|
393
|
-
}
|
|
394
|
-
if (dynamicFriction !== undefined) {
|
|
395
|
-
this.emit(`float physxJointAxis:${axisToken}:dynamicFrictionEffort = ${dynamicFriction}`);
|
|
396
|
-
}
|
|
397
|
-
if (viscousFriction !== undefined) {
|
|
398
|
-
this.emit(`float physxJointAxis:${axisToken}:viscousFrictionCoefficient = ${viscousFriction}`);
|
|
399
|
-
}
|
|
400
|
-
if (armature !== undefined) {
|
|
401
|
-
this.emit(`float physxJointAxis:${axisToken}:armature = ${armature}`);
|
|
402
|
-
}
|
|
403
|
-
if (velocity !== undefined) {
|
|
404
|
-
this.emit(`float physxJointAxis:${axisToken}:maxJointVelocity = ${this.formatNumber(this.convertJointScalar(velocity, kind))}`);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
emitActuatorGroupComment(group) {
|
|
408
|
-
const details = [
|
|
409
|
-
`type=${group.type}`,
|
|
410
|
-
`joints=[${group.jointNames.join(', ')}]`,
|
|
411
|
-
group.stiffness !== undefined ? `stiffness=${group.stiffness}` : undefined,
|
|
412
|
-
group.damping !== undefined ? `damping=${group.damping}` : undefined,
|
|
413
|
-
group.friction !== undefined ? `friction=${group.friction}` : undefined,
|
|
414
|
-
group.latency !== undefined ? `latencySeconds=${group.latency}` : undefined,
|
|
415
|
-
].filter(Boolean);
|
|
416
|
-
this.emit(`# ${group.name}: ${details.join(' ')}`);
|
|
417
|
-
}
|
|
418
|
-
numberProp(props, ...keys) {
|
|
419
|
-
for (const key of keys) {
|
|
420
|
-
const value = props[key];
|
|
421
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
422
|
-
return value;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return undefined;
|
|
426
|
-
}
|
|
427
|
-
convertJointScalar(value, kind) {
|
|
428
|
-
return kind === 'revolute' ? (value * 180) / Math.PI : value;
|
|
429
|
-
}
|
|
430
|
-
formatNumber(value) {
|
|
431
|
-
return Number(value.toFixed(6)).toString();
|
|
432
|
-
}
|
|
433
|
-
formatTokenArray(values) {
|
|
434
|
-
return `[${values.map((value) => `"${value}"`).join(', ')}]`;
|
|
435
|
-
}
|
|
436
|
-
vectorToAxisName(axis) {
|
|
437
|
-
const [x = 0, y = 0, z = 1] = axis;
|
|
438
|
-
if (Math.abs(x) > Math.abs(y) && Math.abs(x) > Math.abs(z)) {
|
|
439
|
-
return 'X';
|
|
440
|
-
}
|
|
441
|
-
else if (Math.abs(y) > Math.abs(z)) {
|
|
442
|
-
return 'Y';
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
return 'Z';
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
emitDomainRandomizationAsComment(dr) {
|
|
449
|
-
if (dr.physics) {
|
|
450
|
-
this.emit('# physics:');
|
|
451
|
-
if (dr.physics.massScale) {
|
|
452
|
-
this.emit(`# massScale: [${dr.physics.massScale[0]}, ${dr.physics.massScale[1]}]`);
|
|
453
|
-
}
|
|
454
|
-
if (dr.physics.frictionRange) {
|
|
455
|
-
this.emit(`# frictionRange: [${dr.physics.frictionRange[0]}, ${dr.physics.frictionRange[1]}]`);
|
|
456
|
-
}
|
|
457
|
-
if (dr.physics.dampingRange) {
|
|
458
|
-
this.emit(`# dampingRange: [${dr.physics.dampingRange[0]}, ${dr.physics.dampingRange[1]}]`);
|
|
459
|
-
}
|
|
460
|
-
if (dr.physics.armatureRange) {
|
|
461
|
-
this.emit(`# armatureRange: [${dr.physics.armatureRange[0]}, ${dr.physics.armatureRange[1]}]`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
if (dr.actuator) {
|
|
465
|
-
this.emit('# actuator:');
|
|
466
|
-
if (dr.actuator.kpNoise !== undefined) {
|
|
467
|
-
this.emit(`# kpNoise: ${dr.actuator.kpNoise}`);
|
|
468
|
-
}
|
|
469
|
-
if (dr.actuator.kdNoise !== undefined) {
|
|
470
|
-
this.emit(`# kdNoise: ${dr.actuator.kdNoise}`);
|
|
471
|
-
}
|
|
472
|
-
if (dr.actuator.latencyNoise !== undefined) {
|
|
473
|
-
this.emit(`# latencyNoise: ${dr.actuator.latencyNoise}`);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (dr.observation) {
|
|
477
|
-
this.emit('# observation:');
|
|
478
|
-
if (dr.observation.jointPosNoise !== undefined) {
|
|
479
|
-
this.emit(`# jointPosNoise: ${dr.observation.jointPosNoise}`);
|
|
480
|
-
}
|
|
481
|
-
if (dr.observation.jointVelNoise !== undefined) {
|
|
482
|
-
this.emit(`# jointVelNoise: ${dr.observation.jointVelNoise}`);
|
|
483
|
-
}
|
|
484
|
-
if (dr.observation.imuNoise !== undefined) {
|
|
485
|
-
this.emit(`# imuNoise: ${dr.observation.imuNoise}`);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
if (dr.initialState) {
|
|
489
|
-
this.emit('# initialState:');
|
|
490
|
-
if (dr.initialState.rootPoseRange) {
|
|
491
|
-
this.emit(`# rootPoseRange: [${dr.initialState.rootPoseRange.join(', ')}]`);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (dr.disturbance) {
|
|
495
|
-
this.emit('# disturbance:');
|
|
496
|
-
if (dr.disturbance.forceRange) {
|
|
497
|
-
this.emit(`# forceRange: [${dr.disturbance.forceRange[0]}, ${dr.disturbance.forceRange[1]}]`);
|
|
498
|
-
}
|
|
499
|
-
if (dr.disturbance.intervalRange) {
|
|
500
|
-
this.emit(`# intervalRange: [${dr.disturbance.intervalRange[0]}, ${dr.disturbance.intervalRange[1]}]`);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|