Coverage for tatlin/lib/gl/stlmodel.py: 85%

109 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 numpy 

20import logging 

21import time 

22 

23from OpenGL.GL import * # type:ignore 

24from OpenGL.GLE import * # type:ignore 

25from OpenGL.arrays.vbo import VBO 

26 

27from .model import Model 

28 

29from tatlin.lib import vector 

30 

31 

32class StlModel(Model): 

33 """ 

34 Model for displaying and manipulating STL data. 

35 """ 

36 

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

38 t_start = time.time() 

39 

40 vertices, normals = model_data 

41 # convert python lists to numpy arrays for constructing vbos 

42 self.vertices = numpy.require(vertices, "f") 

43 self.normals = numpy.require(normals, "f") 

44 

45 self.scaling_factor = 1.0 

46 self.rotation_angle = { 

47 self.AXIS_X: 0.0, 

48 self.AXIS_Y: 0.0, 

49 self.AXIS_Z: 0.0, 

50 } 

51 

52 self.mat_specular = (1.0, 1.0, 1.0, 1.0) 

53 self.mat_shininess = 50.0 

54 self.light_position = (20.0, 20.0, 20.0) 

55 

56 self.vertex_count = len(self.vertices) 

57 self.initialized = False 

58 

59 t_end = time.time() 

60 

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

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

63 

64 def normal_data_empty(self): 

65 """ 

66 Return true if the model has no normal data. 

67 """ 

68 empty = self.normals.max() == 0 and self.normals.min() == 0 

69 return empty 

70 

71 def calculate_normals(self): 

72 """ 

73 Calculate surface normals for model vertices. 

74 """ 

75 a = self.vertices[0::3] - self.vertices[1::3] 

76 b = self.vertices[1::3] - self.vertices[2::3] 

77 cross = numpy.cross(a, b) 

78 

79 # normalize the cross product 

80 magnitudes = numpy.apply_along_axis(numpy.linalg.norm, 1, cross).reshape(-1, 1) 

81 normals = cross / magnitudes 

82 

83 # each of 3 facet vertices shares the same normal 

84 normals = normals.repeat(3, 0) 

85 return normals 

86 

87 # ------------------------------------------------------------------------ 

88 # DRAWING 

89 # ------------------------------------------------------------------------ 

90 

91 def init(self): 

92 """ 

93 Create vertex buffer objects (VBOs). 

94 """ 

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

96 

97 if self.normal_data_empty(): 

98 logging.info("STL model has no normal data") 

99 self.normals = self.calculate_normals() 

100 

101 self.normal_buffer = VBO(self.normals, "GL_STATIC_DRAW") 

102 self.initialized = True 

103 

104 def draw_facets(self): 

105 glPushMatrix() 

106 

107 glEnable(GL_LIGHT0) 

108 glEnable(GL_LIGHT1) 

109 glShadeModel(GL_SMOOTH) 

110 

111 # material properties (white plastic) 

112 glMaterial(GL_FRONT, GL_AMBIENT, (0.0, 0.0, 0.0, 1.0)) 

113 glMaterial(GL_FRONT, GL_DIFFUSE, (0.55, 0.55, 0.55, 1.0)) 

114 glMaterial(GL_FRONT, GL_SPECULAR, (0.7, 0.7, 0.7, 1.0)) 

115 glMaterial(GL_FRONT, GL_SHININESS, 32.0) 

116 

117 # lights properties 

118 glLight(GL_LIGHT0, GL_AMBIENT, (0.3, 0.3, 0.3, 1.0)) 

119 glLight(GL_LIGHT0, GL_DIFFUSE, (0.3, 0.3, 0.3, 1.0)) 

120 glLight(GL_LIGHT1, GL_DIFFUSE, (0.3, 0.3, 0.3, 1.0)) 

121 

122 # lights position 

123 glLightfv(GL_LIGHT0, GL_POSITION, self.light_position) 

124 glLightfv(GL_LIGHT1, GL_POSITION, (-20.0, -20.0, 20.0)) 

125 

126 glColor(1.0, 1.0, 1.0) 

127 

128 # Begin VBO stuff 

129 

130 self.vertex_buffer.bind() 

131 glVertexPointer(3, GL_FLOAT, 0, None) 

132 self.normal_buffer.bind() 

133 glNormalPointer(GL_FLOAT, 0, None) 

134 

135 glEnableClientState(GL_VERTEX_ARRAY) 

136 glEnableClientState(GL_NORMAL_ARRAY) 

137 

138 glDrawArrays(GL_TRIANGLES, 0, len(self.vertices)) 

139 

140 glDisableClientState(GL_NORMAL_ARRAY) 

141 glDisableClientState(GL_VERTEX_ARRAY) 

142 self.normal_buffer.unbind() 

143 self.vertex_buffer.unbind() 

144 

145 # End VBO stuff 

146 

147 glDisable(GL_LIGHT1) 

148 glDisable(GL_LIGHT0) 

149 

150 glPopMatrix() 

151 

152 def display(self, *args, **kwargs): 

153 glEnable(GL_LIGHTING) 

154 self.draw_facets() 

155 glDisable(GL_LIGHTING) 

156 

157 # ------------------------------------------------------------------------ 

158 # TRANSFORMATIONS 

159 # ------------------------------------------------------------------------ 

160 

161 def scale(self, factor): 

162 if factor != self.scaling_factor: 

163 logging.info("actually scaling vertices") 

164 self.vertices *= factor / self.scaling_factor 

165 self.scaling_factor = factor 

166 self.invalidate_bounding_box() 

167 self.modified = True 

168 

169 def translate(self, x, y, z): 

170 self.vertices = vector.translate(self.vertices, x, y, z) 

171 self.invalidate_bounding_box() 

172 self.modified = True 

173 

174 def rotate_rel(self, angle, axis): 

175 logging.info( 

176 "rotating vertices by a relative angle of " 

177 "%.2f degrees along the %s axis" % (angle, self.axis_letter_map[axis]) 

178 ) 

179 

180 angle = angle % 360 

181 self.vertices = vector.rotate(self.vertices, angle, *axis) 

182 self.rotation_angle[axis] += angle 

183 self.invalidate_bounding_box() 

184 self.modified = True 

185 

186 def rotate_abs(self, angle, axis): 

187 angle = angle % 360 

188 if self.rotation_angle[axis] == angle: 

189 return 

190 

191 logging.info( 

192 "rotating vertices by an absolute angle of " 

193 "%.2f degrees along the %s axis" % (angle, self.axis_letter_map[axis]) 

194 ) 

195 final_matrix = vector.identity_matrix() 

196 

197 # modify matrix to rotate to initial position 

198 for v in [self.AXIS_Z, self.AXIS_Y, self.AXIS_X]: 

199 matrix = vector.rotation_matrix(-self.rotation_angle[v], *v) 

200 final_matrix = final_matrix.dot(matrix) 

201 

202 # change the angle 

203 self.rotation_angle[axis] = angle 

204 

205 # modify matrix to rotate to new position 

206 for v in [self.AXIS_X, self.AXIS_Y, self.AXIS_Z]: 

207 matrix = vector.rotation_matrix(self.rotation_angle[v], *v) 

208 final_matrix = final_matrix.dot(matrix) 

209 

210 self.vertices = self.vertices.dot(final_matrix) 

211 self.invalidate_bounding_box() 

212 self.modified = True