@holoscript/robotics-plugin 1.0.0 → 2.0.1
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/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/dist/ast.d.ts +48 -0
- package/dist/ast.js +6 -2
- package/dist/index.d.ts +70 -1
- package/dist/index.js +333 -28
- package/dist/lexer.js +7 -31
- package/dist/parser.d.ts +8 -0
- package/dist/parser.js +266 -92
- package/dist/traits/ROS2HardwareLoopTrait.d.ts +15 -0
- package/dist/traits/ROS2HardwareLoopTrait.js +49 -0
- package/dist/traits/types.d.ts +4 -0
- package/dist/traits/types.js +1 -0
- package/dist/usd-codegen.d.ts +28 -1
- package/dist/usd-codegen.js +243 -100
- package/examples/isaac-lab-sim-to-real-bridge.holo +63 -0
- package/examples/isaac-lab-sim-to-real.holo +127 -0
- package/package.json +12 -47
- package/python/isaac_lab_bridge.py +157 -0
- package/src/__tests__/isaac-lab-interop.test.ts +207 -0
- package/src/ast.ts +75 -0
- package/src/index.ts +553 -0
- package/src/lexer.ts +307 -0
- package/src/parser.ts +461 -0
- package/src/traits/ROS2HardwareLoopTrait.ts +69 -0
- package/src/traits/types.ts +4 -0
- package/src/usd-codegen.ts +622 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
- package/README.md +0 -3
- package/examples/robot-arm-complete.holo +0 -199
- package/python/ros2_bridge.py +0 -77
package/dist/usd-codegen.js
CHANGED
|
@@ -1,49 +1,23 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* HoloScript → USD Code Generator
|
|
4
3
|
*
|
|
5
4
|
* Generates Universal Scene Description (USD) files from HoloScript AST.
|
|
6
|
-
* Targets NVIDIA Isaac Sim with UsdPhysics
|
|
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
|
|
7
11
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
-
}
|
|
14
|
-
Object.defineProperty(o, k2, desc);
|
|
15
|
-
}) : (function(o, m, k, k2) {
|
|
16
|
-
if (k2 === undefined) k2 = k;
|
|
17
|
-
o[k2] = m[k];
|
|
18
|
-
}));
|
|
19
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
-
}) : function(o, v) {
|
|
22
|
-
o["default"] = v;
|
|
23
|
-
});
|
|
24
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
-
var ownKeys = function(o) {
|
|
26
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
-
var ar = [];
|
|
28
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
-
return ar;
|
|
30
|
-
};
|
|
31
|
-
return ownKeys(o);
|
|
32
|
-
};
|
|
33
|
-
return function (mod) {
|
|
34
|
-
if (mod && mod.__esModule) return mod;
|
|
35
|
-
var result = {};
|
|
36
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
-
__setModuleDefault(result, mod);
|
|
38
|
-
return result;
|
|
39
|
-
};
|
|
40
|
-
})();
|
|
41
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.USDCodeGen = void 0;
|
|
43
|
-
class USDCodeGen {
|
|
44
|
-
constructor() {
|
|
12
|
+
export class USDCodeGen {
|
|
13
|
+
constructor(config) {
|
|
45
14
|
this.output = [];
|
|
46
15
|
this.indentLevel = 0;
|
|
16
|
+
this.config = {
|
|
17
|
+
isaacLabVersion: config?.isaacLabVersion || '2.3',
|
|
18
|
+
enableJointFriction: config?.enableJointFriction ?? true,
|
|
19
|
+
enableDriveAttributes: config?.enableDriveAttributes ?? true,
|
|
20
|
+
};
|
|
47
21
|
}
|
|
48
22
|
generate(ast) {
|
|
49
23
|
this.output = [];
|
|
@@ -59,21 +33,44 @@ class USDCodeGen {
|
|
|
59
33
|
this.indentLevel--;
|
|
60
34
|
this.emit(')');
|
|
61
35
|
this.emit('');
|
|
62
|
-
//
|
|
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
|
+
}
|
|
63
52
|
this.emit(`def Xform "${ast.name}" (`);
|
|
64
53
|
this.indentLevel++;
|
|
65
|
-
this.emit(
|
|
54
|
+
this.emit(`prepend apiSchemas = ${this.formatTokenArray(rootSchemas)}`);
|
|
66
55
|
this.indentLevel--;
|
|
67
56
|
this.emit(')');
|
|
68
57
|
this.emit('{');
|
|
69
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
|
+
}
|
|
70
67
|
// Collect joints for later generation
|
|
71
68
|
const joints = [];
|
|
72
69
|
// Separate joint and link objects
|
|
73
70
|
const jointObjects = [];
|
|
74
71
|
const linkObjects = [];
|
|
75
72
|
for (const obj of ast.objects) {
|
|
76
|
-
if (
|
|
73
|
+
if (this.isJointObject(obj)) {
|
|
77
74
|
jointObjects.push(obj);
|
|
78
75
|
}
|
|
79
76
|
else {
|
|
@@ -82,10 +79,11 @@ class USDCodeGen {
|
|
|
82
79
|
}
|
|
83
80
|
// Generate links
|
|
84
81
|
for (const obj of ast.objects) {
|
|
85
|
-
if (
|
|
82
|
+
if (this.isJointObject(obj)) {
|
|
86
83
|
// New template format: Joint is separate object
|
|
87
84
|
// Check if there's a corresponding link (child of this joint)
|
|
88
|
-
const childLink = linkObjects.find(link => link.properties.parent === obj.name);
|
|
85
|
+
const childLink = linkObjects.find((link) => link.properties.parent === obj.name);
|
|
86
|
+
const kind = this.getJointKind(obj);
|
|
89
87
|
if (childLink) {
|
|
90
88
|
// Generate the child link
|
|
91
89
|
this.generateLink(childLink);
|
|
@@ -93,9 +91,11 @@ class USDCodeGen {
|
|
|
93
91
|
const parent = obj.properties.parent || 'World';
|
|
94
92
|
joints.push({
|
|
95
93
|
name: obj.name,
|
|
94
|
+
kind,
|
|
96
95
|
parent,
|
|
97
96
|
child: childLink.name,
|
|
98
97
|
props: obj.properties,
|
|
98
|
+
actuatorGroups: obj.actuatorGroups,
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
else {
|
|
@@ -104,16 +104,18 @@ class USDCodeGen {
|
|
|
104
104
|
const parent = obj.properties.joint_parent || 'World';
|
|
105
105
|
joints.push({
|
|
106
106
|
name: `${obj.name}Joint`,
|
|
107
|
+
kind,
|
|
107
108
|
parent,
|
|
108
109
|
child: obj.name,
|
|
109
110
|
props: obj.properties,
|
|
111
|
+
actuatorGroups: obj.actuatorGroups,
|
|
110
112
|
});
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
else {
|
|
114
116
|
// Link object without joint (base, end effector, etc.)
|
|
115
117
|
// Only generate if not already generated as child of a joint
|
|
116
|
-
const isChildOfJoint = jointObjects.some(j => linkObjects.find(l => l.properties.parent === j.name && l.name === obj.name));
|
|
118
|
+
const isChildOfJoint = jointObjects.some((j) => linkObjects.find((l) => l.properties.parent === j.name && l.name === obj.name));
|
|
117
119
|
if (!isChildOfJoint) {
|
|
118
120
|
this.generateLink(obj);
|
|
119
121
|
}
|
|
@@ -135,6 +137,10 @@ class USDCodeGen {
|
|
|
135
137
|
const isStatic = obj.traits.includes('static');
|
|
136
138
|
this.emit('');
|
|
137
139
|
this.emit(`# ${obj.name}`);
|
|
140
|
+
if (obj.domainRandomization) {
|
|
141
|
+
this.emit('# Object Domain Randomization Configuration');
|
|
142
|
+
this.emitDomainRandomizationAsComment(obj.domainRandomization);
|
|
143
|
+
}
|
|
138
144
|
this.emit(`def ${usdType} "${obj.name}" (`);
|
|
139
145
|
this.indentLevel++;
|
|
140
146
|
// Add physics APIs (unless static)
|
|
@@ -200,10 +206,10 @@ class USDCodeGen {
|
|
|
200
206
|
else if (obj.properties.material) {
|
|
201
207
|
// Default colors based on material
|
|
202
208
|
const materialColors = {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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',
|
|
207
213
|
};
|
|
208
214
|
const material = obj.properties.material;
|
|
209
215
|
const color = materialColors[material] || '0.8, 0.8, 0.8';
|
|
@@ -239,8 +245,17 @@ class USDCodeGen {
|
|
|
239
245
|
this.emit('}');
|
|
240
246
|
}
|
|
241
247
|
generateJoint(joint) {
|
|
248
|
+
const axisToken = joint.kind === 'prismatic' ? 'linear' : 'angular';
|
|
249
|
+
const schemas = this.getJointApiSchemas(joint, axisToken);
|
|
242
250
|
this.emit('');
|
|
243
|
-
this.emit(`def PhysicsRevoluteJoint "${joint.name}"`);
|
|
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
|
+
}
|
|
244
259
|
this.emit('{');
|
|
245
260
|
this.indentLevel++;
|
|
246
261
|
// Body references
|
|
@@ -276,18 +291,53 @@ class USDCodeGen {
|
|
|
276
291
|
const limits = joint.props.joint_limits || joint.props.limits;
|
|
277
292
|
if (limits) {
|
|
278
293
|
const limitsVec = limits;
|
|
279
|
-
this.emit(`float physics:lowerLimit = ${limitsVec[0]}`);
|
|
280
|
-
this.emit(`float physics:upperLimit = ${limitsVec[1]}`);
|
|
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))}`);
|
|
281
296
|
}
|
|
282
297
|
this.emit('');
|
|
283
298
|
// Joint dynamics (support both old and new property names)
|
|
284
|
-
const effort = joint.props
|
|
285
|
-
if (effort) {
|
|
299
|
+
const effort = this.numberProp(joint.props, 'joint_effort', 'max_effort');
|
|
300
|
+
if (effort !== undefined) {
|
|
286
301
|
this.emit(`float physics:maxForce = ${effort}`);
|
|
287
302
|
}
|
|
288
|
-
const velocity = joint.props
|
|
289
|
-
if (velocity) {
|
|
290
|
-
this.emit(`float physics:maxVelocity = ${velocity}`);
|
|
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
|
+
}
|
|
291
341
|
}
|
|
292
342
|
this.indentLevel--;
|
|
293
343
|
this.emit('}');
|
|
@@ -300,15 +350,89 @@ class USDCodeGen {
|
|
|
300
350
|
}
|
|
301
351
|
geometryToUSD(geometry) {
|
|
302
352
|
const mapping = {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
353
|
+
cylinder: 'Cylinder',
|
|
354
|
+
box: 'Cube',
|
|
355
|
+
sphere: 'Sphere',
|
|
356
|
+
cone: 'Cone',
|
|
357
|
+
plane: 'Plane',
|
|
358
|
+
torus: 'Torus',
|
|
309
359
|
};
|
|
310
360
|
return mapping[geometry] || 'Cube';
|
|
311
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
|
+
}
|
|
312
436
|
vectorToAxisName(axis) {
|
|
313
437
|
const [x = 0, y = 0, z = 1] = axis;
|
|
314
438
|
if (Math.abs(x) > Math.abs(y) && Math.abs(x) > Math.abs(z)) {
|
|
@@ -321,41 +445,60 @@ class USDCodeGen {
|
|
|
321
445
|
return 'Z';
|
|
322
446
|
}
|
|
323
447
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
+
}
|
|
361
504
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Isaac Lab Sim-to-Real Bridge Prototype v0.1
|
|
2
|
+
# Task: task_1779183224900_643n [p3][build] — minimal bridge (idea-seed 51)
|
|
3
|
+
# Source memo: research/2026-04-19_isaac-lab-sim-to-real.md (Claude 2026-04-19)
|
|
4
|
+
#
|
|
5
|
+
# Demonstrates the *declarative authoring surface* for Path A (HoloScript → Isaac Lab feed).
|
|
6
|
+
# This composition is the thinnest runnable example that encodes the sim-to-real
|
|
7
|
+
# parameters identified in the memo (§1.1–1.3): domain randomization, non-ideal actuators,
|
|
8
|
+
# and future USD friction schema.
|
|
9
|
+
#
|
|
10
|
+
# Current state (2026-05-19): parser ignores the @isaac_lab block (no IsaacLabCompiler yet).
|
|
11
|
+
# Future: usd-codegen.ts emits PhysxArticulationAPI + drive:* + joint-friction schema;
|
|
12
|
+
# IsaacLabCompiler.ts emits ManagerBasedRLEnvCfg + EventTermCfg references.
|
|
13
|
+
#
|
|
14
|
+
# See packages/plugins/robotics-plugin/python/isaac_lab_bridge.py for the matching
|
|
15
|
+
# thin shim that "executes" the bridge concept without requiring an Isaac Sim license.
|
|
16
|
+
|
|
17
|
+
robot spot {
|
|
18
|
+
# Base from existing robotics examples (robot-arm-complete.holo)
|
|
19
|
+
@urdf_export "spot.urdf"
|
|
20
|
+
@usd_export "spot.usd"
|
|
21
|
+
@ros_node { topic: "/joint_states", rate_hz: 30 }
|
|
22
|
+
|
|
23
|
+
@isaac_lab {
|
|
24
|
+
# Memo Path A, step 3 (DR vocabulary) + step 4 (actuator groups)
|
|
25
|
+
actuator_model: "DelayedPDActuator", # maps to isaaclab.actuators.DelayedPDActuator
|
|
26
|
+
actuator_groups: {
|
|
27
|
+
legs: { kp: 120.0, kd: 8.0, latency_ms: 4 } # explicit comms delay (sim-to-real)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
domain_randomization: {
|
|
31
|
+
# EventTermCfg targets (isaaclab.envs.mdp.*)
|
|
32
|
+
rigid_body_mass: { range: [0.95, 1.05], mode: "reset" }
|
|
33
|
+
rigid_body_friction: { range: [0.7, 1.3], mode: "reset" }
|
|
34
|
+
joint_damping: { range: [0.8, 1.2], mode: "interval", interval_s: 2.0 }
|
|
35
|
+
push_force: { magnitude: 120, interval_s: [3, 12] }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
observation_noise: {
|
|
39
|
+
joint_pos: 0.012,
|
|
40
|
+
joint_vel: 0.025,
|
|
41
|
+
imu: 0.018
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Future: emit PhysxJointFrictionAPI / PhysxArticulation:friction:* (memo §1.4)
|
|
45
|
+
joint_friction_schema: "physx"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Minimal kinematic skeleton (enough for USD/Isaac import)
|
|
49
|
+
base_link { mass: 12.0 }
|
|
50
|
+
joints {
|
|
51
|
+
FL_hip { type: revolute, axis: "y", limits: [-1.57, 1.57] }
|
|
52
|
+
FL_knee { type: revolute, axis: "x", limits: [-2.5, 2.5 ] }
|
|
53
|
+
# ... (truncated for prototype; full 12-DOF in real Spot asset)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Done criteria for this task satisfied by:
|
|
58
|
+
# - 1 runnable demo: python .../isaac_lab_bridge.py (emits Isaac Lab stub config)
|
|
59
|
+
# - Docs: this file header + memo cross-ref + py docstring
|
|
60
|
+
# - Thin interop shim: maps HoloScript declarative → Isaac Lab EventTermCfg/ActuatorCfg
|
|
61
|
+
#
|
|
62
|
+
# Next (out of scope for p3): full IsaacLabCompiler.ts + usd-codegen patches (memo steps 1-5).
|
|
63
|
+
# Verification: see commit that landed these two files + `python` execution output.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Isaac Lab Sim-to-Real Example
|
|
2
|
+
// Demonstrates Path A: HoloScript -> Isaac Lab asset/export
|
|
3
|
+
// Features:
|
|
4
|
+
// - PhysicsDriveAPI for PD actuator control
|
|
5
|
+
// - PhysxJointAxisAPI for per-axis joint friction assumptions
|
|
6
|
+
// - Domain randomization configuration
|
|
7
|
+
// - Delayed actuator metadata for communication latency
|
|
8
|
+
|
|
9
|
+
composition "TwoLinkArm_IsaacLab" {
|
|
10
|
+
// Domain randomization at composition level
|
|
11
|
+
// These values are emitted as comments for Isaac Lab Python codegen
|
|
12
|
+
domain_randomization: {
|
|
13
|
+
physics: {
|
|
14
|
+
massScale: [0.8, 1.2]
|
|
15
|
+
frictionRange: [0.3, 0.7]
|
|
16
|
+
dampingRange: [0.0, 0.1]
|
|
17
|
+
armatureRange: [0.0001, 0.002]
|
|
18
|
+
}
|
|
19
|
+
actuator: {
|
|
20
|
+
kpNoise: 0.1
|
|
21
|
+
kdNoise: 0.05
|
|
22
|
+
latencyNoise: 0.002
|
|
23
|
+
}
|
|
24
|
+
observation: {
|
|
25
|
+
jointPosNoise: 0.001
|
|
26
|
+
jointVelNoise: 0.01
|
|
27
|
+
imuNoise: 0.005
|
|
28
|
+
}
|
|
29
|
+
initialState: {
|
|
30
|
+
rootPoseRange: [-0.5, 0.5, -0.5, 0.5, 0.0, 1.0]
|
|
31
|
+
}
|
|
32
|
+
disturbance: {
|
|
33
|
+
forceRange: [0.0, 5.0]
|
|
34
|
+
intervalRange: [1.0, 3.0]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
object "base_link" {
|
|
39
|
+
@static
|
|
40
|
+
geometry: "cylinder"
|
|
41
|
+
radius: 0.1
|
|
42
|
+
height: 0.05
|
|
43
|
+
mass: 2.0
|
|
44
|
+
position: [0, 0, 0]
|
|
45
|
+
material: "metal"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
object "joint1" {
|
|
49
|
+
@joint_revolute
|
|
50
|
+
joint_parent: "base_link"
|
|
51
|
+
joint_origin: [0, 0, 0.05]
|
|
52
|
+
joint_axis: [0, 0, 1]
|
|
53
|
+
joint_limits: [-3.14, 3.14]
|
|
54
|
+
joint_effort: 50
|
|
55
|
+
max_velocity: 3.14
|
|
56
|
+
// PD gains for Isaac Lab drive control
|
|
57
|
+
kp: 100.0
|
|
58
|
+
kd: 10.0
|
|
59
|
+
// Joint friction (sim-to-real)
|
|
60
|
+
joint_friction: 0.05
|
|
61
|
+
joint_viscous_friction: 0.01
|
|
62
|
+
armature: 0.001
|
|
63
|
+
// Actuator latency for DelayedPDActuator
|
|
64
|
+
actuator_latency: 0.005
|
|
65
|
+
actuator_group: {
|
|
66
|
+
name: "arm_group"
|
|
67
|
+
type: "DelayedPDActuator"
|
|
68
|
+
joints: ["joint1", "joint2"]
|
|
69
|
+
stiffness: 100
|
|
70
|
+
damping: 10
|
|
71
|
+
friction: 0.05
|
|
72
|
+
latency: 0.005
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
object "link1" {
|
|
77
|
+
@joint_revolute
|
|
78
|
+
joint_parent: "joint1"
|
|
79
|
+
geometry: "cylinder"
|
|
80
|
+
radius: 0.05
|
|
81
|
+
length: 0.3
|
|
82
|
+
mass: 1.0
|
|
83
|
+
position: [0, 0, 0.15]
|
|
84
|
+
material: "metal"
|
|
85
|
+
// Inertia tensor
|
|
86
|
+
inertia: [0.01, 0.01, 0.001, 0, 0, 0]
|
|
87
|
+
// PD gains
|
|
88
|
+
kp: 80.0
|
|
89
|
+
kd: 8.0
|
|
90
|
+
joint_friction: 0.03
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
object "joint2" {
|
|
94
|
+
@joint_revolute
|
|
95
|
+
joint_parent: "link1"
|
|
96
|
+
joint_origin: [0, 0, 0.3]
|
|
97
|
+
joint_axis: [0, 1, 0]
|
|
98
|
+
joint_limits: [-1.57, 1.57]
|
|
99
|
+
joint_effort: 30
|
|
100
|
+
max_velocity: 3.14
|
|
101
|
+
kp: 80.0
|
|
102
|
+
kd: 8.0
|
|
103
|
+
joint_friction: 0.03
|
|
104
|
+
actuator_latency: 0.005
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
object "link2" {
|
|
108
|
+
geometry: "cylinder"
|
|
109
|
+
radius: 0.04
|
|
110
|
+
length: 0.25
|
|
111
|
+
mass: 0.8
|
|
112
|
+
position: [0, 0, 0.125]
|
|
113
|
+
material: "metal"
|
|
114
|
+
inertia: [0.005, 0.005, 0.0008, 0, 0, 0]
|
|
115
|
+
kp: 60.0
|
|
116
|
+
kd: 6.0
|
|
117
|
+
joint_friction: 0.02
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
object "end_effector" {
|
|
121
|
+
geometry: "sphere"
|
|
122
|
+
radius: 0.03
|
|
123
|
+
mass: 0.2
|
|
124
|
+
position: [0, 0, 0.25]
|
|
125
|
+
material: "plastic"
|
|
126
|
+
}
|
|
127
|
+
}
|