Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

urdf-loader

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

urdf-loader - npm Package Compare versions

Comparing version 0.5.3 to 0.6.0

15

CHANGELOG.md

@@ -7,2 +7,16 @@ # Changelog

## [0.6.0] - 2019-02-23
### Added
- Added `setAngle` and `setAngles` function to the Robot node
- Added `meshLoadFunc` and `urlModifierFunc` functions to `URDFViewer` component.
### Changed
- Changed `loadMeshCb` function API to take `url`, `manager`, and `done`.
- Materials defined as "shared" by name in the URDF are shared among meshes, now.
- The root link is now the same as the URDF Robot object.
- Moved the `packages` parameter to the options object
### Removed
- Removed `urdfLoader` and `loadingManager` fields from `URDFViewer` component.
## [0.5.3] - 2018-12-17

@@ -14,2 +28,3 @@ ### Added

- Scale being overwritten on models that are loaded with pre-set scale values.
- Loader not completing if a mesh could not be loaded.

@@ -16,0 +31,0 @@ ## [0.5.2] - 2018-12-5

11

example/dragAndDrop.js

@@ -109,3 +109,3 @@ /* globals animToggle viewer setColor */

const fileNames = Object.keys(files).map(n => cleanFilePath(n));
viewer.loadingManager.setURLModifier(url => {
viewer.urlModifierFunc = url => {

@@ -135,3 +135,3 @@ // find the matching file given the requested url

});
};

@@ -148,9 +148,2 @@ // set the source of the element to the most likely intended display model

// remove the url modifier function is it doesn't affect other actions
viewer.addEventListener(
'geometry-loaded',
() => viewer.loadingManager.setURLModifier(null),
{ once: true }
);
});

@@ -157,0 +150,0 @@

@@ -189,5 +189,6 @@ /* globals viewer THREE */

const modelLoader = new THREE.ModelLoader(viewer.loadingManager);
viewer.urdfLoader.defaultMeshLoader = (path, ext, done) => {
modelLoader.load(path, res => done(res.model));
viewer.loadMeshFunc = (path, manager, done) => {
new THREE.ModelLoader(manager).load(path, res => done(res.model), null, err => done(null, err));
};

@@ -194,0 +195,0 @@

{
"name": "urdf-loader",
"version": "0.5.3",
"version": "0.6.0",
"description": "URDF Loader for THREE.js and webcomponent viewer",

@@ -45,6 +45,6 @@ "main": "umd/URDFLoader.js",

"concurrently": "^4.0.1",
"eslint": "^5.4.0",
"jest": "^23.5.0",
"jest-cli": "^23.5.0",
"nyc": "^12.0.2",
"eslint": "^5.14.1",
"jest": "^24.1.0",
"jest-cli": "^24.1.0",
"nyc": "^13.3.0",
"opn-cli": "^3.1.0",

@@ -56,3 +56,3 @@ "puppeteer": "^1.7.0",

"static-server": "^3.0.0",
"three": "^0.94.0",
"three": "^0.101.1",
"threejs-model-loader": "0.0.1",

@@ -59,0 +59,0 @@ "wc-loader": "^1.1.12",

@@ -21,8 +21,8 @@ # javascript urdf-loader

'T12/urdf/T12.URDF', // The path to the URDF within the package OR absolute
{
packageName : '.../package/dir/' // The equivelant of a (list of) ROS package(s):// directory
},
robot => { }, // The robot is loaded!
{
loadMeshCb: (path, ext, done) => { }, // Callback for each mesh for custom mesh processing and loading code
packages: {
packageName : '.../package/dir/' // The equivalent of a (list of) ROS package(s):// directory
},
loadMeshCb: (path, manager, done) => { }, // Callback for each mesh for custom mesh processing and loading code
}

@@ -46,3 +46,3 @@ );

### load(urdfpath, packages, onComplete, options)
### load(urdfpath, onComplete, options)

@@ -57,6 +57,16 @@ Loads and builds the specified URDF robot in THREE.js

#### packages : String | Object
#### onComplete(robot) : Function
_required_
Callback that is called once the urdf robots have been loaded. The loaded robot is passed to the function.
See `URDFRobot` documentation.
#### options : Object
_optional_
##### options.packages : String | Object
The path representing the `package://` directory(s) to load `package://` relative files.

@@ -75,18 +85,12 @@

#### onComplete(robot) : Function
##### options.loadMeshCb(pathToModel, manager, onComplete) : Function
_required_
An optional function that can be used to override the default mesh loading functionality. The default loader is specified at `URDFLoader.defaultMeshLoader`.
Callback that is called once the urdf robots have been loaded. The loaded robot is passed to the function.
`pathToModel` is the url to load the model from.
See `URDFRobot` documentation.
`manager` is the THREE.js `LoadingManager` used by the `URDFLoader`.
#### options : Object
`onComplete` is called with the mesh once the geometry has been loaded.
_optional_
##### options.loadMeshCb(pathToModel, fileExtension, onComplete) : Function
An optional function that can be used to override the default mesh loading functionality. The default loader is specified at `URDFLoader.defaultMeshLoader`. `onComplete` is called with the mesh once the geometry has been loaded.
##### options.fetchOptions : Object

@@ -102,3 +106,3 @@

### parse(urdfContent, packages, onComplete, options) : THREE.Object3D
### parse(urdfContent, options) : THREE.Object3D

@@ -113,8 +117,2 @@ Parses URDF content and returns the robot model.

#### packages : String | Object
_required_
See `load`.
#### onComplete(robot) : Function

@@ -134,24 +132,8 @@

## URDFRobot
## URDFJoint : THREE.Object3D
Object that describes the URDF Robot. An extension of `THREE.Object3D`.
An object representing a robot joint.
#### name : String
The name of the robot described in the `<robot>` tag.
#### links : Object
A dictionary of `linkName : URDFLink` with all links in the robot.
#### joints : Object
A dictionary of `jointName : URDFJoint` with all joints in the robot.
## URDFJoint
An object representing a robot joint. An extension of `THREE.Object3D`.
#### name : String
The name of the joint.

@@ -187,3 +169,3 @@

## URDFLink
## URDFLink : THREE.Object3D

@@ -194,2 +176,18 @@ #### name

## URDFRobot : URDFLink
Object that describes the URDF Robot.
#### robotName : String
The name of the robot described in the `<robot>` tag.
#### links : Object
A dictionary of `linkName : URDFLink` with all links in the robot.
#### joints : Object
A dictionary of `jointName : URDFJoint` with all joints in the robot.
## urdf-viewer Element

@@ -317,3 +315,3 @@ ```html

Run `npm run server`.
Run `npm start`.

@@ -320,0 +318,0 @@ Visit `localhost:9080/javascript/example/` to view the page.

@@ -47,6 +47,2 @@ import * as THREE from 'three';

get loadingManager() { return this._loadingManager = this._loadingManager || new THREE.LoadingManager(); }
get urdfLoader() { return this._urdfLoader = this._urdfLoader || new URDFLoader(this.loadingManager); }
get angles() {

@@ -75,2 +71,4 @@

this.robot = null;
this.loadMeshFunc = null;
this.urlModifierFunc = null;

@@ -92,2 +90,3 @@ // Scene setup

dirLight.castShadow = true;
dirLight.shadow.bias = -0.00001;
scene.add(dirLight);

@@ -145,5 +144,2 @@ scene.add(dirLight.target);

// redraw when something new has loaded
this.loadingManager.onLoad = () => this.recenter();
const _renderLoop = () => {

@@ -291,17 +287,16 @@

// Set the joint with jointname to
// Set the joint with jointName to
// angle in degrees
setAngle(jointname, angle) {
setAngle(jointName, angle) {
if (!this.robot) return;
if (!this.robot.joints[jointName]) return;
const joint = this.robot.joints[jointname];
if (joint && joint.angle !== angle) {
joint.setAngle(angle);
const origAngle = this.robot.joints[jointName].angle;
const newAngle = this.robot.setAngle(jointName, angle);
if (origAngle !== newAngle) {
this.redraw();
}
this.dispatchEvent(new CustomEvent('angle-change', { bubles: true, cancelable: true, detail: jointname }));
this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }));

@@ -404,3 +399,3 @@ }

if (c.type === 'Mesh') {
if (c.isMesh) {

@@ -462,45 +457,43 @@ c.castShadow = true;

this.urdfLoader.load(
urdf,
pkg,
let robot = null;
const manager = new THREE.LoadingManager();
manager.onLoad = () => {
robot => {
// If another request has come in to load a new
// robot, then ignore this one
if (this._requestId !== requestId) {
// If another request has come in to load a new
// robot, then ignore this one
if (this._requestId !== requestId) {
robot.traverse(c => c.dispose && c.dispose());
return;
robot.traverse(c => c.dispose && c.dispose());
return;
}
}
this.robot = robot;
this.world.add(robot);
updateMaterials(robot);
this.robot = robot;
this.world.add(robot);
updateMaterials(robot);
this._setIgnoreLimits(this.ignoreLimits);
this._setIgnoreLimits(this.ignoreLimits);
this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
this.recenter();
this.recenter();
};
},
if (this.urlModifierFunc) {
// options
{
loadMeshCb: (path, ext, done) => {
manager.setURLModifier(this.urlModifierFunc);
// Load meshes and enable shadow casting
this.urdfLoader.defaultMeshLoader(path, ext, mesh => {
}
updateMaterials(mesh);
done(mesh);
this.recenter();
new URDFLoader(manager).load(
urdf,
model => robot = model,
});
// options
{
},
packages: pkg,
loadMeshCb: this.loadMeshFunc,
fetchOptions: { mode: 'cors', credentials: 'same-origin' },

@@ -507,0 +500,0 @@

import { Object3D, Quaternion } from 'three';
class URDFRobot extends Object3D {
constructor(...args) {
super(...args);
this.isURDFRobot = true;
this.type = 'URDFRobot';
this.urdfNode = null;
this.links = null;
this.joints = null;
}
copy(source, recursive) {
super.copy(source, recursive);
this.links = {};
this.joints = {};
this.traverse(c => {
if (c.isURDFJoint && c.name in source.joints) {
this.joints[c.name] = c;
}
if (c.isURDFLink && c.name in source.links) {
this.links[c.name] = c;
}
});
return this;
}
}
class URDFLink extends Object3D {

@@ -223,2 +180,69 @@

class URDFRobot extends URDFLink {
constructor(...args) {
super(...args);
this.isURDFRobot = true;
this.urdfNode = null;
this.urdfRobotNode = null;
this.robotName = null;
this.links = null;
this.joints = null;
}
copy(source, recursive) {
super.copy(source, recursive);
this.urdfRobotNode = source.urdfRobotNode;
this.robotName = source.robotName;
this.links = {};
this.joints = {};
this.traverse(c => {
if (c.isURDFJoint && c.name in source.joints) {
this.joints[c.name] = c;
}
if (c.isURDFLink && c.name in source.links) {
this.links[c.name] = c;
}
});
return this;
}
setAngle(jointName, ...angle) {
const joint = this.joints[jointName];
if (joint) {
return joint.setAngle(...angle);
}
return null;
}
setAngles(angles) {
// TODO: How to handle other, multi-dimensional joint types?
for (const name in angles) this.setAngle(name, angles[name]);
}
}
export { URDFRobot, URDFLink, URDFJoint };

@@ -31,29 +31,30 @@ import * as THREE from 'three';

/* URDFLoader Class */
// Loads and reads a URDF file into a THREEjs Object3D format
export default
class URDFLoader {
// take a vector "x y z" and process it into
// an array [x, y, z]
function processTuple(val) {
// Cached mesh loaders
get STLLoader() {
if (!val) return [0, 0, 0];
return val.trim().split(/\s+/g).map(num => parseFloat(num));
this._stlloader = this._stlloader || new STLLoader(this.manager);
return this._stlloader;
}
}
// applies a rotation a threejs object in URDF order
function applyRotation(obj, rpy, additive = false) {
get DAELoader() {
// if additive is true the rotation is applied in
// addition to the existing rotation
if (!additive) obj.rotation.set(0, 0, 0);
this._daeloader = this._daeloader || new ColladaLoader(this.manager);
return this._daeloader;
tempEuler.set(rpy[0], rpy[1], rpy[2], 'ZYX');
tempQuaternion.setFromEuler(tempEuler);
tempQuaternion.multiply(obj.quaternion);
obj.quaternion.copy(tempQuaternion);
}
}
get TextureLoader() {
/* URDFLoader Class */
// Loads and reads a URDF file into a THREEjs Object3D format
export default
class URDFLoader {
this._textureloader = this._textureloader || new THREE.TextureLoader(this.manager);
return this._textureloader;
}
constructor(manager) {

@@ -65,47 +66,10 @@

/* Utilities */
// forEach and filter function wrappers because
// HTMLCollection does not the by default
forEach(coll, func) {
return [].forEach.call(coll, func);
}
filter(coll, func) {
return [].filter.call(coll, func);
}
// take a vector "x y z" and process it into
// an array [x, y, z]
_processTuple(val) {
if (!val) return [0, 0, 0];
return val.trim().split(/\s+/g).map(num => parseFloat(num));
}
// applies a rotation a threejs object in URDF order
_applyRotation(obj, rpy, additive = false) {
// if additive is true the rotation is applied in
// addition to the existing rotation
if (!additive) obj.rotation.set(0, 0, 0);
tempEuler.set(rpy[0], rpy[1], rpy[2], 'ZYX');
tempQuaternion.setFromEuler(tempEuler);
tempQuaternion.multiply(obj.quaternion);
obj.quaternion.copy(tempQuaternion);
}
/* Public API */
// urdf: The path to the URDF within the package OR absolute
// packages: The equivelant of a (list of) ROS package(s):// directory
// onComplete: Callback that is passed the model once loaded
load(urdf, packages, onComplete, options) {
load(urdf, onComplete, options) {
// Check if a full URI is specified before
// prepending the package info
const manager = this.manager;
const workingPath = THREE.LoaderUtils.extractUrlBase(urdf);

@@ -116,459 +80,417 @@ const urdfPath = this.manager.resolveURL(urdf);

manager.itemStart(urdfPath);
fetch(urdfPath, options.fetchOptions)
.then(res => res.text())
.then(data => this.parse(data, packages, onComplete, options));
.then(data => {
}
const model = this.parse(data, options);
onComplete(model);
manager.itemEnd(urdfPath);
parse(content, packages, onComplete, options) {
})
.catch(e => {
options = Object.assign({
// TODO: Add onProgress and onError functions here
console.error('URDFLoader: Error parsing file.', e);
manager.itemError(urdfPath);
manager.itemEnd(urdfPath);
loadMeshCb: this.defaultMeshLoader.bind(this),
workingPath: '',
});
}, options);
}
let result = null;
let meshCount = 0;
const loadMeshFunc = (path, ext, done) => {
parse(content, options = {}) {
meshCount++;
options.loadMeshCb(path, ext, (...args) => {
const packages = options.packages || '';
const loadMeshCb = options.loadMeshCb || this.defaultMeshLoader.bind(this);
const workingPath = options.workingPath || '';
const manager = this.manager;
const linkMap = {};
const jointMap = {};
const materialMap = {};
done(...args);
meshCount--;
if (meshCount === 0) {
// Resolves the path of mesh files
function resolvePath(path) {
requestAnimationFrame(() => {
if (typeof onComplete === 'function') {
onComplete(result);
}
});
if (!/^package:\/\//.test(path)) {
}
return workingPath ? workingPath + path : path;
});
}
};
result = this._processUrdf(content, packages, options.workingPath, loadMeshFunc);
// Remove "package://" keyword and split meshPath at the first slash
const [targetPkg, relPath] = path.replace(/^package:\/\//, '').split(/\/(.+)/);
if (meshCount === 0 && typeof onComplete === 'function') {
if (typeof packages === 'string') {
onComplete(result);
onComplete = null;
// "pkg" is one single package
if (packages.endsWith(targetPkg)) {
}
// "pkg" is the target package
return packages + '/' + relPath;
return result;
} else {
}
// Assume "pkg" is the target package's parent directory
return packages + '/' + targetPkg + '/' + relPath;
// Default mesh loading function
defaultMeshLoader(path, ext, done) {
}
if (/\.stl$/i.test(path)) {
} else if (typeof packages === 'object') {
this.STLLoader.load(path, geom => {
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial());
done(mesh);
});
// "pkg" is a map of packages
if (targetPkg in packages) {
} else if (/\.dae$/i.test(path)) {
return packages[targetPkg] + '/' + relPath;
this.DAELoader.load(path, dae => done(dae.scene));
} else {
} else {
console.error(`URDFLoader : ${ targetPkg } not found in provided package list.`);
return null;
console.warn(`URDFLoader: Could not load model at ${ path }.\nNo loader available`);
}
}
}
}
// Process the URDF text format
function processUrdf(data) {
/* Private Functions */
const parser = new DOMParser();
const urdf = parser.parseFromString(data, 'text/xml');
const children = [ ...urdf.children ];
// Resolves the path of mesh files
_resolvePackagePath(pkg, meshPath, currPath) {
const robotNode = children.filter(c => c.nodeName === 'robot').pop();
return processRobot(robotNode);
if (!/^package:\/\//.test(meshPath)) {
}
return currPath !== undefined ? currPath + meshPath : meshPath;
// Process the <robot> node
function processRobot(robot) {
}
const robotNodes = [ ...robot.children ];
const links = robotNodes.filter(c => c.nodeName.toLowerCase() === 'link');
const joints = robotNodes.filter(c => c.nodeName.toLowerCase() === 'joint');
const materials = robotNodes.filter(c => c.nodeName.toLowerCase() === 'material');
const obj = new URDFRobot();
// Remove "package://" keyword and split meshPath at the first slash
const [targetPkg, relPath] = meshPath.replace(/^package:\/\//, '').split(/\/(.+)/);
obj.robotName = robot.getAttribute('name');
obj.urdfRobotNode = robot;
if (typeof pkg === 'string') {
// Create the <material> map
materials.forEach(m => {
// "pkg" is one single package
if (pkg.endsWith(targetPkg)) {
const name = m.getAttribute('name');
materialMap[name] = processMaterial(m);
// "pkg" is the target package
return pkg + '/' + relPath;
});
} else {
// Create the <link> map
links.forEach(l => {
// Assume "pkg" is the target package's parent directory
return pkg + '/' + targetPkg + '/' + relPath;
const name = l.getAttribute('name');
const isRoot = robot.querySelector(`child[link="${ name }"]`) === null;
linkMap[name] = processLink(l, isRoot ? obj : null);
}
});
} else if (typeof pkg === 'object') {
// Create the <joint> map
joints.forEach(j => {
// "pkg" is a map of packages
if (targetPkg in pkg) {
const name = j.getAttribute('name');
jointMap[name] = processJoint(j);
return pkg[targetPkg] + '/' + relPath;
});
} else {
obj.joints = jointMap;
obj.links = linkMap;
console.error(`URDFLoader : ${ targetPkg } not found in provided package list!`);
return null;
return obj;
}
}
}
// Process the URDF text format
_processUrdf(data, packages, path, loadMeshCb) {
// Process joint nodes and parent them
function processJoint(joint) {
const parser = new DOMParser();
const urdf = parser.parseFromString(data, 'text/xml');
const children = [ ...joint.children ];
const jointType = joint.getAttribute('type');
const obj = new URDFJoint();
obj.urdfNode = joint;
obj.name = joint.getAttribute('name');
obj.jointType = jointType;
const robottag = this.filter(urdf.children, c => c.nodeName === 'robot').pop();
return this._processRobot(robottag, packages, path, loadMeshCb);
let parent = null;
let child = null;
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
}
// Extract the attributes
children.forEach(n => {
// Process the <robot> node
_processRobot(robot, packages, path, loadMeshCb) {
const type = n.nodeName.toLowerCase();
if (type === 'origin') {
const materials = robot.querySelectorAll('material');
const links = [];
const joints = [];
const obj = new URDFRobot();
obj.name = robot.getAttribute('name');
xyz = processTuple(n.getAttribute('xyz'));
rpy = processTuple(n.getAttribute('rpy'));
// Process the <joint> and <link> nodes
this.forEach(robot.children, n => {
} else if (type === 'child') {
const type = n.nodeName.toLowerCase();
if (type === 'link') links.push(n);
else if (type === 'joint') joints.push(n);
child = linkMap[n.getAttribute('link')];
});
} else if (type === 'parent') {
// Create the <material> map
const materialMap = {};
this.forEach(materials, m => {
parent = linkMap[n.getAttribute('link')];
const name = m.getAttribute('name');
if (!materialMap[name]) {
} else if (type === 'limit') {
materialMap[name] = {};
this.forEach(m.children, c => {
obj.limit.lower = parseFloat(n.getAttribute('lower') || obj.limit.lower);
obj.limit.upper = parseFloat(n.getAttribute('upper') || obj.limit.upper);
this._processMaterial(
materialMap[name],
c,
packages,
path
);
}
});
});
}
// Join the links
parent.add(obj);
obj.add(child);
applyRotation(obj, rpy);
obj.position.set(xyz[0], xyz[1], xyz[2]);
});
// Set up the rotate function
const axisNode = children.filter(n => n.nodeName.toLowerCase() === 'axis')[0];
// Create the <link> map
const linkMap = {};
this.forEach(links, l => {
if (axisNode) {
const name = l.getAttribute('name');
linkMap[name] = this._processLink(l, materialMap, packages, path, loadMeshCb);
const axisXYZ = axisNode.getAttribute('xyz').split(/\s+/g).map(num => parseFloat(num));
obj.axis = new THREE.Vector3(axisXYZ[0], axisXYZ[1], axisXYZ[2]);
obj.axis.normalize();
});
}
// Create the <joint> map
const jointMap = {};
this.forEach(joints, j => {
return obj;
const name = j.getAttribute('name');
jointMap[name] = this._processJoint(j, linkMap);
}
});
// Process the <link> nodes
function processLink(link, target = null) {
for (const key in linkMap) {
if (target === null) {
if (linkMap[key].parent == null) {
target = new URDFLink();
obj.add(linkMap[key]);
}
}
const children = [ ...link.children ];
const visualNodes = children.filter(n => n.nodeName.toLowerCase() === 'visual');
target.name = link.getAttribute('name');
target.urdfNode = link;
obj.joints = jointMap;
obj.links = linkMap;
visualNodes.forEach(vn => processVisualNode(vn, target, materialMap));
return obj;
return target;
}
}
// Process joint nodes and parent them
_processJoint(joint, linkMap) {
function processMaterial(node) {
const jointType = joint.getAttribute('type');
const obj = new URDFJoint();
obj.urdfNode = joint;
obj.name = joint.getAttribute('name');
obj.jointType = jointType;
const matNodes = [ ...node.children ];
const material = new THREE.MeshPhongMaterial();
let parent = null;
let child = null;
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
material.name = node.getAttribute('name') || '';
matNodes.forEach(n => {
// Extract the attributes
this.forEach(joint.children, n => {
const type = n.nodeName.toLowerCase();
if (type === 'color') {
const type = n.nodeName.toLowerCase();
if (type === 'origin') {
const rgba =
n
.getAttribute('rgba')
.split(/\s/g)
.map(v => parseFloat(v));
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
material.color.setRGB(rgba[0], rgba[1], rgba[2]);
material.opacity = rgba[3];
material.transparent = rgba[3] < 1;
} else if (type === 'child') {
} else if (type === 'texture') {
child = linkMap[n.getAttribute('link')];
const loader = new THREE.TextureLoader(manager);
const filename = n.getAttribute('filename');
const filePath = resolvePath(filename);
material.map = loader.load(filePath);
} else if (type === 'parent') {
}
});
parent = linkMap[n.getAttribute('link')];
return material;
} else if (type === 'limit') {
obj.limit.lower = parseFloat(n.getAttribute('lower') || obj.limit.lower);
obj.limit.upper = parseFloat(n.getAttribute('upper') || obj.limit.upper);
}
});
// Join the links
parent.add(obj);
obj.add(child);
this._applyRotation(obj, rpy);
obj.position.set(xyz[0], xyz[1], xyz[2]);
// Set up the rotate function
const axisnode = this.filter(joint.children, n => n.nodeName.toLowerCase() === 'axis')[0];
if (axisnode) {
const axisxyz = axisnode.getAttribute('xyz').split(/\s+/g).map(num => parseFloat(num));
obj.axis = new THREE.Vector3(axisxyz[0], axisxyz[1], axisxyz[2]);
obj.axis.normalize();
}
return obj;
// Process the visual nodes into meshes
function processVisualNode(vn, linkObj, materialMap) {
}
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
let scale = [1, 1, 1];
// Process the <link> nodes
_processLink(link, materialMap, packages, path, loadMeshCb) {
const children = [ ...vn.children ];
let material = null;
let primitiveModel = null;
const visualNodes = this.filter(link.children, n => n.nodeName.toLowerCase() === 'visual');
const obj = new URDFLink();
obj.name = link.getAttribute('name');
obj.urdfNode = link;
// get the material first
const materialNode = children.filter(n => n.nodeName.toLowerCase() === 'material')[0];
if (materialNode) {
this.forEach(visualNodes, vn => this._processVisualNode(vn, obj, materialMap, packages, path, loadMeshCb));
const name = materialNode.getAttribute('name');
if (name && name in materialMap) {
return obj;
material = materialMap[name];
}
} else {
_processMaterial(material, node, packages, path) {
material = processMaterial(materialNode);
const type = node.nodeName.toLowerCase();
if (type === 'color') {
}
const rgba =
node
.getAttribute('rgba')
.split(/\s/g)
.map(v => parseFloat(v));
} else {
this._copyMaterialAttributes(
material,
{
color: new THREE.Color(rgba[0], rgba[1], rgba[2]),
opacity: rgba[3],
transparent: rgba[3] < 1,
});
material = new THREE.MeshPhongMaterial();
} else if (type === 'texture') {
}
const filename = node.getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
this._copyMaterialAttributes(
material,
{
map: this.TextureLoader.load(filePath),
});
children.forEach(n => {
}
}
const type = n.nodeName.toLowerCase();
if (type === 'geometry') {
_copyMaterialAttributes(material, materialAttributes) {
const geoType = n.children[0].nodeName.toLowerCase();
if (geoType === 'mesh') {
if ('color' in materialAttributes) {
const filename = n.children[0].getAttribute('filename');
const filePath = resolvePath(filename);
material.color = materialAttributes.color.clone();
material.opacity = materialAttributes.opacity;
material.transparent = materialAttributes.transparent;
// file path is null if a package directory is not provided.
if (filePath !== null) {
}
const scaleAttr = n.children[0].getAttribute('scale');
if (scaleAttr) scale = processTuple(scaleAttr);
if ('map' in materialAttributes) {
loadMeshCb(filePath, manager, (obj, err) => {
material.map = materialAttributes.map.clone();
if (err) {
}
console.error('URDFLoader: Error loading mesh.', err);
}
} else if (obj) {
// Process the visual nodes into meshes
_processVisualNode(vn, linkObj, materialMap, packages, path, loadMeshCb) {
if (obj instanceof THREE.Mesh) {
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
let scale = [1, 1, 1];
obj.material = material;
const material = new THREE.MeshPhongMaterial();
let primitiveModel = null;
this.forEach(vn.children, n => {
}
const type = n.nodeName.toLowerCase();
if (type === 'geometry') {
linkObj.add(obj);
const geoType = n.children[0].nodeName.toLowerCase();
if (geoType === 'mesh') {
obj.position.set(xyz[0], xyz[1], xyz[2]);
obj.rotation.set(0, 0, 0);
const filename = n.children[0].getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
// multiply the existing scale by the scale components because
// the loaded model could have important scale values already applied
// to the root. Collada files, for example, can load in with a scale
// to convert the model units to meters.
obj.scale.x *= scale[0];
obj.scale.y *= scale[1];
obj.scale.z *= scale[2];
// file path is null if a package directory is not provided.
if (filePath !== null) {
applyRotation(obj, rpy);
const ext = filePath.match(/.*\.([A-Z0-9]+)$/i).pop() || '';
const scaleAttr = n.children[0].getAttribute('scale');
if (scaleAttr) scale = this._processTuple(scaleAttr);
loadMeshCb(filePath, ext, obj => {
if (obj) {
if (obj instanceof THREE.Mesh) {
obj.material.copy(material);
}
linkObj.add(obj);
});
obj.position.set(xyz[0], xyz[1], xyz[2]);
obj.rotation.set(0, 0, 0);
}
// multiply the existing scale by the scale components because
// the loaded model could have important scale values already applied
// to the root. Collada files, for example, can load in with a scale
// to convert the model units to meters.
obj.scale.x *= scale[0];
obj.scale.y *= scale[1];
obj.scale.z *= scale[2];
} else if (geoType === 'box') {
this._applyRotation(obj, rpy);
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.BoxBufferGeometry(1, 1, 1);
primitiveModel.material = material;
}
const size = processTuple(n.children[0].getAttribute('size'));
});
linkObj.add(primitiveModel);
primitiveModel.scale.set(size[0], size[1], size[2]);
}
} else if (geoType === 'sphere') {
} else if (geoType === 'box') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.SphereBufferGeometry(1, 30, 30);
primitiveModel.material = material;
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.BoxBufferGeometry(1, 1, 1);
primitiveModel.material = material;
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
primitiveModel.scale.set(radius, radius, radius);
const size = this._processTuple(n.children[0].getAttribute('size'));
linkObj.add(primitiveModel);
linkObj.add(primitiveModel);
primitiveModel.scale.set(size[0], size[1], size[2]);
} else if (geoType === 'cylinder') {
} else if (geoType === 'sphere') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 30);
primitiveModel.material = material;
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.SphereBufferGeometry(1, 30, 30);
primitiveModel.material = material;
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
const length = parseFloat(n.children[0].getAttribute('length')) || 0;
primitiveModel.scale.set(radius, length, radius);
primitiveModel.rotation.set(Math.PI / 2, 0, 0);
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
primitiveModel.scale.set(radius, radius, radius);
linkObj.add(primitiveModel);
linkObj.add(primitiveModel);
}
} else if (geoType === 'cylinder') {
} else if (type === 'origin') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 30);
primitiveModel.material = material;
xyz = processTuple(n.getAttribute('xyz'));
rpy = processTuple(n.getAttribute('rpy'));
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
const length = parseFloat(n.children[0].getAttribute('length')) || 0;
primitiveModel.scale.set(radius, length, radius);
primitiveModel.rotation.set(Math.PI / 2, 0, 0);
}
linkObj.add(primitiveModel);
});
}
// apply the position and rotation to the primitive geometry after
// the fact because it's guaranteed to have been scraped from the child
// nodes by this point
if (primitiveModel) {
} else if (type === 'origin') {
applyRotation(primitiveModel, rpy, true);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
}
} else if (type === 'material') {
}
const materialName = n.getAttribute('name');
if (materialName) {
return processUrdf(content);
this._copyMaterialAttributes(material, materialMap[materialName]);
}
} else {
// Default mesh loading function
defaultMeshLoader(path, manager, done) {
this.forEach(n.children, c => {
if (/\.stl$/i.test(path)) {
this._processMaterial(material, c, packages, path);
const loader = new STLLoader(manager);
loader.load(path, geom => {
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial());
done(mesh);
});
});
} else if (/\.dae$/i.test(path)) {
}
const loader = new ColladaLoader(manager);
loader.load(path, dae => done(dae.scene));
}
});
} else {
// apply the position and rotation to the primitive geometry after
// the fact because it's guaranteed to have been scraped from the child
// nodes by this point
if (primitiveModel) {
console.warn(`URDFLoader: Could not load model at ${ path }.\nNo loader available`);
this._applyRotation(primitiveModel, rpy, true);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);
}

@@ -575,0 +497,0 @@

@@ -50,6 +50,2 @@ (function (global, factory) {

get loadingManager() { return this._loadingManager = this._loadingManager || new THREE.LoadingManager(); }
get urdfLoader() { return this._urdfLoader = this._urdfLoader || new URDFLoader(this.loadingManager); }
get angles() {

@@ -78,2 +74,4 @@

this.robot = null;
this.loadMeshFunc = null;
this.urlModifierFunc = null;

@@ -95,2 +93,3 @@ // Scene setup

dirLight.castShadow = true;
dirLight.shadow.bias = -0.00001;
scene.add(dirLight);

@@ -148,5 +147,2 @@ scene.add(dirLight.target);

// redraw when something new has loaded
this.loadingManager.onLoad = () => this.recenter();
const _renderLoop = () => {

@@ -294,17 +290,16 @@

// Set the joint with jointname to
// Set the joint with jointName to
// angle in degrees
setAngle(jointname, angle) {
setAngle(jointName, angle) {
if (!this.robot) return;
if (!this.robot.joints[jointName]) return;
const joint = this.robot.joints[jointname];
if (joint && joint.angle !== angle) {
joint.setAngle(angle);
const origAngle = this.robot.joints[jointName].angle;
const newAngle = this.robot.setAngle(jointName, angle);
if (origAngle !== newAngle) {
this.redraw();
}
this.dispatchEvent(new CustomEvent('angle-change', { bubles: true, cancelable: true, detail: jointname }));
this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }));

@@ -407,3 +402,3 @@ }

if (c.type === 'Mesh') {
if (c.isMesh) {

@@ -465,45 +460,43 @@ c.castShadow = true;

this.urdfLoader.load(
urdf,
pkg,
let robot = null;
const manager = new THREE.LoadingManager();
manager.onLoad = () => {
robot => {
// If another request has come in to load a new
// robot, then ignore this one
if (this._requestId !== requestId) {
// If another request has come in to load a new
// robot, then ignore this one
if (this._requestId !== requestId) {
robot.traverse(c => c.dispose && c.dispose());
return;
robot.traverse(c => c.dispose && c.dispose());
return;
}
}
this.robot = robot;
this.world.add(robot);
updateMaterials(robot);
this.robot = robot;
this.world.add(robot);
updateMaterials(robot);
this._setIgnoreLimits(this.ignoreLimits);
this._setIgnoreLimits(this.ignoreLimits);
this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
this.recenter();
this.recenter();
};
},
if (this.urlModifierFunc) {
// options
{
loadMeshCb: (path, ext, done) => {
manager.setURLModifier(this.urlModifierFunc);
// Load meshes and enable shadow casting
this.urdfLoader.defaultMeshLoader(path, ext, mesh => {
}
updateMaterials(mesh);
done(mesh);
this.recenter();
new URDFLoader(manager).load(
urdf,
model => robot = model,
});
// options
{
},
packages: pkg,
loadMeshCb: this.loadMeshFunc,
fetchOptions: { mode: 'cors', credentials: 'same-origin' },

@@ -510,0 +503,0 @@

@@ -7,45 +7,2 @@ (function (global, factory) {

class URDFRobot extends THREE.Object3D {
constructor(...args) {
super(...args);
this.isURDFRobot = true;
this.type = 'URDFRobot';
this.urdfNode = null;
this.links = null;
this.joints = null;
}
copy(source, recursive) {
super.copy(source, recursive);
this.links = {};
this.joints = {};
this.traverse(c => {
if (c.isURDFJoint && c.name in source.joints) {
this.joints[c.name] = c;
}
if (c.isURDFLink && c.name in source.links) {
this.links[c.name] = c;
}
});
return this;
}
}
class URDFLink extends THREE.Object3D {

@@ -228,2 +185,69 @@

class URDFRobot extends URDFLink {
constructor(...args) {
super(...args);
this.isURDFRobot = true;
this.urdfNode = null;
this.urdfRobotNode = null;
this.robotName = null;
this.links = null;
this.joints = null;
}
copy(source, recursive) {
super.copy(source, recursive);
this.urdfRobotNode = source.urdfRobotNode;
this.robotName = source.robotName;
this.links = {};
this.joints = {};
this.traverse(c => {
if (c.isURDFJoint && c.name in source.joints) {
this.joints[c.name] = c;
}
if (c.isURDFLink && c.name in source.links) {
this.links[c.name] = c;
}
});
return this;
}
setAngle(jointName, ...angle) {
const joint = this.joints[jointName];
if (joint) {
return joint.setAngle(...angle);
}
return null;
}
setAngles(angles) {
// TODO: How to handle other, multi-dimensional joint types?
for (const name in angles) this.setAngle(name, angles[name]);
}
}
/*

@@ -254,28 +278,29 @@ Reference coordinate frames for THREE.js and ROS.

/* URDFLoader Class */
// Loads and reads a URDF file into a THREEjs Object3D format
class URDFLoader {
// take a vector "x y z" and process it into
// an array [x, y, z]
function processTuple(val) {
// Cached mesh loaders
get STLLoader() {
if (!val) return [0, 0, 0];
return val.trim().split(/\s+/g).map(num => parseFloat(num));
this._stlloader = this._stlloader || new STLLoader.STLLoader(this.manager);
return this._stlloader;
}
}
// applies a rotation a threejs object in URDF order
function applyRotation(obj, rpy, additive = false) {
get DAELoader() {
// if additive is true the rotation is applied in
// addition to the existing rotation
if (!additive) obj.rotation.set(0, 0, 0);
this._daeloader = this._daeloader || new ColladaLoader.ColladaLoader(this.manager);
return this._daeloader;
tempEuler.set(rpy[0], rpy[1], rpy[2], 'ZYX');
tempQuaternion.setFromEuler(tempEuler);
tempQuaternion.multiply(obj.quaternion);
obj.quaternion.copy(tempQuaternion);
}
}
get TextureLoader() {
/* URDFLoader Class */
// Loads and reads a URDF file into a THREEjs Object3D format
class URDFLoader {
this._textureloader = this._textureloader || new THREE.TextureLoader(this.manager);
return this._textureloader;
}
constructor(manager) {

@@ -287,47 +312,10 @@

/* Utilities */
// forEach and filter function wrappers because
// HTMLCollection does not the by default
forEach(coll, func) {
return [].forEach.call(coll, func);
}
filter(coll, func) {
return [].filter.call(coll, func);
}
// take a vector "x y z" and process it into
// an array [x, y, z]
_processTuple(val) {
if (!val) return [0, 0, 0];
return val.trim().split(/\s+/g).map(num => parseFloat(num));
}
// applies a rotation a threejs object in URDF order
_applyRotation(obj, rpy, additive = false) {
// if additive is true the rotation is applied in
// addition to the existing rotation
if (!additive) obj.rotation.set(0, 0, 0);
tempEuler.set(rpy[0], rpy[1], rpy[2], 'ZYX');
tempQuaternion.setFromEuler(tempEuler);
tempQuaternion.multiply(obj.quaternion);
obj.quaternion.copy(tempQuaternion);
}
/* Public API */
// urdf: The path to the URDF within the package OR absolute
// packages: The equivelant of a (list of) ROS package(s):// directory
// onComplete: Callback that is passed the model once loaded
load(urdf, packages, onComplete, options) {
load(urdf, onComplete, options) {
// Check if a full URI is specified before
// prepending the package info
const manager = this.manager;
const workingPath = THREE.LoaderUtils.extractUrlBase(urdf);

@@ -338,459 +326,417 @@ const urdfPath = this.manager.resolveURL(urdf);

manager.itemStart(urdfPath);
fetch(urdfPath, options.fetchOptions)
.then(res => res.text())
.then(data => this.parse(data, packages, onComplete, options));
.then(data => {
}
const model = this.parse(data, options);
onComplete(model);
manager.itemEnd(urdfPath);
parse(content, packages, onComplete, options) {
})
.catch(e => {
options = Object.assign({
// TODO: Add onProgress and onError functions here
console.error('URDFLoader: Error parsing file.', e);
manager.itemError(urdfPath);
manager.itemEnd(urdfPath);
loadMeshCb: this.defaultMeshLoader.bind(this),
workingPath: '',
});
}, options);
}
let result = null;
let meshCount = 0;
const loadMeshFunc = (path, ext, done) => {
parse(content, options = {}) {
meshCount++;
options.loadMeshCb(path, ext, (...args) => {
const packages = options.packages || '';
const loadMeshCb = options.loadMeshCb || this.defaultMeshLoader.bind(this);
const workingPath = options.workingPath || '';
const manager = this.manager;
const linkMap = {};
const jointMap = {};
const materialMap = {};
done(...args);
meshCount--;
if (meshCount === 0) {
// Resolves the path of mesh files
function resolvePath(path) {
requestAnimationFrame(() => {
if (typeof onComplete === 'function') {
onComplete(result);
}
});
if (!/^package:\/\//.test(path)) {
}
return workingPath ? workingPath + path : path;
});
}
};
result = this._processUrdf(content, packages, options.workingPath, loadMeshFunc);
// Remove "package://" keyword and split meshPath at the first slash
const [targetPkg, relPath] = path.replace(/^package:\/\//, '').split(/\/(.+)/);
if (meshCount === 0 && typeof onComplete === 'function') {
if (typeof packages === 'string') {
onComplete(result);
onComplete = null;
// "pkg" is one single package
if (packages.endsWith(targetPkg)) {
}
// "pkg" is the target package
return packages + '/' + relPath;
return result;
} else {
}
// Assume "pkg" is the target package's parent directory
return packages + '/' + targetPkg + '/' + relPath;
// Default mesh loading function
defaultMeshLoader(path, ext, done) {
}
if (/\.stl$/i.test(path)) {
} else if (typeof packages === 'object') {
this.STLLoader.load(path, geom => {
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial());
done(mesh);
});
// "pkg" is a map of packages
if (targetPkg in packages) {
} else if (/\.dae$/i.test(path)) {
return packages[targetPkg] + '/' + relPath;
this.DAELoader.load(path, dae => done(dae.scene));
} else {
} else {
console.error(`URDFLoader : ${ targetPkg } not found in provided package list.`);
return null;
console.warn(`URDFLoader: Could not load model at ${ path }.\nNo loader available`);
}
}
}
}
// Process the URDF text format
function processUrdf(data) {
/* Private Functions */
const parser = new DOMParser();
const urdf = parser.parseFromString(data, 'text/xml');
const children = [ ...urdf.children ];
// Resolves the path of mesh files
_resolvePackagePath(pkg, meshPath, currPath) {
const robotNode = children.filter(c => c.nodeName === 'robot').pop();
return processRobot(robotNode);
if (!/^package:\/\//.test(meshPath)) {
}
return currPath !== undefined ? currPath + meshPath : meshPath;
// Process the <robot> node
function processRobot(robot) {
}
const robotNodes = [ ...robot.children ];
const links = robotNodes.filter(c => c.nodeName.toLowerCase() === 'link');
const joints = robotNodes.filter(c => c.nodeName.toLowerCase() === 'joint');
const materials = robotNodes.filter(c => c.nodeName.toLowerCase() === 'material');
const obj = new URDFRobot();
// Remove "package://" keyword and split meshPath at the first slash
const [targetPkg, relPath] = meshPath.replace(/^package:\/\//, '').split(/\/(.+)/);
obj.robotName = robot.getAttribute('name');
obj.urdfRobotNode = robot;
if (typeof pkg === 'string') {
// Create the <material> map
materials.forEach(m => {
// "pkg" is one single package
if (pkg.endsWith(targetPkg)) {
const name = m.getAttribute('name');
materialMap[name] = processMaterial(m);
// "pkg" is the target package
return pkg + '/' + relPath;
});
} else {
// Create the <link> map
links.forEach(l => {
// Assume "pkg" is the target package's parent directory
return pkg + '/' + targetPkg + '/' + relPath;
const name = l.getAttribute('name');
const isRoot = robot.querySelector(`child[link="${ name }"]`) === null;
linkMap[name] = processLink(l, isRoot ? obj : null);
}
});
} else if (typeof pkg === 'object') {
// Create the <joint> map
joints.forEach(j => {
// "pkg" is a map of packages
if (targetPkg in pkg) {
const name = j.getAttribute('name');
jointMap[name] = processJoint(j);
return pkg[targetPkg] + '/' + relPath;
});
} else {
obj.joints = jointMap;
obj.links = linkMap;
console.error(`URDFLoader : ${ targetPkg } not found in provided package list!`);
return null;
return obj;
}
}
}
// Process the URDF text format
_processUrdf(data, packages, path, loadMeshCb) {
// Process joint nodes and parent them
function processJoint(joint) {
const parser = new DOMParser();
const urdf = parser.parseFromString(data, 'text/xml');
const children = [ ...joint.children ];
const jointType = joint.getAttribute('type');
const obj = new URDFJoint();
obj.urdfNode = joint;
obj.name = joint.getAttribute('name');
obj.jointType = jointType;
const robottag = this.filter(urdf.children, c => c.nodeName === 'robot').pop();
return this._processRobot(robottag, packages, path, loadMeshCb);
let parent = null;
let child = null;
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
}
// Extract the attributes
children.forEach(n => {
// Process the <robot> node
_processRobot(robot, packages, path, loadMeshCb) {
const type = n.nodeName.toLowerCase();
if (type === 'origin') {
const materials = robot.querySelectorAll('material');
const links = [];
const joints = [];
const obj = new URDFRobot();
obj.name = robot.getAttribute('name');
xyz = processTuple(n.getAttribute('xyz'));
rpy = processTuple(n.getAttribute('rpy'));
// Process the <joint> and <link> nodes
this.forEach(robot.children, n => {
} else if (type === 'child') {
const type = n.nodeName.toLowerCase();
if (type === 'link') links.push(n);
else if (type === 'joint') joints.push(n);
child = linkMap[n.getAttribute('link')];
});
} else if (type === 'parent') {
// Create the <material> map
const materialMap = {};
this.forEach(materials, m => {
parent = linkMap[n.getAttribute('link')];
const name = m.getAttribute('name');
if (!materialMap[name]) {
} else if (type === 'limit') {
materialMap[name] = {};
this.forEach(m.children, c => {
obj.limit.lower = parseFloat(n.getAttribute('lower') || obj.limit.lower);
obj.limit.upper = parseFloat(n.getAttribute('upper') || obj.limit.upper);
this._processMaterial(
materialMap[name],
c,
packages,
path
);
}
});
});
}
// Join the links
parent.add(obj);
obj.add(child);
applyRotation(obj, rpy);
obj.position.set(xyz[0], xyz[1], xyz[2]);
});
// Set up the rotate function
const axisNode = children.filter(n => n.nodeName.toLowerCase() === 'axis')[0];
// Create the <link> map
const linkMap = {};
this.forEach(links, l => {
if (axisNode) {
const name = l.getAttribute('name');
linkMap[name] = this._processLink(l, materialMap, packages, path, loadMeshCb);
const axisXYZ = axisNode.getAttribute('xyz').split(/\s+/g).map(num => parseFloat(num));
obj.axis = new THREE.Vector3(axisXYZ[0], axisXYZ[1], axisXYZ[2]);
obj.axis.normalize();
});
}
// Create the <joint> map
const jointMap = {};
this.forEach(joints, j => {
return obj;
const name = j.getAttribute('name');
jointMap[name] = this._processJoint(j, linkMap);
}
});
// Process the <link> nodes
function processLink(link, target = null) {
for (const key in linkMap) {
if (target === null) {
if (linkMap[key].parent == null) {
target = new URDFLink();
obj.add(linkMap[key]);
}
}
const children = [ ...link.children ];
const visualNodes = children.filter(n => n.nodeName.toLowerCase() === 'visual');
target.name = link.getAttribute('name');
target.urdfNode = link;
obj.joints = jointMap;
obj.links = linkMap;
visualNodes.forEach(vn => processVisualNode(vn, target, materialMap));
return obj;
return target;
}
}
// Process joint nodes and parent them
_processJoint(joint, linkMap) {
function processMaterial(node) {
const jointType = joint.getAttribute('type');
const obj = new URDFJoint();
obj.urdfNode = joint;
obj.name = joint.getAttribute('name');
obj.jointType = jointType;
const matNodes = [ ...node.children ];
const material = new THREE.MeshPhongMaterial();
let parent = null;
let child = null;
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
material.name = node.getAttribute('name') || '';
matNodes.forEach(n => {
// Extract the attributes
this.forEach(joint.children, n => {
const type = n.nodeName.toLowerCase();
if (type === 'color') {
const type = n.nodeName.toLowerCase();
if (type === 'origin') {
const rgba =
n
.getAttribute('rgba')
.split(/\s/g)
.map(v => parseFloat(v));
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
material.color.setRGB(rgba[0], rgba[1], rgba[2]);
material.opacity = rgba[3];
material.transparent = rgba[3] < 1;
} else if (type === 'child') {
} else if (type === 'texture') {
child = linkMap[n.getAttribute('link')];
const loader = new THREE.TextureLoader(manager);
const filename = n.getAttribute('filename');
const filePath = resolvePath(filename);
material.map = loader.load(filePath);
} else if (type === 'parent') {
}
});
parent = linkMap[n.getAttribute('link')];
return material;
} else if (type === 'limit') {
obj.limit.lower = parseFloat(n.getAttribute('lower') || obj.limit.lower);
obj.limit.upper = parseFloat(n.getAttribute('upper') || obj.limit.upper);
}
});
// Join the links
parent.add(obj);
obj.add(child);
this._applyRotation(obj, rpy);
obj.position.set(xyz[0], xyz[1], xyz[2]);
// Set up the rotate function
const axisnode = this.filter(joint.children, n => n.nodeName.toLowerCase() === 'axis')[0];
if (axisnode) {
const axisxyz = axisnode.getAttribute('xyz').split(/\s+/g).map(num => parseFloat(num));
obj.axis = new THREE.Vector3(axisxyz[0], axisxyz[1], axisxyz[2]);
obj.axis.normalize();
}
return obj;
// Process the visual nodes into meshes
function processVisualNode(vn, linkObj, materialMap) {
}
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
let scale = [1, 1, 1];
// Process the <link> nodes
_processLink(link, materialMap, packages, path, loadMeshCb) {
const children = [ ...vn.children ];
let material = null;
let primitiveModel = null;
const visualNodes = this.filter(link.children, n => n.nodeName.toLowerCase() === 'visual');
const obj = new URDFLink();
obj.name = link.getAttribute('name');
obj.urdfNode = link;
// get the material first
const materialNode = children.filter(n => n.nodeName.toLowerCase() === 'material')[0];
if (materialNode) {
this.forEach(visualNodes, vn => this._processVisualNode(vn, obj, materialMap, packages, path, loadMeshCb));
const name = materialNode.getAttribute('name');
if (name && name in materialMap) {
return obj;
material = materialMap[name];
}
} else {
_processMaterial(material, node, packages, path) {
material = processMaterial(materialNode);
const type = node.nodeName.toLowerCase();
if (type === 'color') {
}
const rgba =
node
.getAttribute('rgba')
.split(/\s/g)
.map(v => parseFloat(v));
} else {
this._copyMaterialAttributes(
material,
{
color: new THREE.Color(rgba[0], rgba[1], rgba[2]),
opacity: rgba[3],
transparent: rgba[3] < 1,
});
material = new THREE.MeshPhongMaterial();
} else if (type === 'texture') {
}
const filename = node.getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
this._copyMaterialAttributes(
material,
{
map: this.TextureLoader.load(filePath),
});
children.forEach(n => {
}
}
const type = n.nodeName.toLowerCase();
if (type === 'geometry') {
_copyMaterialAttributes(material, materialAttributes) {
const geoType = n.children[0].nodeName.toLowerCase();
if (geoType === 'mesh') {
if ('color' in materialAttributes) {
const filename = n.children[0].getAttribute('filename');
const filePath = resolvePath(filename);
material.color = materialAttributes.color.clone();
material.opacity = materialAttributes.opacity;
material.transparent = materialAttributes.transparent;
// file path is null if a package directory is not provided.
if (filePath !== null) {
}
const scaleAttr = n.children[0].getAttribute('scale');
if (scaleAttr) scale = processTuple(scaleAttr);
if ('map' in materialAttributes) {
loadMeshCb(filePath, manager, (obj, err) => {
material.map = materialAttributes.map.clone();
if (err) {
}
console.error('URDFLoader: Error loading mesh.', err);
}
} else if (obj) {
// Process the visual nodes into meshes
_processVisualNode(vn, linkObj, materialMap, packages, path, loadMeshCb) {
if (obj instanceof THREE.Mesh) {
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
let scale = [1, 1, 1];
obj.material = material;
const material = new THREE.MeshPhongMaterial();
let primitiveModel = null;
this.forEach(vn.children, n => {
}
const type = n.nodeName.toLowerCase();
if (type === 'geometry') {
linkObj.add(obj);
const geoType = n.children[0].nodeName.toLowerCase();
if (geoType === 'mesh') {
obj.position.set(xyz[0], xyz[1], xyz[2]);
obj.rotation.set(0, 0, 0);
const filename = n.children[0].getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
// multiply the existing scale by the scale components because
// the loaded model could have important scale values already applied
// to the root. Collada files, for example, can load in with a scale
// to convert the model units to meters.
obj.scale.x *= scale[0];
obj.scale.y *= scale[1];
obj.scale.z *= scale[2];
// file path is null if a package directory is not provided.
if (filePath !== null) {
applyRotation(obj, rpy);
const ext = filePath.match(/.*\.([A-Z0-9]+)$/i).pop() || '';
const scaleAttr = n.children[0].getAttribute('scale');
if (scaleAttr) scale = this._processTuple(scaleAttr);
loadMeshCb(filePath, ext, obj => {
if (obj) {
if (obj instanceof THREE.Mesh) {
obj.material.copy(material);
}
linkObj.add(obj);
});
obj.position.set(xyz[0], xyz[1], xyz[2]);
obj.rotation.set(0, 0, 0);
}
// multiply the existing scale by the scale components because
// the loaded model could have important scale values already applied
// to the root. Collada files, for example, can load in with a scale
// to convert the model units to meters.
obj.scale.x *= scale[0];
obj.scale.y *= scale[1];
obj.scale.z *= scale[2];
} else if (geoType === 'box') {
this._applyRotation(obj, rpy);
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.BoxBufferGeometry(1, 1, 1);
primitiveModel.material = material;
}
const size = processTuple(n.children[0].getAttribute('size'));
});
linkObj.add(primitiveModel);
primitiveModel.scale.set(size[0], size[1], size[2]);
}
} else if (geoType === 'sphere') {
} else if (geoType === 'box') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.SphereBufferGeometry(1, 30, 30);
primitiveModel.material = material;
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.BoxBufferGeometry(1, 1, 1);
primitiveModel.material = material;
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
primitiveModel.scale.set(radius, radius, radius);
const size = this._processTuple(n.children[0].getAttribute('size'));
linkObj.add(primitiveModel);
linkObj.add(primitiveModel);
primitiveModel.scale.set(size[0], size[1], size[2]);
} else if (geoType === 'cylinder') {
} else if (geoType === 'sphere') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 30);
primitiveModel.material = material;
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.SphereBufferGeometry(1, 30, 30);
primitiveModel.material = material;
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
const length = parseFloat(n.children[0].getAttribute('length')) || 0;
primitiveModel.scale.set(radius, length, radius);
primitiveModel.rotation.set(Math.PI / 2, 0, 0);
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
primitiveModel.scale.set(radius, radius, radius);
linkObj.add(primitiveModel);
linkObj.add(primitiveModel);
}
} else if (geoType === 'cylinder') {
} else if (type === 'origin') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 30);
primitiveModel.material = material;
xyz = processTuple(n.getAttribute('xyz'));
rpy = processTuple(n.getAttribute('rpy'));
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
const length = parseFloat(n.children[0].getAttribute('length')) || 0;
primitiveModel.scale.set(radius, length, radius);
primitiveModel.rotation.set(Math.PI / 2, 0, 0);
}
linkObj.add(primitiveModel);
});
}
// apply the position and rotation to the primitive geometry after
// the fact because it's guaranteed to have been scraped from the child
// nodes by this point
if (primitiveModel) {
} else if (type === 'origin') {
applyRotation(primitiveModel, rpy, true);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
}
} else if (type === 'material') {
}
const materialName = n.getAttribute('name');
if (materialName) {
return processUrdf(content);
this._copyMaterialAttributes(material, materialMap[materialName]);
}
} else {
// Default mesh loading function
defaultMeshLoader(path, manager, done) {
this.forEach(n.children, c => {
if (/\.stl$/i.test(path)) {
this._processMaterial(material, c, packages, path);
const loader = new STLLoader.STLLoader(manager);
loader.load(path, geom => {
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial());
done(mesh);
});
});
} else if (/\.dae$/i.test(path)) {
}
const loader = new ColladaLoader.ColladaLoader(manager);
loader.load(path, dae => done(dae.scene));
}
});
} else {
// apply the position and rotation to the primitive geometry after
// the fact because it's guaranteed to have been scraped from the child
// nodes by this point
if (primitiveModel) {
console.warn(`URDFLoader: Could not load model at ${ path }.\nNo loader available`);
this._applyRotation(primitiveModel, rpy, true);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);
}

@@ -797,0 +743,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc