model-converter-python/d3/model/formats/ply.py

495 lines
17 KiB
Python
Raw Permalink Normal View History

2017-01-13 10:18:15 +00:00
import os
2017-01-17 16:16:10 +00:00
import sys
import struct
from ..basemodel import ModelParser, TextModelParser, Exporter, Vertex, Face, Color, FaceVertex, TexCoord, Material
2016-11-21 14:59:03 +00:00
2017-01-17 16:16:10 +00:00
class UnkownTypeError(Exception):
def __init__(self, message):
self.message = message
2016-11-22 10:27:42 +00:00
def is_ply(filename):
2017-01-18 14:18:31 +00:00
"""Checks that the file is a .ply file
Only checks the extension of the file
:param filename: path to the file
"""
2016-11-22 10:27:42 +00:00
return filename[-4:] == '.ply'
2017-01-17 16:16:10 +00:00
# List won't work with this function
def _ply_type_size(type):
2017-01-18 14:18:31 +00:00
"""Returns the size of a ply property
:param type: a string that is in a ply element
2017-01-19 10:23:48 +00:00
"""
2017-01-17 16:16:10 +00:00
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):
2017-01-18 14:18:31 +00:00
"""Returns the list containing the sizes of the elements
:param type: a string that is in a ply element
"""
2017-01-17 16:16:10 +00:00
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'):
2017-01-18 14:18:31 +00:00
"""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
"""
2017-01-17 16:16:10 +00:00
if type == 'char':
return ord(struct.unpack('<b', bytes)[0])
if type == 'uchar':
return ord(struct.unpack('<c', bytes)[0])
elif type == 'short':
return struct.unpack('<h', bytes)[0]
elif type == 'ushort':
return struct.unpack('<H', bytes)[0]
elif type == 'int':
return struct.unpack('<i', bytes)[0]
elif type == 'uint':
return struct.unpack('<I', bytes)[0]
elif type == 'float':
return struct.unpack('<f', bytes)[0]
elif type == 'double':
return struct.unpack('<d', bytes)[0]
else:
raise UnkownTypeError('Type ' + type + ' is unknown')
class PLYParser(ModelParser):
2017-01-18 14:18:31 +00:00
"""Parser that parses a .ply file
"""
2016-11-21 14:59:03 +00:00
2016-11-30 14:37:58 +00:00
def __init__(self, up_conversion = None):
super().__init__(up_conversion)
2016-11-21 14:59:03 +00:00
self.counter = 0
self.elements = []
self.inner_parser = PLYHeaderParser(self)
2017-01-17 16:16:10 +00:00
self.beginning_of_line = ''
self.header_finished = False
2016-11-21 14:59:03 +00:00
2017-01-17 16:16:10 +00:00
def parse_bytes(self, bytes, byte_counter):
2017-01-18 14:18:31 +00:00
"""Parses bytes of a .ply file
"""
2017-01-17 16:16:10 +00:00
if self.header_finished:
self.inner_parser.parse_bytes(self.beginning_of_line + bytes, byte_counter - len(self.beginning_of_line))
self.beginning_of_line = b''
return
# Build lines for header and use PLYHeaderParser
current_line = self.beginning_of_line
for (i, c) in enumerate(bytes):
char = chr(c)
if char == '\n':
self.inner_parser.parse_line(current_line)
if current_line == 'end_header':
self.header_finished = True
self.beginning_of_line = bytes[i+1:]
return
current_line = ''
else:
current_line += chr(c)
self.beginning_of_line = current_line
2016-11-21 14:59:03 +00:00
class PLYHeaderParser:
2017-01-18 14:18:31 +00:00
"""Parser that parses the header of a .ply file
"""
2016-11-21 14:59:03 +00:00
def __init__(self, parent):
self.current_element = None
self.parent = parent
2017-01-17 16:16:10 +00:00
self.content_parser = None
2016-11-21 14:59:03 +00:00
def parse_line(self, string):
2016-11-22 10:54:59 +00:00
split = string.split()
2016-11-21 14:59:03 +00:00
if string == 'ply':
return
elif split[0] == 'format':
2017-01-17 16:16:10 +00:00
if split[2] != '1.0':
print('Only format 1.0 is supported', file=sys.stderr)
sys.exit(-1)
if split[1] == 'ascii':
self.content_parser = PLY_ASCII_ContentParser(self.parent)
elif split[1] == 'binary_little_endian':
self.content_parser = PLYLittleEndianContentParser(self.parent)
elif split[1] == 'binary_big_endian':
self.content_parser = PLYBigEndianContentParser(self.parent)
else:
print('Only ascii, binary_little_endian and binary_big_endian are supported', \
file=sys.stderr)
2016-11-21 14:59:03 +00:00
sys.exit(-1)
elif split[0] == 'element':
self.current_element = PLYElement(split[1], int(split[2]))
self.parent.elements.append(self.current_element)
elif split[0] == 'property':
2017-01-17 16:16:10 +00:00
self.current_element.add_property(split[-1], ' '.join(split[1:-1]))
2016-11-21 14:59:03 +00:00
elif split[0] == 'end_header':
2017-01-17 16:16:10 +00:00
self.parent.inner_parser = self.content_parser
2016-11-21 14:59:03 +00:00
2017-01-13 10:18:15 +00:00
elif split[0] == 'comment' and split[1] == 'TextureFile':
material = Material('mat' + str(len(self.parent.materials)))
self.parent.materials.append(material)
2017-01-19 10:23:48 +00:00
material.relative_path_to_texture = split[2]
material.absolute_path_to_texture = os.path.join(os.path.dirname(self.parent.path), split[2])
2017-01-13 10:18:15 +00:00
2016-11-21 14:59:03 +00:00
class PLYElement:
def __init__(self, name, number):
self.name = name
self.number = number
self.properties = []
2016-11-21 15:18:49 +00:00
def add_property(self, name, type):
2016-11-21 14:59:03 +00:00
self.properties.append((name, type))
2017-01-17 16:16:10 +00:00
class PLY_ASCII_ContentParser:
2016-11-21 14:59:03 +00:00
def __init__(self, parent):
self.parent = parent
self.element_index = 0
self.counter = 0
2017-01-17 16:16:10 +00:00
self.current_element = None
self.beginning_of_line = ''
def parse_bytes(self, bytes, byte_counter):
current_line = self.beginning_of_line
for (i, c) in enumerate(bytes):
char = chr(c)
if char == '\n':
self.parse_line(current_line)
current_line = ''
else:
current_line += chr(c)
self.beginning_of_line = current_line
2016-11-21 14:59:03 +00:00
def parse_line(self, string):
if string == '':
return
2017-01-17 16:16:10 +00:00
if self.current_element is None:
self.current_element = self.parent.elements[0]
2016-11-22 10:54:59 +00:00
split = string.split()
color = None
2016-11-21 14:59:03 +00:00
if self.current_element.name == 'vertex':
vertex = Vertex()
red = None
blue = None
green = None
alpha = None
offset = 0
for property in self.current_element.properties:
if property[0] == 'x':
vertex.x = float(split[offset])
elif property[0] == 'y':
vertex.y = float(split[offset])
elif property[0] == 'z':
vertex.z = float(split[offset])
elif property[0] == 'red':
red = float(split[offset]) / 255
elif property[0] == 'green':
green = float(split[offset]) / 255
elif property[0] == 'blue':
blue = float(split[offset]) / 255
elif property[0] == 'alpha':
alpha = float(split[offset]) / 255
offset += 1
self.parent.add_vertex(vertex)
if red is not None:
color = Color(red, blue, green)
self.parent.add_color(color)
2016-11-21 14:59:03 +00:00
elif self.current_element.name == 'face':
2017-01-13 08:43:24 +00:00
faceVertexArray = []
2017-01-13 10:18:15 +00:00
current_material = None
2017-01-13 08:43:24 +00:00
# Analyse element
2017-01-13 10:18:15 +00:00
offset = 0
2017-01-13 08:43:24 +00:00
for property in self.current_element.properties:
2017-01-13 08:43:24 +00:00
if property[0] == 'vertex_indices':
2017-01-13 10:18:15 +00:00
for i in range(int(split[offset])):
faceVertexArray.append(FaceVertex(int(split[i+offset+1])))
offset += int(split[0]) + 1
2017-01-13 10:18:15 +00:00
elif property[0] == 'texcoord':
offset += 1
for i in range(3):
# Create corresponding tex_coords
tex_coord = TexCoord().from_array(split[offset:offset+2])
offset += 2
self.parent.add_tex_coord(tex_coord)
faceVertexArray[i].tex_coord = len(self.parent.tex_coords) - 1
2017-01-13 10:18:15 +00:00
elif property[0] == 'texnumber':
current_material = self.parent.materials[int(split[offset])]
2017-01-19 10:23:48 +00:00
offset += 1
2017-01-13 10:18:15 +00:00
face = Face(*faceVertexArray)
face.material = current_material
self.parent.add_face(face)
2016-11-21 14:59:03 +00:00
self.counter += 1
2016-11-21 15:18:49 +00:00
if self.counter == self.current_element.number:
self.next_element()
2016-11-21 14:59:03 +00:00
def next_element(self):
self.element_index += 1
2016-11-21 15:18:49 +00:00
if self.element_index < len(self.parent.elements):
self.current_element = self.parent.elements[self.element_index]
2016-11-21 14:59:03 +00:00
2017-01-17 16:16:10 +00:00
class PLYLittleEndianContentParser:
def __init__(self, parent):
self.parent = parent
self.previous_bytes = b''
self.element_index = 0
self.counter = 0
self.current_element = None
self.started = False
# Serves for debugging purposes
# self.current_byte = 0
def parse_bytes(self, bytes, byte_counter):
if not self.started:
# self.current_byte = byte_counter
self.started = True
if self.current_element is None:
self.current_element = self.parent.elements[0]
bytes = self.previous_bytes + bytes
current_byte_index = 0
while True:
property_values = []
beginning_byte_index = current_byte_index
for property in self.current_element.properties:
size = ply_type_size(property[1])
if current_byte_index + size[0] > 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':
2017-01-18 09:41:41 +00:00
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))
2017-01-17 16:16:10 +00:00
elif self.current_element.name == 'face':
2017-01-17 17:03:00 +00:00
vertex_indices = []
tex_coords = []
material = None
2017-01-17 16:16:10 +00:00
2017-01-17 17:03:00 +00:00
for (i, property) in enumerate(self.current_element.properties):
2017-01-17 16:16:10 +00:00
2017-01-17 17:03:00 +00:00
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])
2017-01-17 16:16:10 +00:00
2017-01-17 17:03:00 +00:00
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)
2017-01-17 16:16:10 +00:00
2017-01-17 17:03:00 +00:00
elif property[0] == 'texnumber':
material = self.parent.materials[property_values[i]]
for tex_coord in tex_coords:
self.parent.add_tex_coord(tex_coord)
2017-01-17 17:03:00 +00:00
face = Face(*list(map(lambda x: FaceVertex(x), vertex_indices)))
counter = 3
2017-01-18 09:41:41 +00:00
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]
2017-01-17 17:03:00 +00:00
face.material = material
self.parent.add_face(face)
2017-01-17 16:16:10 +00:00
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)
2016-11-21 14:59:03 +00:00
class PLYExporter(Exporter):
def __init__(self, model):
super().__init__(model)
def __str__(self):
faces = sum([part.faces for part in self.model.parts], [])
2016-11-21 14:59:03 +00:00
# Header
string = "ply\nformat ascii 1.0\ncomment Automatically gnerated by model-converter\n"
2017-01-19 10:23:48 +00:00
for material in self.model.materials:
2017-02-27 13:29:39 +00:00
string += "comment TextureFile " + (material.relative_path_to_texture or 'None') + "\n"
2017-01-19 10:23:48 +00:00
2016-11-21 14:59:03 +00:00
# Types : vertices
string += "element vertex " + str(len(self.model.vertices)) +"\n"
2017-01-17 16:16:10 +00:00
string += "property float x\nproperty float y\nproperty float z\n"
2016-11-21 14:59:03 +00:00
# Types : faces
string += "element face " + str(len(faces)) + "\n"
2017-01-19 10:23:48 +00:00
string += "property list uchar int vertex_indices\n"
if len(self.model.tex_coords) > 0:
string += "property list uchar float texcoord\n"
string += "property int texnumber\n"
2016-11-21 14:59:03 +00:00
# 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"
2016-11-21 14:59:03 +00:00
for face in faces:
2017-01-19 10:23:48 +00:00
string += "3 " + str(face.a.vertex) + " " + str(face.b.vertex) + " " + str(face.c.vertex)
if len(self.model.tex_coords) > 0:
string += " 6 " \
+ str(self.model.tex_coords[face.a.tex_coord].x) + " " \
+ str(self.model.tex_coords[face.a.tex_coord].y) + " " \
+ str(self.model.tex_coords[face.b.tex_coord].x) + " " \
+ str(self.model.tex_coords[face.b.tex_coord].y) + " " \
+ str(self.model.tex_coords[face.c.tex_coord].x) + " " \
+ str(self.model.tex_coords[face.c.tex_coord].y) + " " \
+ str(self.model.get_material_index(face.material))
string += "\n"
2016-11-21 14:59:03 +00:00
return string