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

1""" 

2notebook.py 

3------------- 

4 

5Render trimesh.Scene objects in HTML 

6and jupyter and marimo notebooks using three.js 

7""" 

8 

9import base64 

10import os 

11from typing import Literal 

12 

13# for our template 

14from .. import resources, util 

15 

16 

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 

21 

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. 

29 

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) 

52 

53 if escape_quotes: 

54 return html.replace('"', """) 

55 

56 return html 

57 

58 

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. 

64 

65 Parameters 

66 ------------- 

67 scene : trimesh.Scene 

68 Source geometry 

69 

70 Returns 

71 ------------- 

72 html : IPython.display.HTML 

73 Object containing rendered scene 

74 """ 

75 # keep as soft dependency 

76 from IPython import display 

77 

78 # convert scene to a full HTML page 

79 as_html = scene_to_html(scene=scene, escape_quotes=True) 

80 

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 

97 

98 

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. 

104 

105 Parameters 

106 ------------- 

107 scene : trimesh.Scene 

108 Source geometry 

109 

110 Returns 

111 ------------- 

112 html : mo.Html 

113 Object containing rendered scene 

114 """ 

115 # keep as soft dependency 

116 import marimo as mo 

117 

118 # convert scene to a full HTML page 

119 srcdoc = scene_to_html(scene=scene, escape_quotes=True) 

120 

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 ) 

133 

134 return embedded 

135 

136 

137def in_notebook() -> Literal["jupyter", "marimo", False]: 

138 """ 

139 Check to see if we are in a Jypyter or Marimo notebook. 

140 

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 

154 

155 # spyder uses ZMQshell, and can appear to be a notebook 

156 spyder = "_" in os.environ and "spyder" in os.environ["_"] 

157 

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 

164 

165 try: 

166 import marimo as mo 

167 

168 if mo.running_in_notebook(): 

169 return "marimo" 

170 

171 except BaseException: 

172 pass 

173 

174 return False