Completed
Push — master ( 2154a3...ce8cea )
by
unknown
01:03
created

ed2d.assets.OBJ.__process_in_house()   D

Complexity

Conditions 11

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 11
dl 0
loc 54
rs 4.4999

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like ed2d.assets.OBJ.__process_in_house() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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