Skip to content

Commit 37d5422

Browse files
Merge pull request #97 Joint Editor from Yahiewi
Add Editor folder to controls panel Add option to modify joint and reset button Fix the Editor/Viewer split panel
2 parents 0109519 + 7f99383 commit 37d5422

File tree

7 files changed

+1233
-29
lines changed

7 files changed

+1233
-29
lines changed

src/controls.ts

Lines changed: 167 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class URDFControls extends GUI {
1616
private _workspaceFolder: any;
1717
private _sceneFolder: any;
1818
private _jointsFolder: any;
19+
private _jointsEditorFolder: any;
1920
private _workingPath = '';
2021

2122
controls: any = {
@@ -26,7 +27,8 @@ export class URDFControls extends GUI {
2627
height: {}
2728
},
2829
joints: {},
29-
lights: {}
30+
lights: {},
31+
editor: {}
3032
};
3133

3234
/**
@@ -51,6 +53,12 @@ export class URDFControls extends GUI {
5153
this._jointsFolder = this.addFolder('Joints');
5254
this._jointsFolder.domElement.setAttribute('class', 'dg joints-folder');
5355

56+
this._jointsEditorFolder = this.addFolder('Joints Editor');
57+
this._jointsEditorFolder.domElement.setAttribute(
58+
'class',
59+
'dg editor-folder'
60+
);
61+
5462
this._workspaceFolder = this.addFolder('Workspace');
5563
this._workspaceFolder.domElement.setAttribute(
5664
'class',
@@ -82,6 +90,13 @@ export class URDFControls extends GUI {
8290
return this._jointsFolder;
8391
}
8492

93+
/**
94+
* Retrieves the folder with editor settings
95+
*/
96+
get jointsEditorFolder() {
97+
return this._jointsEditorFolder;
98+
}
99+
85100
/**
86101
* Checks if a given object is empty {}
87102
*
@@ -92,6 +107,28 @@ export class URDFControls extends GUI {
92107
return Object.keys(obj).length === 0;
93108
}
94109

110+
/**
111+
* Restricts input on a control to numeric and special characters.
112+
*
113+
* @param control - The dat.gui controller to modify.
114+
*/
115+
private _enforceNumericInput(control: any): void {
116+
const inputElement = control.domElement as HTMLInputElement;
117+
118+
inputElement.addEventListener('input', (event: Event) => {
119+
const target = event.target as HTMLInputElement;
120+
const originalValue = target.value;
121+
122+
// Remove any characters that aren't digits, spaces, periods, or minus signs
123+
const filteredValue = originalValue.replace(/[^\d.\s-]/g, '');
124+
125+
if (originalValue !== filteredValue) {
126+
target.value = filteredValue;
127+
control.updateDisplay();
128+
}
129+
});
130+
}
131+
95132
/**
96133
* Creates an input box and a button to modify the working path
97134
*
@@ -155,6 +192,9 @@ export class URDFControls extends GUI {
155192
stepSize
156193
);
157194

195+
// Enforce input validation
196+
this._enforceNumericInput(this.controls.scene.height);
197+
158198
this._sceneFolder.open();
159199
}
160200
return this.controls.scene;
@@ -200,7 +240,7 @@ export class URDFControls extends GUI {
200240
return;
201241
}
202242

203-
const stepSize = (limitMax - limitMin) / 100.0;
243+
const stepSize = (limitMax - limitMin) / 100;
204244
const initValue = joints[name].jointValue[0];
205245

206246
this.controls.joints[name] = this._jointsFolder.add(
@@ -210,7 +250,24 @@ export class URDFControls extends GUI {
210250
limitMax,
211251
stepSize
212252
);
253+
this._enforceNumericInput(this.controls.joints[name]);
213254
});
255+
256+
// Add reset button
257+
const resetSettings = {
258+
'Reset Joints': () => {
259+
Object.keys(this.controls.joints).forEach((jointName: string) => {
260+
if (jointName !== 'reset' && this.controls.joints[jointName]) {
261+
this.controls.joints[jointName].setValue(0);
262+
}
263+
});
264+
}
265+
};
266+
this.controls.joints.reset = this._jointsFolder.add(
267+
resetSettings,
268+
'Reset Joints'
269+
);
270+
214271
this._jointsFolder.open();
215272
}
216273
return this.controls.joints;
@@ -385,11 +442,118 @@ export class URDFControls extends GUI {
385442
.name('Show Helper')
386443
};
387444

445+
this._enforceNumericInput(
446+
this.controls.lights.directional.position.altitude
447+
);
448+
this._enforceNumericInput(
449+
this.controls.lights.directional.position.azimuth
450+
);
451+
this._enforceNumericInput(this.controls.lights.directional.intensity);
452+
this._enforceNumericInput(this.controls.lights.ambient.intensity);
453+
this._enforceNumericInput(this.controls.lights.hemisphere.intensity);
454+
388455
// Open Scene (lights) and directional subfolder
389456
this._sceneFolder.open();
390457
directionalFolder.open();
391458
}
392-
393459
return this.controls.lights;
394460
}
461+
462+
/**
463+
* Creates controls for the editor mode
464+
*
465+
* @returns - The controls to trigger callbacks when editor settings change
466+
*/
467+
createEditorControls(
468+
addJointCallback: () => void,
469+
linkNames: string[] = [],
470+
jointNames: string[] = []
471+
) {
472+
if (this._isEmpty(this.controls.editor)) {
473+
const editorSettings = {
474+
'Cursor Link Selection': false,
475+
'Select Joint': 'New Joint',
476+
'Parent Link': 'none',
477+
'Child Link': 'none',
478+
'Joint Name': 'new_joint',
479+
'Joint Type': 'revolute',
480+
'Origin XYZ': '0 0 0',
481+
'Origin RPY': '0 0 0',
482+
'Axis XYZ': '0 0 1',
483+
'Lower Limit': '-1.0',
484+
'Upper Limit': '1.0',
485+
Effort: '0.0',
486+
Velocity: '0.0',
487+
'Add Joint': addJointCallback
488+
};
489+
490+
const dropdownOptions = ['none', ...linkNames];
491+
const jointOptions = ['New Joint', ...jointNames];
492+
493+
this.controls.editor.mode = this._jointsEditorFolder.add(
494+
editorSettings,
495+
'Cursor Link Selection'
496+
);
497+
this.controls.editor.selectedJoint = this._jointsEditorFolder
498+
.add(editorSettings, 'Select Joint', jointOptions)
499+
.name('Select Joint');
500+
this.controls.editor.parent = this._jointsEditorFolder
501+
.add(editorSettings, 'Parent Link', dropdownOptions)
502+
.listen();
503+
this.controls.editor.child = this._jointsEditorFolder
504+
.add(editorSettings, 'Child Link', dropdownOptions)
505+
.listen();
506+
this.controls.editor.name = this._jointsEditorFolder.add(
507+
editorSettings,
508+
'Joint Name'
509+
);
510+
this.controls.editor.type = this._jointsEditorFolder.add(
511+
editorSettings,
512+
'Joint Type',
513+
['revolute', 'continuous', 'prismatic', 'fixed', 'floating', 'planar']
514+
);
515+
516+
// Add origin and axis controls
517+
this.controls.editor.origin_xyz = this._jointsEditorFolder
518+
.add(editorSettings, 'Origin XYZ')
519+
.name('Origin XYZ');
520+
this.controls.editor.origin_rpy = this._jointsEditorFolder
521+
.add(editorSettings, 'Origin RPY')
522+
.name('Origin RPY');
523+
this.controls.editor.axis_xyz = this._jointsEditorFolder
524+
.add(editorSettings, 'Axis XYZ')
525+
.name('Axis XYZ');
526+
527+
// Add limit controls
528+
this.controls.editor.lower = this._jointsEditorFolder
529+
.add(editorSettings, 'Lower Limit')
530+
.name('Lower Limit');
531+
this.controls.editor.upper = this._jointsEditorFolder
532+
.add(editorSettings, 'Upper Limit')
533+
.name('Upper Limit');
534+
this.controls.editor.effort = this._jointsEditorFolder
535+
.add(editorSettings, 'Effort')
536+
.name('Effort');
537+
this.controls.editor.velocity = this._jointsEditorFolder
538+
.add(editorSettings, 'Velocity')
539+
.name('Velocity');
540+
541+
this._enforceNumericInput(this.controls.editor.origin_xyz);
542+
this._enforceNumericInput(this.controls.editor.origin_rpy);
543+
this._enforceNumericInput(this.controls.editor.axis_xyz);
544+
this._enforceNumericInput(this.controls.editor.lower);
545+
this._enforceNumericInput(this.controls.editor.upper);
546+
this._enforceNumericInput(this.controls.editor.effort);
547+
this._enforceNumericInput(this.controls.editor.velocity);
548+
549+
this.controls.editor.add = this._jointsEditorFolder.add(
550+
editorSettings,
551+
'Add Joint'
552+
);
553+
554+
this._jointsEditorFolder.open();
555+
}
556+
557+
return this.controls.editor;
558+
}
395559
}

0 commit comments

Comments
 (0)