Ajout de python-obja
This commit is contained in:
		
							parent
							
								
									61bf8f59ad
								
							
						
					
					
						commit
						bd1ba14af7
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,2 @@ | ||||
| assets | ||||
| __pycache__ | ||||
|  | ||||
							
								
								
									
										37
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								README.md
									
									
									
									
									
								
							| @ -12,6 +12,34 @@ Vous pouvez récupérer les sources de cette application en lançant la commande | ||||
| git clone https://gitea.tforgione.fr/tforgione/obja | ||||
| ``` | ||||
| 
 | ||||
| ## Écriture d'un logiciel de compression progressive | ||||
| 
 | ||||
| Le module `obja.py` permet de parser facilement des fichiers OBJ et de générer | ||||
| des fichiers au format OBJA. | ||||
| 
 | ||||
| La classe `obja.Model` permet de facilement parser un fichier OBJ (grâce à la | ||||
| méthode `parse_file`. Elle contient les attributs suivants : | ||||
| 
 | ||||
|   - `vertices` : une liste de `numpy.array` qui représente les sommets du | ||||
|     modèle (attention, les vecteurs sont en ligne) | ||||
|   - `faces` : une liste de `obja.Face`, qui contiennent eux-mêmes des attributs | ||||
|     `a`, `b` et `c` qui sont les indices des sommets dans l'attribut | ||||
|     `vertices` (les indices commencent à partir de 0). | ||||
| 
 | ||||
| La classe `obja.Output` permet de générer facilement un modèle OBJA. Lors de la | ||||
| transformation d'un modèle pour l'adapter à un chargement progressif, le modèle | ||||
| doit être reconstruit et les indices des sommets et faces sont changés. La | ||||
| classe permet de travailler avec les indices du modèle d'origine, et donc de | ||||
| gérer automatiquement la transformation des indices de l'ancien modèle vers le | ||||
| nouveau modèle. | ||||
| 
 | ||||
| Le fichier `decimate.py` contient un exemple basique de programme permettant la | ||||
| réécriture d'un fichier OBJ en OBJA de manière naïve. Il contient un programme | ||||
| principal qui transforme le fichier `exemple/suzanne.obj` en | ||||
| `exemple/suzanne.obja`, le rendant progressif. | ||||
| 
 | ||||
| ## Visualisation du streaming | ||||
| 
 | ||||
| À la racine de ce projet, le script `server.py` vous permet de démarrer un | ||||
| server de streaming. Vous pouvez l'exécuter en lançant `./server.py`. Une fois | ||||
| cela fait, vous pouvez allez sur [localhost:8000](http://localhost:8000) pour | ||||
| @ -20,14 +48,15 @@ les affiche. | ||||
| 
 | ||||
| Les modèles doivent être sauvegardés dans le dossiers `assets`, et peuvent être | ||||
| visualisés en ajouter `?nom_du_modele.obj` à la fin de l'url. Par exemple, | ||||
| [localhost:8000/?bunny.obj](http://localhost:8000/?bunny.obj) chargera le | ||||
| modèle `bunny.obj` du dossier `assets`. Ce modèle est un modèle d'exemple, il | ||||
| commence par encoder la version basse résolution du [Stanford | ||||
| [localhost:8000/?exemple/suzanne.obja](http://localhost:8000/?exemple/suzanne.obja) | ||||
| chargera le modèle `bunny.obj` du dossier `assets`. Ce modèle est un modèle | ||||
| d'exemple, il commence par encoder la version basse résolution du [Stanford | ||||
| bunny](https://graphics.stanford.edu/data/3Dscanrep/), translate tous ses | ||||
| sommets, les retranslate vers leurs positions d'origine puis supprime toutes | ||||
| les faces. | ||||
| 
 | ||||
| ## Commandes | ||||
| ### Détails du format OBJA | ||||
| 
 | ||||
| ###### Ajout d'un sommet | ||||
| 
 | ||||
| Comme dans le OBJ standard, pour ajouter un sommet, il suffit d'utiliser le | ||||
|  | ||||
							
								
								
									
										4204
									
								
								assets/bunny.obj
									
									
									
									
									
								
							
							
						
						
									
										4204
									
								
								assets/bunny.obj
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										58
									
								
								decimate.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										58
									
								
								decimate.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,58 @@ | ||||
| #!/usr/bin/env python | ||||
| 
 | ||||
| import obja | ||||
| import numpy as np | ||||
| import sys | ||||
| 
 | ||||
| class Decimater(obja.Model): | ||||
|     """ | ||||
|     A simple class that decimates a 3D model stupidly. | ||||
|     """ | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|         self.deleted_faces = set() | ||||
| 
 | ||||
|     def contract(self, output): | ||||
|         """ | ||||
|         Decimates the model stupidly, and write the resulting obja in output. | ||||
|         """ | ||||
|         operations = [] | ||||
| 
 | ||||
|         # Iterate through the vertex | ||||
|         for (vertex_index, vertex) in enumerate(self.vertices): | ||||
| 
 | ||||
|             for (face_index, face) in enumerate(self.faces): | ||||
| 
 | ||||
|                 # Delete any face that depends on this vertex | ||||
|                 if face_index not in self.deleted_faces: | ||||
|                     self.deleted_faces.add(face_index) | ||||
|                     operations.append(('face', face_index, face)) | ||||
| 
 | ||||
|             # Delete the vertex | ||||
|             operations.append(('vertex', vertex_index, vertex)) | ||||
| 
 | ||||
|         # To rebuild the model, run operations in reverse order | ||||
|         operations.reverse() | ||||
| 
 | ||||
|         output_model = obja.Output(output) | ||||
| 
 | ||||
|         for (ty, index, value) in operations: | ||||
|             if ty == "vertex": | ||||
|                 output_model.add_vertex(index, value) | ||||
|             else: | ||||
|                 output_model.add_face(index, value) | ||||
| 
 | ||||
| def main(): | ||||
|     """ | ||||
|     Runs the program on the model given as parameter. | ||||
|     """ | ||||
|     np.seterr(invalid = 'raise') | ||||
|     model = Decimater() | ||||
|     model.parse_file('exemple/suzanne.obj') | ||||
| 
 | ||||
|     with open('exemple/suzanne.obja', 'w') as output: | ||||
|         model.contract(output) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										2580
									
								
								exemple/suzanne.obj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2580
									
								
								exemple/suzanne.obj
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1479
									
								
								exemple/suzanne.obja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1479
									
								
								exemple/suzanne.obja
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -5,7 +5,7 @@ animate(); | ||||
| 
 | ||||
| function init() { | ||||
| 
 | ||||
|     let url = 'assets/' + (document.URL.split('?')[1] || "bunny.obj"); | ||||
|     let url = (document.URL.split('?')[1] || "exemple/suzanne.obja"); | ||||
| 
 | ||||
|     loader = new Loader(url, 1024, 20); | ||||
|     loader.start(function(elements) { | ||||
|  | ||||
| @ -29,8 +29,7 @@ function fetchData(path, start, end, callback) { | ||||
| 
 | ||||
| function parseLine(line, number) { | ||||
|     let element = {}; | ||||
|     let split = line.split(/[ \t]+/); | ||||
| 
 | ||||
|     let split = line.split('#')[0].split(/[ \t]+/); | ||||
|     if (split.length === 0) { | ||||
|         return; | ||||
|     } | ||||
| @ -358,6 +357,7 @@ class Model extends THREE.Mesh { | ||||
|                 normal.normalize(); | ||||
| 
 | ||||
|                 f.normal = normal; | ||||
|                 f.color = this.geometry.faces[element.id].color; | ||||
| 
 | ||||
| 
 | ||||
|                 this.geometry.faces[element.id] = f; | ||||
|  | ||||
							
								
								
									
										326
									
								
								obja.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										326
									
								
								obja.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,326 @@ | ||||
| #!/usr/bin/env python3 | ||||
| 
 | ||||
| import sys | ||||
| import numpy as np | ||||
| import random | ||||
| 
 | ||||
| """ | ||||
| obja model for python. | ||||
| """ | ||||
| 
 | ||||
| class Face: | ||||
|     """ | ||||
|     The class that holds a, b, and c, the indices of the vertices of the face. | ||||
|     """ | ||||
|     def __init__(self, a, b, c, visible = True): | ||||
|         self.a = a | ||||
|         self.b = b | ||||
|         self.c = c | ||||
|         self.visible = visible | ||||
| 
 | ||||
|     def from_array(array): | ||||
|         """ | ||||
|         Initializes a face from an array of strings representing vector indices (starting at 1) | ||||
|         """ | ||||
|         face = Face(0, 0, 0) | ||||
|         face.set(array) | ||||
|         face.visible = True | ||||
|         return face | ||||
| 
 | ||||
|     def set(self, array): | ||||
|         """ | ||||
|         Sets a face from an array of strings representing vector indices (starting at 1) | ||||
|         """ | ||||
|         self.a = int(array[0].split('/')[0]) - 1 | ||||
|         self.b = int(array[1].split('/')[0]) - 1 | ||||
|         self.c = int(array[2].split('/')[0]) - 1 | ||||
|         return self | ||||
| 
 | ||||
|     def clone(self): | ||||
|         """ | ||||
|         Clones a face from another face | ||||
|         """ | ||||
|         return Face(self.a, self.b, self.c, self.visible) | ||||
| 
 | ||||
|     def copy(self, other): | ||||
|         """ | ||||
|         Sets a face from another face | ||||
|         """ | ||||
|         self.a = other.a | ||||
|         self.b = other.b | ||||
|         self.c = other.c | ||||
|         self.visible = other.visible | ||||
|         return self | ||||
| 
 | ||||
|     def test(self, vertices, line = "unknown"): | ||||
|         """ | ||||
|         Tests if a face references only vertices that exist when the face is declared. | ||||
|         """ | ||||
|         if self.a >= len(vertices): | ||||
|             raise VertexError(self.a + 1, line) | ||||
|         if self.b >= len(vertices): | ||||
|             raise VertexError(self.b + 1, line) | ||||
|         if self.c >= len(vertices): | ||||
|             raise VertexError(self.c + 1, line) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return "Face({}, {}, {})".format(self.a, self.b, self.c) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return str(self) | ||||
| 
 | ||||
| class VertexError(Exception): | ||||
|     """ | ||||
|     An operation references a vertex that does not exist. | ||||
|     """ | ||||
|     def __init__(self, index, line): | ||||
|         """ | ||||
|         Creates the error from index of the referenced vertex and the line where the error occured. | ||||
|         """ | ||||
|         self.line = line | ||||
|         self.index = index | ||||
|         super().__init__() | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         """ | ||||
|         Pretty prints the error. | ||||
|         """ | ||||
|         return f'There is no vector {self.index} (line {self.line})' | ||||
| 
 | ||||
| class FaceError(Exception): | ||||
|     """ | ||||
|     An operation references a face that does not exist. | ||||
|     """ | ||||
|     def __init__(self, index, line): | ||||
|         """ | ||||
|         Creates the error from index of the referenced face and the line where the error occured. | ||||
|         """ | ||||
|         self.line = line | ||||
|         self.index = index | ||||
|         super().__init__() | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         """ | ||||
|         Pretty prints the error. | ||||
|         """ | ||||
|         return f'There is no face {self.index} (line {self.line})' | ||||
| 
 | ||||
| class FaceVertexError(Exception): | ||||
|     """ | ||||
|     An operation references a face vector that does not exist. | ||||
|     """ | ||||
|     def __init__(self, index, line): | ||||
|         """ | ||||
|         Creates the error from index of the referenced face vector and the line where the error occured. | ||||
|         """ | ||||
|         self.line = line | ||||
|         self.index = index | ||||
|         super().__init__() | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         """ | ||||
|         Pretty prints the error. | ||||
|         """ | ||||
|         return f'Face has no vector {self.index} (line {self.line})' | ||||
| 
 | ||||
| class UnknownInstruction(Exception): | ||||
|     """ | ||||
|     An instruction is unknown. | ||||
|     """ | ||||
|     def __init__(self, instruction, line): | ||||
|         """ | ||||
|         Creates the error from instruction and the line where the error occured. | ||||
|         """ | ||||
|         self.line = line | ||||
|         self.instruction = instruction | ||||
|         super().__init__() | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         """ | ||||
|         Pretty prints the error. | ||||
|         """ | ||||
|         return f'Instruction {self.instruction} unknown (line {self.line})' | ||||
| 
 | ||||
| class Model: | ||||
|     """ | ||||
|     The OBJA model. | ||||
|     """ | ||||
|     def __init__(self): | ||||
|         """ | ||||
|         Intializes an empty model. | ||||
|         """ | ||||
|         self.vertices = [] | ||||
|         self.faces = [] | ||||
|         self.line = 0 | ||||
| 
 | ||||
|     def get_vector_from_string(self, string): | ||||
|         """ | ||||
|         Gets a vector from a string representing the index of the vector, starting at 1. | ||||
| 
 | ||||
|         To get the vector from its index, simply use model.vertices[i]. | ||||
|         """ | ||||
|         index = int(string) - 1 | ||||
|         if index >= len(self.vertices): | ||||
|             raise FaceError(index + 1, self.line) | ||||
|         return self.vertices[index] | ||||
| 
 | ||||
|     def get_face_from_string(self, string): | ||||
|         """ | ||||
|         Gets a face from a string representing the index of the face, starting at 1. | ||||
| 
 | ||||
|         To get the face from its index, simply use model.faces[i]. | ||||
|         """ | ||||
|         index = int(string) - 1 | ||||
|         if index >= len(self.faces): | ||||
|             raise FaceError(index + 1, self.line) | ||||
|         return self.faces[index] | ||||
| 
 | ||||
|     def parse_file(self, path): | ||||
|         """ | ||||
|         Parses an OBJA file. | ||||
|         """ | ||||
|         with open(path, "r") as file: | ||||
|             for line in file.readlines(): | ||||
|                 self.parse_line(line) | ||||
| 
 | ||||
|     def parse_line(self, line): | ||||
|         """ | ||||
|         Parses a line of obja file. | ||||
|         """ | ||||
|         self.line += 1 | ||||
| 
 | ||||
|         split = line.split() | ||||
| 
 | ||||
|         if len(split) == 0: | ||||
|             return | ||||
| 
 | ||||
|         if split[0] == "v": | ||||
|             self.vertices.append(np.array(split[1:], np.double)) | ||||
| 
 | ||||
|         elif split[0] == "ev": | ||||
|             self.get_vector_from_string(split[1]).set(split[2:]) | ||||
| 
 | ||||
|         elif split[0] == "tv": | ||||
|             self.get_vector_from_string(split[1]).translate(split[2:]) | ||||
| 
 | ||||
|         elif split[0] == "f" or split[0] == "tf": | ||||
|             for i in range(1, len(split) - 2): | ||||
|                 face = Face.from_array(split[i:i+3]) | ||||
|                 face.test(self.vertices, self.line) | ||||
|                 self.faces.append(face) | ||||
| 
 | ||||
|         elif split[0] == "ts": | ||||
|             for i in range(1, len(split) - 2): | ||||
|                 if i % 2 == 1: | ||||
|                     face = Face.from_array([split[i], split[i + 1], split[i + 2]]) | ||||
|                 else: | ||||
|                     face = Face.from_array([split[i], split[i + 2], split[i + 1]]) | ||||
|                 face.test(self.vertices, self.line) | ||||
|                 self.faces.append(face) | ||||
| 
 | ||||
|         elif split[0] == "ef": | ||||
|             self.get_face_from_string(split[1]).set(split[2:]) | ||||
| 
 | ||||
|         elif split[0] == "efv": | ||||
|             face = self.get_face_from_string(split[1]) | ||||
|             vector = int(split[2]) | ||||
|             new_index = int(split[3]) - 1 | ||||
|             if vector == 1: | ||||
|                 face.a = new_index | ||||
|             elif vector == 2: | ||||
|                 face.b = new_index | ||||
|             elif vector == 3: | ||||
|                 face.c = new_index | ||||
|             else: | ||||
|                 raise FaceVertexError(vector, self.line) | ||||
| 
 | ||||
|         elif split[0] == "df": | ||||
|             self.get_face_from_string(split[1]).visible = False | ||||
| 
 | ||||
|         elif split[0] == "#": | ||||
|             return | ||||
| 
 | ||||
|         else: | ||||
|             return | ||||
|             # raise UnknownInstruction(split[0], self.line) | ||||
| 
 | ||||
| def parse_file(path): | ||||
|     """ | ||||
|     Parses a file and returns the model. | ||||
|     """ | ||||
|     model = Model() | ||||
|     model.parse_file(path) | ||||
|     return model | ||||
| 
 | ||||
| class Output: | ||||
|     """ | ||||
|     The type for a model that outputs as obja. | ||||
|     """ | ||||
|     def __init__(self, output, random_color = False): | ||||
|         """ | ||||
|         Initializes the index mapping dictionnaries. | ||||
|         """ | ||||
|         self.vertex_mapping = dict() | ||||
|         self.face_mapping = dict() | ||||
|         self.output = output | ||||
|         self.random_color = random_color | ||||
| 
 | ||||
|     def add_vertex(self, index, vertex): | ||||
|         """ | ||||
|         Adds a new vertex to the model with the specified index. | ||||
|         """ | ||||
|         self.vertex_mapping[index] = len(self.vertex_mapping) | ||||
|         print('v {} {} {}'.format(vertex[0], vertex[1], vertex[2]), file = self.output) | ||||
| 
 | ||||
|     def edit_vertex(self, index, vertex): | ||||
|         """ | ||||
|         Changes the coordinates of a vertex. | ||||
|         """ | ||||
|         print('ev {} {} {} {}'.format(self.vertex_mapping[index] + 1, vertex[0], vertex[1],vertex[2]), file = self.output) | ||||
| 
 | ||||
|     def add_face(self, index, face): | ||||
|         """ | ||||
|         Adds a face to the model. | ||||
|         """ | ||||
|         self.face_mapping[index] = len(self.face_mapping) | ||||
|         print('f {} {} {}'.format( | ||||
|                 self.vertex_mapping[face.a] + 1, | ||||
|                 self.vertex_mapping[face.b] + 1, | ||||
|                 self.vertex_mapping[face.c] + 1, | ||||
|             ), | ||||
|             file = self.output | ||||
|         ) | ||||
| 
 | ||||
|         if self.random_color: | ||||
|             print('fc {} {} {} {}'.format( | ||||
|                 len(self.face_mapping), | ||||
|                 random.uniform(0, 1), | ||||
|                 random.uniform(0, 1), | ||||
|                 random.uniform(0, 1)), | ||||
|                 file = self.output | ||||
|             ) | ||||
| 
 | ||||
|     def edit_face(self, index, face): | ||||
|         """ | ||||
|         Changes the indices of the vertices of the specified face. | ||||
|         """ | ||||
|         print('ef {} {} {} {}'.format( | ||||
|                 self.face_mapping[index] + 1, | ||||
|                 self.vertex_mapping[face.a] + 1, | ||||
|                 self.vertex_mapping[face.b] + 1, | ||||
|                 self.vertex_mapping[face.c] + 1 | ||||
|             ), | ||||
|             file = self.output | ||||
|         ) | ||||
| 
 | ||||
| def main(): | ||||
|     if len(sys.argv) == 1: | ||||
|         print("obja needs a path to an obja file") | ||||
|         return | ||||
| 
 | ||||
|     model = parse_file(sys.argv[1]) | ||||
|     print(model.vertices) | ||||
|     print(model.faces) | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
| @ -29,8 +29,7 @@ function fetchData(path, start, end, callback) { | ||||
| 
 | ||||
| function parseLine(line, number) { | ||||
|     let element = {}; | ||||
|     let split = line.split(/[ \t]+/); | ||||
| 
 | ||||
|     let split = line.split('#')[0].split(/[ \t]+/); | ||||
|     if (split.length === 0) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
| @ -137,6 +137,7 @@ class Model extends THREE.Mesh { | ||||
|                 normal.normalize(); | ||||
| 
 | ||||
|                 f.normal = normal; | ||||
|                 f.color = this.geometry.faces[element.id].color; | ||||
| 
 | ||||
| 
 | ||||
|                 this.geometry.faces[element.id] = f; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user