feat(auto_export): restructured & improved changed parameters detection

* now all centralized in the operator's did_export_settings_change() function
 * now works correctly ! ie only compares the parameters for auto & gltf
at the time of exporting ! ie it does not matter anymore how many parameters you changed, until you save/export
 * this solves a lot of randomness bugs in change detection
 * related & various cleanups
This commit is contained in:
kaosat.dev 2024-04-02 16:42:24 +02:00
parent 73e81c2b64
commit 73441f34a4
4 changed files with 194 additions and 121 deletions

View File

@ -10,6 +10,9 @@ bl_info = {
"tracker_url": "https://github.com/kaosat-dev/Blender_bevy_components_workflow/issues/new", "tracker_url": "https://github.com/kaosat-dev/Blender_bevy_components_workflow/issues/new",
"category": "Import-Export" "category": "Import-Export"
} }
import os
from pathlib import Path
import json
import bpy import bpy
from bpy.types import Context from bpy.types import Context
from bpy.props import (StringProperty, BoolProperty, PointerProperty) from bpy.props import (StringProperty, BoolProperty, PointerProperty)
@ -174,9 +177,7 @@ def register():
# add our addon to the toolbar # add our addon to the toolbar
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_exporter_running = BoolProperty(default=False)
bpy.types.WindowManager.gltf_settings_changed = BoolProperty(default=False)
def unregister(): def unregister():
for cls in classes: for cls in classes:
@ -185,8 +186,45 @@ def unregister():
bpy.app.handlers.depsgraph_update_post.remove(post_update) bpy.app.handlers.depsgraph_update_post.remove(post_update)
bpy.app.handlers.save_post.remove(post_save) bpy.app.handlers.save_post.remove(post_save)
del bpy.types.WindowManager.gltf_exporter_running
global gltf_settings_backup
def glTF2_pre_export_callback(data):
pass #print("pre_export", data)
# we backup any existing gltf export settings, if there where any
scene = bpy.context.scene
if "glTF2ExportSettings" in scene:
existing_setting = scene["glTF2ExportSettings"]
gltf_settings_backup = existing_setting
def glTF2_post_export_callback(data):
gltf_settings_backup = ""
#print("post_export", data)
gltf_filepath = data["gltf_filepath"]
filename = Path(gltf_filepath).stem
if filename == "dummy": # TODO: potentially use a hidden gltf exporter extension with an additional parameter instead of this hack
if os.path.exists(gltf_filepath):
print("removing dummy file")
os.unlink(gltf_filepath)
# get the parameters
scene = bpy.context.scene
if "glTF2ExportSettings" in scene:
settings = scene["glTF2ExportSettings"]
export_settings = bpy.data.texts[".gltf_auto_export_gltf_settings"] if ".gltf_auto_export_gltf_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_gltf_settings")
# now write new settings
export_settings.clear()
export_settings.write(json.dumps(dict(settings)))
# now reset the original gltf_settings
if gltf_settings_backup != "":
print("resetting original gltf settings")
#scene["glTF2ExportSettings"] = gltf_settings_backup
else:
print("no pre_existing settings")
if "glTF2ExportSettings" in scene:
del scene["glTF2ExportSettings"]
gltf_settings_backup = ""
if "gltf_auto_export" == "__main__": if "gltf_auto_export" == "__main__":
register() register()

View File

@ -68,7 +68,7 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
except: except:
return True return True
def save_settings(self, context): def format_settings(self):
# find all props to save # find all props to save
exceptional = [ exceptional = [
# options that don't start with 'export_' # options that don't start with 'export_'
@ -93,6 +93,10 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
export_props['main_scene_names'] = list(map(lambda scene_data: scene_data.name, getattr(self,"main_scenes"))) export_props['main_scene_names'] = list(map(lambda scene_data: scene_data.name, getattr(self,"main_scenes")))
export_props['library_scene_names'] = list(map(lambda scene_data: scene_data.name, getattr(self,"library_scenes"))) export_props['library_scene_names'] = list(map(lambda scene_data: scene_data.name, getattr(self,"library_scenes")))
return export_props
def save_settings(self, context):
export_props = self.format_settings()
self.properties['main_scene_names'] = export_props['main_scene_names'] self.properties['main_scene_names'] = export_props['main_scene_names']
self.properties['library_scene_names'] = export_props['library_scene_names'] self.properties['library_scene_names'] = export_props['library_scene_names']
@ -142,28 +146,65 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings") self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
bpy.data.texts.remove(bpy.data.texts[".gltf_auto_export_settings"]) bpy.data.texts.remove(bpy.data.texts[".gltf_auto_export_settings"])
"""
This should ONLY be run when actually doing exports/aka calling auto_export function, because we only care about the difference in settings between EXPORTS
"""
def did_export_settings_change(self): def did_export_settings_change(self):
print("comparing settings")
# compare both the auto export settings & the gltf settings # compare both the auto export settings & the gltf settings
previous_export_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else None previous_auto_settings = bpy.data.texts[".gltf_auto_export_settings_previous"] if ".gltf_auto_export_settings_previous" in bpy.data.texts else None
changed_gltf_settings = bpy.context.window_manager.gltf_settings_changed previous_gltf_settings = bpy.data.texts[".gltf_auto_export_gltf_settings_previous"] if ".gltf_auto_export_gltf_settings_previous" in bpy.data.texts else None
# if there was no setting before, it is new, we need export
print("changed settings", changed_gltf_settings, previous_export_settings.as_string())
if previous_export_settings == None:
return changed_gltf_settings
else:
export_settings = {}
for (k, v) in self.properties.items():
if k in self.white_list or k not in AutoExportGltfPreferenceNames:
export_settings[k] = v
current_auto_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else None
current_gltf_settings = bpy.data.texts[".gltf_auto_export_gltf_settings"] if ".gltf_auto_export_gltf_settings" in bpy.data.texts else None
#check if params have changed
# if there were no setting before, it is new, we need export
changed = False
if previous_auto_settings == None or previous_gltf_settings == None:
print("previous settings missing, exporting")
changed = True
else:
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
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_changed", auto_settings_changed)
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_changed", gltf_settings_changed)
changed = auto_settings_changed or gltf_settings_changed
# now write the current settings to the "previous settings"
previous_auto_settings = bpy.data.texts[".gltf_auto_export_settings_previous"] if ".gltf_auto_export_settings_previous" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings_previous")
previous_auto_settings.clear()
previous_auto_settings.write(current_auto_settings.as_string()) # TODO : check if this is always valid
previous_gltf_settings = bpy.data.texts[".gltf_auto_export_gltf_settings_previous"] if ".gltf_auto_export_gltf_settings_previous" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_gltf_settings_previous")
previous_gltf_settings.clear()
previous_gltf_settings.write(current_gltf_settings.as_string())
print("changed", changed)
return changed
"""# if there was no setting before, it is new, we need export
print("changed settings IN OPERATOR", changed_gltf_settings, previous_export_settings)
if previous_export_settings == None:
return True # we can disregard the gltf settings, we need to save either way
else:
export_settings = self.format_settings()
if len(export_settings.keys()) == 0: # first time after we already used the addon, since we already have export settings, but they have not yet been applied if len(export_settings.keys()) == 0: # first time after we already used the addon, since we already have export settings, but they have not yet been applied
return changed_gltf_settings return changed_gltf_settings
# print("foo", json.loads(previous_export_settings.as_string()).items()) print("previous", sorted(json.loads(previous_export_settings.as_string()).items()))
print("current", sorted(export_settings.items()))
changed = sorted(json.loads(previous_export_settings.as_string()).items()) != sorted(export_settings.items()) changed = sorted(json.loads(previous_export_settings.as_string()).items()) != sorted(export_settings.items())
print("changed final", changed and changed_gltf_settings) print("changed FINAL: auto_settings", changed, "gltf_settings", changed_gltf_settings, "combo", changed or changed_gltf_settings)
return changed and changed_gltf_settings return changed and changed_gltf_settings"""
def execute(self, context): def execute(self, context):
# disable change detection while the operator runs # disable change detection while the operator runs
@ -171,13 +212,15 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
if self.direct_mode: if self.direct_mode:
self.load_settings(context) self.load_settings(context)
if self.will_save_settings: if self.will_save_settings:
print("SAVING SETTINGS")
self.save_settings(context) self.save_settings(context)
changes_per_scene = context.window_manager.auto_export_tracker.changed_objects_per_scene changes_per_scene = context.window_manager.auto_export_tracker.changed_objects_per_scene
#determine changed parameters
params_changed = self.did_export_settings_change()
#& do the export #& do the export
if self.direct_mode: #Do not auto export when applying settings in the menu, do it on save only if self.direct_mode: #Do not auto export when applying settings in the menu, do it on save only
#determine changed parameters
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
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)

View File

@ -48,113 +48,18 @@ class AutoExportTracker(PropertyGroup):
# all our logic is done, mark this as done # all our logic is done, mark this as done
print("EXPORT DONE") print("EXPORT DONE")
@classmethod
def gltf_exporter_handler(cls):
# FOr some reason, the active operator here is always None, so using a workaround
# active_operator = bpy.context.active_operator
print("here", bpy.context.window_manager.gltf_exporter_running)
if bpy.context.window_manager.gltf_exporter_running:
try:
dummy_file_path = "/home/ckaos/projects/bevy/Blender_bevy_components_worklflow/testing/bevy_example/assets/dummy.glb"
import os
if os.path.exists(dummy_file_path):
print("dummy file exists, assuming it worked")
os.unlink(dummy_file_path)
# get the parameters
scene = bpy.context.scene
if "glTF2ExportSettings" in scene:
settings = scene["glTF2ExportSettings"]
formatted_settings = dict(settings)
export_settings = bpy.data.texts[".gltf_auto_export_gltf_settings"] if ".gltf_auto_export_gltf_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_gltf_settings")
#check if params have changed
bpy.context.window_manager.gltf_settings_changed = sorted(json.loads(export_settings.as_string()).items()) != sorted(formatted_settings.items())
print("gltf NEW settings", formatted_settings, "OLD settings", export_settings, "CHANGED ?", bpy.context.window_manager.gltf_settings_changed)
# now write new settings
export_settings.clear()
export_settings.write(json.dumps(formatted_settings))
# now reset the original gltf_settings
if getattr(cls, "existing_gltf_settings", None) != None:
print("resetting original gltf settings")
scene["glTF2ExportSettings"] = cls.existing_gltf_settings
else:
print("no pre_existing settings")
if "glTF2ExportSettings" in scene:
del scene["glTF2ExportSettings"]
cls.existing_gltf_settings = None
except:pass
bpy.context.window_manager.gltf_exporter_running = False
return None
else:
try:
bpy.app.timers.unregister(cls.gltf_exporter_handler)
except:pass
return None
return 1
@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)
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, "bla", bpy.context.window_manager.gltf_exporter_running) # print("Operator", active_operator.bl_label, active_operator.bl_idname)
if active_operator.bl_idname == "EXPORT_SCENE_OT_gltf" and not bpy.context.window_manager.gltf_exporter_running: if active_operator.bl_idname == "EXPORT_SCENE_OT_gltf" :
print("matching")
try:
bpy.app.timers.unregister(cls.gltf_exporter_handler)
except:pass
bpy.app.timers.register(cls.gltf_exporter_handler, first_interval=3)
# we force saving params # we force saving params
active_operator.will_save_settings = True active_operator.will_save_settings = True
if active_operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf":
# we backup any existing gltf export settings, if there where any # we force saving params
scene = bpy.context.scene
if "glTF2ExportSettings" in scene:
existing_setting = scene["glTF2ExportSettings"]
cls.existing_gltf_settings = existing_setting
bpy.context.window_manager.gltf_exporter_running = True
else:
if bpy.context.window_manager.gltf_exporter_running:
bpy.context.window_manager.gltf_exporter_running = False
"""if active_operator.bl_idname == "EXPORT_SCENE_OT_gltf":
scene = bpy.context.scene
if "glTF2ExportSettings" in scene:
existing_setting = scene["glTF2ExportSettings"]
cls.existing_gltf_settings = existing_setting
print("we just executed the correct operator")
active_operator.will_save_settings = True active_operator.will_save_settings = True
else:
import os
dummy_file_path = "/home/ckaos/projects/bevy/Blender_bevy_components_worklflow/testing/bevy_example/assets/dummy.glb"
if os.path.exists(dummy_file_path):
print("dummy file exists")
os.unlink(dummy_file_path)
# get the parameters
scene = bpy.context.scene
settings = scene["glTF2ExportSettings"]
print("gltf settings", dict(settings))
# now reset the original gltf_settings
if hasattr(cls, "existing_gltf_settings"):
print("resetting original gltf settings")
scene["glTF2ExportSettings"] = cls.existing_gltf_settings
else:
del scene["glTF2ExportSettings"]"""
if scene.name != "temp_scene": if scene.name != "temp_scene":
# print("depsgraph_update_post", scene.name) # print("depsgraph_update_post", scene.name)

View File

@ -241,3 +241,90 @@ def duplicate_object2(object, original_name):
new_obj.animation_data.action = object.animation_data.action.copy() new_obj.animation_data.action = object.animation_data.action.copy()
return new_obj return new_obj
if active_operator:
# print("Operator", active_operator.bl_label, active_operator.bl_idname, "bla", bpy.context.window_manager.gltf_exporter_running)
if active_operator.bl_idname == "EXPORT_SCENE_OT_gltf" : #and not bpy.context.window_manager.gltf_exporter_running:
# we force saving params
active_operator.will_save_settings = True
if active_operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf":
# we force saving params
active_operator.will_save_settings = True
"""
print("matching")
try:
bpy.app.timers.unregister(cls.gltf_exporter_handler)
except:pass
bpy.app.timers.register(cls.gltf_exporter_handler, first_interval=3)
# we backup any existing gltf export settings, if there where any
scene = bpy.context.scene
if "glTF2ExportSettings" in scene:
existing_setting = scene["glTF2ExportSettings"]
cls.existing_gltf_settings = existing_setting
bpy.context.window_manager.gltf_exporter_running = True
else:
if bpy.context.window_manager.gltf_exporter_running:
bpy.context.window_manager.gltf_exporter_running = False"""
"""@classmethod
def gltf_exporter_handler(cls):
# FOr some reason, the active operator here is always None, so using a workaround
# active_operator = bpy.context.active_operator
print("here", bpy.context.window_manager.gltf_exporter_running)
if bpy.context.window_manager.gltf_exporter_running:
try:
dummy_file_path = "/home/ckaos/projects/bevy/Blender_bevy_components_worklflow/testing/bevy_example/assets/dummy.glb"
import os
if os.path.exists(dummy_file_path):
print("dummy file exists, assuming it worked")
os.unlink(dummy_file_path)
# get the parameters
scene = bpy.context.scene
if "glTF2ExportSettings" in scene:
settings = scene["glTF2ExportSettings"]
formatted_settings = dict(settings)
export_settings = bpy.data.texts[".gltf_auto_export_gltf_settings"] if ".gltf_auto_export_gltf_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_gltf_settings")
#check if params have changed
bpy.context.window_manager.gltf_settings_changed = sorted(json.loads(export_settings.as_string()).items()) != sorted(formatted_settings.items())
print("gltf NEW settings", formatted_settings, "OLD settings", export_settings, "CHANGED ?", bpy.context.window_manager.gltf_settings_changed)
# now write new settings
export_settings.clear()
export_settings.write(json.dumps(formatted_settings))
# now reset the original gltf_settings
if getattr(cls, "existing_gltf_settings", None) != None:
print("resetting original gltf settings")
scene["glTF2ExportSettings"] = cls.existing_gltf_settings
else:
print("no pre_existing settings")
if "glTF2ExportSettings" in scene:
del scene["glTF2ExportSettings"]
cls.existing_gltf_settings = None
except:pass
bpy.context.window_manager.gltf_exporter_running = False
return None
else:
try:
bpy.app.timers.unregister(cls.gltf_exporter_handler)
except:pass
return None
return 1"""