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