Skip to content

Commit d53f503

Browse files
committed
Fix link selection for Joints Editor
1 parent 04bfe1c commit d53f503

File tree

3 files changed

+45
-25
lines changed

3 files changed

+45
-25
lines changed

src/layout.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -644,11 +644,11 @@ export class URDFLayout extends PanelLayout {
644644
this._interactionEditor.unHighlightLink('parent');
645645
this._selectedLinks.parent = { name: null, obj: null };
646646
} else {
647-
const link = this._loader.robotModel.links[linkName];
648-
const linkObject = link.children.find((c: any) => c.isURDFVisual)
649-
?.children[0];
647+
const linkObject = this._renderer.getLinkObject(linkName);
650648
this._selectedLinks.parent = { name: linkName, obj: linkObject };
651-
this._interactionEditor.highlightLink(linkObject, 'parent');
649+
if (linkObject) {
650+
this._interactionEditor.highlightLink(linkObject, 'parent');
651+
}
652652
}
653653
updateJointName();
654654
});
@@ -663,11 +663,11 @@ export class URDFLayout extends PanelLayout {
663663
this._interactionEditor.unHighlightLink('child');
664664
this._selectedLinks.child = { name: null, obj: null };
665665
} else {
666-
const link = this._loader.robotModel.links[linkName];
667-
const linkObject = link.children.find((c: any) => c.isURDFVisual)
668-
?.children[0];
666+
const linkObject = this._renderer.getLinkObject(linkName);
669667
this._selectedLinks.child = { name: linkName, obj: linkObject };
670-
this._interactionEditor.highlightLink(linkObject, 'child');
668+
if (linkObject) {
669+
this._interactionEditor.highlightLink(linkObject, 'child');
670+
}
671671
}
672672
updateJointName();
673673
});
@@ -703,24 +703,25 @@ export class URDFLayout extends PanelLayout {
703703
parentLink: string,
704704
childLink: string
705705
): void {
706+
this._interactionEditor.clearHighlights();
706707
if (parentLink !== 'none') {
707-
const link = this._loader.robotModel.links[parentLink];
708-
const linkObject = link?.children.find((c: any) => c.isURDFVisual)
709-
?.children[0];
708+
const linkObject = this._renderer.getLinkObject(parentLink);
710709
this._selectedLinks.parent = { name: parentLink, obj: linkObject };
711710
if (linkObject) {
712711
this._interactionEditor.highlightLink(linkObject, 'parent');
713712
}
713+
} else {
714+
this._selectedLinks.parent = { name: null, obj: null };
714715
}
715716

716717
if (childLink !== 'none') {
717-
const link = this._loader.robotModel.links[childLink];
718-
const linkObject = link?.children.find((c: any) => c.isURDFVisual)
719-
?.children[0];
718+
const linkObject = this._renderer.getLinkObject(childLink);
720719
this._selectedLinks.child = { name: childLink, obj: linkObject };
721720
if (linkObject) {
722721
this._interactionEditor.highlightLink(linkObject, 'child');
723722
}
723+
} else {
724+
this._selectedLinks.child = { name: null, obj: null };
724725
}
725726
}
726727

src/links/linkManager.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class LinkManager {
99
private _frameHelpers: THREE.Group;
1010
private _redrawCallback: () => void;
1111
private _linkToMeshes: Map<string, THREE.Mesh[]> = new Map();
12-
private _correctLinkMap: Map<string, THREE.Object3D> = new Map(); // Use base Object3D for flexibility
12+
private _correctLinkMap: Map<string, THREE.Object3D> = new Map();
1313

1414
constructor(scene: THREE.Scene, redrawCallback: () => void) {
1515
this._frameHelpers = new THREE.Group();
@@ -139,6 +139,17 @@ export class LinkManager {
139139
this._redrawCallback();
140140
}
141141

142+
/**
143+
* Retrieves the visual object for a given link name.
144+
* @param linkName - The name of the link.
145+
* @returns The THREE.Object3D associated with the link's visual, or null.
146+
*/
147+
public getLinkObject(linkName: string): THREE.Object3D | null {
148+
const meshes = this._linkToMeshes.get(linkName);
149+
// Return the first mesh if it exists, otherwise null.
150+
return meshes && meshes.length > 0 ? meshes[0] : null;
151+
}
152+
142153
/**
143154
* Disposes of managed resources.
144155
*/
@@ -162,8 +173,7 @@ export class LinkManager {
162173
return;
163174
}
164175

165-
// Step 1: Build a map from the <link> XML Element to the THREE.URDFVisual object.
166-
// This is the bridge from the XML world to the THREE.js world.
176+
// Step 1: Build a map from the <link> XML Element to the THREE.URDFVisual object
167177
const linkXmlToVisualMap = new Map<Element, URDFVisual>();
168178
robot.traverse(node => {
169179
if ((node as any).isURDFVisual) {
@@ -176,10 +186,10 @@ export class LinkManager {
176186
}
177187
});
178188

179-
// Step 2: Get all <link> tags from the XML. This is our ground truth list of links.
189+
// Step 2: Get all <link> tags from the XML
180190
const allLinkElements = rootXml.querySelectorAll('link');
181191

182-
// Step 3: Iterate through the ground truth list and populate our maps.
192+
// Step 3: Iterate through the list and populate our maps
183193
allLinkElements.forEach(linkElement => {
184194
const linkName = linkElement.getAttribute('name');
185195
if (!linkName) {
@@ -199,23 +209,23 @@ export class LinkManager {
199209

200210
// Map the transform object.
201211
if (visual.parent && visual.parent !== robot) {
202-
// Jointed link: the parent is the distinct URDFLink object.
212+
// Jointed link: the parent is the distinct URDFLink object
203213
this._correctLinkMap.set(linkName, visual.parent);
204214
} else {
205-
// Jointless link: the visual itself is the best object representing the transform.
215+
// Jointless link: the visual itself is the best object representing the transform
206216
this._correctLinkMap.set(linkName, visual);
207217
}
208218
} else {
209-
// This link has no visual component (like 'world').
219+
// This link has no visual component (like 'world')
210220
this._linkToMeshes.set(linkName, []);
211-
// The root URDFRobot object itself acts as the 'world' link.
221+
// The root URDFRobot object itself acts as the 'world' link
212222
if (linkName === 'world') {
213223
this._correctLinkMap.set(linkName, robot);
214224
}
215225
}
216226
});
217227

218-
// Step 4: Clone materials for all found meshes to ensure uniqueness.
228+
// Step 4: Clone materials for all found meshes to ensure uniqueness
219229
for (const meshes of this._linkToMeshes.values()) {
220230
meshes.forEach(mesh => {
221231
if (Array.isArray(mesh.material)) {
@@ -228,7 +238,7 @@ export class LinkManager {
228238
}
229239

230240
/**
231-
* Creates a custom axes helper.
241+
* Creates a custom axes helper
232242
*/
233243
private _createCustomAxesHelper(size = 0.3): THREE.Group {
234244
const axesGroup = new THREE.Group();

src/renderer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,15 @@ export class URDFRenderer extends THREE.WebGLRenderer {
269269
this._linkManager.setLinkOpacity(linkName, opacity);
270270
}
271271

272+
/**
273+
* Retrieves the visual object for a given link name.
274+
* @param linkName - The name of the link.
275+
* @returns The THREE.Object3D associated with the link's visual, or null.
276+
*/
277+
getLinkObject(linkName: string): THREE.Object3D | null {
278+
return this._linkManager.getLinkObject(linkName);
279+
}
280+
272281
/**
273282
* Refreshes the viewer by re-rendering the scene and its elements
274283
*/

0 commit comments

Comments
 (0)