2
$\begingroup$

I'm still new with python and scripting. I tried editing the obj exporter that I found. Since I'm exporting tons of object groups, I need each of scene collection or selected objects to be exported as an individual gtlf file:

import bpy
import os

# get the path where the blend file is located
basedir = bpy.path.abspath('//')

# deselect all objects
# bpy.ops.object.select_all(action='DESELECT')    

# loop through all the objects in the scene
scene = bpy.context.scene
for ob in scene.objects:
    # make the current object active and select it
    scene.objects.active = ob
    ob.select = True

    # make sure that we only export meshes
    if ob.type == 'MESH':
        # export the currently selected object to its own file based on its name
        bpy.ops.export_scene.gltf(
                filepath=os.path.join(basedir+'gltf-individual', ob.name + '.gltf'),
                use_selection=True,
                )
    # deselect the object and move on to another if any more are left
    ob.select = False

Q: Can someone help me with this?

$\endgroup$
5
  • $\begingroup$ What error or problem are you seeing when you run this? It's not clear what you're asking sorry. Also note that use_selection will need to be renamed as export_selected until github.com/KhronosGroup/glTF-Blender-IO/issues/956 is fixed and released. $\endgroup$ Mar 15, 2020 at 0:25
  • $\begingroup$ @TeeJeyJeytee84 The script above is for version < 2.8. See blender.stackexchange.com/questions/132825/… to upgrade to 2.8. If your question is re above comment .. convert to 2.8 export collection, perhaps ask a new question... or edit the bounty description accordingly (if you can do that, can't remember) $\endgroup$
    – batFINGER
    Apr 24, 2020 at 10:34
  • $\begingroup$ @brockmann this is really clear and cool stuff. However when I test your "Batch export glTF/glb files" script in Blender 2.83 just warning happen: ret = op_call(self.idname_py(), None, kw) TypeError: Converting py args to operator properties: enum "PNG" not found in ('AUTO', 'JPEG') location: <unknown location>:-1 Some idea to fix it? I am a Python beginner... $\endgroup$ Jul 21, 2020 at 9:04
  • $\begingroup$ Fixed it for 2.83: blender.stackexchange.com/posts/175968/revisions $\endgroup$
    – brockmann
    Jul 21, 2020 at 10:40
  • $\begingroup$ Awesome! Thanks for godspeed. $\endgroup$ Jul 21, 2020 at 11:02

1 Answer 1

3
+50
$\begingroup$

Approach to export collections using unlink and link methods

You can temporarily .unlink() all collections from the root level, then (re-)link() one by one to the scene level in order to export them and link them all back at the end of the script.

Tested this approach for a huge scene using the alembic export operator and it was pretty fast, surprisingly. For convenience I added a col_level integer to determine how many (sub-)levels of collections are going to be exported to gtlf or glb files:

import bpy

col_levels = 2                          # Levels to export
scn_col = bpy.context.scene.collection  # Root collection

def file_name(s):
    '''Return valid file name from string'''
    return "".join(x for x in s if x.isalnum())

def col_hierarchy(root_col, levels=1):
    '''Read hierarchy of the collections in the scene'''
    level_lookup = {}
    def recurse(root_col, parent, depth):
        if depth > levels: 
            return
        if isinstance(parent,  bpy.types.Collection):
            level_lookup.setdefault(parent, []).append(root_col)
        for child in root_col.children:
            recurse(child, root_col,  depth + 1)
    recurse(root_col, root_col.children, 0)
    return level_lookup

# Root objects (in case)
scn_obj = [o for o in scn_col.objects]

# Lookups (Collections per level and Parents)
lkp_col = col_hierarchy(scn_col, levels=col_levels)
prt_col = {i : k for k, v in lkp_col.items() for i in v}

# All collections to export
candidates = [x for v in lkp_col.values() for x in v]

# (1) Unlink all Collections
for c in candidates:
    prt_col.get(c).children.unlink(c)

# (2) Export root objects (optional)
if scn_obj:
    bpy.ops.export_scene.gltf(
        filepath="/tmp/{}.glb".format(file_name(scn_col.name))
    )
    # Unlink objects of master collection
    for o in scn_obj: scn_col.objects.unlink(o)

# (3) (Re-)link collections of choice to root level and export
for c in candidates:
    # (Re-)link collection
    scn_col.children.link(c)
    # Export collection
    bpy.ops.export_scene.gltf(
        filepath="/tmp/{}.glb".format(file_name(c.name))
    )
    # Unlink collection
    scn_col.children.unlink(c)

# (4) Reset all back
for o in scn_obj: scn_col.objects.link(o)
for c in candidates: prt_col.get(c).children.link(c)

Note: The script is an initial proof of concept.


Operator to batch export collections

The core idea can be converted into an add-on using the famous ExportHelper class. Have a look into Templates > Python > Operator File Export in the Text Editor. Following add-on adds a new entry to the File > Export menu. The operator comes with the most important export settings as well as a level integer to export Collections to single gltf/glb files:

enter image description here

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>

bl_info = {
    "name": "Batch export glTF/glb files",
    "author": "brockmann",
    "version": (0, 2),
    "blender": (2, 91, 0),
    "location": "File > Import-Export",
    "description": "Batch Export Collections to glTF/glb",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Import-Export"}


import bpy
import os

from bpy_extras.io_utils import ExportHelper
from bpy.props import (BoolProperty,
                       IntProperty,
                       StringProperty,
                       EnumProperty,
                       CollectionProperty
                       )


def file_name(s):
    """Return valid file name from string"""
    #return "".join(x for x in s if x.isalnum())
    return "".join( x for x in s if (x.isalnum() or x in "._- "))

def col_hierarchy(root_col, levels=1):
    """Read hierarchy of the collections in the scene"""
    level_lookup = {}
    def recurse(root_col, parent, depth):
        if depth > levels: 
            return
        if isinstance(parent,  bpy.types.Collection):
            level_lookup.setdefault(parent, []).append(root_col)
        for child in root_col.children:
            recurse(child, root_col,  depth + 1)
    recurse(root_col, root_col.children, 0)
    return level_lookup


class SCENE_OT_batch_gltf(bpy.types.Operator, ExportHelper):
    """Batch export collections to glTF/glb files"""
    bl_idname = "export_scene.batch_gltf"
    bl_label = "Collections to glTF/glb"
    bl_options = {'PRESET', 'UNDO'}

    # ExportHelper mixin class uses this
    filename_ext = ''

    filter_glob: StringProperty(
            default='*.glb;*.gltf', 
            options={'HIDDEN'}
    )

    # List of operator properties, the attributes will be assigned
    # to the class instance from the operator setting before calling.
    batch_export_format: EnumProperty(
        name='Format',
        items=(('GLB', 'glTF Binary (.glb)',
                'Exports a single file, with all data packed in binary form. '
                'Most efficient and portable, but more difficult to edit later'),
               ('GLTF_EMBEDDED', 'glTF Embedded (.gltf)',
                'Exports a single file, with all data packed in JSON. '
                'Less efficient than binary, but easier to edit later'),
               ('GLTF_SEPARATE', 'glTF Separate (.gltf + .bin + textures)',
                'Exports multiple files, with separate JSON, binary and texture data. '
                'Easiest to edit later')),
        description=(
            'Output format and embedding options. Binary is most efficient, '
            'but JSON (embedded or separate) may be easier to edit later'
        ),
        default='GLB'
    )

    batch_export_copyright: StringProperty(
        name='Copyright',
        description='Legal rights and conditions for the model',
        default=''
    )

    batch_export_image_format: EnumProperty(
        name='Images',
        items=(('AUTO', 'Automatic',
                'Save PNGs as PNGs and JPEGs as JPEGs.\n'
                'If neither one, use PNG'),
                ('JPEG', 'JPEG Format (.jpg)',
                'Save images as JPEGs. (Images that need alpha are saved as PNGs though.)\n'
                'Be aware of a possible loss in quality'),
               ),
        description=(
            'Output format for images. PNG is lossless and generally preferred, but JPEG might be preferable for web '
            'applications due to the smaller file size'
        ),
        default='AUTO'
    )

    batch_export_levels: IntProperty(
        name='Collection Levels',
        description='Set the levels of collections',
        default=2
    )

    batch_export_materials: EnumProperty(
        name='Materials',
        items=(('EXPORT', 'Export',
        'Export all materials used by included objects'),
        ('PLACEHOLDER', 'Placeholder',
        'Do not export materials, but write multiple primitive groups per mesh, keeping material slot information'),
        ('NONE', 'No export',
        'Do not export materials, and combine mesh primitive groups, losing material slot information')),
        description='Export materials ',
        default='EXPORT'
    )

    batch_export_colors: BoolProperty(
        name='Export Vertex Colors',
        description='Export vertex colors with meshes',
        default=True
    )

    batch_export_cameras: BoolProperty(
        name='Export Cameras',
        description='Export cameras',
        default=False
    )

    batch_export_extras: BoolProperty(
        name='Export Custom Properties',
        description='Export custom properties as glTF extras',
        default=False
    )

    batch_export_apply: BoolProperty(
        name='Export Apply Modifiers',
        description='Apply modifiers (excluding Armatures) to mesh objects -'
                    'WARNING: prevents exporting shape keys',
        default=False
    )

    batch_export_yup: BoolProperty(
        name='+Y Up',
        description='Export using glTF convention, +Y up',
        default=True
    )


    def execute(self, context):                

        # Get the folder
        folder_path = os.path.dirname(self.filepath)
        scn_col = context.scene.collection

        # Lookups (Collections per level and Parents)
        lkp_col = col_hierarchy(scn_col, levels=self.batch_export_levels)
        prt_col = {i : k for k, v in lkp_col.items() for i in v}

        scn_obj = [o for o in scn_col.objects]
        candidates = [x for v in lkp_col.values() for x in v]

        if not candidates:
            self.report({'INFO'}, "Nothing to export")
            return {'CANCELLED'}        

        # Unlink all Collections and objects
        for c in candidates:
            prt_col.get(c).children.unlink(c)
        for o in scn_obj: 
            scn_col.objects.unlink(o)

        # (Re-)link collections of choice to root level and export
        for c in candidates:
            
            scn_col.children.link(c)

            fname = file_name(c.name)
            fpath = os.path.join(folder_path, fname)

            bpy.ops.export_scene.gltf(
                filepath = fpath,
                export_format = self.batch_export_format,
                export_copyright = self.batch_export_copyright,
                export_image_format = self.batch_export_image_format,
                export_materials = self.batch_export_materials,
                export_colors = self.batch_export_colors,
                export_cameras = self.batch_export_cameras,
                export_extras = self.batch_export_extras,
                export_yup = self.batch_export_yup,
                export_apply = self.batch_export_apply
            )

            scn_col.children.unlink(c)

        # Reset all back
        for o in scn_obj: 
            scn_col.objects.link(o)
        for c in candidates: 
            prt_col.get(c).children.link(c)

        return {'FINISHED'}


# Only needed if you want to add into a dynamic menu
def menu_func_import(self, context):
    self.layout.operator(SCENE_OT_batch_gltf.bl_idname, text="glTF Batch Export Collection (.glb/gltf)")


def register():
    bpy.utils.register_class(SCENE_OT_batch_gltf)
    bpy.types.TOPBAR_MT_file_export.append(menu_func_import)


def unregister():
    bpy.utils.unregister_class(SCENE_OT_batch_gltf)
    bpy.types.TOPBAR_MT_file_export.remove(menu_func_import)


if __name__ == "__main__":
    register()

    # test call
    #bpy.ops.export_scene.batch_gltf('INVOKE_DEFAULT')

For versions pre 2.91, replace the batch_export_materials property (line 117) using:

    batch_export_materials: BoolProperty(
            name='Export Materials',
            description='Export materials',
            default=True
    )

See the actual revision for 2.83: https://blender.stackexchange.com/revisions/175968/7

$\endgroup$
2
  • $\begingroup$ would it be possible to allow your tool to save individual objects in the collection as individual glTF files? thank you! $\endgroup$ Feb 8, 2021 at 17:20
  • $\begingroup$ Yeah, absolutely, same as blender.stackexchange.com/a/31852/31447 @viktorpetrovich $\endgroup$
    – brockmann
    Feb 8, 2021 at 17:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .