diff --git a/3d-converter-0.8.0/3d_converter.egg-info/PKG-INFO b/3d-converter-0.8.0/3d_converter.egg-info/PKG-INFO deleted file mode 100644 index 1cd65d1..0000000 --- a/3d-converter-0.8.0/3d_converter.egg-info/PKG-INFO +++ /dev/null @@ -1,25 +0,0 @@ -Metadata-Version: 2.1 -Name: 3d-converter -Version: 0.8.0 -Summary: Python 3D Models Converter -Home-page: https://github.com/vorono4ka/3d-converter -Author: Vorono4ka -Author-email: crowo4ka@gmail.com -License: GPLv3 -Description: ## `Python 3D Models Converter` - - **Version**: 0.8.0 - - ### Thanks a lot for motivating [AMIRMISTIK]! - - ### **THIS IS NOT RELEASE VERSION!** - - - [AMIRMISTIK]: https://www.youtube.com/channel/UCksd1LeoySP5St6dKlv6mvQ - -Platform: UNKNOWN -Classifier: Programming Language :: Python :: 3 -Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) -Classifier: Operating System :: OS Independent -Requires-Python: >=3.7 -Description-Content-Type: text/markdown diff --git a/3d-converter-0.8.0/3d_converter.egg-info/SOURCES.txt b/3d-converter-0.8.0/3d_converter.egg-info/SOURCES.txt deleted file mode 100644 index 922e012..0000000 --- a/3d-converter-0.8.0/3d_converter.egg-info/SOURCES.txt +++ /dev/null @@ -1,29 +0,0 @@ -README.md -setup.py -3d_converter.egg-info/PKG-INFO -3d_converter.egg-info/SOURCES.txt -3d_converter.egg-info/dependency_links.txt -3d_converter.egg-info/top_level.txt -models_converter/__init__.py -models_converter/formats/__init__.py -models_converter/formats/dae.py -models_converter/formats/gltf.py -models_converter/formats/obj.py -models_converter/formats/scw/__init__.py -models_converter/formats/scw/parser.py -models_converter/formats/scw/writer.py -models_converter/formats/scw/chunks/__init__.py -models_converter/formats/scw/chunks/came.py -models_converter/formats/scw/chunks/chunk.py -models_converter/formats/scw/chunks/geom.py -models_converter/formats/scw/chunks/head.py -models_converter/formats/scw/chunks/mate.py -models_converter/formats/scw/chunks/node.py -models_converter/formats/scw/chunks/wend.py -models_converter/utils/__init__.py -models_converter/utils/reader.py -models_converter/utils/writer.py -models_converter/utils/matrix/__init__.py -models_converter/utils/matrix/matrix2x2.py -models_converter/utils/matrix/matrix3x3.py -models_converter/utils/matrix/matrix4x4.py \ No newline at end of file diff --git a/3d-converter-0.8.0/3d_converter.egg-info/dependency_links.txt b/3d-converter-0.8.0/3d_converter.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/3d-converter-0.8.0/3d_converter.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/3d-converter-0.8.0/3d_converter.egg-info/top_level.txt b/3d-converter-0.8.0/3d_converter.egg-info/top_level.txt deleted file mode 100644 index da3db61..0000000 --- a/3d-converter-0.8.0/3d_converter.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -models_converter diff --git a/3d-converter-0.8.0/README.md b/3d-converter-0.8.0/README.md deleted file mode 100644 index 7ec19cc..0000000 --- a/3d-converter-0.8.0/README.md +++ /dev/null @@ -1,10 +0,0 @@ -## `Python 3D Models Converter` - -**Version**: 0.8.0 - -### Thanks a lot for motivating [AMIRMISTIK]! - -### **THIS IS NOT RELEASE VERSION!** - - -[AMIRMISTIK]: https://www.youtube.com/channel/UCksd1LeoySP5St6dKlv6mvQ diff --git a/3d-converter-0.8.0/models_converter/__init__.py b/3d-converter-0.8.0/models_converter/__init__.py deleted file mode 100644 index f5dd35a..0000000 --- a/3d-converter-0.8.0/models_converter/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -__all__ = [ - 'formats', - 'chunks', - 'utils' -] diff --git a/3d-converter-0.8.0/models_converter/formats/__init__.py b/3d-converter-0.8.0/models_converter/formats/__init__.py deleted file mode 100644 index 3e518f9..0000000 --- a/3d-converter-0.8.0/models_converter/formats/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -__all__ = [ - 'scw', - 'dae', - 'obj', - 'gltf' -] diff --git a/3d-converter-0.8.0/models_converter/formats/dae.py b/3d-converter-0.8.0/models_converter/formats/dae.py deleted file mode 100644 index 2c2dbe1..0000000 --- a/3d-converter-0.8.0/models_converter/formats/dae.py +++ /dev/null @@ -1,786 +0,0 @@ -from xml.etree.ElementTree import * - -from ..utils.matrix.matrix4x4 import Matrix4x4 - - -def _(*args): - print('[ScwUtils]', end=' ') - for arg in args: - print(arg, end=' ') - print() - - -class Collada: - def __init__(self): - self.version = '1.4.1' - self.xml_namespace = 'http://www.collada.org/2005/11/COLLADASchema' - self.collada = Element('COLLADA', version=self.version, xmlns=self.xml_namespace) - - @staticmethod - def write_source(parent, - source_id: str, - array_tag: str, - array_data: list, - stride: int, - params: list): - source = SubElement(parent, 'source', id=source_id) - - array = SubElement(source, array_tag) - array.attrib = {'id': f'{source_id}-array', - 'count': f'{len(array_data) * stride}'} - - technique_common = SubElement(source, 'technique_common') - accessor = SubElement(technique_common, 'accessor') - accessor.attrib = {'source': f'#{source_id}-array', - 'count': f'{len(array_data)}', - 'stride': f'{stride}'} - - for param_data in params: - param = SubElement(accessor, 'param') - param.attrib = param_data - - array.text = ' '.join(array_data) - - @staticmethod - def write_input(parent, - semantic: str, - source_id: str, - offset: int = None): - attributes = { - 'semantic': semantic, - 'source': f'#{source_id}' - } - - if offset is not None: - attributes['offset'] = f'{offset}' - - _input = SubElement(parent, 'input') - _input.attrib = attributes - - -class Writer: - def __init__(self): - self.writen = '' - - def write(self, data: dict): - dae = Collada() - asset = SubElement(dae.collada, 'asset') - - # - library_materials = SubElement(dae.collada, 'library_materials') - library_effects = SubElement(dae.collada, 'library_effects') - # library_images = SubElement(dae.collada, 'library_images') - library_geometries = SubElement(dae.collada, 'library_geometries') - library_controllers = SubElement(dae.collada, 'library_controllers') - library_animations = SubElement(dae.collada, 'library_animations') - # library_cameras = SubElement(dae.collada, 'library_cameras') - library_visual_scenes = SubElement(dae.collada, 'library_visual_scenes') - # - - # - contributor = SubElement(asset, 'contributor') - SubElement(contributor, 'author').text = 'Vorono4ka' - SubElement(contributor, 'authoring_tool').text = 'models_converter (https://github.com/vorono4ka/3d-converter)' - - if 'version' in data['header']: - SubElement(contributor, 'comments').text = 'SCW Version: ' + str(data['header']['version']) - # - - # - for material_data in data['materials']: - material_name = material_data['name'] - - SubElement(library_materials, 'material', id=material_name) - effect_name = f'{material_name}-effect' - - material = SubElement(library_materials, 'material', id=material_name) - SubElement(material, 'instance_effect', url=f'#{effect_name}') - - effect = SubElement(library_effects, 'effect', id=effect_name) - profile = SubElement(effect, 'profile_COMMON') - technique = SubElement(profile, 'technique', sid='common') - - ambient_data = material_data['effect']['ambient'] - diffuse_data = material_data['effect']['diffuse'] - emission_data = material_data['effect']['emission'] - specular_data = material_data['effect']['specular'] - - phong = SubElement(technique, 'phong') - - if type(ambient_data) is list: - ambient = SubElement(phong, 'ambient') - ambient_data[3] /= 255 - ambient_data = [str(item) for item in ambient_data] - SubElement(ambient, 'color').text = ' '.join(ambient_data) - # else: - # SubElement(ambient, 'texture', texture=ambient_data, texcoord='CHANNEL0') - - if type(diffuse_data) is list: - diffuse = SubElement(phong, 'diffuse') - diffuse_data[3] /= 255 - diffuse_data = [str(item) for item in diffuse_data] - SubElement(diffuse, 'color').text = ' '.join(diffuse_data) - # else: - # SubElement(diffuse, 'texture', texture=diffuse_data, texcoord='CHANNEL0') - - if type(emission_data) is list: - emission = SubElement(phong, 'emission') - emission_data[3] /= 255 - emission_data = [str(item) for item in emission_data] - SubElement(emission, 'color').text = ' '.join(emission_data) - # else: - # SubElement(emission, 'texture', texture=emission_data, texcoord='CHANNEL0') - - if type(specular_data) is list: - specular = SubElement(phong, 'specular') - specular_data[3] /= 255 - specular_data = [str(item) for item in specular_data] - SubElement(specular, 'color').text = ' '.join(specular_data) - # else: - # SubElement(specular, 'texture', texture=specular_data, texcoord='CHANNEL0') - # - - # - for geometry_data in data['geometries']: - geometry_name = geometry_data['name'] - - geometry = SubElement(library_geometries, 'geometry', id=f'{geometry_name}-geom') - mesh = SubElement(geometry, 'mesh') - - # - for vertex_data in geometry_data['vertices']: - params = [] - - vertex_type = vertex_data['type'] - vertex_name = vertex_data['name'] - vertex = vertex_data['vertex'] - stride = len(vertex[0]) - - if vertex_type == 'VERTEX': - vertex_type = 'POSITION' - - source_name = f'{geometry_name}-{vertex_name}' - - if vertex_type in ['POSITION', 'NORMAL']: - params.append({'name': 'X', 'type': 'float'}) - params.append({'name': 'Y', 'type': 'float'}) - params.append({'name': 'Z', 'type': 'float'}) - elif vertex_type in ['TEXCOORD']: - params.append({'name': 'S', 'type': 'float'}) - params.append({'name': 'T', 'type': 'float'}) - - dae.write_source( - mesh, - source_name, - 'float_array', - [' '.join([str(sub_item * vertex_data['scale']) for sub_item in item]) for item in vertex], - stride, - params - ) - - if vertex_type == 'POSITION': - vertices = SubElement(mesh, 'vertices', id=f'{source_name}-vertices') - dae.write_input(vertices, 'POSITION', source_name) - # - - # - for material in geometry_data['materials']: - polygons_data = material['polygons'] - material_name = material['name'] - - triangles = SubElement(mesh, 'triangles', - count=f'{len(polygons_data)}', - material=material_name) - for _input in material['inputs']: - input_offset = _input['offset'] - input_name = _input['name'] - input_type = _input['type'] - - if input_type == 'POSITION': - input_type = 'VERTEX' - source_id = f'{geometry_name}-{input_name}' - if input_type == 'VERTEX': - source_id = f'{source_id}-vertices' - - dae.write_input(triangles, input_type, source_id, input_offset) - polygons = SubElement(triangles, 'p') - - formatted_polygons_data = [] - for polygon in polygons_data: - for point in polygon: - for vertex in point: - formatted_polygons_data.append(str(vertex)) - - polygons.text = ' '.join(formatted_polygons_data) - # - - # - if geometry_data['have_bind_matrix']: - joints_matrices = [] - joints_names = [] - - controller = SubElement(library_controllers, 'controller', id=f'{geometry_name}-cont') - skin = SubElement(controller, 'skin', source=f'#{geometry_name}-geom') - - if 'bind_matrix' in geometry_data: - bind_matrix_data = [str(value) for value in geometry_data['bind_matrix']] - SubElement(skin, 'bind_shape_matrix').text = ' '.join(bind_matrix_data) - - for joint in geometry_data['joints']: - joints_names.append(joint['name']) - joint_matrix = [str(value) for value in joint['matrix']] - joint_matrix = ' '.join(joint_matrix) - joints_matrices.append(joint_matrix) - - joints_names_source_id = f'{geometry_name}-joints' - joints_matrices_source_id = f'{geometry_name}-joints-bind-matrices' - weights_source_id = f'{geometry_name}-weights' - - dae.write_source( - skin, - joints_names_source_id, - 'Name_array', - joints_names, - 1, - [{'name': 'JOINT', 'type': 'name'}] - ) - - dae.write_source( - skin, - joints_matrices_source_id, - 'float_array', - joints_matrices, - 16, - [{'name': 'TRANSFORM', 'type': 'float4x4'}] - ) - - dae.write_source( - skin, - weights_source_id, - 'float_array', - [str(value) for value in geometry_data['weights']['weights']], - 1, - [{'name': 'WEIGHT', 'type': 'float'}] - ) - - joints = SubElement(skin, 'joints') - dae.write_input(joints, 'JOINT', joints_names_source_id) - dae.write_input(joints, 'INV_BIND_MATRIX', joints_matrices_source_id) - - vertex_weights_data = [str(value) for value in geometry_data['weights']['vertex_weights']] - vcount = [str(value) for value in geometry_data['weights']['vcount']] - - vertex_weights = SubElement(skin, 'vertex_weights', count=f'{len(vcount)}') - dae.write_input(vertex_weights, 'JOINT', joints_names_source_id, 0) - dae.write_input(vertex_weights, 'WEIGHT', weights_source_id, 1) - - SubElement(vertex_weights, 'vcount').text = ' '.join(vcount) - SubElement(vertex_weights, 'v').text = ' '.join(vertex_weights_data) - # - # - - # - - visual_scene = SubElement(library_visual_scenes, 'visual_scene', - id='3dConverterScene', - name='3d-Converter Scene') - - for node_data in data['nodes']: - parent_name = node_data['parent'] - parent = visual_scene - if parent_name != '': - parent = visual_scene.find(f'.//*[@id="{parent_name}"]') - if parent is None: - parent = visual_scene - node_name = node_data['name'] - - node = SubElement(parent, 'node', id=node_data['name']) - - for instance in node_data['instances']: - instance_type = instance['instance_type'] - instance_name = instance['instance_name'] - bind_material = None - - if instance_type == 'CONT': - instance_controller = SubElement(node, 'instance_controller', url=f'#{instance_name}-cont') - bind_material = SubElement(instance_controller, 'bind_material') - elif instance_type == 'GEOM': - instance_controller = SubElement(node, 'instance_geometry', url=f'#{instance_name}-geom') - bind_material = SubElement(instance_controller, 'bind_material') - - if instance_type in ['GEOM', 'CONT']: - technique_common = SubElement(bind_material, 'technique_common') - for bind in instance['binds']: - symbol = bind['symbol'] - target = bind['target'] - - SubElement(technique_common, 'instance_material', - symbol=symbol, - target=f'#{target}') - else: - if parent_name != '' and len(node_data['instances']) == 0: - node.attrib['type'] = 'JOINT' - - # - frame_rate = data['header']['frame_rate'] - time_input = [] - matrix_output = [] - # - - frames = node_data['frames'] - for frame in frames: - frame_id = frame['frame_id'] - matrix = Matrix4x4(size=(4, 4)) - - time_input.append(str(frame_id/frame_rate)) - - position_xyz = (frame['position']['x'], frame['position']['y'], frame['position']['z']) - rotation_xyz = (frame['rotation']['x'], frame['rotation']['y'], frame['rotation']['z']) - scale_xyz = (frame['scale']['x'], frame['scale']['y'], frame['scale']['z']) - - matrix.put_rotation(rotation_xyz, frame['rotation']['w']) - matrix.put_position(position_xyz) - matrix.put_scale(scale_xyz) - - matrix = matrix.translation_matrix @ matrix.rotation_matrix @ matrix.scale_matrix - matrix_values = [] - for row in matrix.matrix: - for column in row: - matrix_values.append(str(column)) - - if node_data['frames'].index(frame) == 0: - SubElement(node, 'matrix', sid='transform').text = ' '.join(matrix_values) - matrix_output.append(' '.join(matrix_values)) - - if len(frames) > 1: - animation = SubElement(library_animations, 'animation', id=node_name) - - dae.write_source( - animation, - f'{node_name}-time-input', - 'float_array', - time_input, - 1, - [{'name': 'TIME', 'type': 'float'}] - ) - dae.write_source( - animation, - f'{node_name}-matrix-output', - 'float_array', - matrix_output, - 16, - [{'name': 'TRANSFORM', 'type': 'float4x4'}] - ) - dae.write_source( - animation, - f'{node_name}-interpolation', - 'Name_array', - ['LINEAR'] * len(frames), - 1, - [{'name': 'INTERPOLATION', 'type': 'name'}] - ) - - sampler = SubElement(animation, 'sampler', id=f'{node_name}-sampler') - - dae.write_input( - sampler, - 'INPUT', - f'{node_name}-time-input' - ) - - dae.write_input( - sampler, - 'OUTPUT', - f'{node_name}-matrix-output' - ) - - dae.write_input( - sampler, - 'INTERPOLATION', - f'{node_name}-interpolation' - ) - - SubElement(animation, 'channel', - source=f'#{node_name}-sampler', - target=f'{node_name}/transform') - - scene = SubElement(dae.collada, 'scene') - SubElement(scene, 'instance_visual_scene', - url='#3dConverterScene', - name='3d-Converter Scene') - - # - - self.writen = tostring(dae.collada, xml_declaration=True).decode() - - -class Parser: - def node(self, nodes): - nodes_list = [] - for node in nodes: - instance_geometry = node.findall('collada:instance_geometry', self.namespaces) - instance_controller = node.findall('collada:instance_controller', self.namespaces) - - instances = [*instance_geometry, *instance_controller] - - children = self.node(node.findall('collada:node', self.namespaces)) - - if 'name' not in node.attrib: - node.attrib['name'] = node.attrib['id'] - - node_data = { - 'name': node.attrib['name'], - 'instances': [] - } - - for instance in instances: - instance_data = {} - binds = [] - - bind_material = instance.find('collada:bind_material', self.namespaces) - technique_common = bind_material[0] - - for instance_material in technique_common: - binds.append({ - 'symbol': instance_material.attrib['symbol'], - 'target': instance_material.attrib['target'][1:] - }) - - if instance_geometry: - instance_data['instance_type'] = 'GEOM' - - geometry_url = instance.attrib['url'] - instance_data['instance_name'] = geometry_url[1:] - elif instance_controller: - instance_data['instance_type'] = 'CONT' - - controller_url = instance.attrib['url'] - instance_data['instance_name'] = controller_url[1:] - - instance_data['binds'] = binds - node_data['instances'].append(instance_data) - - matrix = node.findall('collada:matrix', self.namespaces) - if matrix: - matrix_data = matrix[0].text.split() - matrix_data = [[float(value) for value in matrix_data[x:x + 4]] for x in range(0, len(matrix_data), 4)] - - node_data['matrix'] = matrix_data - - node_data['children'] = children - - nodes_list.append(node_data) - - return nodes_list - - def fix_nodes_list(self, nodes, parent: str = ''): - for node in nodes: - node_data = { - 'name': node['name'], - 'parent': parent, - 'instances': node['instances'] - } - - if len(node_data['instances']) == 0: - node_data['frames_settings'] = [0, 0, 0, 0, 0, 0, 0, 0] - node_data['frames'] = [] - - if 'matrix' in node: - matrix = Matrix4x4(matrix=node['matrix']) - - # scale = matrix.get_scale() - position = matrix.get_position() - - node_data['frames'] = [ - { - 'frame_id': 0, - 'rotation': {'x': 0, 'y': 0, 'z': 0, 'w': 0}, - 'position': position, - 'scale': {'x': 1, 'y': 1, 'z': 1} - } - ] - else: - node_data['frames'] = [] - - # node_data['frames'] = node['frames'] - self.parsed['nodes'].append(node_data) - self.fix_nodes_list(node['children'], node['name']) - - def __init__(self, file_data): - self.parsed = {'header': {'version': 2, - 'frame_rate': 30, - 'materials_file': 'sc3d/character_materials.scw'}, - 'materials': [], - 'geometries': [], - 'cameras': [], - 'nodes': []} - - self.geometry_info = {} - - root = fromstring(file_data) - - self.namespaces = { - 'collada': 'http://www.collada.org/2005/11/COLLADASchema' - } - - # - self.library_materials = root.find('./collada:library_materials', self.namespaces) - self.library_effects = root.find('./collada:library_effects', self.namespaces) - - self.library_geometries = root.find('./collada:library_geometries', self.namespaces) - self.library_controllers = root.find('./collada:library_controllers', self.namespaces) - - self.instance_scene = root.find('./collada:scene', self.namespaces).find('collada:instance_visual_scene', - self.namespaces) - self.library_scenes = root.find('./collada:library_visual_scenes', self.namespaces) - # - - if self.library_materials is None: - self.library_materials = [] - - def parse(self): - for material in self.library_materials: - material_name = material.attrib['name'] - - instance_effect = material.find('collada:instance_effect', self.namespaces) - if instance_effect is not None: - effect_url = instance_effect.attrib['url'][1:] - effect = self.library_effects.find(f'collada:effect[@id="{effect_url}"]', self.namespaces) - - if effect is not None: - # profile = None - # for item in effect: - # if 'profile' in item.tag: - # profile = item - # technique = profile.find('collada:technique', self.namespaces) - # - # emission_data = None - # ambient_data = None - # diffuse_data = None - # - # emission = technique[0].find('collada:emission', self.namespaces) - # ambient = technique[0].find('collada:ambient', self.namespaces) - # diffuse = technique[0].find('collada:diffuse', self.namespaces) - # - # if 'color' in emission[0].tag: - # emission_data = [float(item) for item in emission[0].text.split()] - # emission_data[3] *= 255 - # elif 'texture' in emission[0].tag: - # # emission_data = emission[0].attrib['texture'] - # emission_data = '.' - # - # if 'color' in ambient[0].tag: - # ambient_data = [float(item) for item in ambient[0].text.split()] - # ambient_data[3] *= 255 - # elif 'texture' in ambient[0].tag: - # # ambient_data = ambient[0].attrib['texture'] - # ambient_data = '.' - # - # if 'color' in diffuse[0].tag: - # diffuse_data = [float(item) for item in diffuse[0].text.split()] - # diffuse_data[3] *= 255 - # elif 'texture' in diffuse[0].tag: - # # diffuse_data = diffuse[0].attrib['texture'] - # diffuse_data = '.' - - material_data = { - 'name': material_name, - 'effect': { - 'ambient': [0, 0, 0, 255], # ambient_data, - 'diffuse': '.', # diffuse_data, - 'specular': '.', - 'colorize': [255, 255, 255, 255], - 'emission': [0, 0, 0, 255], # emission_data, - 'lightmaps': { - 'diffuse': 'sc3d/diffuse_lightmap.png', - 'specular': 'sc3d/specular_lightmap.png' - }, - 'shader_define_flags': 3014 - } - } - - self.parsed['materials'].append(material_data) - - scene_url = self.instance_scene.attrib['url'][1:] - scene = self.library_scenes.find(f'collada:visual_scene[@id="{scene_url}"]', self.namespaces) - - nodes = self.node(scene.findall('collada:node', self.namespaces)) - self.fix_nodes_list(nodes) - self.parse_nodes() - - def parse_nodes(self): - nodes = self.parsed['nodes'] - for node_index in range(len(nodes)): - node = nodes[node_index] - for instance in node['instances']: - controller = None - geometry = None - - if instance['instance_type'] == 'CONT': - controller = self.library_controllers \ - .find(f'collada:controller[@id="{instance["instance_name"]}"]', self.namespaces) - - geometry_url = controller[0].attrib['source'][1:] - geometry = self.library_geometries \ - .find(f'collada:geometry[@id="{geometry_url}"]', self.namespaces) - elif instance['instance_type'] == 'GEOM': - geometry = self.library_geometries \ - .find(f'collada:geometry[@id="{instance["instance_name"]}"]', self.namespaces) - - if not ('name' in geometry.attrib): - geometry.attrib['name'] = geometry.attrib['id'] - - instance['instance_name'] = geometry.attrib['name'] - - for suffix in ['-skin', '-cont']: - instance['instance_name'] = instance['instance_name'].removesuffix(suffix) - for suffix in ['-mesh', '-geom']: - instance['instance_name'] = instance['instance_name'].removesuffix(suffix) - - self.parsed['nodes'][node_index] = node - - if geometry is not None: - self.geometry_info = {'name': '', - 'group': '', # node['parent'], - 'vertices': [], - 'have_bind_matrix': False, - 'materials': []} - if controller is not None: - self.parse_controller(controller) - - self.parse_geometry(geometry) - - def parse_controller(self, controller): - self.geometry_info['have_bind_matrix'] = True - - skin = controller[0] - - bind_shape_matrix = skin.find('collada:bind_shape_matrix', self.namespaces).text - bind_shape_matrix = [float(x) for x in bind_shape_matrix.split()] - - self.geometry_info['bind_matrix'] = bind_shape_matrix - - self.geometry_info['joints'] = [] - joints = skin.find('collada:joints', self.namespaces) - joint_inputs = joints.findall('collada:input', self.namespaces) - for _input in joint_inputs: - # semantic = _input.attrib['semantic'] - source_url = _input.attrib['source'] - source = skin.find(f'collada:source[@id="{source_url[1:]}"]', self.namespaces) - - accessor = source.find('collada:technique_common/collada:accessor', self.namespaces) - accessor_stride = int(accessor.attrib['stride']) - accessor_source_url = accessor.attrib['source'] - accessor_source = source.find(f'collada:*[@id="{accessor_source_url[1:]}"]', self.namespaces) - params = accessor.findall('collada:param', self.namespaces) - - for param in params: - param_name = param.attrib['name'] - # param_type = param.attrib['type'] - - if param_name == 'JOINT': - for name in accessor_source.text.split(): - self.geometry_info['joints'].append({ - 'name': name - }) - - if param_name == 'TRANSFORM': - for x in range(int(accessor_source.attrib['count']) // int(accessor_stride)): - matrix = [] - for y in accessor_source.text.split()[x * accessor_stride:(x + 1) * accessor_stride]: - matrix.append(float(y)) - self.geometry_info['joints'][x]['matrix'] = matrix - - self.geometry_info['weights'] = {} - vertex_weights = skin.find('collada:vertex_weights', self.namespaces) - vertex_weights_inputs = vertex_weights.findall('collada:input', self.namespaces) - for _input in vertex_weights_inputs: - semantic = _input.attrib['semantic'] - source_url = _input.attrib['source'] - source = skin.find(f'collada:source[@id="{source_url[1:]}"]', self.namespaces) - - if semantic == 'WEIGHT': - accessor = source.find('collada:technique_common/collada:accessor', self.namespaces) - accessor_source_url = accessor.attrib['source'] - accessor_source = source.find(f'collada:*[@id="{accessor_source_url[1:]}"]', self.namespaces) - - params = accessor.findall('collada:param', self.namespaces) - for param in params: - param_name = param.attrib['name'] - # param_type = param.attrib['type'] - - if param_name == 'WEIGHT': - weights = [float(x) for x in accessor_source.text.split()] - self.geometry_info['weights']['weights'] = weights - - vcount = vertex_weights.find('collada:vcount', self.namespaces).text - vcount = [int(x) for x in vcount.split()] - self.geometry_info['weights']['vcount'] = vcount - - v = vertex_weights.find('collada:v', self.namespaces).text - v = [int(x) for x in v.split()] - self.geometry_info['weights']['vertex_weights'] = v - - def parse_geometry(self, geometry): - name = geometry.attrib['name'] - - if name[-5:] in ['-mesh', '-geom']: - name = name[:-5] - - self.geometry_info['name'] = name - self.geometry_info['group'] = name - - mesh = geometry[0] - - triangles = mesh.findall('collada:triangles', self.namespaces) - if triangles: - pass - else: - triangles = mesh.findall('collada:polylist', self.namespaces) - inputs = triangles[0].findall('collada:input', self.namespaces) - for _input in inputs: - semantic = _input.attrib['semantic'] - source_link = _input.attrib['source'][1:] - source = mesh.find(f'*[@id="{source_link}"]') - - if semantic == 'VERTEX': - vertices_input = source[0] - semantic = vertices_input.attrib['semantic'] - source_link = vertices_input.attrib['source'][1:] - source = mesh.find(f'*[@id="{source_link}"]') - - float_array = source.find('collada:float_array', self.namespaces) - accessor = source.find('collada:technique_common/collada:accessor', self.namespaces) - - vertex_temp = [float(floating) for floating in float_array.text.split()] - - scale = max(max(vertex_temp), abs(min(vertex_temp))) - if scale < 1: - scale = 1 - if semantic == 'TEXCOORD': - vertex_temp[1::2] = [1 - x for x in vertex_temp[1::2]] - vertex_temp = [value / scale for value in vertex_temp] - - vertex = [] - for x in range(0, len(vertex_temp), len(accessor)): - vertex.append(vertex_temp[x: x + len(accessor)]) - - self.geometry_info['vertices'].append({'type': semantic, - 'index': len(self.geometry_info['vertices']), - 'scale': scale, - 'vertex': vertex}) - for triangle in triangles: - triangles_material = triangle.attrib['material'] - - p = triangle.find('collada:p', self.namespaces) - polygons_temp = [int(integer) for integer in p.text.split()] - - polygons = [ - [ - polygons_temp[polygon_index + point_index:polygon_index + point_index + 3] - for point_index in range(0, len(inputs) * 3, 3) - ] for polygon_index in range(0, len(polygons_temp), len(inputs) * 3) - ] - self.geometry_info['materials'].append({'name': triangles_material, - 'polygons': polygons}) - self.parsed['geometries'].append(self.geometry_info) diff --git a/3d-converter-0.8.0/models_converter/formats/gltf.py b/3d-converter-0.8.0/models_converter/formats/gltf.py deleted file mode 100644 index 281da6b..0000000 --- a/3d-converter-0.8.0/models_converter/formats/gltf.py +++ /dev/null @@ -1,779 +0,0 @@ -import json - -from models_converter.formats.dae import Writer -from models_converter.utils.reader import Reader - - -def to_camelcase(property_name: str): - words = property_name.split('_') - for word_index in range(len(words)): - word = words[word_index] - if word_index > 0: - word = list(word) - word[0] = word[0].upper() - - word = ''.join(word) - - words[word_index] = word - camelcase_name = ''.join(words) - return camelcase_name - - -def to_lowercase(property_name: str): - letters = list(property_name) - - for letter_index in range(len(letters)): - letter = letters[letter_index] - - if letter.isupper(): - letter = f'_{letter.lower()}' - - letters[letter_index] = letter - - lowercase_name = ''.join(letters) - return lowercase_name - - -def get_data_from_dict(dictionary, key, default=None): - if key in dictionary: - return dictionary[key] - return default - - -from_dict = get_data_from_dict - - -class GlTFProperty: - def __init__(self): - self.extensions = None - self.extras = None - - def from_dict(self, dictionary: dict): - if dictionary: - for key, value in dictionary.items(): - attribute_name = to_lowercase(key) - 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]: - attribute_value = value - elif issubclass(attribute_value_type, GlTFProperty): - if value_type is list: - value_type = attribute_value_type - values = [] - - for item in value: - new_value = value_type() - new_value.from_dict(item) - - values.append(new_value) - - attribute_value = values - else: - attribute_value = attribute_value_type() - attribute_value.from_dict(value) - - setattr(self, attribute_name, attribute_value) - - def to_dict(self) -> dict: - dictionary = {} - for key, value in self.__dict__.items(): - if value is not None: - attribute_name = to_camelcase(key) - value_type = type(value) - - attribute_value = None - - if value_type is list: - attribute_value = [] - for item in value: - item_type = type(item) - - if issubclass(item_type, GlTFProperty): - item = item.to_dict() - attribute_value.append(item) - elif issubclass(value_type, GlTFProperty): - attribute_value = value.to_dict() - elif attribute_value is None: - attribute_value = value - - dictionary[attribute_name] = attribute_value - return dictionary - - def __getitem__(self, item): - item = to_lowercase(item) - if hasattr(self, item): - return getattr(self, item) - else: - raise IndexError('The object has no attribute named ' + item) - - def __repr__(self) -> str: - return f'<{self.__class__.__name__} ({self.to_dict()})>' - - -class Accessor(GlTFProperty): - class Sparse(GlTFProperty): - class Indices(GlTFProperty): - def __init__(self): - super().__init__() - self.buffer_view = None - self.component_type = None - - self.byte_offset = 0 - - class Values(GlTFProperty): - def __init__(self): - super().__init__() - self.buffer_view = None - - self.byte_offset = 0 - - def __init__(self): - super().__init__() - self.count = None - self.indices = self.Indices() - self.values = self.Values() - - def __init__(self): - super().__init__() - self.component_type = None - self.count = None - self.type = None - - self.buffer_view = None - self.byte_offset = 0 - self.normalized = False - self.max = None - self.min = None - self.sparse = self.Sparse() - self.name = None - - -class Animation(GlTFProperty): - class AnimationSampler(GlTFProperty): - def __init__(self): - super().__init__() - self.input = None - self.output = None - - self.interpolation = None # Default: 'LINEAR' - - class Channel(GlTFProperty): - class Target(GlTFProperty): - def __init__(self): - super().__init__() - self.path = None - - self.node = None - - def __init__(self): - super().__init__() - self.sampler = None - self.target = self.Target() - - def __init__(self): - super().__init__() - self.channels = self.Channel() - self.samplers = self.AnimationSampler() - - self.name = None - - -class Asset(GlTFProperty): - def __init__(self): - super().__init__() - self.version = None - - self.copyright = None - self.generator = None - self.min_version = None - - -class Buffer(GlTFProperty): - def __init__(self): - super().__init__() - self.byte_length = None - - self.uri = None - self.name = None - - -class BufferView(GlTFProperty): - def __init__(self): - super().__init__() - self.buffer = None - self.byte_length = None - - self.byte_offset = 0 - self.byte_stride = None - self.target = None - self.name = None - - -class Camera(GlTFProperty): - class Orthographic(GlTFProperty): - def __init__(self): - super().__init__() - self.xmag = None - self.ymag = None - self.zfar = None - self.znear = None - - class Perspective(GlTFProperty): - def __init__(self): - super().__init__() - self.yfov = None - self.znear = None - - self.aspect_ratio = None - self.zfar = None - - def __init__(self): - super().__init__() - self.type = None - - self.orthographic = None - self.perspective = None - self.name = None - - -class Image(GlTFProperty): - def __init__(self): - super().__init__() - self.uri = None - self.mime_type = None - self.buffer_view = None - self.name = None - - -class Material(GlTFProperty): - class NormalTextureInfo(GlTFProperty): - def __init__(self): - super().__init__() - self.index = None - - self.tex_coord = None # Default: 0 - self.scale = None # Default: 1 - - class OcclusionTextureInfo(GlTFProperty): - def __init__(self): - super().__init__() - self.index = None - - self.tex_coord = None # Default: 0 - self.strength = None # Default: 1 - - class PbrMetallicRoughness(GlTFProperty): - def __init__(self): - super().__init__() - self.base_color_factor = None # Default: [1, 1, 1, 1] - self.base_color_texture = None - self.metallic_factor = None # Default: 1 - self.roughness_factor = None # Default: 1 - self.metallic_roughness_texture = None - - def __init__(self): - super().__init__() - self.name = None - self.pbr_metallic_roughness = None - self.normal_texture = None - self.occlusion_texture = None - self.emissive_texture = None - self.emissive_factor = None # Default: [0, 0, 0] - self.alpha_mode = None # Default: 'OPAQUE' - self.alpha_cutoff = None # Default: 0.5 - self.double_sided = None # Default: False - - -class Mesh(GlTFProperty): - class Primitive(GlTFProperty): - def __init__(self): - super().__init__() - self.attributes = None - - self.indices = None - self.material = None - self.mode = None # Default: 4 - self.targets = None - - def __init__(self): - super().__init__() - self.primitives = self.Primitive() - - self.weights = None - self.name = None - - -class Node(GlTFProperty): - def __init__(self): - super().__init__() - 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.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.weights = None - self.name = None - - -class Sampler(GlTFProperty): - def __init__(self): - super().__init__() - self.mag_filter = None - self.min_filter = None - self.wrap_s = None # Default: 10497 - self.wrap_t = None # Default: 10497 - self.name = None - - -class Scene(GlTFProperty): - def __init__(self): - super().__init__() - self.nodes = None - self.name = None - - -class Skin(GlTFProperty): - def __init__(self): - super().__init__() - self.joints = None - - self.inverse_bind_matrices = None - self.skeleton = None - self.name = None - - -class Texture(GlTFProperty): - def __init__(self): - super().__init__() - self.sampler = None - self.source = None - self.name = None - - -class TextureInfo(GlTFProperty): - def __init__(self): - super().__init__() - self.index = None - - self.tex_coord = None # Default: 0 - - -class GlTF(GlTFProperty): - def __init__(self): - super().__init__() - 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.scene = None - self.scenes = Scene() - self.skins = Skin() - self.textures = Texture() - - -class GlTFChunk: - def __init__(self): - self.chunk_length = 0 - self.chunk_name = b'' - self.data = b'' - - -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"') - - self.parsed = { - 'header': { - 'frame_rate': 30 - }, - 'materials': [], - 'geometries': [], - 'cameras': [], - 'nodes': [] - } - - self.version = None - self.length = None - - self.json_chunk = None - self.bin_chunk = None - - self.buffer_views = [] - self.accessors = [] - self.buffers = [] - - self.gltf = GlTF() - - def parse_bin(self): - super().__init__(self.bin_chunk.data, 'little') - - for buffer in self.gltf.buffers: - parsed_buffer = self.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') - - self.read(buffer_view.byte_offset) - - length = buffer_view.byte_length - data = self.read(length) - - self.buffer_views.append(data) - - for accessor in self.gltf.accessors: - super().__init__(self.buffer_views[accessor.buffer_view], '<') - temp_accessor = [] - - self.read(accessor.byte_offset) - - types = { - 5120: self.readByte, - 5121: self.readUByte, - 5122: self.readShort, - 5123: self.readUShort, - 5125: self.readUInt32, - 5126: self.readFloat - } - - items_count = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 - } - - for x in range(accessor.count): - temp_list = [] - for i in range(items_count[accessor.type]): - temp_list.append(types[accessor.component_type]()) - temp_accessor.append(temp_list) - - if accessor.normalized: - for item_index, data in enumerate(temp_accessor): - new_data = [] - for item in data: - if accessor['component_type'] == 5120: - new_data.append(max(item / 127, -1.0)) - elif accessor['component_type'] == 5121: - new_data.append(item / 255) - elif accessor['component_type'] == 5122: - new_data.append(max(item / 32767, -1.0)) - elif accessor['component_type'] == 5123: - new_data.append(item / 65535) - else: - new_data.append(item) - temp_accessor[item_index] = new_data - - self.accessors.append(temp_accessor) - - def parse(self): - # - - self.version = self.readUInt32() - self.length = self.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.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.gltf.from_dict(json.loads(self.json_chunk.data)) - - self.parse_bin() - - # - - scene_id = self.gltf.scene - scene = self.gltf.scenes[scene_id] - - for node_id in scene.nodes: - node = self.gltf.nodes[node_id] - self.parse_node(node) - - # - - def parse_node(self, node: Node, parent: str = None): - node_name = node.name - # node_name = node_name.split('|') - # if len(node_name) > 1: - # node_name = node_name[1] - # parent = node_name[0] - # else: - # node_name = node_name[0] - - # node_name = node_name.split(':') - # if len(node_name) > 1: - # if node_name[1] == 'PIV': - # print(node.name, node.translation) - # # else: - # print(node) - # node_name = node_name[0] - # else: - # node_name = node_name[0] - - node_data = { - 'name': node_name, - 'parent': parent, - 'has_target': False, - 'frames': [] - } - - if node.mesh: - node_data['has_target'] = True - node_data['target_type'] = 'GEOM' - - geometry_data = { - 'name': '', - 'group': '', - 'vertices': [], - 'have_bind_matrix': False, - 'weights': { - 'vertex_weights': [], - 'weights': [], - 'vcount': [] - }, - 'materials': [] - } - - if node.skin: - geometry_data['have_bind_matrix'] = True - geometry_data['bind_matrix'] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] - geometry_data['joints'] = [] - node_data['target_type'] = 'CONT' - - skin_id = node.skin - skin = self.gltf.skins[skin_id] - bind_matrices = self.accessors[skin.inverse_bind_matrices] - - for joint in skin.joints: - joint_index = skin['joints'].index(joint) - joint_node = self.gltf.nodes[joint] - joint_name = joint_node['name'] - matrix = bind_matrices[joint_index] - - joint_data = { - 'name': joint_name, - 'matrix': matrix - } - geometry_data['joints'].append(joint_data) - - mesh_id = node.mesh - mesh = self.gltf.meshes[mesh_id] - mesh_name = mesh.name - mesh_name = mesh_name.split('|') - - if len(mesh_name) > 1: - geometry_data['group'] = mesh_name[0] - geometry_data['name'] = mesh_name[1] - else: - geometry_data['name'] = mesh_name[0] - - node_data['target'] = geometry_data['name'] - node_data['binds'] = [] - - offsets = { - 'POSITION': 0, - 'NORMAL': 0, - 'TEXCOORD': 0 - } - - for primitive in mesh.primitives: - if primitive.to_dict() != {}: - primitive_index = mesh.primitives.index(primitive) - attributes = primitive.attributes - material_id = primitive.material - polygons_id = primitive.indices - - inputs = [] - - polygons = self.accessors[polygons_id] - material = self.gltf.materials[material_id] - - material_name = material.extensions['SC_shader']['name'] - node_data['binds'].append({ - 'symbol': material_name, - 'target': material_name - }) - - for attribute_id in attributes: - attribute = attributes[attribute_id] - if attribute_id == 'POSITION': - position = self.accessors[attribute] - - geometry_data['vertices'].append({ - 'type': 'POSITION', - 'name': f'position_{primitive_index}', - 'index': len(geometry_data['vertices']), - 'scale': 1, - 'vertex': position - }) - - inputs.append({ - 'type': 'POSITION', - 'offset': '1', - 'name': f'position_{primitive_index}', - }) - elif attribute_id == 'NORMAL': - normal = self.accessors[attribute] - - geometry_data['vertices'].append({ - 'type': 'NORMAL', - 'name': f'normal_{primitive_index}', - 'index': len(geometry_data['vertices']), - 'scale': 1, - 'vertex': normal - }) - - inputs.append({ - 'type': 'NORMAL', - 'offset': '0', - 'name': f'normal_{primitive_index}', - }) - elif attribute_id.startswith('TEXCOORD'): - texcoord = self.accessors[attribute] - - texcoord = [[item[0], 1 - item[1]] for item in texcoord] - - geometry_data['vertices'].append({ - 'type': 'TEXCOORD', - 'name': f'texcoord_{primitive_index}', - 'index': len(geometry_data['vertices']), - 'scale': 1, - 'vertex': texcoord - }) - - inputs.append({ - 'type': 'TEXCOORD', - 'offset': '2', - 'name': f'texcoord_{primitive_index}', - }) - elif attribute_id.startswith('JOINTS'): - vertex_weights = self.accessors[attribute] - elif attribute_id.startswith('WEIGHTS'): - weights = self.accessors[attribute] - - for x in range(len(vertex_weights)): - geometry_data['weights']['vcount'].append(0) - - temp_list = [ - [vertex_weights[x][0], weights[x][0]], - [vertex_weights[x][1], weights[x][1]], - [vertex_weights[x][2], weights[x][2]], - [vertex_weights[x][3], weights[x][3]] - ] - for pair in temp_list: - if pair[1] != 0: - geometry_data['weights']['vcount'][x] += 1 - geometry_data['weights']['vertex_weights'].append(pair[0]) - if pair[1] not in geometry_data['weights']['weights']: - geometry_data['weights']['weights'].append(pair[1]) - geometry_data['weights']['vertex_weights'].append( - geometry_data['weights']['weights'].index(pair[1]) - ) - - polygons = [ - [ - [ - value[0] + offsets['NORMAL'], - value[0] + offsets['POSITION'], - value[0] + offsets['TEXCOORD'] - ] for value in polygons[x:x + 3] - ] for x in range(0, len(polygons), 3) - ] - - geometry_data['materials'].append({ - 'name': material_name, - 'inputs': inputs, - 'polygons': polygons - }) - - for attribute_id in attributes: - if attribute_id == 'POSITION': - offsets['POSITION'] += len(position) - elif attribute_id == 'NORMAL': - offsets['NORMAL'] += len(normal) - elif attribute_id.startswith('TEXCOORD'): - offsets['TEXCOORD'] += len(texcoord) - - self.parsed['geometries'].append(geometry_data) - - self.parsed['nodes'].append(node_data) - - if node.translation or node.rotation or node.scale: - node_data['frames'].append({ - 'frame_id': 0, - 'rotation': {'x': 0, 'y': 0, 'z': 0, 'w': 0}, - 'position': {'x': 0, 'y': 0, 'z': 0}, - 'scale': {'x': 1, 'y': 1, 'z': 1} - }) - - if node.translation: - node_data['frames'][0]['position'] = { - 'x': node.translation[0], - 'y': node.translation[1], - 'z': node.translation[2] - } - if node.rotation: - node_data['frames'][0]['rotation'] = { - 'x': node.rotation[0], - 'y': node.rotation[1], - 'z': node.rotation[2], - 'w': node.rotation[3] - } - if node.scale: - node_data['frames'][0]['scale'] = { - 'x': node.scale[0], - 'y': node.scale[1], - 'z': node.scale[2] - } - - if node.children: - for child_id in node.children: - child = self.gltf.nodes[child_id] - self.parse_node(child, node_name) - - -if __name__ == '__main__': - with open('../crow_geo.glb', 'rb') as fh: - file_data = fh.read() - fh.close() - parser = Parser(file_data) - parser.parse() - - writer = Writer() - with open('../crow_geo.dae', 'w') as fh: - fh.write(writer.writen) - fh.close() diff --git a/3d-converter-0.8.0/models_converter/formats/obj.py b/3d-converter-0.8.0/models_converter/formats/obj.py deleted file mode 100644 index ecdada4..0000000 --- a/3d-converter-0.8.0/models_converter/formats/obj.py +++ /dev/null @@ -1,189 +0,0 @@ -def _(*args): - print('[ScwUtils]', end=' ') - for arg in args: - print(arg, end=' ') - print() - - -class Writer: - def __init__(self): - self.writen = '' - - self.temp_vertices_offsets = { - 'POSITION': 0, - 'TEXCOORD': 0, - 'NORMAL': 0 - } - - self.vertices_offsets = { - 'POSITION': 0, - 'TEXCOORD': 0, - 'NORMAL': 0 - } - - def write(self, data: dict): - for geom in data['geometries']: - for key in self.vertices_offsets.keys(): - self.vertices_offsets[key] = self.temp_vertices_offsets[key] - prefix = '' - - name = geom['name'] - vertices = geom['vertices'] - materials = geom['materials'] - for vertex in vertices: - if vertex['type'] == 'POSITION': - prefix = 'v ' - elif vertex['type'] == 'NORMAL': - prefix = 'vn ' - elif vertex['type'] == 'TEXCOORD': - prefix = 'vt ' - - self.temp_vertices_offsets[vertex['type']] += len(vertex['vertex']) - - for item in vertex['vertex']: - temp_string = prefix - for subitem in item: - temp_string += str(subitem * vertex['scale']) + ' ' - self.writen += f'{temp_string}\n' - self.writen += '\n\n' - for material in materials: - self.writen += f'o {name}_{material["name"]}\n\n' - for item in material['polygons']: - temp_string = 'f ' - for subitem in item: - temp_list = [ - str(subitem[0] + self.vertices_offsets['POSITION'] + 1), # POSITION - str(subitem[2] + self.vertices_offsets['TEXCOORD'] + 1), # TEXCOORD - str(subitem[1] + self.vertices_offsets['NORMAL'] + 1) # NORMAL - ] - - temp_string += '/'.join(temp_list) + ' ' - self.writen += f'{temp_string}\n' - self.writen += '\n\n' - - -class Parser: - def __init__(self, file_data: str): - # - - self.parsed = {'header': {'version': 2, - 'frame_rate': 30, - 'materials_file': 'sc3d/character_materials.scw'}, - 'materials': [], - 'geometries': [], - 'cameras': [], - 'nodes': []} - - self.lines = file_data.split('\n') - - # - self.position_temp, self.position = [], [] - self.normals_temp, self.normals = [], [] - self.texcoord_temp, self.texcoord = [], [] - # - - self.polygons = [] - - # - - self.parse() - - def parse(self): - geometry_name = 'This model haven \'t a chunk_name!:( Its VERY SAD!' - is_first_name = True - for line in self.lines: - items = line.split()[1:] - if line.startswith('v '): # POSITION - for item in items: - self.position_temp.append(float(item)) - elif line.startswith('vn '): # NORMAL - for item in items: - self.normals_temp.append(float(item)) - elif line.startswith('vt '): # TEXCOORD - if len(items) > 2: - items = items[:-1] - for item in items: - self.texcoord_temp.append(float(item)) - elif line.startswith('f '): - temp_list = [] - if len(items) > 3: - _('It is necessary to triangulate the model') - break - for item in items: - second_temp_list = [] - if len(item.split('/')) == 2: - _('Model have not normals or texture') - break - elif len(item.split('/')) == 1: - _('Model have not normals and texture') - break - for x in item.split('/'): - second_temp_list.append(int(x) - 1) - temp_list.append([second_temp_list[0], second_temp_list[2], second_temp_list[1]]) - self.polygons.append(temp_list) - elif line.startswith('o '): - # if not is_first_name: - # position_scale = self.get_vertex_scale(self.position_temp) - # normals_scale = self.get_vertex_scale(self.normals_temp) - # texcoord_scale = self.get_vertex_scale(self.texcoord_temp) - # - # self.parsed['geometries'].append({ - # 'chunk_name': geometry_name, - # 'group': '', - # 'vertices': [ - # {'type': 'POSITION', 'index': 0, 'scale': position_scale, 'vertex': self.position}, - # {'type': 'NORMAL', 'index': 1, 'scale': normals_scale, 'vertex': self.normals}, - # {'type': 'TEXCOORD', 'index': 2, 'scale': texcoord_scale, 'vertex': self.texcoord} - # ], - # 'have_bind_matrix': False, - # 'materials': [{'chunk_name': 'character_mat', 'polygons': self.polygons}] - # }) - # - # # - # - # # - # self.position_temp, self.position = [], [] - # self.normals_temp, self.normals = [], [] - # self.texcoord_temp, self.texcoord = [], [] - # # - # - # self.polygons = [] - # - # # - # geometry_name = line.split('o ')[0] - if is_first_name: - geometry_name = line.split('o ')[0] - - position_scale = self.get_vertex_scale(self.position_temp) - normals_scale = self.get_vertex_scale(self.normals_temp) - texcoord_scale = self.get_vertex_scale(self.texcoord_temp) - - for x in range(0, len(self.position_temp), 3): - self.position.append(self.position_temp[x: x + 3]) - - for x in range(0, len(self.normals_temp), 3): - self.normals.append(self.normals_temp[x: x + 3]) - - for x in range(0, len(self.texcoord_temp), 2): - self.texcoord.append(self.texcoord_temp[x: x + 2]) - - self.parsed['geometries'].append({ - 'chunk_name': geometry_name, - 'group': '', - 'vertices': [ - {'type': 'POSITION', 'index': 0, 'scale': position_scale, 'vertex': self.position}, - {'type': 'NORMAL', 'index': 1, 'scale': normals_scale, 'vertex': self.normals}, - {'type': 'TEXCOORD', 'index': 2, 'scale': texcoord_scale, 'vertex': self.texcoord} - ], - 'have_bind_matrix': False, - 'materials': [{'chunk_name': 'character_mat', 'polygons': self.polygons}] - }) - - # TODO: nodes - - @staticmethod - def get_vertex_scale(vertex_data: list): - vertex_scale = max(max(vertex_data), abs(min(vertex_data))) - if vertex_scale < 1: - vertex_scale = 1 - return vertex_scale diff --git a/3d-converter-0.8.0/models_converter/formats/scw/__init__.py b/3d-converter-0.8.0/models_converter/formats/scw/__init__.py deleted file mode 100644 index cb85844..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .writer import Writer -from .parser import Parser - -__all__ = [ - 'Writer', - 'Parser' -] diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/__init__.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/__init__.py deleted file mode 100644 index b8bd048..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from .chunk import Chunk -from .head import HEAD -from .mate import MATE -from .geom import GEOM -from .came import CAME -from .node import NODE -from .wend import WEND - -__all__ = [ - 'Chunk', - 'HEAD', - 'MATE', - 'GEOM', - 'CAME', - 'NODE', - 'WEND' -] diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/came.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/came.py deleted file mode 100644 index eb76959..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/came.py +++ /dev/null @@ -1,29 +0,0 @@ -from . import Chunk - - -class CAME(Chunk): - def __init__(self, header=None): - super().__init__(header) - self.chunk_name = 'CAME' - - def parse(self, buffer: bytes): - super().parse(buffer) - - setattr(self, 'name', self.readString()) - setattr(self, 'v1', self.readFloat()) - setattr(self, 'xFov', self.readFloat()) - setattr(self, 'aspectRatio', self.readFloat()) - setattr(self, 'zNear', self.readFloat()) - setattr(self, 'zFar', self.readFloat()) - - def encode(self): - super().encode() - - self.writeString(getattr(self, 'name')) - self.writeFloat(getattr(self, 'v1')) - self.writeFloat(getattr(self, 'xFov')) - self.writeFloat(getattr(self, 'aspectRatio')) - self.writeFloat(getattr(self, 'zNear')) - self.writeFloat(getattr(self, 'zFar')) - - self.length = len(self.buffer) diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/chunk.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/chunk.py deleted file mode 100644 index 4f28584..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/chunk.py +++ /dev/null @@ -1,70 +0,0 @@ -from ....utils.reader import Reader -from ....utils.writer import Writer - - -class Chunk(Writer, Reader): - def __init__(self, header=None): - super().__init__() - - if header is None: - header = {} - - self.header = header - self.chunk_name = '' - - self.buffer = b'' - self.length = 0 - - def from_dict(self, dictionary: dict): - if dictionary: - for key, value in dictionary.items(): - setattr(self, key, value) - - def to_dict(self) -> dict: - dictionary = {} - for key, value in self.__dict__.items(): - if key in ['header', 'buffer', 'length', 'endian', 'i']: - continue - if value is not None: - attribute_name = key - value_type = type(value) - - attribute_value = None - - if value_type is list: - attribute_value = [] - for item in value: - item_type = type(item) - - if issubclass(item_type, Chunk): - item = item.to_dict() - attribute_value.append(item) - elif issubclass(value_type, Chunk): - attribute_value = value.to_dict() - elif attribute_value is None: - attribute_value = value - - dictionary[attribute_name] = attribute_value - return dictionary - - def __getitem__(self, key): - if hasattr(self, key): - return getattr(self, key) - else: - raise IndexError('The object has no attribute named ' + key) - - def __repr__(self) -> str: - return f'<{self.__class__.__name__} ({self.to_dict()})>' - - def set(self, key: str, value): - setattr(self, key, value) - - def parse(self, buffer: bytes): - Reader.__init__(self, buffer, 'big') - - def encode(self): - Writer.__init__(self, 'big') - - self.length = len(self.buffer) - - get = __getitem__ diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/geom.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/geom.py deleted file mode 100644 index ffa2c5d..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/geom.py +++ /dev/null @@ -1,268 +0,0 @@ -from . import Chunk - - -class GEOM(Chunk): - def __init__(self, header: dict): - super().__init__(header) - self.chunk_name = 'GEOM' - - def parse(self, buffer: bytes): - super().parse(buffer) - - setattr(self, 'name', self.readString()) - setattr(self, 'group', self.readString()) - if self.header['version'] < 2: - matrix = [] - for x in range(4): - temp_list = [] - for x1 in range(4): - temp_list.append(self.readFloat()) - matrix.append(temp_list) - - self.parse_vertices() - self.parse_skin() - self.parse_materials() - - def parse_vertices(self): - vertices = [] - inputs = [] - - vertex_count = self.readUByte() - for x in range(vertex_count): - vertex = [] - vertex_type = self.readString() - vertex_index = self.readUByte() - self.readUByte() # sub_index - vertex_stride = self.readUByte() - vertex_scale = self.readFloat() - vertex_count = self.readUInt32() - - if vertex_type == 'VERTEX': - vertex_type = 'POSITION' - - for x1 in range(vertex_count): - coordinates_massive = [] - for x2 in range(vertex_stride): - coordinate = self.readShort() - coordinates_massive.append(coordinate / 32512) - if vertex_type == 'TEXCOORD': - coordinates_massive[1::2] = [1 - x for x in coordinates_massive[1::2]] - vertex.append(coordinates_massive) - - inputs.append({ - 'type': vertex_type, - 'offset': vertex_index, - 'name': f'{vertex_type.lower()}_0' - }) - - vertices.append({ - 'name': f'{vertex_type.lower()}_0', - 'type': vertex_type, - 'index': vertex_index, - 'scale': vertex_scale, - 'vertex': vertex - }) - setattr(self, 'inputs', inputs) - setattr(self, 'vertices', vertices) - - def parse_skin(self): - bind_matrix = [] - - setattr(self, 'have_bind_matrix', self.readBool()) - if getattr(self, 'have_bind_matrix'): - for x in range(16): - bind_matrix.append(self.readFloat()) - - setattr(self, 'bind_matrix', bind_matrix) - - self.parse_joints() - self.parse_weights() - - def parse_joints(self): - joints = [] - - joint_counts = self.readUByte() - for x in range(joint_counts): - joint_matrix = [] - joint_name = self.readString() - for x1 in range(16): - joint_matrix.append(self.readFloat()) - joints.append({'name': joint_name, 'matrix': joint_matrix}) - - setattr(self, 'joints', joints) - - def parse_weights(self): - vertex_weights = [] - weights = [] - vcount = [] - - vertex_weights_count = self.readUInt32() - for x in range(vertex_weights_count): - vcount.append(0) - joint_a = self.readUByte() - joint_b = self.readUByte() - joint_c = self.readUByte() - joint_d = self.readUByte() - weight_a = self.readUShort() - weight_b = self.readUShort() - weight_c = self.readUShort() - weight_d = self.readUShort() - temp_list = [ - [joint_a, weight_a], - [joint_b, weight_b], - [joint_c, weight_c], - [joint_d, weight_d] - ] - for pair in temp_list: - if pair[1] != 0: - vcount[x] += 1 - vertex_weights.append(pair[0]) - if pair[1] / 65535 not in weights: - weights.append(pair[1] / 65535) - vertex_weights.append(weights.index(pair[1] / 65535)) - - setattr(self, 'weights', - { - 'vertex_weights': vertex_weights, - 'weights': weights, - 'vcount': vcount - }) - - def parse_materials(self): - materials = [] - - materials_count = self.readUByte() - for x in range(materials_count): - polygons = [] - material_name = self.readString() - self.readString() - polygons_count = self.readUShort() - inputs_count = self.readUByte() - vertex_id_length = self.readUByte() - for x1 in range(polygons_count): - temp_list = [] - for x2 in range(3): - second_temp_list = [] - for x3 in range(inputs_count): - second_temp_list.append(self.readUInteger(vertex_id_length)) - temp_list.append(second_temp_list) - polygons.append(temp_list) - materials.append({ - 'name': material_name, - 'inputs': getattr(self, 'inputs'), - 'polygons': polygons - }) - - setattr(self, 'materials', materials) - - def encode(self): - super().encode() - - self.writeString(self.get('name')) - self.writeString(self.get('group')) - - self.encode_vertices(self.get('vertices')) - - self.encode_skin() - - self.encode_materials() - - self.length = len(self.buffer) - - def encode_vertices(self, vertices: dict): - self.writeUByte(len(vertices)) - for vertex in vertices: - self.writeString(vertex['type']) - self.writeUByte(vertex['index']) - self.writeUByte(0) # sub_index - self.writeUByte(len(vertex['vertex'][0])) - self.writeFloat(vertex['scale']) - self.writeUInt32(len(vertex['vertex'])) - for coordinates_massive in vertex['vertex']: - if vertex['type'] == 'TEXCOORD': - coordinates_massive[1::2] = [1 - x for x in coordinates_massive[1::2]] - for coordinate in coordinates_massive: - # coordinate /= vertex['scale'] - coordinate *= 32512 - self.writeShort(round(coordinate)) - - def encode_skin(self): - self.writeBool(self.get('have_bind_matrix')) - if self.get('have_bind_matrix'): - for x in self.get('bind_matrix'): - self.writeFloat(x) - - self.encode_joints() - - self.encode_weight() - - def encode_joints(self): - if self.get('have_bind_matrix'): - self.writeUByte(len(self.get('joints'))) - - for joint in self.get('joints'): - self.writeString(joint['name']) - for x in joint['matrix']: - self.writeFloat(x) - else: - self.writeUByte(0) - - def encode_weight(self): - if self.get('have_bind_matrix'): - self.writeUInt32(len(self.get('weights')['vcount'])) - past_index = 0 - for vcount in self.get('weights')['vcount']: - temp_list = [] - for x in range(vcount): - vertex_weights_index = x * 2 + past_index * 2 - joint_id = self.get('weights')['vertex_weights'][vertex_weights_index] - weight_id = self.get('weights')['vertex_weights'][vertex_weights_index + 1] - - weight = self.get('weights')['weights'][weight_id] - - if weight > 1: - weight = 1 - elif weight < 0: - weight = 0 - - weight = int(weight * 65535) - - temp_list.append([joint_id, weight]) - past_index += vcount - while len(temp_list) < 4: - temp_list.append([0, 0]) - for x in temp_list: - self.writeUByte(x[0]) - for x in temp_list: - self.writeUShort(x[1]) - else: - self.writeUInt32(0) - - def encode_materials(self): - self.writeUByte(len(self.get('materials'))) - for material in self.get('materials'): - self.writeString(material['name']) - self.writeString('') - self.writeUShort(len(material['polygons'])) - - # Calculate settings - inputs_count = len(material['polygons'][0][0]) - - maximal_value = 0 - for points in material['polygons']: - for point in points: - for vertex in point: - if vertex > maximal_value: - maximal_value = vertex - - short_length = 1 if maximal_value <= 255 else 2 - - # Write Settings - self.writeUByte(inputs_count) - self.writeUByte(short_length) - - # Write Polygons - for points in material['polygons']: - for point in points: - for vertex in point: - self.writeUInteger(vertex, short_length) diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/head.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/head.py deleted file mode 100644 index 2b17bbe..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/head.py +++ /dev/null @@ -1,30 +0,0 @@ -from . import Chunk - - -class HEAD(Chunk): - def __init__(self, header=None): - super().__init__(header) - self.chunk_name = 'HEAD' - - def parse(self, buffer: bytes): - super().parse(buffer) - - setattr(self, 'version', self.readUShort()) - setattr(self, 'frame_rate', self.readUShort()) - setattr(self, 'v1', self.readUShort()) - setattr(self, 'v2', self.readUShort()) - setattr(self, 'materials_file', self.readString()) - if self.get('version') == 2: - setattr(self, 'v3', self.readUByte()) - - def encode(self): - super().encode() - - self.writeUShort(2) # getattr(self, 'version') - self.writeUShort(getattr(self, 'frame_rate')) - self.writeUShort(0) # getattr(self, 'v1') - self.writeUShort(249) # getattr(self, 'v2') - self.writeString(getattr(self, 'materials_file')) - self.writeUByte(0) # getattr(self, 'v3') - - self.length = len(self.buffer) diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/mate.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/mate.py deleted file mode 100644 index a508caa..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/mate.py +++ /dev/null @@ -1,174 +0,0 @@ -from . import Chunk - - -class MATE(Chunk): - def __init__(self, header: dict): - super().__init__(header) - self.chunk_name = 'MATE' - - def parse(self, buffer: bytes): - super().parse(buffer) - - setattr(self, 'name', self.readString()) - setattr(self, 'shader', self.readString()) - setattr(self, 'v1', self.readUByte()) - setattr(self, 'v2', self.readUByte()) - - effect = {} - a = self.readUByte() - r = self.readUByte() - g = self.readUByte() - b = self.readUByte() - ambient_color = (r, g, b, a) - effect['ambient'] = ambient_color - - use_diffuse_tex = self.readBool() - if use_diffuse_tex: - diffuse_tex = self.readString() - effect['diffuse'] = diffuse_tex - else: - a = self.readUByte() - r = self.readUByte() - g = self.readUByte() - b = self.readUByte() - diffuse_color = (r, g, b, a) - effect['diffuse'] = diffuse_color - - use_specular_tex = self.readBool() - if use_specular_tex: - specular_tex = self.readString() - effect['specular'] = specular_tex - else: - a = self.readUByte() - r = self.readUByte() - g = self.readUByte() - b = self.readUByte() - specular_color = (r, g, b, a) - effect['specular'] = specular_color - - setattr(self, 'v3', self.readString()) - setattr(self, 'v4', self.readString()) - - use_colorize_tex = self.readBool() - if use_colorize_tex: - colorize_tex = self.readString() - effect['colorize'] = colorize_tex - else: - a = self.readUByte() - r = self.readUByte() - g = self.readUByte() - b = self.readUByte() - colorize_color = (r, g, b, a) - effect['colorize'] = colorize_color - - use_emission_tex = self.readBool() - if use_emission_tex: - emission_tex = self.readString() - effect['emission'] = emission_tex - else: - a = self.readUByte() - r = self.readUByte() - g = self.readUByte() - b = self.readUByte() - emission_color = (r, g, b, a) - effect['emission'] = emission_color - - setattr(self, 'opacity_texture', self.readString()) - setattr(self, 'v5', self.readFloat()) - setattr(self, 'v6', self.readFloat()) - - effect['lightmaps'] = { - 'diffuse': self.readString(), - 'specular': self.readString() - } - - if self.header['version'] == 2: - setattr(self, 'v7', self.readString()) - - shader_define_flags = self.readUInt32() - effect['shader_define_flags'] = shader_define_flags - - if shader_define_flags & 32768: - self.readFloat() - self.readFloat() - self.readFloat() - self.readFloat() - - setattr(self, 'effect', effect) - - def encode(self): - super().encode() - - self.writeString(getattr(self, 'name')) - self.writeString(getattr(self, 'shader')) - self.writeUByte(4) # getattr(self, 'v1') - self.writeUByte(0) # getattr(self, 'v2') - - effect = getattr(self, 'effect') - r, g, b, a = effect['ambient'] - self.writeUByte(a) - self.writeUByte(r) - self.writeUByte(g) - self.writeUByte(b) - - use_diffuse_tex = type(effect['diffuse']) is str - self.writeBool(use_diffuse_tex) - if use_diffuse_tex: - self.writeString(effect['diffuse']) - else: - r, g, b, a = effect['diffuse'] - self.writeUByte(a) - self.writeUByte(r) - self.writeUByte(g) - self.writeUByte(b) - - use_specular_tex = type(effect['specular']) is str - self.writeBool(use_specular_tex) - if use_specular_tex: - self.writeString(effect['specular']) - else: - r, g, b, a = effect['specular'] - self.writeUByte(a) - self.writeUByte(r) - self.writeUByte(g) - self.writeUByte(b) - - self.writeString('.') # getattr(self, 'v3') - self.writeString('') # getattr(self, 'v4') - - use_colorize_tex = type(effect['colorize']) is str - self.writeBool(use_colorize_tex) - if use_colorize_tex: - self.writeString(effect['colorize']) - else: - r, g, b, a = effect['colorize'] - self.writeUByte(a) - self.writeUByte(r) - self.writeUByte(g) - self.writeUByte(b) - - use_emission_tex = type(effect['emission']) is str - self.writeBool(use_emission_tex) - if use_emission_tex: - self.writeString(effect['emission']) - else: - r, g, b, a = effect['emission'] - self.writeUByte(a) - self.writeUByte(r) - self.writeUByte(g) - self.writeUByte(b) - - self.writeString(getattr(self, 'opacity_texture')) - self.writeFloat(1) # getattr(self, 'v5') - self.writeFloat(0) # getattr(self, 'v6') - - self.writeString(effect['lightmaps']['diffuse']) - self.writeString(effect['lightmaps']['specular']) - - if self.header['version'] == 2: - self.writeString(getattr(self, 'v7')) - - self.writeUInt32(effect['shader_define_flags']) - - self.length = len(self.buffer) - diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/node.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/node.py deleted file mode 100644 index b76ae33..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/node.py +++ /dev/null @@ -1,145 +0,0 @@ -from . import Chunk - - -class NODE(Chunk): - def __init__(self, header: dict): - super().__init__(header) - self.chunk_name = 'NODE' - - def parse(self, buffer: bytes): - super().parse(buffer) - nodes = [] - - nodes_count = self.readUShort() - for node in range(nodes_count): - node_data = { - 'name': self.readString(), - 'parent': self.readString() - } - - instances_count = self.readUShort() - node_data['instances'] = [{}] * instances_count - for x in range(instances_count): - instance_type = self.readChar(4) - instance_name = self.readString() - - node_data['instances'][x] = {} - if instance_type in ['GEOM', 'CONT']: - materials_count = self.readUShort() - binds = [] - for bind in range(materials_count): - binds.append({}) - symbol = self.readString() - target = self.readString() - binds[bind] = {'symbol': symbol, - 'target': target} - node_data['instances'][x]['binds'] = binds - elif instance_type in ['CAME']: - target = self.readString() - node_data['instances'][x]['target'] = target - node_data['instances'][x]['instance_name'] = instance_name - node_data['instances'][x]['instance_type'] = instance_type - - frames_count = self.readUShort() - node_data['frames'] = [] - if frames_count > 0: - rotation = {'x': 0, 'y': 0, 'z': 0, 'w': 0} - scale_x, scale_y, scale_z = 0, 0, 0 - pos_x, pos_y, pos_z = 0, 0, 0 - - settings = list(bin(self.readUByte())[2:].zfill(8)) - settings = [bool(int(value)) for value in settings] - node_data['frames_settings'] = settings - for frame in range(frames_count): - frame_data = { - 'frame_id': self.readUShort() - } - - if settings[7] or frame == 0: # Rotation - rotation = { - 'x': self.readNShort(), - 'y': self.readNShort(), - 'z': self.readNShort(), - 'w': self.readNShort() - } - - if settings[4] or frame == 0: # Position X - pos_x = self.readFloat() - if settings[5] or frame == 0: # Position Y - pos_y = self.readFloat() - if settings[6] or frame == 0: # Position Z - pos_z = self.readFloat() - - if settings[1] or frame == 0: # Scale X - scale_x = self.readFloat() - if settings[2] or frame == 0: # Scale Y - scale_y = self.readFloat() - if settings[3] or frame == 0: # Scale Z - scale_z = self.readFloat() - - frame_data['rotation'] = rotation - frame_data['position'] = { - 'x': pos_x, - 'y': pos_y, - 'z': pos_z - } - frame_data['scale'] = { - 'x': scale_x, - 'y': scale_y, - 'z': scale_z - } - - node_data['frames'].append(frame_data) - nodes.append(node_data) - setattr(self, 'nodes', nodes) - - def encode(self): - super().encode() - - self.writeUShort(len(self.get('nodes'))) - for node in self.get('nodes'): - self.writeString(node['name']) - self.writeString(node['parent']) - - self.writeUShort(len(node['instances'])) - for instance in node['instances']: - self.writeChar(instance['instance_type']) - self.writeString(instance['instance_name']) - self.writeUShort(len(instance['binds'])) - for bind in instance['binds']: - self.writeString(bind['symbol']) - self.writeString(bind['target']) - - if 'frames_settings' in node: - frames_settings = node['frames_settings'] - else: - frames_settings = None - self.encode_frames(node['frames'], frames_settings) - - self.length = len(self.buffer) - - def encode_frames(self, frames, frames_settings): - self.writeUShort(len(frames)) - if len(frames) > 0: - self.writeUByte(int(''.join([('1' if item else '0') for item in frames_settings])[::], 2)) - for frame in frames: - self.writeUShort(frame['frame_id']) - if frames_settings[7] or frames.index(frame) == 0: # Rotation - self.writeNShort(frame['rotation']['x']) - self.writeNShort(frame['rotation']['y']) - self.writeNShort(frame['rotation']['z']) - self.writeNShort(frame['rotation']['w']) - - if frames_settings[4] or frames.index(frame) == 0: # Position X - self.writeFloat(frame['position']['x']) - if frames_settings[5] or frames.index(frame) == 0: # Position Y - self.writeFloat(frame['position']['y']) - if frames_settings[6] or frames.index(frame) == 0: # Position Z - self.writeFloat(frame['position']['z']) - - if frames_settings[1] or frames.index(frame) == 0: # Scale X - self.writeFloat(frame['scale']['x']) - if frames_settings[2] or frames.index(frame) == 0: # Scale Y - self.writeFloat(frame['scale']['y']) - if frames_settings[3] or frames.index(frame) == 0: # Scale Z - self.writeFloat(frame['scale']['z']) diff --git a/3d-converter-0.8.0/models_converter/formats/scw/chunks/wend.py b/3d-converter-0.8.0/models_converter/formats/scw/chunks/wend.py deleted file mode 100644 index 0252602..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/chunks/wend.py +++ /dev/null @@ -1,15 +0,0 @@ -from . import Chunk - - -class WEND(Chunk): - def __init__(self, header=None): - super().__init__(header) - self.chunk_name = 'WEND' - - def parse(self, buffer: bytes): - super().parse(buffer) - - def encode(self): - super().encode() - - self.length = len(self.buffer) diff --git a/3d-converter-0.8.0/models_converter/formats/scw/parser.py b/3d-converter-0.8.0/models_converter/formats/scw/parser.py deleted file mode 100644 index df8649f..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/parser.py +++ /dev/null @@ -1,66 +0,0 @@ -from ...utils.reader import Reader -from .chunks import * - - -class Parser(Reader): - def __init__(self, file_data: bytes): - super().__init__(file_data) - self.file_data = file_data - self.parsed = { - 'header': {}, - 'materials': [], - 'geometries': [], - 'cameras': [], - 'nodes': [] - } - self.chunks = [] - - file_magic = self.read(4) - if file_magic != b'SC3D': - raise TypeError('File Magic isn\'t "SC3D"') - - def split_chunks(self): - # len(Chunk Length) + len(Chunk Name) + len(Chunk CRC) - while len(self.file_data[self.tell():]) >= 12: - chunk_length = self.readUInt32() - chunk_name = self.readChar(4) - chunk_data = self.read(chunk_length) - chunk_crc = self.readUInt32() - - self.chunks.append({ - 'chunk_name': chunk_name, - 'data': chunk_data, - 'crc': chunk_crc - }) - - def parse(self): - for chunk in self.chunks: - chunk_name = chunk['chunk_name'] - chunk_data = chunk['data'] - - if chunk_name == 'HEAD': - head = HEAD() - head.parse(chunk_data) - - self.parsed['header'] = head.to_dict() - elif chunk_name == 'MATE': - mate = MATE(self.parsed['header']) - mate.parse(chunk_data) - self.parsed['materials'].append(mate.to_dict()) - elif chunk_name == 'GEOM': - geom = GEOM(self.parsed['header']) - geom.parse(chunk_data) - self.parsed['geometries'].append(geom.to_dict()) - elif chunk_name == 'CAME': - came = CAME(self.parsed['header']) - came.parse(chunk_data) - self.parsed['cameras'].append(came.to_dict()) - elif chunk_name == 'NODE': - node = NODE(self.parsed['header']) - node.parse(chunk_data) - self.parsed['nodes'] = node.to_dict()['nodes'] - elif chunk_name == 'WEND': - wend = WEND() - wend.parse(chunk_data) - else: - raise TypeError(f'Unknown chunk: {chunk_name}') diff --git a/3d-converter-0.8.0/models_converter/formats/scw/writer.py b/3d-converter-0.8.0/models_converter/formats/scw/writer.py deleted file mode 100644 index b55efb2..0000000 --- a/3d-converter-0.8.0/models_converter/formats/scw/writer.py +++ /dev/null @@ -1,51 +0,0 @@ -import binascii - -from .chunks import * - - -class Writer: - def __init__(self): - self.writen = b'SC3D' - - def write(self, data: dict): - - header = data['header'] - head = HEAD() - head.from_dict(header) - - self.write_chunk(head) - - # TODO: materials - for material in data['materials']: - mate = MATE(header) - mate.from_dict(material) - - self.write_chunk(mate) - - for geometry in data['geometries']: - geom = GEOM(header) - geom.from_dict(geometry) - - self.write_chunk(geom) - - # TODO: cameras - for camera in data['cameras']: - came = CAME(header) - came.from_dict(camera) - - self.write_chunk(came) - - node = NODE(header) - node.from_dict({'nodes': data['nodes']}) - - self.write_chunk(node) - - wend = WEND() - - self.write_chunk(wend) - - def write_chunk(self, chunk: Chunk): - chunk.encode() - - self.writen += chunk.length.to_bytes(4, 'big') + chunk.chunk_name.encode('utf-8') + chunk.buffer - self.writen += binascii.crc32(chunk.chunk_name.encode('utf-8') + chunk.buffer).to_bytes(4, 'big') diff --git a/3d-converter-0.8.0/models_converter/utils/__init__.py b/3d-converter-0.8.0/models_converter/utils/__init__.py deleted file mode 100644 index 87b30e1..0000000 --- a/3d-converter-0.8.0/models_converter/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -__all__ = [ - 'reader', - 'writer', - 'matrix' -] diff --git a/3d-converter-0.8.0/models_converter/utils/reader.py b/3d-converter-0.8.0/models_converter/utils/reader.py deleted file mode 100644 index ddf0668..0000000 --- a/3d-converter-0.8.0/models_converter/utils/reader.py +++ /dev/null @@ -1,119 +0,0 @@ -class Reader: - def __init__(self, buffer: bytes, endian: str = 'big'): - self.buffer = buffer - self.endian = endian - self.i = 0 - - def read(self, length: int = 1) -> bytes: - result = self.buffer[self.i:self.i + length] - self.i += length - - return result - - def readUInteger(self, length: int = 1) -> int: - result = 0 - for x in range(length): - byte = self.buffer[self.i] - - bit_padding = x * 8 - if self.endian == 'big': - bit_padding = (8 * (length - 1)) - bit_padding - - result |= byte << bit_padding - self.i += 1 - - return result - - def readInteger(self, length: int = 1) -> int: - integer = self.readUInteger(length) - result = integer - if integer > 2 ** (length * 8) / 2: - result -= 2 ** (length * 8) - return result - - def readUInt64(self) -> int: - return self.readUInteger(8) - - def readInt64(self) -> int: - return self.readInteger(8) - - def readFloat(self) -> float: - as_int = self.readUInt32() - binary = bin(as_int) - binary = binary[2:].zfill(32) - - sign = -1 if binary[0] == '1' else 1 - exponent = int(binary[1:9], 2) - 127 - mantissa_base = binary[9:] - mantissa_bin = '1' + mantissa_base - mantissa = 0 - val = 1 - - if exponent == -127: - if mantissa_base[1] == -1: - return 0 - else: - exponent = -126 - mantissa_bin = '0' + mantissa_base - - for char in mantissa_bin: - mantissa += val * int(char) - val = val / 2 - - result = sign * 2 ** exponent * mantissa - return result - - def readUInt32(self) -> int: - return self.readUInteger(4) - - def readInt32(self) -> int: - return self.readInteger(4) - - def readNUInt16(self) -> float: - return self.readUInt16() / 65535 - - def readUInt16(self) -> int: - return self.readUInteger(2) - - def readNInt16(self) -> float: - return self.readInt16() / 32512 - - def readInt16(self) -> int: - return self.readInteger(2) - - def readUInt8(self) -> int: - return self.readUInteger() - - def readInt8(self) -> int: - return self.readInteger() - - def readBool(self) -> bool: - if self.readUInt8() >= 1: - return True - else: - return False - - readUInt = readUInteger - readInt = readInteger - - readULong = readUInt64 - readLong = readInt64 - - readNUShort = readNUInt16 - readNShort = readNInt16 - - readUShort = readUInt16 - readShort = readInt16 - - readUByte = readUInt8 - readByte = readInt8 - - def readChar(self, length: int = 1) -> str: - return self.read(length).decode('utf-8') - - def readString(self) -> str: - length = self.readUShort() - return self.readChar(length) - - def tell(self) -> int: - return self.i diff --git a/3d-converter-0.8.0/setup.py b/3d-converter-0.8.0/setup.py deleted file mode 100644 index 9db5ce4..0000000 --- a/3d-converter-0.8.0/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -import setuptools - -with open('README.md') as fh: - long_description = fh.read() - fh.close() - -setuptools.setup( - name='3d-converter', - version='0.8.0', - author='Vorono4ka', - author_email='crowo4ka@gmail.com', - description='Python 3D Models Converter', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/vorono4ka/3d-converter', - license='GPLv3', - packages=setuptools.find_packages(), - classifiers=[ - 'Programming Language :: Python :: 3', - 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', - 'Operating System :: OS Independent', - ], - python_requires='>=3.7', -) diff --git a/3d_converter.egg-info/PKG-INFO b/3d_converter.egg-info/PKG-INFO index 8ac1aac..c785db6 100644 --- a/3d_converter.egg-info/PKG-INFO +++ b/3d_converter.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: 3d-converter -Version: 0.8.1 +Version: 0.8.5 Summary: Python 3D Models Converter Home-page: https://github.com/vorono4ka/3d-converter Author: Vorono4ka @@ -8,7 +8,7 @@ Author-email: crowo4ka@gmail.com License: GPLv3 Description: ## `Python 3D Models Converter` - **Version**: 0.8.1 + **Version**: 0.8.5 ### Thanks a lot for motivating [AMIRMISTIK]! diff --git a/3d_converter.egg-info/SOURCES.txt b/3d_converter.egg-info/SOURCES.txt index 922e012..cf33c16 100644 --- a/3d_converter.egg-info/SOURCES.txt +++ b/3d_converter.egg-info/SOURCES.txt @@ -1,3 +1,4 @@ +LICENSE README.md setup.py 3d_converter.egg-info/PKG-INFO @@ -7,8 +8,27 @@ setup.py models_converter/__init__.py models_converter/formats/__init__.py models_converter/formats/dae.py -models_converter/formats/gltf.py models_converter/formats/obj.py +models_converter/formats/gltf/__init__.py +models_converter/formats/gltf/accessor.py +models_converter/formats/gltf/animation.py +models_converter/formats/gltf/asset.py +models_converter/formats/gltf/buffer.py +models_converter/formats/gltf/buffer_view.py +models_converter/formats/gltf/camera.py +models_converter/formats/gltf/chunk.py +models_converter/formats/gltf/gltf.py +models_converter/formats/gltf/gltf_property.py +models_converter/formats/gltf/image.py +models_converter/formats/gltf/material.py +models_converter/formats/gltf/mesh.py +models_converter/formats/gltf/node.py +models_converter/formats/gltf/parser.py +models_converter/formats/gltf/sampler.py +models_converter/formats/gltf/scene.py +models_converter/formats/gltf/skin.py +models_converter/formats/gltf/texture.py +models_converter/formats/gltf/texture_info.py models_converter/formats/scw/__init__.py models_converter/formats/scw/parser.py models_converter/formats/scw/writer.py diff --git a/README.md b/README.md index 6f2fcc1..d249ccf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## `Python 3D Models Converter` -**Version**: 0.8.1 +**Version**: 0.8.5 ### Thanks a lot for motivating [AMIRMISTIK]! diff --git a/models_converter/formats/dae.py b/models_converter/formats/dae.py index 1792769..5cc442f 100644 --- a/models_converter/formats/dae.py +++ b/models_converter/formats/dae.py @@ -285,6 +285,22 @@ def write(self, data: dict): id='3dConverterScene', name='3d-Converter Scene') + not_joint_nodes = [] + node_index = 0 + + parent_name = None + while node_index < len(not_joint_nodes) or len(not_joint_nodes) == 0: + if len(not_joint_nodes) > 0: + parent_name = not_joint_nodes[node_index] + + for _node in data['nodes']: + if _node['instances'] or _node['name'] == parent_name: + if not (_node['name'] in not_joint_nodes): + not_joint_nodes.append(_node['name']) + if not (_node['parent'] in not_joint_nodes): + not_joint_nodes.append(_node['parent']) + node_index += 1 + for node_data in data['nodes']: parent_name = node_data['parent'] parent = visual_scene @@ -318,7 +334,7 @@ def write(self, data: dict): symbol=symbol, target=f'#{target}') else: - if parent_name != '' and len(node_data['instances']) == 0: + if not (node_data['name'] in not_joint_nodes): node.attrib['type'] = 'JOINT' # diff --git a/models_converter/formats/gltf.py b/models_converter/formats/gltf.py deleted file mode 100644 index 281da6b..0000000 --- a/models_converter/formats/gltf.py +++ /dev/null @@ -1,779 +0,0 @@ -import json - -from models_converter.formats.dae import Writer -from models_converter.utils.reader import Reader - - -def to_camelcase(property_name: str): - words = property_name.split('_') - for word_index in range(len(words)): - word = words[word_index] - if word_index > 0: - word = list(word) - word[0] = word[0].upper() - - word = ''.join(word) - - words[word_index] = word - camelcase_name = ''.join(words) - return camelcase_name - - -def to_lowercase(property_name: str): - letters = list(property_name) - - for letter_index in range(len(letters)): - letter = letters[letter_index] - - if letter.isupper(): - letter = f'_{letter.lower()}' - - letters[letter_index] = letter - - lowercase_name = ''.join(letters) - return lowercase_name - - -def get_data_from_dict(dictionary, key, default=None): - if key in dictionary: - return dictionary[key] - return default - - -from_dict = get_data_from_dict - - -class GlTFProperty: - def __init__(self): - self.extensions = None - self.extras = None - - def from_dict(self, dictionary: dict): - if dictionary: - for key, value in dictionary.items(): - attribute_name = to_lowercase(key) - 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]: - attribute_value = value - elif issubclass(attribute_value_type, GlTFProperty): - if value_type is list: - value_type = attribute_value_type - values = [] - - for item in value: - new_value = value_type() - new_value.from_dict(item) - - values.append(new_value) - - attribute_value = values - else: - attribute_value = attribute_value_type() - attribute_value.from_dict(value) - - setattr(self, attribute_name, attribute_value) - - def to_dict(self) -> dict: - dictionary = {} - for key, value in self.__dict__.items(): - if value is not None: - attribute_name = to_camelcase(key) - value_type = type(value) - - attribute_value = None - - if value_type is list: - attribute_value = [] - for item in value: - item_type = type(item) - - if issubclass(item_type, GlTFProperty): - item = item.to_dict() - attribute_value.append(item) - elif issubclass(value_type, GlTFProperty): - attribute_value = value.to_dict() - elif attribute_value is None: - attribute_value = value - - dictionary[attribute_name] = attribute_value - return dictionary - - def __getitem__(self, item): - item = to_lowercase(item) - if hasattr(self, item): - return getattr(self, item) - else: - raise IndexError('The object has no attribute named ' + item) - - def __repr__(self) -> str: - return f'<{self.__class__.__name__} ({self.to_dict()})>' - - -class Accessor(GlTFProperty): - class Sparse(GlTFProperty): - class Indices(GlTFProperty): - def __init__(self): - super().__init__() - self.buffer_view = None - self.component_type = None - - self.byte_offset = 0 - - class Values(GlTFProperty): - def __init__(self): - super().__init__() - self.buffer_view = None - - self.byte_offset = 0 - - def __init__(self): - super().__init__() - self.count = None - self.indices = self.Indices() - self.values = self.Values() - - def __init__(self): - super().__init__() - self.component_type = None - self.count = None - self.type = None - - self.buffer_view = None - self.byte_offset = 0 - self.normalized = False - self.max = None - self.min = None - self.sparse = self.Sparse() - self.name = None - - -class Animation(GlTFProperty): - class AnimationSampler(GlTFProperty): - def __init__(self): - super().__init__() - self.input = None - self.output = None - - self.interpolation = None # Default: 'LINEAR' - - class Channel(GlTFProperty): - class Target(GlTFProperty): - def __init__(self): - super().__init__() - self.path = None - - self.node = None - - def __init__(self): - super().__init__() - self.sampler = None - self.target = self.Target() - - def __init__(self): - super().__init__() - self.channels = self.Channel() - self.samplers = self.AnimationSampler() - - self.name = None - - -class Asset(GlTFProperty): - def __init__(self): - super().__init__() - self.version = None - - self.copyright = None - self.generator = None - self.min_version = None - - -class Buffer(GlTFProperty): - def __init__(self): - super().__init__() - self.byte_length = None - - self.uri = None - self.name = None - - -class BufferView(GlTFProperty): - def __init__(self): - super().__init__() - self.buffer = None - self.byte_length = None - - self.byte_offset = 0 - self.byte_stride = None - self.target = None - self.name = None - - -class Camera(GlTFProperty): - class Orthographic(GlTFProperty): - def __init__(self): - super().__init__() - self.xmag = None - self.ymag = None - self.zfar = None - self.znear = None - - class Perspective(GlTFProperty): - def __init__(self): - super().__init__() - self.yfov = None - self.znear = None - - self.aspect_ratio = None - self.zfar = None - - def __init__(self): - super().__init__() - self.type = None - - self.orthographic = None - self.perspective = None - self.name = None - - -class Image(GlTFProperty): - def __init__(self): - super().__init__() - self.uri = None - self.mime_type = None - self.buffer_view = None - self.name = None - - -class Material(GlTFProperty): - class NormalTextureInfo(GlTFProperty): - def __init__(self): - super().__init__() - self.index = None - - self.tex_coord = None # Default: 0 - self.scale = None # Default: 1 - - class OcclusionTextureInfo(GlTFProperty): - def __init__(self): - super().__init__() - self.index = None - - self.tex_coord = None # Default: 0 - self.strength = None # Default: 1 - - class PbrMetallicRoughness(GlTFProperty): - def __init__(self): - super().__init__() - self.base_color_factor = None # Default: [1, 1, 1, 1] - self.base_color_texture = None - self.metallic_factor = None # Default: 1 - self.roughness_factor = None # Default: 1 - self.metallic_roughness_texture = None - - def __init__(self): - super().__init__() - self.name = None - self.pbr_metallic_roughness = None - self.normal_texture = None - self.occlusion_texture = None - self.emissive_texture = None - self.emissive_factor = None # Default: [0, 0, 0] - self.alpha_mode = None # Default: 'OPAQUE' - self.alpha_cutoff = None # Default: 0.5 - self.double_sided = None # Default: False - - -class Mesh(GlTFProperty): - class Primitive(GlTFProperty): - def __init__(self): - super().__init__() - self.attributes = None - - self.indices = None - self.material = None - self.mode = None # Default: 4 - self.targets = None - - def __init__(self): - super().__init__() - self.primitives = self.Primitive() - - self.weights = None - self.name = None - - -class Node(GlTFProperty): - def __init__(self): - super().__init__() - 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.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.weights = None - self.name = None - - -class Sampler(GlTFProperty): - def __init__(self): - super().__init__() - self.mag_filter = None - self.min_filter = None - self.wrap_s = None # Default: 10497 - self.wrap_t = None # Default: 10497 - self.name = None - - -class Scene(GlTFProperty): - def __init__(self): - super().__init__() - self.nodes = None - self.name = None - - -class Skin(GlTFProperty): - def __init__(self): - super().__init__() - self.joints = None - - self.inverse_bind_matrices = None - self.skeleton = None - self.name = None - - -class Texture(GlTFProperty): - def __init__(self): - super().__init__() - self.sampler = None - self.source = None - self.name = None - - -class TextureInfo(GlTFProperty): - def __init__(self): - super().__init__() - self.index = None - - self.tex_coord = None # Default: 0 - - -class GlTF(GlTFProperty): - def __init__(self): - super().__init__() - 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.scene = None - self.scenes = Scene() - self.skins = Skin() - self.textures = Texture() - - -class GlTFChunk: - def __init__(self): - self.chunk_length = 0 - self.chunk_name = b'' - self.data = b'' - - -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"') - - self.parsed = { - 'header': { - 'frame_rate': 30 - }, - 'materials': [], - 'geometries': [], - 'cameras': [], - 'nodes': [] - } - - self.version = None - self.length = None - - self.json_chunk = None - self.bin_chunk = None - - self.buffer_views = [] - self.accessors = [] - self.buffers = [] - - self.gltf = GlTF() - - def parse_bin(self): - super().__init__(self.bin_chunk.data, 'little') - - for buffer in self.gltf.buffers: - parsed_buffer = self.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') - - self.read(buffer_view.byte_offset) - - length = buffer_view.byte_length - data = self.read(length) - - self.buffer_views.append(data) - - for accessor in self.gltf.accessors: - super().__init__(self.buffer_views[accessor.buffer_view], '<') - temp_accessor = [] - - self.read(accessor.byte_offset) - - types = { - 5120: self.readByte, - 5121: self.readUByte, - 5122: self.readShort, - 5123: self.readUShort, - 5125: self.readUInt32, - 5126: self.readFloat - } - - items_count = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 - } - - for x in range(accessor.count): - temp_list = [] - for i in range(items_count[accessor.type]): - temp_list.append(types[accessor.component_type]()) - temp_accessor.append(temp_list) - - if accessor.normalized: - for item_index, data in enumerate(temp_accessor): - new_data = [] - for item in data: - if accessor['component_type'] == 5120: - new_data.append(max(item / 127, -1.0)) - elif accessor['component_type'] == 5121: - new_data.append(item / 255) - elif accessor['component_type'] == 5122: - new_data.append(max(item / 32767, -1.0)) - elif accessor['component_type'] == 5123: - new_data.append(item / 65535) - else: - new_data.append(item) - temp_accessor[item_index] = new_data - - self.accessors.append(temp_accessor) - - def parse(self): - # - - self.version = self.readUInt32() - self.length = self.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.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.gltf.from_dict(json.loads(self.json_chunk.data)) - - self.parse_bin() - - # - - scene_id = self.gltf.scene - scene = self.gltf.scenes[scene_id] - - for node_id in scene.nodes: - node = self.gltf.nodes[node_id] - self.parse_node(node) - - # - - def parse_node(self, node: Node, parent: str = None): - node_name = node.name - # node_name = node_name.split('|') - # if len(node_name) > 1: - # node_name = node_name[1] - # parent = node_name[0] - # else: - # node_name = node_name[0] - - # node_name = node_name.split(':') - # if len(node_name) > 1: - # if node_name[1] == 'PIV': - # print(node.name, node.translation) - # # else: - # print(node) - # node_name = node_name[0] - # else: - # node_name = node_name[0] - - node_data = { - 'name': node_name, - 'parent': parent, - 'has_target': False, - 'frames': [] - } - - if node.mesh: - node_data['has_target'] = True - node_data['target_type'] = 'GEOM' - - geometry_data = { - 'name': '', - 'group': '', - 'vertices': [], - 'have_bind_matrix': False, - 'weights': { - 'vertex_weights': [], - 'weights': [], - 'vcount': [] - }, - 'materials': [] - } - - if node.skin: - geometry_data['have_bind_matrix'] = True - geometry_data['bind_matrix'] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] - geometry_data['joints'] = [] - node_data['target_type'] = 'CONT' - - skin_id = node.skin - skin = self.gltf.skins[skin_id] - bind_matrices = self.accessors[skin.inverse_bind_matrices] - - for joint in skin.joints: - joint_index = skin['joints'].index(joint) - joint_node = self.gltf.nodes[joint] - joint_name = joint_node['name'] - matrix = bind_matrices[joint_index] - - joint_data = { - 'name': joint_name, - 'matrix': matrix - } - geometry_data['joints'].append(joint_data) - - mesh_id = node.mesh - mesh = self.gltf.meshes[mesh_id] - mesh_name = mesh.name - mesh_name = mesh_name.split('|') - - if len(mesh_name) > 1: - geometry_data['group'] = mesh_name[0] - geometry_data['name'] = mesh_name[1] - else: - geometry_data['name'] = mesh_name[0] - - node_data['target'] = geometry_data['name'] - node_data['binds'] = [] - - offsets = { - 'POSITION': 0, - 'NORMAL': 0, - 'TEXCOORD': 0 - } - - for primitive in mesh.primitives: - if primitive.to_dict() != {}: - primitive_index = mesh.primitives.index(primitive) - attributes = primitive.attributes - material_id = primitive.material - polygons_id = primitive.indices - - inputs = [] - - polygons = self.accessors[polygons_id] - material = self.gltf.materials[material_id] - - material_name = material.extensions['SC_shader']['name'] - node_data['binds'].append({ - 'symbol': material_name, - 'target': material_name - }) - - for attribute_id in attributes: - attribute = attributes[attribute_id] - if attribute_id == 'POSITION': - position = self.accessors[attribute] - - geometry_data['vertices'].append({ - 'type': 'POSITION', - 'name': f'position_{primitive_index}', - 'index': len(geometry_data['vertices']), - 'scale': 1, - 'vertex': position - }) - - inputs.append({ - 'type': 'POSITION', - 'offset': '1', - 'name': f'position_{primitive_index}', - }) - elif attribute_id == 'NORMAL': - normal = self.accessors[attribute] - - geometry_data['vertices'].append({ - 'type': 'NORMAL', - 'name': f'normal_{primitive_index}', - 'index': len(geometry_data['vertices']), - 'scale': 1, - 'vertex': normal - }) - - inputs.append({ - 'type': 'NORMAL', - 'offset': '0', - 'name': f'normal_{primitive_index}', - }) - elif attribute_id.startswith('TEXCOORD'): - texcoord = self.accessors[attribute] - - texcoord = [[item[0], 1 - item[1]] for item in texcoord] - - geometry_data['vertices'].append({ - 'type': 'TEXCOORD', - 'name': f'texcoord_{primitive_index}', - 'index': len(geometry_data['vertices']), - 'scale': 1, - 'vertex': texcoord - }) - - inputs.append({ - 'type': 'TEXCOORD', - 'offset': '2', - 'name': f'texcoord_{primitive_index}', - }) - elif attribute_id.startswith('JOINTS'): - vertex_weights = self.accessors[attribute] - elif attribute_id.startswith('WEIGHTS'): - weights = self.accessors[attribute] - - for x in range(len(vertex_weights)): - geometry_data['weights']['vcount'].append(0) - - temp_list = [ - [vertex_weights[x][0], weights[x][0]], - [vertex_weights[x][1], weights[x][1]], - [vertex_weights[x][2], weights[x][2]], - [vertex_weights[x][3], weights[x][3]] - ] - for pair in temp_list: - if pair[1] != 0: - geometry_data['weights']['vcount'][x] += 1 - geometry_data['weights']['vertex_weights'].append(pair[0]) - if pair[1] not in geometry_data['weights']['weights']: - geometry_data['weights']['weights'].append(pair[1]) - geometry_data['weights']['vertex_weights'].append( - geometry_data['weights']['weights'].index(pair[1]) - ) - - polygons = [ - [ - [ - value[0] + offsets['NORMAL'], - value[0] + offsets['POSITION'], - value[0] + offsets['TEXCOORD'] - ] for value in polygons[x:x + 3] - ] for x in range(0, len(polygons), 3) - ] - - geometry_data['materials'].append({ - 'name': material_name, - 'inputs': inputs, - 'polygons': polygons - }) - - for attribute_id in attributes: - if attribute_id == 'POSITION': - offsets['POSITION'] += len(position) - elif attribute_id == 'NORMAL': - offsets['NORMAL'] += len(normal) - elif attribute_id.startswith('TEXCOORD'): - offsets['TEXCOORD'] += len(texcoord) - - self.parsed['geometries'].append(geometry_data) - - self.parsed['nodes'].append(node_data) - - if node.translation or node.rotation or node.scale: - node_data['frames'].append({ - 'frame_id': 0, - 'rotation': {'x': 0, 'y': 0, 'z': 0, 'w': 0}, - 'position': {'x': 0, 'y': 0, 'z': 0}, - 'scale': {'x': 1, 'y': 1, 'z': 1} - }) - - if node.translation: - node_data['frames'][0]['position'] = { - 'x': node.translation[0], - 'y': node.translation[1], - 'z': node.translation[2] - } - if node.rotation: - node_data['frames'][0]['rotation'] = { - 'x': node.rotation[0], - 'y': node.rotation[1], - 'z': node.rotation[2], - 'w': node.rotation[3] - } - if node.scale: - node_data['frames'][0]['scale'] = { - 'x': node.scale[0], - 'y': node.scale[1], - 'z': node.scale[2] - } - - if node.children: - for child_id in node.children: - child = self.gltf.nodes[child_id] - self.parse_node(child, node_name) - - -if __name__ == '__main__': - with open('../crow_geo.glb', 'rb') as fh: - file_data = fh.read() - fh.close() - parser = Parser(file_data) - parser.parse() - - writer = Writer() - with open('../crow_geo.dae', 'w') as fh: - fh.write(writer.writen) - fh.close() diff --git a/models_converter/formats/gltf/__init__.py b/models_converter/formats/gltf/__init__.py new file mode 100644 index 0000000..bad092d --- /dev/null +++ b/models_converter/formats/gltf/__init__.py @@ -0,0 +1,35 @@ +from .accessor import Accessor +from .animation import Animation +from .asset import Asset +from .buffer import Buffer +from .buffer_view import BufferView +from .camera import Camera +from .gltf_property import GlTFProperty +from .image import Image +from .material import Material +from .mesh import Mesh +from .node import Node +from .sampler import Sampler +from .scene import Scene +from .skin import Skin +from .texture import Texture + +from .parser import Parser + +__all__ = [ + 'Accessor', + 'Animation', + 'Asset', + 'Buffer', + 'BufferView', + 'Camera', + 'Image', + 'Material', + 'Mesh', + 'Node', + 'Sampler', + 'Scene', + 'Skin', + 'Texture', + 'Parser' +] diff --git a/models_converter/formats/gltf/accessor.py b/models_converter/formats/gltf/accessor.py new file mode 100644 index 0000000..ad20dd0 --- /dev/null +++ b/models_converter/formats/gltf/accessor.py @@ -0,0 +1,39 @@ +from .gltf_property import GlTFProperty + + +class Accessor(GlTFProperty): + class Sparse(GlTFProperty): + class Indices(GlTFProperty): + def __init__(self): + super().__init__() + self.buffer_view = None + self.component_type = None + + self.byte_offset = 0 + + class Values(GlTFProperty): + def __init__(self): + super().__init__() + self.buffer_view = None + + self.byte_offset = 0 + + def __init__(self): + super().__init__() + self.count = None + self.indices = self.Indices() + self.values = self.Values() + + def __init__(self): + super().__init__() + self.component_type = None + self.count = None + self.type = None + + self.buffer_view = None + self.byte_offset = 0 + self.normalized = False + self.max = None + self.min = None + self.sparse = self.Sparse() + self.name = None diff --git a/models_converter/formats/gltf/animation.py b/models_converter/formats/gltf/animation.py new file mode 100644 index 0000000..d9476d0 --- /dev/null +++ b/models_converter/formats/gltf/animation.py @@ -0,0 +1,32 @@ +from .gltf_property import GlTFProperty + + +class Animation(GlTFProperty): + class AnimationSampler(GlTFProperty): + def __init__(self): + super().__init__() + self.input = None + self.output = None + + self.interpolation = None # Default: 'LINEAR' + + class Channel(GlTFProperty): + class Target(GlTFProperty): + def __init__(self): + super().__init__() + self.path = None + + self.node = None + + def __init__(self): + super().__init__() + self.sampler = None + self.target = self.Target() + + def __init__(self): + super().__init__() + self.channels = self.Channel() + self.samplers = self.AnimationSampler() + + self.name = None + diff --git a/models_converter/formats/gltf/asset.py b/models_converter/formats/gltf/asset.py new file mode 100644 index 0000000..d063162 --- /dev/null +++ b/models_converter/formats/gltf/asset.py @@ -0,0 +1,11 @@ +from .gltf_property import GlTFProperty + + +class Asset(GlTFProperty): + def __init__(self): + super().__init__() + self.version = None + + self.copyright = None + self.generator = None + self.min_version = None diff --git a/models_converter/formats/gltf/buffer.py b/models_converter/formats/gltf/buffer.py new file mode 100644 index 0000000..0008f51 --- /dev/null +++ b/models_converter/formats/gltf/buffer.py @@ -0,0 +1,10 @@ +from .gltf_property import GlTFProperty + + +class Buffer(GlTFProperty): + def __init__(self): + super().__init__() + self.byte_length = None + + self.uri = None + self.name = None diff --git a/models_converter/formats/gltf/buffer_view.py b/models_converter/formats/gltf/buffer_view.py new file mode 100644 index 0000000..1e7bfea --- /dev/null +++ b/models_converter/formats/gltf/buffer_view.py @@ -0,0 +1,13 @@ +from .gltf_property import GlTFProperty + + +class BufferView(GlTFProperty): + def __init__(self): + super().__init__() + self.buffer = None + self.byte_length = None + + self.byte_offset = 0 + self.byte_stride = None + self.target = None + self.name = None diff --git a/models_converter/formats/gltf/camera.py b/models_converter/formats/gltf/camera.py new file mode 100644 index 0000000..18e849a --- /dev/null +++ b/models_converter/formats/gltf/camera.py @@ -0,0 +1,28 @@ +from .gltf_property import GlTFProperty + + +class Camera(GlTFProperty): + class Orthographic(GlTFProperty): + def __init__(self): + super().__init__() + self.xmag = None + self.ymag = None + self.zfar = None + self.znear = None + + class Perspective(GlTFProperty): + def __init__(self): + super().__init__() + self.yfov = None + self.znear = None + + self.aspect_ratio = None + self.zfar = None + + def __init__(self): + super().__init__() + self.type = None + + self.orthographic = None + self.perspective = None + self.name = None diff --git a/models_converter/formats/gltf/chunk.py b/models_converter/formats/gltf/chunk.py new file mode 100644 index 0000000..b21a94e --- /dev/null +++ b/models_converter/formats/gltf/chunk.py @@ -0,0 +1,5 @@ +class GlTFChunk: + def __init__(self): + self.chunk_length = 0 + self.chunk_name = b'' + self.data = b'' diff --git a/models_converter/formats/gltf/gltf.py b/models_converter/formats/gltf/gltf.py new file mode 100644 index 0000000..c5a4b7e --- /dev/null +++ b/models_converter/formats/gltf/gltf.py @@ -0,0 +1,26 @@ +from .gltf_property import GlTFProperty +from . import * + + +class GlTF(GlTFProperty): + def __init__(self): + super().__init__() + 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.scene = None + 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 new file mode 100644 index 0000000..7d554c4 --- /dev/null +++ b/models_converter/formats/gltf/gltf_property.py @@ -0,0 +1,94 @@ +def to_camelcase(property_name: str): + words = property_name.split('_') + for word_index in range(len(words)): + word = words[word_index] + if word_index > 0: + word = word.capitalize() + words[word_index] = word + camelcase_name = ''.join(words) + return camelcase_name + + +def to_lowercase(property_name: str): + letters = list(property_name) + + for letter_index in range(len(letters)): + letter = letters[letter_index] + + if letter.isupper(): + letter = f'_{letter.lower()}' + + letters[letter_index] = letter + + lowercase_name = ''.join(letters) + return lowercase_name + + +class GlTFProperty: + def __init__(self): + self.extensions = None + self.extras = None + + def from_dict(self, dictionary: dict): + if dictionary: + for key, value in dictionary.items(): + attribute_name = to_lowercase(key) + 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]: + attribute_value = value + elif issubclass(attribute_value_type, GlTFProperty): + if value_type is list: + value_type = attribute_value_type + values = [] + + for item in value: + new_value = value_type() + new_value.from_dict(item) + + values.append(new_value) + + attribute_value = values + else: + attribute_value = attribute_value_type() + attribute_value.from_dict(value) + + setattr(self, attribute_name, attribute_value) + + def to_dict(self) -> dict: + dictionary = {} + for key, value in self.__dict__.items(): + if value is not None: + attribute_name = to_camelcase(key) + value_type = type(value) + + attribute_value = None + + if value_type is list: + attribute_value = [] + for item in value: + item_type = type(item) + + if issubclass(item_type, GlTFProperty): + item = item.to_dict() + attribute_value.append(item) + elif issubclass(value_type, GlTFProperty): + attribute_value = value.to_dict() + elif attribute_value is None: + attribute_value = value + + dictionary[attribute_name] = attribute_value + return dictionary + + def __getitem__(self, item): + item = to_lowercase(item) + if hasattr(self, item): + return getattr(self, item) + else: + raise IndexError('The object has no attribute named ' + item) + + def __repr__(self) -> str: + return f'<{self.__class__.__name__} ({self.to_dict()})>' diff --git a/models_converter/formats/gltf/image.py b/models_converter/formats/gltf/image.py new file mode 100644 index 0000000..270cbb8 --- /dev/null +++ b/models_converter/formats/gltf/image.py @@ -0,0 +1,10 @@ +from .gltf_property import GlTFProperty + + +class Image(GlTFProperty): + def __init__(self): + super().__init__() + self.uri = None + self.mime_type = None + self.buffer_view = None + self.name = None diff --git a/models_converter/formats/gltf/material.py b/models_converter/formats/gltf/material.py new file mode 100644 index 0000000..35cc76f --- /dev/null +++ b/models_converter/formats/gltf/material.py @@ -0,0 +1,40 @@ +from .gltf_property import GlTFProperty + + +class Material(GlTFProperty): + class NormalTextureInfo(GlTFProperty): + def __init__(self): + super().__init__() + self.index = None + + self.tex_coord = None # Default: 0 + self.scale = None # Default: 1 + + class OcclusionTextureInfo(GlTFProperty): + def __init__(self): + super().__init__() + self.index = None + + self.tex_coord = None # Default: 0 + self.strength = None # Default: 1 + + class PbrMetallicRoughness(GlTFProperty): + def __init__(self): + super().__init__() + self.base_color_factor = None # Default: [1, 1, 1, 1] + self.base_color_texture = None + self.metallic_factor = None # Default: 1 + self.roughness_factor = None # Default: 1 + self.metallic_roughness_texture = None + + def __init__(self): + super().__init__() + self.name = None + self.pbr_metallic_roughness = None + self.normal_texture = None + self.occlusion_texture = None + self.emissive_texture = None + self.emissive_factor = None # Default: [0, 0, 0] + self.alpha_mode = None # Default: 'OPAQUE' + self.alpha_cutoff = None # Default: 0.5 + self.double_sided = None # Default: False diff --git a/models_converter/formats/gltf/mesh.py b/models_converter/formats/gltf/mesh.py new file mode 100644 index 0000000..f636ae1 --- /dev/null +++ b/models_converter/formats/gltf/mesh.py @@ -0,0 +1,20 @@ +from .gltf_property import GlTFProperty + + +class Mesh(GlTFProperty): + class Primitive(GlTFProperty): + def __init__(self): + super().__init__() + self.attributes = None + + self.indices = None + self.material = None + self.mode = None # Default: 4 + self.targets = None + + def __init__(self): + super().__init__() + 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 new file mode 100644 index 0000000..920e39d --- /dev/null +++ b/models_converter/formats/gltf/node.py @@ -0,0 +1,16 @@ +from .gltf_property import GlTFProperty + + +class Node(GlTFProperty): + def __init__(self): + super().__init__() + 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.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.weights = None + self.name = None diff --git a/models_converter/formats/gltf/parser.py b/models_converter/formats/gltf/parser.py new file mode 100644 index 0000000..807cb65 --- /dev/null +++ b/models_converter/formats/gltf/parser.py @@ -0,0 +1,378 @@ +import json + +from models_converter.formats.gltf.chunk import GlTFChunk +from models_converter.formats.gltf.gltf import GlTF +from models_converter.formats.gltf.node import Node +from models_converter.utils.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"') + + self.parsed = { + 'header': { + 'frame_rate': 30 + }, + 'materials': [], + 'geometries': [], + 'cameras': [], + 'nodes': [] + } + + self.version = None + self.length = None + + self.json_chunk = None + self.bin_chunk = None + + self.buffer_views = [] + self.accessors = [] + self.buffers = [] + + self.gltf = GlTF() + + def parse_bin(self): + super().__init__(self.bin_chunk.data, 'little') + + for buffer in self.gltf.buffers: + parsed_buffer = self.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') + + self.read(buffer_view.byte_offset) + + length = buffer_view.byte_length + data = self.read(length) + + self.buffer_views.append(data) + + for accessor in self.gltf.accessors: + super().__init__(self.buffer_views[accessor.buffer_view], 'little') + temp_accessor = [] + + self.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) + } + + items_count = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 + } + + component_nb = items_count[accessor.type] + read_type, bytes_per_elem = types[accessor.component_type] + default_stride = bytes_per_elem * component_nb + + 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()) + + temp_accessor = [temp_list[x:x + component_nb] for x in range(0, num_elems, elems_per_stride)] + + 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) + + def parse(self): + # + + self.version = self.readUInt32() + self.length = self.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.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.gltf.from_dict(json.loads(self.json_chunk.data)) + + self.parse_bin() + + # + + scene_id = self.gltf.scene + scene = self.gltf.scenes[scene_id] + + for node_id in scene.nodes: + node = self.gltf.nodes[node_id] + self.parse_node(node) + + # + + def parse_node(self, node: Node, parent: str = None): + node_name = node.name + + node_data = { + 'name': node_name, + 'parent': parent, + 'instances': [], + 'frames': [] + } + + instance = None + if node.mesh: + instance = { + 'instance_type': 'GEOM', + 'instance_name': None, + 'binds': [] + } + geometry_data = { + 'name': '', + 'group': '', + 'vertices': [], + 'have_bind_matrix': False, + 'weights': { + 'vertex_weights': [], + 'weights': [], + 'vcount': [] + }, + 'materials': [] + } + + if node.skin: + instance['instance_type'] = 'CONT' + + geometry_data['have_bind_matrix'] = True + geometry_data['bind_matrix'] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + geometry_data['joints'] = [] + + skin_id = node.skin + skin = self.gltf.skins[skin_id] + bind_matrices = self.accessors[skin.inverse_bind_matrices] + bind_matrices = [[m[0::4], m[1::4], m[2::4], m[3::4]] for m in bind_matrices] + for matrix_index in range(len(bind_matrices)): + m = bind_matrices[matrix_index] + + matrix = m[0] + matrix.extend(m[1]) + matrix.extend(m[2]) + matrix.extend(m[3]) + + bind_matrices[matrix_index] = matrix + + for joint in skin.joints: + joint_index = skin['joints'].index(joint) + joint_node = self.gltf.nodes[joint] + joint_name = joint_node['name'] + matrix = bind_matrices[joint_index] + + joint_data = { + 'name': joint_name, + 'matrix': matrix + } + geometry_data['joints'].append(joint_data) + + mesh_id = node.mesh + mesh = self.gltf.meshes[mesh_id] + mesh_name = mesh.name + mesh_name = mesh_name.split('|') + + if len(mesh_name) > 1: + geometry_data['group'] = mesh_name[0] + geometry_data['name'] = mesh_name[1] + else: + geometry_data['name'] = mesh_name[0] + + instance['instance_name'] = geometry_data['name'] + + offsets = { + 'POSITION': 0, + 'NORMAL': 0, + 'TEXCOORD': 0 + } + + for primitive in mesh.primitives: + if primitive.to_dict() != {}: + primitive_index = mesh.primitives.index(primitive) + attributes = primitive.attributes + material_id = primitive.material + polygons_id = primitive.indices + + inputs = [] + + polygons = self.accessors[polygons_id] + material = self.gltf.materials[material_id] + + material_name = material.extensions['SC_shader']['name'] + instance['binds'].append({ + 'symbol': material_name, + 'target': material_name + }) + + position = [] + normal = [] + texcoord = [] + + vertex_weights = 0 + + for attribute_id in attributes: + attribute = attributes[attribute_id] + vertex = None + + if attribute_id == 'POSITION': + position = self.accessors[attribute] + vertex = position + elif attribute_id == 'NORMAL': + normal = self.accessors[attribute] + vertex = normal + elif attribute_id.startswith('TEXCOORD'): + texcoord = self.accessors[attribute] + + texcoord = [[item[0], 1 - item[1]] for item in texcoord] + attribute_id = 'TEXCOORD' + vertex = texcoord + elif attribute_id.startswith('JOINTS'): + vertex_weights = self.accessors[attribute] + elif attribute_id.startswith('WEIGHTS'): + weights = self.accessors[attribute] + + for x in range(len(vertex_weights)): + geometry_data['weights']['vcount'].append(0) + + temp_list = [ + [vertex_weights[x][0], weights[x][0]], + [vertex_weights[x][1], weights[x][1]], + [vertex_weights[x][2], weights[x][2]], + [vertex_weights[x][3], weights[x][3]] + ] + for pair in temp_list: + if pair[1] != 0: + geometry_data['weights']['vcount'][x] += 1 + geometry_data['weights']['vertex_weights'].append(pair[0]) + if pair[1] not in geometry_data['weights']['weights']: + geometry_data['weights']['weights'].append(pair[1]) + geometry_data['weights']['vertex_weights'].append( + geometry_data['weights']['weights'].index(pair[1]) + ) + for weight_index in range(len(geometry_data['weights']['weights'])): + geometry_data['weights']['weights'][weight_index] /= 255 + + if vertex: + geometry_data['vertices'].append({ + 'type': attribute_id, + 'name': f'{attribute_id.lower()}_{primitive_index}', + 'index': len(geometry_data['vertices']), + 'scale': 1, + 'vertex': vertex + }) + + inputs.append({ + 'type': attribute_id, + 'offset': len(inputs), + 'name': f'{attribute_id.lower()}_{primitive_index}', + }) + + polygons = [ + [ + [ + point[0] + offsets['NORMAL'], + point[0] + offsets['POSITION'], + point[0] + offsets['TEXCOORD'] + ] for point in polygons[x:x + 3] + ] for x in range(0, len(polygons), 3) + ] + + geometry_data['materials'].append({ + 'name': material_name, + 'inputs': inputs, + 'polygons': polygons + }) + + for attribute_id in attributes: + if attribute_id == 'POSITION': + offsets['POSITION'] += len(position) + elif attribute_id == 'NORMAL': + offsets['NORMAL'] += len(normal) + elif attribute_id.startswith('TEXCOORD'): + offsets['TEXCOORD'] += len(texcoord) + + self.parsed['geometries'].append(geometry_data) + + if instance is not None: + node_data['instances'].append(instance) + + self.parsed['nodes'].append(node_data) + + if node.translation or node.rotation or node.scale: + node_data['frames'].append({ + 'frame_id': 0, + 'rotation': {'x': 0, 'y': 0, 'z': 0, 'w': 0}, + 'position': {'x': 0, 'y': 0, 'z': 0}, + 'scale': {'x': 1, 'y': 1, 'z': 1} + }) + + if node.translation: + node_data['frames'][0]['position'] = { + 'x': node.translation[0], + 'y': node.translation[1], + 'z': node.translation[2] + } + if node.rotation: + node_data['frames'][0]['rotation'] = { + 'x': node.rotation[0], + 'y': node.rotation[1], + 'z': node.rotation[2], + 'w': node.rotation[3] + } + if node.scale: + node_data['frames'][0]['scale'] = { + 'x': node.scale[0], + 'y': node.scale[1], + 'z': node.scale[2] + } + + if node.children: + for child_id in node.children: + child = self.gltf.nodes[child_id] + self.parse_node(child, node_name) diff --git a/models_converter/formats/gltf/sampler.py b/models_converter/formats/gltf/sampler.py new file mode 100644 index 0000000..d18167e --- /dev/null +++ b/models_converter/formats/gltf/sampler.py @@ -0,0 +1,11 @@ +from .gltf_property import GlTFProperty + + +class Sampler(GlTFProperty): + def __init__(self): + super().__init__() + self.mag_filter = None + self.min_filter = None + self.wrap_s = None # Default: 10497 + self.wrap_t = None # Default: 10497 + self.name = None diff --git a/models_converter/formats/gltf/scene.py b/models_converter/formats/gltf/scene.py new file mode 100644 index 0000000..916e5dd --- /dev/null +++ b/models_converter/formats/gltf/scene.py @@ -0,0 +1,8 @@ +from .gltf_property import GlTFProperty + + +class Scene(GlTFProperty): + def __init__(self): + super().__init__() + self.nodes = None + self.name = None diff --git a/models_converter/formats/gltf/skin.py b/models_converter/formats/gltf/skin.py new file mode 100644 index 0000000..d0334bd --- /dev/null +++ b/models_converter/formats/gltf/skin.py @@ -0,0 +1,11 @@ +from .gltf_property import GlTFProperty + + +class Skin(GlTFProperty): + def __init__(self): + super().__init__() + self.joints = None + + self.inverse_bind_matrices = None + self.skeleton = None + self.name = None diff --git a/models_converter/formats/gltf/texture.py b/models_converter/formats/gltf/texture.py new file mode 100644 index 0000000..0f1efda --- /dev/null +++ b/models_converter/formats/gltf/texture.py @@ -0,0 +1,9 @@ +from .gltf_property import GlTFProperty + + +class Texture(GlTFProperty): + def __init__(self): + super().__init__() + self.sampler = None + self.source = None + self.name = None diff --git a/models_converter/formats/gltf/texture_info.py b/models_converter/formats/gltf/texture_info.py new file mode 100644 index 0000000..78749ef --- /dev/null +++ b/models_converter/formats/gltf/texture_info.py @@ -0,0 +1,9 @@ +from models_converter.formats.gltf import GlTFProperty + + +class TextureInfo(GlTFProperty): + def __init__(self): + super().__init__() + self.index = None + + self.tex_coord = None # Default: 0 diff --git a/models_converter/formats/obj.py b/models_converter/formats/obj.py index ecdada4..673edd3 100644 --- a/models_converter/formats/obj.py +++ b/models_converter/formats/obj.py @@ -47,7 +47,7 @@ def write(self, data: dict): self.writen += f'{temp_string}\n' self.writen += '\n\n' for material in materials: - self.writen += f'o {name}_{material["name"]}\n\n' + self.writen += f'o {name}|{material["name"]}\n\n' for item in material['polygons']: temp_string = 'f ' for subitem in item: @@ -63,11 +63,14 @@ def write(self, data: dict): class Parser: - def __init__(self, file_data: str): - # + def __init__(self, file_data: bytes or str): + if type(file_data) is bytes: + file_data = file_data.decode() self.parsed = {'header': {'version': 2, 'frame_rate': 30, + 'first_frame': 0, + 'last_frame': 0, 'materials_file': 'sc3d/character_materials.scw'}, 'materials': [], 'geometries': [], @@ -76,22 +79,27 @@ def __init__(self, file_data: str): self.lines = file_data.split('\n') - # self.position_temp, self.position = [], [] self.normals_temp, self.normals = [], [] self.texcoord_temp, self.texcoord = [], [] - # - self.polygons = [] + def parse(self): + polygons = [] + geometry_name = None + material = 'character_mat' + position_scale, normals_scale, texcoord_scale = 1, 1, 1 - # + vertices_offsets = { + 'POSITION': 0, + 'TEXCOORD': 0, + 'NORMAL': 0 + } - self.parse() + names = [line[2:].split('|')[0] + for line in list(filter(lambda line: line.startswith('o '), self.lines))] - def parse(self): - geometry_name = 'This model haven \'t a chunk_name!:( Its VERY SAD!' - is_first_name = True - for line in self.lines: + for line_index in range(len(self.lines)): + line = self.lines[line_index] items = line.split()[1:] if line.startswith('v '): # POSITION for item in items: @@ -119,67 +127,110 @@ def parse(self): break for x in item.split('/'): second_temp_list.append(int(x) - 1) - temp_list.append([second_temp_list[0], second_temp_list[2], second_temp_list[1]]) - self.polygons.append(temp_list) + temp_list.append([second_temp_list[0] - vertices_offsets['POSITION'], + second_temp_list[2] - vertices_offsets['TEXCOORD'], + second_temp_list[1] - vertices_offsets['NORMAL']]) + polygons.append(temp_list) elif line.startswith('o '): - # if not is_first_name: - # position_scale = self.get_vertex_scale(self.position_temp) - # normals_scale = self.get_vertex_scale(self.normals_temp) - # texcoord_scale = self.get_vertex_scale(self.texcoord_temp) - # - # self.parsed['geometries'].append({ - # 'chunk_name': geometry_name, - # 'group': '', - # 'vertices': [ - # {'type': 'POSITION', 'index': 0, 'scale': position_scale, 'vertex': self.position}, - # {'type': 'NORMAL', 'index': 1, 'scale': normals_scale, 'vertex': self.normals}, - # {'type': 'TEXCOORD', 'index': 2, 'scale': texcoord_scale, 'vertex': self.texcoord} - # ], - # 'have_bind_matrix': False, - # 'materials': [{'chunk_name': 'character_mat', 'polygons': self.polygons}] - # }) - # - # # - # - # # - # self.position_temp, self.position = [], [] - # self.normals_temp, self.normals = [], [] - # self.texcoord_temp, self.texcoord = [], [] - # # - # - # self.polygons = [] - # - # # - # geometry_name = line.split('o ')[0] - if is_first_name: - geometry_name = line.split('o ')[0] - - position_scale = self.get_vertex_scale(self.position_temp) - normals_scale = self.get_vertex_scale(self.normals_temp) - texcoord_scale = self.get_vertex_scale(self.texcoord_temp) - - for x in range(0, len(self.position_temp), 3): - self.position.append(self.position_temp[x: x + 3]) - - for x in range(0, len(self.normals_temp), 3): - self.normals.append(self.normals_temp[x: x + 3]) - - for x in range(0, len(self.texcoord_temp), 2): - self.texcoord.append(self.texcoord_temp[x: x + 2]) - - self.parsed['geometries'].append({ - 'chunk_name': geometry_name, - 'group': '', - 'vertices': [ - {'type': 'POSITION', 'index': 0, 'scale': position_scale, 'vertex': self.position}, - {'type': 'NORMAL', 'index': 1, 'scale': normals_scale, 'vertex': self.normals}, - {'type': 'TEXCOORD', 'index': 2, 'scale': texcoord_scale, 'vertex': self.texcoord} - ], - 'have_bind_matrix': False, - 'materials': [{'chunk_name': 'character_mat', 'polygons': self.polygons}] - }) - - # TODO: nodes + geometry_name = items[0] + if '|' in items[0]: + geometry_name, material = items[0].split('|') + + if self.position_temp: + self.position = [] + position_scale = self.get_vertex_scale(self.position_temp) + for x in range(0, len(self.position_temp), 3): + self.position.append([vertex / position_scale for vertex in self.position_temp[x: x + 3]]) + + if self.normals_temp: + self.normals = [] + normals_scale = self.get_vertex_scale(self.normals_temp) + for x in range(0, len(self.normals_temp), 3): + self.normals.append([vertex / normals_scale for vertex in self.normals_temp[x: x + 3]]) + + if self.texcoord_temp: + self.texcoord = [] + texcoord_scale = self.get_vertex_scale(self.texcoord_temp) + for x in range(0, len(self.texcoord_temp), 2): + self.texcoord.append([vertex / texcoord_scale for vertex in self.texcoord_temp[x: x + 2]]) + + if not line.startswith('f ') and polygons and geometry_name and \ + self.position and self.normals and self.texcoord: + self.position_temp = [] + self.normals_temp = [] + self.texcoord_temp = [] + + if len(names) >= len(self.parsed['geometries']) + 1 and \ + names[len(self.parsed['geometries']) + 1] != geometry_name: + vertices_offsets['POSITION'] += len(self.position) + vertices_offsets['TEXCOORD'] += len(self.normals) + vertices_offsets['NORMAL'] += len(self.texcoord) + if not (self.parsed['geometries'] and self.parsed['geometries'][-1]['name'] == geometry_name): + self.parsed['geometries'].append({ + 'name': geometry_name, + 'group': '', + 'vertices': [ + { + 'type': 'POSITION', + 'name': 'position_0', + 'index': 0, + 'scale': position_scale, + 'vertex': self.position + }, + { + 'type': 'NORMAL', + 'name': 'normal_0', + 'index': 1, + 'scale': normals_scale, + 'vertex': self.normals + }, + { + 'type': 'TEXCOORD', + 'name': 'texcoord_0', + 'index': 2, + 'scale': texcoord_scale, + 'vertex': self.texcoord + } + ], + 'have_bind_matrix': False, + 'materials': [] + }) + self.parsed['geometries'][-1]['materials'].append( + {'name': material, 'polygons': polygons, 'inputs': [ + {'offset': self.parsed['geometries'][-1]['vertices'].index(vertex), + 'name': vertex['name'], + 'type': vertex['type']} + for vertex in self.parsed['geometries'][-1]['vertices'] + ]} + ) + + material = 'character_mat' + polygons = [] + + for geometry in self.parsed['geometries']: + self.parsed['nodes'].append({ + 'name': geometry['name'], + 'parent': '', + 'instances': [ + { + 'instance_name': geometry['name'], + 'instance_type': 'GEOM', + 'binds': [ + {'symbol': material['name'], 'target': material['name']} + for material in geometry['materials'] + ] + } + ], + 'frames': [ + { + 'frame_id': 0, + 'rotation': {'x': 0, 'y': 0, 'z': 0, 'w': 0}, + 'position': {'x': 0, 'y': 0, 'z': 0}, + 'scale': {'x': 1, 'y': 1, 'z': 1} + } + ], + 'frames_settings': [False] * 8 + }) @staticmethod def get_vertex_scale(vertex_data: list): diff --git a/models_converter/formats/scw/chunks/head.py b/models_converter/formats/scw/chunks/head.py index 028f489..a0a4371 100644 --- a/models_converter/formats/scw/chunks/head.py +++ b/models_converter/formats/scw/chunks/head.py @@ -11,20 +11,23 @@ def parse(self, buffer: bytes): setattr(self, 'version', self.readUShort()) setattr(self, 'frame_rate', self.readUShort()) - setattr(self, 'v1', self.readUShort()) - setattr(self, 'animation_end_frame', self.readUShort()) + setattr(self, 'first_frame', self.readUShort()) + setattr(self, 'last_frame', self.readUShort()) setattr(self, 'materials_file', self.readString()) - if self.get('version') == 2: + if self.get('version') >= 1: setattr(self, 'v3', self.readUByte()) def encode(self): super().encode() - self.writeUShort(2) # getattr(self, 'version') + setattr(self, 'version', 2) + + self.writeUShort(getattr(self, 'version')) self.writeUShort(getattr(self, 'frame_rate')) - self.writeUShort(0) # getattr(self, 'v1') - self.writeUShort(getattr(self, 'last_frame')) # animation end frame + self.writeUShort(getattr(self, 'first_frame')) + self.writeUShort(getattr(self, 'last_frame')) self.writeString(getattr(self, 'materials_file')) - self.writeUByte(0) # getattr(self, 'v3') + if self.get('version') >= 1: + self.writeUByte(0) # getattr(self, 'v3') self.length = len(self.buffer) diff --git a/setup.py b/setup.py index 2c275ab..922a0e2 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setuptools.setup( name='3d-converter', - version='0.8.1', + version='0.8.5', author='Vorono4ka', author_email='crowo4ka@gmail.com', description='Python 3D Models Converter',