@holoscript/robotics-plugin 1.0.0 → 2.0.2
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/examples/isaac-lab-sim-to-real-bridge.holo +63 -0
- package/examples/isaac-lab-sim-to-real.holo +127 -0
- package/package.json +7 -42
- package/python/isaac_lab_bridge.py +157 -0
- package/src/__tests__/isaac-lab-interop.test.ts +210 -0
- package/src/ast.ts +80 -0
- package/src/index.ts +563 -0
- package/src/lexer.ts +307 -0
- package/src/parser.ts +473 -0
- package/src/traits/ROS2HardwareLoopTrait.ts +84 -0
- package/src/traits/types.ts +4 -0
- package/src/usd-codegen.ts +651 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
- package/README.md +0 -3
- package/dist/ast.d.ts +0 -21
- package/dist/ast.js +0 -5
- package/dist/index.d.ts +0 -58
- package/dist/index.js +0 -44
- package/dist/lexer.d.ts +0 -52
- package/dist/lexer.js +0 -275
- package/dist/parser.d.ts +0 -22
- package/dist/parser.js +0 -202
- package/dist/usd-codegen.d.ts +0 -18
- package/dist/usd-codegen.js +0 -361
- package/examples/robot-arm-complete.holo +0 -199
- package/python/ros2_bridge.py +0 -77
package/dist/usd-codegen.js
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* HoloScript → USD Code Generator
|
|
4
|
-
*
|
|
5
|
-
* Generates Universal Scene Description (USD) files from HoloScript AST.
|
|
6
|
-
* Targets NVIDIA Isaac Sim with UsdPhysics schema.
|
|
7
|
-
*/
|
|
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.USDCodeGen = void 0;
|
|
43
|
-
class USDCodeGen {
|
|
44
|
-
constructor() {
|
|
45
|
-
this.output = [];
|
|
46
|
-
this.indentLevel = 0;
|
|
47
|
-
}
|
|
48
|
-
generate(ast) {
|
|
49
|
-
this.output = [];
|
|
50
|
-
this.indentLevel = 0;
|
|
51
|
-
// USD header
|
|
52
|
-
this.emit('#usda 1.0');
|
|
53
|
-
this.emit('(');
|
|
54
|
-
this.indentLevel++;
|
|
55
|
-
this.emit(`defaultPrim = "${ast.name}"`);
|
|
56
|
-
this.emit('upAxis = "Z"');
|
|
57
|
-
this.emit('metersPerUnit = 1.0');
|
|
58
|
-
this.emit('kilogramsPerMass = 1.0');
|
|
59
|
-
this.indentLevel--;
|
|
60
|
-
this.emit(')');
|
|
61
|
-
this.emit('');
|
|
62
|
-
// Root articulation
|
|
63
|
-
this.emit(`def Xform "${ast.name}" (`);
|
|
64
|
-
this.indentLevel++;
|
|
65
|
-
this.emit('prepend apiSchemas = ["PhysicsArticulationRootAPI"]');
|
|
66
|
-
this.indentLevel--;
|
|
67
|
-
this.emit(')');
|
|
68
|
-
this.emit('{');
|
|
69
|
-
this.indentLevel++;
|
|
70
|
-
// Collect joints for later generation
|
|
71
|
-
const joints = [];
|
|
72
|
-
// Separate joint and link objects
|
|
73
|
-
const jointObjects = [];
|
|
74
|
-
const linkObjects = [];
|
|
75
|
-
for (const obj of ast.objects) {
|
|
76
|
-
if (obj.traits.includes('joint_revolute') || obj.traits.includes('joint_prismatic')) {
|
|
77
|
-
jointObjects.push(obj);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
linkObjects.push(obj);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Generate links
|
|
84
|
-
for (const obj of ast.objects) {
|
|
85
|
-
if (obj.traits.includes('joint_revolute') || obj.traits.includes('joint_prismatic')) {
|
|
86
|
-
// New template format: Joint is separate object
|
|
87
|
-
// Check if there's a corresponding link (child of this joint)
|
|
88
|
-
const childLink = linkObjects.find(link => link.properties.parent === obj.name);
|
|
89
|
-
if (childLink) {
|
|
90
|
-
// Generate the child link
|
|
91
|
-
this.generateLink(childLink);
|
|
92
|
-
// Collect joint info
|
|
93
|
-
const parent = obj.properties.parent || 'World';
|
|
94
|
-
joints.push({
|
|
95
|
-
name: obj.name,
|
|
96
|
-
parent,
|
|
97
|
-
child: childLink.name,
|
|
98
|
-
props: obj.properties,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
// Old format: joint_parent property (backward compatibility)
|
|
103
|
-
this.generateLink(obj);
|
|
104
|
-
const parent = obj.properties.joint_parent || 'World';
|
|
105
|
-
joints.push({
|
|
106
|
-
name: `${obj.name}Joint`,
|
|
107
|
-
parent,
|
|
108
|
-
child: obj.name,
|
|
109
|
-
props: obj.properties,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
// Link object without joint (base, end effector, etc.)
|
|
115
|
-
// Only generate if not already generated as child of a joint
|
|
116
|
-
const isChildOfJoint = jointObjects.some(j => linkObjects.find(l => l.properties.parent === j.name && l.name === obj.name));
|
|
117
|
-
if (!isChildOfJoint) {
|
|
118
|
-
this.generateLink(obj);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// Generate joints after all links
|
|
123
|
-
this.emit('');
|
|
124
|
-
this.emit('# Joints');
|
|
125
|
-
for (const joint of joints) {
|
|
126
|
-
this.generateJoint(joint);
|
|
127
|
-
}
|
|
128
|
-
this.indentLevel--;
|
|
129
|
-
this.emit('}');
|
|
130
|
-
return this.output.join('\n');
|
|
131
|
-
}
|
|
132
|
-
generateLink(obj) {
|
|
133
|
-
const geometry = obj.properties.geometry || 'box';
|
|
134
|
-
const usdType = this.geometryToUSD(geometry);
|
|
135
|
-
const isStatic = obj.traits.includes('static');
|
|
136
|
-
this.emit('');
|
|
137
|
-
this.emit(`# ${obj.name}`);
|
|
138
|
-
this.emit(`def ${usdType} "${obj.name}" (`);
|
|
139
|
-
this.indentLevel++;
|
|
140
|
-
// Add physics APIs (unless static)
|
|
141
|
-
if (!isStatic) {
|
|
142
|
-
this.emit('prepend apiSchemas = ["PhysicsCollisionAPI", "PhysicsRigidBodyAPI", "PhysicsMassAPI"]');
|
|
143
|
-
}
|
|
144
|
-
this.indentLevel--;
|
|
145
|
-
this.emit(')');
|
|
146
|
-
this.emit('{');
|
|
147
|
-
this.indentLevel++;
|
|
148
|
-
// Geometry dimensions (support both array and individual properties)
|
|
149
|
-
if (geometry === 'cylinder') {
|
|
150
|
-
let radius, height;
|
|
151
|
-
if (obj.properties.dimensions) {
|
|
152
|
-
// Old format: dimensions: [radius, height]
|
|
153
|
-
const dims = obj.properties.dimensions;
|
|
154
|
-
[radius, height] = dims;
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
// New format: radius: 0.2, height/length: 0.2
|
|
158
|
-
radius = obj.properties.radius || 0.1;
|
|
159
|
-
height = (obj.properties.height || obj.properties.length) || 0.1;
|
|
160
|
-
}
|
|
161
|
-
this.emit(`float radius = ${radius}`);
|
|
162
|
-
this.emit(`float height = ${height}`);
|
|
163
|
-
}
|
|
164
|
-
else if (geometry === 'box') {
|
|
165
|
-
let length, width, height;
|
|
166
|
-
if (obj.properties.dimensions) {
|
|
167
|
-
// Old format: dimensions: [length, width, height]
|
|
168
|
-
const dims = obj.properties.dimensions;
|
|
169
|
-
[length, width, height] = dims;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
// New format: length: 1.0, width: 0.1, height: 0.1
|
|
173
|
-
length = obj.properties.length || 1.0;
|
|
174
|
-
width = obj.properties.width || 1.0;
|
|
175
|
-
height = obj.properties.height || 1.0;
|
|
176
|
-
}
|
|
177
|
-
this.emit('double size = 1.0');
|
|
178
|
-
this.emit(`float3 xformOp:scale = (${length}, ${width}, ${height})`);
|
|
179
|
-
this.emit('uniform token[] xformOpOrder = ["xformOp:scale"]');
|
|
180
|
-
}
|
|
181
|
-
else if (geometry === 'sphere') {
|
|
182
|
-
let radius;
|
|
183
|
-
if (obj.properties.dimensions) {
|
|
184
|
-
// Old format: dimensions: [radius]
|
|
185
|
-
const dims = obj.properties.dimensions;
|
|
186
|
-
[radius] = dims;
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
// New format: radius: 0.08
|
|
190
|
-
radius = obj.properties.radius || 0.1;
|
|
191
|
-
}
|
|
192
|
-
this.emit(`float radius = ${radius}`);
|
|
193
|
-
}
|
|
194
|
-
// Color/Material
|
|
195
|
-
if (obj.properties.color) {
|
|
196
|
-
const color = obj.properties.color;
|
|
197
|
-
const [r, g, b] = color;
|
|
198
|
-
this.emit(`color3f[] primvars:displayColor = [(${r}, ${g}, ${b})]`);
|
|
199
|
-
}
|
|
200
|
-
else if (obj.properties.material) {
|
|
201
|
-
// Default colors based on material
|
|
202
|
-
const materialColors = {
|
|
203
|
-
'metal': '0.6, 0.6, 0.65',
|
|
204
|
-
'plastic': '0.8, 0.8, 0.8',
|
|
205
|
-
'wood': '0.6, 0.4, 0.2',
|
|
206
|
-
'glass': '0.9, 0.9, 1.0',
|
|
207
|
-
};
|
|
208
|
-
const material = obj.properties.material;
|
|
209
|
-
const color = materialColors[material] || '0.8, 0.8, 0.8';
|
|
210
|
-
this.emit(`color3f[] primvars:displayColor = [(${color})]`);
|
|
211
|
-
}
|
|
212
|
-
// Physics properties
|
|
213
|
-
if (!isStatic) {
|
|
214
|
-
this.emit('');
|
|
215
|
-
this.emit('# Physics properties');
|
|
216
|
-
const mass = obj.properties.mass || 1.0;
|
|
217
|
-
this.emit(`float physics:mass = ${mass}`);
|
|
218
|
-
if (obj.properties.inertia) {
|
|
219
|
-
const inertia = obj.properties.inertia;
|
|
220
|
-
this.emit(`float3 physics:diagonalInertia = (${inertia[0]}, ${inertia[1]}, ${inertia[2]})`);
|
|
221
|
-
}
|
|
222
|
-
this.emit('uniform token physics:approximation = "convexHull"');
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
this.emit('');
|
|
226
|
-
this.emit('# Static object');
|
|
227
|
-
this.emit('uniform token physics:rigidBodyEnabled = false');
|
|
228
|
-
}
|
|
229
|
-
// Position (if specified)
|
|
230
|
-
if (obj.properties.position) {
|
|
231
|
-
const pos = obj.properties.position;
|
|
232
|
-
this.emit('');
|
|
233
|
-
this.emit(`double3 xformOp:translate = (${pos[0]}, ${pos[1]}, ${pos[2]})`);
|
|
234
|
-
if (!obj.properties.dimensions) {
|
|
235
|
-
this.emit('uniform token[] xformOpOrder = ["xformOp:translate"]');
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
this.indentLevel--;
|
|
239
|
-
this.emit('}');
|
|
240
|
-
}
|
|
241
|
-
generateJoint(joint) {
|
|
242
|
-
this.emit('');
|
|
243
|
-
this.emit(`def PhysicsRevoluteJoint "${joint.name}"`);
|
|
244
|
-
this.emit('{');
|
|
245
|
-
this.indentLevel++;
|
|
246
|
-
// Body references
|
|
247
|
-
this.emit(`rel physics:body0 = <../${joint.parent}>`);
|
|
248
|
-
this.emit(`rel physics:body1 = <../${joint.child}>`);
|
|
249
|
-
this.emit('');
|
|
250
|
-
// Joint frames
|
|
251
|
-
const origin = joint.props.joint_origin || joint.props.position;
|
|
252
|
-
if (origin) {
|
|
253
|
-
const pos = origin;
|
|
254
|
-
this.emit(`point3f physics:localPos0 = (${pos[0]}, ${pos[1]}, ${pos[2]})`);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
this.emit('point3f physics:localPos0 = (0, 0, 0)');
|
|
258
|
-
}
|
|
259
|
-
this.emit('quatf physics:localRot0 = (1, 0, 0, 0)');
|
|
260
|
-
this.emit('');
|
|
261
|
-
this.emit('point3f physics:localPos1 = (0, 0, 0)');
|
|
262
|
-
this.emit('quatf physics:localRot1 = (1, 0, 0, 0)');
|
|
263
|
-
this.emit('');
|
|
264
|
-
// Joint axis (support both old and new property names)
|
|
265
|
-
const axis = joint.props.joint_axis || joint.props.axis;
|
|
266
|
-
if (axis) {
|
|
267
|
-
const axisVec = axis;
|
|
268
|
-
const axisName = this.vectorToAxisName(axisVec);
|
|
269
|
-
this.emit(`uniform token physics:axis = "${axisName}"`);
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
this.emit('uniform token physics:axis = "Y"');
|
|
273
|
-
}
|
|
274
|
-
this.emit('');
|
|
275
|
-
// Joint limits (support both old and new property names)
|
|
276
|
-
const limits = joint.props.joint_limits || joint.props.limits;
|
|
277
|
-
if (limits) {
|
|
278
|
-
const limitsVec = limits;
|
|
279
|
-
this.emit(`float physics:lowerLimit = ${limitsVec[0]}`);
|
|
280
|
-
this.emit(`float physics:upperLimit = ${limitsVec[1]}`);
|
|
281
|
-
}
|
|
282
|
-
this.emit('');
|
|
283
|
-
// Joint dynamics (support both old and new property names)
|
|
284
|
-
const effort = joint.props.joint_effort || joint.props.max_effort;
|
|
285
|
-
if (effort) {
|
|
286
|
-
this.emit(`float physics:maxForce = ${effort}`);
|
|
287
|
-
}
|
|
288
|
-
const velocity = joint.props.max_velocity;
|
|
289
|
-
if (velocity) {
|
|
290
|
-
this.emit(`float physics:maxVelocity = ${velocity}`);
|
|
291
|
-
}
|
|
292
|
-
this.indentLevel--;
|
|
293
|
-
this.emit('}');
|
|
294
|
-
}
|
|
295
|
-
indent() {
|
|
296
|
-
return ' '.repeat(this.indentLevel);
|
|
297
|
-
}
|
|
298
|
-
emit(line) {
|
|
299
|
-
this.output.push(this.indent() + line);
|
|
300
|
-
}
|
|
301
|
-
geometryToUSD(geometry) {
|
|
302
|
-
const mapping = {
|
|
303
|
-
'cylinder': 'Cylinder',
|
|
304
|
-
'box': 'Cube',
|
|
305
|
-
'sphere': 'Sphere',
|
|
306
|
-
'cone': 'Cone',
|
|
307
|
-
'plane': 'Plane',
|
|
308
|
-
'torus': 'Torus',
|
|
309
|
-
};
|
|
310
|
-
return mapping[geometry] || 'Cube';
|
|
311
|
-
}
|
|
312
|
-
vectorToAxisName(axis) {
|
|
313
|
-
const [x = 0, y = 0, z = 1] = axis;
|
|
314
|
-
if (Math.abs(x) > Math.abs(y) && Math.abs(x) > Math.abs(z)) {
|
|
315
|
-
return 'X';
|
|
316
|
-
}
|
|
317
|
-
else if (Math.abs(y) > Math.abs(z)) {
|
|
318
|
-
return 'Y';
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
return 'Z';
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
exports.USDCodeGen = USDCodeGen;
|
|
326
|
-
// Example usage
|
|
327
|
-
if (require.main === module) {
|
|
328
|
-
Promise.resolve().then(() => __importStar(require('./lexer'))).then(({ Lexer }) => {
|
|
329
|
-
Promise.resolve().then(() => __importStar(require('./parser'))).then(({ Parser }) => {
|
|
330
|
-
const source = `
|
|
331
|
-
composition "TwoLinkArm" {
|
|
332
|
-
object "Base" @static {
|
|
333
|
-
geometry: "cylinder"
|
|
334
|
-
dimensions: [0.1, 0.05]
|
|
335
|
-
material: "metal"
|
|
336
|
-
position: [0, 0, 0]
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
object "Link1" @joint_revolute {
|
|
340
|
-
geometry: "box"
|
|
341
|
-
dimensions: [0.5, 0.1, 0.1]
|
|
342
|
-
material: "metal"
|
|
343
|
-
joint_parent: "Base"
|
|
344
|
-
joint_axis: [0, 0, 1]
|
|
345
|
-
joint_limits: [-3.14, 3.14]
|
|
346
|
-
joint_effort: 100
|
|
347
|
-
mass: 1.0
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
`;
|
|
351
|
-
const lexer = new Lexer(source);
|
|
352
|
-
const tokens = lexer.tokenize();
|
|
353
|
-
const parser = new Parser(tokens);
|
|
354
|
-
const ast = parser.parse();
|
|
355
|
-
const codegen = new USDCodeGen();
|
|
356
|
-
const usd = codegen.generate(ast);
|
|
357
|
-
console.log('USD Output:');
|
|
358
|
-
console.log(usd);
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Complete Robotics Example: 2-DOF Robot Arm
|
|
3
|
-
* Demonstrates compile-time (USD/URDF) + runtime (ROS2/VR control)
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - @ros_node: ROS2 runtime integration
|
|
7
|
-
* - @gazebo_sim: Gazebo physics simulation
|
|
8
|
-
* - @urdf_export: Export to URDF format
|
|
9
|
-
* - @robot_joint: Articulated joints with limits
|
|
10
|
-
* - VR teleoperation: Control robot with VR controllers
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
scene "Robotics Lab" {
|
|
14
|
-
background: "#1a1a2e";
|
|
15
|
-
lighting: "industrial";
|
|
16
|
-
camera_position: [2, 1.5, 3];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// 2-DOF Robot Arm with ROS2 integration
|
|
20
|
-
object "TwoLinkArm" @ros_node @gazebo_sim @urdf_export {
|
|
21
|
-
// ROS2 configuration
|
|
22
|
-
ros_bridge_url: "ws://localhost:9090",
|
|
23
|
-
namespace: "/robot_arm",
|
|
24
|
-
tf_prefix: "arm_",
|
|
25
|
-
|
|
26
|
-
// Gazebo simulation
|
|
27
|
-
gazebo_world: "empty.world",
|
|
28
|
-
physics_engine: "ode",
|
|
29
|
-
gravity: [0, 0, -9.81],
|
|
30
|
-
|
|
31
|
-
// Robot structure
|
|
32
|
-
links: [
|
|
33
|
-
{
|
|
34
|
-
name: "base_link",
|
|
35
|
-
visual: {
|
|
36
|
-
geometry: "cylinder",
|
|
37
|
-
dimensions: [0.1, 0.05],
|
|
38
|
-
material: "steel",
|
|
39
|
-
},
|
|
40
|
-
collision: { geometry: "cylinder", dimensions: [0.1, 0.05] },
|
|
41
|
-
inertial: { mass: 1.0, inertia: [0.01, 0.01, 0.01, 0, 0, 0] },
|
|
42
|
-
position: [0, 0, 0],
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "link1",
|
|
46
|
-
visual: {
|
|
47
|
-
geometry: "box",
|
|
48
|
-
dimensions: [0.05, 0.05, 0.5],
|
|
49
|
-
material: "aluminum",
|
|
50
|
-
},
|
|
51
|
-
collision: { geometry: "box", dimensions: [0.05, 0.05, 0.5] },
|
|
52
|
-
inertial: { mass: 0.5, inertia: [0.01, 0.01, 0.001, 0, 0, 0] },
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "link2",
|
|
56
|
-
visual: {
|
|
57
|
-
geometry: "box",
|
|
58
|
-
dimensions: [0.05, 0.05, 0.4],
|
|
59
|
-
material: "carbon_fiber",
|
|
60
|
-
},
|
|
61
|
-
collision: { geometry: "box", dimensions: [0.05, 0.05, 0.4] },
|
|
62
|
-
inertial: { mass: 0.3, inertia: [0.005, 0.005, 0.001, 0, 0, 0] },
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
|
|
66
|
-
// Joints with actuators
|
|
67
|
-
joints: [
|
|
68
|
-
{
|
|
69
|
-
name: "shoulder_joint",
|
|
70
|
-
type: "revolute",
|
|
71
|
-
parent: "base_link",
|
|
72
|
-
child: "link1",
|
|
73
|
-
axis: [0, 0, 1],
|
|
74
|
-
origin: { position: [0, 0, 0.05], rotation: [0, 0, 0] },
|
|
75
|
-
limits: { lower: -3.14, upper: 3.14, effort: 10.0, velocity: 2.0 },
|
|
76
|
-
actuator: {
|
|
77
|
-
type: "position",
|
|
78
|
-
kp: 100.0,
|
|
79
|
-
kd: 10.0,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: "elbow_joint",
|
|
84
|
-
type: "revolute",
|
|
85
|
-
parent: "link1",
|
|
86
|
-
child: "link2",
|
|
87
|
-
axis: [0, 1, 0],
|
|
88
|
-
origin: { position: [0, 0, 0.5], rotation: [0, 0, 0] },
|
|
89
|
-
limits: { lower: -2.0, upper: 2.0, effort: 5.0, velocity: 1.5 },
|
|
90
|
-
actuator: {
|
|
91
|
-
type: "position",
|
|
92
|
-
kp: 50.0,
|
|
93
|
-
kd: 5.0,
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
|
|
98
|
-
// VR teleoperation
|
|
99
|
-
vr_control: {
|
|
100
|
-
enabled: true,
|
|
101
|
-
controller_mapping: {
|
|
102
|
-
left_hand: "shoulder_joint", // Left VR controller → shoulder
|
|
103
|
-
right_hand: "elbow_joint", // Right VR controller → elbow
|
|
104
|
-
},
|
|
105
|
-
force_feedback: true, // Haptic feedback when robot hits limits
|
|
106
|
-
inverse_kinematics: true, // IK solver for end-effector control
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
// Digital twin sync
|
|
110
|
-
twin_sync: {
|
|
111
|
-
enabled: true,
|
|
112
|
-
sync_rate: 30, // 30 Hz (sync every 33ms)
|
|
113
|
-
bidirectional: true, // Real robot ↔ VR twin
|
|
114
|
-
sensors: [
|
|
115
|
-
{ type: "joint_states", topic: "/joint_states" },
|
|
116
|
-
{ type: "tf", topic: "/tf" },
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
// Export options
|
|
121
|
-
export: {
|
|
122
|
-
urdf: "build/two_link_arm.urdf",
|
|
123
|
-
sdf: "build/two_link_arm.sdf",
|
|
124
|
-
usd: "build/two_link_arm.usd", // For NVIDIA Isaac Sim
|
|
125
|
-
mjcf: "build/two_link_arm.xml", // For MuJoCo
|
|
126
|
-
},
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Example 2: Mobile robot (differential drive)
|
|
130
|
-
object "MobileBot" @ros_node @gazebo_sim {
|
|
131
|
-
ros_bridge_url: "ws://localhost:9090",
|
|
132
|
-
namespace: "/mobile_bot",
|
|
133
|
-
|
|
134
|
-
// Chassis
|
|
135
|
-
links: [
|
|
136
|
-
{
|
|
137
|
-
name: "chassis",
|
|
138
|
-
visual: { geometry: "box", dimensions: [0.4, 0.3, 0.1], material: "plastic" },
|
|
139
|
-
inertial: { mass: 5.0 },
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: "left_wheel",
|
|
143
|
-
visual: { geometry: "cylinder", dimensions: [0.1, 0.05], material: "rubber" },
|
|
144
|
-
inertial: { mass: 0.5 },
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: "right_wheel",
|
|
148
|
-
visual: { geometry: "cylinder", dimensions: [0.1, 0.05], material: "rubber" },
|
|
149
|
-
inertial: { mass: 0.5 },
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
|
|
153
|
-
joints: [
|
|
154
|
-
{
|
|
155
|
-
name: "left_wheel_joint",
|
|
156
|
-
type: "continuous",
|
|
157
|
-
parent: "chassis",
|
|
158
|
-
child: "left_wheel",
|
|
159
|
-
axis: [0, 1, 0],
|
|
160
|
-
actuator: { type: "velocity" },
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: "right_wheel_joint",
|
|
164
|
-
type: "continuous",
|
|
165
|
-
parent: "chassis",
|
|
166
|
-
child: "right_wheel",
|
|
167
|
-
axis: [0, 1, 0],
|
|
168
|
-
actuator: { type: "velocity" },
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
|
|
172
|
-
// Differential drive controller
|
|
173
|
-
drive_controller: {
|
|
174
|
-
type: "diff_drive",
|
|
175
|
-
wheel_separation: 0.3,
|
|
176
|
-
wheel_radius: 0.1,
|
|
177
|
-
max_velocity: 1.0, // m/s
|
|
178
|
-
max_angular_velocity: 2.0, // rad/s
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
// Sensors
|
|
182
|
-
sensors: [
|
|
183
|
-
{
|
|
184
|
-
name: "lidar",
|
|
185
|
-
type: "laser",
|
|
186
|
-
update_rate: 10,
|
|
187
|
-
topic: "/scan",
|
|
188
|
-
range: { min: 0.1, max: 10.0 },
|
|
189
|
-
fov: 360,
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
name: "camera",
|
|
193
|
-
type: "camera",
|
|
194
|
-
update_rate: 30,
|
|
195
|
-
topic: "/camera/image_raw",
|
|
196
|
-
resolution: [640, 480],
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
}
|
package/python/ros2_bridge.py
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
ROS2 Bridge for HoloScript Robotics Plugin
|
|
4
|
-
Runtime integration with ROS2 via rosbridge_server
|
|
5
|
-
|
|
6
|
-
Requirements:
|
|
7
|
-
pip install roslibpy
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
from ros2_bridge import ROS2Bridge
|
|
11
|
-
|
|
12
|
-
bridge = ROS2Bridge('ws://localhost:9090')
|
|
13
|
-
bridge.connect()
|
|
14
|
-
bridge.publish_joint_command('/joint_states', {...})
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
import sys
|
|
18
|
-
import json
|
|
19
|
-
from typing import Dict, Any, List
|
|
20
|
-
import roslibpy
|
|
21
|
-
|
|
22
|
-
class ROS2Bridge:
|
|
23
|
-
"""ROS2 runtime bridge for HoloScript robotics"""
|
|
24
|
-
|
|
25
|
-
def __init__(self, ros_bridge_url: str = 'ws://localhost:9090'):
|
|
26
|
-
self.ros_bridge_url = ros_bridge_url
|
|
27
|
-
self.client = None
|
|
28
|
-
self.subscribers = {}
|
|
29
|
-
self.publishers = {}
|
|
30
|
-
|
|
31
|
-
def connect(self) -> Dict[str, Any]:
|
|
32
|
-
"""Connect to ROS2 via rosbridge_server"""
|
|
33
|
-
try:
|
|
34
|
-
self.client = roslibpy.Ros(host=self.ros_bridge_url.replace('ws://', '').split(':')[0],
|
|
35
|
-
port=int(self.ros_bridge_url.split(':')[-1]))
|
|
36
|
-
self.client.run()
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
'status': 'success',
|
|
40
|
-
'connected': self.client.is_connected,
|
|
41
|
-
'url': self.ros_bridge_url,
|
|
42
|
-
}
|
|
43
|
-
except Exception as e:
|
|
44
|
-
return {
|
|
45
|
-
'status': 'failed',
|
|
46
|
-
'error': str(e),
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
def publish_joint_command(self, topic: str, joint_positions: Dict[str, float]) -> Dict[str, Any]:
|
|
50
|
-
"""Publish joint command to ROS2 topic"""
|
|
51
|
-
try:
|
|
52
|
-
if topic not in self.publishers:
|
|
53
|
-
self.publishers[topic] = roslibpy.Topic(self.client, topic, 'sensor_msgs/JointState')
|
|
54
|
-
|
|
55
|
-
msg = {
|
|
56
|
-
'name': list(joint_positions.keys()),
|
|
57
|
-
'position': list(joint_positions.values()),
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
self.publishers[topic].publish(roslibpy.Message(msg))
|
|
61
|
-
|
|
62
|
-
return {'status': 'success', 'topic': topic}
|
|
63
|
-
except Exception as e:
|
|
64
|
-
return {'status': 'failed', 'error': str(e)}
|
|
65
|
-
|
|
66
|
-
def get_status(self) -> Dict[str, Any]:
|
|
67
|
-
"""Get bridge status"""
|
|
68
|
-
return {
|
|
69
|
-
'connected': self.client.is_connected if self.client else False,
|
|
70
|
-
'url': self.ros_bridge_url,
|
|
71
|
-
'version': '1.0.0',
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if __name__ == '__main__':
|
|
75
|
-
bridge = ROS2Bridge()
|
|
76
|
-
status = bridge.connect()
|
|
77
|
-
print(f"ROS2 Bridge: {status}")
|