Before OPW, my experience with collaborative programming was limited to group projects where all participants were in the same room. So being part of a bigger project is new to me and I’m learning tons. I’ll describe here the process I went through to add a feature to Gzweb and I hope the general idea is not only useful for future Gzweb contributors, but for everyone who is getting into Free Open Source Software 😉
Figuring out how to contribute
The cool thing about big project is that there are so many details and little places where one can contribute. After some days playing with Gzweb and looking into the code, I noticed that if you wanted rotate an object, you had to go into translate mode and then press ‘r’ on your keyboard. Gazebo has both rotate and translate buttons, which is more intuitive, so I thought it would be nice if Gzweb had one too. It’s also good for my project, since a mobile version can’t rely on keyboard commands.The first thing I did was to go to Gzweb’s repository in Bitbucket and create an issue, which is a way to tell the people who are interested in Gzweb that I think a rotate button would be a nice feature to add. Then I started looking into how to add it myself.
How to modify Gzweb
To change something in Gzweb, I must have it in my computer, modify it until I’m satisfied, and then send the modification to be scrutinized by the community.
The installation of Gzweb involves installing Gazebo and other dependencies, all detailed in the Gazebo wiki. The readme files within Gzweb are also helpful. I’m not going to detail how to fork the Gzweb repository and clone it to your machine since there are plenty of resources online for that.
All the files I’ll be modifying are within the gzweb/gz3d folder. With the help of my mentor I got to this work-flow:
- Modify files within gzweb/gz3d
- Run ‘grunt build’ from the gzweb/gz3d/utils directory
- Run ‘cmake ..’ from the gzweb/build directory
Then, to check the changes, i.e. run Gzweb:
- Run ‘gzserver’
- Run ‘./start_gzweb.sh’ from the gzweb directory
- Open browser to http://localhost:8080 (to check on mobile, open at http://{IP of machine running gzserver}:8080)
The general idea
My idea was pretty simple, Gzweb already has a rotation feature, pressing ‘r’. I just wanted to tie that to a new button. Basically, I had to create a new button and then connect it to the existing rotate function. The problem was finding myself inside the code. So I started from where I knew and followed clues in the code from there:
Creating a new button
Since we’re on the web, I started with index.html. I knew my rotate button would be very similar to the translate one that is already there, so I just duplicated the translate button and renamed it rotate.
<input type='radio' id='rotate' name='radio'> <label for='rotate' />Rotate Mode</label>
I knew that the interface had to be connected to gzgui.js since GUI means Graphical User Interface. So I opened it and searched for “translate”. In fact, there was some stuff written about the translate button, so I just duplicated all of it and made it into rotate.
$( '#rotate' ).button({ text: false, icons: { primary: 'toolbar-rotate' } }) .click(function() { guiEvents.emit('manipulation_mode', 'rotate'); });
I had a vague idea that I’d need to edit a CSS file somewhere, but there are a few of them in the project. To find out which one it was, I made a search for which files within the project contained the text ‘toolbar-translate’ from the snippet above (one thing keeps pulling the next, as you’ll see). I did that using grep on the Linux command line. The -r flag tells it to look at all the sub-directories of the path given and -l tells it not to print the line where the text is contained, just give me the file names.
grep -rl "toolbar-translate" ~[path]/gzweb/
And grep has the answer, gz3d.css it is! Again, I copied the translate stuff it had and made it into rotate:
.ui-button .ui-icon.toolbar-rotate { background-image: url(images/rotate.png); width: 16; height: 16; } .ui-button.ui-state-hover .ui-icon.toolbar-rotate { background-image: url(images/rotate.png); width: 16; height: 16; }
It seems like I need a rotate.png image for the button icon. I went to the Gazebo repository and took the one they had there, since the icons seem to be the same. All other icons on Gzweb were 16×16 pixels, so I resized the new icon with Inkscape and saved it in the same folder as the rest (/gzweb/gz3d/client/style/images).
Connecting the button to the rotate function
So I have a button, but pressing it does nothing. The next clue I followed was the manipulation_mode event that is emitted by gzgui.js. Before, it could only emit ‘view’ or ‘translate’, but I added a third option ‘rotate’. A quick grep search told me that manipulation_mode is only found within gzgui.js itself and gz3d.js and gz3d.min.js.
A little parenthesis here about gz3d.js and gz3d.min.js. As I understand it, these files should never be edited directly. As the readme file in gzweb/gz3d says, you gotta use grunt to “concatenate and minimize the files under src and replace gzweb.js and gzweb.min.js in the `gzweb/gz3d/build` directory.” So it is expected that whatever is in the other files under src, such as gzgui.js, will also end up inside these two files.
Conclusion, manipulation_mode is only inside gzgui.js. And this is where it is caught:
guiEvents.on('manipulation_mode', function (mode) { that.scene.setManipulationMode(mode); } );
So this setManipulationMode function is the one handling the mode changes. I need to follow it to see what’s happening with the already existing ‘view’ and ‘translate’ modes and see where I can fit my new ‘rotate’ mode. And grep tells us to look into gzscene.js.
GZ3D.Scene.prototype.setManipulationMode = function(mode) { this.manipulationMode = mode; if (this.manipulationMode === 'view') { this.killCameraControl = false; this.modelManipulator.detach(); this.scene.remove(this.modelManipulator.gizmo); } };
So it seems that when we go into view mode, a bunch of things are killed and removed. One can only guess that all the things being thrown away are related to the translate mode. This is treating translate and rotate the same way. Remember, rotate is already implemented somewhere, but only accessible by keyboard. I want to find how the keyboard triggers it and use the same functions for my button to trigger it.
Looking for modelManipulator within gzscene.js, I found out that it is an instance of THREE.TransformControls. This means that all the member functions used above (like detach) are inside this other file, TransformControls.js. That’s where I stop next. In fact, this file is the one where translation and rotation are being triggered by the keyboard. When using Gzweb, If you’re not in view mode and press ‘r’, the following happens:
if ( event.keyCode == 82 ) { // R if ( scope.mode == 'rotate' ) scope.space = ( scope.space == 'world' ) ? 'local' : 'world'; scope.mode = 'rotate'; } (...) if ( scope.mode !== currentMode || scope.space !== currentSpace || scope.scale !== currentScale ) { scope.setMode( scope.mode ); scope.dispatchEvent( changeEvent ); }
Basically, the mode variable is changed to rotate and then setMode does whatever is necessary to change the mode. I think this is as deep as I must go, all I need to do is setMode to rotate directly when my new button is pressed on the GUI. Let’s take a look back at what’s happening when the button is pressed:
- We made an icon rotate.png
- gz3d.css styles that image to make a button inside index.html
- gzgui.js makes the button emit an event (‘manipulation_mode’, ‘rotate’) when clicked
- This event calls the function setManipulationMode, which is inside gzscene.js
- setManipulationMode detaches the manipulators in case we’re in view mode
We also saw that when the ‘r’ key is pressed, the setMode function is called with either ‘translate’ or ‘rotate’ as argument. So we could call setMode directly from setManipulationMode. This didn’t work though, because the manipulator is created by default in translate mode… But this is done inside TransformControls.js and I don’t want to edit that file since it was taken from somewhere else (I did edit it during my application for OPW though, maybe I’ll tell you that story someday 😉 ). So I should setMode the moment the tool is created in gzscene.js. When’s that? If there’s a detach, there must be an attach, right? … bingo! And then I added the two lines after attach, which was already there:
this.modelManipulator.attach(model); this.modelManipulator.mode = this.manipulationMode; this.modelManipulator.setMode( this.modelManipulator.mode );
Then I changed setManipulationMode to also kill and detach the manipulator whenever the new mode is different from the current mode:
GZ3D.Scene.prototype.setManipulationMode = function(mode) { if (mode !== this.manipulationMode) { this.killCameraControl = false; this.modelManipulator.detach(); this.scene.remove(this.modelManipulator.gizmo); } this.manipulationMode = mode; if (this.manipulationMode === 'view') { this.killCameraControl = false; this.modelManipulator.detach(); this.scene.remove(this.modelManipulator.gizmo); } };
Sending a pull request
It worked! But it’s not over… Now I gotta send the modifications to the Gzweb repository where other contributors will check if it’s ok and approve it or not. This means I gotta make a pull request and deal with the Mercurial version control system, which I mentioned in the beginning. All I’m going to add is hg add (pun intended). Since I’ve added a file to the repository (rotate.png) which isn’t being tracked yet, I must tell the repository to add it to the tracked list. Just run this inside the gzweb directory:
hg add gz3d/client/style/images/rotate.png
Afterwards, I just sent the pull request and waited.
Collaboration
Then a very important step in collaboration happened: I got a comment on the pull request saying it would be better if the tools didn’t disappear when changing from translate to rotate and vice-versa. So I changed setManipulationMode as follows:
GZ3D.Scene.prototype.setManipulationMode = function(mode) { if (mode === 'view') { this.killCameraControl = false; this.modelManipulator.detach(); this.scene.remove(this.modelManipulator.gizmo); } this.manipulationMode = mode; this.modelManipulator.mode = this.manipulationMode; this.modelManipulator.setMode( this.modelManipulator.mode ); };
Now, only in case of view mode the tools get detached. Otherwise, the mode is updated. That’s all. I pushed the new changes to my repository and edited the pull request. Eventually it got approved and my new button is now part of Gzweb!
Summing up
I hope the explanation was clear and useful for someone. I’m open for questions and your opinions on my code! Or better yet, go to the repository and send your own pull request fixing whatever you find weird 😉
I hope the general flow was clear though: I started from a place I knew and followed the clues on the code from there. It wasn’t necessary for me to understand every single bit of the code. But it was important to know how to navigate within it and search for what I needed. For example, a grep search for “translate” doesn’t help much since it’s written all over the code with various meanings.
Not sure if this is a nice method, but for this newbie here it worked 🙂 I don’t think this is the only way to approach unknown code though, this was a particular situation where I just had to make a few changes to get what I wanted. Nevertheless, in the process I also got more familiar with the structure of Gzweb, which is very important, since I want to insert Gzweb mobile into it in a coherent way.