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
« 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
19import numpy
20import logging
21import time
23from OpenGL.GL import * # type:ignore
24from OpenGL.GLE import * # type:ignore
25from OpenGL.arrays.vbo import VBO
27from .model import Model
29from tatlin.lib import vector
32class StlModel(Model):
33 """
34 Model for displaying and manipulating STL data.
35 """
37 def load_data(self, model_data, callback=None):
38 t_start = time.time()
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")
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 }
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)
56 self.vertex_count = len(self.vertices)
57 self.initialized = False
59 t_end = time.time()
61 logging.info("Initialized STL model in %.2f seconds" % (t_end - t_start))
62 logging.info("Vertex count: %d" % self.vertex_count)
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
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)
79 # normalize the cross product
80 magnitudes = numpy.apply_along_axis(numpy.linalg.norm, 1, cross).reshape(-1, 1)
81 normals = cross / magnitudes
83 # each of 3 facet vertices shares the same normal
84 normals = normals.repeat(3, 0)
85 return normals
87 # ------------------------------------------------------------------------
88 # DRAWING
89 # ------------------------------------------------------------------------
91 def init(self):
92 """
93 Create vertex buffer objects (VBOs).
94 """
95 self.vertex_buffer = VBO(self.vertices, "GL_STATIC_DRAW")
97 if self.normal_data_empty():
98 logging.info("STL model has no normal data")
99 self.normals = self.calculate_normals()
101 self.normal_buffer = VBO(self.normals, "GL_STATIC_DRAW")
102 self.initialized = True
104 def draw_facets(self):
105 glPushMatrix()
107 glEnable(GL_LIGHT0)
108 glEnable(GL_LIGHT1)
109 glShadeModel(GL_SMOOTH)
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)
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))
122 # lights position
123 glLightfv(GL_LIGHT0, GL_POSITION, self.light_position)
124 glLightfv(GL_LIGHT1, GL_POSITION, (-20.0, -20.0, 20.0))
126 glColor(1.0, 1.0, 1.0)
128 # Begin VBO stuff
130 self.vertex_buffer.bind()
131 glVertexPointer(3, GL_FLOAT, 0, None)
132 self.normal_buffer.bind()
133 glNormalPointer(GL_FLOAT, 0, None)
135 glEnableClientState(GL_VERTEX_ARRAY)
136 glEnableClientState(GL_NORMAL_ARRAY)
138 glDrawArrays(GL_TRIANGLES, 0, len(self.vertices))
140 glDisableClientState(GL_NORMAL_ARRAY)
141 glDisableClientState(GL_VERTEX_ARRAY)
142 self.normal_buffer.unbind()
143 self.vertex_buffer.unbind()
145 # End VBO stuff
147 glDisable(GL_LIGHT1)
148 glDisable(GL_LIGHT0)
150 glPopMatrix()
152 def display(self, *args, **kwargs):
153 glEnable(GL_LIGHTING)
154 self.draw_facets()
155 glDisable(GL_LIGHTING)
157 # ------------------------------------------------------------------------
158 # TRANSFORMATIONS
159 # ------------------------------------------------------------------------
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
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
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 )
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
186 def rotate_abs(self, angle, axis):
187 angle = angle % 360
188 if self.rotation_angle[axis] == angle:
189 return
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()
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)
202 # change the angle
203 self.rotation_angle[axis] = angle
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)
210 self.vertices = self.vertices.dot(final_matrix)
211 self.invalidate_bounding_box()
212 self.modified = True