diff --git a/examples/ipynb/show_boxes.ipynb b/examples/ipynb/show_boxes.ipynb new file mode 100644 index 000000000..a63c6aeb8 --- /dev/null +++ b/examples/ipynb/show_boxes.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gustaf as gus\n", + "import vedo\n", + "vedo.settings.default_backend = \"k3d\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mesh_faces_box = gus.create.faces.box(\n", + " bounds=[[0, 0], [2, 2]], resolutions=[2, 3]\n", + ")\n", + "a = mesh_faces_box.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Volumes creates an error since vedo does something funny with UGrid when k3d is the backend. It might be an error introduced after they switched ugrid in one of the prior versions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mesh_volumes_box = gus.create.volumes.box(\n", + " bounds=[[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]], resolutions=[2, 3, 4]\n", + " )\n", + "mesh_volumes_box.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mesh_faces_triangle = gus.create.faces.box(\n", + " bounds=[[0, 0], [2, 2]],\n", + " resolutions=[3, 3],\n", + " simplex=True,\n", + " backslash=False,\n", + ")\n", + "mesh_faces_triangle.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mesh_faces_triangle_bs = gus.create.faces.box(\n", + " bounds=[[0, 0], [2, 2]],\n", + " resolutions=[3, 3],\n", + " simplex=True,\n", + " backslash=True,\n", + ")\n", + "mesh_faces_triangle_bs.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "gus.show(\n", + " [\"faces-box\", mesh_faces_box],\n", + " # [\"volumes-box\", mesh_volumes_box],\n", + " [\"faces-triangle\", mesh_faces_triangle],\n", + " [\"faces-triangle-backslash\", mesh_faces_triangle_bs],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import splinepy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def CubeLinear(origin, length):\n", + " origin_length = origin + length\n", + " return splinepy.Bezier(degrees=[1] * 3, control_points=[[origin, origin, origin], [origin_length, origin, origin], [origin, origin_length, origin], [origin_length, origin_length, \\\n", + " origin], [origin, origin, origin_length], [origin_length, origin, origin_length], [origin, origin_length, origin_length], [origin_length, origin_length, \\\n", + " origin_length]])\n", + "\n", + "length_center, length_corner = 0.2, 0.1\n", + "cube_center, cube_corner = CubeLinear(origin=0.5, length=length_center), CubeLinear(origin=1.0, length=-length_corner)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following works but looks very stupidly since the knot vector points and control points are shown much bigger than they should." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "gus.show([\"cube-center\", cube_center], [\"cube-corner\", cube_corner])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gustaf-main", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/gustaf/show.py b/gustaf/show.py index 3c23c1eb0..c3bb55fe6 100644 --- a/gustaf/show.py +++ b/gustaf/show.py @@ -7,6 +7,7 @@ import numpy as np from gustaf import utils +from gustaf.utils.notebook_helper import K3DPlotterN # @linux it raises error if vedo is imported inside the function. try: @@ -34,6 +35,19 @@ def __call__(self, *args, **kwargs): sys.modules[__name__].__class__ = _CallableShowDotPy +def is_ipython() -> bool: + """Returns True if the current environment is IPython. + + Check if the code is run in a notebook. + """ + try: + from IPython import get_ipython + + return get_ipython() is not None + except ImportError: + return False + + def show( *args, **kwargs, @@ -82,14 +96,17 @@ def cam_tuple_to_list(dict_cam): # get plotter if plt is None: - plt = vedo.Plotter( - N=N, - sharecam=False, - offscreen=offs, - size=size, - title=title, - bg=background, - ) + if is_ipython(): + plt = K3DPlotterN(N, size, background) + else: + plt = vedo.Plotter( + N=N, + sharecam=False, + offscreen=offs, + size=size, + title=title, + bg=background, + ) else: # check if plt has enough Ns @@ -171,6 +188,9 @@ def cam_tuple_to_list(dict_cam): # offscreen=offs, ) + if is_ipython(): + plt.display() + return if interact and not offs: # only way to ensure memory is released clear_vedo_plotter(plt, np.prod(plt.shape)) @@ -179,13 +199,33 @@ def cam_tuple_to_list(dict_cam): # It seems to leak some memory, but here it goes. plt.close() # if i close it, this cannot be reused... plt = None - if return_show_list: return (plt, list_of_showables) else: return plt +def show_notebook( + *args, + **kwargs, +): + elements_to_show = [] + for element in args: + if hasattr(element, "showable"): + elements_to_show.append([element.showable(**kwargs)]) + elif isinstance(element, list): + sub_list = [] + for sub_element in element: + if hasattr(sub_element, "showable"): + sub_list.append(sub_element.showable(**kwargs)) + else: + raise TypeError( + "Only gustaf objects can be shown in notebook" + ) + else: + raise TypeError("For vedo_show, only list or dict is valid input") + + def make_showable(obj, as_dict=False, **kwargs): """Generates a vedo obj based on `kind` attribute from given obj, as well as show_options. diff --git a/gustaf/utils/notebook_helper.py b/gustaf/utils/notebook_helper.py new file mode 100644 index 000000000..256de4be1 --- /dev/null +++ b/gustaf/utils/notebook_helper.py @@ -0,0 +1,72 @@ +import numpy as np +import vedo +from IPython.display import display +from ipywidgets import GridspecLayout + + +def get_shape(N, x, y): + """Taken verbatim from vedo plotter class. + + Args: + N (_type_): _description_ + x (_type_): _description_ + y (_type_): _description_ + + Returns: + _type_: _description_ + """ + nx = int(np.sqrt(int(N * y / x) + 1)) + ny = int(np.sqrt(int(N * x / y) + 1)) + lm = [ + (nx, ny), + (nx, ny + 1), + (nx - 1, ny), + (nx + 1, ny), + (nx, ny - 1), + (nx - 1, ny + 1), + (nx + 1, ny - 1), + (nx + 1, ny + 1), + (nx - 1, ny - 1), + ] + ind, minl = 0, 1000 + for i, m in enumerate(lm): + l_something = m[0] * m[1] + if N <= l_something < minl: + ind = i + minl = l_something + return lm[ind] + + +class K3DPlotterN(GridspecLayout): + def __init__(self, N, size, background=0xFFFFFF): + self.N = N + self.x, self.y = get_shape(N, *(2160, 1440)) + self.shape = (self.x, self.y) + super().__init__(self.x, self.y) + self.renderers = [] + for _ in range(N): + self.renderers.append( + vedo.Plotter( + size=size, + bg=background, + ) + ) + + def _at_get_location(self, N): + if self.x * self.y < N: + return (self.x - 1, self.y - 1) + return (N // (self.y + 1), N % self.y) + + def show(self, list_of_showables, at, interactive, camera): + self[self._at_get_location(at)] = self.renderers[at].show( + list_of_showables, + interactive=interactive, + camera=camera, + # offscreen=offscreen, + ) + + def display(self): + display(self) + + def clear(*args, **kwargs): + pass