feat(auto_export): exploration, changes, debug etc for change detection weirdness

This commit is contained in:
kaosat.dev 2024-04-08 00:08:06 +02:00
parent 11e8786b59
commit 64fd308fd3
5 changed files with 95 additions and 38 deletions

View File

@ -116,7 +116,6 @@ classes = [
GLTF_PT_auto_export_SidePanel, GLTF_PT_auto_export_SidePanel,
AutoExportTracker, AutoExportTracker,
] ]
def glTF2_pre_export_callback(data): def glTF2_pre_export_callback(data):
@ -191,10 +190,6 @@ def register():
bpy.types.TOPBAR_MT_file_export.append(menu_func_import) bpy.types.TOPBAR_MT_file_export.append(menu_func_import)
bpy.types.WindowManager.gltf_settings_backup = StringProperty(default="") bpy.types.WindowManager.gltf_settings_backup = StringProperty(default="")
# FIXME: perhaps move this to tracker
bpy.types.WindowManager.exports_count = IntProperty(default=0)
"""bpy.utils.register_class(AutoExportExtensionProperties) """bpy.utils.register_class(AutoExportExtensionProperties)
bpy.types.Scene.AutoExportExtensionProperties = bpy.props.PointerProperty(type=AutoExportExtensionProperties)""" bpy.types.Scene.AutoExportExtensionProperties = bpy.props.PointerProperty(type=AutoExportExtensionProperties)"""
@ -207,7 +202,6 @@ def unregister():
bpy.app.handlers.save_post.remove(post_save) bpy.app.handlers.save_post.remove(post_save)
"""bpy.utils.unregister_class(AutoExportExtensionProperties)""" """bpy.utils.unregister_class(AutoExportExtensionProperties)"""
del bpy.types.WindowManager.exports_count
if "gltf_auto_export" == "__main__": if "gltf_auto_export" == "__main__":
register() register()

View File

@ -109,20 +109,23 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
main_scenes_to_export = [scene_name for scene_name in main_scene_names if not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)] main_scenes_to_export = [scene_name for scene_name in main_scene_names if not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)]
bpy.context.window_manager.exports_count = len(collections_to_export) bpy.context.window_manager.auto_export_tracker.exports_count = len(collections_to_export)
bpy.context.window_manager.exports_count += len(main_scenes_to_export) bpy.context.window_manager.auto_export_tracker.exports_count += len(main_scenes_to_export)
if export_materials_library: if export_materials_library:
bpy.context.window_manager.exports_count += 1 bpy.context.window_manager.auto_export_tracker.exports_count += 1
print("--------------") print("-------------------------------")
print("collections: all:", collections) print("collections: all:", collections)
print("collections: changed:", changed_collections) print("collections: changed:", changed_collections)
print("collections: not found on disk:", collections_not_on_disk) print("collections: not found on disk:", collections_not_on_disk)
print("collections: in library:", library_collections) print("collections: in library:", library_collections)
print("collections: to export:", collections_to_export) print("collections: to export:", collections_to_export)
print("collections: per_scene:", collections_per_scene) print("collections: per_scene:", collections_per_scene)
print("--------------") print("-------------------------------")
print("MAIN SCENES TO EXPORT", main_scenes_to_export) print("BLUEPRINTS: to export:", collections_to_export)
print("-------------------------------")
print("MAIN SCENES: to export:", main_scenes_to_export)
print("-------------------------------")
# backup current active scene # backup current active scene
@ -131,19 +134,20 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
old_selections = bpy.context.selected_objects old_selections = bpy.context.selected_objects
# first export any main/level/world scenes # first export any main/level/world scenes
print("export MAIN scenes") if len(main_scenes_to_export) > 0:
for scene_name in main_scene_names: print("export MAIN scenes")
# we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc) for scene_name in main_scene_names:
do_export_main_scene = not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension) # we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc)
if do_export_main_scene: do_export_main_scene = not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)
print(" exporting scene:", scene_name) if do_export_main_scene:
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, library_collections) print(" exporting scene:", scene_name)
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, library_collections)
# now deal with blueprints/collections # now deal with blueprints/collections
do_export_library_scene = not export_change_detection or changed_export_parameters or len(collections_to_export) > 0 # export_library_scene_name in changes_per_scene.keys() do_export_library_scene = not export_change_detection or 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: if do_export_library_scene:
print("export LIBRARY")
# we only want to go through the library scenes where our collections to export are present # 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(): for (scene_name, collections_to_export) in collections_per_scene.items():
print(" exporting collections from scene:", scene_name) print(" exporting collections from scene:", scene_name)
@ -164,6 +168,8 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
for scene_name in main_scene_names: for scene_name in main_scene_names:
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, []) export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, [])
print("we are done with all export work",bpy.context.window_manager.auto_export_tracker.change_detection_enabled)
except Exception as error: except Exception as error:
print(traceback.format_exc()) print(traceback.format_exc())

View File

@ -168,13 +168,13 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
auto_settings_changed = sorted(json.loads(previous_auto_settings.as_string()).items()) != sorted(json.loads(current_auto_settings.as_string()).items()) if current_auto_settings != None else False auto_settings_changed = sorted(json.loads(previous_auto_settings.as_string()).items()) != sorted(json.loads(current_auto_settings.as_string()).items()) if current_auto_settings != None else False
gltf_settings_changed = sorted(json.loads(previous_gltf_settings.as_string()).items()) != sorted(json.loads(current_gltf_settings.as_string()).items()) if current_gltf_settings != None else False gltf_settings_changed = sorted(json.loads(previous_gltf_settings.as_string()).items()) != sorted(json.loads(current_gltf_settings.as_string()).items()) if current_gltf_settings != None else False
print("auto settings previous", sorted(json.loads(previous_auto_settings.as_string()).items())) """print("auto settings previous", sorted(json.loads(previous_auto_settings.as_string()).items()))
print("auto settings current", sorted(json.loads(current_auto_settings.as_string()).items())) print("auto settings current", sorted(json.loads(current_auto_settings.as_string()).items()))
print("auto_settings_changed", auto_settings_changed) print("auto_settings_changed", auto_settings_changed)
print("gltf settings previous", sorted(json.loads(previous_gltf_settings.as_string()).items())) print("gltf settings previous", sorted(json.loads(previous_gltf_settings.as_string()).items()))
print("gltf settings current", sorted(json.loads(current_gltf_settings.as_string()).items())) print("gltf settings current", sorted(json.loads(current_gltf_settings.as_string()).items()))
print("gltf_settings_changed", gltf_settings_changed) print("gltf_settings_changed", gltf_settings_changed)"""
changed = auto_settings_changed or gltf_settings_changed changed = auto_settings_changed or gltf_settings_changed
# now write the current settings to the "previous settings" # now write the current settings to the "previous settings"
@ -191,6 +191,7 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
return changed return changed
def execute(self, context): def execute(self, context):
print("execute")
# disable change detection while the operator runs # disable change detection while the operator runs
bpy.context.window_manager.auto_export_tracker.disable_change_detection() bpy.context.window_manager.auto_export_tracker.disable_change_detection()
if self.direct_mode: if self.direct_mode:
@ -206,12 +207,14 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
params_changed = self.did_export_settings_change() params_changed = self.did_export_settings_change()
auto_export(changes_per_scene, params_changed, self) auto_export(changes_per_scene, params_changed, self)
# cleanup # cleanup
if bpy.context.window_manager.exports_count == 0: # we need this in case there was nothing to export, to make sure change detection is enabled again print("AUTO EXPORT DONE")
print("YOLOOO") if bpy.context.window_manager.auto_export_tracker.exports_count == 0: # we need this in case there was nothing to export, to make sure change detection is enabled again
bpy.context.window_manager.auto_export_tracker.enable_change_detection() pass #print("YOLOOO")
#py.context.window_manager.auto_export_tracker.enable_change_detection()
#bpy.app.timers.register(bpy.context.window_manager.auto_export_tracker.enable_change_detection, first_interval=1)
#bpy.context.window_manager.auto_export_tracker.enable_change_detection() #bpy.context.window_manager.auto_export_tracker.enable_change_detection()
# FIXME: wrong logic, this should be called only in an glTF2_post_export_callback # FIXME: wrong logic, this should be called only in an glTF2_post_export_callback
bpy.app.timers.register(bpy.context.window_manager.auto_export_tracker.enable_change_detection, first_interval=1) #bpy.app.timers.register(bpy.context.window_manager.auto_export_tracker.enable_change_detection, first_interval=1)
else: else:
print("auto export disabled, skipping") print("auto export disabled, skipping")
return {'FINISHED'} return {'FINISHED'}

View File

@ -1,7 +1,7 @@
import json import json
import bpy import bpy
from bpy.types import (PropertyGroup) from bpy.types import (PropertyGroup)
from bpy.props import (PointerProperty) from bpy.props import (PointerProperty, IntProperty)
from .internals import CollectionsToExport from .internals import CollectionsToExport
@ -15,6 +15,12 @@ class AutoExportTracker(PropertyGroup):
last_operator = None last_operator = None
dummy_file_path = "" dummy_file_path = ""
exports_count : IntProperty(
name='exports_count',
description='Number of exports in progress',
default=0
) # type: ignore
@classmethod @classmethod
def register(cls): def register(cls):
bpy.types.WindowManager.auto_export_tracker = PointerProperty(type=AutoExportTracker) bpy.types.WindowManager.auto_export_tracker = PointerProperty(type=AutoExportTracker)
@ -53,7 +59,7 @@ class AutoExportTracker(PropertyGroup):
@classmethod @classmethod
def deps_update_handler(cls, scene, depsgraph): def deps_update_handler(cls, scene, depsgraph):
print("change detection enabled", cls.change_detection_enabled) print("change detection enabled", cls.change_detection_enabled, bpy.context.window_manager.auto_export_tracker.change_detection_enabled)
active_operator = bpy.context.active_operator active_operator = bpy.context.active_operator
if active_operator: if active_operator:
# print("Operator", active_operator.bl_label, active_operator.bl_idname) # print("Operator", active_operator.bl_label, active_operator.bl_idname)
@ -74,8 +80,8 @@ class AutoExportTracker(PropertyGroup):
active_operator.will_save_settings = True active_operator.will_save_settings = True
active_operator.auto_export = True active_operator.auto_export = True
if scene.name != "temp_scene": if not scene.name.startswith("__temp_scene"):
# print("depsgraph_update_post", scene.name) print("depsgraph_update_post", scene.name)
changed_scene = scene.name or "" changed_scene = scene.name or ""
# only deal with changes if we are no in the mids of saving/exporting # only deal with changes if we are no in the mids of saving/exporting
@ -90,7 +96,8 @@ class AutoExportTracker(PropertyGroup):
if isinstance(obj.id, bpy.types.Object): if isinstance(obj.id, bpy.types.Object):
# get the actual object # get the actual object
object = bpy.data.objects[obj.id.name] object = bpy.data.objects[obj.id.name]
#print("changed object", obj.id.name) print("changed object", obj.id.name)
print("FOO","transforms", obj.is_updated_transform, "geometry", obj.is_updated_geometry)
cls.changed_objects_per_scene[scene.name][obj.id.name] = object cls.changed_objects_per_scene[scene.name][obj.id.name] = object
elif isinstance(obj.id, bpy.types.Material): # or isinstance(obj.id, bpy.types.ShaderNodeTree): elif isinstance(obj.id, bpy.types.Material): # or isinstance(obj.id, bpy.types.ShaderNodeTree):
# print("changed material", obj.id, "scene", scene.name,) # print("changed material", obj.id, "scene", scene.name,)
@ -114,19 +121,27 @@ class AutoExportTracker(PropertyGroup):
for update in depsgraph.updates: for update in depsgraph.updates:
print("update", update)""" print("update", update)"""
def disable_change_detection(self,): def disable_change_detection(self):
print("disable change detection")
self.change_detection_enabled = False self.change_detection_enabled = False
self.__class__.change_detection_enabled = False self.__class__.change_detection_enabled = False
return None return None
def enable_change_detection(self): def enable_change_detection(self):
print("enable change detection")
self.change_detection_enabled = True self.change_detection_enabled = True
self.__class__.change_detection_enabled = True self.__class__.change_detection_enabled = True
# bpy.context.window_manager.auto_export_tracker.change_detection_enabled = True
print("bpy.context.window_manager.auto_export_tracker.change_detection_enabled", bpy.context.window_manager.auto_export_tracker.change_detection_enabled)
return None return None
def export_finished(self): def export_finished(self):
print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHH export_finished") print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHH export_finished")
bpy.context.window_manager.exports_count -= 1 bpy.context.window_manager.auto_export_tracker.exports_count -= 1
if bpy.context.window_manager.exports_count == 0: if bpy.context.window_manager.auto_export_tracker.exports_count == 0:
print("YOLOOO") #print("preparing to reset change detection")
#bpy.app.timers.register(bpy.context.window_manager.auto_export_tracker.enable_change_detection, first_interval=1)
self.enable_change_detection() self.enable_change_detection()
return None

View File

@ -1,8 +1,10 @@
import bpy import bpy
import os import os
import json import json
import mathutils
import pytest import pytest
import shutil import shutil
import pathlib
@pytest.fixture @pytest.fixture
def setup_data(request): def setup_data(request):
@ -133,9 +135,20 @@ def test_export_changed_parameters(setup_data):
models_library_path = os.path.join(models_path, "library") models_library_path = os.path.join(models_path, "library")
model_library_file_paths = list(map(lambda file_name: os.path.join(models_library_path, file_name), sorted(os.listdir(models_library_path)))) model_library_file_paths = list(map(lambda file_name: os.path.join(models_library_path, file_name), sorted(os.listdir(models_library_path))))
modification_times_first = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths + [world_file_path])) modification_times_first = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths + [world_file_path]))
#print("files", model_library_file_paths)
mapped_files_to_timestamps_and_index = {}
for (index, file_path) in enumerate(model_library_file_paths+ [world_file_path]):
file_path = pathlib.Path(file_path).stem
mapped_files_to_timestamps_and_index[file_path] = (modification_times_first[index], index)
print("files", mapped_files_to_timestamps_and_index)
#print("mod times", modification_times_first) #print("mod times", modification_times_first)
# export again with no changes
print("----------------")
print("no changes")
print("----------------")
bpy.context.window_manager.auto_export_tracker.enable_change_detection() # FIXME: should not be needed, but ..
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
@ -154,7 +167,7 @@ def test_export_changed_parameters(setup_data):
print("main scene change") print("main scene change")
print("----------------") print("----------------")
#py.context.window_manager.auto_export_tracker.enable_change_detection() # FIXME: should not be needed, but .. bpy.context.window_manager.auto_export_tracker.enable_change_detection() # FIXME: should not be needed, but ..
bpy.data.objects["Cube"].location = [1, 0, 0] bpy.data.objects["Cube"].location = [1, 0, 0]
auto_export_operator( auto_export_operator(
@ -169,6 +182,14 @@ def test_export_changed_parameters(setup_data):
modification_times = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths + [world_file_path])) modification_times = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths + [world_file_path]))
assert modification_times != modification_times_first assert modification_times != modification_times_first
# only the "world" file should have changed
world_file_index = mapped_files_to_timestamps_and_index["World"][1]
other_files_modification_times = [value for index, value in enumerate(modification_times) if index not in [world_file_index]]
other_files_modification_times_first = [value for index, value in enumerate(modification_times_first) if index not in [world_file_index]]
assert modification_times[world_file_index] != modification_times_first[world_file_index]
assert other_files_modification_times == other_files_modification_times_first
# reset the comparing
modification_times_first = modification_times modification_times_first = modification_times
@ -176,6 +197,7 @@ def test_export_changed_parameters(setup_data):
print("----------------") print("----------------")
print("library change") print("library change")
print("----------------") print("----------------")
bpy.context.window_manager.auto_export_tracker.enable_change_detection() # FIXME: should not be needed, but ..
bpy.data.objects["Blueprint1_mesh"].location = [1, 2, 1] bpy.data.objects["Blueprint1_mesh"].location = [1, 2, 1]
auto_export_operator( auto_export_operator(
@ -190,14 +212,31 @@ def test_export_changed_parameters(setup_data):
modification_times = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths + [world_file_path])) modification_times = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths + [world_file_path]))
assert modification_times != modification_times_first assert modification_times != modification_times_first
# only the "world" file should have changed
blueprint1_file_index = mapped_files_to_timestamps_and_index["Blueprint1"][1]
other_files_modification_times = [value for index, value in enumerate(modification_times) if index not in [blueprint1_file_index]]
other_files_modification_times_first = [value for index, value in enumerate(modification_times_first) if index not in [blueprint1_file_index]]
assert modification_times[blueprint1_file_index] != modification_times_first[blueprint1_file_index]
assert other_files_modification_times == other_files_modification_times_first
# reset the comparing
modification_times_first = modification_times modification_times_first = modification_times
# now same, but using an operator # now same, but using an operator
print("----------------") print("----------------")
print("change using operator") print("change using operator")
print("----------------") print("----------------")
bpy.context.window_manager.auto_export_tracker.enable_change_detection() # FIXME: should not be needed, but ..
with bpy.context.temp_override(active_object=bpy.data.objects["Cube"]):
print("translate using operator")
bpy.ops.transform.translate(value=mathutils.Vector((2.0, 1.0, -5.0)))
bpy.ops.transform.rotate(value=0.378874, constraint_axis=(False, False, True), mirror=False, proportional_edit_falloff='SMOOTH', proportional_size=1)
bpy.ops.object.transform_apply()
bpy.ops.transform.translate(value=(0.5, 0, 0), constraint_axis=(True, False, False))
bpy.ops.transform.translate(value=(20.0, 0.0, 0.0))
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,