@holoscript/robotics-plugin 2.0.2 → 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/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/ast.d.ts +69 -0
- package/dist/ast.js +9 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +350 -0
- package/dist/lexer.d.ts +52 -0
- package/dist/lexer.js +251 -0
- package/dist/parser.d.ts +30 -0
- package/dist/parser.js +376 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.js +6 -0
- package/dist/traits/ROS2HardwareLoopTrait.d.ts +15 -0
- package/dist/traits/ROS2HardwareLoopTrait.js +64 -0
- package/dist/traits/types.js +1 -0
- package/dist/usd-codegen.d.ts +45 -0
- package/dist/usd-codegen.js +505 -0
- package/package.json +10 -2
- 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 -210
- package/src/ast.ts +0 -80
- package/src/index.ts +0 -563
- package/src/lexer.ts +0 -307
- package/src/parser.ts +0 -473
- package/src/traits/ROS2HardwareLoopTrait.ts +0 -84
- package/src/usd-codegen.ts +0 -651
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -11
- /package/{src/traits/types.ts → dist/traits/types.d.ts} +0 -0
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/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @holoscript/robotics-plugin
|
|
2
|
+
|
|
3
|
+
Robotics plugin for HoloScript compile-time USD/URDF generation and ROS2/Gazebo
|
|
4
|
+
integration surfaces.
|
|
5
|
+
|
|
6
|
+
## Public Consumption
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import { Parser, USDCodeGen, extractURDFFromHoloComposition } from '@holoscript/robotics-plugin';
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The package root ships built JavaScript and declaration files. It does not
|
|
13
|
+
expose TypeScript source as the public runtime entry.
|
package/dist/ast.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
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
|
|
8
|
+
*/
|
|
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
|
+
}
|
|
50
|
+
export interface ObjectNode {
|
|
51
|
+
type: 'object';
|
|
52
|
+
name: string;
|
|
53
|
+
traits: string[];
|
|
54
|
+
properties: Record<string, PropertyValue>;
|
|
55
|
+
domainRandomization?: DomainRandomizationConfig;
|
|
56
|
+
actuatorGroups?: ActuatorGroupConfig[];
|
|
57
|
+
template?: string;
|
|
58
|
+
line?: number;
|
|
59
|
+
column?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface CompositionNode {
|
|
62
|
+
type: 'composition';
|
|
63
|
+
name: string;
|
|
64
|
+
objects: ObjectNode[];
|
|
65
|
+
domainRandomization?: DomainRandomizationConfig;
|
|
66
|
+
line?: number;
|
|
67
|
+
column?: number;
|
|
68
|
+
}
|
|
69
|
+
export type ASTNode = CompositionNode | ObjectNode;
|
package/dist/ast.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
export { USDCodeGen } from './usd-codegen.js';
|
|
18
|
+
export type { IsaacLabConfig } from './usd-codegen.js';
|
|
19
|
+
export { Lexer, TokenType } from './lexer.js';
|
|
20
|
+
export type { Token } from './lexer.js';
|
|
21
|
+
export { Parser } from './parser.js';
|
|
22
|
+
export * from './ast.js';
|
|
23
|
+
export type { DomainRandomizationConfig, ActuatorGroupConfig } from './ast.js';
|
|
24
|
+
import type { CompositionNode, DomainRandomizationConfig, ActuatorGroupConfig } from './ast.js';
|
|
25
|
+
export interface ROS2Config {
|
|
26
|
+
ros_bridge_url: string;
|
|
27
|
+
namespace?: string;
|
|
28
|
+
tf_prefix?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface GazeboConfig {
|
|
31
|
+
world_file?: string;
|
|
32
|
+
model_sdf?: string;
|
|
33
|
+
spawn_position?: [number, number, number];
|
|
34
|
+
}
|
|
35
|
+
export interface RobotJoint {
|
|
36
|
+
name: string;
|
|
37
|
+
type: 'revolute' | 'prismatic' | 'fixed' | 'continuous';
|
|
38
|
+
parent_link: string;
|
|
39
|
+
child_link: string;
|
|
40
|
+
axis?: [number, number, number];
|
|
41
|
+
limits?: {
|
|
42
|
+
lower: number;
|
|
43
|
+
upper: number;
|
|
44
|
+
effort: number;
|
|
45
|
+
velocity: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface RobotLink {
|
|
49
|
+
name: string;
|
|
50
|
+
inertial?: {
|
|
51
|
+
mass: number;
|
|
52
|
+
inertia: number[];
|
|
53
|
+
};
|
|
54
|
+
visual?: {
|
|
55
|
+
geometry: string;
|
|
56
|
+
material?: string;
|
|
57
|
+
};
|
|
58
|
+
collision?: {
|
|
59
|
+
geometry: string;
|
|
60
|
+
};
|
|
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.js';
|
|
101
|
+
export declare const VERSION = "1.0.0";
|
|
102
|
+
declare const _default: {
|
|
103
|
+
VERSION: string;
|
|
104
|
+
buildURDFXML: typeof buildURDFXML;
|
|
105
|
+
extractURDFFromHoloComposition: typeof extractURDFFromHoloComposition;
|
|
106
|
+
generateIsaacLabFeed: typeof generateIsaacLabFeed;
|
|
107
|
+
};
|
|
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
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
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
|
+
// Compile-time: USD/URDF codegen (from holoscript-compiler)
|
|
18
|
+
export { USDCodeGen } from './usd-codegen.js';
|
|
19
|
+
export { Lexer, TokenType } from './lexer.js';
|
|
20
|
+
export { Parser } from './parser.js';
|
|
21
|
+
export * from './ast.js';
|
|
22
|
+
import { USDCodeGen } from './usd-codegen.js';
|
|
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 [
|
|
52
|
+
asNumber(value[0], fallback[0]),
|
|
53
|
+
asNumber(value[1], fallback[1]),
|
|
54
|
+
asNumber(value[2], fallback[2]),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
return fallback;
|
|
58
|
+
}
|
|
59
|
+
function asJointLimits(value, fallback) {
|
|
60
|
+
if (Array.isArray(value) && value.length >= 4) {
|
|
61
|
+
return {
|
|
62
|
+
lower: asNumber(value[0], fallback?.lower ?? -1.57),
|
|
63
|
+
upper: asNumber(value[1], fallback?.upper ?? 1.57),
|
|
64
|
+
effort: asNumber(value[2], fallback?.effort ?? 10),
|
|
65
|
+
velocity: asNumber(value[3], fallback?.velocity ?? 1),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const record = asRecord(value);
|
|
69
|
+
if (Object.keys(record).length > 0) {
|
|
70
|
+
return {
|
|
71
|
+
lower: asNumber(record.lower, fallback?.lower ?? -1.57),
|
|
72
|
+
upper: asNumber(record.upper, fallback?.upper ?? 1.57),
|
|
73
|
+
effort: asNumber(record.effort, fallback?.effort ?? 10),
|
|
74
|
+
velocity: asNumber(record.velocity, fallback?.velocity ?? 1),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
function findTrait(node, predicate) {
|
|
80
|
+
for (const trait of node.traits ?? []) {
|
|
81
|
+
const normalizedName = normalizeTraitName(trait.name);
|
|
82
|
+
if (predicate(normalizedName)) {
|
|
83
|
+
return { normalizedName, trait };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function getJointTrait(node) {
|
|
89
|
+
return findTrait(node, (name) => name === 'joint' || name === 'hinge' || name === 'slider' || name.startsWith('joint_'));
|
|
90
|
+
}
|
|
91
|
+
function getMotorTrait(node) {
|
|
92
|
+
return findTrait(node, (name) => name === 'motor' || name === 'servo' || name === 'actuator' || name.endsWith('_motor'));
|
|
93
|
+
}
|
|
94
|
+
function jointTypeFromTrait(jointTrait, nodeProps, defaultJointType) {
|
|
95
|
+
const explicitType = normalizeTraitName(asString(nodeProps.joint_type ?? nodeProps.type, defaultJointType));
|
|
96
|
+
switch (explicitType) {
|
|
97
|
+
case 'revolute':
|
|
98
|
+
case 'prismatic':
|
|
99
|
+
case 'fixed':
|
|
100
|
+
case 'continuous':
|
|
101
|
+
return explicitType;
|
|
102
|
+
case 'hinge':
|
|
103
|
+
return 'revolute';
|
|
104
|
+
case 'slider':
|
|
105
|
+
return 'prismatic';
|
|
106
|
+
}
|
|
107
|
+
switch (jointTrait?.normalizedName) {
|
|
108
|
+
case 'joint_revolute':
|
|
109
|
+
case 'hinge':
|
|
110
|
+
return 'revolute';
|
|
111
|
+
case 'joint_prismatic':
|
|
112
|
+
case 'slider':
|
|
113
|
+
return 'prismatic';
|
|
114
|
+
case 'joint_continuous':
|
|
115
|
+
return 'continuous';
|
|
116
|
+
case 'joint_fixed':
|
|
117
|
+
return 'fixed';
|
|
118
|
+
default:
|
|
119
|
+
return defaultJointType;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function hardwareInterfaceFromMotor(motorProps) {
|
|
123
|
+
const commandMode = normalizeTraitName(asString(motorProps.commandMode ?? motorProps.mode, 'position'));
|
|
124
|
+
if (commandMode === 'velocity')
|
|
125
|
+
return 'velocity';
|
|
126
|
+
if (commandMode === 'effort' || commandMode === 'torque')
|
|
127
|
+
return 'effort';
|
|
128
|
+
return 'position';
|
|
129
|
+
}
|
|
130
|
+
function scaleMass(mass, hardwareScale) {
|
|
131
|
+
return Number((mass * Math.pow(hardwareScale, 3)).toFixed(6));
|
|
132
|
+
}
|
|
133
|
+
function mapNodeToRobotLink(node, hardwareScale) {
|
|
134
|
+
const props = asRecord(node.properties);
|
|
135
|
+
const baseMass = asNumber(props.mass, 1);
|
|
136
|
+
const inertia = Array.isArray(props.inertia)
|
|
137
|
+
? props.inertia.map((value) => asNumber(value, 0))
|
|
138
|
+
: [0.01, 0.01, 0.01, 0, 0, 0];
|
|
139
|
+
const geometry = asString(props.geometry, node.type ?? 'box');
|
|
140
|
+
const material = typeof props.material === 'string' ? props.material : undefined;
|
|
141
|
+
const collisionGeometry = asString(props.collisionGeometry ?? props.geometry, geometry);
|
|
142
|
+
return {
|
|
143
|
+
name: sanitizeRobotName(node.name || node.id || 'link'),
|
|
144
|
+
inertial: {
|
|
145
|
+
mass: scaleMass(baseMass, hardwareScale),
|
|
146
|
+
inertia,
|
|
147
|
+
},
|
|
148
|
+
visual: {
|
|
149
|
+
geometry,
|
|
150
|
+
material,
|
|
151
|
+
},
|
|
152
|
+
collision: {
|
|
153
|
+
geometry: collisionGeometry,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function createTransmission(jointName, motorProps, hardwareScale) {
|
|
158
|
+
return {
|
|
159
|
+
name: sanitizeRobotName(asString(motorProps.name, `${jointName}_transmission`)),
|
|
160
|
+
joint: jointName,
|
|
161
|
+
actuator: sanitizeRobotName(asString(motorProps.actuator ?? motorProps.name, `${jointName}_motor`)),
|
|
162
|
+
mechanicalReduction: Number((asNumber(motorProps.mechanicalReduction ?? motorProps.gearRatio, 1) * hardwareScale).toFixed(6)),
|
|
163
|
+
hardwareInterface: hardwareInterfaceFromMotor(motorProps),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function buildLinkXml(link) {
|
|
167
|
+
const lines = [` <link name="${xmlEscape(link.name)}">`];
|
|
168
|
+
if (link.inertial) {
|
|
169
|
+
const [ixx = 0.01, iyy = 0.01, izz = 0.01, ixy = 0, ixz = 0, iyz = 0] = link.inertial.inertia;
|
|
170
|
+
lines.push(' <inertial>');
|
|
171
|
+
lines.push(` <mass value="${link.inertial.mass}"/>`);
|
|
172
|
+
lines.push(` <inertia ixx="${ixx}" iyy="${iyy}" izz="${izz}" ixy="${ixy}" ixz="${ixz}" iyz="${iyz}"/>`);
|
|
173
|
+
lines.push(' </inertial>');
|
|
174
|
+
}
|
|
175
|
+
if (link.visual) {
|
|
176
|
+
lines.push(' <visual>');
|
|
177
|
+
lines.push(` <geometry><mesh filename="${xmlEscape(link.visual.geometry)}"/></geometry>`);
|
|
178
|
+
if (link.visual.material) {
|
|
179
|
+
lines.push(` <material name="${xmlEscape(link.visual.material)}"/>`);
|
|
180
|
+
}
|
|
181
|
+
lines.push(' </visual>');
|
|
182
|
+
}
|
|
183
|
+
if (link.collision) {
|
|
184
|
+
lines.push(' <collision>');
|
|
185
|
+
lines.push(` <geometry><mesh filename="${xmlEscape(link.collision.geometry)}"/></geometry>`);
|
|
186
|
+
lines.push(' </collision>');
|
|
187
|
+
}
|
|
188
|
+
lines.push(' </link>');
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
function buildJointXml(joint) {
|
|
192
|
+
const lines = [` <joint name="${xmlEscape(joint.name)}" type="${joint.type}">`];
|
|
193
|
+
lines.push(` <parent link="${xmlEscape(joint.parent_link)}"/>`);
|
|
194
|
+
lines.push(` <child link="${xmlEscape(joint.child_link)}"/>`);
|
|
195
|
+
if (joint.axis) {
|
|
196
|
+
lines.push(` <axis xyz="${joint.axis.join(' ')}"/>`);
|
|
197
|
+
}
|
|
198
|
+
if (joint.limits && joint.type !== 'fixed') {
|
|
199
|
+
lines.push(` <limit lower="${joint.limits.lower}" upper="${joint.limits.upper}" effort="${joint.limits.effort}" velocity="${joint.limits.velocity}"/>`);
|
|
200
|
+
}
|
|
201
|
+
lines.push(' </joint>');
|
|
202
|
+
return lines.join('\n');
|
|
203
|
+
}
|
|
204
|
+
function buildTransmissionXml(transmission) {
|
|
205
|
+
return [
|
|
206
|
+
` <transmission name="${xmlEscape(transmission.name)}">`,
|
|
207
|
+
' <type>transmission_interface/SimpleTransmission</type>',
|
|
208
|
+
` <joint name="${xmlEscape(transmission.joint)}">`,
|
|
209
|
+
` <hardwareInterface>${transmission.hardwareInterface}</hardwareInterface>`,
|
|
210
|
+
' </joint>',
|
|
211
|
+
` <actuator name="${xmlEscape(transmission.actuator)}">`,
|
|
212
|
+
' <hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>',
|
|
213
|
+
` <mechanicalReduction>${transmission.mechanicalReduction}</mechanicalReduction>`,
|
|
214
|
+
' </actuator>',
|
|
215
|
+
' </transmission>',
|
|
216
|
+
].join('\n');
|
|
217
|
+
}
|
|
218
|
+
export function buildURDFXML(model) {
|
|
219
|
+
const lines = [`<robot name="${xmlEscape(model.name)}">`];
|
|
220
|
+
lines.push(...model.links.map(buildLinkXml));
|
|
221
|
+
lines.push(...model.joints.map(buildJointXml));
|
|
222
|
+
lines.push(...model.transmissions.map(buildTransmissionXml));
|
|
223
|
+
lines.push('</robot>');
|
|
224
|
+
return lines.join('\n');
|
|
225
|
+
}
|
|
226
|
+
export function extractURDFFromHoloComposition(composition, options = {}) {
|
|
227
|
+
const defaultJointType = options.defaultJointType ?? 'fixed';
|
|
228
|
+
const defaultAxis = options.defaultAxis ?? DEFAULT_URDF_AXIS;
|
|
229
|
+
const hardwareScale = options.hardwareScale ?? 1;
|
|
230
|
+
const links = [];
|
|
231
|
+
const joints = [];
|
|
232
|
+
const transmissions = [];
|
|
233
|
+
const visitNode = (node, parentLinkName) => {
|
|
234
|
+
const props = asRecord(node.properties);
|
|
235
|
+
const link = mapNodeToRobotLink(node, hardwareScale);
|
|
236
|
+
links.push(link);
|
|
237
|
+
const jointTrait = getJointTrait(node);
|
|
238
|
+
const motorTrait = getMotorTrait(node);
|
|
239
|
+
const jointName = sanitizeRobotName(asString(props.jointName, `${link.name}_joint`));
|
|
240
|
+
if (parentLinkName && jointTrait) {
|
|
241
|
+
const limits = asJointLimits(props.joint_limits ?? props.limits, {
|
|
242
|
+
lower: -1.57,
|
|
243
|
+
upper: 1.57,
|
|
244
|
+
effort: 10 * hardwareScale,
|
|
245
|
+
velocity: 1 * hardwareScale,
|
|
246
|
+
});
|
|
247
|
+
const joint = {
|
|
248
|
+
name: jointName,
|
|
249
|
+
type: jointTypeFromTrait(jointTrait, props, defaultJointType),
|
|
250
|
+
parent_link: sanitizeRobotName(parentLinkName),
|
|
251
|
+
child_link: link.name,
|
|
252
|
+
axis: asVec3(props.joint_axis ?? props.axis, defaultAxis),
|
|
253
|
+
limits,
|
|
254
|
+
};
|
|
255
|
+
joints.push(joint);
|
|
256
|
+
if (motorTrait) {
|
|
257
|
+
const motorProps = {
|
|
258
|
+
...props,
|
|
259
|
+
...asRecord(motorTrait.trait.properties),
|
|
260
|
+
};
|
|
261
|
+
transmissions.push(createTransmission(joint.name, motorProps, hardwareScale));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (const child of node.children ?? []) {
|
|
265
|
+
visitNode(child, link.name);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const baseLinkName = sanitizeRobotName(options.baseLinkName ?? `${composition.name}_base`);
|
|
269
|
+
links.push({
|
|
270
|
+
name: baseLinkName,
|
|
271
|
+
inertial: {
|
|
272
|
+
mass: scaleMass(1, hardwareScale),
|
|
273
|
+
inertia: [0.01, 0.01, 0.01, 0, 0, 0],
|
|
274
|
+
},
|
|
275
|
+
visual: {
|
|
276
|
+
geometry: 'base_link',
|
|
277
|
+
},
|
|
278
|
+
collision: {
|
|
279
|
+
geometry: 'base_link',
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
for (const child of composition.children ?? []) {
|
|
283
|
+
visitNode(child, baseLinkName);
|
|
284
|
+
}
|
|
285
|
+
const modelWithoutXml = {
|
|
286
|
+
name: sanitizeRobotName(composition.name),
|
|
287
|
+
links,
|
|
288
|
+
joints,
|
|
289
|
+
transmissions,
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
...modelWithoutXml,
|
|
293
|
+
xml: buildURDFXML(modelWithoutXml),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
// Traits
|
|
297
|
+
export * from './traits/ROS2HardwareLoopTrait.js';
|
|
298
|
+
// Version
|
|
299
|
+
export const VERSION = '1.0.0';
|
|
300
|
+
// Re-export for convenience
|
|
301
|
+
export default {
|
|
302
|
+
VERSION,
|
|
303
|
+
buildURDFXML,
|
|
304
|
+
extractURDFFromHoloComposition,
|
|
305
|
+
generateIsaacLabFeed,
|
|
306
|
+
};
|
|
307
|
+
export function generateIsaacLabFeed(ast, opts = {}) {
|
|
308
|
+
const version = opts.isaacLabVersion || '2.3';
|
|
309
|
+
const codegen = new USDCodeGen({ isaacLabVersion: version });
|
|
310
|
+
const usd = codegen.generate(ast);
|
|
311
|
+
// Pull randomization + actuators that the AST already carries
|
|
312
|
+
const randomization = ast.domainRandomization;
|
|
313
|
+
const actuators = [];
|
|
314
|
+
for (const obj of ast.objects ?? []) {
|
|
315
|
+
if (obj.actuatorGroups)
|
|
316
|
+
actuators.push(...obj.actuatorGroups);
|
|
317
|
+
}
|
|
318
|
+
// Minimal Isaac Lab task config stub (observations / actions / randomization ranges)
|
|
319
|
+
const taskConfig = {
|
|
320
|
+
env: 'HoloScriptRobotEnv',
|
|
321
|
+
num_envs: 4096,
|
|
322
|
+
seed: 42,
|
|
323
|
+
observations: ['joint_pos', 'joint_vel', 'imu'],
|
|
324
|
+
actions: ['joint_torques'],
|
|
325
|
+
randomization: randomization || {},
|
|
326
|
+
actuator_groups: actuators,
|
|
327
|
+
source: 'holoscript',
|
|
328
|
+
usd_prim: ast.name,
|
|
329
|
+
};
|
|
330
|
+
// Simple content hashes for the receipt (evidence loop)
|
|
331
|
+
const sourceHoloHash = `holo:${ast.name}:${ast.objects?.length || 0}`;
|
|
332
|
+
const usdHash = `usd:${usd.length}`;
|
|
333
|
+
const configHash = `cfg:${JSON.stringify(taskConfig).length}`;
|
|
334
|
+
const receipt = {
|
|
335
|
+
sourceHoloHash,
|
|
336
|
+
usdHash,
|
|
337
|
+
configHash,
|
|
338
|
+
generatedAt: new Date().toISOString(),
|
|
339
|
+
isaacLabVersion: version,
|
|
340
|
+
readyForTraining: true,
|
|
341
|
+
notes: 'Narrow feed bundle — assets + randomization + actuator config. Policy training & real-robot eval are downstream.',
|
|
342
|
+
};
|
|
343
|
+
return {
|
|
344
|
+
usd,
|
|
345
|
+
taskConfig,
|
|
346
|
+
randomization,
|
|
347
|
+
actuators,
|
|
348
|
+
receipt,
|
|
349
|
+
};
|
|
350
|
+
}
|
package/dist/lexer.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HoloScript Lexer (Tokenizer)
|
|
3
|
+
*
|
|
4
|
+
* Converts HoloScript source code into a stream of tokens for parsing.
|
|
5
|
+
* Supports: keywords, identifiers, traits (@trait), strings, numbers, symbols
|
|
6
|
+
*/
|
|
7
|
+
export declare enum TokenType {
|
|
8
|
+
KEYWORD = "KEYWORD",// composition, object, using, template, group
|
|
9
|
+
IDENTIFIER = "IDENTIFIER",// variable_name, link1, base
|
|
10
|
+
TRAIT = "TRAIT",// @joint_revolute, @force_sensor
|
|
11
|
+
STRING = "STRING",// "Base", "metal", "cylinder"
|
|
12
|
+
NUMBER = "NUMBER",// 0.1, 3.14, 100, -2.0
|
|
13
|
+
LBRACE = "LBRACE",// {
|
|
14
|
+
RBRACE = "RBRACE",// }
|
|
15
|
+
LBRACKET = "LBRACKET",// [
|
|
16
|
+
RBRACKET = "RBRACKET",// ]
|
|
17
|
+
COLON = "COLON",// :
|
|
18
|
+
COMMA = "COMMA",// ,
|
|
19
|
+
COMMENT = "COMMENT",// // comment
|
|
20
|
+
EOF = "EOF"
|
|
21
|
+
}
|
|
22
|
+
export interface Token {
|
|
23
|
+
type: TokenType;
|
|
24
|
+
value: string;
|
|
25
|
+
line: number;
|
|
26
|
+
column: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class Lexer {
|
|
29
|
+
private source;
|
|
30
|
+
private position;
|
|
31
|
+
private line;
|
|
32
|
+
private column;
|
|
33
|
+
private tokens;
|
|
34
|
+
private readonly keywords;
|
|
35
|
+
constructor(source: string);
|
|
36
|
+
tokenize(): Token[];
|
|
37
|
+
private scanToken;
|
|
38
|
+
private scanTrait;
|
|
39
|
+
private scanString;
|
|
40
|
+
private scanNumber;
|
|
41
|
+
private scanIdentifierOrKeyword;
|
|
42
|
+
private scanSymbol;
|
|
43
|
+
private skipWhitespace;
|
|
44
|
+
private skipComment;
|
|
45
|
+
private currentChar;
|
|
46
|
+
private peek;
|
|
47
|
+
private advance;
|
|
48
|
+
private isWhitespace;
|
|
49
|
+
private isDigit;
|
|
50
|
+
private isAlpha;
|
|
51
|
+
private isAlphaNumericOrUnderscore;
|
|
52
|
+
}
|