Completed
Push — master ( 308305...c45d91 )
by Matthew
01:08
created

ed2d.assets.OBJ.write()   B

Complexity

Conditions 5

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 5
dl 0
loc 13
rs 8.5454
1
from ed2d.assets.mtlloader import MTL
2
from ed2d import files
3
4
def merge_dicts(dicts):
5
    dictKeys = set()
6
    for dic in dicts:
7
        dictKeys.update(dic.keys())
8
    
9
    finalDictionary = {}
10
11
    for key in dictKeys:
12
        # Vertices
13
        finalDictionary[key] = [ [], [], [] ]
14
     
15
    for dic in dicts:
16
        for key in dictKeys:
17
            try:
18
                # Vertices
19
                for i, data in enumerate(dic[key]):
20
                    finalDictionary[key][i].extend(data)
21
            except KeyError:
22
                pass
23
            
24
    return finalDictionary
25
26
27
class OBJObject(object):
28
    ''' Storage for each object inside a .obj file.'''
29
    def __init__(self, data):
30
        # Store the lines that represent one object from obj file.
31
        self.object = data
32
        # Size of the object
33
        self.objectlen = len(self.object)
34
        # Store number of faces/indices
35
        self.fcount = 0
36
        # Store number of vertices
37
        self.vcount = 0
38
        # Store numver of uv indices
39
        self.vtcount = 0
40
        # Store number of normals
41
        self.vncount = 0
42
        # Store number of materials used
43
        self.gcount = 0
44
45
        # Indices dict
46
        self.tmvnig = {}
47
        # Final parsed data dict
48
        self.fmvnig = {}
49
50
        # Material list
51
        self.matList = []
52
53
        # Do a head count and setup the storage layout using dictionaries
54
        for i in range(self.objectlen):
55
            value = self.object[i][:2]
56
            if value == 'v ':
57
                self.vcount += 1
58
            elif value == 'vt':
59
                self.vtcount += 1
60
            elif value == 'vn':
61
                self.vncount += 1
62
            elif value == 'g ':
63
                self.gcount += 1
64
                # The material used currently
65
                materialName = None
66
                if self.object[i+1][:6] == 'usemtl':
67
                    materialName = self.object[i+1].split()[1]
68
                    self.matList.append(materialName)
69
                    # Initialize the two dictionaries, indices and final data
70
                    self.tmvnig[materialName] = [ [], [], [] ]
71
                    self.fmvnig[materialName] = [ [], [], [] ]
72
73
74
                for j in range(i+2, self.objectlen, 1):
75
                    fval = self.object[j][:2]
76
77
                    if fval == 'f ':
78
                        self.fcount += 1
79
                        # Vertices
80
                        self.tmvnig[materialName][0].extend(None for _ in range(3))
81
                        self.fmvnig[materialName][0].extend([None] for _ in range(3))
82
                        # Normals
83
                        self.tmvnig[materialName][2].extend(None for _ in range(3))
84
                        self.fmvnig[materialName][2].extend([None] for _ in range(3))
85
                        # UV Coordinates (usually these are not always generated)
86
                        if self.vtcount != 0:
87
                            self.tmvnig[materialName][1].extend(None for _ in range(3))
88
                            self.fmvnig[materialName][1].extend([None] for _ in range(3))
89
                    elif fval == 'g ':
90
                        # We need this here for any material that is not the last
91
                        # Otherwise it will not know when to stop
92
                        break
93
94
        # Faces, Vertices, UVs, Normals each store 3 values per line
95
        self.fcount *= 3
96
        self.vcount *= 3
97
        self.vtcount *= 3
98
        self.vncount *= 3
99
100
        # Create temporary storage
101
        self.tempVertices = [None] * self.vcount
102
        self.tempNormals = [None] * self.vncount
103
        self.tempUVs = [None] * self.fcount
104
105
        # Store the final counts
106
        self.fnumber = 0
107
        self.vnumber = 0
108
        self.vtnumber = 0
109
        self.vnnumber = 0
110
        self.matnumber = 0
111
112
113
114
class OBJ(object):
115
    def __init__(self, fileName):
116
        ''' Wavefront .obj file parser.'''
117
        ocount = 0
118
119
        __objpath = files.resolve_path('data', 'models', fileName + '.obj')
120
        __mtlpath = files.resolve_path('data', 'models', fileName + '.mtl')
121
        __txtpath = files.resolve_path('data', 'models', fileName + '.txt')
122
123
        # Load the mtl file
124
        self.mtlfile = MTL(__mtlpath)
125
126
        # Load the obj file
127
        with open(__objpath, 'r') as objfile:
128
            lines = objfile.readlines()
129
            lineslen = len(lines)
130
131
            self.objects = []
132
            self.fmvnig = {}
133
            
134
            start = []
135
            end = []
136
            # Do an object count in the file
137
            for i in range(lineslen):
138
                value = lines[i][:2]
139
                value1 = lines[i - 1][:2]
140
                # Look for 'o ' at the begining of the line and count them
141
                if value1 == 'o ':
142
                    # Create a layout of where all the lines starting with o start
143
                    ocount +=1
144
                    start.append((i - 1))
145
                if (value1 == 'f ' or value1 == 'l ') and value == 'o ':
146
                    end.append((i + 1))
147
            end.append(lineslen)
148
149
            for i in range(len(end)):
150
                self.objects.append(OBJObject(lines[start[i]:end[i]]))
151
152
        vertNumberOffset = 0
153
        normNumberOffset = 0
154
        vtNumberOffset = 0
155
        for i in range(len(self.objects)):
156
            self.__process_in_house(self.objects[i], vertNumberOffset, normNumberOffset, vtNumberOffset)
157
            vertNumberOffset += (self.objects[i].vcount / 3)
158
            normNumberOffset += (self.objects[i].vncount / 3)
159
            vtNumberOffset += (self.objects[i].vtcount / 3)
160
161
        for i in range(len(self.objects)):
162
            self.get_final_data(self.objects[i])
163
164
        self.combine_data()
165
166
        # Debug Output
167
        #self.write(__txtpath)
168
169
    def write(self, filename):
170
        ''' Used for debuging. '''
171
        with open(filename, 'w') as objfile:
172
            for j in range(self.matnumber):
173
                objfile.write('\n')
174
                matrname = list(self.tmvnig.keys())[j]
175
                objfile.write(matrname + '\n')
176
                objfile.write('VERTICES\n')
177
                for i in range(len(self.fmvnig[matrname][0]) - 1):
178
                    objfile.write(str(self.tmvnig[matrname][0][i]) + " " + str(self.fmvnig[matrname][0][i]) + '\n')
179
                objfile.write('NORMALS\n')
180
                for k in range(len(self.fmvnig[matrname][0]) - 1):
181
                    objfile.write(str(self.tmvnig[matrname][2][k]) + " " + str(self.fmvnig[matrname][2][k]) + '\n')
182
183
184
    def __process_in_house(self, obj, voffset, noffset, vtoffset):
185
        matname = None
186
187
        for line in obj.object:
188
189
            value = line.split()
190
            valueType = value[0]
191
192
            # Don't bother unless the following key words exist in the line
193
            if valueType not in ['f', 'v', 'vt', 'vn', 'usemtl']:
194
                continue
195
196
            # Start ignoring the first word of the line to grab the values
197
            value = value[1:]
198
199
            # Check first and continue on early because of string splitting
200
            if valueType == "usemtl":
201
                matname = value[0]
202
                # Material Vertex UV Normal Indices Group (Vertex, UV, Normal)
203
                obj.matnumber += 1
204
                obj.fnumber = 0
205
                continue
206
207
            if valueType == "f":
208
                temp = [item.split("/") for item in value]
209
                for i in range(3):
210
                    # 0 - Vertex
211
                    # 1 - UV
212
                    # 2 - Normal
213
                    # Make sure UV index data exists
214
                    if temp[i][1] != '':
215
                        obj.tmvnig[matname][1][obj.fnumber] = abs(int(temp[i][1]) - vtoffset)
216
                    obj.tmvnig[matname][0][obj.fnumber] = abs(int(temp[i][0]) - voffset)
217
                    obj.tmvnig[matname][2][obj.fnumber] = abs(int(temp[i][2]) - noffset)
218
                    obj.fnumber += 1
219
                continue
220
221
            # Map the values after the keyword to floats
222
            value = list(map(float, value))
223
            print (len(value))
224
225
            if valueType == "v":
226
                v = [value[0], value[1], value[2]]
227
                obj.tempVertices[obj.vnumber] = v
228
                obj.vnumber += 1
229
230
            elif valueType == "vt":
231
                vt = [value[0], value[0]]
232
                obj.tempUVs[obj.vtnumber] = vt
233
                obj.vtnumber += 1
234
235
            elif valueType == "vn":
236
                n = [value[0], value[1], value[2]]
237
                obj.tempNormals[obj.vnnumber] = n
238
                obj.vnnumber += 1
239
240
241
    def get_final_data(self, obj):
242
        for j in range(obj.matnumber):
243
            matrname = list(obj.tmvnig.keys())[j]
244
            for i in range(len(obj.tmvnig[matrname][0])):
245
                vertexIndex = int(obj.tmvnig[matrname][0][i]) - 1
246
                vertex = obj.tempVertices[vertexIndex]
247
                obj.fmvnig[matrname][0][i] = vertex
248
249
                normalIndex = int(obj.tmvnig[matrname][2][i]) - 1
250
                normal = obj.tempNormals[normalIndex]
251
                obj.fmvnig[matrname][2][i] = normal
252
253
                if obj.tmvnig[matrname][1] is None and obj.vtnumber != 0:
254
                    uvIndex = int(obj.tmvnig[matrname][1][i]) - 1
255
                    uv = obj.tempUVs[uvIndex]
256
                    obj.fmvnig[matrname][1][i] = uv
257
258
    def combine_data(self):
259
        obj = [obj.fmvnig for obj in self.objects]
260
        self.fmvnig = merge_dicts(obj)
261