@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/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
- 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.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(lexer_1.TokenType.KEYWORD, 'composition');
84
- const name = this.expect(lexer_1.TokenType.STRING).value;
85
- this.expect(lexer_1.TokenType.LBRACE);
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
- while (this.currentToken().type !== lexer_1.TokenType.RBRACE && this.currentToken().type !== lexer_1.TokenType.EOF) {
88
- objects.push(this.parseObject());
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(lexer_1.TokenType.RBRACE);
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(lexer_1.TokenType.KEYWORD, 'object');
101
- const name = this.expect(lexer_1.TokenType.STRING).value;
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 === lexer_1.TokenType.TRAIT) {
80
+ while (this.currentToken().type === TokenType.TRAIT) {
105
81
  traits.push(this.advance().value);
106
82
  }
107
- this.expect(lexer_1.TokenType.LBRACE);
83
+ this.expect(TokenType.LBRACE);
108
84
  // Parse properties (key: value)
109
85
  const properties = {};
110
- while (this.currentToken().type !== lexer_1.TokenType.RBRACE && this.currentToken().type !== lexer_1.TokenType.EOF) {
111
- const key = this.expect(lexer_1.TokenType.IDENTIFIER).value;
112
- this.expect(lexer_1.TokenType.COLON);
113
- const value = this.parseValue();
114
- properties[key] = value;
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(lexer_1.TokenType.RBRACE);
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 === lexer_1.TokenType.STRING) {
137
+ if (token.type === TokenType.STRING) {
130
138
  return this.advance().value;
131
139
  }
132
140
  // Number value
133
- if (token.type === lexer_1.TokenType.NUMBER) {
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 === lexer_1.TokenType.LBRACKET) {
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 === lexer_1.TokenType.IDENTIFIER && this.peek().type === lexer_1.TokenType.LBRACKET) {
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(lexer_1.TokenType.LBRACKET);
159
+ this.expect(TokenType.LBRACKET);
152
160
  const values = [];
153
- while (this.currentToken().type !== lexer_1.TokenType.RBRACKET && this.currentToken().type !== lexer_1.TokenType.EOF) {
154
- if (this.currentToken().type === lexer_1.TokenType.NUMBER) {
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 === lexer_1.TokenType.STRING) {
166
+ else if (this.currentToken().type === TokenType.STRING) {
158
167
  values.push(this.advance().value);
159
168
  }
160
- else if (this.currentToken().type === lexer_1.TokenType.LBRACKET) {
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 === lexer_1.TokenType.COMMA) {
177
+ if (this.currentToken().type === TokenType.COMMA) {
169
178
  this.advance();
170
179
  }
171
180
  }
172
- this.expect(lexer_1.TokenType.RBRACKET);
181
+ this.expect(TokenType.RBRACKET);
173
182
  return values;
174
183
  }
175
- }
176
- exports.Parser = Parser;
177
- // Example usage
178
- if (require.main === module) {
179
- Promise.resolve().then(() => __importStar(require('./lexer'))).then(({ Lexer }) => {
180
- const source = `
181
- composition "TwoLinkArm" {
182
- object "Base" @static {
183
- geometry: "cylinder"
184
- dimensions: [0.1, 0.05]
185
- material: "metal"
186
- }
187
-
188
- object "Link1" @joint_revolute {
189
- joint_axis: [0, 0, 1]
190
- joint_limits: [-3.14, 3.14]
191
- joint_effort: 100
192
- }
193
- }
194
- `;
195
- const lexer = new Lexer(source);
196
- const tokens = lexer.tokenize();
197
- const parser = new Parser(tokens);
198
- const ast = parser.parse();
199
- console.log('AST:');
200
- console.log(JSON.stringify(ast, null, 2));
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,4 @@
1
+ export type TraitContext = unknown;
2
+ export type TraitEvent = unknown;
3
+ export type HSPlusNode = unknown;
4
+ export type TraitHandler<_T> = unknown;
@@ -0,0 +1 @@
1
+ export {};
@@ -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 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
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
  }