BruceSherwood / vpython-jupyter

This repository has been moved to https://github.com/vpython/vpython-jupyter
64 stars 33 forks source link

Feature: Support Menu option addition and deletion #40

Open wilsoc5 opened 6 years ago

wilsoc5 commented 6 years ago

I'm writing a placement tool using Vpython as a great visualization tool for the placement of the objects. However, I need to support placing 1 to 15 objects. I have a set of sliders to allow adjustment of position (i.e. X,Y,Z in the global reference frame) and a drop down menu to allow selecting which object to edit.

What I would like to do is have a button to add an object, then that object name would be added to a drop down menu, set as active, and continue. Additionally, once selected an object, you could click another button to delete the object and remove its entry from the drop down list.

I noticed that I can do:

import vpython as vp
choicesList = ['name0',]
objmenu = vp.men(choices = choicesList, bind = somefunc)
objmenu._choices.append('name1')

which works (yes, I'm ignoring the API here) presumably because the object has not fully been constructed yet.

However, updating _choices doesn't work from the callback function. So I'd recommend a feature update to allow changing the choices list in a reasonable and sane manner.

BruceSherwood commented 6 years ago

Good suggestion. Can you offer any specific ideas for reasonable and sane methods?

wilsoc5 commented 6 years ago

One approach would be a push_choice(string) or pop_choice() approach treating the menu as a stack where push simply appends the desired string into the option list and pop will remove the last one. Then the user can set menu.index to be the desired option.

Alternatively, an insert_choice(N,string) will insert the choice at position N. remove_choice(N) or remove_choice(string,removeLast=False) would then remove the choice at position N or the first(or last) entry of the string (in the case of duplicate entries).

In further thinking about the remove functions (e.g pop() and remove_choice(...)), if the removed objects are currently selected, the API could either throw an exception, or change the selection back to either the 0 index or the index immediately before it (or after if we're removing self._choices[0] item).

I've tried digging into the code for the actual commands sent to the front-end, but that just ended up creating a new menu.

Personally, I like the insert_choice(N,string) and remove_choice(N) the best as insert_choice(-1,string) is the same as push_choice(string) and remove_choice(-1) is the same as pop_choice() but both insert_choice and remove_choice are more flexible.

BruceSherwood commented 6 years ago

Stimulated by your suggestions, it occurs to me that we could mimic the methods of the curve object, which address the same issues: append, shift, splice, modify, clear, pop, slice.

wilsoc5 commented 6 years ago

I think that would work, except in looking at the curveMethods code, I don't see a way to remove a particular element in the middle of the point list, something that a normal python lists have (see: https://docs.python.org/3.6/tutorial/datastructures.html#more-on-lists Excerpt from curveMethod's pop:

def pop(self):
        if len(self._pts) == 0: return None
        val = self._pts[-1]
        self._pts = self._pts[0:-1]
        self.appendcmd({"val":"None","method":"pop","idx":self.idx})
        return val

could be rehashed to allow python-list pop syntax:

def pop(self,i=-1):
        if len(self._pts) == 0: return None
        val = self._pts.pop(i)
        self.appendcmd({"val":"None","method":"pop","i":i,"idx":self.idx})
        return val

since self._pts is a normal python list anyway. I have no idea what the argument has to be for the appendcmd call (which I am not an expert in).

BruceSherwood commented 6 years ago

Ah. Thanks for noticing this. The curve object does permit pop(n), a recent improvement, but the vpython pop function doesn't send n to the GlowScript library.

BruceSherwood commented 6 years ago

If you've been gleaning your information about VPython by reading the vpython.py file, I should make sure to point out that the real documentation is at

http://www.glowscript.org/docs/VPythonDocs/index.html

The problem of pop() is that the vpython module not only needs to permit pop(n) but also needs to send n to the browser to tell the GlowScript library to remove the nth point from being rendered.

wilsoc5 commented 6 years ago

If you'll allow me to get off topic briefly: Yes, the documentation got me started. However, as someone just starting VPython coming from matplotlib Axes3d (which has a massive bug in terms of 3d depth rasterization that caused me to learn VPython), much of the documentation assumes (which is your right as author and a project goal) that a person is running the VPython online-only in the browser (I believe you call this GlowscriptVPython). This makes sense as a project geared to novices and education where installation of software on a computer may be locked down.

My draw to VPython was an easy access to OpenGL from python. I would personally probably be using VPython6 with WxWidgets if it were up to date, as I could then use the Wx widgets to implement my desired GUI. However, since that has been discontinued (for a few years now, it seems), I looked at VPython7. Unfortunately, The line between what is "VPython7 documentation" must be teased out of the "GlowScript VPython" documentation. Which is really a misnomer because the implementation of "VPython7" installed-python requires interaction with the GlowScript Library for the browser visualizations. Thus, it seems like the distinction really is more of BrowserLimitedGlowScriptVPython (due to javascript limitations and sandboxing) or GlowScriptVPython (what you traditionally call VPython7). This is highlighted by your last statement:

The problem of pop() is that the vpython module not only needs to permit pop(n) but also needs to send n to the browser to tell the GlowScript library to remove the nth point from being rendered.

which means that VPython7 definitely requires GlowScript, which then gives confusion as to what are the differences between VPython7 and GlowscriptVPython as they both require VPython.

It appears that VPython7 took a feature regression compared to VPython6 (with Wx) due to the lack of 2D GUI controls around the 3D viewport. WxPython allowed all the features of WxWidgets to be used for GUI Controls. VPython7 no longer has that and so 2d GUI elements must now be implemented in Glowscript & VPython7 itself.

That was not as brief as I thought it would be. And please don't get me wrong, I'm liking VPython a lot, though for "VPython7" the numpy integration could be better. (I'm writing a lot of vp.vector(numpyarray[0],numpyarray[1],numpyarray[2]) ) But now I'm getting off topic again.

================================== Back on topic: I'm not saying that the curve should necessarily be able to pop(i), I'm just saying that the menu should. If that requires additional front-end support, then maybe curve gets pop(i) support for the same cost of implementing menu.pop(i)?

BruceSherwood commented 6 years ago

I'm not sure I fully understand the GlowScript vs. VPython 7 distinction you are trying to make. The goal, which is very nearly achieved, is that a GlowScript VPython program will run without change using the vpython module, and a VPython 7 program will run without change at glowscript.org unless, of course, the program uses numpy or other Python module. The case of pop(n) is just a bug to be fixed in vpython.py (it already works in GlowScript VPython curves). So to a very great extent the documentation really does apply to both VPython environments, and there are a few places in the documentation where the differences are mentioned (e.g. the article on textures).

A strong stimulus to creating this unification is that newcomers to programming and to VPython can and typically should start at glowscript.org, but they can eventually graduate to VPython 7 to get access to Python modules, and they'll still be able to use their VPython programs.

Minor point: Why write "a lot of" those vector statements? Why not use a function nvec(x,y,z) that returns a vec? I'll also mention that often one needs to use numpy to avoid the high overhead of Python loops. Due to transpiling to very fast JavaScript, a GlowScript VPython program runs many times faster than the same program running in VPython 7. Here is data I posted last October to the VPython forum:

ClassicGasNoNumpy.py: 2.3 milliseconds per loop ClassicGasNumpy.py: 0.6 ms Gas.py (VPython 7): 5.5 ms (Classic VPython was mostly C++; VPython 7 is mostly Python) GasNumpy.py: (VPython 7): 1.3 ms Gas.py (GlowScript): 0.12 ms

Summary for this particular (but representative) example program:

1) Use of the numpy module with true Python increases the speed by a factor of 4.

2) Classic is 2 times faster than VPython 7. This is presumably due to the fact that the Classic visual module is mostly written in (fast) C++, whereas most of VPython 7 (except for the vector class) is written in (slow) Python. The vector class was speeded up by using Cython, and the other major component of the vpython module is a candidate for similar treatment, which would likely bring it close to the speed of Classic VPython. I might have thought that the fact that Classic VPython has to do all of the rendering in the CPU, using OpenGL, might have been compensated for in VPython 7 by offloading the rendering to WebGL, but that's not the case.

3) GlowScript VPython is 5 times faster than ClassicGasNumpy and 20 times faster than ClassicGasNoNumpy.

4) GlowScript VPython is 10 times faster than VPython 7 without numpy and 40 times faster than VPython 7 without numpy.