@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/parser.js
CHANGED
|
@@ -1,47 +1,11 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* HoloScript Parser
|
|
4
3
|
*
|
|
5
4
|
* Converts token stream into Abstract Syntax Tree (AST).
|
|
6
5
|
* Uses recursive descent parsing.
|
|
7
6
|
*/
|
|
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.Parser = void 0;
|
|
43
|
-
const lexer_1 = require("./lexer");
|
|
44
|
-
class Parser {
|
|
7
|
+
import { TokenType } from './lexer';
|
|
8
|
+
export class Parser {
|
|
45
9
|
constructor(tokens) {
|
|
46
10
|
this.position = 0;
|
|
47
11
|
this.tokens = tokens;
|
|
@@ -80,45 +44,89 @@ class Parser {
|
|
|
80
44
|
return this.advance();
|
|
81
45
|
}
|
|
82
46
|
parseComposition() {
|
|
83
|
-
const startToken = this.expect(
|
|
84
|
-
const name = this.expect(
|
|
85
|
-
this.expect(
|
|
47
|
+
const startToken = this.expect(TokenType.KEYWORD, 'composition');
|
|
48
|
+
const name = this.expect(TokenType.STRING).value;
|
|
49
|
+
this.expect(TokenType.LBRACE);
|
|
86
50
|
const objects = [];
|
|
87
|
-
|
|
88
|
-
|
|
51
|
+
let domainRandomization;
|
|
52
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
53
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
54
|
+
// Check for domain_randomization block at composition level
|
|
55
|
+
if (this.currentToken().type === TokenType.IDENTIFIER &&
|
|
56
|
+
this.currentToken().value === 'domain_randomization') {
|
|
57
|
+
this.advance();
|
|
58
|
+
this.expect(TokenType.COLON);
|
|
59
|
+
domainRandomization = this.parseDomainRandomizationBlock();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
objects.push(this.parseObject());
|
|
63
|
+
}
|
|
89
64
|
}
|
|
90
|
-
this.expect(
|
|
65
|
+
this.expect(TokenType.RBRACE);
|
|
91
66
|
return {
|
|
92
67
|
type: 'composition',
|
|
93
68
|
name,
|
|
94
69
|
objects,
|
|
70
|
+
domainRandomization,
|
|
95
71
|
line: startToken.line,
|
|
96
72
|
column: startToken.column,
|
|
97
73
|
};
|
|
98
74
|
}
|
|
99
75
|
parseObject() {
|
|
100
|
-
const startToken = this.expect(
|
|
101
|
-
const name = this.expect(
|
|
76
|
+
const startToken = this.expect(TokenType.KEYWORD, 'object');
|
|
77
|
+
const name = this.expect(TokenType.STRING).value;
|
|
102
78
|
// Parse traits (@trait1 @trait2 ...)
|
|
103
79
|
const traits = [];
|
|
104
|
-
while (this.currentToken().type ===
|
|
80
|
+
while (this.currentToken().type === TokenType.TRAIT) {
|
|
105
81
|
traits.push(this.advance().value);
|
|
106
82
|
}
|
|
107
|
-
this.expect(
|
|
83
|
+
this.expect(TokenType.LBRACE);
|
|
108
84
|
// Parse properties (key: value)
|
|
109
85
|
const properties = {};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
86
|
+
let domainRandomization;
|
|
87
|
+
let actuatorGroups;
|
|
88
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
89
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
90
|
+
// Support both object "link" @trait { ... } and object "link" { @trait ... }.
|
|
91
|
+
if (this.currentToken().type === TokenType.TRAIT) {
|
|
92
|
+
const trait = this.advance().value;
|
|
93
|
+
if (!traits.includes(trait)) {
|
|
94
|
+
traits.push(trait);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Check for domain_randomization block
|
|
98
|
+
else if (this.currentToken().type === TokenType.IDENTIFIER &&
|
|
99
|
+
this.currentToken().value === 'domain_randomization') {
|
|
100
|
+
this.advance();
|
|
101
|
+
this.expect(TokenType.COLON);
|
|
102
|
+
domainRandomization = this.parseDomainRandomizationBlock();
|
|
103
|
+
}
|
|
104
|
+
// Check for actuator_group block
|
|
105
|
+
else if (this.currentToken().type === TokenType.IDENTIFIER &&
|
|
106
|
+
this.currentToken().value === 'actuator_group') {
|
|
107
|
+
this.advance();
|
|
108
|
+
this.expect(TokenType.COLON);
|
|
109
|
+
const group = this.parseActuatorGroupBlock();
|
|
110
|
+
if (!actuatorGroups)
|
|
111
|
+
actuatorGroups = [];
|
|
112
|
+
actuatorGroups.push(group);
|
|
113
|
+
}
|
|
114
|
+
// Regular property
|
|
115
|
+
else {
|
|
116
|
+
const keyName = this.expect(TokenType.IDENTIFIER).value;
|
|
117
|
+
this.expect(TokenType.COLON);
|
|
118
|
+
const value = this.parseValue();
|
|
119
|
+
properties[keyName] = value;
|
|
120
|
+
}
|
|
115
121
|
}
|
|
116
|
-
this.expect(
|
|
122
|
+
this.expect(TokenType.RBRACE);
|
|
117
123
|
return {
|
|
118
124
|
type: 'object',
|
|
119
125
|
name,
|
|
120
126
|
traits,
|
|
121
127
|
properties,
|
|
128
|
+
domainRandomization,
|
|
129
|
+
actuatorGroups,
|
|
122
130
|
line: startToken.line,
|
|
123
131
|
column: startToken.column,
|
|
124
132
|
};
|
|
@@ -126,19 +134,19 @@ class Parser {
|
|
|
126
134
|
parseValue() {
|
|
127
135
|
const token = this.currentToken();
|
|
128
136
|
// String value
|
|
129
|
-
if (token.type ===
|
|
137
|
+
if (token.type === TokenType.STRING) {
|
|
130
138
|
return this.advance().value;
|
|
131
139
|
}
|
|
132
140
|
// Number value
|
|
133
|
-
if (token.type ===
|
|
141
|
+
if (token.type === TokenType.NUMBER) {
|
|
134
142
|
return parseFloat(this.advance().value);
|
|
135
143
|
}
|
|
136
144
|
// Array value [1, 2, 3]
|
|
137
|
-
if (token.type ===
|
|
145
|
+
if (token.type === TokenType.LBRACKET) {
|
|
138
146
|
return this.parseArray();
|
|
139
147
|
}
|
|
140
148
|
// Function call (for future: diagonal([1, 2, 3]))
|
|
141
|
-
if (token.type ===
|
|
149
|
+
if (token.type === TokenType.IDENTIFIER && this.peek().type === TokenType.LBRACKET) {
|
|
142
150
|
this.advance(); // Skip function name for now
|
|
143
151
|
const args = this.parseArray();
|
|
144
152
|
// For now, treat function calls as arrays (e.g., diagonal([1,2,3]) -> [1,2,3])
|
|
@@ -148,16 +156,17 @@ class Parser {
|
|
|
148
156
|
throw new Error(`Unexpected value type ${token.type} "${token.value}" at line ${token.line}, column ${token.column}`);
|
|
149
157
|
}
|
|
150
158
|
parseArray() {
|
|
151
|
-
this.expect(
|
|
159
|
+
this.expect(TokenType.LBRACKET);
|
|
152
160
|
const values = [];
|
|
153
|
-
while (this.currentToken().type !==
|
|
154
|
-
|
|
161
|
+
while (this.currentToken().type !== TokenType.RBRACKET &&
|
|
162
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
163
|
+
if (this.currentToken().type === TokenType.NUMBER) {
|
|
155
164
|
values.push(parseFloat(this.advance().value));
|
|
156
165
|
}
|
|
157
|
-
else if (this.currentToken().type ===
|
|
166
|
+
else if (this.currentToken().type === TokenType.STRING) {
|
|
158
167
|
values.push(this.advance().value);
|
|
159
168
|
}
|
|
160
|
-
else if (this.currentToken().type ===
|
|
169
|
+
else if (this.currentToken().type === TokenType.LBRACKET) {
|
|
161
170
|
// Nested array
|
|
162
171
|
values.push(this.parseArray());
|
|
163
172
|
}
|
|
@@ -165,38 +174,203 @@ class Parser {
|
|
|
165
174
|
throw new Error(`Unexpected token in array: ${this.currentToken().type} at line ${this.currentToken().line}`);
|
|
166
175
|
}
|
|
167
176
|
// Optional comma
|
|
168
|
-
if (this.currentToken().type ===
|
|
177
|
+
if (this.currentToken().type === TokenType.COMMA) {
|
|
169
178
|
this.advance();
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
|
-
this.expect(
|
|
181
|
+
this.expect(TokenType.RBRACKET);
|
|
173
182
|
return values;
|
|
174
183
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
184
|
+
parseDomainRandomizationBlock() {
|
|
185
|
+
this.expect(TokenType.LBRACE);
|
|
186
|
+
const config = {};
|
|
187
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
188
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
189
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
190
|
+
this.expect(TokenType.COLON);
|
|
191
|
+
switch (key) {
|
|
192
|
+
case 'physics':
|
|
193
|
+
config.physics = this.parsePhysicsRandomization();
|
|
194
|
+
break;
|
|
195
|
+
case 'actuator':
|
|
196
|
+
config.actuator = this.parseActuatorRandomization();
|
|
197
|
+
break;
|
|
198
|
+
case 'observation':
|
|
199
|
+
config.observation = this.parseObservationRandomization();
|
|
200
|
+
break;
|
|
201
|
+
case 'initialState':
|
|
202
|
+
config.initialState = this.parseInitialStateRandomization();
|
|
203
|
+
break;
|
|
204
|
+
case 'disturbance':
|
|
205
|
+
config.disturbance = this.parseDisturbanceRandomization();
|
|
206
|
+
break;
|
|
207
|
+
default:
|
|
208
|
+
// Skip unknown keys
|
|
209
|
+
this.parseValue();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
this.expect(TokenType.RBRACE);
|
|
213
|
+
return config;
|
|
214
|
+
}
|
|
215
|
+
parsePhysicsRandomization() {
|
|
216
|
+
this.expect(TokenType.LBRACE);
|
|
217
|
+
const physics = {};
|
|
218
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
219
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
220
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
221
|
+
this.expect(TokenType.COLON);
|
|
222
|
+
const value = this.parseValue();
|
|
223
|
+
if (key === 'massScale' && Array.isArray(value)) {
|
|
224
|
+
physics.massScale = [value[0], value[1]];
|
|
225
|
+
}
|
|
226
|
+
else if (key === 'frictionRange' && Array.isArray(value)) {
|
|
227
|
+
physics.frictionRange = [value[0], value[1]];
|
|
228
|
+
}
|
|
229
|
+
else if (key === 'dampingRange' && Array.isArray(value)) {
|
|
230
|
+
physics.dampingRange = [value[0], value[1]];
|
|
231
|
+
}
|
|
232
|
+
else if (key === 'armatureRange' && Array.isArray(value)) {
|
|
233
|
+
physics.armatureRange = [value[0], value[1]];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
this.expect(TokenType.RBRACE);
|
|
237
|
+
return physics;
|
|
238
|
+
}
|
|
239
|
+
parseActuatorRandomization() {
|
|
240
|
+
this.expect(TokenType.LBRACE);
|
|
241
|
+
const actuator = {};
|
|
242
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
243
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
244
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
245
|
+
this.expect(TokenType.COLON);
|
|
246
|
+
const value = this.parseValue();
|
|
247
|
+
if (key === 'kpNoise' && typeof value === 'number') {
|
|
248
|
+
actuator.kpNoise = value;
|
|
249
|
+
}
|
|
250
|
+
else if (key === 'kdNoise' && typeof value === 'number') {
|
|
251
|
+
actuator.kdNoise = value;
|
|
252
|
+
}
|
|
253
|
+
else if (key === 'latencyNoise' && typeof value === 'number') {
|
|
254
|
+
actuator.latencyNoise = value;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
this.expect(TokenType.RBRACE);
|
|
258
|
+
return actuator;
|
|
259
|
+
}
|
|
260
|
+
parseObservationRandomization() {
|
|
261
|
+
this.expect(TokenType.LBRACE);
|
|
262
|
+
const observation = {};
|
|
263
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
264
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
265
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
266
|
+
this.expect(TokenType.COLON);
|
|
267
|
+
const value = this.parseValue();
|
|
268
|
+
if (key === 'jointPosNoise' && typeof value === 'number') {
|
|
269
|
+
observation.jointPosNoise = value;
|
|
270
|
+
}
|
|
271
|
+
else if (key === 'jointVelNoise' && typeof value === 'number') {
|
|
272
|
+
observation.jointVelNoise = value;
|
|
273
|
+
}
|
|
274
|
+
else if (key === 'imuNoise' && typeof value === 'number') {
|
|
275
|
+
observation.imuNoise = value;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
this.expect(TokenType.RBRACE);
|
|
279
|
+
return observation;
|
|
280
|
+
}
|
|
281
|
+
parseInitialStateRandomization() {
|
|
282
|
+
this.expect(TokenType.LBRACE);
|
|
283
|
+
const initialState = {};
|
|
284
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
285
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
286
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
287
|
+
this.expect(TokenType.COLON);
|
|
288
|
+
if (key === 'jointPosRange') {
|
|
289
|
+
initialState.jointPosRange = this.parseNumberRangeMap();
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const value = this.parseValue();
|
|
293
|
+
if (key === 'rootPoseRange' && Array.isArray(value)) {
|
|
294
|
+
initialState.rootPoseRange = value;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this.expect(TokenType.RBRACE);
|
|
299
|
+
return initialState;
|
|
300
|
+
}
|
|
301
|
+
parseNumberRangeMap() {
|
|
302
|
+
this.expect(TokenType.LBRACE);
|
|
303
|
+
const ranges = {};
|
|
304
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
305
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
306
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
307
|
+
this.expect(TokenType.COLON);
|
|
308
|
+
const value = this.parseValue();
|
|
309
|
+
if (Array.isArray(value)) {
|
|
310
|
+
ranges[key] = [value[0], value[1]];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
this.expect(TokenType.RBRACE);
|
|
314
|
+
return ranges;
|
|
315
|
+
}
|
|
316
|
+
parseDisturbanceRandomization() {
|
|
317
|
+
this.expect(TokenType.LBRACE);
|
|
318
|
+
const disturbance = {};
|
|
319
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
320
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
321
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
322
|
+
this.expect(TokenType.COLON);
|
|
323
|
+
const value = this.parseValue();
|
|
324
|
+
if (key === 'forceRange' && Array.isArray(value)) {
|
|
325
|
+
disturbance.forceRange = [value[0], value[1]];
|
|
326
|
+
}
|
|
327
|
+
else if (key === 'intervalRange' && Array.isArray(value)) {
|
|
328
|
+
disturbance.intervalRange = [value[0], value[1]];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
this.expect(TokenType.RBRACE);
|
|
332
|
+
return disturbance;
|
|
333
|
+
}
|
|
334
|
+
parseActuatorGroupBlock() {
|
|
335
|
+
// Parse actuator_group { name: "foo" type: "DelayedPDActuator" joints: ["joint1", "joint2"] ... }
|
|
336
|
+
this.expect(TokenType.LBRACE);
|
|
337
|
+
const group = {
|
|
338
|
+
name: '',
|
|
339
|
+
type: 'IdealPDActuator',
|
|
340
|
+
jointNames: [],
|
|
341
|
+
};
|
|
342
|
+
while (this.currentToken().type !== TokenType.RBRACE &&
|
|
343
|
+
this.currentToken().type !== TokenType.EOF) {
|
|
344
|
+
const key = this.expect(TokenType.IDENTIFIER).value;
|
|
345
|
+
this.expect(TokenType.COLON);
|
|
346
|
+
const value = this.parseValue();
|
|
347
|
+
switch (key) {
|
|
348
|
+
case 'name':
|
|
349
|
+
group.name = value;
|
|
350
|
+
break;
|
|
351
|
+
case 'type':
|
|
352
|
+
group.type = value;
|
|
353
|
+
break;
|
|
354
|
+
case 'joints':
|
|
355
|
+
if (Array.isArray(value)) {
|
|
356
|
+
group.jointNames = value;
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
case 'stiffness':
|
|
360
|
+
group.stiffness = value;
|
|
361
|
+
break;
|
|
362
|
+
case 'damping':
|
|
363
|
+
group.damping = value;
|
|
364
|
+
break;
|
|
365
|
+
case 'friction':
|
|
366
|
+
group.friction = value;
|
|
367
|
+
break;
|
|
368
|
+
case 'latency':
|
|
369
|
+
group.latency = value;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
this.expect(TokenType.RBRACE);
|
|
374
|
+
return group;
|
|
375
|
+
}
|
|
202
376
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @ros2_hardware_loop Trait — Bidirectional hardware-in-the-loop bridge for physical robotics. @trait ros2_hardware_loop */
|
|
2
|
+
export interface ROS2Config {
|
|
3
|
+
nodeName: string;
|
|
4
|
+
topicPrefix: string;
|
|
5
|
+
updateFrequencyHz: number;
|
|
6
|
+
bidirectional: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ROS2State {
|
|
9
|
+
connected: boolean;
|
|
10
|
+
lastPingMs: number;
|
|
11
|
+
activeTopics: string[];
|
|
12
|
+
hardwareSyncDriftMs: number;
|
|
13
|
+
}
|
|
14
|
+
import type { TraitHandler } from './types';
|
|
15
|
+
export declare function createROS2HardwareLoopHandler(): TraitHandler<ROS2Config>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function createROS2HardwareLoopHandler() {
|
|
2
|
+
return {
|
|
3
|
+
name: 'ros2_hardware_loop',
|
|
4
|
+
defaultConfig: { nodeName: 'holo_rig_01', topicPrefix: '/holo', updateFrequencyHz: 60, bidirectional: true },
|
|
5
|
+
onAttach(n, c, ctx) {
|
|
6
|
+
n.__ros2State = { connected: false, lastPingMs: 0, activeTopics: [], hardwareSyncDriftMs: 0 };
|
|
7
|
+
ctx.emit?.('ros2:init', { node: c.nodeName });
|
|
8
|
+
},
|
|
9
|
+
onDetach(n, _c, ctx) {
|
|
10
|
+
delete n.__ros2State;
|
|
11
|
+
ctx.emit?.('ros2:disconnect');
|
|
12
|
+
},
|
|
13
|
+
onUpdate() { },
|
|
14
|
+
onEvent(n, c, ctx, e) {
|
|
15
|
+
const s = n.__ros2State;
|
|
16
|
+
if (!s)
|
|
17
|
+
return;
|
|
18
|
+
const evt = e;
|
|
19
|
+
switch (evt.type) {
|
|
20
|
+
case 'ros2:connect':
|
|
21
|
+
s.connected = true;
|
|
22
|
+
// In real usage, initializes rclnodejs bridge for pub/sub physics
|
|
23
|
+
ctx.emit?.('ros2:connected', { latencyMs: 12 });
|
|
24
|
+
break;
|
|
25
|
+
case 'scene:transform_change':
|
|
26
|
+
if (c.bidirectional && s.connected) {
|
|
27
|
+
// Push virtual set transform to physical robot joint
|
|
28
|
+
ctx.emit?.('ros2:publish', { topic: `${c.topicPrefix}/joint_cmd`, payload: evt.payload });
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
case 'ros2:telemetry':
|
|
32
|
+
// Receive physical robot telemetry to drive virtual set
|
|
33
|
+
s.hardwareSyncDriftMs = Math.abs(Date.now() - (evt.payload.timestamp || Date.now()));
|
|
34
|
+
n.transform = evt.payload.transform;
|
|
35
|
+
break;
|
|
36
|
+
case 'ttu:manifested':
|
|
37
|
+
// Dynamically bind to Text-To-Universe AST streams
|
|
38
|
+
const crdtRoot = evt.payload?.crdtRoot;
|
|
39
|
+
if (crdtRoot && s.connected) {
|
|
40
|
+
const dynamicTopic = `${c.topicPrefix}/ttu_sync/${crdtRoot}`;
|
|
41
|
+
s.activeTopics.push(dynamicTopic);
|
|
42
|
+
ctx.emit?.('ros2:subscribe', { topic: dynamicTopic });
|
|
43
|
+
ctx.emit?.('ros2:log', { message: `Bridged digital twin for TTU root: ${crdtRoot}` });
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/usd-codegen.d.ts
CHANGED
|
@@ -2,17 +2,44 @@
|
|
|
2
2
|
* HoloScript → USD Code Generator
|
|
3
3
|
*
|
|
4
4
|
* Generates Universal Scene Description (USD) files from HoloScript AST.
|
|
5
|
-
* 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
|
|
6
11
|
*/
|
|
7
12
|
import { CompositionNode } from './ast';
|
|
13
|
+
export interface IsaacLabConfig {
|
|
14
|
+
/** Isaac Lab version target for generated code */
|
|
15
|
+
isaacLabVersion?: string;
|
|
16
|
+
/** Enable PhysX articulation and per-axis joint friction schemas (default: true) */
|
|
17
|
+
enableJointFriction?: boolean;
|
|
18
|
+
/** Enable drive attributes for PD control (default: true) */
|
|
19
|
+
enableDriveAttributes?: boolean;
|
|
20
|
+
}
|
|
8
21
|
export declare class USDCodeGen {
|
|
9
22
|
private output;
|
|
10
23
|
private indentLevel;
|
|
24
|
+
private config;
|
|
25
|
+
constructor(config?: IsaacLabConfig);
|
|
11
26
|
generate(ast: CompositionNode): string;
|
|
12
27
|
private generateLink;
|
|
13
28
|
private generateJoint;
|
|
14
29
|
private indent;
|
|
15
30
|
private emit;
|
|
16
31
|
private geometryToUSD;
|
|
32
|
+
private isJointObject;
|
|
33
|
+
private getJointKind;
|
|
34
|
+
private getJointApiSchemas;
|
|
35
|
+
private hasDriveProperties;
|
|
36
|
+
private hasPhysxJointAxisProperties;
|
|
37
|
+
private emitPhysxJointAxisProperties;
|
|
38
|
+
private emitActuatorGroupComment;
|
|
39
|
+
private numberProp;
|
|
40
|
+
private convertJointScalar;
|
|
41
|
+
private formatNumber;
|
|
42
|
+
private formatTokenArray;
|
|
17
43
|
private vectorToAxisName;
|
|
44
|
+
private emitDomainRandomizationAsComment;
|
|
18
45
|
}
|