Coverage for tatlin/lib/gl/gcodemodel.py: 62%

157 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-20 05:56 +0000

1# -*- coding: utf-8 -*- 

2# Copyright (C) 2011 Denis Kobozev 

3# 

4# This program is free software; you can redistribute it and/or modify 

5# it under the terms of the GNU General Public License as published by 

6# the Free Software Foundation; either version 2 of the License, or 

7# (at your option) any later version. 

8# 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU General Public License for more details. 

13# 

14# You should have received a copy of the GNU General Public License 

15# along with this program; if not, write to the Free Software Foundation, 

16# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

17 

18 

19import math 

20import numpy 

21import logging 

22import time 

23 

24from OpenGL.GL import * # type:ignore 

25from OpenGL.GLE import * # type:ignore 

26from OpenGL.arrays.vbo import VBO 

27 

28from .model import Model 

29 

30from tatlin.lib import vector 

31from tatlin.lib.model.gcode.parser import Movement 

32 

33 

34class GcodeModel(Model): 

35 """ 

36 Model for displaying Gcode data. 

37 """ 

38 

39 # vertices for arrow to display the direction of movement 

40 arrow = numpy.require( 

41 [ 

42 [0.0, 0.0, 0.0], 

43 [0.4, -0.1, 0.0], 

44 [0.4, 0.1, 0.0], 

45 ], 

46 "f", 

47 ) 

48 layer_entry_marker = numpy.require( 

49 [ 

50 [0.23, -0.14, 0.0], 

51 [0.0, 0.26, 0.0], 

52 [-0.23, -0.14, 0.0], 

53 ], 

54 "f", 

55 ) 

56 layer_exit_marker = numpy.require( 

57 [ 

58 [-0.23, -0.23, 0.0], 

59 [0.23, -0.23, 0.0], 

60 [0.23, 0.23, 0.0], 

61 [0.23, 0.23, 0.0], 

62 [-0.23, 0.23, 0.0], 

63 [-0.23, -0.23, 0.0], 

64 ], 

65 "f", 

66 ) 

67 

68 def load_data(self, model_data, callback=None): 

69 t_start = time.time() 

70 

71 vertex_list = [] 

72 color_list = [] 

73 self.layer_stops = [0] 

74 self.layer_heights = [] 

75 arrow_list = [] 

76 layer_markers_list = [] 

77 self.layer_marker_stops = [0] 

78 

79 num_layers = len(model_data) 

80 callback_every = max(1, int(math.floor(num_layers / 100))) 

81 

82 # the first movement designates the starting point 

83 start = prev = model_data[0][0] 

84 del model_data[0][0] 

85 for layer_idx, layer in enumerate(model_data): 

86 first = layer[0] 

87 for movement in layer: 

88 vertex_list.append(prev.v) 

89 vertex_list.append(movement.v) 

90 arrow = self.arrow 

91 # position the arrow with respect to movement 

92 arrow = vector.rotate(arrow, movement.angle(prev.v), 0.0, 0.0, 1.0) 

93 arrow_list.extend(arrow) 

94 vertex_color = self.movement_color(movement) 

95 color_list.append(vertex_color) 

96 prev = movement 

97 

98 self.layer_stops.append(len(vertex_list)) 

99 self.layer_heights.append(first.v[2]) 

100 

101 # add the layer entry marker 

102 if layer_idx > 0 and len(model_data[layer_idx - 1]) > 0: 

103 layer_markers_list.extend( 

104 self.layer_entry_marker + model_data[layer_idx - 1][-1].v 

105 ) 

106 elif layer_idx == 0 and len(layer) > 0: 

107 layer_markers_list.extend(self.layer_entry_marker + layer[0].v) 

108 

109 # add the layer exit marker 

110 if len(layer) > 1: 

111 layer_markers_list.extend(self.layer_exit_marker + layer[-1].v) 

112 

113 self.layer_marker_stops.append(len(layer_markers_list)) 

114 

115 if callback and layer_idx % callback_every == 0: 

116 callback(layer_idx + 1, num_layers) 

117 

118 self.vertices = numpy.array(vertex_list, "f") 

119 self.colors = numpy.array(color_list, "f") 

120 self.arrows = numpy.array(arrow_list, "f") 

121 self.layer_markers = numpy.array(layer_markers_list, "f") 

122 

123 # by translating the arrow vertices outside of the loop, we achieve a 

124 # significant performance gain thanks to numpy. it would be really nice 

125 # if we could rotate in a similar fashion... 

126 self.arrows = self.arrows + self.vertices[1::2].repeat(3, 0) 

127 

128 # for every pair of vertices of the model, there are 3 vertices for the arrow 

129 assert len(self.arrows) == ( 

130 (len(self.vertices) // 2) * 3 

131 ), "The 2:3 ratio of model vertices to arrow vertices does not hold." 

132 

133 self.max_layers = len(self.layer_stops) - 1 

134 self.num_layers_to_draw = self.max_layers 

135 self.arrows_enabled = True 

136 self.initialized = False 

137 self.vertex_count = len(self.vertices) 

138 

139 t_end = time.time() 

140 

141 logging.info("Initialized Gcode model in %.2f seconds" % (t_end - t_start)) 

142 logging.info("Vertex count: %d" % self.vertex_count) 

143 

144 def movement_color(self, move): 

145 """ 

146 Return the color to use for particular type of movement. 

147 """ 

148 # default movement color is gray 

149 color = (0.6, 0.6, 0.6, 0.6) 

150 

151 extruder_on = move.flags & Movement.FLAG_EXTRUDER_ON or move.delta_e > 0 

152 outer_perimeter = ( 

153 move.flags & Movement.FLAG_PERIMETER 

154 and move.flags & Movement.FLAG_PERIMETER_OUTER 

155 ) 

156 

157 if extruder_on and outer_perimeter: 

158 color = (0.0, 0.875, 0.875, 0.6) # cyan 

159 elif extruder_on and move.flags & Movement.FLAG_PERIMETER: 

160 color = (0.0, 1.0, 0.0, 0.6) # green 

161 elif extruder_on and move.flags & Movement.FLAG_LOOP: 

162 color = (1.0, 0.875, 0.0, 0.6) # yellow 

163 elif extruder_on: 

164 color = (1.0, 0.0, 0.0, 0.6) # red 

165 

166 return color 

167 

168 # ------------------------------------------------------------------------ 

169 # DRAWING 

170 # ------------------------------------------------------------------------ 

171 

172 def init(self): 

173 self.vertex_buffer = VBO(self.vertices, "GL_STATIC_DRAW") 

174 self.vertex_color_buffer = VBO( 

175 self.colors.repeat(2, 0), "GL_STATIC_DRAW" 

176 ) # each pair of vertices shares the color 

177 

178 if self.arrows_enabled: 

179 self.arrow_buffer = VBO(self.arrows, "GL_STATIC_DRAW") 

180 self.arrow_color_buffer = VBO( 

181 self.colors.repeat(3, 0), "GL_STATIC_DRAW" 

182 ) # each triplet of vertices shares the color 

183 

184 self.layer_marker_buffer = VBO(self.layer_markers, "GL_STATIC_DRAW") 

185 

186 self.initialized = True 

187 

188 def display(self, elevation=0, eye_height=0, mode_ortho=False, mode_2d=False): 

189 glPushMatrix() 

190 

191 offset_z = self.offset_z if not mode_2d else 0 

192 glTranslate(self.offset_x, self.offset_y, offset_z) 

193 

194 glEnableClientState(GL_VERTEX_ARRAY) 

195 glEnableClientState(GL_COLOR_ARRAY) 

196 

197 self._display_movements(elevation, eye_height, mode_ortho, mode_2d) 

198 

199 if self.arrows_enabled: 

200 self._display_arrows() 

201 

202 glDisableClientState(GL_COLOR_ARRAY) 

203 

204 if self.arrows_enabled: 

205 self._display_layer_markers() 

206 

207 glDisableClientState(GL_VERTEX_ARRAY) 

208 glPopMatrix() 

209 

210 def _display_movements( 

211 self, elevation=0, eye_height=0, mode_ortho=False, mode_2d=False 

212 ): 

213 self.vertex_buffer.bind() 

214 glVertexPointer(3, GL_FLOAT, 0, None) 

215 

216 self.vertex_color_buffer.bind() 

217 glColorPointer(4, GL_FLOAT, 0, None) 

218 

219 if mode_2d: 

220 glScale(1.0, 1.0, 0.0) # discard z coordinates 

221 start = self.layer_stops[self.num_layers_to_draw - 1] 

222 end = self.layer_stops[self.num_layers_to_draw] 

223 glDrawArrays(GL_LINES, start, end - start) 

224 

225 elif mode_ortho: 

226 if elevation >= 0: 

227 # draw layers in normal order, bottom to top 

228 start = 0 

229 end = self.layer_stops[self.num_layers_to_draw] 

230 glDrawArrays(GL_LINES, start, end - start) 

231 

232 else: 

233 # draw layers in reverse order, top to bottom 

234 stop_idx = self.num_layers_to_draw - 1 

235 while stop_idx >= 0: 

236 start = self.layer_stops[stop_idx] 

237 end = self.layer_stops[stop_idx + 1] 

238 glDrawArrays(GL_LINES, start, end - start) 

239 stop_idx -= 1 

240 

241 else: # 3d projection mode 

242 reverse_threshold_layer = self._layer_up_to_height( 

243 eye_height - self.offset_z 

244 ) 

245 

246 if reverse_threshold_layer >= 0: 

247 # draw layers up to (and including) the threshold in normal order, bottom to top 

248 normal_layers_to_draw = min( 

249 self.num_layers_to_draw, reverse_threshold_layer + 1 

250 ) 

251 start = 0 

252 end = self.layer_stops[normal_layers_to_draw] 

253 glDrawArrays(GL_LINES, start, end - start) 

254 

255 if reverse_threshold_layer + 1 < self.num_layers_to_draw: 

256 # draw layers from the threshold in reverse order, top to bottom 

257 stop_idx = self.num_layers_to_draw - 1 

258 while stop_idx > reverse_threshold_layer: 

259 start = self.layer_stops[stop_idx] 

260 end = self.layer_stops[stop_idx + 1] 

261 glDrawArrays(GL_LINES, start, end - start) 

262 stop_idx -= 1 

263 

264 self.vertex_buffer.unbind() 

265 self.vertex_color_buffer.unbind() 

266 

267 def _layer_up_to_height(self, height): 

268 """Return the index of the last layer lower than height.""" 

269 for idx in range(len(self.layer_heights) - 1, -1, -1): 

270 if self.layer_heights[idx] < height: 

271 return idx 

272 

273 return 0 

274 

275 def _display_arrows(self): 

276 self.arrow_buffer.bind() 

277 glVertexPointer(3, GL_FLOAT, 0, None) 

278 

279 self.arrow_color_buffer.bind() 

280 glColorPointer(4, GL_FLOAT, 0, None) 

281 

282 start = (self.layer_stops[self.num_layers_to_draw - 1] // 2) * 3 

283 end = (self.layer_stops[self.num_layers_to_draw] // 2) * 3 

284 

285 glDrawArrays(GL_TRIANGLES, start, end - start) 

286 

287 self.arrow_buffer.unbind() 

288 self.arrow_color_buffer.unbind() 

289 

290 def _display_layer_markers(self): 

291 self.layer_marker_buffer.bind() 

292 glVertexPointer(3, GL_FLOAT, 0, None) 

293 

294 start = self.layer_marker_stops[self.num_layers_to_draw - 1] 

295 end = self.layer_marker_stops[self.num_layers_to_draw] 

296 

297 glColor4f(0.6, 0.6, 0.6, 0.6) 

298 glDrawArrays(GL_TRIANGLES, start, end - start) 

299 

300 self.layer_marker_buffer.unbind()