refactor(tools/gltf_auto_export): complete restructuring of the tool (#67)
* split up code in logical modules instead of one giant file * added module structure, imports etc * updated README * add storing & reloading of lists of scenes * minor cleanups & tweaks * closes #64 * moved gltf_auto_export specific docs/images inside the addon's folder * updated main title image
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
*.blend1
|
*.blend1
|
||||||
target/
|
target/
|
||||||
|
__pycache__/
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# Blender_bevy_components_workflow
|
# Blender_bevy_components_workflow
|
||||||
|
|
||||||
![demo](./docs/blender_gltf_components.png)
|
![demo](./docs/blender_bevy.png)
|
||||||
|
|
||||||
Crates & tools for adding components from gltf files in the [Bevy](https://bevyengine.org/) game engine.
|
Crates & tools for adding components from gltf files in the [Bevy](https://bevyengine.org/) game engine.
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 287 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 54 KiB |
BIN
docs/blender_bevy.png
Normal file
After Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 86 KiB |
BIN
tools/gltf_auto_export.zip
Normal file
@ -1,6 +1,6 @@
|
|||||||
# gltf_auto_export
|
# gltf_auto_export
|
||||||
|
|
||||||
This [Blender addon](./gltf_auto_export.py)
|
This [Blender addon](./)
|
||||||
- automatically exports your level/world from Blender to gltf whenever you save your Blend file.
|
- automatically exports your level/world from Blender to gltf whenever you save your Blend file.
|
||||||
- in Blueprints mode (highly recommended !) :
|
- in Blueprints mode (highly recommended !) :
|
||||||
- supports automatic exports of used collections as [Gltf blueprints](../../crates/bevy_gltf_blueprints/README.md)
|
- supports automatic exports of used collections as [Gltf blueprints](../../crates/bevy_gltf_blueprints/README.md)
|
||||||
@ -16,11 +16,11 @@ This [Blender addon](./gltf_auto_export.py)
|
|||||||
|
|
||||||
* in Blender go to edit => preferences => install
|
* in Blender go to edit => preferences => install
|
||||||
|
|
||||||
![blender addon install](../../docs/blender_addon_install.png)
|
![blender addon install](./docs/blender_addon_install.png)
|
||||||
|
|
||||||
* choose the path where ```gltf_auto_export/gltf_auto_export.py``` is stored
|
* choose the path where ```gltf_auto_export.zip``` is stored
|
||||||
|
|
||||||
![blender addon install](../../docs/blender_addon_install2.png)
|
![blender addon install](./docs/blender_addon_install2.png)
|
||||||
|
|
||||||
## Usage:
|
## Usage:
|
||||||
|
|
||||||
@ -30,32 +30,32 @@ This [Blender addon](./gltf_auto_export.py)
|
|||||||
* before it can automatically save to gltf, you need to configure it
|
* before it can automatically save to gltf, you need to configure it
|
||||||
* go to file => export => gltf auto export
|
* go to file => export => gltf auto export
|
||||||
|
|
||||||
![blender addon use](../../docs/blender_addon_use.png)
|
![blender addon use](./docs/blender_addon_use.png)
|
||||||
|
|
||||||
* set the autoexport parameters in the **auto export** panel:
|
* set the autoexport parameters in the **auto export** panel:
|
||||||
|
|
||||||
![blender addon use3](../../docs/blender_addon_use3.png)
|
![blender addon use3](./docs/blender_addon_use3.png)
|
||||||
|
|
||||||
|
|
||||||
- export folder
|
- export folder
|
||||||
- pick your main (level) scenes and library scenes (see the chapter about Blueprints below)
|
- pick your main (level) scenes and library scenes (see the chapter about Blueprints below)
|
||||||
- click in the scene picker & select your scene
|
- click in the scene picker & select your scene
|
||||||
|
|
||||||
![select scene](../../docs/blender_addon_add_scene.png)
|
![select scene](./docs/blender_addon_add_scene.png)
|
||||||
|
|
||||||
- click on the "+" icon
|
- click on the "+" icon
|
||||||
|
|
||||||
![select scene2](../../docs/blender_addon_add_scene2.png)
|
![select scene2](./docs/blender_addon_add_scene2.png)
|
||||||
|
|
||||||
- your scene is added to the list
|
- your scene is added to the list
|
||||||
|
|
||||||
![select scene3](../../docs/blender_addon_add_scene3.png)
|
![select scene3](./docs/blender_addon_add_scene3.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* and your standard gltf export parameters in the **gltf** panel
|
* and your standard gltf export parameters in the **gltf** panel
|
||||||
|
|
||||||
![blender addon use2](../../docs/blender_addon_use2.png)
|
![blender addon use2](./docs/blender_addon_use2.png)
|
||||||
|
|
||||||
|
|
||||||
* click on "apply settings"
|
* click on "apply settings"
|
||||||
@ -73,41 +73,50 @@ You can enable this option to automatically replace all the **collection instanc
|
|||||||
- this means you will have
|
- this means you will have
|
||||||
* one small main gltf file (your level/world)
|
* one small main gltf file (your level/world)
|
||||||
* as many gltf files as you have used collections in the main scene , in the library path you specified :
|
* as many gltf files as you have used collections in the main scene , in the library path you specified :
|
||||||
for the included [advanced](../../examples/advanced/) example's [assets](../../assets/advanced/models/), it looks something like this:
|
for the included [basic](../../examples/bevy_gltf_blueprints/basic/) example's [assets](../../examples/bevy_gltf_blueprints/basic/assets/), it looks something like this:
|
||||||
|
|
||||||
![library](../../docs/exported_library_files.png)
|
![library](./docs/exported_library_files.png)
|
||||||
|
|
||||||
the .blend file that they are generated from can be found [here](../../assets/advanced/advanced.blend)
|
the .blend file that they are generated from can be found [here](../../examples/bevy_gltf_blueprints/basic/assets/advanced.blend)
|
||||||
|
|
||||||
- the above only applies to collections that have **instances** in your main scene!
|
- the above only applies to collections that have **instances** in your main scene!
|
||||||
if you want a specific collection in your library to always get exported regardless of its use, you need to add
|
if you want a specific collection in your library to always get exported regardless of its use, you need to add
|
||||||
a **COLLECTION** (boolean) custom property called ```AutoExport``` set to true
|
a **COLLECTION** (boolean) custom property called ```AutoExport``` set to true
|
||||||
> not at the object level ! the collection level !
|
> not at the object level ! the collection level !
|
||||||
|
|
||||||
![force-export](../../docs/force_export.jpg)
|
![force-export](./docs/force_export.jpg)
|
||||||
|
|
||||||
It will get automatically exported like any of the "in-use" collections.
|
It will get automatically exported like any of the "in-use" collections.
|
||||||
|
|
||||||
- you can also get an overview of all the exported collections in the export menu
|
- you can also get an overview of all the exported collections in the export menu
|
||||||
|
|
||||||
![exported collections](../../docs/exported_collections.png)
|
![exported collections](./docs/exported_collections.png)
|
||||||
|
|
||||||
#### Process
|
#### Process
|
||||||
|
|
||||||
This is the internal logic of the export process with blueprints
|
This is the internal logic of the export process with blueprints
|
||||||
|
|
||||||
![process](../../docs/process.svg)
|
![process](./docs/process.svg)
|
||||||
|
|
||||||
ie this is an example scene...
|
ie this is an example scene...
|
||||||
|
|
||||||
![](../../docs/workflow_original.jpg)
|
![](./docs/workflow_original.jpg)
|
||||||
|
|
||||||
and what actually gets exported for the main scene/world/level
|
and what actually gets exported for the main scene/world/level
|
||||||
|
|
||||||
![](../../docs/workflow_empties.jpg)
|
![](./docs/workflow_empties.jpg)
|
||||||
|
|
||||||
all collections instances replaced with empties, and all those collections exported to gltf files as seen above
|
all collections instances replaced with empties, and all those collections exported to gltf files as seen above
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- since the code has now been split up into multiple modules, to make your life easier, I highly recomend (if you are using vscode like me) to use
|
||||||
|
[this](https://marketplace.visualstudio.com/items?itemName=JacquesLucke.blender-development) excellent extension , works easilly and fast , even for the latest
|
||||||
|
versions of Blender (v4.0 as of this writing)
|
||||||
|
- this [article](https://polynook.com/learn/set-up-blender-addon-development-environment-in-windows) might also help out
|
||||||
|
(easy enough to get it working on linux too)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This tool, all its code, contents & assets is Dual-licensed under either of
|
This tool, all its code, contents & assets is Dual-licensed under either of
|
||||||
|
@ -1,46 +1,203 @@
|
|||||||
#TODO: this is not actually in use yet, just use the gltf_auto_export.py file
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Test glTF/glb auto-export",
|
"name": "gltf_auto_export",
|
||||||
"author": "kaosigh",
|
"author": "kaosigh",
|
||||||
"version": (0, 1),
|
"version": (0, 5, 4),
|
||||||
"blender": (3, 4, 0),
|
"blender": (3, 4, 0),
|
||||||
"location": "File > Import-Export",
|
"location": "File > Import-Export",
|
||||||
"description": "glTF/glb auto-export",
|
"description": "glTF/glb auto-export",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
"wiki_url": "",
|
"wiki_url": "https://github.com/kaosat-dev/Blender_bevy_components_workflow",
|
||||||
"tracker_url": "",
|
"tracker_url": "https://github.com/kaosat-dev/Blender_bevy_components_workflow/issues/new",
|
||||||
"category": "Import-Export"
|
"category": "Import-Export"
|
||||||
}
|
}
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import os
|
||||||
|
|
||||||
from .gltf_auto_export import TEST_AUTO_OT_gltf
|
from bpy.app.handlers import persistent
|
||||||
from .gltf_auto_export import deps_update_handler
|
from bpy.props import (IntProperty)
|
||||||
from .gltf_auto_export import save_handler
|
|
||||||
from .gltf_auto_export import get_changedScene
|
|
||||||
from .gltf_auto_export import set_ChangedScene
|
|
||||||
|
|
||||||
|
|
||||||
# Only needed if you want to add into a dynamic menu
|
from . import helpers
|
||||||
|
from .internals import (SceneLink,
|
||||||
|
SceneLinks,
|
||||||
|
CollectionToExport,
|
||||||
|
CollectionsToExport,
|
||||||
|
CUSTOM_PG_sceneName
|
||||||
|
)
|
||||||
|
from .auto_export import auto_export
|
||||||
|
from .preferences import (AutoExportGltfPreferenceNames,
|
||||||
|
AutoExportGltfAddonPreferences
|
||||||
|
)
|
||||||
|
from .ui.main import (GLTF_PT_auto_export_main,
|
||||||
|
GLTF_PT_auto_export_root,
|
||||||
|
GLTF_PT_auto_export_blueprints,
|
||||||
|
GLTF_PT_auto_export_collections_list,
|
||||||
|
GLTF_PT_auto_export_gltf,
|
||||||
|
SCENE_UL_GLTF_auto_export,
|
||||||
|
AutoExportGLTF
|
||||||
|
)
|
||||||
|
from .ui.various import (SCENES_LIST_OT_actions)
|
||||||
|
from .helpers_scenes import (is_scene_ok)
|
||||||
|
|
||||||
|
bpy.context.window_manager['changed_objects_per_scene'] = {}
|
||||||
|
bpy.context.window_manager['previous_params'] = {}
|
||||||
|
bpy.context.window_manager['__gltf_auto_export_initialized'] = False
|
||||||
|
bpy.context.window_manager['__gltf_auto_export_gltf_params_changed'] = False
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
""" there are two places where we load settings for auto_export from:
|
||||||
|
- in ui/main AutoExportGLTF -> invoke
|
||||||
|
- in auto_export.py -> auto_export
|
||||||
|
This is a workaround needed because of the way the settings are stored , perhaps there is a better way to deal with it ? ie by calling the AutoExportGLTF operator from the auto_export function ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
#see here for original gltf exporter infos https://github.com/KhronosGroup/glTF-Blender-IO/blob/main/addons/io_scene_gltf2/__init__.py
|
||||||
|
@persistent
|
||||||
|
def deps_update_handler(scene, depsgraph):
|
||||||
|
if scene.name != "temp_scene": # actually do we care about anything else than the main scene(s) ?
|
||||||
|
print("depsgraph_update_post", scene.name)
|
||||||
|
print("-------------")
|
||||||
|
changed = scene.name or ""
|
||||||
|
|
||||||
|
# depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||||
|
if not 'changed_objects_per_scene' in bpy.context.window_manager:
|
||||||
|
bpy.context.window_manager['changed_objects_per_scene'] = {}
|
||||||
|
|
||||||
|
if not changed in bpy.context.window_manager['changed_objects_per_scene']:
|
||||||
|
bpy.context.window_manager['changed_objects_per_scene'][changed] = {}
|
||||||
|
|
||||||
|
for obj in depsgraph.updates:
|
||||||
|
if isinstance(obj.id, bpy.types.Object):
|
||||||
|
# get the actual object
|
||||||
|
object = bpy.data.objects[obj.id.name]
|
||||||
|
bpy.context.window_manager['changed_objects_per_scene'][scene.name][obj.id.name] = object
|
||||||
|
|
||||||
|
bpy.context.window_manager.changedScene = changed
|
||||||
|
|
||||||
|
@persistent
|
||||||
|
def save_handler(dummy):
|
||||||
|
print("-------------")
|
||||||
|
print("saved", bpy.data.filepath)
|
||||||
|
if not 'changed_objects_per_scene' in bpy.context.window_manager:
|
||||||
|
bpy.context.window_manager['changed_objects_per_scene'] = {}
|
||||||
|
changes_per_scene = bpy.context.window_manager['changed_objects_per_scene']
|
||||||
|
|
||||||
|
#determine changed parameters
|
||||||
|
addon_prefs = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
|
||||||
|
prefs = {}
|
||||||
|
for (k,v) in addon_prefs.items():
|
||||||
|
if k not in AutoExportGltfPreferenceNames:
|
||||||
|
prefs[k] = v
|
||||||
|
|
||||||
|
previous_params = bpy.context.window_manager['previous_params'] if 'previous_params' in bpy.context.window_manager else {}
|
||||||
|
set1 = set(previous_params.items())
|
||||||
|
set2 = set(prefs.items())
|
||||||
|
difference = dict(set1 ^ set2)
|
||||||
|
|
||||||
|
changed_param_names = list(set(difference.keys())- set(AutoExportGltfPreferenceNames))
|
||||||
|
changed_parameters = len(changed_param_names) > 0
|
||||||
|
# do the export
|
||||||
|
auto_export(changes_per_scene, changed_parameters)
|
||||||
|
|
||||||
|
|
||||||
|
# save the parameters
|
||||||
|
# todo add back
|
||||||
|
for (k, v) in prefs.items():
|
||||||
|
bpy.context.window_manager['previous_params'][k] = v
|
||||||
|
|
||||||
|
# reset a few things after exporting
|
||||||
|
# reset wether the gltf export paramters were changed since the last save
|
||||||
|
bpy.context.window_manager['__gltf_auto_export_gltf_params_changed'] = False
|
||||||
|
# reset whether there have been changed objects since the last save
|
||||||
|
bpy.context.window_manager['changed_objects_per_scene'] = {}
|
||||||
|
|
||||||
|
def get_changedScene(self):
|
||||||
|
return self["changedScene"]
|
||||||
|
|
||||||
|
def set_changedScene(self, value):
|
||||||
|
self["changedScene"] = value
|
||||||
|
|
||||||
|
classes = [
|
||||||
|
SceneLink,
|
||||||
|
SceneLinks,
|
||||||
|
CUSTOM_PG_sceneName,
|
||||||
|
SCENE_UL_GLTF_auto_export,
|
||||||
|
SCENES_LIST_OT_actions,
|
||||||
|
|
||||||
|
AutoExportGLTF,
|
||||||
|
AutoExportGltfAddonPreferences,
|
||||||
|
|
||||||
|
CollectionToExport,
|
||||||
|
CollectionsToExport,
|
||||||
|
|
||||||
|
GLTF_PT_auto_export_main,
|
||||||
|
GLTF_PT_auto_export_root,
|
||||||
|
GLTF_PT_auto_export_blueprints,
|
||||||
|
GLTF_PT_auto_export_collections_list,
|
||||||
|
GLTF_PT_auto_export_gltf
|
||||||
|
]
|
||||||
|
|
||||||
def menu_func_import(self, context):
|
def menu_func_import(self, context):
|
||||||
self.layout.operator(TEST_AUTO_OT_gltf.bl_idname, text="glTF auto Export (.glb/gltf)")
|
self.layout.operator(AutoExportGLTF.bl_idname, text="glTF auto Export (.glb/gltf)")
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_class(TEST_AUTO_OT_gltf)
|
for cls in classes:
|
||||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_import)
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
bpy.types.Scene.main_scene = bpy.props.PointerProperty(type=bpy.types.Scene, name="main scene", description="main_scene_chooser", poll=is_scene_ok)
|
||||||
|
bpy.types.Scene.library_scene = bpy.props.PointerProperty(type=bpy.types.Scene, name="library scene", description="library_scene_picker", poll=is_scene_ok)
|
||||||
|
|
||||||
|
# setup handlers for updates & saving
|
||||||
bpy.app.handlers.depsgraph_update_post.append(deps_update_handler)
|
bpy.app.handlers.depsgraph_update_post.append(deps_update_handler)
|
||||||
bpy.app.handlers.save_post.append(save_handler)
|
bpy.app.handlers.save_post.append(save_handler)
|
||||||
#bpy.types.TOPBAR_MT_file_export.append(menu_func_import)
|
|
||||||
bpy.types.Scene.changedScene = bpy.props.StringProperty(get=get_changedScene, set=set_ChangedScene)
|
|
||||||
|
|
||||||
|
bpy.types.WindowManager.changedScene = bpy.props.StringProperty(get=get_changedScene, set=set_changedScene)
|
||||||
|
bpy.types.WindowManager.exportedCollections = bpy.props.CollectionProperty(type=CollectionsToExport)
|
||||||
|
|
||||||
|
# add our addon to the toolbar
|
||||||
|
bpy.types.TOPBAR_MT_file_export.append(menu_func_import)
|
||||||
|
|
||||||
|
## just experiments
|
||||||
|
bpy.types.Scene.main_scenes_list_index = IntProperty(name = "Index for main scenes list", default = 0)
|
||||||
|
bpy.types.Scene.library_scenes_list_index = IntProperty(name = "Index for library scenes list", default = 0)
|
||||||
|
|
||||||
|
"""
|
||||||
|
mock_main_scenes = []
|
||||||
|
main_scenes = bpy.context.preferences.addons["gltf_auto_export"].preferences.main_scenes
|
||||||
|
for item_name in mock_main_scenes:
|
||||||
|
item = main_scenes.add()
|
||||||
|
item.name = item_name
|
||||||
|
|
||||||
|
mock_library_scenes = []
|
||||||
|
library_scenes = bpy.context.preferences.addons["gltf_auto_export"].preferences.library_scenes
|
||||||
|
for item_name in mock_library_scenes:
|
||||||
|
item = library_scenes.add()
|
||||||
|
item.name = item_name"""
|
||||||
|
|
||||||
|
bpy.context.preferences.addons["gltf_auto_export"].preferences.main_scenes_index = 0
|
||||||
|
bpy.context.preferences.addons["gltf_auto_export"].preferences.library_scenes_index = 0
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.utils.unregister_class(TEST_AUTO_OT_gltf)
|
for cls in classes:
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_import)
|
bpy.types.TOPBAR_MT_file_export.remove(menu_func_import)
|
||||||
|
|
||||||
|
# remove handlers & co
|
||||||
bpy.app.handlers.depsgraph_update_post.remove(deps_update_handler)
|
bpy.app.handlers.depsgraph_update_post.remove(deps_update_handler)
|
||||||
bpy.app.handlers.save_post.remove(save_handler)
|
bpy.app.handlers.save_post.remove(save_handler)
|
||||||
#bpy.types.TOPBAR_MT_file_export.remove(menu_func_import)
|
|
||||||
del bpy.types.Scene.changedScene
|
del bpy.types.WindowManager.changedScene
|
||||||
|
del bpy.types.WindowManager.exportedCollections
|
||||||
|
|
||||||
|
del bpy.types.Scene.main_scene
|
||||||
|
del bpy.types.Scene.library_scene
|
||||||
|
|
||||||
|
del bpy.types.Scene.main_scenes_list_index
|
||||||
|
del bpy.types.Scene.library_scenes_list_index
|
||||||
|
|
||||||
|
|
||||||
|
if "gltf_auto_export" == "__main__":
|
||||||
|
register()
|
154
tools/gltf_auto_export/auto_export.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import os
|
||||||
|
import bpy
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from .helpers_scenes import (get_scenes, )
|
||||||
|
from .helpers_collections import (get_exportable_collections, get_collections_per_scene)
|
||||||
|
from .helpers_export import (export_main_scene, export_blueprints_from_collections)
|
||||||
|
from .helpers import (check_if_blueprints_exist, check_if_blueprint_on_disk)
|
||||||
|
from .config import scene_key
|
||||||
|
|
||||||
|
"""Main function"""
|
||||||
|
def auto_export(changes_per_scene, changed_export_parameters):
|
||||||
|
addon_prefs = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
|
||||||
|
# a semi_hack to ensure we have the latest version of the settings
|
||||||
|
initialized = bpy.context.window_manager['__gltf_auto_export_initialized'] if '__gltf_auto_export_initialized' in bpy.context.window_manager else False
|
||||||
|
if not initialized:
|
||||||
|
print("not initialized, fetching settings if any")
|
||||||
|
# semi_hack to restore the correct settings if the add_on was installed before
|
||||||
|
settings = bpy.context.scene.get(scene_key)
|
||||||
|
if settings:
|
||||||
|
print("loading settings in main function")
|
||||||
|
try:
|
||||||
|
# Update filter if user saved settings
|
||||||
|
#if hasattr(self, 'export_format'):
|
||||||
|
# self.filter_glob = '*.glb' if self.export_format == 'GLB' else '*.gltf'
|
||||||
|
for (k, v) in settings.items():
|
||||||
|
setattr(addon_prefs, k, v)
|
||||||
|
# inject scenes data
|
||||||
|
if k == 'main_scene_names':
|
||||||
|
main_scenes = addon_prefs.main_scenes
|
||||||
|
for item_name in v:
|
||||||
|
item = main_scenes.add()
|
||||||
|
item.name = item_name
|
||||||
|
|
||||||
|
if k == 'library_scene_names':
|
||||||
|
library_scenes = addon_prefs.library_scenes
|
||||||
|
for item_name in v:
|
||||||
|
item = library_scenes.add()
|
||||||
|
item.name = item_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
print("error setting preferences from saved settings", error)
|
||||||
|
bpy.context.window_manager['__gltf_auto_export_initialized'] = True
|
||||||
|
|
||||||
|
# have the export parameters (not auto export, just gltf export) have changed: if yes (for example switch from glb to gltf, compression or not, animations or not etc), we need to re-export everything
|
||||||
|
print ("changed_export_parameters", changed_export_parameters)
|
||||||
|
try:
|
||||||
|
file_path = bpy.data.filepath
|
||||||
|
# Get the folder
|
||||||
|
folder_path = os.path.dirname(file_path)
|
||||||
|
# get the preferences for our addon
|
||||||
|
|
||||||
|
export_blueprints = getattr(addon_prefs,"export_blueprints")
|
||||||
|
export_output_folder = getattr(addon_prefs,"export_output_folder")
|
||||||
|
|
||||||
|
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
|
||||||
|
|
||||||
|
print("main scenes", main_scene_names, "library_scenes", library_scene_names)
|
||||||
|
print("export_output_folder", export_output_folder)
|
||||||
|
|
||||||
|
# export everything everytime
|
||||||
|
if export_blueprints:
|
||||||
|
print("EXPORTING")
|
||||||
|
# get a list of all collections actually in use
|
||||||
|
collections = get_exportable_collections(level_scenes, library_scenes)
|
||||||
|
# first check if all collections have already been exported before (if this is the first time the exporter is run
|
||||||
|
# in your current Blender session for example)
|
||||||
|
export_blueprints_path = os.path.join(folder_path, export_output_folder, getattr(addon_prefs,"export_blueprints_path")) if getattr(addon_prefs,"export_blueprints_path") != '' else folder_path
|
||||||
|
export_levels_path = os.path.join(folder_path, export_output_folder)
|
||||||
|
|
||||||
|
gltf_extension = getattr(addon_prefs, "export_format")
|
||||||
|
gltf_extension = '.glb' if gltf_extension == 'GLB' else '.gltf'
|
||||||
|
collections_not_on_disk = check_if_blueprints_exist(collections, export_blueprints_path, gltf_extension)
|
||||||
|
changed_collections = []
|
||||||
|
|
||||||
|
print('changes_per_scene', changes_per_scene.items(), changes_per_scene.keys())
|
||||||
|
for scene, bla in changes_per_scene.items():
|
||||||
|
print(" changed scene", scene)
|
||||||
|
for obj_name, obj in bla.items():
|
||||||
|
object_collections = list(obj.users_collection)
|
||||||
|
object_collection_names = list(map(lambda collection: collection.name, object_collections))
|
||||||
|
if len(object_collection_names) > 1:
|
||||||
|
print("ERRROR, objects in multiple collections not supported")
|
||||||
|
else:
|
||||||
|
object_collection_name = object_collection_names[0] if len(object_collection_names) > 0 else None
|
||||||
|
print(" object ", obj, object_collection_name)
|
||||||
|
if object_collection_name in collections:
|
||||||
|
changed_collections.append(object_collection_name)
|
||||||
|
|
||||||
|
collections_to_export = list(set(changed_collections + collections_not_on_disk))
|
||||||
|
|
||||||
|
# we need to re_export everything if the export parameters have been changed
|
||||||
|
collections_to_export = collections if changed_export_parameters else collections_to_export
|
||||||
|
|
||||||
|
collections_per_scene = get_collections_per_scene(collections_to_export, library_scenes)
|
||||||
|
|
||||||
|
# collections that do not come from a library should not be exported as seperate blueprints
|
||||||
|
library_collections = [name for sublist in collections_per_scene.values() for name in sublist]
|
||||||
|
collections_to_export = list(set(collections_to_export).intersection(set(library_collections)))
|
||||||
|
|
||||||
|
print("--------------")
|
||||||
|
print("collections: all:", collections)
|
||||||
|
print("collections: changed:", changed_collections)
|
||||||
|
print("collections: not found on disk:", collections_not_on_disk)
|
||||||
|
print("collections: to export:", collections_to_export)
|
||||||
|
print("collections: per_scene:", collections_per_scene)
|
||||||
|
|
||||||
|
# backup current active scene
|
||||||
|
old_current_scene = bpy.context.scene
|
||||||
|
# backup current selections
|
||||||
|
old_selections = bpy.context.selected_objects
|
||||||
|
|
||||||
|
# first export any main/level/world scenes
|
||||||
|
print("export MAIN scenes")
|
||||||
|
for scene_name in main_scene_names:
|
||||||
|
do_export_main_scene = changed_export_parameters or (scene_name in changes_per_scene.keys() and len(changes_per_scene[scene_name].keys()) > 0) or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)
|
||||||
|
if do_export_main_scene:
|
||||||
|
print(" exporting scene:", scene_name)
|
||||||
|
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, collections)
|
||||||
|
|
||||||
|
|
||||||
|
# now deal with blueprints/collections
|
||||||
|
do_export_library_scene = changed_export_parameters or len(collections_to_export) > 0 # export_library_scene_name in changes_per_scene.keys()
|
||||||
|
print("export LIBRARY")
|
||||||
|
if do_export_library_scene:
|
||||||
|
# we only want to go through the library scenes where our collections to export are present
|
||||||
|
for (scene_name, collections_to_export) in collections_per_scene.items():
|
||||||
|
print(" exporting collections from scene:", scene_name)
|
||||||
|
print(" collections to export", collections_to_export)
|
||||||
|
library_scene = bpy.data.scenes[scene_name]
|
||||||
|
export_blueprints_from_collections(collections_to_export, library_scene, folder_path, addon_prefs)
|
||||||
|
|
||||||
|
|
||||||
|
# reset current scene from backup
|
||||||
|
bpy.context.window.scene = old_current_scene
|
||||||
|
|
||||||
|
# reset selections
|
||||||
|
for obj in old_selections:
|
||||||
|
obj.select_set(True)
|
||||||
|
else:
|
||||||
|
for scene_name in main_scene_names:
|
||||||
|
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
traceback.print_stack()
|
||||||
|
def error_message(self, context):
|
||||||
|
self.layout.label(text="Failure during auto_export: Error: "+ str(error))
|
||||||
|
|
||||||
|
bpy.context.window_manager.popup_menu(error_message, title="Error", icon='ERROR')
|
||||||
|
|
||||||
|
|
1
tools/gltf_auto_export/config.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
scene_key = "auto_gltfExportSettings"
|
BIN
tools/gltf_auto_export/docs/blender_addon_add_scene.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
tools/gltf_auto_export/docs/blender_addon_add_scene2.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
tools/gltf_auto_export/docs/blender_addon_add_scene3.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
tools/gltf_auto_export/docs/blender_addon_install.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
tools/gltf_auto_export/docs/blender_addon_install2.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
tools/gltf_auto_export/docs/blender_addon_use.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
tools/gltf_auto_export/docs/blender_addon_use2.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
tools/gltf_auto_export/docs/blender_addon_use3.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
tools/gltf_auto_export/docs/exported_collections.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
tools/gltf_auto_export/docs/exported_library_files.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
tools/gltf_auto_export/docs/force_export.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
BIN
tools/gltf_auto_export/docs/workflow_empties.jpg
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
tools/gltf_auto_export/docs/workflow_original.jpg
Normal file
After Width: | Height: | Size: 41 KiB |
63
tools/gltf_auto_export/helpers.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import os
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
#####################################################
|
||||||
|
#### Helpers ####
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Makes an empty, at location, stores it in existing collection, from https://blender.stackexchange.com/questions/51290/how-to-add-empty-object-not-using-bpy-ops
|
||||||
|
def make_empty(name, location, coll_name): #string, vector, string of existing coll
|
||||||
|
empty_obj = bpy.data.objects.new( "empty", None, )
|
||||||
|
empty_obj.name = name
|
||||||
|
empty_obj.empty_display_size = 1
|
||||||
|
bpy.data.collections[coll_name].objects.link(empty_obj)
|
||||||
|
empty_obj.location = location
|
||||||
|
return empty_obj
|
||||||
|
|
||||||
|
|
||||||
|
def make_empty2(name, location, collection):
|
||||||
|
object_data = None #bpy.data.meshes.new("NewMesh") #None
|
||||||
|
empty_obj = bpy.data.objects.new( name, object_data )
|
||||||
|
empty_obj.name = name
|
||||||
|
empty_obj.location = location
|
||||||
|
|
||||||
|
|
||||||
|
empty_obj.empty_display_size = 2
|
||||||
|
empty_obj.empty_display_type = 'PLAIN_AXES'
|
||||||
|
collection.objects.link( empty_obj )
|
||||||
|
return empty_obj
|
||||||
|
|
||||||
|
def make_empty3(name, location, rotation, scale, collection):
|
||||||
|
original_active_object = bpy.context.active_object
|
||||||
|
bpy.ops.object.empty_add(type='PLAIN_AXES', location=location, rotation=rotation, scale=scale)
|
||||||
|
empty_obj = bpy.context.active_object
|
||||||
|
empty_obj.name = name
|
||||||
|
empty_obj.scale = scale # scale is not set correctly ?????
|
||||||
|
bpy.context.view_layer.objects.active = original_active_object
|
||||||
|
return empty_obj
|
||||||
|
|
||||||
|
|
||||||
|
def traverse_tree(t):
|
||||||
|
yield t
|
||||||
|
for child in t.children:
|
||||||
|
yield from traverse_tree(child)
|
||||||
|
|
||||||
|
|
||||||
|
def check_if_blueprints_exist(collections, folder_path, extension):
|
||||||
|
not_found_blueprints = []
|
||||||
|
for collection_name in collections:
|
||||||
|
gltf_output_path = os.path.join(folder_path, collection_name + extension)
|
||||||
|
print("gltf_output_path", gltf_output_path)
|
||||||
|
found = os.path.exists(gltf_output_path) and os.path.isfile(gltf_output_path)
|
||||||
|
if not found:
|
||||||
|
not_found_blueprints.append(collection_name)
|
||||||
|
return not_found_blueprints
|
||||||
|
|
||||||
|
|
||||||
|
def check_if_blueprint_on_disk(scene_name, folder_path, extension):
|
||||||
|
gltf_output_path = os.path.join(folder_path, scene_name + extension)
|
||||||
|
found = os.path.exists(gltf_output_path) and os.path.isfile(gltf_output_path)
|
||||||
|
print("level", scene_name, "found", found, "path", gltf_output_path)
|
||||||
|
return found
|
||||||
|
|
97
tools/gltf_auto_export/helpers_collections.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import bpy
|
||||||
|
from .helpers import traverse_tree
|
||||||
|
|
||||||
|
# returns the list of the collections in use for a given scene
|
||||||
|
def get_used_collections(scene):
|
||||||
|
root_collection = scene.collection
|
||||||
|
|
||||||
|
scene_objects = [o for o in root_collection.objects]
|
||||||
|
collection_names = set()
|
||||||
|
used_collections = []
|
||||||
|
for object in scene_objects:
|
||||||
|
#print("object ", object)
|
||||||
|
if object.instance_type == 'COLLECTION':
|
||||||
|
#print("THIS OBJECT IS A COLLECTION")
|
||||||
|
# print("instance_type" ,object.instance_type)
|
||||||
|
collection_name = object.instance_collection.name
|
||||||
|
#print("instance collection", object.instance_collection.name)
|
||||||
|
#object.instance_collection.users_scene
|
||||||
|
# del object['blueprint']
|
||||||
|
# object['BlueprintName'] = '"'+collection_name+'"'
|
||||||
|
if not collection_name in collection_names:
|
||||||
|
collection_names.add(collection_name)
|
||||||
|
used_collections.append(object.instance_collection)
|
||||||
|
|
||||||
|
#print("scene objects", scene_objects)
|
||||||
|
return (collection_names, used_collections)
|
||||||
|
|
||||||
|
# gets all collections that should ALWAYS be exported to their respective gltf files, even if they are not used in the main scene/level
|
||||||
|
def get_marked_collections(scene):
|
||||||
|
# print("checking library for marked collections")
|
||||||
|
root_collection = scene.collection
|
||||||
|
marked_collections = []
|
||||||
|
collection_names = []
|
||||||
|
for collection in traverse_tree(root_collection):
|
||||||
|
if 'AutoExport' in collection and collection['AutoExport'] == True:
|
||||||
|
marked_collections.append(collection)
|
||||||
|
collection_names.append(collection.name)
|
||||||
|
return (collection_names, marked_collections)
|
||||||
|
|
||||||
|
# get exportable collections from lists of mains scenes and lists of library scenes
|
||||||
|
def get_exportable_collections(main_scenes, library_scenes):
|
||||||
|
all_collections = []
|
||||||
|
for main_scene in main_scenes:
|
||||||
|
(collection_names, _) = get_used_collections(main_scene)
|
||||||
|
all_collections = all_collections + list(collection_names)
|
||||||
|
for library_scene in library_scenes:
|
||||||
|
marked_collections = get_marked_collections(library_scene)
|
||||||
|
all_collections = all_collections + marked_collections[0]
|
||||||
|
return all_collections
|
||||||
|
|
||||||
|
def get_collections_per_scene(collection_names, library_scenes):
|
||||||
|
collections_per_scene = {}
|
||||||
|
for scene in library_scenes:
|
||||||
|
root_collection = scene.collection
|
||||||
|
for cur_collection in traverse_tree(root_collection):
|
||||||
|
if cur_collection.name in collection_names:
|
||||||
|
if not scene.name in collections_per_scene:
|
||||||
|
collections_per_scene[scene.name] = []
|
||||||
|
collections_per_scene[scene.name].append(cur_collection.name)
|
||||||
|
|
||||||
|
return collections_per_scene
|
||||||
|
|
||||||
|
|
||||||
|
def get_collection_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
|
||||||
|
|
||||||
|
# the active collection is a View Layer concept, so you actually have to find the active LayerCollection
|
||||||
|
# which must be done recursively
|
||||||
|
def find_layer_collection_recursive(find, col):
|
||||||
|
# print("root collection", col)
|
||||||
|
for c in col.children:
|
||||||
|
# print("child collection", c)
|
||||||
|
if c.collection == find:
|
||||||
|
return c
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
#Recursivly transverse layer_collection for a particular name
|
||||||
|
def recurLayerCollection(layerColl, collName):
|
||||||
|
found = None
|
||||||
|
if (layerColl.name == collName):
|
||||||
|
return layerColl
|
||||||
|
for layer in layerColl.children:
|
||||||
|
found = recurLayerCollection(layer, collName)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
|
149
tools/gltf_auto_export/helpers_export.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import os
|
||||||
|
import bpy
|
||||||
|
from .preferences import (AutoExportGltfPreferenceNames)
|
||||||
|
from .helpers_scenes import (generate_hollow_scene, clear_hollow_scene)
|
||||||
|
from .helpers_collections import (recurLayerCollection)
|
||||||
|
from .helpers import (traverse_tree)
|
||||||
|
######################################################
|
||||||
|
#### Export logic #####
|
||||||
|
|
||||||
|
|
||||||
|
def generate_gltf_export_preferences(addon_prefs):
|
||||||
|
# default values
|
||||||
|
gltf_export_preferences = dict(
|
||||||
|
export_format= 'GLB', #'GLB', 'GLTF_SEPARATE', 'GLTF_EMBEDDED'
|
||||||
|
check_existing=False,
|
||||||
|
|
||||||
|
use_selection=False,
|
||||||
|
use_visible=True, # Export visible and hidden objects. See Object/Batch Export to skip.
|
||||||
|
use_renderable=False,
|
||||||
|
use_active_collection= False,
|
||||||
|
use_active_collection_with_nested=False,
|
||||||
|
use_active_scene = False,
|
||||||
|
|
||||||
|
export_texcoords=True,
|
||||||
|
export_normals=True,
|
||||||
|
# here add draco settings
|
||||||
|
export_draco_mesh_compression_enable = False,
|
||||||
|
|
||||||
|
export_tangents=False,
|
||||||
|
#export_materials
|
||||||
|
export_colors=True,
|
||||||
|
export_attributes=True,
|
||||||
|
#use_mesh_edges
|
||||||
|
#use_mesh_vertices
|
||||||
|
export_cameras=True,
|
||||||
|
export_extras=True, # For custom exported properties.
|
||||||
|
export_lights=True,
|
||||||
|
export_yup=True,
|
||||||
|
export_skins=True,
|
||||||
|
export_morph=False,
|
||||||
|
export_apply=False,
|
||||||
|
export_animations=False
|
||||||
|
)
|
||||||
|
|
||||||
|
for key in addon_prefs.__annotations__.keys():
|
||||||
|
if str(key) not in AutoExportGltfPreferenceNames:
|
||||||
|
#print("overriding setting", key, "value", getattr(addon_prefs,key))
|
||||||
|
gltf_export_preferences[key] = getattr(addon_prefs,key)
|
||||||
|
|
||||||
|
return gltf_export_preferences
|
||||||
|
|
||||||
|
# find which of the library scenes the given collection stems from
|
||||||
|
# TODO: does not seem efficient at all ?
|
||||||
|
def get_source_scene(collection_name, library_scenes):
|
||||||
|
match = None
|
||||||
|
for scene in library_scenes:
|
||||||
|
root_collection = scene.collection
|
||||||
|
found = False
|
||||||
|
for cur_collection in traverse_tree(root_collection):
|
||||||
|
if cur_collection.name == collection_name:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
match = scene
|
||||||
|
break
|
||||||
|
return match
|
||||||
|
|
||||||
|
# export collections: all the collections that have an instance in the main scene AND any marked collections, even if they do not have instances
|
||||||
|
def export_collections(collections, folder_path, library_scene, addon_prefs, gltf_export_preferences):
|
||||||
|
# set active scene to be the library scene (hack for now)
|
||||||
|
bpy.context.window.scene = library_scene
|
||||||
|
# save current active collection
|
||||||
|
active_collection = bpy.context.view_layer.active_layer_collection
|
||||||
|
|
||||||
|
for collection_name in collections:
|
||||||
|
print("exporting collection", collection_name)
|
||||||
|
layer_collection = bpy.context.view_layer.layer_collection
|
||||||
|
layerColl = recurLayerCollection(layer_collection, collection_name)
|
||||||
|
# set active collection to the collection
|
||||||
|
bpy.context.view_layer.active_layer_collection = layerColl
|
||||||
|
|
||||||
|
gltf_output_path = os.path.join(folder_path, collection_name)
|
||||||
|
|
||||||
|
export_settings = { **gltf_export_preferences, 'use_active_scene': True, 'use_active_collection': True, 'use_active_collection_with_nested':True} #'use_visible': False,
|
||||||
|
export_gltf(gltf_output_path, export_settings)
|
||||||
|
|
||||||
|
# reset active collection to the one we save before
|
||||||
|
bpy.context.view_layer.active_layer_collection = active_collection
|
||||||
|
|
||||||
|
|
||||||
|
def export_blueprints_from_collections(collections, library_scene, folder_path, addon_prefs):
|
||||||
|
export_output_folder = getattr(addon_prefs,"export_output_folder")
|
||||||
|
gltf_export_preferences = generate_gltf_export_preferences(addon_prefs)
|
||||||
|
export_blueprints_path = os.path.join(folder_path, export_output_folder, getattr(addon_prefs,"export_blueprints_path")) if getattr(addon_prefs,"export_blueprints_path") != '' else folder_path
|
||||||
|
|
||||||
|
#print("-----EXPORTING BLUEPRINTS----")
|
||||||
|
#print("LIBRARY EXPORT", export_blueprints_path )
|
||||||
|
|
||||||
|
try:
|
||||||
|
export_collections(collections, export_blueprints_path, library_scene, addon_prefs, gltf_export_preferences)
|
||||||
|
except Exception as error:
|
||||||
|
print("failed to export collections to gltf: ", error)
|
||||||
|
# TODO : rethrow
|
||||||
|
|
||||||
|
|
||||||
|
# export all main scenes
|
||||||
|
def export_main_scenes(scenes, folder_path, addon_prefs):
|
||||||
|
for scene in scenes:
|
||||||
|
export_main_scene(scene, folder_path, addon_prefs)
|
||||||
|
|
||||||
|
def export_main_scene(scene, folder_path, addon_prefs, library_collections):
|
||||||
|
export_output_folder = getattr(addon_prefs,"export_output_folder")
|
||||||
|
gltf_export_preferences = generate_gltf_export_preferences(addon_prefs)
|
||||||
|
|
||||||
|
export_blueprints = getattr(addon_prefs,"export_blueprints")
|
||||||
|
|
||||||
|
if export_blueprints :
|
||||||
|
(hollow_scene, object_names) = generate_hollow_scene(scene, library_collections)
|
||||||
|
#except Exception:
|
||||||
|
# print("failed to create hollow scene")
|
||||||
|
|
||||||
|
# set active scene to be the given scene
|
||||||
|
bpy.context.window.scene = hollow_scene
|
||||||
|
|
||||||
|
gltf_output_path = os.path.join(folder_path, export_output_folder, scene.name)
|
||||||
|
print(" exporting gltf to", gltf_output_path, ".gltf/glb")
|
||||||
|
|
||||||
|
|
||||||
|
export_settings = { **gltf_export_preferences,
|
||||||
|
'use_active_scene': True,
|
||||||
|
'use_active_collection':True,
|
||||||
|
'use_active_collection_with_nested':True,
|
||||||
|
'use_visible': False,
|
||||||
|
'use_renderable': False,
|
||||||
|
'export_apply':True
|
||||||
|
}
|
||||||
|
export_gltf(gltf_output_path, export_settings)
|
||||||
|
|
||||||
|
if export_blueprints :
|
||||||
|
clear_hollow_scene(hollow_scene, scene, object_names)
|
||||||
|
|
||||||
|
|
||||||
|
#https://docs.blender.org/api/current/bpy.ops.export_scene.html#bpy.ops.export_scene.gltf
|
||||||
|
def export_gltf (path, export_settings):
|
||||||
|
settings = {**export_settings, "filepath": path}
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
bpy.ops.export_scene.gltf(**settings)
|
||||||
|
|
||||||
|
|
102
tools/gltf_auto_export/helpers_scenes.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import bpy
|
||||||
|
from .helpers_collections import (find_layer_collection_recursive)
|
||||||
|
from .helpers import (make_empty3)
|
||||||
|
|
||||||
|
# generate a copy of a scene that replaces collection instances with empties
|
||||||
|
# alternative: copy original names before creating a new scene, & reset them
|
||||||
|
# or create empties, hide original ones, and do the same renaming trick
|
||||||
|
def generate_hollow_scene(scene, library_collections):
|
||||||
|
root_collection = scene.collection
|
||||||
|
temp_scene = bpy.data.scenes.new(name="temp_scene")
|
||||||
|
copy_root_collection = temp_scene.collection
|
||||||
|
|
||||||
|
# we set our active scene to be this one : this is needed otherwise the stand-in empties get generated in the wrong scene
|
||||||
|
bpy.context.window.scene = temp_scene
|
||||||
|
|
||||||
|
found = find_layer_collection_recursive(copy_root_collection, bpy.context.view_layer.layer_collection)
|
||||||
|
if found:
|
||||||
|
# once it's found, set the active layer collection to the one we found
|
||||||
|
bpy.context.view_layer.active_layer_collection = found
|
||||||
|
|
||||||
|
#original_names = {}
|
||||||
|
original_names = []
|
||||||
|
|
||||||
|
# copies the contents of a collection into another one while replacing library instances with empties
|
||||||
|
def copy_hollowed_collection_into(source_collection, destination_collection):
|
||||||
|
for object in source_collection.objects:
|
||||||
|
if object.instance_type == 'COLLECTION' and (object.instance_collection.name in library_collections):
|
||||||
|
collection_name = object.instance_collection.name
|
||||||
|
|
||||||
|
original_name = object.name
|
||||||
|
original_names.append(original_name)
|
||||||
|
|
||||||
|
object.name = original_name + "____bak"
|
||||||
|
empty_obj = make_empty3(original_name, object.location, object.rotation_euler, object.scale, destination_collection)
|
||||||
|
"""we inject the collection/blueprint name, as a component called 'BlueprintName', but we only do this in the empty, not the original object"""
|
||||||
|
empty_obj['BlueprintName'] = '"'+collection_name+'"'
|
||||||
|
empty_obj['SpawnHere'] = ''
|
||||||
|
|
||||||
|
for k, v in object.items():
|
||||||
|
empty_obj[k] = v
|
||||||
|
else:
|
||||||
|
destination_collection.objects.link(object)
|
||||||
|
|
||||||
|
# for every sub-collection of the source, copy its content into a new sub-collection of the destination
|
||||||
|
for collection in source_collection.children:
|
||||||
|
copy_collection = bpy.data.collections.new(collection.name + "____collection_export")
|
||||||
|
copy_hollowed_collection_into(collection, copy_collection)
|
||||||
|
destination_collection.children.link(copy_collection)
|
||||||
|
|
||||||
|
copy_hollowed_collection_into(root_collection, copy_root_collection)
|
||||||
|
|
||||||
|
# objs = bpy.data.objects
|
||||||
|
#objs.remove(objs["Cube"], do_unlink=True)
|
||||||
|
return (temp_scene, original_names)
|
||||||
|
|
||||||
|
# clear & remove "hollow scene"
|
||||||
|
def clear_hollow_scene(temp_scene, original_scene, original_names):
|
||||||
|
# reset original names
|
||||||
|
root_collection = original_scene.collection
|
||||||
|
|
||||||
|
def restore_original_names(collection):
|
||||||
|
for object in collection.objects:
|
||||||
|
if object.instance_type == 'COLLECTION':
|
||||||
|
if object.name.endswith("____bak"):
|
||||||
|
object.name = object.name.replace("____bak", "")
|
||||||
|
for child_collection in collection.children:
|
||||||
|
restore_original_names(child_collection)
|
||||||
|
|
||||||
|
restore_original_names(root_collection)
|
||||||
|
|
||||||
|
# remove empties (only needed when we go via ops ????)
|
||||||
|
root_collection = temp_scene.collection
|
||||||
|
scene_objects = [o for o in root_collection.objects]
|
||||||
|
for object in scene_objects:
|
||||||
|
if object.type == 'EMPTY':
|
||||||
|
if hasattr(object, "SpawnHere"):
|
||||||
|
bpy.data.objects.remove(object, do_unlink=True)
|
||||||
|
else:
|
||||||
|
bpy.context.scene.collection.objects.unlink(object)
|
||||||
|
#bpy.data.objects.remove(object, do_unlink=True)
|
||||||
|
|
||||||
|
bpy.data.scenes.remove(temp_scene)
|
||||||
|
|
||||||
|
|
||||||
|
# convenience utility to get lists of scenes
|
||||||
|
def get_scenes(addon_prefs):
|
||||||
|
level_scene_names= list(map(lambda scene: scene.name, getattr(addon_prefs,"main_scenes")))
|
||||||
|
library_scene_names = list(map(lambda scene: scene.name, getattr(addon_prefs,"library_scenes")))
|
||||||
|
|
||||||
|
level_scene_names = list(filter(lambda name: name in bpy.data.scenes, level_scene_names))
|
||||||
|
library_scene_names = list(filter(lambda name: name in bpy.data.scenes, library_scene_names))
|
||||||
|
|
||||||
|
level_scenes = list(map(lambda name: bpy.data.scenes[name], level_scene_names))
|
||||||
|
library_scenes = list(map(lambda name: bpy.data.scenes[name], library_scene_names))
|
||||||
|
|
||||||
|
return [level_scene_names, level_scenes, library_scene_names, library_scenes]
|
||||||
|
|
||||||
|
|
||||||
|
def is_scene_ok(self, scene):
|
||||||
|
prefs = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
return scene.name not in prefs.main_scenes and scene.name not in prefs.library_scenes
|
||||||
|
|
22
tools/gltf_auto_export/internals.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
class SceneLink(bpy.types.PropertyGroup):
|
||||||
|
name: bpy.props.StringProperty(name="")
|
||||||
|
scene: bpy.props.PointerProperty(type=bpy.types.Scene)
|
||||||
|
|
||||||
|
class SceneLinks(bpy.types.PropertyGroup):
|
||||||
|
name = bpy.props.StringProperty(name="List of scenes to export", default="Unknown")
|
||||||
|
items: bpy.props.CollectionProperty(type = SceneLink)
|
||||||
|
|
||||||
|
class CUSTOM_PG_sceneName(bpy.types.PropertyGroup):
|
||||||
|
name: bpy.props.StringProperty()
|
||||||
|
display: bpy.props.BoolProperty()
|
||||||
|
|
||||||
|
class CollectionToExport(bpy.types.PropertyGroup):
|
||||||
|
name: bpy.props.StringProperty(name="")
|
||||||
|
|
||||||
|
class CollectionsToExport(bpy.types.PropertyGroup):
|
||||||
|
name = bpy.props.StringProperty(name="List of collections to export", default="Unknown")
|
||||||
|
items: bpy.props.CollectionProperty(type = CollectionToExport)
|
||||||
|
|
||||||
|
|
319
tools/gltf_auto_export/preferences.py
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
|
||||||
|
from bpy.types import AddonPreferences
|
||||||
|
|
||||||
|
from bpy.props import (BoolProperty,
|
||||||
|
IntProperty,
|
||||||
|
StringProperty,
|
||||||
|
EnumProperty,
|
||||||
|
CollectionProperty
|
||||||
|
)
|
||||||
|
|
||||||
|
from .internals import (CUSTOM_PG_sceneName)
|
||||||
|
|
||||||
|
AutoExportGltfPreferenceNames = [
|
||||||
|
'auto_export',
|
||||||
|
'export_main_scene_name',
|
||||||
|
'export_output_folder',
|
||||||
|
'export_library_scene_name',
|
||||||
|
'export_blueprints',
|
||||||
|
'export_blueprints_path',
|
||||||
|
|
||||||
|
'main_scenes',
|
||||||
|
'library_scenes',
|
||||||
|
'main_scenes_index',
|
||||||
|
'library_scenes_index'
|
||||||
|
]
|
||||||
|
|
||||||
|
class AutoExportGltfAddonPreferences(AddonPreferences):
|
||||||
|
# this must match the add-on name, use '__package__'
|
||||||
|
# when defining this in a submodule of a python package.
|
||||||
|
bl_idname = __package__
|
||||||
|
bl_options = {'PRESET'}
|
||||||
|
|
||||||
|
auto_export: BoolProperty(
|
||||||
|
name='Auto export',
|
||||||
|
description='Automatically export to gltf on save',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
export_main_scene_name: StringProperty(
|
||||||
|
name='Main scene',
|
||||||
|
description='The name of the main scene/level/world to auto export',
|
||||||
|
default='Scene'
|
||||||
|
)
|
||||||
|
export_output_folder: StringProperty(
|
||||||
|
name='Export folder (relative)',
|
||||||
|
description='The root folder for all exports(relative to current file) Defaults to current folder',
|
||||||
|
default=''
|
||||||
|
)
|
||||||
|
export_library_scene_name: StringProperty(
|
||||||
|
name='Library scene',
|
||||||
|
description='The name of the library scene to auto export',
|
||||||
|
default='Library'
|
||||||
|
)
|
||||||
|
# blueprint settings
|
||||||
|
export_blueprints: BoolProperty(
|
||||||
|
name='Export Blueprints',
|
||||||
|
description='Replaces collection instances with an Empty with a BlueprintName custom property',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
export_blueprints_path: StringProperty(
|
||||||
|
name='Blueprints path',
|
||||||
|
description='path to export the blueprints to (relative to the Export folder)',
|
||||||
|
default='library'
|
||||||
|
)
|
||||||
|
|
||||||
|
main_scenes: CollectionProperty(name="main scenes", type=CUSTOM_PG_sceneName)
|
||||||
|
main_scenes_index: IntProperty(name = "Index for main scenes list", default = 0)
|
||||||
|
|
||||||
|
library_scenes: CollectionProperty(name="library scenes", type=CUSTOM_PG_sceneName)
|
||||||
|
library_scenes_index: IntProperty(name = "Index for library scenes list", default = 0)
|
||||||
|
|
||||||
|
#####
|
||||||
|
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'
|
||||||
|
)
|
||||||
|
export_copyright: StringProperty(
|
||||||
|
name='Copyright',
|
||||||
|
description='Legal rights and conditions for the model',
|
||||||
|
default=''
|
||||||
|
)
|
||||||
|
|
||||||
|
export_image_format: EnumProperty(
|
||||||
|
name='Images',
|
||||||
|
items=(('AUTO', 'Automatic',
|
||||||
|
'Save PNGs as PNGs and JPEGs as JPEGs. '
|
||||||
|
'If neither one, use PNG'),
|
||||||
|
('JPEG', 'JPEG Format (.jpg)',
|
||||||
|
'Save images as JPEGs. (Images that need alpha are saved as PNGs though.) '
|
||||||
|
'Be aware of a possible loss in quality'),
|
||||||
|
('NONE', 'None',
|
||||||
|
'Don\'t export images'),
|
||||||
|
),
|
||||||
|
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. Alternatively they can be omitted if they are not needed'
|
||||||
|
),
|
||||||
|
default='AUTO'
|
||||||
|
)
|
||||||
|
|
||||||
|
export_texture_dir: StringProperty(
|
||||||
|
name='Textures',
|
||||||
|
description='Folder to place texture files in. Relative to the .gltf file',
|
||||||
|
default='',
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
export_jpeg_quality: IntProperty(
|
||||||
|
name='JPEG quality',
|
||||||
|
description='Quality of JPEG export',
|
||||||
|
default=75,
|
||||||
|
min=0,
|
||||||
|
max=100
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
export_keep_originals: BoolProperty(
|
||||||
|
name='Keep original',
|
||||||
|
description=('Keep original textures files if possible. '
|
||||||
|
'WARNING: if you use more than one texture, '
|
||||||
|
'where pbr standard requires only one, only one texture will be used. '
|
||||||
|
'This can lead to unexpected results'
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
export_texcoords: BoolProperty(
|
||||||
|
name='UVs',
|
||||||
|
description='Export UVs (texture coordinates) with meshes',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
export_normals: BoolProperty(
|
||||||
|
name='Normals',
|
||||||
|
description='Export vertex normals with meshes',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
export_draco_mesh_compression_enable: BoolProperty(
|
||||||
|
name='Draco mesh compression',
|
||||||
|
description='Compress mesh using Draco',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
export_draco_mesh_compression_level: IntProperty(
|
||||||
|
name='Compression level',
|
||||||
|
description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)',
|
||||||
|
default=6,
|
||||||
|
min=0,
|
||||||
|
max=10
|
||||||
|
)
|
||||||
|
|
||||||
|
export_draco_position_quantization: IntProperty(
|
||||||
|
name='Position quantization bits',
|
||||||
|
description='Quantization bits for position values (0 = no quantization)',
|
||||||
|
default=14,
|
||||||
|
min=0,
|
||||||
|
max=30
|
||||||
|
)
|
||||||
|
|
||||||
|
export_draco_normal_quantization: IntProperty(
|
||||||
|
name='Normal quantization bits',
|
||||||
|
description='Quantization bits for normal values (0 = no quantization)',
|
||||||
|
default=10,
|
||||||
|
min=0,
|
||||||
|
max=30
|
||||||
|
)
|
||||||
|
|
||||||
|
export_draco_texcoord_quantization: IntProperty(
|
||||||
|
name='Texcoord quantization bits',
|
||||||
|
description='Quantization bits for texture coordinate values (0 = no quantization)',
|
||||||
|
default=12,
|
||||||
|
min=0,
|
||||||
|
max=30
|
||||||
|
)
|
||||||
|
|
||||||
|
export_draco_color_quantization: IntProperty(
|
||||||
|
name='Color quantization bits',
|
||||||
|
description='Quantization bits for color values (0 = no quantization)',
|
||||||
|
default=10,
|
||||||
|
min=0,
|
||||||
|
max=30
|
||||||
|
)
|
||||||
|
|
||||||
|
export_draco_generic_quantization: IntProperty(
|
||||||
|
name='Generic quantization bits',
|
||||||
|
description='Quantization bits for generic coordinate values like weights or joints (0 = no quantization)',
|
||||||
|
default=12,
|
||||||
|
min=0,
|
||||||
|
max=30
|
||||||
|
)
|
||||||
|
|
||||||
|
export_tangents: BoolProperty(
|
||||||
|
name='Tangents',
|
||||||
|
description='Export vertex tangents with meshes',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
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'
|
||||||
|
)
|
||||||
|
|
||||||
|
export_original_specular: BoolProperty(
|
||||||
|
name='Export original PBR Specular',
|
||||||
|
description=(
|
||||||
|
'Export original glTF PBR Specular, instead of Blender Principled Shader Specular'
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
export_colors: BoolProperty(
|
||||||
|
name='Vertex Colors',
|
||||||
|
description='Export vertex colors with meshes',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
export_attributes: BoolProperty(
|
||||||
|
name='Attributes',
|
||||||
|
description='Export Attributes (when starting with underscore)',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
use_mesh_edges: BoolProperty(
|
||||||
|
name='Loose Edges',
|
||||||
|
description=(
|
||||||
|
'Export loose edges as lines, using the material from the first material slot'
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
use_mesh_vertices: BoolProperty(
|
||||||
|
name='Loose Points',
|
||||||
|
description=(
|
||||||
|
'Export loose points as glTF points, using the material from the first material slot'
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
export_cameras: BoolProperty(
|
||||||
|
name='Cameras',
|
||||||
|
description='Export cameras',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
use_selection: BoolProperty(
|
||||||
|
name='Selected Objects',
|
||||||
|
description='Export selected objects only',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
use_visible: BoolProperty(
|
||||||
|
name='Visible Objects',
|
||||||
|
description='Export visible objects only',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
use_renderable: BoolProperty(
|
||||||
|
name='Renderable Objects',
|
||||||
|
description='Export renderable objects only',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
export_apply: BoolProperty(
|
||||||
|
name='Export Apply Modifiers',
|
||||||
|
description='Apply modifiers (excluding Armatures) to mesh objects -'
|
||||||
|
'WARNING: prevents exporting shape keys',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
export_yup: BoolProperty(
|
||||||
|
name='+Y Up',
|
||||||
|
description='Export using glTF convention, +Y up',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
use_visible: BoolProperty(
|
||||||
|
name='Visible Objects',
|
||||||
|
description='Export visible objects only',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
use_renderable: BoolProperty(
|
||||||
|
name='Renderable Objects',
|
||||||
|
description='Export renderable objects only',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
export_extras: BoolProperty(
|
||||||
|
name='Custom Properties',
|
||||||
|
description='Export custom properties as glTF extras',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
export_animations: BoolProperty(
|
||||||
|
name='Animations',
|
||||||
|
description='Exports active actions and NLA tracks as glTF animations',
|
||||||
|
default=False
|
||||||
|
)
|
0
tools/gltf_auto_export/ui/__init__.py
Normal file
387
tools/gltf_auto_export/ui/main.py
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy_extras.io_utils import ExportHelper
|
||||||
|
from bpy.props import (BoolProperty,
|
||||||
|
IntProperty,
|
||||||
|
StringProperty,
|
||||||
|
EnumProperty,
|
||||||
|
CollectionProperty
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..preferences import (AutoExportGltfAddonPreferences, AutoExportGltfPreferenceNames)
|
||||||
|
from ..helpers_scenes import (get_scenes)
|
||||||
|
from ..helpers_collections import (get_exportable_collections)
|
||||||
|
######################################################
|
||||||
|
## ui logic & co
|
||||||
|
|
||||||
|
class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
|
||||||
|
"""test"""
|
||||||
|
bl_idname = "export_scenes.auto_gltf"
|
||||||
|
bl_label = "Apply settings"
|
||||||
|
bl_options = {'PRESET', 'UNDO'}
|
||||||
|
|
||||||
|
# ExportHelper mixin class uses this
|
||||||
|
filename_ext = ''
|
||||||
|
|
||||||
|
filter_glob: StringProperty(
|
||||||
|
default='*.glb;*.gltf',
|
||||||
|
options={'HIDDEN'}
|
||||||
|
)
|
||||||
|
|
||||||
|
will_save_settings: BoolProperty(
|
||||||
|
name='Remember Export Settings',
|
||||||
|
description='Store glTF export settings in the Blender project',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Custom scene property for saving settings
|
||||||
|
scene_key = "auto_gltfExportSettings"
|
||||||
|
|
||||||
|
def save_settings(self, context):
|
||||||
|
# find all props to save
|
||||||
|
exceptional = [
|
||||||
|
# options that don't start with 'export_'
|
||||||
|
'main_scenes',
|
||||||
|
'library_scenes'
|
||||||
|
]
|
||||||
|
all_props = self.properties
|
||||||
|
export_props = {
|
||||||
|
x: getattr(self, x) for x in dir(all_props)
|
||||||
|
if (x.startswith("export_") or x in exceptional) and all_props.get(x) is not None
|
||||||
|
}
|
||||||
|
# we add main & library scene names to our preferences
|
||||||
|
main_scenes = list(map(lambda scene_data: scene_data, getattr(bpy.context.preferences.addons["gltf_auto_export"].preferences,"main_scenes")))
|
||||||
|
library_scenes = list(map(lambda scene_data: scene_data, getattr(bpy.context.preferences.addons["gltf_auto_export"].preferences,"library_scenes")))
|
||||||
|
|
||||||
|
export_props['main_scene_names'] = list(map(lambda scene_data: scene_data.name, getattr(bpy.context.preferences.addons["gltf_auto_export"].preferences,"main_scenes")))
|
||||||
|
export_props['library_scene_names'] = list(map(lambda scene_data: scene_data.name, getattr(bpy.context.preferences.addons["gltf_auto_export"].preferences,"library_scenes")))
|
||||||
|
self.properties['main_scene_names'] = export_props['main_scene_names']
|
||||||
|
self.properties['library_scene_names'] = export_props['library_scene_names']
|
||||||
|
context.scene[self.scene_key] = export_props
|
||||||
|
|
||||||
|
|
||||||
|
def apply_settings_to_preferences(self, context):
|
||||||
|
# find all props to save
|
||||||
|
exceptional = [
|
||||||
|
# options that don't start with 'export_'
|
||||||
|
'main_scenes',
|
||||||
|
'library_scenes'
|
||||||
|
]
|
||||||
|
all_props = self.properties
|
||||||
|
export_props = {
|
||||||
|
x: getattr(self, x) for x in dir(all_props)
|
||||||
|
if (x.startswith("export_") or x in exceptional) and all_props.get(x) is not None
|
||||||
|
}
|
||||||
|
addon_prefs = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
|
||||||
|
for (k, v) in export_props.items():
|
||||||
|
setattr(addon_prefs, k, v)
|
||||||
|
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
if self.will_save_settings:
|
||||||
|
self.save_settings(context)
|
||||||
|
# apply the operator properties to the addon preferences
|
||||||
|
self.apply_settings_to_preferences(context)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
settings = context.scene.get(self.scene_key)
|
||||||
|
print("settings", settings)
|
||||||
|
self.will_save_settings = False
|
||||||
|
if settings:
|
||||||
|
print("loading settings in invoke AutoExportGLTF")
|
||||||
|
try:
|
||||||
|
for (k, v) in settings.items():
|
||||||
|
print("loading setting", k, v)
|
||||||
|
setattr(self, k, v)
|
||||||
|
self.will_save_settings = True
|
||||||
|
|
||||||
|
# Update filter if user saved settings
|
||||||
|
if hasattr(self, 'export_format'):
|
||||||
|
self.filter_glob = '*.glb' if self.export_format == 'GLB' else '*.gltf'
|
||||||
|
|
||||||
|
# inject scenes data
|
||||||
|
if hasattr(self, 'main_scene_names'):
|
||||||
|
main_scenes = bpy.context.preferences.addons["gltf_auto_export"].preferences.main_scenes
|
||||||
|
main_scenes.clear()
|
||||||
|
for item_name in self.main_scene_names:
|
||||||
|
item = main_scenes.add()
|
||||||
|
item.name = item_name
|
||||||
|
|
||||||
|
if hasattr(self, 'library_scene_names'):
|
||||||
|
library_scenes = bpy.context.preferences.addons["gltf_auto_export"].preferences.library_scenes
|
||||||
|
library_scenes.clear()
|
||||||
|
for item_name in self.library_scene_names:
|
||||||
|
item = library_scenes.add()
|
||||||
|
item.name = item_name
|
||||||
|
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
|
||||||
|
del context.scene[self.scene_key]
|
||||||
|
|
||||||
|
|
||||||
|
for (k, v) in self.properties.items():
|
||||||
|
print("PROPERTIES", k, v)
|
||||||
|
|
||||||
|
addon_prefs = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[main_scene_names, level_scenes, library_scene_names, library_scenes]=get_scenes(addon_prefs)
|
||||||
|
collections = get_exportable_collections(level_scenes, library_scenes)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# we save this list of collections in the context
|
||||||
|
bpy.context.window_manager.exportedCollections.clear()
|
||||||
|
#TODO: add error handling for this
|
||||||
|
for collection_name in collections:
|
||||||
|
ui_info = bpy.context.window_manager.exportedCollections.add()
|
||||||
|
ui_info.name = collection_name
|
||||||
|
except Exception as error:
|
||||||
|
self.report({"ERROR"}, "Failed to populate list of exported collections/blueprints")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
wm = context.window_manager
|
||||||
|
wm.fileselect_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
# return self.execute(context)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class GLTF_PT_auto_export_main(bpy.types.Panel):
|
||||||
|
bl_space_type = 'FILE_BROWSER'
|
||||||
|
bl_region_type = 'TOOL_PROPS'
|
||||||
|
bl_label = ""
|
||||||
|
bl_parent_id = "FILE_PT_operator"
|
||||||
|
bl_options = {'HIDE_HEADER'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
|
sfile = context.space_data
|
||||||
|
|
||||||
|
|
||||||
|
class GLTF_PT_auto_export_root(bpy.types.Panel):
|
||||||
|
bl_space_type = 'FILE_BROWSER'
|
||||||
|
bl_region_type = 'TOOL_PROPS'
|
||||||
|
bl_label = "Auto export"
|
||||||
|
bl_parent_id = "GLTF_PT_auto_export_main"
|
||||||
|
#bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf"
|
||||||
|
|
||||||
|
def draw_header(self, context):
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
self.layout.prop(operator, "auto_export", text="")
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
layout.active = operator.auto_export
|
||||||
|
layout.prop(operator, 'will_save_settings')
|
||||||
|
layout.prop(operator, "export_output_folder")
|
||||||
|
|
||||||
|
# scene selectors
|
||||||
|
row = layout.row()
|
||||||
|
col = row.column(align=True)
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
source = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
|
||||||
|
rows = 2
|
||||||
|
|
||||||
|
# main/level scenes
|
||||||
|
layout.label(text="main scenes")
|
||||||
|
layout.prop(context.scene, "main_scene", text='')
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.template_list("SCENE_UL_GLTF_auto_export", "level scenes", source, "main_scenes", source, "main_scenes_index", rows=rows)
|
||||||
|
|
||||||
|
col = row.column(align=True)
|
||||||
|
sub_row = col.row()
|
||||||
|
add_operator = sub_row.operator("scene_list.list_action", icon='ADD', text="")
|
||||||
|
add_operator.action = 'ADD'
|
||||||
|
add_operator.scene_type = 'level'
|
||||||
|
#add_operator.source = operator
|
||||||
|
sub_row.enabled = context.scene.main_scene is not None
|
||||||
|
|
||||||
|
sub_row = col.row()
|
||||||
|
remove_operator = sub_row.operator("scene_list.list_action", icon='REMOVE', text="")
|
||||||
|
remove_operator.action = 'REMOVE'
|
||||||
|
remove_operator.scene_type = 'level'
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
#up_operator = col.operator("scene_list.list_action", icon='TRIA_UP', text="")
|
||||||
|
#up_operator.action = 'UP'
|
||||||
|
#col.operator("scene_list.list_action", icon='TRIA_DOWN', text="").action = 'DOWN'
|
||||||
|
|
||||||
|
# library scenes
|
||||||
|
layout.label(text="library scenes")
|
||||||
|
layout.prop(context.scene, "library_scene", text='')
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.template_list("SCENE_UL_GLTF_auto_export", "library scenes", source, "library_scenes", source, "library_scenes_index", rows=rows)
|
||||||
|
|
||||||
|
col = row.column(align=True)
|
||||||
|
sub_row = col.row()
|
||||||
|
add_operator = sub_row.operator("scene_list.list_action", icon='ADD', text="")
|
||||||
|
add_operator.action = 'ADD'
|
||||||
|
add_operator.scene_type = 'library'
|
||||||
|
sub_row.enabled = context.scene.library_scene is not None
|
||||||
|
|
||||||
|
|
||||||
|
sub_row = col.row()
|
||||||
|
remove_operator = sub_row.operator("scene_list.list_action", icon='REMOVE', text="")
|
||||||
|
remove_operator.action = 'REMOVE'
|
||||||
|
remove_operator.scene_type = 'library'
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class GLTF_PT_auto_export_blueprints(bpy.types.Panel):
|
||||||
|
bl_space_type = 'FILE_BROWSER'
|
||||||
|
bl_region_type = 'TOOL_PROPS'
|
||||||
|
bl_label = "Blueprints"
|
||||||
|
bl_parent_id = "GLTF_PT_auto_export_root"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf" #"EXPORT_SCENE_OT_gltf"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
layout.prop(operator, "export_blueprints")
|
||||||
|
layout.prop(operator, "export_blueprints_path")
|
||||||
|
|
||||||
|
class GLTF_PT_auto_export_collections_list(bpy.types.Panel):
|
||||||
|
bl_space_type = 'FILE_BROWSER'
|
||||||
|
bl_region_type = 'TOOL_PROPS'
|
||||||
|
bl_label = "Blueprints: Exported Collections"
|
||||||
|
bl_parent_id = "GLTF_PT_auto_export_blueprints"
|
||||||
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf" #"EXPORT_SCENE_OT_gltf"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
addon_prefs = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
|
||||||
|
for collection in bpy.context.window_manager.exportedCollections:
|
||||||
|
row = layout.row()
|
||||||
|
row.label(text=collection.name)
|
||||||
|
|
||||||
|
class GLTF_PT_auto_export_gltf(bpy.types.Panel):
|
||||||
|
bl_space_type = 'FILE_BROWSER'
|
||||||
|
bl_region_type = 'TOOL_PROPS'
|
||||||
|
bl_label = "Gltf"
|
||||||
|
bl_parent_id = "GLTF_PT_auto_export_main"
|
||||||
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf" #"EXPORT_SCENE_OT_gltf"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
preferences = context.preferences
|
||||||
|
addon_prefs = preferences.addons["gltf_auto_export"].preferences
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
#preferences = context.preferences
|
||||||
|
#print("ADDON PREFERENCES ", list(preferences.addons.keys()))
|
||||||
|
#print("standard blender gltf prefs", list(preferences.addons["io_scene_gltf2"].preferences.keys()))
|
||||||
|
# we get the addon preferences from the standard gltf exporter & use those :
|
||||||
|
addon_prefs_gltf = preferences.addons["io_scene_gltf2"].preferences
|
||||||
|
|
||||||
|
#addon_prefs = preferences.addons["gltf_auto_export"].preferences
|
||||||
|
|
||||||
|
# print("KEYS", operator.properties.keys())
|
||||||
|
#print("BLAS", addon_prefs.__annotations__)
|
||||||
|
#print(addon_prefs.__dict__)
|
||||||
|
for key in addon_prefs.__annotations__.keys():
|
||||||
|
if key not in AutoExportGltfPreferenceNames:
|
||||||
|
layout.prop(operator, key)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SCENE_UL_GLTF_auto_export(bpy.types.UIList):
|
||||||
|
# The draw_item function is called for each item of the collection that is visible in the list.
|
||||||
|
# data is the RNA object containing the collection,
|
||||||
|
# item is the current drawn item of the collection,
|
||||||
|
# icon is the "computed" icon for the item (as an integer, because some objects like materials or textures
|
||||||
|
# have custom icons ID, which are not available as enum items).
|
||||||
|
# active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the
|
||||||
|
# active item of the collection).
|
||||||
|
# active_propname is the name of the active property (use 'getattr(active_data, active_propname)').
|
||||||
|
# index is index of the current item in the collection.
|
||||||
|
# flt_flag is the result of the filtering process for this item.
|
||||||
|
# Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't
|
||||||
|
# need them.
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||||
|
ob = data
|
||||||
|
# draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
|
||||||
|
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||||
|
# You should always start your row layout by a label (icon + text), or a non-embossed text field,
|
||||||
|
# this will also make the row easily selectable in the list! The later also enables ctrl-click rename.
|
||||||
|
# We use icon_value of label, as our given icon is an integer value, not an enum ID.
|
||||||
|
# Note "data" names should never be translated!
|
||||||
|
#if ma:
|
||||||
|
# layout.prop(ma, "name", text="", emboss=False, icon_value=icon)
|
||||||
|
#else:
|
||||||
|
# layout.label(text="", translate=False, icon_value=icon)
|
||||||
|
layout.label(text=item.name, icon_value=icon)
|
||||||
|
#layout.prop(item, "name", text="", emboss=False, icon_value=icon)
|
||||||
|
# 'GRID' layout type should be as compact as possible (typically a single icon!).
|
||||||
|
elif self.layout_type == 'GRID':
|
||||||
|
layout.alignment = 'CENTER'
|
||||||
|
layout.label(text="", icon_value=icon)
|
||||||
|
|
||||||
|
|
85
tools/gltf_auto_export/ui/various.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
|
||||||
|
class SCENES_LIST_OT_actions(Operator):
|
||||||
|
"""Move items up and down, add and remove"""
|
||||||
|
bl_idname = "scene_list.list_action"
|
||||||
|
bl_label = "List Actions"
|
||||||
|
bl_description = "Move items up and down, add and remove"
|
||||||
|
bl_options = {'REGISTER'}
|
||||||
|
|
||||||
|
action: bpy.props.EnumProperty(
|
||||||
|
items=(
|
||||||
|
('UP', "Up", ""),
|
||||||
|
('DOWN', "Down", ""),
|
||||||
|
('REMOVE', "Remove", ""),
|
||||||
|
('ADD', "Add", "")))
|
||||||
|
|
||||||
|
|
||||||
|
scene_type: bpy.props.StringProperty()#TODO: replace with enum
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
print("INVOKE", self.scene_type, "gltf_auto_export")
|
||||||
|
source = bpy.context.preferences.addons["gltf_auto_export"].preferences
|
||||||
|
target_name = "library_scenes"
|
||||||
|
target_index = "library_scenes_index"
|
||||||
|
if self.scene_type == "level":
|
||||||
|
target_name = "main_scenes"
|
||||||
|
target_index = "main_scenes_index"
|
||||||
|
|
||||||
|
target = getattr(source, target_name)
|
||||||
|
idx = getattr(source, target_index)
|
||||||
|
|
||||||
|
try:
|
||||||
|
item = target[idx]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.action == 'DOWN' and idx < len(target) - 1:
|
||||||
|
item_next = target[idx + 1].name
|
||||||
|
target.move(idx, idx + 1)
|
||||||
|
source[target_index] += 1
|
||||||
|
info = 'Item "%s" moved to position %d' % (item.name, source[target_index] + 1)
|
||||||
|
self.report({'INFO'}, info)
|
||||||
|
|
||||||
|
elif self.action == 'UP' and idx >= 1:
|
||||||
|
item_prev = target[idx - 1].name
|
||||||
|
target.move(idx, idx - 1)
|
||||||
|
source[target_index] -= 1
|
||||||
|
info = 'Item "%s" moved to position %d' % (item.name, source[target_index] + 1)
|
||||||
|
self.report({'INFO'}, info)
|
||||||
|
|
||||||
|
elif self.action == 'REMOVE':
|
||||||
|
info = 'Item "%s" removed from list' % (target[idx].name)
|
||||||
|
source[target_index] -= 1
|
||||||
|
target.remove(idx)
|
||||||
|
self.report({'INFO'}, info)
|
||||||
|
|
||||||
|
if self.action == 'ADD':
|
||||||
|
new_scene_name = None
|
||||||
|
if self.scene_type == "level":
|
||||||
|
if context.scene.main_scene:
|
||||||
|
new_scene_name = context.scene.main_scene.name
|
||||||
|
else:
|
||||||
|
if context.scene.library_scene:
|
||||||
|
new_scene_name = context.scene.library_scene.name
|
||||||
|
if new_scene_name:
|
||||||
|
item = target.add()
|
||||||
|
item.name = new_scene_name#f"Rule {idx +1}"
|
||||||
|
|
||||||
|
if self.scene_type == "level":
|
||||||
|
context.scene.main_scene = None
|
||||||
|
else:
|
||||||
|
context.scene.library_scene = None
|
||||||
|
|
||||||
|
#name = f"Rule {idx +1}"
|
||||||
|
#target.append({"name": name})
|
||||||
|
|
||||||
|
source[target_index] = len(target) - 1
|
||||||
|
info = '"%s" added to list' % (item.name)
|
||||||
|
self.report({'INFO'}, info)
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|