Added binary little endian ply support

This commit is contained in:
Thomas FORGIONE 2017-01-17 17:16:10 +01:00
parent 41a13e6014
commit c0ced36370
No known key found for this signature in database
GPG Key ID: 2A210FFC062E00C3
5 changed files with 251 additions and 22 deletions

View File

@ -8,3 +8,6 @@ Ka 0.5 0.5 0.
Kd 0.9 0.9 0.9 Kd 0.9 0.9 0.9
Ks 0.0 0.0 0.0 Ks 0.0 0.0 0.0
map_Kd cube.png map_Kd cube.png
newmtl fuckyou
Ka 1 1 1

View File

@ -29,3 +29,6 @@ f 6/1/1 5/3/1 7/4/1 8/2/1
f 5/1/6 1/3/6 3/4/6 7/2/6 f 5/1/6 1/3/6 3/4/6 7/2/6
f 4/1/2 8/3/2 7/4/2 3/2/2 f 4/1/2 8/3/2 7/4/2 3/2/2
f 2/1/5 1/3/5 5/4/5 6/2/5 f 2/1/5 1/3/5 5/4/5 6/2/5
usemtl fuckyou

View File

@ -134,8 +134,18 @@ class ModelParser:
self.current_part.add_face(face) self.current_part.add_face(face)
def parse_bytes(self, bytes): def parse_file(self, path, chunk_size = 512):
pass """Sets the path of the model and parse bytes by chunk
"""
self.path = path
byte_counter = 0
with open(path, 'rb') as f:
while True:
bytes = f.read(chunk_size)
if bytes == b'':
return
self.parse_bytes(bytes, byte_counter)
byte_counter += chunk_size
def draw(self): def draw(self):
"""Draws each part of the model with OpenGL """Draws each part of the model with OpenGL

View File

@ -1,11 +1,65 @@
import os import os
import sys
import PIL import PIL
import struct
from ..basemodel import ModelParser, TextModelParser, Exporter, Vertex, Face, FaceVertex, TexCoord, Material from ..basemodel import ModelParser, TextModelParser, Exporter, Vertex, Face, FaceVertex, TexCoord, Material
class UnkownTypeError(Exception):
def __init__(self, message):
self.message = message
def is_ply(filename): def is_ply(filename):
return filename[-4:] == '.ply' return filename[-4:] == '.ply'
class PLYParser(TextModelParser): # List won't work with this function
def _ply_type_size(type):
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):
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'):
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):
def __init__(self, up_conversion = None): def __init__(self, up_conversion = None):
super().__init__(up_conversion) super().__init__(up_conversion)
@ -13,14 +67,35 @@ class PLYParser(TextModelParser):
self.elements = [] self.elements = []
self.materials = [] self.materials = []
self.inner_parser = PLYHeaderParser(self) self.inner_parser = PLYHeaderParser(self)
self.beginning_of_line = ''
self.header_finished = False
def parse_line(self, string): def parse_bytes(self, bytes, byte_counter):
self.inner_parser.parse_line(string) 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
class PLYHeaderParser: class PLYHeaderParser:
def __init__(self, parent): def __init__(self, parent):
self.current_element = None self.current_element = None
self.parent = parent self.parent = parent
self.content_parser = None
def parse_line(self, string): def parse_line(self, string):
split = string.split() split = string.split()
@ -28,8 +103,19 @@ class PLYHeaderParser:
return return
elif split[0] == 'format': elif split[0] == 'format':
if split[1] != 'ascii' or split[2] != '1.0': if split[2] != '1.0':
print('Only ascii 1.0 format is supported', file=sys.stderr) 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)
sys.exit(-1) sys.exit(-1)
elif split[0] == 'element': elif split[0] == 'element':
@ -37,10 +123,10 @@ class PLYHeaderParser:
self.parent.elements.append(self.current_element) self.parent.elements.append(self.current_element)
elif split[0] == 'property': elif split[0] == 'property':
self.current_element.add_property(split[-1], (split[1:][:-1])) self.current_element.add_property(split[-1], ' '.join(split[1:-1]))
elif split[0] == 'end_header': elif split[0] == 'end_header':
self.parent.inner_parser = PLYContentParser(self.parent) self.parent.inner_parser = self.content_parser
elif split[0] == 'comment' and split[1] == 'TextureFile': elif split[0] == 'comment' and split[1] == 'TextureFile':
material = Material('mat' + str(len(self.parent.materials))) material = Material('mat' + str(len(self.parent.materials)))
@ -48,11 +134,9 @@ class PLYHeaderParser:
try: try:
material.map_Kd = PIL.Image.open(os.path.join(os.path.dirname(self.parent.path), split[2])) material.map_Kd = PIL.Image.open(os.path.join(os.path.dirname(self.parent.path), split[2]))
except: except ImportError:
pass pass
class PLYElement: class PLYElement:
def __init__(self, name, number): def __init__(self, name, number):
self.name = name self.name = name
@ -62,22 +146,31 @@ class PLYElement:
def add_property(self, name, type): def add_property(self, name, type):
self.properties.append((name, type)) self.properties.append((name, type))
class PLYVertex(Vertex): class PLY_ASCII_ContentParser:
def __init__(self, string):
pass
def add_to_parser(self, parser):
pass
class PLYContentParser:
def __init__(self, parent): def __init__(self, parent):
self.parent = parent self.parent = parent
self.element_index = 0 self.element_index = 0
self.counter = 0 self.counter = 0
self.current_element = self.parent.elements[0] 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
def parse_line(self, string): def parse_line(self, string):
if self.current_element is None:
self.current_element = self.parent.elements[0]
split = string.split() split = string.split()
if self.current_element.name == 'vertex': if self.current_element.name == 'vertex':
@ -119,6 +212,125 @@ class PLYContentParser:
if self.element_index < len(self.parent.elements): if self.element_index < len(self.parent.elements):
self.current_element = self.parent.elements[self.element_index] self.current_element = self.parent.elements[self.element_index]
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':
self.parent.add_vertex(Vertex().from_array(property_values))
elif self.current_element.name == 'face':
# Create texture coords
for i in range(0,6,2):
tex_coord = TexCoord(*property_values[1][i:i+2])
self.parent.add_tex_coord(tex_coord)
face = Face(\
FaceVertex(property_values[0][0], len(self.parent.tex_coords)-3), \
FaceVertex(property_values[0][1], len(self.parent.tex_coords)-2), \
FaceVertex(property_values[0][2], len(self.parent.tex_coords)-1))
face.material = self.parent.materials[property_values[2]]
self.parent.add_face(face)
pass
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): class PLYExporter(Exporter):
def __init__(self, model): def __init__(self, model):
super().__init__(model) super().__init__(model)
@ -132,7 +344,7 @@ class PLYExporter(Exporter):
# Types : vertices # Types : vertices
string += "element vertex " + str(len(self.model.vertices)) +"\n" string += "element vertex " + str(len(self.model.vertices)) +"\n"
string += "property float32 x\nproperty float32 y\nproperty float32 z\n" string += "property float x\nproperty float y\nproperty float z\n"
# Types : faces # Types : faces
string += "element face " + str(len(faces)) + "\n" string += "element face " + str(len(faces)) + "\n"

View File

@ -24,6 +24,7 @@ class Material:
try: try:
ix, iy, image = self.map_Kd.size[0], self.map_Kd.size[1], self.map_Kd.tobytes("raw", "RGBA", 0, -1) ix, iy, image = self.map_Kd.size[0], self.map_Kd.size[1], self.map_Kd.tobytes("raw", "RGBA", 0, -1)
except: except:
print('Humm')
ix, iy, image = self.map_Kd.size[0], self.map_Kd.size[1], self.map_Kd.tobytes("raw", "RGBX", 0, -1) ix, iy, image = self.map_Kd.size[0], self.map_Kd.size[1], self.map_Kd.tobytes("raw", "RGBX", 0, -1)
self.id = gl.glGenTextures(1) self.id = gl.glGenTextures(1)