Disabled thingiview for obj models, since thingiview's support for them seems to...
[mediagoblin.git] / mediagoblin / media_types / stl / model_loader.py
CommitLineData
76918e52
AN
1# GNU MediaGoblin -- federated, autonomous media hosting
2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
18import struct
19
20
21class ThreeDeeParseError(Exception):
22 pass
23
24
25class ThreeDee():
26 """
27 3D model parser base class. Derrived classes are used for basic
28 analysis of 3D models, and are not intended to be used for 3D
29 rendering.
30 """
31
32 def __init__(self, fileob):
33 self.verts = []
34 self.average = [0, 0, 0]
35 self.min = [None, None, None]
36 self.max = [None, None, None]
37 self.width = 0 # x axis
38 self.depth = 0 # y axis
39 self.height = 0 # z axis
40
41 self.load(fileob)
42 if not len(self.verts):
43 raise ThreeDeeParseError("Empyt model.")
44
45 for vector in self.verts:
46 for i in range(3):
47 num = vector[i]
48 self.average[i] += num
49 if not self.min[i]:
50 self.min[i] = num
51 self.max[i] = num
52 else:
53 if self.min[i] > num:
54 self.min[i] = num
55 if self.max[i] < num:
56 self.max[i] = num
57
58 for i in range(3):
59 self.average[i]/=len(self.verts)
60
61 self.width = abs(self.min[0] - self.max[0])
62 self.depth = abs(self.min[1] - self.max[1])
63 self.height = abs(self.min[2] - self.max[2])
64
65
66 def load(self, fileob):
67 """Override this method in your subclass."""
68 pass
69
70
71class ObjModel(ThreeDee):
72 """
73 Parser for textureless wavefront obj files. File format
74 reference: http://en.wikipedia.org/wiki/Wavefront_.obj_file
75 """
76
77 def __vector(self, line, expected=3):
78 nums = map(float, line.strip().split(" ")[1:])
79 return tuple(nums[:expected])
80
81 def load(self, fileob):
82 for line in fileob:
83 if line[0] == "v":
84 self.verts.append(self.__vector(line))
85
86
87class BinaryStlModel(ThreeDee):
88 """
89 Parser for ascii-encoded stl files. File format reference:
90 http://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL
91 """
92
93 def __num(self, fileob, hint):
94 assert hint == "uint" or hint == "real" or hint == "short"
95 form = None
96 bits = 0
97 if hint == "uint":
98 form = "<I" # little-endian unsigned int
99 bits = 32
100 elif hint == "real":
101 form = "<i" # little-endian signed int
102 bits = 32
103 elif hint == "short":
104 form = "<H" # little-endian unsigned short
105 bits = 16
106 return struct.unpack(form, fileob.read(bits/8))[0]
107
108 def __vector(self, fileob):
109 return tuple([self.__num(fileob, "real") for n in range(3)])
110
111 def load(self, fileob):
112 fileob.seek(80) # skip the header
113 triangle_count = self.__num(fileob, "uint")
114 for i in range(triangle_count):
115 self.__vector(fileob) # skip the normal vector
116 for v in range(3):
117 # - FIXME - traingle_count IS reporting the correct
118 # number, but the vertex information appears to be
119 # total nonsense :(
120 self.verts.append(self.__vector(fileob))
121 self.__num(fileob, "short") # skip the attribute byte count
122
123
124def auto_detect(fileob, hint):
125 """
126 Attempt to divine which parser to use to divine information about
127 the model / verify the file."""
128
129 if hint == "obj" or not hint:
130 try:
131 return ObjModel(fileob)
132 except ThreeDeeParseError:
133 pass
134
135 if hint == "stl" or not hint:
136 try:
137 # HACK Ascii formatted stls are similar enough to obj
138 # files that we can just use the same parser for both.
139 # Isn't that something?
140 return ObjModel(fileob)
141 except ThreeDeeParseError:
142 pass
ecee8d62
AN
143 except ValueError:
144 pass
76918e52
AN
145 try:
146 # It is pretty important that the binary stl model loader
147 # is tried second, because its possible for it to parse
148 # total garbage from plaintext =)
149 return BinaryStlModel(fileob)
150 except ThreeDeeParseError:
151 pass
152 except MemoryError:
153 pass
154
155 raise ThreeDeeParseError("Could not successfully parse the model :(")