CadQuery / cadquery

A python parametric CAD scripting framework based on OCCT
https://cadquery.readthedocs.io
Other
3.15k stars 289 forks source link

In jupyter lab html export, all cadquery object displays pile up with the last one #1565

Closed kalanzun closed 5 months ago

kalanzun commented 5 months ago

Cadquery has some javascript code to display a 3D object within a jupyter lab notebook. This works for me inside jupyter lab. But if I have multiple displays in a notebook and if I export that notebook with the html exporter, then all object displays pile up at the last object display. I expect the object displays to stay in their code cell and not to move down to the last one. It also happens with webpdf, which is based on the html export.

Environment

cadquery==2.4.0 and jupyterlab==4.1.6 from pip install

Firefox

Steps to reproduce

#!/usr/bin/env python
# coding: utf-8

# In[1]:
import cadquery as cq

# In[2]:
result = cq.Workplane().box(1, 1, 1)
result

# In[3]:
result = cq.Workplane().sphere(1)
result

Please run jupyter lab and create a new Python 3 notebook with those three cells. When executed, it displays a box in the output section of the second cell and a sphere in the output section of the third cell, as expected.

all_to_last_jupyter_lab

Now, if you export that notebook to plain html (i.e. in jupyter lab menu: File / Save and Export Notebook As... / HTML), the order of elements in the html file are different. It displays both the box and the sphere in the output section of the third cell.

all_to_last_html

Expected output

Code snippets and cell output shall have the same order in jupyter lab and in html export.

all_to_last_fix

Fix

For jupyter lab, cadquery generates some javascript code in jupyter_tools.py that displays the 3D object within html in a browser. This code receives a handle to the current (html-)element of the cell from jupyter lab by a global variable "element".

More specifically, it creates a promise that will load VTK and "then()" will attach the 3D drawing to the element. It does not hurt that "element" is a global variable, because jupyter lab will execute the cells one by one and the promise will resolve before "element" is changed again.

However, in html export, all javascript code ends up in the html file and the browser will execute all of it at once. Especially will it parse all javascript code and create every promise before resolving the first one. The functions in "then()" will then access the global variable "element" and will all see the (html-)element of the last code cell that has a 3D object display. So, all displays will pile up at the element of the last one.

To fix this, I suggest to change the javascript code, so that it reads the global variable element right away at creation time of the promise, when it still containes the correct value for this cell. I prepare a pull request.

kalanzun commented 5 months ago

Workaround

Wrapping the javascript code in a function call makes the argument local as well

from IPython.display import Javascript

cq_render_js = """
function cq_render(element) {{
{code}
}}

cq_render(element);
"""

def show(shape):
    display(Javascript(cq_render_js.format(code=shape._repr_javascript_())))