Coverage for trimesh/viewer/notebook.py: 71%
41 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
1"""
2notebook.py
3-------------
5Render trimesh.Scene objects in HTML
6and jupyter and marimo notebooks using three.js
7"""
9import base64
10import os
11from typing import Literal
13# for our template
14from .. import resources, util
17def scene_to_html(scene, escape_quotes: bool = False) -> str:
18 """
19 Return HTML that will render the scene using
20 GLTF/GLB encoded to base64 loaded by three.js
22 Parameters
23 --------------
24 scene : trimesh.Scene
25 Source geometry
26 escape_quotes
27 If true, replaces quotes '"' with '"' so that the
28 HTML is valid inside a `srcdoc` property.
30 Returns
31 --------------
32 html : str
33 HTML containing embedded geometry
34 """
35 # fetch HTML template from ZIP archive
36 # it is bundling all of three.js so compression is nice
37 base = (
38 util.decompress(resources.get_bytes("templates/viewer.zip"), file_type="zip")[
39 "viewer.html.template"
40 ]
41 .read()
42 .decode("utf-8")
43 )
44 # make sure scene has camera populated before export
45 _ = scene.camera
46 # get export as bytes
47 data = scene.export(file_type="glb")
48 # encode as base64 string
49 encoded = base64.b64encode(data).decode("utf-8")
50 # replace keyword with our scene data
51 html = base.replace("$B64GLTF", encoded)
53 if escape_quotes:
54 return html.replace('"', """)
56 return html
59def scene_to_notebook(scene, height=500, **kwargs):
60 """
61 Convert a scene to HTML containing embedded geometry
62 and a three.js viewer that will display nicely in
63 an IPython/Jupyter notebook.
65 Parameters
66 -------------
67 scene : trimesh.Scene
68 Source geometry
70 Returns
71 -------------
72 html : IPython.display.HTML
73 Object containing rendered scene
74 """
75 # keep as soft dependency
76 from IPython import display
78 # convert scene to a full HTML page
79 as_html = scene_to_html(scene=scene, escape_quotes=True)
81 # escape the quotes in the HTML
82 srcdoc = as_html
83 # embed this puppy as the srcdoc attr of an IFframe
84 # I tried this a dozen ways and this is the only one that works
85 # display.IFrame/display.Javascript really, really don't work
86 # div is to avoid IPython's pointless hardcoded warning
87 embedded = display.HTML(
88 " ".join(
89 [
90 '<div><iframe srcdoc="{srcdoc}"',
91 'width="100%" height="{height}px"',
92 'style="border:none;"></iframe></div>',
93 ]
94 ).format(srcdoc=srcdoc, height=height)
95 )
96 return embedded
99def scene_to_mo_notebook(scene, height=500, **kwargs):
100 """
101 Convert a scene to HTML containing embedded geometry
102 and a three.js viewer that will display nicely in
103 an Marimo notebook.
105 Parameters
106 -------------
107 scene : trimesh.Scene
108 Source geometry
110 Returns
111 -------------
112 html : mo.Html
113 Object containing rendered scene
114 """
115 # keep as soft dependency
116 import marimo as mo
118 # convert scene to a full HTML page
119 srcdoc = scene_to_html(scene=scene, escape_quotes=True)
121 # Embed as srcdoc attr of IFrame, using mo.iframe
122 # turns out displaying an empty image. Likely
123 # similar to display.IFrame
124 embedded = mo.Html(
125 " ".join(
126 [
127 '<div><iframe srcdoc="{srcdoc}"',
128 'width="100%" height="{height}px"',
129 'style="border:none;"></iframe></div>',
130 ]
131 ).format(srcdoc=srcdoc, height=height)
132 )
134 return embedded
137def in_notebook() -> Literal["jupyter", "marimo", False]:
138 """
139 Check to see if we are in a Jypyter or Marimo notebook.
141 Returns
142 -----------
143 in_notebook
144 Returns the type of notebook we're in or False if it
145 is running as terminal application.
146 """
147 try:
148 # function returns IPython context, but only in IPython
149 ipy = get_ipython() # NOQA
150 # we only want to render rich output in notebooks
151 # in terminals we definitely do not want to output HTML
152 name = str(ipy.__class__).lower()
153 terminal = "terminal" in name
155 # spyder uses ZMQshell, and can appear to be a notebook
156 spyder = "_" in os.environ and "spyder" in os.environ["_"]
158 # assume we are in a notebook if we are not in
159 # a terminal and we haven't been run by spyder
160 if (not terminal) and (not spyder):
161 return "jupyter"
162 except BaseException:
163 pass
165 try:
166 import marimo as mo
168 if mo.running_in_notebook():
169 return "marimo"
171 except BaseException:
172 pass
174 return False