[x3d-public] A few suggestions from the Python API (x3d.py)

Hans Moritz Guenther hgunther at mit.edu
Wed Feb 9 08:23:25 PST 2022


Hi,

I'm starting to use the x3d.py library to generate x3d output. I'm very 
much a Python programmer using what people call the "scientific stack" 
in Python (the libraries numpy, scipy, pandas, etc.) with very little 
experience in 3D visualization or web-programming.

I understand that you autogenerate the programming language API from the 
X3DOUM, but I do not know which, if any, of the comments generalize to 
the APIs in other programming languages or which ones are specific to 
the Python implementation in x3d.py.

For a little bit of background, I'm an astronomer and one of the things 
I do is ray-tracing of the designs for new space-based observatories. It 
is then very useful to have some form of output that allows me to put 
images of how the instrument looks and what path the photons take into 
presentations or on the web. As it turns out, X3D is a great format for 
that, see e.g. https://space.mit.edu/home/guenther/ARCUS/3Dview.html 
<https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fspace.mit.edu%2Fhome%2Fguenther%2FARCUS%2F3Dview.html&data=04%7C01%7Cbrutzman%40nps.edu%7Cdb2b534b2a804d7bfdd208d9ea4aeafa%7C6d936231a51740ea9199f7578963378e%7C0%7C0%7C637798431440614306%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C2000&sdata=%2FT7oRCpoMKrtbHjscVH9lQp%2FBP070YtGI6t4TF%2BPoL0%3D&reserved=0> 
In that past, I've generated the X3D output through a python package 
called mayavi (https://docs.enthought.com/mayavi/mayavi/ 
<https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdocs.enthought.com%2Fmayavi%2Fmayavi%2F&data=04%7C01%7Cbrutzman%40nps.edu%7Cdb2b534b2a804d7bfdd208d9ea4aeafa%7C6d936231a51740ea9199f7578963378e%7C0%7C0%7C637798431440614306%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C2000&sdata=CdLWWXcET8e3si%2BUjGRjAkGfv2TvSQ9KKIlM9bc66oA%3D&reserved=0>) 
but that is a rather heavy dependency for this very limited purpose. So, 
I'm trying to convert to use your x3d.py library instead.

First, let me thank you for your effort to make x3d.py at all. In 
general it works great!

I'd like to mention a few glitches I've run into so far:

- IndexTriangleSet: Coordinates are given as a list of tuples [(x1, y1, 
z1), (x2, y2, y3), ...]. I think that makes sense. It orders stuff in a 
natural way and is easy to understand. However, the index needs to be 
given as a flat list [ind11, ind12, ind13, ind21, ind22, ind23, ...]. 
Since there are three indices per triangle, I think the same list of 
tuples as for the coordinates would make more sense [( ind11, ind12, 
ind13), (ind21, ind22, ind23) ...]. I've not checked if the same thought 
applied to e.g. the IndexedFaceSet etc. I'm still pretty new to this and 
can't claim to know all relevant nodes.

- the __init__.py file. The file is present, but not correct. It lists 
all the classes in __all__ but does not actually import them. Thus, that 
can't actually be used.

In [1]: import x3d

In [2]: x3d.IndexedTriangleSet
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-93a98d5f0414> in <module>
----> 1 x3d.IndexedTriangleSet

Instead, I need to use "x3d.x3d". That's of course possible but a little 
cumbersome.

In [4]: import x3d.x3d
x3d.py package loaded, have fun with X3D Graphics!

In [5]: x3d.x3d.IndexedTriangleSet
Out[5]: x3d.x3d.IndexedTriangleSet

I think all that's missing is the following line in the __init__.py

from x3d import *

which will import everything that's defined in x3d.py into the 
__init__.py namespace and than the user an get it from "import x3d" 
which gives you the names defined in x3d/__init__.py

- Python usually makes heavy use of duck-typing (you can use any object 
as long as it behaves as you expect) instead of explicit "isinstance" 
checks. A lot of numerical computations in Python are done using the 
numpy (https://numpy.org 
<https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fnumpy.org%2F&data=04%7C01%7Cbrutzman%40nps.edu%7Cdb2b534b2a804d7bfdd208d9ea4aeafa%7C6d936231a51740ea9199f7578963378e%7C0%7C0%7C637798431440614306%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C2000&sdata=DgLaPPURD5FLJTT9FnjaglzyPNA9oeH93qpXmJIcHvM%3D&reserved=0>) 
library, which implements array-based math and is optimized in C. Numpy 
has datatypes that are essentially similar to Python int but fail an 
isinstance check, and I can pass those in some, but not all places in 
x3d.py. Here is an example that fails:

  

~/mambaforge/envs/kitchensink/lib/python3.10/site-packages/x3d/x3d.py  in__init__(self, ccw, colorPerVertex, index, normalPerVertex, solid, 
color, coord, fogCoord, normal, texCoord, attrib, DEF, USE, IS, 
metadata, class_, id_, style_)

   48191          self.ccw=  ccw

   48192          self.colorPerVertex=  colorPerVertex

> 48193self.index=  index

   48194          self.normalPerVertex=  normalPerVertex

   48195          self.solid=  solid

  

~/mambaforge/envs/kitchensink/lib/python3.10/site-packages/x3d/x3d.py  inindex(self, index)

   48230          if   indexis  None:

   48231              index=  MFInt32.DEFAULT_VALUE()

> 48232assertValidMFInt32(index)

   48233          assertNonNegative('index',  index)

   48234          self.__index=  index

  

~/mambaforge/envs/kitchensink/lib/python3.10/site-packages/x3d/x3d.py  inassertValidMFInt32(value)

    2622          if  not  isinstance(each,  int):

    2623              # print(flush=True)

-> 2624raise  X3DTypeError('MFInt32 list has contained value='  +  str(each)  +  ' with type='  +  str(type(each))  +  ' which is not a valid int')

    2625      if  not  isValidMFInt32(value):

    2626          # print(flush=True)

  

X3DTypeError: MFInt32 list has contained value=1 with type=<class 'numpy.int64'> which is not a valid int

  

  

There is an easy workaround:

  

x3d.IndexedTriangleSet(index = [int(x) for x in myindex]

  

but it would be nice if IndexedTriangleSet accepted my numbers directly.

There are several ways of doing that, but the easiest is probably to change line 2622 to:

  

if not int(x) == x:

  

That will work for any object that can be converted to an int, including 
numpy, python decimal, fraction, ..

- Numpy: One might consider taking numpy arrays directly, i.e. instead of

x3d.Coordinate(point=[(x1, y1, z1), (x2, y2, y3), ...])

one could do

x3d.Coordinate(point=arr)

where arr is a (3, n) numpy array. Now, if done naively that would 
require numpy as a dependency to x3d.py and it's probably good to avoid 
that. However, there are ways to accept numpy arrays without requiring 
numpy. That's a little more involved, but can be done (for example using 
decorators or a separate module (x3d.numpy_interface) or separate 
package (x3d-numpy)). Not sure if it's worth the effort at this point - 
that depends on what your future plans for this package are and how fast 
this is developing.

- Changelog. From the pypi entry and the docs on 
https://www.web3d.org/x3d/stylesheets/python/python.html it was not 
quite clear to me how stable the package is or where I would see changes 
listed, for example, if you do the change that I suggested above 
(accepting index values as tuples instead of a flat list) that would 
break the interface. Would you do that? Where would I find a list of 
changes from one version to another?

- Jupyter notebook: The Jupyter notebook seems like an ideal tool for 
work with X3D in Python, since it is rendered on the web and can display 
any web output. Here is a simple addition of a `_repr_html_`method to 
the Scene class in the x3d.py that will render any valid scene with no 
additional effort to the screen. Sure, the header is a little 
simplistic, but it's just a quick way to look at what your are 
specifying. Since I did not want to edit x3d.py itself, I simply made a 
new class that inherits from the x3d.Scene, but it would obviously be 
even easier if this was part of x3d itself. See 
http://nbviewer.org/github/hamogu/x3d-experiements/blob/main/Scence_for_notebook.ipynb 
for an example and note how the X3D output at the bottom is not just a 
screenshot, but a live output that your can zoom and rotate with your 
mouse, even though the notebook is not running live, but instead you 
just see the rendered output of what I run some time in the past on my 
laptop. (I admit that this is a naive implementation and it might be 
useful to add a few <meta> or <WorldInfo> nodes. Also, maybe Scene is 
not the best node, or not the only node, where to define this 
functionality, but it seems to work well.)

classScene(x3d.Scene):
js_source= 'https://www.x3dom.org/download/x3dom.js'
css_source= 'https://www.x3dom.org/download/x3dom.css'
dimension_px= (600, 400)
def_repr_html_(self):
return(f"""
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<script type='text/javascript' src='{self.js_source}'> </script>
<link rel='stylesheet' type='text/css' href='{self.css_source}'></link>
</head>
<body>
<x3d width='{self.dimension_px[0]}px' height='{self.dimension_px[1]}px'>
{self.XML()}
</x3d>
</body>
</html>
""")


Please let me know if there is anything I can do to help with this 
awesome package, that really makes generating X3D from Python so much 
simpler already.

Yours,

Moritz

-- 
Hans Moritz Günther
Massachusetts Institute of Technology
Kavli Institute for Astrophysics and Space Research
77 Massachusetts Avenue
NE83-569
Cambridge, MA 02139
hgunther at mit.edu
https://space.mit.edu/home/guenther/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://web3d.org/pipermail/x3d-public_web3d.org/attachments/20220209/0748d38d/attachment-0001.html>


More information about the x3d-public mailing list