Coverage for lasso/plotting/plot_shell_mesh.py: 15%

80 statements  

« prev     ^ index     » next       coverage.py v7.2.4, created at 2023-04-28 18:42 +0100

1import os 

2import io 

3import uuid 

4import json 

5from base64 import b64encode 

6from zipfile import ZipFile, ZIP_DEFLATED 

7from typing import Union, Tuple 

8import numpy as np 

9 

10 

11def _read_file(filepath: str): 

12 """This function reads file as str 

13 

14 Parameters 

15 ---------- 

16 filepath : str 

17 filepath of the file to read as string 

18 

19 Returns 

20 ------- 

21 file_content : str 

22 """ 

23 

24 with open(filepath, "r", encoding="utf-8") as fp_filepath: 

25 return fp_filepath.read() 

26 

27 

28def plot_shell_mesh( 

29 node_coordinates: np.ndarray, 

30 shell_node_indexes: np.ndarray, 

31 field: Union[np.ndarray, None] = None, 

32 is_element_field: bool = True, 

33 fringe_limits: Union[Tuple[float, float], None] = None, 

34): 

35 """Plot a mesh 

36 

37 Parameters 

38 ---------- 

39 node_coordinates : np.ndarray 

40 array of node coordinates for elements 

41 shell_node_indexes : np.ndarray 

42 node indexes of shells 

43 field : Union[np.ndarray, None] 

44 Array containing a field value for every element or node 

45 is_element_field : bool 

46 if the specified field is for elements or nodes 

47 fringe_limits : Union[Tuple[float, float], None] 

48 limits for the fringe bar. Set by default to min and max. 

49 

50 Returns 

51 ------- 

52 html : str 

53 html code for plotting as string 

54 """ 

55 

56 # pylint: disable = too-many-locals, too-many-statements 

57 

58 assert node_coordinates.ndim == 2 

59 assert node_coordinates.shape[1] == 3 

60 assert shell_node_indexes.ndim == 2 

61 assert shell_node_indexes.shape[1] in [3, 4] 

62 if isinstance(field, np.ndarray): 

63 assert field.ndim == 1 

64 if is_element_field: 

65 assert field.shape[0] == shell_node_indexes.shape[0] 

66 else: 

67 assert field.shape[0] == node_coordinates.shape[0] 

68 

69 # cast types correctly 

70 # the types MUST be float32 

71 node_coordinates = node_coordinates.astype(np.float32) 

72 if isinstance(field, np.ndarray): 

73 field = field.astype(np.float32) 

74 

75 # distinguish tria and quads 

76 is_quad = shell_node_indexes[:, 2] != shell_node_indexes[:, 3] 

77 is_tria = np.logical_not(is_quad) 

78 

79 # separate tria and quads ... I know its sad :( 

80 tria_node_indexes = shell_node_indexes[is_tria][:, :3] 

81 quad_node_indexes = shell_node_indexes[is_quad] 

82 

83 # we can only plot tria, therefore we need to split quads 

84 # into two trias 

85 quad_node_indexes_tria1 = quad_node_indexes[:, :3] 

86 # quad_node_indexes_tria2 = quad_node_indexes[:, [True, False, True, True]] 

87 quad_node_indexes_tria2 = quad_node_indexes[:, [0, 2, 3]] 

88 

89 # assemble elements for plotting 

90 # This seems to take a lot of memory, and you are right, thinking this, 

91 # the issue is just in order to plot fringe values, we need to output 

92 # the element values at the 3 corner nodes. Since elements share nodes 

93 # we can not use the same nodes, thus we need to create multiple nodes 

94 # at the same position but with different fringe. 

95 nodes_xyz = np.concatenate( 

96 [ 

97 node_coordinates[tria_node_indexes].reshape((-1, 3)), 

98 node_coordinates[quad_node_indexes_tria1].reshape((-1, 3)), 

99 node_coordinates[quad_node_indexes_tria2].reshape((-1, 3)), 

100 ] 

101 ) 

102 

103 # fringe value and hover title 

104 if isinstance(field, np.ndarray): 

105 

106 if is_element_field: 

107 n_shells = len(shell_node_indexes) 

108 n_tria = np.sum(is_tria) 

109 n_quads = n_shells - n_tria 

110 

111 # split field according to elements 

112 field_tria = field[is_tria] 

113 field_quad = field[is_quad] 

114 

115 # allocate fringe array 

116 node_fringe = np.zeros((len(field_tria) + 2 * len(field_quad), 3), dtype=np.float32) 

117 

118 # set fringe values 

119 node_fringe[:n_tria, 0] = field_tria 

120 node_fringe[:n_tria, 1] = field_tria 

121 node_fringe[:n_tria, 2] = field_tria 

122 

123 node_fringe[n_tria : n_tria + n_quads, 0] = field_quad 

124 node_fringe[n_tria : n_tria + n_quads, 1] = field_quad 

125 node_fringe[n_tria : n_tria + n_quads, 2] = field_quad 

126 

127 node_fringe[n_tria + n_quads : n_tria + 2 * n_quads, 0] = field_quad 

128 node_fringe[n_tria + n_quads : n_tria + 2 * n_quads, 1] = field_quad 

129 node_fringe[n_tria + n_quads : n_tria + 2 * n_quads, 2] = field_quad 

130 

131 # flatty paddy 

132 node_fringe = node_fringe.flatten() 

133 else: 

134 # copy & paste ftw 

135 node_fringe = np.concatenate( 

136 [ 

137 field[tria_node_indexes].reshape((-1, 3)), 

138 field[quad_node_indexes_tria1].reshape((-1, 3)), 

139 field[quad_node_indexes_tria2].reshape((-1, 3)), 

140 ] 

141 ) 

142 node_fringe = node_fringe.flatten() 

143 

144 # element text 

145 node_txt = [str(entry) for entry in node_fringe.flatten()] 

146 else: 

147 node_fringe = np.zeros(len(nodes_xyz), dtype=np.float32) 

148 node_txt = [""] * len(nodes_xyz) 

149 

150 # zip compression of data for HTML (reduces size) 

151 zip_data = io.BytesIO() 

152 with ZipFile(zip_data, "w", compression=ZIP_DEFLATED) as zipfile: 

153 zipfile.writestr("/intensities", node_fringe.tostring()) 

154 zipfile.writestr("/positions", nodes_xyz.tostring()) 

155 zipfile.writestr("/text", json.dumps(node_txt)) 

156 zip_data = b64encode(zip_data.getvalue()).decode("utf-8") 

157 

158 # read html template 

159 _html_template = _read_file( 

160 os.path.join(os.path.dirname(__file__), "resources", "template.html") 

161 ) 

162 

163 # format html template file 

164 min_value = 0 

165 max_value = 0 

166 if fringe_limits: 

167 min_value = fringe_limits[0] 

168 max_value = fringe_limits[1] 

169 elif isinstance(field, np.ndarray): 

170 min_value = field.min() 

171 max_value = field.max() 

172 

173 _html_div = _html_template.format( 

174 div_id=uuid.uuid4(), lowIntensity=min_value, highIntensity=max_value, zdata=zip_data 

175 ) 

176 

177 # wrap it up with all needed js libraries 

178 

179 script_string_js = '<script type="text/javascript">{0}</script>' 

180 jszip_js_format = _read_file( 

181 os.path.join(os.path.dirname(__file__), "resources", "jszip.min.js") 

182 ) 

183 jszip_three_format = _read_file( 

184 os.path.join(os.path.dirname(__file__), "resources", "three.min.js") 

185 ) 

186 jszip_chroma_format = _read_file( 

187 os.path.join(os.path.dirname(__file__), "resources", "chroma.min.js") 

188 ) 

189 jszip_jquery_format = _read_file( 

190 os.path.join(os.path.dirname(__file__), "resources", "jquery.min.js") 

191 ) 

192 _html_jszip_js = script_string_js.format(jszip_js_format) 

193 _html_three_js = script_string_js.format(jszip_three_format) 

194 _html_chroma_js = script_string_js.format(jszip_chroma_format) 

195 _html_jquery_js = script_string_js.format(jszip_jquery_format) 

196 

197 # pylint: disable = consider-using-f-string 

198 return """ 

199<!DOCTYPE html> 

200<html lang="en"> 

201 <head> 

202 <meta charset="utf-8" /> 

203 {_jquery_js} 

204 {_jszip_js} 

205 {_three_js} 

206 {_chroma_js} 

207 </head> 

208 <body> 

209 {_html_div} 

210 </body> 

211</html>""".format( 

212 _html_div=_html_div, 

213 _jszip_js=_html_jszip_js, 

214 _three_js=_html_three_js, 

215 _chroma_js=_html_chroma_js, 

216 _jquery_js=_html_jquery_js, 

217 )