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
Ks 0.0 0.0 0.0
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 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
usemtl fuckyou

View File

@ -134,8 +134,18 @@ class ModelParser:
self.current_part.add_face(face)
def parse_bytes(self, bytes):
pass
def parse_file(self, path, chunk_size = 512):
"""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):
"""Draws each part of the model with OpenGL

View File

@ -1,11 +1,65 @@
import os
import sys
import PIL
import struct
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):
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):
super().__init__(up_conversion)
@ -13,14 +67,35 @@ class PLYParser(TextModelParser):
self.elements = []
self.materials = []
self.inner_parser = PLYHeaderParser(self)
self.beginning_of_line = ''
self.header_finished = False
def parse_line(self, string):
self.inner_parser.parse_line(string)
def parse_bytes(self, bytes, byte_counter):
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:
def __init__(self, parent):
self.current_element = None
self.parent = parent
self.content_parser = None
def parse_line(self, string):
split = string.split()
@ -28,8 +103,19 @@ class PLYHeaderParser:
return
elif split[0] == 'format':
if split[1] != 'ascii' or split[2] != '1.0':
print('Only ascii 1.0 format is supported', file=sys.stderr)
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)
sys.exit(-1)
elif split[0] == 'element':
@ -37,10 +123,10 @@ class PLYHeaderParser:
self.parent.elements.append(self.current_element)
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':
self.parent.inner_parser = PLYContentParser(self.parent)
self.parent.inner_parser = self.content_parser
elif split[0] == 'comment' and split[1] == 'TextureFile':
material = Material('mat' + str(len(self.parent.materials)))
@ -48,11 +134,9 @@ class PLYHeaderParser:
try:
material.map_Kd = PIL.Image.open(os.path.join(os.path.dirname(self.parent.path), split[2]))
except:
except ImportError:
pass
class PLYElement:
def __init__(self, name, number):
self.name = name
@ -62,22 +146,31 @@ class PLYElement:
def add_property(self, name, type):
self.properties.append((name, type))
class PLYVertex(Vertex):
def __init__(self, string):
pass
def add_to_parser(self, parser):
pass
class PLYContentParser:
class PLY_ASCII_ContentParser:
def __init__(self, parent):
self.parent = parent
self.element_index = 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):
if self.current_element is None:
self.current_element = self.parent.elements[0]
split = string.split()
if self.current_element.name == 'vertex':
@ -119,6 +212,125 @@ class PLYContentParser:
if self.element_index < len(self.parent.elements):
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):
def __init__(self, model):
super().__init__(model)
@ -132,7 +344,7 @@ class PLYExporter(Exporter):
# Types : vertices
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
string += "element face " + str(len(faces)) + "\n"

View File

@ -24,6 +24,7 @@ class Material:
try:
ix, iy, image = self.map_Kd.size[0], self.map_Kd.size[1], self.map_Kd.tobytes("raw", "RGBA", 0, -1)
except:
print('Humm')
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)