From cd1a8d1d7842c9972f23819c930d9d1a376c51ac Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 15:08:44 -0400 Subject: [PATCH 01/12] revert: chore(vite): Temporarily disable type checking and declaration generation for PR Signed-off-by: Drew Hoener --- vite.config.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 17464c0ff..ad91b63b3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,19 +5,19 @@ import checker from 'vite-plugin-checker'; export default defineConfig({ plugins: [ - // dts({ - // tsconfigPath: 'tsconfig.build.json', - // insertTypesEntry: true - // }), - // checker({ - // typescript: { - // tsconfigPath: './tsconfig.build.json', - // }, - // eslint: { - // lintCommand: 'eslint .', - // useFlatConfig: true - // } - // }) + dts({ + tsconfigPath: 'tsconfig.build.json', + insertTypesEntry: true + }), + checker({ + typescript: { + tsconfigPath: './tsconfig.build.json', + }, + eslint: { + lintCommand: 'eslint .', + useFlatConfig: true + } + }) ], build: { lib: { From 68117bee99db241961b16474c306c9d8f0851f29 Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 01:44:19 -0400 Subject: [PATCH 02/12] refactor(UrdfTypes): Convert UrdfTypes to TypeScript enum and update references. Signed-off-by: Drew Hoener --- src/urdf/UrdfBox.ts | 4 ++-- src/urdf/UrdfCylinder.ts | 4 ++-- src/urdf/UrdfMesh.ts | 4 ++-- src/urdf/UrdfSphere.ts | 4 ++-- src/urdf/UrdfTypes.ts | 10 ++++++---- src/urdf/index.ts | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/urdf/UrdfBox.ts b/src/urdf/UrdfBox.ts index 437c45399..7da14add2 100644 --- a/src/urdf/UrdfBox.ts +++ b/src/urdf/UrdfBox.ts @@ -5,7 +5,7 @@ */ import Vector3 from '../math/Vector3.js'; -import * as UrdfTypes from './UrdfTypes.js'; +import {UrdfType} from './UrdfTypes.js'; /** * A Box element in a URDF. @@ -18,7 +18,7 @@ export default class UrdfBox { * @param {Element} options.xml - The XML element to parse. */ constructor(options) { - this.type = UrdfTypes.URDF_BOX; + this.type = UrdfType.BOX; // Parse the xml string var xyz = options.xml.getAttribute('size')?.split(' '); diff --git a/src/urdf/UrdfCylinder.ts b/src/urdf/UrdfCylinder.ts index 84b7a98f3..af0faf9bc 100644 --- a/src/urdf/UrdfCylinder.ts +++ b/src/urdf/UrdfCylinder.ts @@ -4,7 +4,7 @@ * @author Russell Toris - rctoris@wpi.edu */ -import * as UrdfTypes from './UrdfTypes.js'; +import {UrdfType} from './UrdfTypes.js'; /** * A Cylinder element in a URDF. @@ -15,7 +15,7 @@ export default class UrdfCylinder { * @param {Element} options.xml - The XML element to parse. */ constructor(options) { - this.type = UrdfTypes.URDF_CYLINDER; + this.type = UrdfType.CYLINDER; // @ts-expect-error -- possibly null this.length = parseFloat(options.xml.getAttribute('length')); // @ts-expect-error -- possibly null diff --git a/src/urdf/UrdfMesh.ts b/src/urdf/UrdfMesh.ts index d4429ec66..5d7edd889 100644 --- a/src/urdf/UrdfMesh.ts +++ b/src/urdf/UrdfMesh.ts @@ -5,7 +5,7 @@ */ import Vector3 from '../math/Vector3.js'; -import * as UrdfTypes from './UrdfTypes.js'; +import {UrdfType} from './UrdfTypes.js'; /** * A Mesh element in a URDF. @@ -18,7 +18,7 @@ export default class UrdfMesh { * @param {Element} options.xml - The XML element to parse. */ constructor(options) { - this.type = UrdfTypes.URDF_MESH; + this.type = UrdfType.MESH; this.filename = options.xml.getAttribute('filename'); // Check for a scale diff --git a/src/urdf/UrdfSphere.ts b/src/urdf/UrdfSphere.ts index 13c585fba..0f94cd81c 100644 --- a/src/urdf/UrdfSphere.ts +++ b/src/urdf/UrdfSphere.ts @@ -4,7 +4,7 @@ * @author Russell Toris - rctoris@wpi.edu */ -import * as UrdfTypes from './UrdfTypes.js'; +import {UrdfType} from './UrdfTypes.js'; /** * A Sphere element in a URDF. @@ -15,7 +15,7 @@ export default class UrdfSphere { * @param {Element} options.xml - The XML element to parse. */ constructor(options) { - this.type = UrdfTypes.URDF_SPHERE; + this.type = UrdfType.SPHERE; this.radius = parseFloat(options.xml.getAttribute('radius') || 'NaN'); } } diff --git a/src/urdf/UrdfTypes.ts b/src/urdf/UrdfTypes.ts index 972d8283a..3c25b4bbc 100644 --- a/src/urdf/UrdfTypes.ts +++ b/src/urdf/UrdfTypes.ts @@ -1,4 +1,6 @@ -export const URDF_SPHERE = 0; -export const URDF_BOX = 1; -export const URDF_CYLINDER = 2; -export const URDF_MESH = 3; +export enum UrdfType { + SPHERE = 0, + BOX = 1, + CYLINDER = 2, + MESH = 3 +} \ No newline at end of file diff --git a/src/urdf/index.ts b/src/urdf/index.ts index 8fa6d56c1..2dce0ded6 100644 --- a/src/urdf/index.ts +++ b/src/urdf/index.ts @@ -8,4 +8,4 @@ export { default as UrdfModel } from './UrdfModel.js'; export { default as UrdfSphere } from './UrdfSphere.js'; export { default as UrdfVisual } from './UrdfVisual.js'; -export * from './UrdfTypes.js'; +export {UrdfType} from './UrdfTypes.js'; From fe674c67bcfa25c621a356369946ed482c8ba419 Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 01:47:15 -0400 Subject: [PATCH 03/12] refactor(UrdfBox, UrdfTypes): Convert UrdfBox to TypeScript. Add enum of Urdf Attributes to remove magic strings when getting attributes from xml Signed-off-by: Drew Hoener --- src/urdf/UrdfBox.ts | 34 ++++++++++++++++------------------ src/urdf/UrdfTypes.ts | 34 +++++++++++++++++++++++++++++++++- src/urdf/index.ts | 2 +- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/urdf/UrdfBox.ts b/src/urdf/UrdfBox.ts index 7da14add2..05167e714 100644 --- a/src/urdf/UrdfBox.ts +++ b/src/urdf/UrdfBox.ts @@ -4,32 +4,30 @@ * @author Russell Toris - rctoris@wpi.edu */ -import Vector3 from '../math/Vector3.js'; -import {UrdfType} from './UrdfTypes.js'; +import { Vector3 } from '../math/index.js'; +import { UrdfAttrs, UrdfType, type UrdfDefaultOptions } from './UrdfTypes.js'; +import type { Optional, Nullable } from '../types/interface-types.js'; /** * A Box element in a URDF. */ export default class UrdfBox { - /** @type {Vector3 | null} */ - dimension; - /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. - */ - constructor(options) { + type: UrdfType; + dimension: Nullable = null; + + constructor({ xml }: UrdfDefaultOptions) { this.type = UrdfType.BOX; // Parse the xml string - var xyz = options.xml.getAttribute('size')?.split(' '); - if (xyz) { - this.dimension = new Vector3({ - x: parseFloat(xyz[0]), - y: parseFloat(xyz[1]), - z: parseFloat(xyz[2]) - }); - } else { - this.dimension = null; + const size: Optional = xml.getAttribute(UrdfAttrs.Size)?.split(' '); + if (!size || size.length !== 3) { + return; } + + this.dimension = new Vector3({ + x: parseFloat(size[0]), + y: parseFloat(size[1]), + z: parseFloat(size[2]) + }); } } diff --git a/src/urdf/UrdfTypes.ts b/src/urdf/UrdfTypes.ts index 3c25b4bbc..c649c58c4 100644 --- a/src/urdf/UrdfTypes.ts +++ b/src/urdf/UrdfTypes.ts @@ -3,4 +3,36 @@ export enum UrdfType { BOX = 1, CYLINDER = 2, MESH = 3 -} \ No newline at end of file +} + +export enum UrdfAttrs { + Name = 'name', + Type = 'type', + Parent = 'parent', + Link = 'link', + Child = 'child', + Limit = 'limit', + Upper = 'upper', + Lower = 'lower', + Origin = 'origin', + Xyz = 'xyz', + Rpy = 'rpy', + Size = 'size', + Rgba = 'rgba', + Length = 'length', + Radius = 'radius', + Visuals = 'visual', + Texture = 'texture', + Filename = 'filename', + Color = 'color', + Geometry = 'geometry', + Material = 'material', + Scale = 'scale', +} + +export interface UrdfDefaultOptions { + /** + * The XML element to parse. + */ + xml: Element; +} diff --git a/src/urdf/index.ts b/src/urdf/index.ts index 2dce0ded6..fe62e5c44 100644 --- a/src/urdf/index.ts +++ b/src/urdf/index.ts @@ -8,4 +8,4 @@ export { default as UrdfModel } from './UrdfModel.js'; export { default as UrdfSphere } from './UrdfSphere.js'; export { default as UrdfVisual } from './UrdfVisual.js'; -export {UrdfType} from './UrdfTypes.js'; +export {UrdfAttrs, UrdfType, type UrdfDefaultOptions} from './UrdfTypes.js'; From e115cfb0c3c15f9949c01eec425ab21d8bddc9ef Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 01:51:07 -0400 Subject: [PATCH 04/12] refactor(UrdfColor): Convert UrdfColor to TypeScript. Signed-off-by: Drew Hoener --- src/urdf/UrdfColor.ts | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/urdf/UrdfColor.ts b/src/urdf/UrdfColor.ts index be5ec2821..850171e4c 100644 --- a/src/urdf/UrdfColor.ts +++ b/src/urdf/UrdfColor.ts @@ -4,22 +4,41 @@ * @author Russell Toris - rctoris@wpi.edu */ +import { UrdfAttrs, type UrdfDefaultOptions } from './UrdfTypes.js'; +import type { Optional } from '../types/interface-types.js'; + /** * A Color element in a URDF. */ export default class UrdfColor { + + /** + * Color Red, [0, 1] + */ + r = 0.0; + /** + * Color Green, [0, 1] + */ + g = 0.0; + /** + * Color Blue, [0, 1] + */ + b = 0.0; /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. + * Alpha/Opacity, [0, 1] */ - constructor(options) { + a = 1.0; + + constructor({ xml }: UrdfDefaultOptions) { // Parse the xml string - var rgba = options.xml.getAttribute('rgba')?.split(' '); - if (rgba) { - this.r = parseFloat(rgba[0]); - this.g = parseFloat(rgba[1]); - this.b = parseFloat(rgba[2]); - this.a = parseFloat(rgba[3]); + const rgba: Optional = xml.getAttribute(UrdfAttrs.Rgba)?.split(' '); + if (!rgba || rgba.length !== 4) { + return; } + + this.r = parseFloat(rgba[0]); + this.g = parseFloat(rgba[1]); + this.b = parseFloat(rgba[2]); + this.a = parseFloat(rgba[3]); } } From 55b135a185ca542961a12a6d0f335d29525b80f9 Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 01:53:36 -0400 Subject: [PATCH 05/12] refactor(UrdfCylinder): Convert UrdfCylinder to TypeScript, fix parsing warning. Signed-off-by: Drew Hoener --- src/urdf/UrdfCylinder.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/urdf/UrdfCylinder.ts b/src/urdf/UrdfCylinder.ts index af0faf9bc..460bdfff0 100644 --- a/src/urdf/UrdfCylinder.ts +++ b/src/urdf/UrdfCylinder.ts @@ -4,21 +4,21 @@ * @author Russell Toris - rctoris@wpi.edu */ -import {UrdfType} from './UrdfTypes.js'; +import { type UrdfDefaultOptions, UrdfType, UrdfAttrs } from './UrdfTypes.js'; /** * A Cylinder element in a URDF. */ export default class UrdfCylinder { - /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. - */ - constructor(options) { + + type: UrdfType; + length: number; + radius: number; + + constructor({ xml }: UrdfDefaultOptions) { this.type = UrdfType.CYLINDER; - // @ts-expect-error -- possibly null - this.length = parseFloat(options.xml.getAttribute('length')); - // @ts-expect-error -- possibly null - this.radius = parseFloat(options.xml.getAttribute('radius')); + + this.length = parseFloat(xml.getAttribute(UrdfAttrs.Length) ?? 'NaN'); + this.radius = parseFloat(xml.getAttribute(UrdfAttrs.Radius) ?? 'NaN'); } } From 202ed57e65871c2521d7a51b27e16b3f1dfe1a7b Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 01:55:55 -0400 Subject: [PATCH 06/12] refactor(UrdfJoint, UrdfUtils): Convert UrdfJoint to TypeScript and extract origin parsing logic to UrdfUtils. Signed-off-by: Drew Hoener --- src/urdf/UrdfJoint.ts | 98 ++++++++++++------------------------------- src/urdf/UrdfUtils.ts | 54 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 71 deletions(-) create mode 100644 src/urdf/UrdfUtils.ts diff --git a/src/urdf/UrdfJoint.ts b/src/urdf/UrdfJoint.ts index be9ec286b..3b5c2bca3 100644 --- a/src/urdf/UrdfJoint.ts +++ b/src/urdf/UrdfJoint.ts @@ -3,93 +3,49 @@ * @author David V. Lu!! - davidvlu@gmail.com */ -import Pose from '../math/Pose.js'; -import Vector3 from '../math/Vector3.js'; -import Quaternion from '../math/Quaternion.js'; +import { UrdfAttrs, type UrdfDefaultOptions } from './UrdfTypes.js'; +import { Pose } from '../math/index.js'; +import { parseUrdfOrigin } from './UrdfUtils.js'; +import type { Nullable } from '../types/interface-types.js'; /** * A Joint element in a URDF. */ export default class UrdfJoint { - /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. - */ - constructor(options) { - this.name = options.xml.getAttribute('name'); - this.type = options.xml.getAttribute('type'); - var parents = options.xml.getElementsByTagName('parent'); + name: string; + type: Nullable; + parent: Nullable = null; + child: Nullable = null; + minval = NaN; + maxval = NaN; + origin: Pose = new Pose(); + + + constructor({xml}: UrdfDefaultOptions) { + this.name = xml.getAttribute(UrdfAttrs.Name) ?? 'unknown_name'; + this.type = xml.getAttribute(UrdfAttrs.Type); + + const parents = xml.getElementsByTagName(UrdfAttrs.Parent); if (parents.length > 0) { - this.parent = parents[0].getAttribute('link'); + this.parent = parents[0].getAttribute(UrdfAttrs.Link); } - var children = options.xml.getElementsByTagName('child'); + const children = xml.getElementsByTagName(UrdfAttrs.Child); if (children.length > 0) { - this.child = children[0].getAttribute('link'); + this.child = children[0].getAttribute(UrdfAttrs.Link); } - var limits = options.xml.getElementsByTagName('limit'); + const limits = xml.getElementsByTagName(UrdfAttrs.Limit); if (limits.length > 0) { - this.minval = parseFloat(limits[0].getAttribute('lower') || 'NaN'); - this.maxval = parseFloat(limits[0].getAttribute('upper') || 'NaN'); + this.minval = parseFloat(limits[0].getAttribute(UrdfAttrs.Lower) ?? 'NaN'); + this.maxval = parseFloat(limits[0].getAttribute(UrdfAttrs.Upper) ?? 'NaN'); } // Origin - var origins = options.xml.getElementsByTagName('origin'); - if (origins.length === 0) { - // use the identity as the default - this.origin = new Pose(); - } else { - // Check the XYZ - var xyzValue = origins[0].getAttribute('xyz'); - var position = new Vector3(); - if (xyzValue) { - var xyz = xyzValue.split(' '); - position = new Vector3({ - x: parseFloat(xyz[0]), - y: parseFloat(xyz[1]), - z: parseFloat(xyz[2]) - }); - } - - // Check the RPY - var rpyValue = origins[0].getAttribute('rpy'); - var orientation = new Quaternion(); - if (rpyValue) { - var rpy = rpyValue.split(' '); - // Convert from RPY - var roll = parseFloat(rpy[0]); - var pitch = parseFloat(rpy[1]); - var yaw = parseFloat(rpy[2]); - var phi = roll / 2.0; - var the = pitch / 2.0; - var psi = yaw / 2.0; - var x = - Math.sin(phi) * Math.cos(the) * Math.cos(psi) - - Math.cos(phi) * Math.sin(the) * Math.sin(psi); - var y = - Math.cos(phi) * Math.sin(the) * Math.cos(psi) + - Math.sin(phi) * Math.cos(the) * Math.sin(psi); - var z = - Math.cos(phi) * Math.cos(the) * Math.sin(psi) - - Math.sin(phi) * Math.sin(the) * Math.cos(psi); - var w = - Math.cos(phi) * Math.cos(the) * Math.cos(psi) + - Math.sin(phi) * Math.sin(the) * Math.sin(psi); - - orientation = new Quaternion({ - x: x, - y: y, - z: z, - w: w - }); - orientation.normalize(); - } - this.origin = new Pose({ - position: position, - orientation: orientation - }); + const origins = xml.getElementsByTagName(UrdfAttrs.Origin); + if (origins.length > 0) { + this.origin = parseUrdfOrigin(origins[0]); } } } diff --git a/src/urdf/UrdfUtils.ts b/src/urdf/UrdfUtils.ts new file mode 100644 index 000000000..28185d379 --- /dev/null +++ b/src/urdf/UrdfUtils.ts @@ -0,0 +1,54 @@ +/********** Utility Methods for parsing Joint **********/ +import { Pose, Quaternion, Vector3 } from '../math/index.js'; +import { UrdfAttrs } from './UrdfTypes.js'; + +export function parseUrdfOrigin(originElement: Element): Pose { + // Check the XYZ + const xyz: string[] | undefined = originElement.getAttribute(UrdfAttrs.Xyz)?.split(' '); + let position: Vector3 = new Vector3(); + if (xyz && xyz.length === 3) { + position = new Vector3({ + x: parseFloat(xyz[0]), + y: parseFloat(xyz[1]), + z: parseFloat(xyz[2]) + }); + } + + // Check the RPY + const rpy = originElement.getAttribute(UrdfAttrs.Rpy)?.split(' '); + let orientation = new Quaternion(); + if (rpy && rpy.length === 3) { + // Convert from RPY + const roll = parseFloat(rpy[0]); + const pitch = parseFloat(rpy[1]); + const yaw = parseFloat(rpy[2]); + const phi = roll / 2.0; + const the = pitch / 2.0; + const psi = yaw / 2.0; + const x = + Math.sin(phi) * Math.cos(the) * Math.cos(psi) - + Math.cos(phi) * Math.sin(the) * Math.sin(psi); + const y = + Math.cos(phi) * Math.sin(the) * Math.cos(psi) + + Math.sin(phi) * Math.cos(the) * Math.sin(psi); + const z = + Math.cos(phi) * Math.cos(the) * Math.sin(psi) - + Math.sin(phi) * Math.sin(the) * Math.cos(psi); + const w = + Math.cos(phi) * Math.cos(the) * Math.cos(psi) + + Math.sin(phi) * Math.sin(the) * Math.sin(psi); + + orientation = new Quaternion({ + x: x, + y: y, + z: z, + w: w + }); + orientation.normalize(); + } + + return new Pose({ + position: position, + orientation: orientation + }); +} \ No newline at end of file From 388eced6e1adc5f43231015f3c9896870e7c1757 Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 01:59:45 -0400 Subject: [PATCH 07/12] refactor(UrdfMaterial): Convert UrdfMaterial to TypeScript. Signed-off-by: Drew Hoener --- src/urdf/UrdfMaterial.ts | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/urdf/UrdfMaterial.ts b/src/urdf/UrdfMaterial.ts index c3fbd3142..c2561358b 100644 --- a/src/urdf/UrdfMaterial.ts +++ b/src/urdf/UrdfMaterial.ts @@ -5,31 +5,30 @@ */ import UrdfColor from './UrdfColor.js'; +import { UrdfAttrs, type UrdfDefaultOptions } from './UrdfTypes.js'; +import type { Nullable } from '../types/interface-types.js'; /** * A Material element in a URDF. */ export default class UrdfMaterial { - /** @type {string | null} */ - textureFilename = null; - /** @type {UrdfColor | null} */ - color = null; - /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. - */ - constructor(options) { - - this.name = options.xml.getAttribute('name'); + + name: string; + textureFilename: Nullable = null; + color: Nullable = null; + + constructor({ xml }: UrdfDefaultOptions) { + + this.name = xml.getAttribute(UrdfAttrs.Name) ?? 'unknown_name'; // Texture - var textures = options.xml.getElementsByTagName('texture'); + const textures = xml.getElementsByTagName(UrdfAttrs.Texture); if (textures.length > 0) { - this.textureFilename = textures[0].getAttribute('filename'); + this.textureFilename = textures[0].getAttribute(UrdfAttrs.Filename); } // Color - var colors = options.xml.getElementsByTagName('color'); + const colors = xml.getElementsByTagName(UrdfAttrs.Color); if (colors.length > 0) { // Parse the RBGA string this.color = new UrdfColor({ @@ -37,10 +36,12 @@ export default class UrdfMaterial { }); } } + isLink() { return this.color === null && this.textureFilename === null; } - assign(obj) { + + assign(obj: UrdfMaterial): this & UrdfMaterial { return Object.assign(this, obj); } } From 4ac34b45ad5e6ad83017a2f571bd4160e49e2abc Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 02:03:19 -0400 Subject: [PATCH 08/12] refactor(UrdfMesh): Convert UrdfMesh to TypeScript. Signed-off-by: Drew Hoener --- src/urdf/UrdfMesh.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/urdf/UrdfMesh.ts b/src/urdf/UrdfMesh.ts index 5d7edd889..26f9fdba5 100644 --- a/src/urdf/UrdfMesh.ts +++ b/src/urdf/UrdfMesh.ts @@ -4,33 +4,36 @@ * @author Russell Toris - rctoris@wpi.edu */ -import Vector3 from '../math/Vector3.js'; -import {UrdfType} from './UrdfTypes.js'; +import { Vector3 } from '../math/index.js'; +import { UrdfAttrs, type UrdfDefaultOptions, UrdfType } from './UrdfTypes.js'; +import type { Nullable, Optional } from '../types/interface-types.js'; /** * A Mesh element in a URDF. */ export default class UrdfMesh { - /** @type {Vector3 | null} */ - scale = null; + type: UrdfType; + scale: Nullable = null; + filename: Nullable; + /** * @param {Object} options * @param {Element} options.xml - The XML element to parse. */ - constructor(options) { + constructor({xml}: UrdfDefaultOptions) { this.type = UrdfType.MESH; - this.filename = options.xml.getAttribute('filename'); + this.filename = xml.getAttribute(UrdfAttrs.Filename); // Check for a scale - var scale = options.xml.getAttribute('scale'); - if (scale) { - // Get the XYZ - var xyz = scale.split(' '); - this.scale = new Vector3({ - x: parseFloat(xyz[0]), - y: parseFloat(xyz[1]), - z: parseFloat(xyz[2]) - }); + const scale: Optional = xml.getAttribute(UrdfAttrs.Scale)?.split(' '); + if (!scale || scale.length !== 3) { + return; } + + this.scale = new Vector3({ + x: parseFloat(scale[0]), + y: parseFloat(scale[1]), + z: parseFloat(scale[2]) + }); } } From 9ab99a29cd3264a7bab33ee14973938522f64448 Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 02:05:03 -0400 Subject: [PATCH 09/12] refactor(UrdfSphere): Convert UrdfSphere to TypeScript. Signed-off-by: Drew Hoener --- src/urdf/UrdfSphere.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/urdf/UrdfSphere.ts b/src/urdf/UrdfSphere.ts index 0f94cd81c..cb5d39d2e 100644 --- a/src/urdf/UrdfSphere.ts +++ b/src/urdf/UrdfSphere.ts @@ -4,18 +4,18 @@ * @author Russell Toris - rctoris@wpi.edu */ -import {UrdfType} from './UrdfTypes.js'; +import { UrdfAttrs, type UrdfDefaultOptions, UrdfType } from './UrdfTypes.js'; /** * A Sphere element in a URDF. */ export default class UrdfSphere { - /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. - */ - constructor(options) { + + type: UrdfType; + radius = NaN; + + constructor({xml}: UrdfDefaultOptions) { this.type = UrdfType.SPHERE; - this.radius = parseFloat(options.xml.getAttribute('radius') || 'NaN'); + this.radius = parseFloat(xml.getAttribute(UrdfAttrs.Radius) ?? 'NaN'); } } From b8f38f0ce9bc19b326830c120299df975389a538 Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 02:09:13 -0400 Subject: [PATCH 10/12] refactor(UrdfVisual, UrdfUtils): Convert UrdfVisual to TypeScript, add isElement check in UrdfUtils Signed-off-by: Drew Hoener --- src/urdf/UrdfUtils.ts | 7 +- src/urdf/UrdfVisual.ts | 158 +++++++++++++---------------------------- src/urdf/index.ts | 3 +- 3 files changed, 59 insertions(+), 109 deletions(-) diff --git a/src/urdf/UrdfUtils.ts b/src/urdf/UrdfUtils.ts index 28185d379..43613d229 100644 --- a/src/urdf/UrdfUtils.ts +++ b/src/urdf/UrdfUtils.ts @@ -51,4 +51,9 @@ export function parseUrdfOrigin(originElement: Element): Pose { position: position, orientation: orientation }); -} \ No newline at end of file +} + +export function isElement(node: Node): node is Element { + // Node.ELEMENT_TYPE = 1 + return node.nodeType === 1; +} diff --git a/src/urdf/UrdfVisual.ts b/src/urdf/UrdfVisual.ts index 2832a72fb..c00b9262b 100644 --- a/src/urdf/UrdfVisual.ts +++ b/src/urdf/UrdfVisual.ts @@ -5,132 +5,76 @@ */ import Pose from '../math/Pose.js'; -import Vector3 from '../math/Vector3.js'; -import Quaternion from '../math/Quaternion.js'; - import UrdfCylinder from './UrdfCylinder.js'; import UrdfBox from './UrdfBox.js'; import UrdfMaterial from './UrdfMaterial.js'; import UrdfMesh from './UrdfMesh.js'; import UrdfSphere from './UrdfSphere.js'; +import { UrdfAttrs, type UrdfDefaultOptions } from './UrdfTypes.js'; +import { isElement, parseUrdfOrigin } from './UrdfUtils.js'; + +export type UrdfGeometryLike = UrdfMesh | UrdfSphere | UrdfBox | UrdfCylinder; + +function parseUrdfGeometry(geometryElem: Element): UrdfGeometryLike | null { + + let childShape: Element | null = null; + for (const childNode of geometryElem.childNodes) { + if (isElement(childNode)) { + // Safe type check after checking nodeType + childShape = childNode; + break; + } + } + + if (!childShape) { + return null; + } + + const options: UrdfDefaultOptions = { + xml: childShape + } + + switch (childShape.nodeName) { + case 'sphere': + return new UrdfSphere(options); + case 'box': + return new UrdfBox(options); + case 'cylinder': + return new UrdfCylinder(options); + case 'mesh': + return new UrdfMesh(options); + default: + console.warn(`Unknown geometry type ${childShape.nodeName}`); + return null + } +} /** * A Visual element in a URDF. */ export default class UrdfVisual { - /** @type {Pose | null} */ - origin = null; - /** @type {UrdfMesh | UrdfSphere | UrdfBox | UrdfCylinder | null} */ - geometry = null; - /** @type {UrdfMaterial | null} */ - material = null; - /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. - */ - constructor(options) { - var xml = options.xml; - this.name = options.xml.getAttribute('name'); + name: string | null; + origin: Pose | null = new Pose(); + geometry: UrdfGeometryLike | null = null; + material: UrdfMaterial | null = null; - // Origin - var origins = xml.getElementsByTagName('origin'); - if (origins.length === 0) { - // use the identity as the default - this.origin = new Pose(); - } else { - // Check the XYZ - var xyzValue = origins[0].getAttribute('xyz'); - var position = new Vector3(); - if (xyzValue) { - var xyz = xyzValue.split(' '); - position = new Vector3({ - x: parseFloat(xyz[0]), - y: parseFloat(xyz[1]), - z: parseFloat(xyz[2]) - }); - } - - // Check the RPY - var rpyValue = origins[0].getAttribute('rpy'); - var orientation = new Quaternion(); - if (rpyValue) { - var rpy = rpyValue.split(' '); - // Convert from RPY - var roll = parseFloat(rpy[0]); - var pitch = parseFloat(rpy[1]); - var yaw = parseFloat(rpy[2]); - var phi = roll / 2.0; - var the = pitch / 2.0; - var psi = yaw / 2.0; - var x = - Math.sin(phi) * Math.cos(the) * Math.cos(psi) - - Math.cos(phi) * Math.sin(the) * Math.sin(psi); - var y = - Math.cos(phi) * Math.sin(the) * Math.cos(psi) + - Math.sin(phi) * Math.cos(the) * Math.sin(psi); - var z = - Math.cos(phi) * Math.cos(the) * Math.sin(psi) - - Math.sin(phi) * Math.sin(the) * Math.cos(psi); - var w = - Math.cos(phi) * Math.cos(the) * Math.cos(psi) + - Math.sin(phi) * Math.sin(the) * Math.sin(psi); + constructor({ xml }: UrdfDefaultOptions) { + this.name = xml.getAttribute(UrdfAttrs.Name); - orientation = new Quaternion({ - x: x, - y: y, - z: z, - w: w - }); - orientation.normalize(); - } - this.origin = new Pose({ - position: position, - orientation: orientation - }); + // Origin + const origins = xml.getElementsByTagName(UrdfAttrs.Origin); + if (origins.length > 0) { + this.origin = parseUrdfOrigin(origins[0]); } // Geometry - var geoms = xml.getElementsByTagName('geometry'); + const geoms = xml.getElementsByTagName(UrdfAttrs.Geometry); if (geoms.length > 0) { - var geom = geoms[0]; - var shape = null; - // Check for the shape - for (var i = 0; i < geom.childNodes.length; i++) { - /** @type {Element} */ - // @ts-expect-error -- unknown why this doesn't work properly. - var node = geom.childNodes[i]; - if (node.nodeType === 1) { - shape = node; - break; - } - } - if (shape) { - // Check the type - var type = shape.nodeName; - if (type === 'sphere') { - this.geometry = new UrdfSphere({ - xml: shape - }); - } else if (type === 'box') { - this.geometry = new UrdfBox({ - xml: shape - }); - } else if (type === 'cylinder') { - this.geometry = new UrdfCylinder({ - xml: shape - }); - } else if (type === 'mesh') { - this.geometry = new UrdfMesh({ - xml: shape - }); - } else { - console.warn('Unknown geometry type ' + type); - } - } + this.geometry = parseUrdfGeometry(geoms[0]); } // Material - var materials = xml.getElementsByTagName('material'); + const materials = xml.getElementsByTagName(UrdfAttrs.Material); if (materials.length > 0) { this.material = new UrdfMaterial({ xml: materials[0] diff --git a/src/urdf/index.ts b/src/urdf/index.ts index fe62e5c44..f961652b5 100644 --- a/src/urdf/index.ts +++ b/src/urdf/index.ts @@ -6,6 +6,7 @@ export { default as UrdfMaterial } from './UrdfMaterial.js'; export { default as UrdfMesh } from './UrdfMesh.js'; export { default as UrdfModel } from './UrdfModel.js'; export { default as UrdfSphere } from './UrdfSphere.js'; -export { default as UrdfVisual } from './UrdfVisual.js'; +export { default as UrdfVisual, type UrdfGeometryLike } from './UrdfVisual.js'; export {UrdfAttrs, UrdfType, type UrdfDefaultOptions} from './UrdfTypes.js'; +export {isElement, parseUrdfOrigin} from './UrdfUtils.js'; From 539061d11b9abd017c06cce39f3668ca6a428e7f Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 02:12:39 -0400 Subject: [PATCH 11/12] refactor(UrdfLink): Convert UrdfLink to TypeScript, loop with for-of since ES6 provides it Signed-off-by: Drew Hoener --- src/urdf/UrdfLink.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/urdf/UrdfLink.ts b/src/urdf/UrdfLink.ts index 8bc1e3a5a..44d399884 100644 --- a/src/urdf/UrdfLink.ts +++ b/src/urdf/UrdfLink.ts @@ -5,24 +5,24 @@ */ import UrdfVisual from './UrdfVisual.js'; +import { UrdfAttrs, type UrdfDefaultOptions } from './UrdfTypes.js'; /** * A Link element in a URDF. */ export default class UrdfLink { - /** - * @param {Object} options - * @param {Element} options.xml - The XML element to parse. - */ - constructor(options) { - this.name = options.xml.getAttribute('name'); - this.visuals = []; - var visuals = options.xml.getElementsByTagName('visual'); - for (var i = 0; i < visuals.length; i++) { + name: string; + visuals: UrdfVisual[] = []; + + constructor({ xml }: UrdfDefaultOptions) { + this.name = xml.getAttribute(UrdfAttrs.Name) ?? 'unknown_name'; + const visuals = xml.getElementsByTagName(UrdfAttrs.Visuals); + + for (const visual of visuals) { this.visuals.push( new UrdfVisual({ - xml: visuals[i] + xml: visual }) ); } From 71a85c57fce36a9992cb5843d2fae0faf0e7da4b Mon Sep 17 00:00:00 2001 From: Drew Hoener Date: Fri, 8 Aug 2025 02:16:57 -0400 Subject: [PATCH 12/12] refactor(UrdfModel): Convert UrdfModel to TypeScript, replace loops with for-of, and improve type safety Signed-off-by: Drew Hoener --- src/urdf/UrdfModel.ts | 125 ++++++++++++++++++++++++------------------ src/urdf/index.ts | 2 +- 2 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/urdf/UrdfModel.ts b/src/urdf/UrdfModel.ts index 3c432d8c6..273843d3f 100644 --- a/src/urdf/UrdfModel.ts +++ b/src/urdf/UrdfModel.ts @@ -4,94 +4,111 @@ * @author Russell Toris - rctoris@wpi.edu */ +import { DOMParser, MIME_TYPE } from '@xmldom/xmldom'; import UrdfMaterial from './UrdfMaterial.js'; import UrdfLink from './UrdfLink.js'; import UrdfJoint from './UrdfJoint.js'; -import { DOMParser, MIME_TYPE } from '@xmldom/xmldom'; +import { isElement } from './UrdfUtils.js'; +import { UrdfAttrs } from './UrdfTypes.js'; // See https://developer.mozilla.org/docs/XPathResult#Constants -var XPATH_FIRST_ORDERED_NODE_TYPE = 9; +// const XPATH_FIRST_ORDERED_NODE_TYPE = 9; + +export interface UrdfModelOptions { + /** + * The XML element to parse. + */ + xml?: Element; + /** + * The XML element to parse as a string. + */ + string: string; +} /** * A URDF Model can be used to parse a given URDF into the appropriate elements. */ export default class UrdfModel { - materials = {}; - links = {}; - joints = {}; - /** - * @param {Object} options - * @param {Element | null} [options.xml] - The XML element to parse. - * @param {string} [options.string] - The XML element to parse as a string. - */ - constructor(options) { - var xmlDoc = options.xml; - var string = options.string; + + name: string | null; + materials: Record = {}; + links: Record = {}; + joints: Record = {}; + + constructor({ xml, string }: UrdfModelOptions) { + let xmlDoc = xml; // Check if we are using a string or an XML element if (string) { // Parse the string - var parser = new DOMParser(); - xmlDoc = parser.parseFromString(string, MIME_TYPE.XML_TEXT).documentElement; + xmlDoc = new DOMParser().parseFromString(string, MIME_TYPE.XML_TEXT).documentElement; } + if (!xmlDoc) { throw new Error('No URDF document parsed!'); } - // Initialize the model with the given XML node. - // Get the robot tag - var robotXml = xmlDoc; - // Get the robot name - this.name = robotXml.getAttribute('name'); + this.name = xmlDoc.getAttribute(UrdfAttrs.Name); + const childNodes = xmlDoc.childNodes; // Parse all the visual elements we need - for (var nodes = robotXml.childNodes, i = 0; i < nodes.length; i++) { - /** @type {Element} */ - // @ts-expect-error -- unknown why this doesn't work properly. - var node = nodes[i]; - if (node.tagName === 'material') { - var material = new UrdfMaterial({ - xml: node - }); - // Make sure this is unique - if (this.materials[material.name] !== void 0) { + for (const node of childNodes) { + + // Safety check to make sure we're working with an element. + if (!isElement(node)) { + continue; + } + + switch (node.tagName) { + case 'material': { + const material = new UrdfMaterial({ xml: node }); + // Make sure this is unique + if (!Object.hasOwn(this.materials, material.name)) { + this.materials[material.name] = material; + break; + } + if (this.materials[material.name].isLink()) { this.materials[material.name].assign(material); } else { - console.warn('Material ' + material.name + 'is not unique.'); + console.warn(`Material ${material.name} is not unique.`); } - } else { - this.materials[material.name] = material; + + break; } - } else if (node.tagName === 'link') { - var link = new UrdfLink({ - xml: node - }); - // Make sure this is unique - if (this.links[link.name] !== void 0) { - console.warn('Link ' + link.name + ' is not unique.'); - } else { + case 'link': { + const link = new UrdfLink({ xml: node }); + // Make sure this is unique + if (Object.hasOwn(this.links, link.name)) { + console.warn(`Link ${link.name} is not unique.`); + break; + } + // Check for a material - for (var j = 0; j < link.visuals.length; j++) { - var mat = link.visuals[j].material; - if (mat !== null && mat.name) { - if (this.materials[mat.name] !== void 0) { - link.visuals[j].material = this.materials[mat.name]; - } else { - this.materials[mat.name] = mat; - } + for (const item of link.visuals) { + const mat = item.material; + if (!mat?.name) { + continue; + } + + if (Object.hasOwn(this.materials, mat.name)) { + item.material = this.materials[mat.name]; + } else { + this.materials[mat.name] = mat; } } // Add the link this.links[link.name] = link; + + break; + } + case 'joint': { + const joint = new UrdfJoint({ xml: node }); + this.joints[joint.name] = joint; + break; } - } else if (node.tagName === 'joint') { - var joint = new UrdfJoint({ - xml: node - }); - this.joints[joint.name] = joint; } } } diff --git a/src/urdf/index.ts b/src/urdf/index.ts index f961652b5..9b7fc33c5 100644 --- a/src/urdf/index.ts +++ b/src/urdf/index.ts @@ -4,7 +4,7 @@ export { default as UrdfCylinder } from './UrdfCylinder.js'; export { default as UrdfLink } from './UrdfLink.js'; export { default as UrdfMaterial } from './UrdfMaterial.js'; export { default as UrdfMesh } from './UrdfMesh.js'; -export { default as UrdfModel } from './UrdfModel.js'; +export { default as UrdfModel, type UrdfModelOptions } from './UrdfModel.js'; export { default as UrdfSphere } from './UrdfSphere.js'; export { default as UrdfVisual, type UrdfGeometryLike } from './UrdfVisual.js';