@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/index.ts
DELETED
|
@@ -1,553 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @holoscript/robotics-plugin v1.0.0
|
|
3
|
-
* Complete robotics solution: Compile-time (USD/URDF) + Runtime (ROS2/Gazebo)
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - USD codegen for NVIDIA Isaac Sim (from holoscript-compiler)
|
|
7
|
-
* - Isaac Lab sim-to-real interop (Path A)
|
|
8
|
-
* - PhysicsDriveAPI for PD actuator control
|
|
9
|
-
* - PhysxJointAxisAPI for per-axis friction assumptions
|
|
10
|
-
* - Domain randomization configuration blocks
|
|
11
|
-
* - URDF/SDF export for ROS2/Gazebo
|
|
12
|
-
* - ROS2 runtime integration (roslibjs)
|
|
13
|
-
* - VR teleoperation and digital twins
|
|
14
|
-
*
|
|
15
|
-
* @packageDocumentation
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
// Compile-time: USD/URDF codegen (from holoscript-compiler)
|
|
19
|
-
export { USDCodeGen } from './usd-codegen';
|
|
20
|
-
export type { IsaacLabConfig } from './usd-codegen';
|
|
21
|
-
export { Lexer, TokenType } from './lexer';
|
|
22
|
-
export type { Token } from './lexer';
|
|
23
|
-
export { Parser } from './parser';
|
|
24
|
-
export * from './ast';
|
|
25
|
-
// Isaac Lab sim-to-real types
|
|
26
|
-
export type { DomainRandomizationConfig, ActuatorGroupConfig } from './ast';
|
|
27
|
-
|
|
28
|
-
// Internal imports for the narrow feed implementation (P1)
|
|
29
|
-
import type { CompositionNode, DomainRandomizationConfig, ActuatorGroupConfig } from './ast';
|
|
30
|
-
import { USDCodeGen } from './usd-codegen';
|
|
31
|
-
|
|
32
|
-
// Runtime: ROS2/Gazebo integration
|
|
33
|
-
export interface ROS2Config {
|
|
34
|
-
ros_bridge_url: string; // ws://localhost:9090
|
|
35
|
-
namespace?: string; // /my_robot
|
|
36
|
-
tf_prefix?: string; // robot_
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface GazeboConfig {
|
|
40
|
-
world_file?: string; // path/to/world.world
|
|
41
|
-
model_sdf?: string; // path/to/model.sdf
|
|
42
|
-
spawn_position?: [number, number, number];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface RobotJoint {
|
|
46
|
-
name: string;
|
|
47
|
-
type: 'revolute' | 'prismatic' | 'fixed' | 'continuous';
|
|
48
|
-
parent_link: string;
|
|
49
|
-
child_link: string;
|
|
50
|
-
axis?: [number, number, number];
|
|
51
|
-
limits?: { lower: number; upper: number; effort: number; velocity: number };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface RobotLink {
|
|
55
|
-
name: string;
|
|
56
|
-
inertial?: {
|
|
57
|
-
mass: number;
|
|
58
|
-
inertia: number[]; // [ixx, iyy, izz, ixy, ixz, iyz]
|
|
59
|
-
};
|
|
60
|
-
visual?: {
|
|
61
|
-
geometry: string;
|
|
62
|
-
material?: string;
|
|
63
|
-
};
|
|
64
|
-
collision?: {
|
|
65
|
-
geometry: string;
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface RobotTransmission {
|
|
70
|
-
name: string;
|
|
71
|
-
joint: string;
|
|
72
|
-
actuator: string;
|
|
73
|
-
mechanicalReduction: number;
|
|
74
|
-
hardwareInterface: 'position' | 'velocity' | 'effort';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface HoloTraitLike {
|
|
78
|
-
name: string;
|
|
79
|
-
properties?: Record<string, unknown>;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface HoloCompositionNodeLike {
|
|
83
|
-
id?: string;
|
|
84
|
-
name: string;
|
|
85
|
-
type?: string;
|
|
86
|
-
properties?: Record<string, unknown>;
|
|
87
|
-
traits?: HoloTraitLike[];
|
|
88
|
-
children?: HoloCompositionNodeLike[];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface HoloCompositionTreeLike {
|
|
92
|
-
name: string;
|
|
93
|
-
children?: HoloCompositionNodeLike[];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface ExtractedRobotModel {
|
|
97
|
-
name: string;
|
|
98
|
-
links: RobotLink[];
|
|
99
|
-
joints: RobotJoint[];
|
|
100
|
-
transmissions: RobotTransmission[];
|
|
101
|
-
xml: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface URDFExtractionOptions {
|
|
105
|
-
defaultJointType?: RobotJoint['type'];
|
|
106
|
-
defaultAxis?: [number, number, number];
|
|
107
|
-
baseLinkName?: string;
|
|
108
|
-
hardwareScale?: number;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
type TraitMatch = {
|
|
112
|
-
normalizedName: string;
|
|
113
|
-
trait: HoloTraitLike;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const DEFAULT_URDF_AXIS: [number, number, number] = [0, 0, 1];
|
|
117
|
-
|
|
118
|
-
function normalizeTraitName(name: string): string {
|
|
119
|
-
return name.replace(/^@/, '').trim().toLowerCase();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function sanitizeRobotName(name: string): string {
|
|
123
|
-
return name.trim().replace(/[^A-Za-z0-9_]+/g, '_') || 'robot';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function xmlEscape(value: string): string {
|
|
127
|
-
return value
|
|
128
|
-
.replace(/&/g, '&')
|
|
129
|
-
.replace(/</g, '<')
|
|
130
|
-
.replace(/>/g, '>')
|
|
131
|
-
.replace(/"/g, '"')
|
|
132
|
-
.replace(/'/g, ''');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function asRecord(value: unknown): Record<string, unknown> {
|
|
136
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
137
|
-
? (value as Record<string, unknown>)
|
|
138
|
-
: {};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function asNumber(value: unknown, fallback: number): number {
|
|
142
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function asString(value: unknown, fallback: string): string {
|
|
146
|
-
return typeof value === 'string' && value.trim().length > 0 ? value : fallback;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function asVec3(value: unknown, fallback: [number, number, number]): [number, number, number] {
|
|
150
|
-
if (Array.isArray(value) && value.length >= 3) {
|
|
151
|
-
return [asNumber(value[0], fallback[0]), asNumber(value[1], fallback[1]), asNumber(value[2], fallback[2])];
|
|
152
|
-
}
|
|
153
|
-
return fallback;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function asJointLimits(
|
|
157
|
-
value: unknown,
|
|
158
|
-
fallback: RobotJoint['limits']
|
|
159
|
-
): RobotJoint['limits'] {
|
|
160
|
-
if (Array.isArray(value) && value.length >= 4) {
|
|
161
|
-
return {
|
|
162
|
-
lower: asNumber(value[0], fallback?.lower ?? -1.57),
|
|
163
|
-
upper: asNumber(value[1], fallback?.upper ?? 1.57),
|
|
164
|
-
effort: asNumber(value[2], fallback?.effort ?? 10),
|
|
165
|
-
velocity: asNumber(value[3], fallback?.velocity ?? 1),
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const record = asRecord(value);
|
|
170
|
-
if (Object.keys(record).length > 0) {
|
|
171
|
-
return {
|
|
172
|
-
lower: asNumber(record.lower, fallback?.lower ?? -1.57),
|
|
173
|
-
upper: asNumber(record.upper, fallback?.upper ?? 1.57),
|
|
174
|
-
effort: asNumber(record.effort, fallback?.effort ?? 10),
|
|
175
|
-
velocity: asNumber(record.velocity, fallback?.velocity ?? 1),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return fallback;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function findTrait(node: HoloCompositionNodeLike, predicate: (normalizedName: string) => boolean): TraitMatch | null {
|
|
183
|
-
for (const trait of node.traits ?? []) {
|
|
184
|
-
const normalizedName = normalizeTraitName(trait.name);
|
|
185
|
-
if (predicate(normalizedName)) {
|
|
186
|
-
return { normalizedName, trait };
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function getJointTrait(node: HoloCompositionNodeLike): TraitMatch | null {
|
|
194
|
-
return findTrait(
|
|
195
|
-
node,
|
|
196
|
-
(name) =>
|
|
197
|
-
name === 'joint' ||
|
|
198
|
-
name === 'hinge' ||
|
|
199
|
-
name === 'slider' ||
|
|
200
|
-
name.startsWith('joint_')
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function getMotorTrait(node: HoloCompositionNodeLike): TraitMatch | null {
|
|
205
|
-
return findTrait(
|
|
206
|
-
node,
|
|
207
|
-
(name) => name === 'motor' || name === 'servo' || name === 'actuator' || name.endsWith('_motor')
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function jointTypeFromTrait(
|
|
212
|
-
jointTrait: TraitMatch | null,
|
|
213
|
-
nodeProps: Record<string, unknown>,
|
|
214
|
-
defaultJointType: RobotJoint['type']
|
|
215
|
-
): RobotJoint['type'] {
|
|
216
|
-
const explicitType = normalizeTraitName(asString(nodeProps.joint_type ?? nodeProps.type, defaultJointType));
|
|
217
|
-
|
|
218
|
-
switch (explicitType) {
|
|
219
|
-
case 'revolute':
|
|
220
|
-
case 'prismatic':
|
|
221
|
-
case 'fixed':
|
|
222
|
-
case 'continuous':
|
|
223
|
-
return explicitType;
|
|
224
|
-
case 'hinge':
|
|
225
|
-
return 'revolute';
|
|
226
|
-
case 'slider':
|
|
227
|
-
return 'prismatic';
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
switch (jointTrait?.normalizedName) {
|
|
231
|
-
case 'joint_revolute':
|
|
232
|
-
case 'hinge':
|
|
233
|
-
return 'revolute';
|
|
234
|
-
case 'joint_prismatic':
|
|
235
|
-
case 'slider':
|
|
236
|
-
return 'prismatic';
|
|
237
|
-
case 'joint_continuous':
|
|
238
|
-
return 'continuous';
|
|
239
|
-
case 'joint_fixed':
|
|
240
|
-
return 'fixed';
|
|
241
|
-
default:
|
|
242
|
-
return defaultJointType;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function hardwareInterfaceFromMotor(
|
|
247
|
-
motorProps: Record<string, unknown>
|
|
248
|
-
): RobotTransmission['hardwareInterface'] {
|
|
249
|
-
const commandMode = normalizeTraitName(asString(motorProps.commandMode ?? motorProps.mode, 'position'));
|
|
250
|
-
|
|
251
|
-
if (commandMode === 'velocity') return 'velocity';
|
|
252
|
-
if (commandMode === 'effort' || commandMode === 'torque') return 'effort';
|
|
253
|
-
return 'position';
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function scaleMass(mass: number, hardwareScale: number): number {
|
|
257
|
-
return Number((mass * Math.pow(hardwareScale, 3)).toFixed(6));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function mapNodeToRobotLink(node: HoloCompositionNodeLike, hardwareScale: number): RobotLink {
|
|
261
|
-
const props = asRecord(node.properties);
|
|
262
|
-
const baseMass = asNumber(props.mass, 1);
|
|
263
|
-
const inertia = Array.isArray(props.inertia)
|
|
264
|
-
? props.inertia.map((value) => asNumber(value, 0))
|
|
265
|
-
: [0.01, 0.01, 0.01, 0, 0, 0];
|
|
266
|
-
const geometry = asString(props.geometry, node.type ?? 'box');
|
|
267
|
-
const material = typeof props.material === 'string' ? props.material : undefined;
|
|
268
|
-
const collisionGeometry = asString(props.collisionGeometry ?? props.geometry, geometry);
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
name: sanitizeRobotName(node.name || node.id || 'link'),
|
|
272
|
-
inertial: {
|
|
273
|
-
mass: scaleMass(baseMass, hardwareScale),
|
|
274
|
-
inertia,
|
|
275
|
-
},
|
|
276
|
-
visual: {
|
|
277
|
-
geometry,
|
|
278
|
-
material,
|
|
279
|
-
},
|
|
280
|
-
collision: {
|
|
281
|
-
geometry: collisionGeometry,
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function createTransmission(
|
|
287
|
-
jointName: string,
|
|
288
|
-
motorProps: Record<string, unknown>,
|
|
289
|
-
hardwareScale: number
|
|
290
|
-
): RobotTransmission {
|
|
291
|
-
return {
|
|
292
|
-
name: sanitizeRobotName(asString(motorProps.name, `${jointName}_transmission`)),
|
|
293
|
-
joint: jointName,
|
|
294
|
-
actuator: sanitizeRobotName(asString(motorProps.actuator ?? motorProps.name, `${jointName}_motor`)),
|
|
295
|
-
mechanicalReduction: Number((asNumber(motorProps.mechanicalReduction ?? motorProps.gearRatio, 1) * hardwareScale).toFixed(6)),
|
|
296
|
-
hardwareInterface: hardwareInterfaceFromMotor(motorProps),
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function buildLinkXml(link: RobotLink): string {
|
|
301
|
-
const lines = [` <link name="${xmlEscape(link.name)}">`];
|
|
302
|
-
|
|
303
|
-
if (link.inertial) {
|
|
304
|
-
const [ixx = 0.01, iyy = 0.01, izz = 0.01, ixy = 0, ixz = 0, iyz = 0] = link.inertial.inertia;
|
|
305
|
-
lines.push(' <inertial>');
|
|
306
|
-
lines.push(` <mass value="${link.inertial.mass}"/>`);
|
|
307
|
-
lines.push(
|
|
308
|
-
` <inertia ixx="${ixx}" iyy="${iyy}" izz="${izz}" ixy="${ixy}" ixz="${ixz}" iyz="${iyz}"/>`
|
|
309
|
-
);
|
|
310
|
-
lines.push(' </inertial>');
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (link.visual) {
|
|
314
|
-
lines.push(' <visual>');
|
|
315
|
-
lines.push(` <geometry><mesh filename="${xmlEscape(link.visual.geometry)}"/></geometry>`);
|
|
316
|
-
if (link.visual.material) {
|
|
317
|
-
lines.push(` <material name="${xmlEscape(link.visual.material)}"/>`);
|
|
318
|
-
}
|
|
319
|
-
lines.push(' </visual>');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (link.collision) {
|
|
323
|
-
lines.push(' <collision>');
|
|
324
|
-
lines.push(` <geometry><mesh filename="${xmlEscape(link.collision.geometry)}"/></geometry>`);
|
|
325
|
-
lines.push(' </collision>');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
lines.push(' </link>');
|
|
329
|
-
return lines.join('\n');
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function buildJointXml(joint: RobotJoint): string {
|
|
333
|
-
const lines = [` <joint name="${xmlEscape(joint.name)}" type="${joint.type}">`];
|
|
334
|
-
lines.push(` <parent link="${xmlEscape(joint.parent_link)}"/>`);
|
|
335
|
-
lines.push(` <child link="${xmlEscape(joint.child_link)}"/>`);
|
|
336
|
-
|
|
337
|
-
if (joint.axis) {
|
|
338
|
-
lines.push(` <axis xyz="${joint.axis.join(' ')}"/>`);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (joint.limits && joint.type !== 'fixed') {
|
|
342
|
-
lines.push(
|
|
343
|
-
` <limit lower="${joint.limits.lower}" upper="${joint.limits.upper}" effort="${joint.limits.effort}" velocity="${joint.limits.velocity}"/>`
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
lines.push(' </joint>');
|
|
348
|
-
return lines.join('\n');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function buildTransmissionXml(transmission: RobotTransmission): string {
|
|
352
|
-
return [
|
|
353
|
-
` <transmission name="${xmlEscape(transmission.name)}">`,
|
|
354
|
-
' <type>transmission_interface/SimpleTransmission</type>',
|
|
355
|
-
` <joint name="${xmlEscape(transmission.joint)}">`,
|
|
356
|
-
` <hardwareInterface>${transmission.hardwareInterface}</hardwareInterface>`,
|
|
357
|
-
' </joint>',
|
|
358
|
-
` <actuator name="${xmlEscape(transmission.actuator)}">`,
|
|
359
|
-
' <hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>',
|
|
360
|
-
` <mechanicalReduction>${transmission.mechanicalReduction}</mechanicalReduction>`,
|
|
361
|
-
' </actuator>',
|
|
362
|
-
' </transmission>',
|
|
363
|
-
].join('\n');
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export function buildURDFXML(model: Omit<ExtractedRobotModel, 'xml'>): string {
|
|
367
|
-
const lines = [`<robot name="${xmlEscape(model.name)}">`];
|
|
368
|
-
lines.push(...model.links.map(buildLinkXml));
|
|
369
|
-
lines.push(...model.joints.map(buildJointXml));
|
|
370
|
-
lines.push(...model.transmissions.map(buildTransmissionXml));
|
|
371
|
-
lines.push('</robot>');
|
|
372
|
-
return lines.join('\n');
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export function extractURDFFromHoloComposition(
|
|
376
|
-
composition: HoloCompositionTreeLike,
|
|
377
|
-
options: URDFExtractionOptions = {}
|
|
378
|
-
): ExtractedRobotModel {
|
|
379
|
-
const defaultJointType = options.defaultJointType ?? 'fixed';
|
|
380
|
-
const defaultAxis = options.defaultAxis ?? DEFAULT_URDF_AXIS;
|
|
381
|
-
const hardwareScale = options.hardwareScale ?? 1;
|
|
382
|
-
const links: RobotLink[] = [];
|
|
383
|
-
const joints: RobotJoint[] = [];
|
|
384
|
-
const transmissions: RobotTransmission[] = [];
|
|
385
|
-
|
|
386
|
-
const visitNode = (
|
|
387
|
-
node: HoloCompositionNodeLike,
|
|
388
|
-
parentLinkName: string | null
|
|
389
|
-
) => {
|
|
390
|
-
const props = asRecord(node.properties);
|
|
391
|
-
const link = mapNodeToRobotLink(node, hardwareScale);
|
|
392
|
-
links.push(link);
|
|
393
|
-
|
|
394
|
-
const jointTrait = getJointTrait(node);
|
|
395
|
-
const motorTrait = getMotorTrait(node);
|
|
396
|
-
const jointName = sanitizeRobotName(asString(props.jointName, `${link.name}_joint`));
|
|
397
|
-
|
|
398
|
-
if (parentLinkName && jointTrait) {
|
|
399
|
-
const limits = asJointLimits(props.joint_limits ?? props.limits, {
|
|
400
|
-
lower: -1.57,
|
|
401
|
-
upper: 1.57,
|
|
402
|
-
effort: 10 * hardwareScale,
|
|
403
|
-
velocity: 1 * hardwareScale,
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const joint: RobotJoint = {
|
|
407
|
-
name: jointName,
|
|
408
|
-
type: jointTypeFromTrait(jointTrait, props, defaultJointType),
|
|
409
|
-
parent_link: sanitizeRobotName(parentLinkName),
|
|
410
|
-
child_link: link.name,
|
|
411
|
-
axis: asVec3(props.joint_axis ?? props.axis, defaultAxis),
|
|
412
|
-
limits,
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
joints.push(joint);
|
|
416
|
-
|
|
417
|
-
if (motorTrait) {
|
|
418
|
-
const motorProps = {
|
|
419
|
-
...props,
|
|
420
|
-
...asRecord(motorTrait.trait.properties),
|
|
421
|
-
};
|
|
422
|
-
transmissions.push(createTransmission(joint.name, motorProps, hardwareScale));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
for (const child of node.children ?? []) {
|
|
427
|
-
visitNode(child, link.name);
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
const baseLinkName = sanitizeRobotName(options.baseLinkName ?? `${composition.name}_base`);
|
|
432
|
-
links.push({
|
|
433
|
-
name: baseLinkName,
|
|
434
|
-
inertial: {
|
|
435
|
-
mass: scaleMass(1, hardwareScale),
|
|
436
|
-
inertia: [0.01, 0.01, 0.01, 0, 0, 0],
|
|
437
|
-
},
|
|
438
|
-
visual: {
|
|
439
|
-
geometry: 'base_link',
|
|
440
|
-
},
|
|
441
|
-
collision: {
|
|
442
|
-
geometry: 'base_link',
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
for (const child of composition.children ?? []) {
|
|
447
|
-
visitNode(child, baseLinkName);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const modelWithoutXml: Omit<ExtractedRobotModel, 'xml'> = {
|
|
451
|
-
name: sanitizeRobotName(composition.name),
|
|
452
|
-
links,
|
|
453
|
-
joints,
|
|
454
|
-
transmissions,
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
return {
|
|
458
|
-
...modelWithoutXml,
|
|
459
|
-
xml: buildURDFXML(modelWithoutXml),
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Traits
|
|
464
|
-
export * from './traits/ROS2HardwareLoopTrait';
|
|
465
|
-
|
|
466
|
-
// Version
|
|
467
|
-
export const VERSION = '1.0.0';
|
|
468
|
-
|
|
469
|
-
// Re-export for convenience
|
|
470
|
-
export default {
|
|
471
|
-
VERSION,
|
|
472
|
-
buildURDFXML,
|
|
473
|
-
extractURDFFromHoloComposition,
|
|
474
|
-
generateIsaacLabFeed,
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
// ---------------------------------------------------------------------------
|
|
478
|
-
// Narrow sim-to-real "feed Isaac Lab" path (P1 scoped deliverable)
|
|
479
|
-
// task_1779180755794_1iil — item 9 from impossible-doors breakthrough analysis
|
|
480
|
-
// Produces a minimal, auditable bundle that Isaac Lab can consume directly
|
|
481
|
-
// (USD + physics metadata + domain randomization + actuator config + task stub)
|
|
482
|
-
// with a receipt for evidence tracking. Does NOT claim full policy round-trip.
|
|
483
|
-
// ---------------------------------------------------------------------------
|
|
484
|
-
|
|
485
|
-
export interface IsaacLabFeedReceipt {
|
|
486
|
-
sourceHoloHash: string;
|
|
487
|
-
usdHash: string;
|
|
488
|
-
configHash: string;
|
|
489
|
-
generatedAt: string;
|
|
490
|
-
isaacLabVersion: string;
|
|
491
|
-
readyForTraining: boolean;
|
|
492
|
-
notes: string;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
export interface IsaacLabFeedBundle {
|
|
496
|
-
usd: string;
|
|
497
|
-
taskConfig: Record<string, unknown>;
|
|
498
|
-
randomization: DomainRandomizationConfig | undefined;
|
|
499
|
-
actuators: ActuatorGroupConfig[];
|
|
500
|
-
receipt: IsaacLabFeedReceipt;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
export function generateIsaacLabFeed(
|
|
504
|
-
ast: CompositionNode,
|
|
505
|
-
opts: { isaacLabVersion?: string } = {}
|
|
506
|
-
): IsaacLabFeedBundle {
|
|
507
|
-
const version = opts.isaacLabVersion || '2.3';
|
|
508
|
-
const codegen = new USDCodeGen({ isaacLabVersion: version });
|
|
509
|
-
const usd = codegen.generate(ast);
|
|
510
|
-
|
|
511
|
-
// Pull randomization + actuators that the AST already carries
|
|
512
|
-
const randomization = ast.domainRandomization;
|
|
513
|
-
const actuators: ActuatorGroupConfig[] = [];
|
|
514
|
-
for (const obj of ast.objects ?? []) {
|
|
515
|
-
if (obj.actuatorGroups) actuators.push(...obj.actuatorGroups);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Minimal Isaac Lab task config stub (observations / actions / randomization ranges)
|
|
519
|
-
const taskConfig = {
|
|
520
|
-
env: 'HoloScriptRobotEnv',
|
|
521
|
-
num_envs: 4096,
|
|
522
|
-
seed: 42,
|
|
523
|
-
observations: ['joint_pos', 'joint_vel', 'imu'],
|
|
524
|
-
actions: ['joint_torques'],
|
|
525
|
-
randomization: randomization || {},
|
|
526
|
-
actuator_groups: actuators,
|
|
527
|
-
source: 'holoscript',
|
|
528
|
-
usd_prim: ast.name,
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
// Simple content hashes for the receipt (evidence loop)
|
|
532
|
-
const sourceHoloHash = `holo:${ast.name}:${(ast.objects?.length || 0)}`;
|
|
533
|
-
const usdHash = `usd:${usd.length}`;
|
|
534
|
-
const configHash = `cfg:${JSON.stringify(taskConfig).length}`;
|
|
535
|
-
|
|
536
|
-
const receipt: IsaacLabFeedReceipt = {
|
|
537
|
-
sourceHoloHash,
|
|
538
|
-
usdHash,
|
|
539
|
-
configHash,
|
|
540
|
-
generatedAt: new Date().toISOString(),
|
|
541
|
-
isaacLabVersion: version,
|
|
542
|
-
readyForTraining: true,
|
|
543
|
-
notes: 'Narrow feed bundle — assets + randomization + actuator config. Policy training & real-robot eval are downstream.',
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
usd,
|
|
548
|
-
taskConfig,
|
|
549
|
-
randomization,
|
|
550
|
-
actuators,
|
|
551
|
-
receipt,
|
|
552
|
-
};
|
|
553
|
-
}
|