import os import sys import PIL import struct from ..basemodel import ModelParser, TextModelParser, Exporter, Vertex, Face, Color, FaceVertex, TexCoord, Material class UnkownTypeError(Exception): def __init__(self, message): self.message = message def is_ply(filename): """Checks that the file is a .ply file Only checks the extension of the file :param filename: path to the file """ return filename[-4:] == '.ply' # List won't work with this function def _ply_type_size(type): """Returns the size of a ply property :param type: a string that is in a ply element """" if type == 'char' or type == 'uchar': return 1 elif type == 'short' or type == 'ushort': return 2 elif type == 'int' or type == 'uint': return 4 elif type == 'float': return 4 elif type == 'double': return 8 else: raise UnkownTypeError('Type ' + type + ' is unknown') def ply_type_size(type): """Returns the list containing the sizes of the elements :param type: a string that is in a ply element """ split = type.split() if len(split) == 1: return [_ply_type_size(type)] else: if split[0] != 'list': print('You have multiple types but it\'s not a list...', file=sys.stderr) sys.exit(-1) else: return list(map(lambda a: _ply_type_size(a), split[1:])) def bytes_to_element(type, bytes, byteorder = 'little'): """Returns a python object parsed from bytes :param type: the type of the object to parse :param bytes: the bytes to read :param byteorder: little or big endian """ if type == 'char': return ord(struct.unpack(' len(bytes): self.previous_bytes = bytes[beginning_byte_index:] # self.current_byte -= len(self.previous_bytes) return if len(size) == 1: size = size[0] current_property_bytes = bytes[current_byte_index:current_byte_index+size] property_values.append(bytes_to_element(property[1], current_property_bytes)) current_byte_index += size # self.current_byte += size elif len(size) == 2: types = property[1].split()[1:] current_property_bytes = bytes[current_byte_index:current_byte_index+size[0]] number_of_elements = bytes_to_element(types[0], current_property_bytes) current_byte_index += size[0] # self.current_byte += size[0] property_values.append([]) # Parse list for i in range(number_of_elements): if current_byte_index + size[1] > len(bytes): self.previous_bytes = bytes[beginning_byte_index:] # self.current_byte -= len(self.previous_bytes) return current_property_bytes = bytes[current_byte_index:current_byte_index+size[1]] property_values[-1].append(bytes_to_element(types[1], current_property_bytes)) current_byte_index += size[1] # self.current_byte += size[1] else: print('I have not idea what this means', file=sys.stderr) # Add element if self.current_element.name == 'vertex': vertex = Vertex() red = None green = None blue = None alpha = None offset = 0 for property in self.current_element.properties: if property[0] == 'x': vertex.x = property_values[offset] elif property[0] == 'y': vertex.y = property_values[offset] elif property[0] == 'z': vertex.z = property_values[offset] elif property[0] == 'red': red = property_values[offset] / 255 elif property[0] == 'green': green = property_values[offset] / 255 elif property[0] == 'blue': blue = property_values[offset] / 255 elif property[0] == 'alpha': alpha = property_values[offset] / 255 offset += 1 self.parent.add_vertex(vertex) if red is not None: self.parent.add_color(Color(red, blue, green)) elif self.current_element.name == 'face': vertex_indices = [] tex_coords = [] material = None for (i, property) in enumerate(self.current_element.properties): if property[0] == 'vertex_indices': vertex_indices.append(property_values[i][0]) vertex_indices.append(property_values[i][1]) vertex_indices.append(property_values[i][2]) elif property[0] == 'texcoord': # Create texture coords for j in range(0,6,2): tex_coord = TexCoord(*property_values[i][j:j+2]) tex_coords.append(tex_coord) elif property[0] == 'texnumber': material = self.parent.materials[property_values[i]] for tex_coord in tex_coords: self.parent.add_tex_coord(tex_coord) face = Face(*list(map(lambda x: FaceVertex(x), vertex_indices))) counter = 3 if len(tex_coords) > 0: for face_vertex in [face.a, face.b, face.c]: face_vertex.tex_coord = len(self.parent.tex_coords) - counter counter -= 1 if material is None and len(self.parent.materials) == 1: material = self.parent.materials[0] face.material = material self.parent.add_face(face) self.counter += 1 if self.counter == self.current_element.number: self.next_element() def next_element(self): self.counter = 0 self.element_index += 1 if self.element_index < len(self.parent.elements): self.current_element = self.parent.elements[self.element_index] class PLYBigEndianContentParser(PLYLittleEndianContentParser): def __init__(self, parent): super().__init__(self, parent) def parse_bytes(self, bytes): # Reverse bytes, and then super().parse_bytes(self, bytes) class PLYExporter(Exporter): def __init__(self, model): super().__init__(model) def __str__(self): faces = sum([part.faces for part in self.model.parts], []) # Header string = "ply\nformat ascii 1.0\ncomment Automatically gnerated by model-converter\n" # Types : vertices string += "element vertex " + str(len(self.model.vertices)) +"\n" string += "property float x\nproperty float y\nproperty float z\n" # Types : faces string += "element face " + str(len(faces)) + "\n" string += "property list uint8 int32 vertex_indices\n" # End header string += "end_header\n" # Content of the model for vertex in self.model.vertices: string += str(vertex.x) + " " + str(vertex.y) + " " + str(vertex.z) + "\n" for face in faces: string += "3 " + str(face.a.vertex) + " " + str(face.b.vertex) + " " + str(face.c.vertex) + "\n" return string