@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/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 HoloScript Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/ast.d.ts
CHANGED
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Abstract Syntax Tree (AST) node types for HoloScript robotics
|
|
3
|
+
*
|
|
4
|
+
* Extended for Isaac Lab sim-to-real interop (Path A):
|
|
5
|
+
* - Domain randomization blocks
|
|
6
|
+
* - Actuator group configurations
|
|
7
|
+
* - Delayed actuator export hints
|
|
3
8
|
*/
|
|
4
9
|
export type PropertyValue = string | number | boolean | number[] | PropertyValue[];
|
|
10
|
+
export interface DomainRandomizationConfig {
|
|
11
|
+
/** Physics randomization: mass, friction, damping */
|
|
12
|
+
physics?: {
|
|
13
|
+
massScale?: [number, number];
|
|
14
|
+
frictionRange?: [number, number];
|
|
15
|
+
dampingRange?: [number, number];
|
|
16
|
+
armatureRange?: [number, number];
|
|
17
|
+
};
|
|
18
|
+
/** Actuator randomization */
|
|
19
|
+
actuator?: {
|
|
20
|
+
kpNoise?: number;
|
|
21
|
+
kdNoise?: number;
|
|
22
|
+
latencyNoise?: number;
|
|
23
|
+
};
|
|
24
|
+
/** Observation noise */
|
|
25
|
+
observation?: {
|
|
26
|
+
jointPosNoise?: number;
|
|
27
|
+
jointVelNoise?: number;
|
|
28
|
+
imuNoise?: number;
|
|
29
|
+
};
|
|
30
|
+
/** Initial state randomization */
|
|
31
|
+
initialState?: {
|
|
32
|
+
jointPosRange?: Record<string, [number, number]>;
|
|
33
|
+
rootPoseRange?: [number, number, number, number, number, number];
|
|
34
|
+
};
|
|
35
|
+
/** Disturbance forces */
|
|
36
|
+
disturbance?: {
|
|
37
|
+
forceRange?: [number, number];
|
|
38
|
+
intervalRange?: [number, number];
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface ActuatorGroupConfig {
|
|
42
|
+
name: string;
|
|
43
|
+
type: 'IdealPDActuator' | 'DCMotorActuator' | 'DelayedPDActuator' | 'RemotizedPDActuator' | 'ImplicitActuator';
|
|
44
|
+
jointNames: string[];
|
|
45
|
+
stiffness?: number;
|
|
46
|
+
damping?: number;
|
|
47
|
+
friction?: number;
|
|
48
|
+
latency?: number;
|
|
49
|
+
}
|
|
5
50
|
export interface ObjectNode {
|
|
6
51
|
type: 'object';
|
|
7
52
|
name: string;
|
|
8
53
|
traits: string[];
|
|
9
54
|
properties: Record<string, PropertyValue>;
|
|
55
|
+
domainRandomization?: DomainRandomizationConfig;
|
|
56
|
+
actuatorGroups?: ActuatorGroupConfig[];
|
|
10
57
|
template?: string;
|
|
11
58
|
line?: number;
|
|
12
59
|
column?: number;
|
|
@@ -15,6 +62,7 @@ export interface CompositionNode {
|
|
|
15
62
|
type: 'composition';
|
|
16
63
|
name: string;
|
|
17
64
|
objects: ObjectNode[];
|
|
65
|
+
domainRandomization?: DomainRandomizationConfig;
|
|
18
66
|
line?: number;
|
|
19
67
|
column?: number;
|
|
20
68
|
}
|
package/dist/ast.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Abstract Syntax Tree (AST) node types for HoloScript robotics
|
|
3
|
+
*
|
|
4
|
+
* Extended for Isaac Lab sim-to-real interop (Path A):
|
|
5
|
+
* - Domain randomization blocks
|
|
6
|
+
* - Actuator group configurations
|
|
7
|
+
* - Delayed actuator export hints
|
|
4
8
|
*/
|
|
5
|
-
|
|
9
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Features:
|
|
6
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
|
|
7
11
|
* - URDF/SDF export for ROS2/Gazebo
|
|
8
12
|
* - ROS2 runtime integration (roslibjs)
|
|
9
13
|
* - VR teleoperation and digital twins
|
|
@@ -11,9 +15,13 @@
|
|
|
11
15
|
* @packageDocumentation
|
|
12
16
|
*/
|
|
13
17
|
export { USDCodeGen } from './usd-codegen';
|
|
14
|
-
export {
|
|
18
|
+
export type { IsaacLabConfig } from './usd-codegen';
|
|
19
|
+
export { Lexer, TokenType } from './lexer';
|
|
20
|
+
export type { Token } from './lexer';
|
|
15
21
|
export { Parser } from './parser';
|
|
16
22
|
export * from './ast';
|
|
23
|
+
export type { DomainRandomizationConfig, ActuatorGroupConfig } from './ast';
|
|
24
|
+
import type { CompositionNode, DomainRandomizationConfig, ActuatorGroupConfig } from './ast';
|
|
17
25
|
export interface ROS2Config {
|
|
18
26
|
ros_bridge_url: string;
|
|
19
27
|
namespace?: string;
|
|
@@ -51,8 +59,69 @@ export interface RobotLink {
|
|
|
51
59
|
geometry: string;
|
|
52
60
|
};
|
|
53
61
|
}
|
|
62
|
+
export interface RobotTransmission {
|
|
63
|
+
name: string;
|
|
64
|
+
joint: string;
|
|
65
|
+
actuator: string;
|
|
66
|
+
mechanicalReduction: number;
|
|
67
|
+
hardwareInterface: 'position' | 'velocity' | 'effort';
|
|
68
|
+
}
|
|
69
|
+
export interface HoloTraitLike {
|
|
70
|
+
name: string;
|
|
71
|
+
properties?: Record<string, unknown>;
|
|
72
|
+
}
|
|
73
|
+
export interface HoloCompositionNodeLike {
|
|
74
|
+
id?: string;
|
|
75
|
+
name: string;
|
|
76
|
+
type?: string;
|
|
77
|
+
properties?: Record<string, unknown>;
|
|
78
|
+
traits?: HoloTraitLike[];
|
|
79
|
+
children?: HoloCompositionNodeLike[];
|
|
80
|
+
}
|
|
81
|
+
export interface HoloCompositionTreeLike {
|
|
82
|
+
name: string;
|
|
83
|
+
children?: HoloCompositionNodeLike[];
|
|
84
|
+
}
|
|
85
|
+
export interface ExtractedRobotModel {
|
|
86
|
+
name: string;
|
|
87
|
+
links: RobotLink[];
|
|
88
|
+
joints: RobotJoint[];
|
|
89
|
+
transmissions: RobotTransmission[];
|
|
90
|
+
xml: string;
|
|
91
|
+
}
|
|
92
|
+
export interface URDFExtractionOptions {
|
|
93
|
+
defaultJointType?: RobotJoint['type'];
|
|
94
|
+
defaultAxis?: [number, number, number];
|
|
95
|
+
baseLinkName?: string;
|
|
96
|
+
hardwareScale?: number;
|
|
97
|
+
}
|
|
98
|
+
export declare function buildURDFXML(model: Omit<ExtractedRobotModel, 'xml'>): string;
|
|
99
|
+
export declare function extractURDFFromHoloComposition(composition: HoloCompositionTreeLike, options?: URDFExtractionOptions): ExtractedRobotModel;
|
|
100
|
+
export * from './traits/ROS2HardwareLoopTrait';
|
|
54
101
|
export declare const VERSION = "1.0.0";
|
|
55
102
|
declare const _default: {
|
|
56
103
|
VERSION: string;
|
|
104
|
+
buildURDFXML: typeof buildURDFXML;
|
|
105
|
+
extractURDFFromHoloComposition: typeof extractURDFFromHoloComposition;
|
|
106
|
+
generateIsaacLabFeed: typeof generateIsaacLabFeed;
|
|
57
107
|
};
|
|
58
108
|
export default _default;
|
|
109
|
+
export interface IsaacLabFeedReceipt {
|
|
110
|
+
sourceHoloHash: string;
|
|
111
|
+
usdHash: string;
|
|
112
|
+
configHash: string;
|
|
113
|
+
generatedAt: string;
|
|
114
|
+
isaacLabVersion: string;
|
|
115
|
+
readyForTraining: boolean;
|
|
116
|
+
notes: string;
|
|
117
|
+
}
|
|
118
|
+
export interface IsaacLabFeedBundle {
|
|
119
|
+
usd: string;
|
|
120
|
+
taskConfig: Record<string, unknown>;
|
|
121
|
+
randomization: DomainRandomizationConfig | undefined;
|
|
122
|
+
actuators: ActuatorGroupConfig[];
|
|
123
|
+
receipt: IsaacLabFeedReceipt;
|
|
124
|
+
}
|
|
125
|
+
export declare function generateIsaacLabFeed(ast: CompositionNode, opts?: {
|
|
126
|
+
isaacLabVersion?: string;
|
|
127
|
+
}): IsaacLabFeedBundle;
|
package/dist/index.js
CHANGED
|
@@ -1,44 +1,349 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* @holoscript/robotics-plugin v1.0.0
|
|
4
3
|
* Complete robotics solution: Compile-time (USD/URDF) + Runtime (ROS2/Gazebo)
|
|
5
4
|
*
|
|
6
5
|
* Features:
|
|
7
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
|
|
8
11
|
* - URDF/SDF export for ROS2/Gazebo
|
|
9
12
|
* - ROS2 runtime integration (roslibjs)
|
|
10
13
|
* - VR teleoperation and digital twins
|
|
11
14
|
*
|
|
12
15
|
* @packageDocumentation
|
|
13
16
|
*/
|
|
14
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
-
if (k2 === undefined) k2 = k;
|
|
16
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
-
}
|
|
20
|
-
Object.defineProperty(o, k2, desc);
|
|
21
|
-
}) : (function(o, m, k, k2) {
|
|
22
|
-
if (k2 === undefined) k2 = k;
|
|
23
|
-
o[k2] = m[k];
|
|
24
|
-
}));
|
|
25
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
26
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.VERSION = exports.Parser = exports.TokenType = exports.Lexer = exports.USDCodeGen = void 0;
|
|
30
17
|
// Compile-time: USD/URDF codegen (from holoscript-compiler)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
export { USDCodeGen } from './usd-codegen';
|
|
19
|
+
export { Lexer, TokenType } from './lexer';
|
|
20
|
+
export { Parser } from './parser';
|
|
21
|
+
export * from './ast';
|
|
22
|
+
import { USDCodeGen } from './usd-codegen';
|
|
23
|
+
const DEFAULT_URDF_AXIS = [0, 0, 1];
|
|
24
|
+
function normalizeTraitName(name) {
|
|
25
|
+
return name.replace(/^@/, '').trim().toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
function sanitizeRobotName(name) {
|
|
28
|
+
return name.trim().replace(/[^A-Za-z0-9_]+/g, '_') || 'robot';
|
|
29
|
+
}
|
|
30
|
+
function xmlEscape(value) {
|
|
31
|
+
return value
|
|
32
|
+
.replace(/&/g, '&')
|
|
33
|
+
.replace(/</g, '<')
|
|
34
|
+
.replace(/>/g, '>')
|
|
35
|
+
.replace(/"/g, '"')
|
|
36
|
+
.replace(/'/g, ''');
|
|
37
|
+
}
|
|
38
|
+
function asRecord(value) {
|
|
39
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
40
|
+
? value
|
|
41
|
+
: {};
|
|
42
|
+
}
|
|
43
|
+
function asNumber(value, fallback) {
|
|
44
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
45
|
+
}
|
|
46
|
+
function asString(value, fallback) {
|
|
47
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : fallback;
|
|
48
|
+
}
|
|
49
|
+
function asVec3(value, fallback) {
|
|
50
|
+
if (Array.isArray(value) && value.length >= 3) {
|
|
51
|
+
return [asNumber(value[0], fallback[0]), asNumber(value[1], fallback[1]), asNumber(value[2], fallback[2])];
|
|
52
|
+
}
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
function asJointLimits(value, fallback) {
|
|
56
|
+
if (Array.isArray(value) && value.length >= 4) {
|
|
57
|
+
return {
|
|
58
|
+
lower: asNumber(value[0], fallback?.lower ?? -1.57),
|
|
59
|
+
upper: asNumber(value[1], fallback?.upper ?? 1.57),
|
|
60
|
+
effort: asNumber(value[2], fallback?.effort ?? 10),
|
|
61
|
+
velocity: asNumber(value[3], fallback?.velocity ?? 1),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const record = asRecord(value);
|
|
65
|
+
if (Object.keys(record).length > 0) {
|
|
66
|
+
return {
|
|
67
|
+
lower: asNumber(record.lower, fallback?.lower ?? -1.57),
|
|
68
|
+
upper: asNumber(record.upper, fallback?.upper ?? 1.57),
|
|
69
|
+
effort: asNumber(record.effort, fallback?.effort ?? 10),
|
|
70
|
+
velocity: asNumber(record.velocity, fallback?.velocity ?? 1),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
function findTrait(node, predicate) {
|
|
76
|
+
for (const trait of node.traits ?? []) {
|
|
77
|
+
const normalizedName = normalizeTraitName(trait.name);
|
|
78
|
+
if (predicate(normalizedName)) {
|
|
79
|
+
return { normalizedName, trait };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
function getJointTrait(node) {
|
|
85
|
+
return findTrait(node, (name) => name === 'joint' ||
|
|
86
|
+
name === 'hinge' ||
|
|
87
|
+
name === 'slider' ||
|
|
88
|
+
name.startsWith('joint_'));
|
|
89
|
+
}
|
|
90
|
+
function getMotorTrait(node) {
|
|
91
|
+
return findTrait(node, (name) => name === 'motor' || name === 'servo' || name === 'actuator' || name.endsWith('_motor'));
|
|
92
|
+
}
|
|
93
|
+
function jointTypeFromTrait(jointTrait, nodeProps, defaultJointType) {
|
|
94
|
+
const explicitType = normalizeTraitName(asString(nodeProps.joint_type ?? nodeProps.type, defaultJointType));
|
|
95
|
+
switch (explicitType) {
|
|
96
|
+
case 'revolute':
|
|
97
|
+
case 'prismatic':
|
|
98
|
+
case 'fixed':
|
|
99
|
+
case 'continuous':
|
|
100
|
+
return explicitType;
|
|
101
|
+
case 'hinge':
|
|
102
|
+
return 'revolute';
|
|
103
|
+
case 'slider':
|
|
104
|
+
return 'prismatic';
|
|
105
|
+
}
|
|
106
|
+
switch (jointTrait?.normalizedName) {
|
|
107
|
+
case 'joint_revolute':
|
|
108
|
+
case 'hinge':
|
|
109
|
+
return 'revolute';
|
|
110
|
+
case 'joint_prismatic':
|
|
111
|
+
case 'slider':
|
|
112
|
+
return 'prismatic';
|
|
113
|
+
case 'joint_continuous':
|
|
114
|
+
return 'continuous';
|
|
115
|
+
case 'joint_fixed':
|
|
116
|
+
return 'fixed';
|
|
117
|
+
default:
|
|
118
|
+
return defaultJointType;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function hardwareInterfaceFromMotor(motorProps) {
|
|
122
|
+
const commandMode = normalizeTraitName(asString(motorProps.commandMode ?? motorProps.mode, 'position'));
|
|
123
|
+
if (commandMode === 'velocity')
|
|
124
|
+
return 'velocity';
|
|
125
|
+
if (commandMode === 'effort' || commandMode === 'torque')
|
|
126
|
+
return 'effort';
|
|
127
|
+
return 'position';
|
|
128
|
+
}
|
|
129
|
+
function scaleMass(mass, hardwareScale) {
|
|
130
|
+
return Number((mass * Math.pow(hardwareScale, 3)).toFixed(6));
|
|
131
|
+
}
|
|
132
|
+
function mapNodeToRobotLink(node, hardwareScale) {
|
|
133
|
+
const props = asRecord(node.properties);
|
|
134
|
+
const baseMass = asNumber(props.mass, 1);
|
|
135
|
+
const inertia = Array.isArray(props.inertia)
|
|
136
|
+
? props.inertia.map((value) => asNumber(value, 0))
|
|
137
|
+
: [0.01, 0.01, 0.01, 0, 0, 0];
|
|
138
|
+
const geometry = asString(props.geometry, node.type ?? 'box');
|
|
139
|
+
const material = typeof props.material === 'string' ? props.material : undefined;
|
|
140
|
+
const collisionGeometry = asString(props.collisionGeometry ?? props.geometry, geometry);
|
|
141
|
+
return {
|
|
142
|
+
name: sanitizeRobotName(node.name || node.id || 'link'),
|
|
143
|
+
inertial: {
|
|
144
|
+
mass: scaleMass(baseMass, hardwareScale),
|
|
145
|
+
inertia,
|
|
146
|
+
},
|
|
147
|
+
visual: {
|
|
148
|
+
geometry,
|
|
149
|
+
material,
|
|
150
|
+
},
|
|
151
|
+
collision: {
|
|
152
|
+
geometry: collisionGeometry,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function createTransmission(jointName, motorProps, hardwareScale) {
|
|
157
|
+
return {
|
|
158
|
+
name: sanitizeRobotName(asString(motorProps.name, `${jointName}_transmission`)),
|
|
159
|
+
joint: jointName,
|
|
160
|
+
actuator: sanitizeRobotName(asString(motorProps.actuator ?? motorProps.name, `${jointName}_motor`)),
|
|
161
|
+
mechanicalReduction: Number((asNumber(motorProps.mechanicalReduction ?? motorProps.gearRatio, 1) * hardwareScale).toFixed(6)),
|
|
162
|
+
hardwareInterface: hardwareInterfaceFromMotor(motorProps),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function buildLinkXml(link) {
|
|
166
|
+
const lines = [` <link name="${xmlEscape(link.name)}">`];
|
|
167
|
+
if (link.inertial) {
|
|
168
|
+
const [ixx = 0.01, iyy = 0.01, izz = 0.01, ixy = 0, ixz = 0, iyz = 0] = link.inertial.inertia;
|
|
169
|
+
lines.push(' <inertial>');
|
|
170
|
+
lines.push(` <mass value="${link.inertial.mass}"/>`);
|
|
171
|
+
lines.push(` <inertia ixx="${ixx}" iyy="${iyy}" izz="${izz}" ixy="${ixy}" ixz="${ixz}" iyz="${iyz}"/>`);
|
|
172
|
+
lines.push(' </inertial>');
|
|
173
|
+
}
|
|
174
|
+
if (link.visual) {
|
|
175
|
+
lines.push(' <visual>');
|
|
176
|
+
lines.push(` <geometry><mesh filename="${xmlEscape(link.visual.geometry)}"/></geometry>`);
|
|
177
|
+
if (link.visual.material) {
|
|
178
|
+
lines.push(` <material name="${xmlEscape(link.visual.material)}"/>`);
|
|
179
|
+
}
|
|
180
|
+
lines.push(' </visual>');
|
|
181
|
+
}
|
|
182
|
+
if (link.collision) {
|
|
183
|
+
lines.push(' <collision>');
|
|
184
|
+
lines.push(` <geometry><mesh filename="${xmlEscape(link.collision.geometry)}"/></geometry>`);
|
|
185
|
+
lines.push(' </collision>');
|
|
186
|
+
}
|
|
187
|
+
lines.push(' </link>');
|
|
188
|
+
return lines.join('\n');
|
|
189
|
+
}
|
|
190
|
+
function buildJointXml(joint) {
|
|
191
|
+
const lines = [` <joint name="${xmlEscape(joint.name)}" type="${joint.type}">`];
|
|
192
|
+
lines.push(` <parent link="${xmlEscape(joint.parent_link)}"/>`);
|
|
193
|
+
lines.push(` <child link="${xmlEscape(joint.child_link)}"/>`);
|
|
194
|
+
if (joint.axis) {
|
|
195
|
+
lines.push(` <axis xyz="${joint.axis.join(' ')}"/>`);
|
|
196
|
+
}
|
|
197
|
+
if (joint.limits && joint.type !== 'fixed') {
|
|
198
|
+
lines.push(` <limit lower="${joint.limits.lower}" upper="${joint.limits.upper}" effort="${joint.limits.effort}" velocity="${joint.limits.velocity}"/>`);
|
|
199
|
+
}
|
|
200
|
+
lines.push(' </joint>');
|
|
201
|
+
return lines.join('\n');
|
|
202
|
+
}
|
|
203
|
+
function buildTransmissionXml(transmission) {
|
|
204
|
+
return [
|
|
205
|
+
` <transmission name="${xmlEscape(transmission.name)}">`,
|
|
206
|
+
' <type>transmission_interface/SimpleTransmission</type>',
|
|
207
|
+
` <joint name="${xmlEscape(transmission.joint)}">`,
|
|
208
|
+
` <hardwareInterface>${transmission.hardwareInterface}</hardwareInterface>`,
|
|
209
|
+
' </joint>',
|
|
210
|
+
` <actuator name="${xmlEscape(transmission.actuator)}">`,
|
|
211
|
+
' <hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>',
|
|
212
|
+
` <mechanicalReduction>${transmission.mechanicalReduction}</mechanicalReduction>`,
|
|
213
|
+
' </actuator>',
|
|
214
|
+
' </transmission>',
|
|
215
|
+
].join('\n');
|
|
216
|
+
}
|
|
217
|
+
export function buildURDFXML(model) {
|
|
218
|
+
const lines = [`<robot name="${xmlEscape(model.name)}">`];
|
|
219
|
+
lines.push(...model.links.map(buildLinkXml));
|
|
220
|
+
lines.push(...model.joints.map(buildJointXml));
|
|
221
|
+
lines.push(...model.transmissions.map(buildTransmissionXml));
|
|
222
|
+
lines.push('</robot>');
|
|
223
|
+
return lines.join('\n');
|
|
224
|
+
}
|
|
225
|
+
export function extractURDFFromHoloComposition(composition, options = {}) {
|
|
226
|
+
const defaultJointType = options.defaultJointType ?? 'fixed';
|
|
227
|
+
const defaultAxis = options.defaultAxis ?? DEFAULT_URDF_AXIS;
|
|
228
|
+
const hardwareScale = options.hardwareScale ?? 1;
|
|
229
|
+
const links = [];
|
|
230
|
+
const joints = [];
|
|
231
|
+
const transmissions = [];
|
|
232
|
+
const visitNode = (node, parentLinkName) => {
|
|
233
|
+
const props = asRecord(node.properties);
|
|
234
|
+
const link = mapNodeToRobotLink(node, hardwareScale);
|
|
235
|
+
links.push(link);
|
|
236
|
+
const jointTrait = getJointTrait(node);
|
|
237
|
+
const motorTrait = getMotorTrait(node);
|
|
238
|
+
const jointName = sanitizeRobotName(asString(props.jointName, `${link.name}_joint`));
|
|
239
|
+
if (parentLinkName && jointTrait) {
|
|
240
|
+
const limits = asJointLimits(props.joint_limits ?? props.limits, {
|
|
241
|
+
lower: -1.57,
|
|
242
|
+
upper: 1.57,
|
|
243
|
+
effort: 10 * hardwareScale,
|
|
244
|
+
velocity: 1 * hardwareScale,
|
|
245
|
+
});
|
|
246
|
+
const joint = {
|
|
247
|
+
name: jointName,
|
|
248
|
+
type: jointTypeFromTrait(jointTrait, props, defaultJointType),
|
|
249
|
+
parent_link: sanitizeRobotName(parentLinkName),
|
|
250
|
+
child_link: link.name,
|
|
251
|
+
axis: asVec3(props.joint_axis ?? props.axis, defaultAxis),
|
|
252
|
+
limits,
|
|
253
|
+
};
|
|
254
|
+
joints.push(joint);
|
|
255
|
+
if (motorTrait) {
|
|
256
|
+
const motorProps = {
|
|
257
|
+
...props,
|
|
258
|
+
...asRecord(motorTrait.trait.properties),
|
|
259
|
+
};
|
|
260
|
+
transmissions.push(createTransmission(joint.name, motorProps, hardwareScale));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const child of node.children ?? []) {
|
|
264
|
+
visitNode(child, link.name);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const baseLinkName = sanitizeRobotName(options.baseLinkName ?? `${composition.name}_base`);
|
|
268
|
+
links.push({
|
|
269
|
+
name: baseLinkName,
|
|
270
|
+
inertial: {
|
|
271
|
+
mass: scaleMass(1, hardwareScale),
|
|
272
|
+
inertia: [0.01, 0.01, 0.01, 0, 0, 0],
|
|
273
|
+
},
|
|
274
|
+
visual: {
|
|
275
|
+
geometry: 'base_link',
|
|
276
|
+
},
|
|
277
|
+
collision: {
|
|
278
|
+
geometry: 'base_link',
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
for (const child of composition.children ?? []) {
|
|
282
|
+
visitNode(child, baseLinkName);
|
|
283
|
+
}
|
|
284
|
+
const modelWithoutXml = {
|
|
285
|
+
name: sanitizeRobotName(composition.name),
|
|
286
|
+
links,
|
|
287
|
+
joints,
|
|
288
|
+
transmissions,
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
...modelWithoutXml,
|
|
292
|
+
xml: buildURDFXML(modelWithoutXml),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Traits
|
|
296
|
+
export * from './traits/ROS2HardwareLoopTrait';
|
|
39
297
|
// Version
|
|
40
|
-
|
|
298
|
+
export const VERSION = '1.0.0';
|
|
41
299
|
// Re-export for convenience
|
|
42
|
-
|
|
43
|
-
VERSION
|
|
300
|
+
export default {
|
|
301
|
+
VERSION,
|
|
302
|
+
buildURDFXML,
|
|
303
|
+
extractURDFFromHoloComposition,
|
|
304
|
+
generateIsaacLabFeed,
|
|
44
305
|
};
|
|
306
|
+
export function generateIsaacLabFeed(ast, opts = {}) {
|
|
307
|
+
const version = opts.isaacLabVersion || '2.3';
|
|
308
|
+
const codegen = new USDCodeGen({ isaacLabVersion: version });
|
|
309
|
+
const usd = codegen.generate(ast);
|
|
310
|
+
// Pull randomization + actuators that the AST already carries
|
|
311
|
+
const randomization = ast.domainRandomization;
|
|
312
|
+
const actuators = [];
|
|
313
|
+
for (const obj of ast.objects ?? []) {
|
|
314
|
+
if (obj.actuatorGroups)
|
|
315
|
+
actuators.push(...obj.actuatorGroups);
|
|
316
|
+
}
|
|
317
|
+
// Minimal Isaac Lab task config stub (observations / actions / randomization ranges)
|
|
318
|
+
const taskConfig = {
|
|
319
|
+
env: 'HoloScriptRobotEnv',
|
|
320
|
+
num_envs: 4096,
|
|
321
|
+
seed: 42,
|
|
322
|
+
observations: ['joint_pos', 'joint_vel', 'imu'],
|
|
323
|
+
actions: ['joint_torques'],
|
|
324
|
+
randomization: randomization || {},
|
|
325
|
+
actuator_groups: actuators,
|
|
326
|
+
source: 'holoscript',
|
|
327
|
+
usd_prim: ast.name,
|
|
328
|
+
};
|
|
329
|
+
// Simple content hashes for the receipt (evidence loop)
|
|
330
|
+
const sourceHoloHash = `holo:${ast.name}:${(ast.objects?.length || 0)}`;
|
|
331
|
+
const usdHash = `usd:${usd.length}`;
|
|
332
|
+
const configHash = `cfg:${JSON.stringify(taskConfig).length}`;
|
|
333
|
+
const receipt = {
|
|
334
|
+
sourceHoloHash,
|
|
335
|
+
usdHash,
|
|
336
|
+
configHash,
|
|
337
|
+
generatedAt: new Date().toISOString(),
|
|
338
|
+
isaacLabVersion: version,
|
|
339
|
+
readyForTraining: true,
|
|
340
|
+
notes: 'Narrow feed bundle — assets + randomization + actuator config. Policy training & real-robot eval are downstream.',
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
usd,
|
|
344
|
+
taskConfig,
|
|
345
|
+
randomization,
|
|
346
|
+
actuators,
|
|
347
|
+
receipt,
|
|
348
|
+
};
|
|
349
|
+
}
|
package/dist/lexer.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* HoloScript Lexer (Tokenizer)
|
|
4
3
|
*
|
|
5
4
|
* Converts HoloScript source code into a stream of tokens for parsing.
|
|
6
5
|
* Supports: keywords, identifiers, traits (@trait), strings, numbers, symbols
|
|
7
6
|
*/
|
|
8
|
-
|
|
9
|
-
exports.Lexer = exports.TokenType = void 0;
|
|
10
|
-
var TokenType;
|
|
7
|
+
export var TokenType;
|
|
11
8
|
(function (TokenType) {
|
|
12
9
|
// Keywords
|
|
13
10
|
TokenType["KEYWORD"] = "KEYWORD";
|
|
@@ -26,8 +23,8 @@ var TokenType;
|
|
|
26
23
|
// Special
|
|
27
24
|
TokenType["COMMENT"] = "COMMENT";
|
|
28
25
|
TokenType["EOF"] = "EOF";
|
|
29
|
-
})(TokenType || (
|
|
30
|
-
class Lexer {
|
|
26
|
+
})(TokenType || (TokenType = {}));
|
|
27
|
+
export class Lexer {
|
|
31
28
|
constructor(source) {
|
|
32
29
|
this.position = 0;
|
|
33
30
|
this.line = 1;
|
|
@@ -95,7 +92,8 @@ class Lexer {
|
|
|
95
92
|
const startColumn = this.column;
|
|
96
93
|
this.advance(); // Skip '@'
|
|
97
94
|
let trait = '';
|
|
98
|
-
while (this.position < this.source.length &&
|
|
95
|
+
while (this.position < this.source.length &&
|
|
96
|
+
this.isAlphaNumericOrUnderscore(this.currentChar())) {
|
|
99
97
|
trait += this.currentChar();
|
|
100
98
|
this.advance();
|
|
101
99
|
}
|
|
@@ -172,7 +170,8 @@ class Lexer {
|
|
|
172
170
|
const startLine = this.line;
|
|
173
171
|
const startColumn = this.column;
|
|
174
172
|
let ident = '';
|
|
175
|
-
while (this.position < this.source.length &&
|
|
173
|
+
while (this.position < this.source.length &&
|
|
174
|
+
this.isAlphaNumericOrUnderscore(this.currentChar())) {
|
|
176
175
|
ident += this.currentChar();
|
|
177
176
|
this.advance();
|
|
178
177
|
}
|
|
@@ -250,26 +249,3 @@ class Lexer {
|
|
|
250
249
|
return /[a-z0-9_]/i.test(char);
|
|
251
250
|
}
|
|
252
251
|
}
|
|
253
|
-
exports.Lexer = Lexer;
|
|
254
|
-
// Example usage
|
|
255
|
-
if (require.main === module) {
|
|
256
|
-
const source = `
|
|
257
|
-
composition "TwoLinkArm" {
|
|
258
|
-
object "Base" @static {
|
|
259
|
-
geometry: "cylinder"
|
|
260
|
-
dimensions: [0.1, 0.05]
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
object "Link1" @joint_revolute {
|
|
264
|
-
joint_axis: [0, 0, 1]
|
|
265
|
-
joint_limits: [-3.14, 3.14]
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
`;
|
|
269
|
-
const lexer = new Lexer(source);
|
|
270
|
-
const tokens = lexer.tokenize();
|
|
271
|
-
console.log('Tokens:');
|
|
272
|
-
tokens.forEach((token, i) => {
|
|
273
|
-
console.log(`${i}: ${token.type.padEnd(15)} "${token.value}" (L${token.line}:C${token.column})`);
|
|
274
|
-
});
|
|
275
|
-
}
|
package/dist/parser.d.ts
CHANGED
|
@@ -19,4 +19,12 @@ export declare class Parser {
|
|
|
19
19
|
private parseObject;
|
|
20
20
|
private parseValue;
|
|
21
21
|
private parseArray;
|
|
22
|
+
private parseDomainRandomizationBlock;
|
|
23
|
+
private parsePhysicsRandomization;
|
|
24
|
+
private parseActuatorRandomization;
|
|
25
|
+
private parseObservationRandomization;
|
|
26
|
+
private parseInitialStateRandomization;
|
|
27
|
+
private parseNumberRangeMap;
|
|
28
|
+
private parseDisturbanceRandomization;
|
|
29
|
+
private parseActuatorGroupBlock;
|
|
22
30
|
}
|