@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.
@@ -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 schema.
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
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
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
- // Root articulation
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('prepend apiSchemas = ["PhysicsArticulationRootAPI"]');
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 (obj.traits.includes('joint_revolute') || obj.traits.includes('joint_prismatic')) {
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 (obj.traits.includes('joint_revolute') || obj.traits.includes('joint_prismatic')) {
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
- 'metal': '0.6, 0.6, 0.65',
204
- 'plastic': '0.8, 0.8, 0.8',
205
- 'wood': '0.6, 0.4, 0.2',
206
- 'glass': '0.9, 0.9, 1.0',
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.joint_effort || joint.props.max_effort;
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.max_velocity;
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
- 'cylinder': 'Cylinder',
304
- 'box': 'Cube',
305
- 'sphere': 'Sphere',
306
- 'cone': 'Cone',
307
- 'plane': 'Plane',
308
- 'torus': 'Torus',
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
- exports.USDCodeGen = USDCodeGen;
326
- // Example usage
327
- if (require.main === module) {
328
- Promise.resolve().then(() => __importStar(require('./lexer'))).then(({ Lexer }) => {
329
- Promise.resolve().then(() => __importStar(require('./parser'))).then(({ Parser }) => {
330
- const source = `
331
- composition "TwoLinkArm" {
332
- object "Base" @static {
333
- geometry: "cylinder"
334
- dimensions: [0.1, 0.05]
335
- material: "metal"
336
- position: [0, 0, 0]
337
- }
338
-
339
- object "Link1" @joint_revolute {
340
- geometry: "box"
341
- dimensions: [0.5, 0.1, 0.1]
342
- material: "metal"
343
- joint_parent: "Base"
344
- joint_axis: [0, 0, 1]
345
- joint_limits: [-3.14, 3.14]
346
- joint_effort: 100
347
- mass: 1.0
348
- }
349
- }
350
- `;
351
- const lexer = new Lexer(source);
352
- const tokens = lexer.tokenize();
353
- const parser = new Parser(tokens);
354
- const ast = parser.parse();
355
- const codegen = new USDCodeGen();
356
- const usd = codegen.generate(ast);
357
- console.log('USD Output:');
358
- console.log(usd);
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
+ }