diff --git a/.travis.yml b/.travis.yml index da02fae..a91c16e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: install: - sudo add-apt-repository -y ppa:thomas-schiex/blender - sudo apt-get update - - sudo apt-get install -y blender + - sudo apt-get install -y --allow-unauthenticated blender - blender --version script: diff --git a/README.md b/README.md index 92809f5..8a093f9 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,24 @@ [![Build Status](https://travis-ci.org/ksons/gltf-blender-importer.svg?branch=master)](https://travis-ci.org/ksons/gltf-blender-importer) -Blender importer for glTF 2.0 (alpha) - -Until now, only designed to load the glTF 2.0 [sample models](https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0). Expect updates soon; feel free to contribute! +Blender importer for glTF 2.0. ## Installation -Blender ≥2.78 is recommended. See [INSTALL.md](INSTALL.md) for installation instructions. +Blender ≥2.78 is recommended. + +Find the latest archive here: + + + +You can install the archive using the ``Install from File...`` button in ``File->User preferences...->Add-ons``. +After installing you have to find the add-on and activate it. +

+ +After this procedure, the exporter is available from ``File->Import->glTF JSON (.gltf/.glb)``. + + + +See [INSTALL.md](INSTALL.md) for further installation instructions. ## Samples Renderings ![BoomBox](https://github.com/ksons/gltf-blender-importer/blob/master/doc/boom-box.png) diff --git a/addons/io_scene_gltf/__init__.py b/addons/io_scene_gltf/__init__.py index 67e5cd1..f2648d0 100644 --- a/addons/io_scene_gltf/__init__.py +++ b/addons/io_scene_gltf/__init__.py @@ -6,16 +6,18 @@ from bpy.props import StringProperty from bpy_extras.io_utils import ImportHelper -from io_scene_gltf import animation, buffer, material, mesh, node +from io_scene_gltf import animation, buffer, material, mesh, node, camera bl_info = { 'name': 'glTF 2.0 Importer', - 'author': 'Kristian Sons', + 'author': 'Kristian Sons (ksons), scurest', 'blender': (2, 71, 0), - 'location': 'File > Import', - 'description': '', + 'version': (0, 0, 0), + 'location': 'File > Import > glTF JSON (.gltf/.glb)', + 'description': 'Importer for the glTF 2.0 file format.', 'warning': '', - 'wiki_url': '', + 'wiki_url': 'https://github.com/ksons/gltf-blender-importer/blob/master/README.md', + 'tracker_url': 'https://github.com/ksons/gltf-blender-importer/issues', 'category': 'Import-Export' } @@ -68,10 +70,7 @@ def get_mesh(self, idx): def get_camera(self, idx): if idx not in self.cameras: - # TODO: actually handle cameras - camera = self.gltf['cameras'][idx] - name = camera.get('name', 'cameras[%d]' % idx) - self.cameras[idx] = bpy.data.cameras.new(name) + self.cameras[idx] = camera.create_camera(self, idx) return self.cameras[idx] def generate_actions(self): @@ -193,10 +192,10 @@ def execute(self, context): self.check_version() self.check_required_extensions() - node.generate_scenes(self) + node.create_hierarchy(self) self.generate_actions() - if 'scene' in self.gltf: + if 'scene' in self.gltf and bpy.context.screen: bpy.context.screen.scene = self.scenes[self.gltf['scene']] return {'FINISHED'} diff --git a/addons/io_scene_gltf/camera.py b/addons/io_scene_gltf/camera.py new file mode 100644 index 0000000..ac40b43 --- /dev/null +++ b/addons/io_scene_gltf/camera.py @@ -0,0 +1,8 @@ +import bpy + + +def create_camera(op, idx): + camera = op.gltf['cameras'][idx] + name = camera.get('name', 'cameras[%d]' % idx) + data = bpy.data.cameras.new(name) + return data diff --git a/addons/io_scene_gltf/material.py b/addons/io_scene_gltf/material.py index 9101044..7d0c918 100644 --- a/addons/io_scene_gltf/material.py +++ b/addons/io_scene_gltf/material.py @@ -5,6 +5,10 @@ import bpy from bpy_extras.image_utils import load_image +# This is a hack as it is not possible to access a "normal" slot via name or +# store it in a temporary variable +NORMAL = 6 + def do_with_temp_file(contents, func): """Call func with the path to a temp file containing contents. @@ -107,35 +111,41 @@ def create_pbr_group(): multColorNode1 = tree.nodes.new('ShaderNodeMixRGB') multColorNode1.location = -680, 466 multColorNode1.blend_type = 'MULTIPLY' - multColorNode1.inputs[0].default_value = 1 - links.new(inputNode.outputs[0], multColorNode1.inputs[1]) - links.new(inputNode.outputs[1], multColorNode1.inputs[2]) + multColorNode1.inputs['Fac'].default_value = 1 + links.new(inputNode.outputs['baseColorFactor'], + multColorNode1.inputs['Color1']) + links.new(inputNode.outputs['baseColorTexture'], + multColorNode1.inputs['Color2']) + multColorNode2 = tree.nodes.new('ShaderNodeMixRGB') multColorNode2.location = -496, 466 multColorNode2.blend_type = 'MULTIPLY' - multColorNode2.inputs[0].default_value = 1 - links.new(inputNode.outputs[5], multColorNode2.inputs[1]) - links.new(multColorNode1.outputs[0], multColorNode2.inputs[2]) - colorOutputLink = multColorNode2.outputs[0] + multColorNode2.inputs['Fac'].default_value = 1 + links.new(inputNode.outputs['Vertex Color'], + multColorNode2.inputs['Color1']) + links.new(multColorNode1.outputs['Color'], multColorNode2.inputs['Color2']) + colorOutputLink = multColorNode2.outputs['Color'] # Calculate roughness and metalness separator = tree.nodes.new('ShaderNodeSeparateRGB') separator.location = -749, -130 - links.new(inputNode.outputs[4], separator.inputs[0]) + links.new( + inputNode.outputs['metallicRoughnessTexture'], separator.inputs['Image']) multRoughnessNode = tree.nodes.new('ShaderNodeMath') multRoughnessNode.location = -476, -50 multRoughnessNode.operation = 'MULTIPLY' - links.new(separator.outputs[1], multRoughnessNode.inputs[0]) - links.new(inputNode.outputs[3], multRoughnessNode.inputs[1]) - roughnessOutputLink = multRoughnessNode.outputs[0] + links.new(separator.outputs['G'], multRoughnessNode.inputs[0]) + links.new(inputNode.outputs['metallicFactor'], multRoughnessNode.inputs[1]) + roughnessOutputLink = multRoughnessNode.outputs['Value'] multMetalnessNode = tree.nodes.new('ShaderNodeMath') multMetalnessNode.location = -476, -227 multMetalnessNode.operation = 'MULTIPLY' - links.new(separator.outputs[2], multMetalnessNode.inputs[0]) - links.new(inputNode.outputs[2], multMetalnessNode.inputs[1]) - metalnessOutputLink = multMetalnessNode.outputs[0] + links.new(separator.outputs['B'], multMetalnessNode.inputs[0]) + links.new(inputNode.outputs['roughnessFactor'], + multMetalnessNode.inputs[1]) + metalnessOutputLink = multMetalnessNode.outputs['Value'] # First mix mixNode1 = tree.nodes.new('ShaderNodeMixShader') @@ -143,18 +153,18 @@ def create_pbr_group(): fresnelNode = tree.nodes.new('ShaderNodeFresnel') fresnelNode.location = 14, 553 - links.new(inputNode.outputs[6], fresnelNode.inputs[1]) + links.new(inputNode.outputs[NORMAL], fresnelNode.inputs[1]) diffuseNode = tree.nodes.new('ShaderNodeBsdfDiffuse') diffuseNode.location = 14, 427 - links.new(colorOutputLink, diffuseNode.inputs[0]) - links.new(roughnessOutputLink, diffuseNode.inputs[1]) - links.new(inputNode.outputs[6], diffuseNode.inputs[2]) + links.new(colorOutputLink, diffuseNode.inputs['Color']) + links.new(roughnessOutputLink, diffuseNode.inputs['Roughness']) + links.new(inputNode.outputs[NORMAL], diffuseNode.inputs['Normal']) glossyNode = tree.nodes.new('ShaderNodeBsdfGlossy') glossyNode.location = 14, 289 - links.new(roughnessOutputLink, glossyNode.inputs[1]) - links.new(inputNode.outputs[6], glossyNode.inputs[2]) + links.new(roughnessOutputLink, glossyNode.inputs['Roughness']) + links.new(inputNode.outputs[NORMAL], glossyNode.inputs['Normal']) links.new(fresnelNode.outputs[0], mixNode1.inputs[0]) links.new(diffuseNode.outputs[0], mixNode1.inputs[1]) @@ -166,9 +176,9 @@ def create_pbr_group(): glossyNode2 = tree.nodes.new('ShaderNodeBsdfGlossy') glossyNode2.location = 66, -114 - links.new(colorOutputLink, glossyNode2.inputs[0]) - links.new(roughnessOutputLink, glossyNode2.inputs[1]) - links.new(inputNode.outputs[6], glossyNode2.inputs[2]) + links.new(colorOutputLink, glossyNode2.inputs['Color']) + links.new(roughnessOutputLink, glossyNode2.inputs['Roughness']) + links.new(inputNode.outputs[NORMAL], glossyNode2.inputs['Normal']) links.new(metalnessOutputLink, mixNode2.inputs[0]) links.new(mixNode1.outputs[0], mixNode2.inputs[1]) @@ -209,28 +219,34 @@ def create_material_from_properties(op, material, material_name): group_node.node_tree = group mo = tree.nodes.new('ShaderNodeOutputMaterial') - mo.location = 365, -25 - links.new(group_node.outputs[0], mo.inputs[0]) + mo.location = 420, -25 + final_output = group_node.outputs[0] metalness = pbr_metallic_roughness.get('metallicFactor', 1) roughness = pbr_metallic_roughness.get('roughnessFactor', 1) base_color = pbr_metallic_roughness.get('baseColorFactor', [1, 1, 1, 1]) - group_node.inputs[0].default_value = base_color - group_node.inputs[2].default_value = metalness - group_node.inputs[3].default_value = roughness + group_node.inputs['baseColorFactor'].default_value = base_color + group_node.inputs['metallicFactor'].default_value = metalness + group_node.inputs['roughnessFactor'].default_value = roughness + base_color_texture = None # TODO texCoord property if 'baseColorTexture' in pbr_metallic_roughness: image_idx = pbr_metallic_roughness['baseColorTexture']['index'] - tex = create_texture(op, image_idx, 'baseColorTexture', tree) - tex.location = -580, 200 - links.new(tex.outputs[0], group_node.inputs[1]) + base_color_texture = create_texture( + op, image_idx, 'baseColorTexture', tree) + base_color_texture.location = -580, 200 + links.new( + base_color_texture.outputs['Color'], group_node.inputs['baseColorTexture']) + if 'metallicRoughnessTexture' in pbr_metallic_roughness: image_idx = pbr_metallic_roughness['metallicRoughnessTexture']['index'] tex = create_texture(op, image_idx, 'metallicRoughnessTexture', tree) tex.location = -580, -150 - links.new(tex.outputs[0], group_node.inputs[4]) + links.new(tex.outputs[0], + group_node.inputs['metallicRoughnessTexture']) + if 'normalTexture' in material: image_idx = material['normalTexture']['index'] tex = create_texture(op, image_idx, 'normalTexture', tree) @@ -238,9 +254,10 @@ def create_material_from_properties(op, material, material_name): tex.color_space = 'NONE' normal_map_node = tree.nodes.new('ShaderNodeNormalMap') normal_map_node.location = -150, -170 - links.new(tex.outputs[0], normal_map_node.inputs[1]) - links.new(normal_map_node.outputs[0], group_node.inputs[6]) + links.new(tex.outputs['Color'], normal_map_node.inputs['Color']) + links.new(normal_map_node.outputs[0], group_node.inputs[NORMAL]) # TODO scale + if 'emissiveTexture' in material: image_idx = material['emissiveTexture']['index'] tex = create_texture(op, image_idx, 'emissiveTexture', tree) @@ -250,12 +267,29 @@ def create_material_from_properties(op, material, material_name): add_node = tree.nodes.new('ShaderNodeAddShader') add_node.location = 357, -89 links.new(tex.outputs[0], emission_node.inputs[0]) - links.new(group_node.outputs[0], add_node.inputs[0]) + links.new(final_output, add_node.inputs[0]) links.new(emission_node.outputs[0], add_node.inputs[1]) - links.new(add_node.outputs[0], mo.inputs[0]) + final_output = add_node.outputs[0] mo.location = 547, -84 # TODO occlusion texture + alpha_mode = material.get("alphaMode", "OPAQUE") + if alpha_mode == "BLEND" and base_color_texture: + + transparent_node = tree.nodes.new('ShaderNodeBsdfTransparent') + transparent_node.location = 43, -240 + + mix_node = tree.nodes.new('ShaderNodeMixShader') + mix_node.location = 250, -151 + + links.new(base_color_texture.outputs['Alpha'], mix_node.inputs['Fac']) + links.new(transparent_node.outputs[0], mix_node.inputs[1]) + links.new(final_output, mix_node.inputs[2]) + + final_output = mix_node.outputs[0] + + links.new(final_output, mo.inputs[0]) + return mat diff --git a/addons/io_scene_gltf/mesh.py b/addons/io_scene_gltf/mesh.py index 38c4867..894cfea 100644 --- a/addons/io_scene_gltf/mesh.py +++ b/addons/io_scene_gltf/mesh.py @@ -120,22 +120,7 @@ def assign_texcoords(uvs, uv_layer): if 'TEXCOORD_1' in attributes: assign_texcoords(op.get_accessor(attributes['TEXCOORD_1']), me.uv_layers[1].data) - # Assign joints by generating vertex groups - if 'JOINTS_0' in attributes and 'WEIGHTS_0' in attributes: - # Don't seem to need to deal with all_attributes here. - # The only way I could find to set vertex groups was by - # round-tripping through a bmesh. - # TODO: find a better way? - joints = op.get_accessor(attributes['JOINTS_0']) - weights = op.get_accessor(attributes['WEIGHTS_0']) - bme = bmesh.new() - bme.from_mesh(me) - layer = bme.verts.layers.deform.new('JOINTS_0') - for vert, joint_vec, weight_vec in zip(bme.verts, joints, weights): - for joint, weight in zip(joint_vec, weight_vec): - vert[layer][joint] = weight - bme.to_mesh(me) - bme.free() + # TODO: handle joints and weights me.update() diff --git a/addons/io_scene_gltf/node.py b/addons/io_scene_gltf/node.py index 9de3b9e..b48d42f 100644 --- a/addons/io_scene_gltf/node.py +++ b/addons/io_scene_gltf/node.py @@ -1,30 +1,27 @@ import bpy -from mathutils import Matrix, Quaternion, Vector +from mathutils import Matrix, Quaternion +from bpy_extras.io_utils import axis_conversion """ Handle nodes and scenes. -The glTF node forest is represented in Blender as a single armature, where the -nodes become bones. The meshes and cameras are set in the correct place in the -heirachy using a "Copy Transform" constraint to lock them to their parent. Using -an armature is necessary to skin meshes -- glTF skins through the configuration -of the node forest and Blender only skins through armatures (AFAIK). The main -drawback of this is that I don't think you can scale a bone's rest position so -scaling nodes doesn't work. Also, the bone positions are fairly meaningless. +The glTF node forest is represented in Blender as objects. Since a glTF node +can have multiple optional components (mesh, camera, etc.) there is no +1:1 mapping between glTF nodes and Blender objects. -COLLADA should have these problems too; check what it does? (I know it doesn't -solve the bone positions issue.) +Hence, glTF nodes map to one or multiple Blender objects. glTF nodes without +a (supported) component map to an Empty object. A glTF mesh components maps +to a Blender object with mesh data, a glTF camera component map to an object +with camera data. -For our purposes it would be really nice if Blender had an armature based on -joints instead of bones. +Transformations are appied as they appear in glTF, while a global scene root +object transforms the glTF space into Blender space. -It would also be desirable to check that what we do gives the correst result ie. -the transform Blender does equals the one glTF calls for, but neither Blender -nor glTF have docs for exactly what that transform should be :-/ +TODO: Camera default orientations of glTF and Blender differ. This is currently +not taken into account. Hence, created cameras will not have the correct +orientation -Scenes are represented by Blender scene. Each one has the whole node armature -linked in, but only has those meshes and cameras linked in that are "visible" in -that scene (ie. are descendants of one of the roots of the scene). +TODO: Skin component is not supported """ @@ -41,74 +38,58 @@ def convert_quaternion(q): return Quaternion([q[3], q[0], q[1], q[2]]) -def get_transform(node): +def set_transform(node, ob): if 'matrix' in node: - return convert_matrix(node['matrix']) + m = node['matrix'] + m = convert_matrix(m) + (loc, rot, sca) = m.decompose() else: - mat = Matrix() - if 'scale' in node: - s = node['scale'] - mat = Matrix([ - [s[0], 0, 0, 0], - [0, s[1], 0, 0], - [0, 0, s[2], 0], - [0, 0, 0, 1] - ]) - if 'rotation' in node: - q = convert_quaternion(node['rotation']) - mat = q.to_matrix().to_4x4() * mat - if 'translation' in node: - t = Vector(node['translation']) - mat = Matrix.Translation(t) * mat - return mat - - -def create_objects(op, idx, root_idx): - node = op.gltf['nodes'][idx] - name = node.get('name', 'nodes[%d]' % idx) + sca = node.get('scale', [1.0, 1.0, 1.0]) + rot = node.get('rotation', [0.0, 0.0, 0.0, 1.0]) + rot = convert_quaternion(rot) # xyzw -> wxyz + loc = node.get('translation', [0.0, 0.0, 0.0]) - def create(name, data): - ob = bpy.data.objects.new(name, data) - ob.parent = op.armature_ob + ob.location = loc + ob.rotation_mode = 'QUATERNION' + ob.rotation_quaternion = rot + ob.scale = sca - # TODO: make the object a child of the bone instead? Making it a - # child puts it at the tail of the bone and we want it at the - # head. We'd just need to translate it along the length of the - # bone. - con = ob.constraints.new('COPY_TRANSFORMS') - con.target = op.armature_ob - con.subtarget = op.node_to_bone_name[idx] - ob.parent = op.armature_ob +def create_node(op, idx): + node = op.gltf['nodes'][idx] - op.root_to_objects[root_idx].append(ob) + # print("Creating node: {}".format(idx) ) + def create(name, data): + ob = bpy.data.objects.new(name, data) + bpy.context.scene.objects.link(ob) return ob + objects = [] if 'mesh' in node: - mesh_name = name - if 'camera' in node: - mesh_name += '.mesh' - ob = create(mesh_name, op.get_mesh(node['mesh'])) + mesh_name = node.get('name', 'mesh[%d]' % idx) + mesh = create(mesh_name, op.get_mesh(node['mesh'])) + objects.append(mesh) - if 'skin' in node: - skin = op.gltf['skins'][node['skin']] - joints = skin['joints'] - for joint in joints: - ob.vertex_groups.new(op.node_to_bone_name[joint]) + if 'camera' in node: + camera_name = node.get('name', 'camera[%d]' % idx) + camera = create(camera_name, op.get_camera(node['camera'])) + objects.append(camera) - mod = ob.modifiers.new('rig', 'ARMATURE') - mod.object = op.armature_ob - mod.use_vertex_groups = True + if not objects: + name = node.get('name', 'node[%d]' % idx) + objects.append(create(name, None)) - if 'camera' in node: - camera_name = name - if 'mesh' in node: - camera_name += '.camera' - create(camera_name, op.get_camera(node['camera'])) + for obj in objects: + set_transform(node, obj) - for idx in node.get('children', []): - create_objects(op, idx, root_idx) + parent = objects[0] + children = node.get('children', []) + for child_idx in children: + for child_node in create_node(op, child_idx): + child_node.parent = parent + + return objects def find_root_idxs(op): @@ -118,95 +99,55 @@ def find_root_idxs(op): for child_idx in node.get('children', []): idxs.remove(child_idx) root_idxs = list(idxs) - root_idxs.sort() - op.root_idxs = root_idxs - - for root_idx in root_idxs: - op.root_to_objects[root_idx] = [] - - -def generate_armature_object(op): - bpy.ops.object.add(type='ARMATURE', enter_editmode=True) - arma_ob = bpy.context.object - arma_ob.name = 'Node Forest' - arma_ob.show_x_ray = True - arma = arma_ob.data - arma.name = 'Node Forest' - op.armature_ob = arma_ob - - # Turn glTF up (+Y) into Blender up (+Z) - # TODO is this right? - arma_ob.matrix_local = Matrix([ - [1, 0, 0, 0], - [0, 0, -1, 0], - [0, 1, 0, 0], - [0, 0, 0, 1] - ]) - - def add_bone(idx, parent, parent_mat): - node = op.gltf['nodes'][idx] - name = node.get('name', 'node[%d]' % idx) - # Urg, isn't this backwards from get_transform? Figure out why. - mat = parent_mat * get_transform(node) - - bone = arma.edit_bones.new(name) - bone.use_connect = False - if parent: - bone.parent = parent - bone.head = mat * Vector((0, 0, 0)) - bone.tail = mat * Vector((0, 1, 0)) - bone.align_roll(mat * Vector((0, 0, 1)) - bone.head) - # NOTE: bones don't seem to have non-uniform scaling. - # This appears to be a serious problem for us. - - op.node_to_bone_name[idx] = bone.name - - children = node.get('children', []) - for child_idx in children: - add_bone(child_idx, bone, mat) - - for root_idx in op.root_idxs: - add_bone(root_idx, None, Matrix()) - # Done with bones; node_to_bone_name is filled out. - # Now create objects. - for root_idx in op.root_idxs: - create_objects(op, root_idx, root_idx) - - bpy.ops.object.mode_set(mode='OBJECT') - - # Linking it in, AFAICT, was necessary to enter edit mode for the above. But - # create_scene is going to be responsible for linking it into each scene, - # and linking it where it's already linked throws an error, so we unlink it - # here for now. - bpy.context.scene.objects.unlink(arma_ob) - - -def create_scene(op, idx): - scene = op.gltf['scenes'][idx] - name = scene.get('name', 'scene[%d]' % idx) - - bpy.ops.scene.new(type='NEW') - scn = bpy.context.scene - scn.name = name - scn.render.engine = 'CYCLES' - # scn.world.use_nodes = True - - # Always link in the whole node forest - scn.objects.link(op.armature_ob) - - roots = scene.get('nodes', []) + return root_idxs + + +def create_root_objects(op, roots, scene): + + # Add a root object to fix the difference in orientation + root_object = bpy.data.objects.new("SceneRoot", None) + root_object.matrix_local = axis_conversion( + from_forward="Z", from_up="Y").to_4x4() + scene.objects.link(root_object) + for root_idx in roots: # Link in any objects in this tree - for ob in op.root_to_objects[root_idx]: - scn.objects.link(ob) + for ob in create_node(op, root_idx): + ob.parent = root_object - return scn +def create_scenes(op): + scenes = op.gltf.get('scenes', []) -def generate_scenes(op): - find_root_idxs(op) - generate_armature_object(op) + if not scenes: + return False - scenes = op.gltf.get('scenes', []) - for scene_idx in range(0, len(scenes)): - op.scenes[scene_idx] = create_scene(op, scene_idx) + default_scene = op.gltf.get('scene', 0) + + for scene_idx, scene in enumerate(scenes): + + if scene_idx == default_scene: + blender_scene = bpy.context.scene + if 'name' in scene: + blender_scene.name = scene.get('name') + else: + bpy.ops.scene.new(type='NEW') + blender_scene = bpy.context.scene + blender_scene.name = scene.get('name', 'scene[%d]' % scene_idx) + + op.scenes[scene_idx] = blender_scene + blender_scene.render.engine = 'CYCLES' + roots = scene.get('nodes', []) + create_root_objects(op, roots, blender_scene) + + return True + + +def create_hierarchy(op): + + # A scene is not mandatory in glTF + if not create_scenes(op): + # Create scene from root nodes using active scene + scene = bpy.context.scene + roots = find_root_idxs(op) + create_root_objects(op, roots, scene) diff --git a/deploy.py b/deploy.py new file mode 100644 index 0000000..7c590aa --- /dev/null +++ b/deploy.py @@ -0,0 +1,48 @@ +import argparse +import os +import sys +import re +import subprocess +import shutil + +parser = argparse.ArgumentParser() +parser.add_argument("version") + +args = parser.parse_args() +pathname = os.path.dirname(sys.argv[0]) + + +def replace_in_file(file, expr, new_substr): + lines = [] + regex = re.compile(expr, re.IGNORECASE) + with open(file) as infile: + for line in infile: + line = regex.sub(new_substr, line) + lines.append(line) + with open(file, 'w') as outfile: + for line in lines: + outfile.write(line) + + +version = args.version.split('.') +version_string = ".".join(version) + +main_file = os.path.join(pathname, 'addons', 'io_scene_gltf', '__init__.py') +readme_file = os.path.join(pathname, 'README.md') + +replace_in_file(main_file, + '\'version\': \([0-9\, ]*\)', + '\'version\': (%s)' % ', '.join(version)) + +replace_in_file(readme_file, + 'download/[0-9\.]*/io_scene_gltf-[0-9\.]*zip', + 'download/{}/io_scene_gltf-{}.zip'.format(version_string, version_string)) + +subprocess.call(["git", "add", main_file, readme_file]) +subprocess.call(["git", "commit", "-m", "Bumb version number to {}".format(version_string)]) +subprocess.call(["git", "tag", "v{}".format(version_string)]) + +if not os.path.exists('dist'): + os.makedirs('dist') +shutil.make_archive('dist/io_scene_gltf-{}'.format(version_string), 'zip', + 'addons') diff --git a/doc/addon-install.png b/doc/addon-install.png new file mode 100644 index 0000000..06e49f5 Binary files /dev/null and b/doc/addon-install.png differ diff --git a/doc/archive.png b/doc/archive.png new file mode 100644 index 0000000..9b88218 Binary files /dev/null and b/doc/archive.png differ diff --git a/test/glTF-Sample-Models b/test/glTF-Sample-Models index 29355d2..463e270 160000 --- a/test/glTF-Sample-Models +++ b/test/glTF-Sample-Models @@ -1 +1 @@ -Subproject commit 29355d2374f829c8f238b3c55ce06981455aa389 +Subproject commit 463e270e596b3f03e02165337d9b1b7e877fb185 diff --git a/test/travis/runtest.py b/test/travis/runtest.py index db279fa..57bc28b 100644 --- a/test/travis/runtest.py +++ b/test/travis/runtest.py @@ -46,6 +46,10 @@ def test(self): glob.glob(samples_path + '/**/*.gltf', recursive=True) + glob.glob(samples_path + '/**/*.glb', recursive=True) ) + + # Skip Draco encoded files for now + files = [fn for fn in files if "Draco" not in fn] + print("%i sample files" % len(files)) for filename in files: test = test_generator(filename)