From 8294a4080bd3680e926ff62c9aaa21448c741b76 Mon Sep 17 00:00:00 2001 From: Danila Date: Sun, 11 Sep 2022 11:57:07 +0300 Subject: [PATCH] glTF parsing improvement (scaling of the points), nodes importing --- README.md | 9 +- models_converter/formats/gltf/__init__.py | 3 +- models_converter/formats/gltf/accessor.py | 6 +- models_converter/formats/gltf/animation.py | 6 +- models_converter/formats/gltf/gltf.py | 28 +-- .../formats/gltf/gltf_property.py | 32 +-- models_converter/formats/gltf/mesh.py | 2 +- models_converter/formats/gltf/node.py | 10 +- models_converter/formats/gltf/parser.py | 199 ++++++++---------- models_converter/formats/scw/parser.py | 23 +- models_converter/formats/universal/node.py | 6 + models_converter/formats/universal/scene.py | 7 + .../interfaces/parser_interface.py | 13 +- .../interfaces/writer_interface.py | 12 +- models_converter/utilities/math/quaternion.py | 3 + models_converter/utilities/math/vector3.py | 3 + models_converter/utilities/matrix/__init__.py | 2 +- models_converter/utilities/reader.py | 5 +- .../utils/__pycache__/__init__.cpython-38.pyc | Bin 215 -> 0 bytes .../utils/__pycache__/__init__.cpython-39.pyc | Bin 209 -> 0 bytes .../utils/__pycache__/reader.cpython-39.pyc | Bin 3923 -> 0 bytes .../utils/__pycache__/writer.cpython-39.pyc | Bin 4002 -> 0 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 2979 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 3468 -> 0 bytes .../__pycache__/matrix2x2.cpython-39.pyc | Bin 779 -> 0 bytes .../__pycache__/matrix3x3.cpython-39.pyc | Bin 1911 -> 0 bytes .../__pycache__/matrix4x4.cpython-39.pyc | Bin 4706 -> 0 bytes setup.py | 4 +- 28 files changed, 195 insertions(+), 178 deletions(-) delete mode 100644 models_converter/utils/__pycache__/__init__.cpython-38.pyc delete mode 100644 models_converter/utils/__pycache__/__init__.cpython-39.pyc delete mode 100644 models_converter/utils/__pycache__/reader.cpython-39.pyc delete mode 100644 models_converter/utils/__pycache__/writer.cpython-39.pyc delete mode 100644 models_converter/utils/matrix/__pycache__/__init__.cpython-38.pyc delete mode 100644 models_converter/utils/matrix/__pycache__/__init__.cpython-39.pyc delete mode 100644 models_converter/utils/matrix/__pycache__/matrix2x2.cpython-39.pyc delete mode 100644 models_converter/utils/matrix/__pycache__/matrix3x3.cpython-39.pyc delete mode 100644 models_converter/utils/matrix/__pycache__/matrix4x4.cpython-39.pyc diff --git a/README.md b/README.md index 84eafd0..8d906c2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ ## Python 3D Models Converter -**Version**: 0.8.8 +A module, which helps convert different 3d formats -### **THIS IS NOT RELEASE VERSION!** - -### Thanks a lot for motivating [AMIRMISTIK]! - - -[AMIRMISTIK]: https://www.youtube.com/channel/UCksd1LeoySP5St6dKlv6mvQ +**Version**: 0.9.0 diff --git a/models_converter/formats/gltf/__init__.py b/models_converter/formats/gltf/__init__.py index 009586a..9eabcaf 100644 --- a/models_converter/formats/gltf/__init__.py +++ b/models_converter/formats/gltf/__init__.py @@ -32,5 +32,6 @@ 'Scene', 'Skin', 'Texture', - 'Parser' + 'Parser', + 'Writer' ] diff --git a/models_converter/formats/gltf/accessor.py b/models_converter/formats/gltf/accessor.py index ad20dd0..7a64eb6 100644 --- a/models_converter/formats/gltf/accessor.py +++ b/models_converter/formats/gltf/accessor.py @@ -21,8 +21,8 @@ def __init__(self): def __init__(self): super().__init__() self.count = None - self.indices = self.Indices() - self.values = self.Values() + self.indices = self.Indices + self.values = self.Values def __init__(self): super().__init__() @@ -35,5 +35,5 @@ def __init__(self): self.normalized = False self.max = None self.min = None - self.sparse = self.Sparse() + self.sparse = self.Sparse self.name = None diff --git a/models_converter/formats/gltf/animation.py b/models_converter/formats/gltf/animation.py index d9476d0..0b85574 100644 --- a/models_converter/formats/gltf/animation.py +++ b/models_converter/formats/gltf/animation.py @@ -21,12 +21,12 @@ def __init__(self): def __init__(self): super().__init__() self.sampler = None - self.target = self.Target() + self.target = self.Target def __init__(self): super().__init__() - self.channels = self.Channel() - self.samplers = self.AnimationSampler() + self.channels = self.Channel + self.samplers = self.AnimationSampler self.name = None diff --git a/models_converter/formats/gltf/gltf.py b/models_converter/formats/gltf/gltf.py index c5a4b7e..eb1b175 100644 --- a/models_converter/formats/gltf/gltf.py +++ b/models_converter/formats/gltf/gltf.py @@ -5,22 +5,22 @@ class GlTF(GlTFProperty): def __init__(self): super().__init__() - self.asset = Asset() + self.asset = Asset self.extensions_used = None self.extensions_required = None - self.accessors = Accessor() - self.animations = Animation() - self.buffers = Buffer() - self.buffer_views = BufferView() - self.cameras = Camera() - self.images = Image() - self.materials = Material() - self.meshes = Mesh() - self.nodes = Node() - self.samplers = Sampler() + self.accessors = Accessor + self.animations = Animation + self.buffers = Buffer + self.buffer_views = BufferView + self.cameras = Camera + self.images = Image + self.materials = Material + self.meshes = Mesh + self.nodes = Node + self.samplers = Sampler self.scene = None - self.scenes = Scene() - self.skins = Skin() - self.textures = Texture() + self.scenes = Scene + self.skins = Skin + self.textures = Texture diff --git a/models_converter/formats/gltf/gltf_property.py b/models_converter/formats/gltf/gltf_property.py index 7d554c4..2f7b83d 100644 --- a/models_converter/formats/gltf/gltf_property.py +++ b/models_converter/formats/gltf/gltf_property.py @@ -1,3 +1,7 @@ +from models_converter.utilities.math import Vector3, Quaternion +from models_converter.utilities.matrix.matrix4x4 import Matrix4x4 + + def to_camelcase(property_name: str): words = property_name.split('_') for word_index in range(len(words)): @@ -9,19 +13,16 @@ def to_camelcase(property_name: str): return camelcase_name -def to_lowercase(property_name: str): - letters = list(property_name) - - for letter_index in range(len(letters)): - letter = letters[letter_index] +def to_lowercase(property_name: str) -> str: + result = '' - if letter.isupper(): - letter = f'_{letter.lower()}' + for char in property_name: + if char.isupper(): + char = f'_{char.lower()}' - letters[letter_index] = letter + result += char - lowercase_name = ''.join(letters) - return lowercase_name + return result class GlTFProperty: @@ -36,13 +37,14 @@ def from_dict(self, dictionary: dict): value_type = type(value) attribute_value = getattr(self, attribute_name) - attribute_value_type = type(attribute_value) - if attribute_value is None or value_type in [int, str]: + if attribute_value is None or value_type in (int, str, bool): attribute_value = value - elif issubclass(attribute_value_type, GlTFProperty): + elif type(attribute_value) in (Vector3, Quaternion, Matrix4x4) and type(value) is list: + attribute_value = type(attribute_value)(*value) + elif issubclass(attribute_value, GlTFProperty): if value_type is list: - value_type = attribute_value_type + value_type = attribute_value values = [] for item in value: @@ -53,7 +55,7 @@ def from_dict(self, dictionary: dict): attribute_value = values else: - attribute_value = attribute_value_type() + attribute_value = attribute_value() attribute_value.from_dict(value) setattr(self, attribute_name, attribute_value) diff --git a/models_converter/formats/gltf/mesh.py b/models_converter/formats/gltf/mesh.py index f636ae1..91076cf 100644 --- a/models_converter/formats/gltf/mesh.py +++ b/models_converter/formats/gltf/mesh.py @@ -14,7 +14,7 @@ def __init__(self): def __init__(self): super().__init__() - self.primitives = self.Primitive() + self.primitives = self.Primitive self.weights = None self.name = None diff --git a/models_converter/formats/gltf/node.py b/models_converter/formats/gltf/node.py index 920e39d..94a8b10 100644 --- a/models_converter/formats/gltf/node.py +++ b/models_converter/formats/gltf/node.py @@ -1,4 +1,6 @@ from .gltf_property import GlTFProperty +from ...utilities.math import Vector3, Quaternion +from ...utilities.matrix.matrix4x4 import Matrix4x4 class Node(GlTFProperty): @@ -7,10 +9,10 @@ def __init__(self): self.camera = None self.children = None self.skin = None - self.matrix = None # Default: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + self.matrix = Matrix4x4(size=(4, 4)) self.mesh = None - self.rotation = None # Default: [0, 0, 0, 1] - self.scale = None # Default: [1, 1, 1] - self.translation = None # Default: [0, 0, 0] + self.rotation = Quaternion() # Default: [0, 0, 0, 1] + self.scale = Vector3(1, 1, 1) # Default: [1, 1, 1] + self.translation = Vector3() # Default: [0, 0, 0] self.weights = None self.name = None diff --git a/models_converter/formats/gltf/parser.py b/models_converter/formats/gltf/parser.py index 391a984..9c6e75a 100644 --- a/models_converter/formats/gltf/parser.py +++ b/models_converter/formats/gltf/parser.py @@ -5,17 +5,13 @@ from models_converter.formats.gltf.gltf import GlTF from models_converter.formats.gltf.node import Node from models_converter.formats.universal import Scene, Geometry -from models_converter.utilities.math import Vector3, Quaternion +from models_converter.interfaces import ParserInterface from models_converter.utilities.reader import Reader -class Parser(Reader): - def __init__(self, initial_bytes: bytes): - super().__init__(initial_bytes, 'little') - - self.magic = self.read(4) - if self.magic != b'glTF': - raise TypeError('File Magic isn\'t "glTF"') +class Parser(ParserInterface): + def __init__(self, data: bytes): + self.file_data = data self.scene = Scene() @@ -32,37 +28,46 @@ def __init__(self, initial_bytes: bytes): self.gltf = GlTF() def parse_bin(self): - super().__init__(self.bin_chunk.data, 'little') + reader = Reader(self.bin_chunk.data, 'little') for buffer in self.gltf.buffers: - parsed_buffer = self.read(buffer.byte_length) + parsed_buffer = reader.read(buffer.byte_length) self.buffers.append(parsed_buffer) for buffer_view in self.gltf.buffer_views: - super().__init__(self.buffers[buffer_view.buffer], 'little') + reader.__init__(self.buffers[buffer_view.buffer], 'little') - self.read(buffer_view.byte_offset) + reader.read(buffer_view.byte_offset) length = buffer_view.byte_length - data = self.read(length) + data = reader.read(length) self.buffer_views.append(data) for accessor in self.gltf.accessors: - super().__init__(self.buffer_views[accessor.buffer_view], 'little') - temp_accessor = [] + reader.__init__(self.buffer_views[accessor.buffer_view], 'little') - self.read(accessor.byte_offset) + reader.read(accessor.byte_offset) types = { - 5120: (self.readByte, 1), - 5121: (self.readUByte, 1), - 5122: (self.readShort, 2), - 5123: (self.readUShort, 2), - 5125: (self.readUInt32, 4), - 5126: (self.readFloat, 4) + 5120: (reader.readByte, 1), + 5121: (reader.readUByte, 1), + 5122: (reader.readShort, 2), + 5123: (reader.readUShort, 2), + 5125: (reader.readUInt32, 4), + 5126: (reader.readFloat, 4) } + if accessor.normalized: + types = { + 5120: (lambda: max(reader.readByte() / 127, -1.0), 1), + 5121: (lambda: reader.readUByte() / 255, 1), + 5122: (lambda: max(reader.readShort() / 32767, -1.0), 2), + 5123: (lambda: reader.readUShort() / 65535, 2), + 5125: (reader.readUInt32, 4), + 5126: (reader.readFloat, 4) + } + items_count = { 'SCALAR': 1, 'VEC2': 2, @@ -73,69 +78,49 @@ def parse_bin(self): 'MAT4': 16 } - component_nb = items_count[accessor.type] - read_type, bytes_per_elem = types[accessor.component_type] - default_stride = bytes_per_elem * component_nb + components_count = items_count[accessor.type] + read_type, bytes_per_element = types[accessor.component_type] + default_stride = bytes_per_element * components_count stride = self.gltf.buffer_views[accessor.buffer_view].byte_stride or default_stride - if default_stride == stride: - for x in range(accessor.count): - temp_list = [] - for i in range(component_nb): - temp_list.append(read_type()) - temp_accessor.append(temp_list) - else: - elems_per_stride = stride // bytes_per_elem - num_elems = (accessor.count - 1) * elems_per_stride + component_nb - temp_list = [] - for i in range(num_elems): - temp_list.append(read_type()) + elements_per_stride = stride // bytes_per_element + elements_count = accessor.count * elements_per_stride - temp_accessor = [temp_list[x:x + component_nb] for x in range(0, num_elems, elems_per_stride)] + temp_list = [] + for i in range(elements_count): + temp_list.append(read_type()) - if accessor.normalized: - for item_index, data in enumerate(temp_accessor): - new_data = [] - for value in data: - if accessor.component_type == 5120: - value = max(value / 127, -1.0) - elif accessor.component_type == 5121: - value /= 255 - elif accessor.component_type == 5122: - value = max(value / 32767, -1.0) - elif accessor.component_type == 5123: - value /= 65535 - new_data.append(value) - temp_accessor[item_index] = new_data - - self.accessors.append(temp_accessor) + self.accessors.append([ + temp_list[i:i + components_count] + for i in range(0, elements_count, elements_per_stride) + ]) def parse(self): - # + reader = Reader(self.file_data, 'little') + + magic = reader.read(4) + if magic != b'glTF': + raise TypeError('Wrong file magic! "676c5446" expected, but given is ' + magic.hex()) - self.version = self.readUInt32() - self.length = self.readUInt32() + self.version = reader.readUInt32() + self.length = reader.readUInt32() self.json_chunk = GlTFChunk() self.bin_chunk = GlTFChunk() - self.json_chunk.chunk_length = self.readUInt32() - self.json_chunk.chunk_name = self.read(4) - self.json_chunk.data = self.read(self.json_chunk.chunk_length) + self.json_chunk.chunk_length = reader.readUInt32() + self.json_chunk.chunk_name = reader.read(4) + self.json_chunk.data = reader.read(self.json_chunk.chunk_length) - self.bin_chunk.chunk_length = self.readUInt32() - self.bin_chunk.chunk_name = self.read(4) - self.bin_chunk.data = self.read(self.bin_chunk.chunk_length) - - # + self.bin_chunk.chunk_length = reader.readUInt32() + self.bin_chunk.chunk_name = reader.read(4) + self.bin_chunk.data = reader.read(self.bin_chunk.chunk_length) self.gltf.from_dict(json.loads(self.json_chunk.data)) self.parse_bin() - # - scene_id = self.gltf.scene scene = self.gltf.scenes[scene_id] @@ -143,10 +128,14 @@ def parse(self): node = self.gltf.nodes[node_id] self.parse_node(node) - # + # TODO: animations + # for animation in self.gltf.animations: + # for channel in animation.channels: + # sampler: Animation.AnimationSampler = animation.samplers[channel.sampler] + # input_accessor = self.accessors[sampler.input] def parse_node(self, gltf_node: Node, parent: str = None): - node_name = gltf_node.name + node_name = gltf_node.name.split('|')[-1] node = universal.Node( name=node_name, @@ -154,8 +143,8 @@ def parse_node(self, gltf_node: Node, parent: str = None): ) instance = None - if gltf_node.mesh: - mesh = self.gltf.meshes[gltf_node.mesh] # TODO: merge _geo.glb and _.*.glb files to fix TypeError + if gltf_node.mesh is not None and type(self.gltf.meshes) is list: + mesh = self.gltf.meshes[gltf_node.mesh] mesh_name = mesh.name.split('|') group = 'GEO' @@ -166,7 +155,7 @@ def parse_node(self, gltf_node: Node, parent: str = None): geometry = Geometry(name=name, group=group) - if gltf_node.skin: + if gltf_node.skin is not None: instance = universal.Node.Instance(name=geometry.get_name(), instance_type='CONT') geometry.set_controller_bind_matrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) @@ -195,11 +184,9 @@ def parse_node(self, gltf_node: Node, parent: str = None): else: instance = universal.Node.Instance(name=geometry.get_name(), instance_type='GEOM') - offsets = { - 'POSITION': 0, - 'NORMAL': 0, - 'TEXCOORD': 0 - } + position_offset = 0 + normal_offset = 0 + texcoord_offset = 0 for primitive in mesh.primitives: if primitive.to_dict() != {}: @@ -226,10 +213,24 @@ def parse_node(self, gltf_node: Node, parent: str = None): if attribute_id == 'POSITION': position = self.accessors[attribute] - points = position + points = list(map( + lambda point: ( + point[0] * gltf_node.scale.x + gltf_node.translation.x, + point[1] * gltf_node.scale.y + gltf_node.translation.y, + point[2] * gltf_node.scale.z + gltf_node.translation.z + ), + position + )) elif attribute_id == 'NORMAL': normal = self.accessors[attribute] - points = normal + points = list(map( + lambda point: ( + point[0] * gltf_node.scale.x, + point[1] * gltf_node.scale.y, + point[2] * gltf_node.scale.z + ), + normal + )) elif attribute_id.startswith('TEXCOORD'): texcoord = self.accessors[attribute] @@ -259,9 +260,9 @@ def parse_node(self, gltf_node: Node, parent: str = None): triangles = [ [ [ - point[0] + offsets['NORMAL'], - point[0] + offsets['POSITION'], - point[0] + offsets['TEXCOORD'] + point[0] + normal_offset, + point[0] + position_offset, + point[0] + texcoord_offset ] for point in triangles[x:x + 3] ] for x in range(0, len(triangles), 3) ] @@ -270,11 +271,11 @@ def parse_node(self, gltf_node: Node, parent: str = None): for attribute_id in attributes: if attribute_id == 'POSITION': - offsets['POSITION'] += len(position) + position_offset += len(position) elif attribute_id == 'NORMAL': - offsets['NORMAL'] += len(normal) + normal_offset += len(normal) elif attribute_id.startswith('TEXCOORD'): - offsets['TEXCOORD'] += len(texcoord) + texcoord_offset += len(texcoord) self.scene.add_geometry(geometry) @@ -283,28 +284,12 @@ def parse_node(self, gltf_node: Node, parent: str = None): self.scene.add_node(node) - if gltf_node.translation or gltf_node.rotation or gltf_node.scale: - node.add_frame(universal.Node.Frame(0, Vector3(), Vector3(1, 1, 1), Quaternion())) - - if gltf_node.translation: - node.get_frames()[0].set_position(Vector3( - gltf_node.translation[0], - gltf_node.translation[1], - gltf_node.translation[2] - )) - if gltf_node.rotation: - node.get_frames()[0].set_rotation(Quaternion( - gltf_node.rotation[0], - gltf_node.rotation[1], - gltf_node.rotation[2], - gltf_node.rotation[3] - )) - if gltf_node.scale: - node.get_frames()[0].set_scale(Vector3( - gltf_node.scale[0], - gltf_node.scale[1], - gltf_node.scale[2] - )) + node.add_frame(universal.Node.Frame( + 0, + gltf_node.translation, + gltf_node.scale, + gltf_node.rotation + )) if gltf_node.children: for child_id in gltf_node.children: diff --git a/models_converter/formats/scw/parser.py b/models_converter/formats/scw/parser.py index 1998e7e..657d56a 100644 --- a/models_converter/formats/scw/parser.py +++ b/models_converter/formats/scw/parser.py @@ -4,22 +4,21 @@ from .chunks import * -class Parser(ParserInterface, Reader): +class Parser(ParserInterface): def __init__(self, file_data: bytes): - Reader.__init__(self, file_data) - self.file_data = file_data self.scene = Scene() self.chunks = [] self.header = None - file_magic = self.read(4) + def parse(self): + reader = Reader(self.file_data) + file_magic = reader.read(4) if file_magic != b'SC3D': raise TypeError('File Magic isn\'t "SC3D"') - def parse(self): - self._split_chunks() + self._split_chunks(reader) for chunk in self.chunks: chunk_name = chunk['chunk_name'] @@ -52,13 +51,13 @@ def parse(self): else: raise TypeError(f'Unknown chunk: {chunk_name}') - def _split_chunks(self): + def _split_chunks(self, reader: Reader): # len(Chunk Length) + len(Chunk Name) + len(Chunk CRC) - while len(self.file_data[self.tell():]) >= 12: - chunk_length = self.readUInt32() - chunk_name = self.readChars(4) - chunk_data = self.read(chunk_length) - chunk_crc = self.readUInt32() + while reader.tell() <= len(self.file_data) - 12: + chunk_length = reader.readUInt32() + chunk_name = reader.readChars(4) + chunk_data = reader.read(chunk_length) + chunk_crc = reader.readUInt32() self.chunks.append({ 'chunk_name': chunk_name, diff --git a/models_converter/formats/universal/node.py b/models_converter/formats/universal/node.py index c5bfc6a..ec380ef 100644 --- a/models_converter/formats/universal/node.py +++ b/models_converter/formats/universal/node.py @@ -104,3 +104,9 @@ def get_frames(self) -> List[Frame]: def add_frame(self, frame: Frame): self._frames.append(frame) + + def set_frames(self, frames: List[Frame]): + self._frames.clear() + + for frame in frames: + self._frames.append(frame) diff --git a/models_converter/formats/universal/scene.py b/models_converter/formats/universal/scene.py index 2125a30..3acfc80 100644 --- a/models_converter/formats/universal/scene.py +++ b/models_converter/formats/universal/scene.py @@ -38,6 +38,13 @@ def get_nodes(self) -> List[Node]: def add_node(self, node: Node): self._nodes.append(node) + def import_nodes(self, animation_scene): + for node in self._nodes: + for animation_node in animation_scene.get_nodes(): + if node.get_name() == animation_node.get_name(): + node.set_frames(animation_node.get_frames()) + break + def get_frame_rate(self) -> int: return self._frame_rate diff --git a/models_converter/interfaces/parser_interface.py b/models_converter/interfaces/parser_interface.py index f20d998..9cfe292 100644 --- a/models_converter/interfaces/parser_interface.py +++ b/models_converter/interfaces/parser_interface.py @@ -1,11 +1,16 @@ +import abc + from models_converter.formats.universal import Scene class ParserInterface: - def __init__(self): + @abc.abstractmethod + def __init__(self, file_data: bytes or str): self.scene: Scene or None = None - raise NotImplementedError('This is an abstract class') - + @abc.abstractmethod def parse(self): - raise NotImplementedError('This is an abstract class') + """ + + :return: + """ diff --git a/models_converter/interfaces/writer_interface.py b/models_converter/interfaces/writer_interface.py index d48f25c..0221572 100644 --- a/models_converter/interfaces/writer_interface.py +++ b/models_converter/interfaces/writer_interface.py @@ -1,13 +1,19 @@ +import abc + from models_converter.formats.universal import Scene class WriterInterface: MAGIC: bytes + @abc.abstractmethod def __init__(self): self.writen: bytes or str = None - raise NotImplementedError('This is an abstract class') - + @abc.abstractmethod def write(self, scene: Scene): - raise NotImplementedError('This is an abstract class') + """ + + :param scene: + :return: + """ diff --git a/models_converter/utilities/math/quaternion.py b/models_converter/utilities/math/quaternion.py index 1038576..c60810a 100644 --- a/models_converter/utilities/math/quaternion.py +++ b/models_converter/utilities/math/quaternion.py @@ -7,3 +7,6 @@ def __init__(self, x: float = 0, y: float = 0, z: float = 0, w: float = 1): def clone(self): return Quaternion(self.x, self.y, self.z, self.w) + + def __repr__(self): + return f'({self.x:.2f}, {self.y:.2f}, {self.z:.2f}, {self.w:.2f})' diff --git a/models_converter/utilities/math/vector3.py b/models_converter/utilities/math/vector3.py index 335ce98..ba149cf 100644 --- a/models_converter/utilities/math/vector3.py +++ b/models_converter/utilities/math/vector3.py @@ -6,3 +6,6 @@ def __init__(self, x: float = 0, y: float = 0, z: float = 0): def clone(self): return Vector3(self.x, self.y, self.z) + + def __repr__(self): + return f'({self.x:.2f}, {self.y:.2f}, {self.z:.2f})' diff --git a/models_converter/utilities/matrix/__init__.py b/models_converter/utilities/matrix/__init__.py index 4589a76..76da29d 100644 --- a/models_converter/utilities/matrix/__init__.py +++ b/models_converter/utilities/matrix/__init__.py @@ -53,7 +53,7 @@ def __str__(self): return str(self.matrix) @staticmethod - def get_identity_matrix(size: tuple): + def get_identity_matrix(size: tuple[int, int]): matrix = [] for y in range(size[1]): diff --git a/models_converter/utilities/reader.py b/models_converter/utilities/reader.py index 467891f..2450a45 100644 --- a/models_converter/utilities/reader.py +++ b/models_converter/utilities/reader.py @@ -1,5 +1,8 @@ +import typing + + class Reader: - def __init__(self, buffer: bytes, endian: str = 'big'): + def __init__(self, buffer: bytes, endian: typing.Literal['big', 'little'] = 'big'): self.buffer = buffer self.endian = endian self.i = 0 diff --git a/models_converter/utils/__pycache__/__init__.cpython-38.pyc b/models_converter/utils/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 7440424aaf309e8333335d0990fd06ec2ba82a14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmWIL<>g`kf-hn=@#aAKF^Gc4QqWrAXg`kf-hn=@#aAKF^Gc+sn>)?` diff --git a/models_converter/utils/__pycache__/reader.cpython-39.pyc b/models_converter/utils/__pycache__/reader.cpython-39.pyc deleted file mode 100644 index d904bf033f0e76b8b7f5d3a8e9b73ab56a6bd941..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3923 zcma)9OLH4V5T4nWWJz{pS@9$92MG}n6WhTtDJm5b2vDR%NT8_NGN@$F*jXa4lG$}C zajlAn!;J&QIfvw!zroMgD<_?~aS30~?pl&9QCZdWPEXHH_t)LiGir{H7Z{$a_HN_P zB4d9O)Bl)YuA>DNkk6Q4f;xM?0l$#ba;0~oTXT==2awiq)J0s45J1nQQ_P4}2a5FOR&dM=&P8QrFvgjU_W%rn@xX0zBdqPgRCuP+=B@er& z#R3po8uNMaHV~cxM+h&78rC_R>Uc-I3+^rP9z4DnJ9lMMNEMrF-e%2+owe% zh1c}gDP-A0eFN<#S}+X~@rX58WPox(eM2xTYSgQ=4ZdP3 z581!BZ(d#fHjpY<{a9>y{_35l>-U>#>yB#wDA&VabxB-YZ~Kp>3Z+`zY75y48ohz- z&}#*&N~4&6_%zNp8lLZkjRu(-I3SEW`0X4@kj`iFE>a4{#!gH6o8kS~Q8L_C{yKe^ zJp_R4*N`uu6R{tu0W2DyVZRx3tOEyFn!f`7G1>3Dq zgHOm94O@hPW~D|?^9f_8m>Ny80n2FBJeN%gJo9|5aDJK^$ zUw%`iYBo_eLyG5!?9(`w;$FUdu1{gPPvJp*lueXi8suN-5XhIT!$SmRSBFiY`qO@U zVF)7$q%h~0u#nG-!uAc}bj*-K>xdl9qmxDCk=+)?a>=(HTOiM8SjQ1&Cx!u&0i7#-d!5hla6TUS(HU2OK3&C;#XVojleQ?WY`e zS|KVRc}jJ47)ZLOK=kg3`9k+K9=&(E6rJu*Al@$+^L_=1`6XQeF0AduPLI zwPMrr!&*@X*>#!$>RjkLkT=y+*9yE%-_6S>58J-P`1n@S54|90HrASfbW1(f!?cWx z&Bx7_Yf-%&n!!4fw_5FHcmZ|>=Rp|H<5w}Nd>Xqlk6gB}GfSWr&!bo71p~R=A8ozB zn`ReMx=TA(r}VJ&QfDE(fi^0Wmloetm#UE>+M2@@#aSLNEq>akt{)llc!)OIVLFeg z+$VWVd7_U-@AYex5Z;UN!tzkvx^~oQV=gRz-lx64W~AabMN7qk@^Q3h--GnaK4trB z#KQ6cUH^-fA-jP!^WzApI^F9UX?=w9M0!BEiRs$WgH=(@V|-*-)%%cs-KS_@K>rUU z6}lv#_n4X>EmU}i22g{h_^o_1LYEdEz_jPkgStj0YCbKvE8q30%I-UvY_uk-=UJLQ zuvM8Z|UxGW~6`QH7u3Kd-|)CWW;R0-*wxc5RuHylU;DrZzS2hQ9?K41qO z^we}plM``YLkJSxT+ztTRI@_aYCVQDLtDHHm{QHtcpY&gfOzQ}+tAlJx;FdGE!k+q zg+>FH?CloyOO3{(?Pe>L*mQvlVlxPpq8+E`JkW8`7bB%F1V?D>7)Wey(7~ZD(7;(D z?-IF4gbIziM1(eDl2?o9#nu{NwIKadT-3yUCuq-S#&VsqUqfQpY5TPd$mx z6lIV diff --git a/models_converter/utils/__pycache__/writer.cpython-39.pyc b/models_converter/utils/__pycache__/writer.cpython-39.pyc deleted file mode 100644 index 70b909f4c8d630e979704f8a4eb7d15887fa35e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4002 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 I;Q0OhFF&Ax-v9sr diff --git a/models_converter/utils/matrix/__pycache__/__init__.cpython-39.pyc b/models_converter/utils/matrix/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 4941021e3ba1f83309634ccdeaf4b68f9a6654c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3468 zcma)9OK%)S5bo}I?0Eg&n1nSU7!(A{!-OOP6h#(~01}8OC=ZF1!?4;O+vChW(laKu zH7lX41wnxWXFwv{%n5{oI1u;?>+4)O<;;x(;j8W)d)N5DTK%l*sp_iws>`+U@gl?T zp(n5S(_@VNNsGasg~h9A@hrNxnPif0p>;)-Ei);;=T$DPi>zYTK_B|rSiOoCucHeX z_*hj)F2xqBTGEm>MmuoY#{yS6-*D;5!j@evNKcM{;>n^M#W)fagHbsK`na6fvb(%G zCMV?-DC2TkmM~7pLvjY=q29BSl0x)rr|(c(+! zBqavZ35@io~9n~N7V>MJ$XxS(3AK|P5VPs!)&t>(vpN&>amXvrXo{k@H~ zB#h!kBXH68!)BQH{(O5Q^YR5L5I>BLaa(xYLn{iK+Yel)$lZTjXLHinbUr^(rz82# z!=!-r23q_Bx&($>`LyK}wYdcXI6-s$e#oa_Cx za~tF@=6Zkh?t<$sX!k(<1+#lQx9`(;wz=`9t6Z$~JR7;R(GK2Js--eV)tbve=GNNn zpeYqaXvI@xyUb}ND}lySBKOghVt+%4JMl<7a#zP$;rXyfy zMW4>$E53gi>%Lo0kV8e0+k8y7qZr4;=Jc*x^D`B$rg3b@zjPI9+P@!RLS9N(LePNU zw0BU*Y?*zk-X&r~eq6*UfF6-P~C z*fXe_qNTQ-t8J$>Rwdd!gf1&IgBxThHA7p3paJ_7U>`EhQ`n9t&>`k|3GFbSC6i3- znS|JevIxl}FKa`Pb#W;K>(|JSbUlpXTU)EYDdx`p%@=^`5qu-6#j)=b-kP;W;QI#L z+$g_KKQO7Ft$hDQd_UGO;{)(BwEt*Dd|E4tfgFAp5sv38BD3N|X_4wtP!BK&=)Qk? zK;E7aGzmdrfjT6W0>VMlQYoV%C_a zY9YruIvkFt9CJ(0r&1FL_sQrQqZMHSf!tqlwMGVDe8c#iuV;lI3K~eU`!1VtxV!&sq|BC>p;Q&k@n_aESgN96!e04kJAs+0BBQmvk}R6m7i^C(L2 z2SQ?eDQrqS80O4dV9S9a3^1M`V9fLlG{ih)8VW*+6KeXAP1r4D<}UvnAxSf1hz|dZ zeTK(V;xXOw@RaHd(&e|f&!vq$&3g_3LpzCOzM-iD8GA@F#Rxfu;2$4Hw~Ro%dh~M! zNdl6ni1A1V3c@b`N?a$HilJ|i_5^PI4Jv;0H%c97v5JgUX17?GnUf~EC^>D5Ow6Cq z_9dVpF%N*+6I9qUx>$;qpqNg<?#tco<&D;)N|-IkN+Pz2U_7Gj%%=JD~xGq!+Hbvukg%C z*4j~^o+f$2X-w;JCD#MdO6kJnARV{!SOU-H<+~BeWW{{od1BLc8`-tZ``C+VkMSa( z5VOeEn^XHQ_Mb~oNN9`_SdnK?>ygX*4-09#ZRDdi4ff}YY!rKeLakM6CLe$|rpTb` zXtUIpJiss{o8<1wrc;Dn^%y4Rh6kl!FUQ`8VB4SbbJ~!UJXrM&U&1L=s_YIjFx9ds zsZLVmdIySN6#dbn`t}jzm{l7&1#RG=eFC3A_Bcsuo}lg&b!F;El%mIIpNq3XNF`?+ z)If{zuLSOm+L8W5Y408FGo7!L)KReL`lKb-B0PiHnD1X-t3~}saTH{Agm|p5nPkpV z)Iu$-bvQ<($B8r+C$%K3H-coPB^8B{&h3hxbECPwgI1qjv;n1cgldrFKGMYz^qujh zOGVG}EWxKHyiw2d9M3WphE*MD=!*X2`pJBHYW-9`J-vP!FRW(GP+8O>lne)n%)GOx O%W>-klC51laqVA@0M~&4 diff --git a/models_converter/utils/matrix/__pycache__/matrix2x2.cpython-39.pyc b/models_converter/utils/matrix/__pycache__/matrix2x2.cpython-39.pyc deleted file mode 100644 index 9ec91e1f8e0716a1543017033347d26a373d0ddd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 779 zcmY*Xv2N5r5S`hzPbTzs30X=dl`*l*sfhT_coW3y_dP3hF#!Ma(NF6(97ZZws{ zZ~KfL23fDAcTsF#EA+C!66E=tk@~79H(77eQ9$l zS0s)c>9JRSj<1}0Ktddmc+Bi(-4r1X+}LP8&o{FkkJsO9ykc>Nz*xHW()~P3$R{Mz z!vwMhGhPDliBCf^pdCulHbP^-I*gJh#Am+whWKW{j#>g^pHaA_Nz?pOFX3AU zt%DX2Tag}wE(`V)lsb`Kw+M+popcO7Q@1wfE( zD0orMG0m8xPR4L$KB2-AcHa<=$oGxD z;hS&icm}+t^ya3#v(jt9+MIB*wFN=N%m?Fy)X2Cfioy`HimfWnL_zTt$zjW_jWc*{ z!f{v5a7RyJ!v}Krn~XacLT@_j2T8yOTagzf%~EQ{LD)~7Jue&u%kFY!wKA(cv07QQ zqTW}Di#3({MF#JIgudS7Atr4;^S>46{(!G05O5xi7-r^Q>6 zUJ&>N@y=x#WD`u}U(PbfCYX5VvkazyCYX3DO8iRnO6q+kiYR$ek~oJ5zOrQV5-Z9F zUgDBXGOi{pkqv!JkLhFbYGYgjZ(Vz_jVF=o$k>(#DN_!vfV3kSt0M7!X+yCW}D5&0E3RzCGwcnWcR uH;#WJ_%UNN9O_(=9?YFrTLgb*>L?|g=^Or(DRmyG0v_U-EYlTef&Bt<3b8)` diff --git a/models_converter/utils/matrix/__pycache__/matrix4x4.cpython-39.pyc b/models_converter/utils/matrix/__pycache__/matrix4x4.cpython-39.pyc deleted file mode 100644 index d41f0ae27a73c6c814a7ee5af332d9b4a19cee47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4706 zcmeHL%Z?jG6s_u)A7jt^oi`AO5eb>G^HdZCWf2L9QA9)*)<`YO)ea6G&rscy%#7MB z$Vgei7tA7&STH}q2O!ogdI_sYd;mfT;-2d6a@*rXEXa;-_4U0~r>g7Rx^=tCjdHoj z=(=pT}-C@EM);<%KXFf6Omay-! zit~a~lM0UtpEUy6-l^_X$#7z$NmT=vnOOd}q`kvlL%UKIANdfszH@qmjlQ6j65SY>+QlHVgR={Cnk^khz9nBuynP!Kh2V zRoz+L-*??Z_SgN}*Vn)BJ?XE1A~xFH^}D;xFB@{>uH1a+H3NUWD$X}I zyN^5>cyfJXQ+OTU?H6nZ?T)`54gjQ98B45ZXv-wa)Rsk-r7S|BZO_&g+Bw=nyPUSrF0U*qSM3Vg zLc5~2&~8jyXjf7eX{udWTWB|~Ewr1^7TQe)Jaocbm~WY3F&qm^^gA9-gp)0^WeWQ{ z-kTzXogfMcqC9|@RtQJ+QJ`MXN11x*_c4%Th&@sYqdmM)woqR#jUz2Zq-e3RgJ4^Tvg+$8duY}n#R>M zu12_A%wr{0R#T;#Dz(TYUUD2Xm6(8rkS@{Ve@=q&@%T1g_iOMx?pJx|&02|1;x_i? z`b#R~)qz!YhF*+is8-b;&5Me`|5sJK?%v|6qIGA4W@yoR8@;;c2*DG?=yA*|grWKv zy}B0?1Z9lZtnS6kIMQr0m}QnS<4Cj3V3t|VjKij!g^V?rWsYUW0R*xQ5dAE3e8e~s zza-i2#E5Yuen}7~M~ox!OM*BxVjM}oKIJ3q1tYw9B$ZO%ek_$>MLQ>6= zvX+%RrsVOY=7h3NDtSuD({T;8VlJ(=XXbN;P0&3iCutp4tbQ(3`5`K|+Fe24)mEb! zY|6?Rl@jFwVdWyk62vmZF^J<3Cm?7S#^fo8(-2tc@*RkGA>M;H3-Lb02N2kW$O^=V zB!DrBsK~1=@GFsE_ano@&s)ndvjzKibDVp=I2Ezo&tB zsIeWvkLbV81wC0UtLFqPbg<>2B}4mpoC(&{ZbD9zE94Z3LreW+1a=#8mPEyn*k?=l zuab+G&jl2GOd{et5#NpYo~j;>ztYxr;HG=%p9t>b23sZ>p)fr*5h$X6VS0Ktxxp4z#tnAj26#YTgvb&` z&{UiZ;T0fOA*v9lAIW%Y({JM#@iPH5<08jBig616w^TzAq?>5b2QJk>RF#nn5~^l~ zs_TrDlAT&(hIg_cVG*J3AX7`lZt?h*^x93 zEIOEQidQopJ!SWyIY11m(NUDfd)+kLhR0lb;PqqiH)`f%>&Og)s~hyksfGMp^6^%30b$^W> znnT2iuG;foBABM02GU6CY2_c(A_>ZKM$yFC(Rt!G(m{kFDq-y@81_>prPs!QK1fj-O zCUNHPHa0vu^A}y0&fVK|qIaW`>wdl6=)^VZjIH($@;r4O*}?W!$BXP%XOsRJs8NxG z=haXnOSGGTyaxLQ1QJtJPW}mKdYz8zX2y=Flj`^aPa^Prkk9jyldt8!nJkUtE{>N} q*2P~Hg?MW~|J%ezJSH&@aZ*M;&2)}_qx_AS)J=HS$6HFgWc~x>hS!q- diff --git a/setup.py b/setup.py index be7912e..7958b50 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setuptools.setup( name='3d-converter', - version='0.8.8', + version='0.9.0', author='Vorono4ka', author_email='crowo4ka@gmail.com', description='Python 3D Models Converter', @@ -20,5 +20,5 @@ 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Operating System :: OS Independent', ], - python_requires='>=3.7', + python_requires='>=3.9', )