Newer
Older
"""
This module contains the functions to parse the input files, and create a ScannedObject.
"""
import numpy as np
from utils.files.output import save_output_file
class FacesNotGiven(Exception):
"""
Exception raised when no faces was given.
"""
class ResultFileNotGiven(Exception):
"""
Exception raised when no faces was given.
"""
class ScannedObject:
"""
This class is used to manage the data of the 3D object.
:param vertices: List of verticesm Ndarray of shape (n,2)
:param faces: List of faces, Ndarray of shape (n,2)
:param result_file_path: Path to the result file (deprecated, used for the bruteforce discretization)
:ivar vertices: List of vertices, Ndarray of shape (n,2)
:ivar faces: List of faces, Ndarray of shape (n,2)
:ivar result_file_path: Path to the result file (deprecated, used for the bruteforce discretization)
:ivar x: List of x values of the vertices
:ivar y: List of y values of the vertices
:ivar z: List of z values of the vertices
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
:static method from_xyz_file(): Creates a ScannedObject from a .xyz file
:static method from_obj_file(): Creates a ScannedObject from a .obj file
:method get_x(): Returns the x values of the vertices
:method get_y(): Returns the y values of the vertices
:method get_z(): Returns the z values of the vertices
:method get_vertices(): Returns the vertices
:method get_faces(): Returns the faces
:method get_discrete_vertices(): Returns the discrete vertices
:method get_data(): Returns the data
:method export: Exports the data to a file
:raises FacesNotGiven: If no faces was given
:raises ResultFileNotGiven: If no result file was given
:Example:
>>> from utils.files.input import ScannedObject
>>> vertices = [(0,0,0), (1,0,0), (1,1,0), (0,1,0), (0,0,1), (1,0,1), (1,1,1), (0,1,1)]
>>> faces = [(0,1,2), (0,2,3), (4,5,6), (4,6,7), (0,1,5), (0,4,5), (1,2,6), (1,5,6), (2,3,7), (2,6,7), (3,0,4), (3,7,4)]
>>> obj = ScannedObject(vertices, faces)
>>> obj.get_x()
[0, 1, 1, 0, 0, 1, 1, 0]
>>> obj.get_y()
[0, 0, 1, 1, 0, 0, 1, 1]
>>> obj.get_z()
[0, 0, 0, 0, 1, 1, 1, 1]
>>> obj.get_vertices()
[(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]
>>> obj.get_faces()
[(0, 1, 2), (0, 2, 3), (4, 5, 6), (4, 6, 7), (0, 1, 5), (0, 4, 5), (1, 2, 6), (1, 5, 6), (2, 3, 7), (2, 6, 7), (3, 0, 4), (3, 7, 4)]
>>> obj.get_discrete_vertices()
[[(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)]],[ (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]]
#TODO Add and test exemples
"""
def __init__(self, vertices, faces=None, result_file_path=None):
self.vertices = np.asarray(vertices)
self.faces = np.asarray(faces)
# Deprecated
self.result_file_path = result_file_path
self.bruteforce_discretization_result = None
#
self.x = np.asarray([vertex[0] for vertex in vertices])
self.y = np.asarray([vertex[1] for vertex in vertices])
self.z = np.asarray([vertex[2] for vertex in vertices])
@staticmethod
def from_obj_file(file_path:str, result_file_path:str = None, ratio:float = 1,normalised:str = '')->'ScannedObject':
"""
Create an Object from an OBJ file.
:param file_path: Path to the OBJ file
:param result_file_path: Path to the result file
:param ratio: Ratio to apply to the vertices
:param normalised: the axis to normalise
:return: A ScannedObject
"""
with open(file_path, 'r') as f:
x, y, z = [], [], []
triangles = []
data = f.readlines()
for line in data :
if line.startswith('f'):
if "//" in line:
triangles.append([int(line.split()[1].split("//")[0])-1, int(line.split()[2].split("//")[0])-1, int(line.split()[3].split("//")[0])-1])
else:
triangles.append([int(line.split()[1])-1, int(line.split()[2])-1, int(line.split()[3])-1])
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
elif line.startswith('v'):
x.append(float(line.split()[1]) * ratio)
y.append(float(line.split()[2]) * ratio)
z.append(float(line.split()[3]) * ratio)
if 'x' in normalised:
xmin = min(x)
for count, value in enumerate(x):
x[count] -= xmin
if 'y' in normalised:
ymin = min(y)
for count, value in enumerate(y):
y[count] -= ymin
if 'z' in normalised:
zmin = min(z)
for count, value in enumerate(z):
z[count] -= zmin
return ScannedObject(list(zip(x,y,z)), triangles, result_file_path)
@staticmethod
def from_xyz_file(file_path: str, result_file_path:str = None, delimiter: str = ' ',normalised:str = '')->'ScannedObject':
"""
Create an Object from an XYZ file.
:param file_path: Path to the XYZ file
:param result_file_path: Path to the result file
:param delimiter: The delimiter used in the xyz file.
:param normalised: the axis to normalise
:return: A ScannedObject
"""
x , y , z = [], [], []
with open(file_path, 'r') as f:
data = f.readlines()
for line in data:
x.append(float(line.split(delimiter)[0]))
y.append(float(line.split(delimiter)[1]))
z.append(float(line.split(delimiter)[2]))
if 'x' in normalised:
xmin = min(x)
for count, value in enumerate(x):
x[count] -= xmin
if 'y' in normalised:
ymin = min(y)
for count, value in enumerate(y):
y[count] -= ymin
if 'z' in normalised:
zmin = min(z)
for count, value in enumerate(z):
z[count] -= zmin
return ScannedObject(list(zip(x,y,z)), result_file_path=result_file_path)
def get_x(self)->list:
"""
Get the x coordinates of the object.
return: x coordinates
"""
return self.x
def get_y(self)->list:
"""
Get the y coordinates of the object.
return: y coordinates
"""
return self.y
def get_z(self)->list:
"""
Get the z coordinates of the object.
return: z coordinates
"""
return self.z
def get_vertices(self, sort:bool = False):
"""
Get the vertices of the object.
:param sort: Sort the vertices by z coordinate
:return: vertices
"""
vertices = self.vertices if not sort else sorted(self.vertices, key=lambda vertex: vertex[2])
return vertices
def get_discrete_vertices(self, step:float = 1):
"""
Discretize the vertices of the object.
:param step: Step of the discretization
:return: Discretized vertices
"""
current_interval = int(min(self.get_z()))
splitted_data = [[]]
for line in self.get_vertices(sort=True):
# TODO check distance instead of equality
if line[2] >= current_interval + step:
splitted_data.append([])
current_interval += step
splitted_data[-1].append(line)
return splitted_data
def get_discrete_vertices2(self, step:float = 1):
"""
Deprecated
"""
cpt = 0
L = [[]]
for vertex in self.get_vertices(sort=True):
step = 1
L[-1].append(vertex)
if vertex[2] > cpt + step:
cpt += step
L.append([])
return L
def get_discrete_vertices3(self, step:float = 1):
"""
Deprecated
"""
cpt = 0
L = [[]]
z = min(self.get_z())
sorted = self.get_vertices(sort=True)
for index in range(len(sorted)):
L[-1].append(sorted[index])
if sorted[index][2] - z > step:
z = sorted[index][2]
L.append([])
return L
def get_faces(self,resolved:bool = False)->list:
"""
Get the faces of the object.
:return: faces
"""
if self.faces is None:
raise FacesNotGiven('No faces was given')
if resolved:
return self.vertices[self.faces]
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def update_from_faces(self,faces:list):
"""
Update the object from the faces.
:param faces: Faces to update the object from
"""
cpt = 0
vertex_dict = {}
new_vertices = []
new_faces = []
for face in faces:
new_faces.append([])
for vertex in face:
vertex = tuple(vertex)
if vertex not in vertex_dict:
vertex_dict[vertex] = cpt
cpt += 1
new_vertices.append(vertex)
new_faces[-1].append(vertex_dict[vertex])
self.vertices = np.asarray(new_vertices)
self.faces = np.asarray(new_faces)
self.x = self.vertices[:,0]
self.y = self.vertices[:,1]
self.z = self.vertices[:,2]
self.normalise()
def normalise(self, axis:str = 'z'):
"""
Normalise the object.
:param axis: Axis to normalise
"""
if 'x' in axis:
self.x -= min(self.x)
if 'y' in axis:
self.y -= min(self.y)
if 'z' in axis:
self.z -= min(self.z)
self.vertices = np.asarray(list(zip(self.x,self.y,self.z)))
def get_data(self)->dict:
"""
Get the data of the object.
:return: Data of the object
"""
return {'verticies': self.vertices, 'faces': self.faces, 'x': self.x, 'y': self.y, 'z': self.z}
def bruteforce_discretization(self):
"""
Deprecated
TODO Remove this when its not needed anymore
"""
if self.bruteforce_discretization_result:
return self.bruteforce_discretization_result
if self.result_file_path is None:
raise ResultFileNotGiven("No result file was given")
L = []
splitted_data = [[]]
moyx, moyy, moyz = parse_result_file(self.result_file_path)
moy = moyx
verticies = self.get_vertices(sort=True)
position = 0
while position < len(verticies):
print('progression :',position/len(verticies)*100,end="\r")
x = verticies[position][0]
y = verticies[position][1]
z = verticies[position][2]
L.append(x)
splitted_data[-1].append(verticies[position])
m = np.mean(L)
if len(moy) > 0 and abs(m - moy[0]) < 0.000001:
moy.pop(0)
L = []
splitted_data.append([])
copyposition = position
while int(verticies[copyposition][2]) == int(verticies[copyposition-1][2]):
copyposition -= 1
position += 1
print(50*" ")
self.bruteforce_discretization_result = splitted_data
return splitted_data
def export_xyz(self, file_path:str,separator:str="\t"):
"""
Export the object in a file.
:param file_path: Path of the file
:param separator: chars used to separate the values
"""
string = ''
with open(file_path, "w") as f:
for vertex in self.get_vertices(sort=True):
x = round(vertex[0], 6)
y = round(vertex[1], 6)
z = round(vertex[2], 6)
string+=f"{x}{separator}{y}{separator}{z}\n"
save_output_file(file_path,string)
def export_obj(self,file_path):
"""
Export the object in a file.
:param file_path: Path of the file
"""
string = ''
with open(file_path, "w") as f:
for vertex in self.get_vertices():
x = round(vertex[0], 6)
y = round(vertex[1], 6)
z = round(vertex[2], 6)
string+=f"v {x} {y} {z}\n"
for face in self.get_faces():
string+="f "
for vertex in face:
string+=f"{vertex+1} "
string+="\n"
save_output_file(file_path,string)
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
def parse_result_file(file_path: str, separator: str = "\t")-> tuple:
"""
This functions parses the discretised output file to retreive the first
three colunms. It is used to extract the means of x y z for the consistency
check and the bruteforce_discretisation
:param file_path: Path of the file
:param separator: chars used to separate the values
:return: x, y, z
:Example:
>>> parse_result_file("test.txt")
([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], [1.0, 2.0, 3.0])
"""
lines = []
x, y, z = [], [], []
with open(file_path, "r") as f:
lines = f.readlines()[1:]
for line in lines:
line = line.replace(",", ".")
values = line.split(separator)
x.append(float(values[0]))
y.append(float(values[1]))
z.append(float(values[2]))
return x, y, z