Socket
Socket
Sign inDemoInstall

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.1 to 0.5.2

src/URDFClasses.js

9

CHANGELOG.md

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

## [0.5.2] - 2018-12-5
### Added
- URDF XML Node to URDF Joint, Link, and Robot objects
- Add clone functionality
### Changed
- OnComplete callback now fires once all meshes have loaded
- Removed unnecessary parent when creating a cylinder visual node
## [0.5.1] - 2018-11-14

@@ -9,0 +18,0 @@ ### Fixed

2

package.json
{
"name": "urdf-loader",
"version": "0.5.1",
"version": "0.5.2",
"description": "URDF Loader for THREE.js and webcomponent viewer",

@@ -5,0 +5,0 @@ "main": "umd/URDFLoader.js",

@@ -437,5 +437,2 @@ import * as THREE from 'three';

let totalMeshes = 0;
let meshesLoaded = 0;
if (pkg.includes(':') && (pkg.split(':')[1].substring(0, 2)) !== '//') {

@@ -464,3 +461,2 @@ // E.g. pkg = "pkg_name: path/to/pkg_name, pk2: path2/to/pk2"}

// Callback with array of robots
robot => {

@@ -477,6 +473,5 @@

updateMaterials(robot);
this.robot = robot;
this.world.add(robot);
updateMaterials(robot);

@@ -486,2 +481,3 @@ 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 }));

@@ -497,16 +493,6 @@ this.recenter();

// Load meshes and enable shadow casting
totalMeshes++;
this.urdfLoader.defaultMeshLoader(path, ext, mesh => {
updateMaterials(mesh);
done(mesh);
meshesLoaded++;
if (meshesLoaded === totalMeshes && this._requestId === requestId) {
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
}
this.recenter();

@@ -513,0 +499,0 @@

import * as THREE from 'three';
import { STLLoader } from 'three/examples/js/loaders/STLLoader';
import { ColladaLoader } from 'three/examples/js/loaders/ColladaLoader';
import { URDFRobot, URDFJoint, URDFLink } from './URDFClasses.js';

@@ -27,2 +28,5 @@ /*

const tempQuaternion = new THREE.Quaternion();
const tempEuler = new THREE.Euler();
/* URDFLoader Class */

@@ -85,6 +89,13 @@ // Loads and reads a URDF file into a THREEjs Object3D format

// applies a rotation a threejs object in URDF order
_applyRotation(obj, rpy) {
_applyRotation(obj, rpy, additive = false) {
obj.rotation.set(rpy[0], rpy[1], rpy[2], 'ZYX');
// 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);
}

@@ -120,7 +131,30 @@

const result = this._processUrdf(content, packages, options.workingPath, options.loadMeshCb);
let result = null;
let meshCount = 0;
const loadMeshFunc = (path, ext, done) => {
if (typeof onComplete === 'function') {
meshCount++;
options.loadMeshCb(path, ext, (...args) => {
done(...args);
meshCount--;
if (meshCount === 0) {
requestAnimationFrame(() => {
if (typeof onComplete === 'function') {
onComplete(result);
}
});
}
});
};
result = this._processUrdf(content, packages, options.workingPath, loadMeshFunc);
if (meshCount === 0 && typeof onComplete === 'function') {
onComplete(result);
onComplete = null;

@@ -217,3 +251,3 @@ }

const joints = [];
const obj = new THREE.Object3D();
const obj = new URDFRobot();
obj.name = robot.getAttribute('name');

@@ -283,4 +317,2 @@

obj.links = linkMap;
obj.isURDFRobot = true;
obj.type = 'URDFRobot';

@@ -295,26 +327,7 @@ return obj;

const jointType = joint.getAttribute('type');
const obj = new THREE.Object3D();
obj.isURDFJoint = true;
obj.type = 'URDFJoint';
const obj = new URDFJoint();
obj.urdfNode = joint;
obj.name = joint.getAttribute('name');
obj.jointType = jointType;
obj.axis = null;
obj.angle = 0;
obj.limit = { lower: 0, upper: 0 };
obj.ignoreLimits = false;
obj.setOffset = () => {};
// copy the 'setOffset' function over to 'setAngle' so
// it makes sense for other joint types (prismatic, planar)
// TODO: Remove the 'setOffset' function
// TODO: Figure out how to handle setting and getting angles of other types
Object.defineProperties(
obj,
{
setAngle: { get() { return this.setOffset; } },
});
let parent = null;

@@ -358,4 +371,2 @@ let child = null;

// Set up the rotate function
const origRot = new THREE.Quaternion().copy(obj.quaternion);
const origPos = new THREE.Vector3().copy(obj.position);
const axisnode = this.filter(joint.children, n => n.nodeName.toLowerCase() === 'axis')[0];

@@ -371,61 +382,2 @@

switch (jointType) {
case 'fixed': break;
case 'continuous':
obj.limit.lower = -Infinity;
obj.limit.upper = Infinity;
// fall through to revolute joint 'setOffset' function
case 'revolute':
obj.setOffset = function(angle = null) {
if (!this.axis) return;
if (angle == null) return;
if (!this.ignoreLimits) {
angle = Math.min(this.limit.upper, angle);
angle = Math.max(this.limit.lower, angle);
}
// FromAxisAngle seems to rotate the opposite of the
// expected angle for URDF, so negate it here
const delta = new THREE.Quaternion().setFromAxisAngle(this.axis, angle);
obj.quaternion.multiplyQuaternions(origRot, delta);
this.angle = angle;
};
break;
case 'prismatic':
obj.setOffset = function(angle = null) {
if (!this.axis) return;
if (angle == null) return;
if (!this.ignoreLimits) {
angle = Math.min(this.limit.upper, angle);
angle = Math.max(this.limit.lower, angle);
}
obj.position.copy(origPos);
obj.position.addScaledVector(this.axis, angle);
this.angle = angle;
};
break;
case 'floating':
case 'planar':
// TODO: Support these joint types
console.warn(`'${ jointType }' joint not yet supported`);
}
return obj;

@@ -439,6 +391,5 @@

const visualNodes = this.filter(link.children, n => n.nodeName.toLowerCase() === 'visual');
const obj = new THREE.Object3D();
const obj = new URDFLink();
obj.name = link.getAttribute('name');
obj.isURDFLink = true;
obj.type = 'URDFLink';
obj.urdfNode = link;

@@ -575,17 +526,12 @@ this.forEach(visualNodes, vn => this._processVisualNode(vn, obj, materialMap, packages, path, loadMeshCb));

primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 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);
primitiveModel = new THREE.Object3D();
const mesh = new THREE.Mesh();
mesh.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 30);
mesh.material = material;
mesh.scale.set(radius, length, radius);
primitiveModel.add(mesh);
mesh.rotation.set(Math.PI / 2, 0, 0);
linkObj.add(primitiveModel);
this._applyRotation(primitiveModel, rpy);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);

@@ -624,3 +570,3 @@ }

this._applyRotation(primitiveModel, rpy);
this._applyRotation(primitiveModel, rpy, true);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);

@@ -627,0 +573,0 @@

@@ -440,5 +440,2 @@ (function (global, factory) {

let totalMeshes = 0;
let meshesLoaded = 0;
if (pkg.includes(':') && (pkg.split(':')[1].substring(0, 2)) !== '//') {

@@ -467,3 +464,2 @@ // E.g. pkg = "pkg_name: path/to/pkg_name, pk2: path2/to/pk2"}

// Callback with array of robots
robot => {

@@ -480,6 +476,5 @@

updateMaterials(robot);
this.robot = robot;
this.world.add(robot);
updateMaterials(robot);

@@ -489,2 +484,3 @@ 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 }));

@@ -500,16 +496,6 @@ this.recenter();

// Load meshes and enable shadow casting
totalMeshes++;
this.urdfLoader.defaultMeshLoader(path, ext, mesh => {
updateMaterials(mesh);
done(mesh);
meshesLoaded++;
if (meshesLoaded === totalMeshes && this._requestId === requestId) {
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
}
this.recenter();

@@ -516,0 +502,0 @@

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('three'), require('three/examples/js/loaders/STLLoader'), require('three/examples/js/loaders/ColladaLoader')) :
typeof define === 'function' && define.amd ? define(['three', 'three/examples/js/loaders/STLLoader', 'three/examples/js/loaders/ColladaLoader'], factory) :
(global.URDFLoader = factory(global.THREE,global.THREE,global.THREE));
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('three'), require('three/examples/js/loaders/STLLoader'), require('three/examples/js/loaders/ColladaLoader')) :
typeof define === 'function' && define.amd ? define(['three', 'three/examples/js/loaders/STLLoader', 'three/examples/js/loaders/ColladaLoader'], factory) :
(global.URDFLoader = factory(global.THREE,global.THREE,global.THREE));
}(this, (function (THREE,STLLoader,ColladaLoader) { 'use strict';
/*
Reference coordinate frames for THREE.js and ROS.
Both coordinate systems are right handed so the URDF is instantiated without
frame transforms. The resulting model can be rotated to rectify the proper up,
right, and forward directions
class URDFRobot extends THREE.Object3D {
THREE.js
Y
|
|
.-----X
Z
constructor(...args) {
ROS URDf
Z
| X
| /
Y-----.
super(...args);
this.isURDFRobot = true;
this.type = 'URDFRobot';
this.urdfNode = null;
*/
this.links = null;
this.joints = null;
/* URDFLoader Class */
// Loads and reads a URDF file into a THREEjs Object3D format
class URDFLoader {
}
// Cached mesh loaders
get STLLoader() {
copy(source, recursive) {
this._stlloader = this._stlloader || new STLLoader.STLLoader(this.manager);
return this._stlloader;
super.copy(source, recursive);
}
this.links = {};
this.joints = {};
get DAELoader() {
this.traverse(c => {
this._daeloader = this._daeloader || new ColladaLoader.ColladaLoader(this.manager);
return this._daeloader;
if (c.isURDFJoint && c.name in source.joints) {
}
this.joints[c.name] = c;
get TextureLoader() {
}
this._textureloader = this._textureloader || new THREE.TextureLoader(this.manager);
return this._textureloader;
if (c.isURDFLink && c.name in source.links) {
}
this.links[c.name] = c;
constructor(manager) {
}
this.manager = manager || THREE.DefaultLoadingManager;
});
}
return this;
/* Utilities */
// forEach and filter function wrappers because
// HTMLCollection does not the by default
forEach(coll, func) {
}
return [].forEach.call(coll, func);
}
}
filter(coll, func) {
class URDFLink extends THREE.Object3D {
return [].filter.call(coll, func);
constructor(...args) {
}
super(...args);
this.isURDFLink = true;
this.type = 'URDFLink';
this.urdfNode = null;
// 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));
copy(source, recursive) {
}
super.copy(source, recursive);
this.urdfNode = source.urdfNode;
// applies a rotation a threejs object in URDF order
_applyRotation(obj, rpy) {
return this;
obj.rotation.set(rpy[0], rpy[1], rpy[2], 'ZYX');
}
}
}
/* 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) {
class URDFJoint extends THREE.Object3D {
// Check if a full URI is specified before
// prepending the package info
const workingPath = THREE.LoaderUtils.extractUrlBase(urdf);
const urdfPath = this.manager.resolveURL(urdf);
get jointType() {
options = Object.assign({ workingPath }, options);
return this._jointType;
fetch(urdfPath, options.fetchOptions)
.then(res => res.text())
.then(data => this.parse(data, packages, onComplete, options));
}
set jointType(v) {
}
if (this.jointType === v) return;
this._jointType = v;
parse(content, packages, onComplete, options) {
switch (v) {
options = Object.assign({
case 'fixed':
case 'continuous':
case 'revolute':
case 'prismatic':
this.jointValue = 0;
break;
loadMeshCb: this.defaultMeshLoader.bind(this),
workingPath: '',
case 'planar':
this.jointValue = new Array(2).fill(0);
break;
}, options);
case 'floating':
this.jointValue = new Array(6).fill(0);
break;
const result = this._processUrdf(content, packages, options.workingPath, options.loadMeshCb);
}
if (typeof onComplete === 'function') {
}
onComplete(result);
get angle() {
}
return this.jointValue;
return result;
}
}
constructor(...args) {
super(...args);
// Default mesh loading function
defaultMeshLoader(path, ext, done) {
this.isURDFJoint = true;
this.type = 'URDFJoint';
if (/\.stl$/i.test(path)) {
this.urdfNode = null;
this.jointValue = null;
this.jointType = 'fixed';
this.axis = null;
this.limit = { lower: 0, upper: 0 };
this.ignoreLimits = false;
this.STLLoader.load(path, geom => {
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial());
done(mesh);
});
this.origPosition = null;
this.origQuaternion = null;
}
} else if (/\.dae$/i.test(path)) {
/* Overrides */
copy(source, recursive) {
this.DAELoader.load(path, dae => done(dae.scene));
super.copy(source, recursive);
} else {
this.urdfNode = source.urdfNode;
this.jointType = source.jointType;
this.axis = source.axis ? source.axis.clone() : null;
this.limit.lower = source.limit.lower;
this.limit.upper = source.limit.upper;
this.ignoreLimits = false;
console.warn(`URDFLoader: Could not load model at ${ path }.\nNo loader available`);
this.jointValue = Array.isArray(source.jointValue) ? [...source.jointValue] : source.jointValue;
}
this.origPosition = source.origPosition ? source.origPosition.clone() : null;
this.origQuaternion = source.origQuaternion ? source.origQuaternion.clone() : null;
}
return this;
}
/* Private Functions */
/* Public Functions */
setAngle(...values) {
return this.setOffset(...values);
}
// Resolves the path of mesh files
_resolvePackagePath(pkg, meshPath, currPath) {
setOffset(...values) {
if (!/^package:\/\//.test(meshPath)) {
if (!this.origPosition || !this.origQuaternion) {
return currPath !== undefined ? currPath + meshPath : meshPath;
this.origPosition = this.position.clone();
this.origQuaternion = this.quaternion.clone();
}
}
// Remove "package://" keyword and split meshPath at the first slash
const [targetPkg, relPath] = meshPath.replace(/^package:\/\//, '').split(/\/(.+)/);
switch (this.jointType) {
if (typeof pkg === 'string') {
case 'fixed': {
break;
}
case 'continuous':
case 'revolute': {
// "pkg" is one single package
if (pkg.endsWith(targetPkg)) {
let angle = values[0];
if (angle == null) break;
// "pkg" is the target package
return pkg + '/' + relPath;
if (!this.ignoreLimits) {
} else {
angle = Math.min(this.limit.upper, angle);
angle = Math.max(this.limit.lower, angle);
// Assume "pkg" is the target package's parent directory
return pkg + '/' + targetPkg + '/' + relPath;
}
}
// FromAxisAngle seems to rotate the opposite of the
// expected angle for URDF, so negate it here
const delta = new THREE.Quaternion().setFromAxisAngle(this.axis, angle);
this.quaternion.multiplyQuaternions(this.origQuaternion, delta);
} else if (typeof pkg === 'object') {
this.jointValue = angle;
break;
}
// "pkg" is a map of packages
if (targetPkg in pkg) {
case 'prismatic': {
return pkg[targetPkg] + '/' + relPath;
let angle = values[0];
if (angle == null) break;
} else {
if (!this.ignoreLimits) {
console.error(`URDFLoader : ${ targetPkg } not found in provided package list!`);
return null;
angle = Math.min(this.limit.upper, angle);
angle = Math.max(this.limit.lower, angle);
}
}
}
}
// Process the URDF text format
_processUrdf(data, packages, path, loadMeshCb) {
this.position.copy(this.origPosition);
this.position.addScaledVector(this.axis, angle);
const parser = new DOMParser();
const urdf = parser.parseFromString(data, 'text/xml');
this.jointValue = angle;
break;
const robottag = this.filter(urdf.children, c => c.nodeName === 'robot').pop();
return this._processRobot(robottag, packages, path, loadMeshCb);
}
}
case 'floating':
case 'planar':
// TODO: Support these joint types
console.warn(`'${ this.jointType }' joint not yet supported`);
// Process the <robot> node
_processRobot(robot, packages, path, loadMeshCb) {
}
const materials = robot.querySelectorAll('material');
const links = [];
const joints = [];
const obj = new THREE.Object3D();
obj.name = robot.getAttribute('name');
return this.jointValue;
// Process the <joint> and <link> nodes
this.forEach(robot.children, n => {
}
const type = n.nodeName.toLowerCase();
if (type === 'link') links.push(n);
else if (type === 'joint') joints.push(n);
}
});
/*
Reference coordinate frames for THREE.js and ROS.
Both coordinate systems are right handed so the URDF is instantiated without
frame transforms. The resulting model can be rotated to rectify the proper up,
right, and forward directions
// Create the <material> map
const materialMap = {};
this.forEach(materials, m => {
THREE.js
Y
|
|
.-----X
Z
const name = m.getAttribute('name');
if (!materialMap[name]) {
ROS URDf
Z
| X
| /
Y-----.
materialMap[name] = {};
this.forEach(m.children, c => {
*/
this._processMaterial(
materialMap[name],
c,
packages,
path
);
const tempQuaternion = new THREE.Quaternion();
const tempEuler = new THREE.Euler();
});
/* URDFLoader Class */
// Loads and reads a URDF file into a THREEjs Object3D format
class URDFLoader {
}
// Cached mesh loaders
get STLLoader() {
});
this._stlloader = this._stlloader || new STLLoader.STLLoader(this.manager);
return this._stlloader;
// Create the <link> map
const linkMap = {};
this.forEach(links, l => {
}
const name = l.getAttribute('name');
linkMap[name] = this._processLink(l, materialMap, packages, path, loadMeshCb);
get DAELoader() {
});
this._daeloader = this._daeloader || new ColladaLoader.ColladaLoader(this.manager);
return this._daeloader;
// Create the <joint> map
const jointMap = {};
this.forEach(joints, j => {
}
const name = j.getAttribute('name');
jointMap[name] = this._processJoint(j, linkMap);
get TextureLoader() {
});
this._textureloader = this._textureloader || new THREE.TextureLoader(this.manager);
return this._textureloader;
for (const key in linkMap) {
}
if (linkMap[key].parent == null) {
constructor(manager) {
obj.add(linkMap[key]);
this.manager = manager || THREE.DefaultLoadingManager;
}
}
}
/* Utilities */
// forEach and filter function wrappers because
// HTMLCollection does not the by default
forEach(coll, func) {
obj.joints = jointMap;
obj.links = linkMap;
obj.isURDFRobot = true;
obj.type = 'URDFRobot';
return [].forEach.call(coll, func);
return obj;
}
filter(coll, func) {
}
return [].filter.call(coll, func);
// Process joint nodes and parent them
_processJoint(joint, linkMap) {
}
const jointType = joint.getAttribute('type');
const obj = new THREE.Object3D();
obj.isURDFJoint = true;
obj.type = 'URDFJoint';
// take a vector "x y z" and process it into
// an array [x, y, z]
_processTuple(val) {
obj.name = joint.getAttribute('name');
obj.jointType = jointType;
obj.axis = null;
obj.angle = 0;
obj.limit = { lower: 0, upper: 0 };
obj.ignoreLimits = false;
obj.setOffset = () => {};
if (!val) return [0, 0, 0];
return val.trim().split(/\s+/g).map(num => parseFloat(num));
// copy the 'setOffset' function over to 'setAngle' so
// it makes sense for other joint types (prismatic, planar)
// TODO: Remove the 'setOffset' function
// TODO: Figure out how to handle setting and getting angles of other types
Object.defineProperties(
obj,
{
}
setAngle: { get() { return this.setOffset; } },
// 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);
let parent = null;
let child = null;
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
tempEuler.set(rpy[0], rpy[1], rpy[2], 'ZYX');
tempQuaternion.setFromEuler(tempEuler);
tempQuaternion.multiply(obj.quaternion);
obj.quaternion.copy(tempQuaternion);
// Extract the attributes
this.forEach(joint.children, n => {
}
const type = n.nodeName.toLowerCase();
if (type === 'origin') {
/* 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) {
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
// Check if a full URI is specified before
// prepending the package info
const workingPath = THREE.LoaderUtils.extractUrlBase(urdf);
const urdfPath = this.manager.resolveURL(urdf);
} else if (type === 'child') {
options = Object.assign({ workingPath }, options);
child = linkMap[n.getAttribute('link')];
fetch(urdfPath, options.fetchOptions)
.then(res => res.text())
.then(data => this.parse(data, packages, onComplete, options));
} else if (type === 'parent') {
}
parent = linkMap[n.getAttribute('link')];
parse(content, packages, onComplete, options) {
} else if (type === 'limit') {
options = Object.assign({
obj.limit.lower = parseFloat(n.getAttribute('lower') || obj.limit.lower);
obj.limit.upper = parseFloat(n.getAttribute('upper') || obj.limit.upper);
loadMeshCb: this.defaultMeshLoader.bind(this),
workingPath: '',
}
}, options);
});
let result = null;
let meshCount = 0;
const loadMeshFunc = (path, ext, done) => {
// Join the links
parent.add(obj);
obj.add(child);
this._applyRotation(obj, rpy);
obj.position.set(xyz[0], xyz[1], xyz[2]);
meshCount++;
options.loadMeshCb(path, ext, (...args) => {
// Set up the rotate function
const origRot = new THREE.Quaternion().copy(obj.quaternion);
const origPos = new THREE.Vector3().copy(obj.position);
const axisnode = this.filter(joint.children, n => n.nodeName.toLowerCase() === 'axis')[0];
done(...args);
meshCount--;
if (meshCount === 0) {
if (axisnode) {
requestAnimationFrame(() => {
if (typeof onComplete === 'function') {
onComplete(result);
}
});
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();
}
}
});
switch (jointType) {
};
result = this._processUrdf(content, packages, options.workingPath, loadMeshFunc);
case 'fixed': break;
case 'continuous':
obj.limit.lower = -Infinity;
obj.limit.upper = Infinity;
if (meshCount === 0 && typeof onComplete === 'function') {
// fall through to revolute joint 'setOffset' function
case 'revolute':
obj.setOffset = function(angle = null) {
onComplete(result);
onComplete = null;
if (!this.axis) return;
if (angle == null) return;
}
if (!this.ignoreLimits) {
return result;
angle = Math.min(this.limit.upper, angle);
angle = Math.max(this.limit.lower, angle);
}
}
// Default mesh loading function
defaultMeshLoader(path, ext, done) {
// FromAxisAngle seems to rotate the opposite of the
// expected angle for URDF, so negate it here
const delta = new THREE.Quaternion().setFromAxisAngle(this.axis, angle);
obj.quaternion.multiplyQuaternions(origRot, delta);
if (/\.stl$/i.test(path)) {
this.angle = angle;
this.STLLoader.load(path, geom => {
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial());
done(mesh);
});
};
break;
} else if (/\.dae$/i.test(path)) {
case 'prismatic':
obj.setOffset = function(angle = null) {
this.DAELoader.load(path, dae => done(dae.scene));
if (!this.axis) return;
if (angle == null) return;
} else {
if (!this.ignoreLimits) {
console.warn(`URDFLoader: Could not load model at ${ path }.\nNo loader available`);
angle = Math.min(this.limit.upper, angle);
angle = Math.max(this.limit.lower, angle);
}
}
}
obj.position.copy(origPos);
obj.position.addScaledVector(this.axis, angle);
/* Private Functions */
this.angle = angle;
// Resolves the path of mesh files
_resolvePackagePath(pkg, meshPath, currPath) {
};
break;
if (!/^package:\/\//.test(meshPath)) {
case 'floating':
case 'planar':
// TODO: Support these joint types
console.warn(`'${ jointType }' joint not yet supported`);
return currPath !== undefined ? currPath + meshPath : meshPath;
}
}
return obj;
// Remove "package://" keyword and split meshPath at the first slash
const [targetPkg, relPath] = meshPath.replace(/^package:\/\//, '').split(/\/(.+)/);
}
if (typeof pkg === 'string') {
// Process the <link> nodes
_processLink(link, materialMap, packages, path, loadMeshCb) {
// "pkg" is one single package
if (pkg.endsWith(targetPkg)) {
const visualNodes = this.filter(link.children, n => n.nodeName.toLowerCase() === 'visual');
const obj = new THREE.Object3D();
obj.name = link.getAttribute('name');
obj.isURDFLink = true;
obj.type = 'URDFLink';
// "pkg" is the target package
return pkg + '/' + relPath;
this.forEach(visualNodes, vn => this._processVisualNode(vn, obj, materialMap, packages, path, loadMeshCb));
} else {
return obj;
// Assume "pkg" is the target package's parent directory
return pkg + '/' + targetPkg + '/' + relPath;
}
}
_processMaterial(material, node, packages, path) {
} else if (typeof pkg === 'object') {
const type = node.nodeName.toLowerCase();
if (type === 'color') {
// "pkg" is a map of packages
if (targetPkg in pkg) {
const rgba =
node
.getAttribute('rgba')
.split(/\s/g)
.map(v => parseFloat(v));
return pkg[targetPkg] + '/' + relPath;
this._copyMaterialAttributes(
material,
{
color: new THREE.Color(rgba[0], rgba[1], rgba[2]),
opacity: rgba[3],
transparent: rgba[3] < 1,
});
} else {
} else if (type === 'texture') {
console.error(`URDFLoader : ${ targetPkg } not found in provided package list!`);
return null;
const filename = node.getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
this._copyMaterialAttributes(
material,
{
map: this.TextureLoader.load(filePath),
});
}
}
}
}
}
// Process the URDF text format
_processUrdf(data, packages, path, loadMeshCb) {
_copyMaterialAttributes(material, materialAttributes) {
const parser = new DOMParser();
const urdf = parser.parseFromString(data, 'text/xml');
if ('color' in materialAttributes) {
const robottag = this.filter(urdf.children, c => c.nodeName === 'robot').pop();
return this._processRobot(robottag, packages, path, loadMeshCb);
material.color = materialAttributes.color.clone();
material.opacity = materialAttributes.opacity;
material.transparent = materialAttributes.transparent;
}
}
// Process the <robot> node
_processRobot(robot, packages, path, loadMeshCb) {
if ('map' in materialAttributes) {
const materials = robot.querySelectorAll('material');
const links = [];
const joints = [];
const obj = new URDFRobot();
obj.name = robot.getAttribute('name');
material.map = materialAttributes.map.clone();
// Process the <joint> and <link> nodes
this.forEach(robot.children, n => {
}
const type = n.nodeName.toLowerCase();
if (type === 'link') links.push(n);
else if (type === 'joint') joints.push(n);
}
});
// Process the visual nodes into meshes
_processVisualNode(vn, linkObj, materialMap, packages, path, loadMeshCb) {
// Create the <material> map
const materialMap = {};
this.forEach(materials, m => {
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
let scale = [1, 1, 1];
const name = m.getAttribute('name');
if (!materialMap[name]) {
const material = new THREE.MeshPhongMaterial();
let primitiveModel = null;
this.forEach(vn.children, n => {
materialMap[name] = {};
this.forEach(m.children, c => {
const type = n.nodeName.toLowerCase();
if (type === 'geometry') {
this._processMaterial(
materialMap[name],
c,
packages,
path
);
const geoType = n.children[0].nodeName.toLowerCase();
if (geoType === 'mesh') {
});
const filename = n.children[0].getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
}
// file path is null if a package directory is not provided.
if (filePath !== null) {
});
const ext = filePath.match(/.*\.([A-Z0-9]+)$/i).pop() || '';
const scaleAttr = n.children[0].getAttribute('scale');
if (scaleAttr) scale = this._processTuple(scaleAttr);
// Create the <link> map
const linkMap = {};
this.forEach(links, l => {
loadMeshCb(filePath, ext, obj => {
const name = l.getAttribute('name');
linkMap[name] = this._processLink(l, materialMap, packages, path, loadMeshCb);
if (obj) {
});
if (obj instanceof THREE.Mesh) {
// Create the <joint> map
const jointMap = {};
this.forEach(joints, j => {
obj.material.copy(material);
const name = j.getAttribute('name');
jointMap[name] = this._processJoint(j, linkMap);
}
});
linkObj.add(obj);
for (const key in linkMap) {
obj.position.set(xyz[0], xyz[1], xyz[2]);
obj.rotation.set(0, 0, 0);
obj.scale.set(scale[0], scale[1], scale[2]);
this._applyRotation(obj, rpy);
if (linkMap[key].parent == null) {
}
obj.add(linkMap[key]);
});
}
}
}
} else if (geoType === 'box') {
obj.joints = jointMap;
obj.links = linkMap;
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.BoxBufferGeometry(1, 1, 1);
primitiveModel.material = material;
return obj;
const size = this._processTuple(n.children[0].getAttribute('size'));
}
linkObj.add(primitiveModel);
primitiveModel.scale.set(size[0], size[1], size[2]);
// Process joint nodes and parent them
_processJoint(joint, linkMap) {
} else if (geoType === 'sphere') {
const jointType = joint.getAttribute('type');
const obj = new URDFJoint();
obj.urdfNode = joint;
obj.name = joint.getAttribute('name');
obj.jointType = jointType;
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.SphereBufferGeometry(1, 30, 30);
primitiveModel.material = material;
let parent = null;
let child = null;
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
primitiveModel.scale.set(radius, radius, radius);
// Extract the attributes
this.forEach(joint.children, n => {
linkObj.add(primitiveModel);
const type = n.nodeName.toLowerCase();
if (type === 'origin') {
} else if (geoType === 'cylinder') {
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
const length = parseFloat(n.children[0].getAttribute('length')) || 0;
} else if (type === 'child') {
primitiveModel = new THREE.Object3D();
const mesh = new THREE.Mesh();
mesh.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 30);
mesh.material = material;
mesh.scale.set(radius, length, radius);
child = linkMap[n.getAttribute('link')];
primitiveModel.add(mesh);
mesh.rotation.set(Math.PI / 2, 0, 0);
} else if (type === 'parent') {
linkObj.add(primitiveModel);
this._applyRotation(primitiveModel, rpy);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);
parent = linkMap[n.getAttribute('link')];
}
} else if (type === 'limit') {
} else if (type === 'origin') {
obj.limit.lower = parseFloat(n.getAttribute('lower') || obj.limit.lower);
obj.limit.upper = parseFloat(n.getAttribute('upper') || obj.limit.upper);
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
}
} else if (type === 'material') {
});
const materialName = n.getAttribute('name');
if (materialName) {
// Join the links
parent.add(obj);
obj.add(child);
this._applyRotation(obj, rpy);
obj.position.set(xyz[0], xyz[1], xyz[2]);
this._copyMaterialAttributes(material, materialMap[materialName]);
// Set up the rotate function
const axisnode = this.filter(joint.children, n => n.nodeName.toLowerCase() === 'axis')[0];
} else {
if (axisnode) {
this.forEach(n.children, c => {
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();
this._processMaterial(material, c, packages, path);
}
});
return obj;
}
}
}
});
// Process the <link> nodes
_processLink(link, materialMap, packages, path, loadMeshCb) {
// 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) {
const visualNodes = this.filter(link.children, n => n.nodeName.toLowerCase() === 'visual');
const obj = new URDFLink();
obj.name = link.getAttribute('name');
obj.urdfNode = link;
this._applyRotation(primitiveModel, rpy);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);
this.forEach(visualNodes, vn => this._processVisualNode(vn, obj, materialMap, packages, path, loadMeshCb));
}
return obj;
}
}
}
_processMaterial(material, node, packages, path) {
return URDFLoader;
const type = node.nodeName.toLowerCase();
if (type === 'color') {
const rgba =
node
.getAttribute('rgba')
.split(/\s/g)
.map(v => parseFloat(v));
this._copyMaterialAttributes(
material,
{
color: new THREE.Color(rgba[0], rgba[1], rgba[2]),
opacity: rgba[3],
transparent: rgba[3] < 1,
});
} else if (type === 'texture') {
const filename = node.getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
this._copyMaterialAttributes(
material,
{
map: this.TextureLoader.load(filePath),
});
}
}
_copyMaterialAttributes(material, materialAttributes) {
if ('color' in materialAttributes) {
material.color = materialAttributes.color.clone();
material.opacity = materialAttributes.opacity;
material.transparent = materialAttributes.transparent;
}
if ('map' in materialAttributes) {
material.map = materialAttributes.map.clone();
}
}
// Process the visual nodes into meshes
_processVisualNode(vn, linkObj, materialMap, packages, path, loadMeshCb) {
let xyz = [0, 0, 0];
let rpy = [0, 0, 0];
let scale = [1, 1, 1];
const material = new THREE.MeshPhongMaterial();
let primitiveModel = null;
this.forEach(vn.children, n => {
const type = n.nodeName.toLowerCase();
if (type === 'geometry') {
const geoType = n.children[0].nodeName.toLowerCase();
if (geoType === 'mesh') {
const filename = n.children[0].getAttribute('filename');
const filePath = this._resolvePackagePath(packages, filename, path);
// file path is null if a package directory is not provided.
if (filePath !== null) {
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);
obj.scale.set(scale[0], scale[1], scale[2]);
this._applyRotation(obj, rpy);
}
});
}
} else if (geoType === 'box') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.BoxBufferGeometry(1, 1, 1);
primitiveModel.material = material;
const size = this._processTuple(n.children[0].getAttribute('size'));
linkObj.add(primitiveModel);
primitiveModel.scale.set(size[0], size[1], size[2]);
} else if (geoType === 'sphere') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.SphereBufferGeometry(1, 30, 30);
primitiveModel.material = material;
const radius = parseFloat(n.children[0].getAttribute('radius')) || 0;
primitiveModel.scale.set(radius, radius, radius);
linkObj.add(primitiveModel);
} else if (geoType === 'cylinder') {
primitiveModel = new THREE.Mesh();
primitiveModel.geometry = new THREE.CylinderBufferGeometry(1, 1, 1, 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);
linkObj.add(primitiveModel);
}
} else if (type === 'origin') {
xyz = this._processTuple(n.getAttribute('xyz'));
rpy = this._processTuple(n.getAttribute('rpy'));
} else if (type === 'material') {
const materialName = n.getAttribute('name');
if (materialName) {
this._copyMaterialAttributes(material, materialMap[materialName]);
} else {
this.forEach(n.children, c => {
this._processMaterial(material, c, packages, path);
});
}
}
});
// 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) {
this._applyRotation(primitiveModel, rpy, true);
primitiveModel.position.set(xyz[0], xyz[1], xyz[2]);
}
}
}
return URDFLoader;
})));
//# sourceMappingURL=URDFLoader.js.map

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