Repository navigation
Added preference setting to solve persistence as optional toggle. #244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,9 @@ | |
| # <pep8 compliant> | ||
|
|
||
|
|
||
| import json | ||
| import os | ||
|
|
||
| import bpy | ||
| from bpy.props import ( | ||
| StringProperty, | ||
|
|
@@ -32,6 +35,22 @@ | |
| from .utils.bl_class_registry import BlClassRegistry | ||
| from . import common | ||
| from . import c_structure as cstruct # extensions.blender.org: Delete line | ||
| from . import ops | ||
|
|
||
|
|
||
| PERSISTED_PREFERENCE_EXCLUDES = { | ||
| "bl_idname", | ||
| "updater_branch_to_update", | ||
| } | ||
|
|
||
| PERSISTED_PREFERENCE_TYPES = { | ||
| 'BOOLEAN', | ||
| 'COLLECTION', | ||
| 'ENUM', | ||
| 'FLOAT', | ||
| 'INT', | ||
| 'STRING', | ||
| } | ||
|
|
||
|
|
||
| # extensions.blender.org: Delete block start | ||
|
|
@@ -132,6 +151,151 @@ class DisplayEventTextAliasProperties(bpy.types.PropertyGroup): | |
| event_id: bpy.props.StringProperty(options={'HIDDEN'}) | ||
|
|
||
|
|
||
| def persisted_preferences_filepath(create=False): | ||
| config_dir = bpy.utils.user_resource( | ||
| 'CONFIG', path="screencast_keys", create=create) | ||
| if not config_dir: | ||
| return "" | ||
|
|
||
| return os.path.join(config_dir, "preferences.json") | ||
|
|
||
|
|
||
| def _rna_properties(data): | ||
| for prop in data.bl_rna.properties: | ||
| if prop.identifier == "rna_type": | ||
| continue | ||
| if prop.identifier in PERSISTED_PREFERENCE_EXCLUDES: | ||
| continue | ||
| if prop.type not in PERSISTED_PREFERENCE_TYPES: | ||
| continue | ||
| if prop.is_readonly and prop.type != 'COLLECTION': | ||
| continue | ||
| yield prop | ||
|
|
||
|
|
||
| def _serialize_property(data, prop): | ||
| value = getattr(data, prop.identifier) | ||
| if prop.type == 'COLLECTION': | ||
| return [_serialize_preferences(item) for item in value] | ||
| if getattr(prop, "array_length", 0) > 0: | ||
| return list(value) | ||
|
|
||
| return value | ||
|
|
||
|
|
||
| def _serialize_preferences(data): | ||
| preferences = {} | ||
| for prop in _rna_properties(data): | ||
| try: | ||
| preferences[prop.identifier] = _serialize_property(data, prop) | ||
| except TypeError: | ||
| continue | ||
|
|
||
| return preferences | ||
|
|
||
|
|
||
| def _apply_preferences(data, preferences): | ||
| properties = { | ||
| prop.identifier: prop | ||
| for prop in _rna_properties(data) | ||
| } | ||
| for identifier, value in preferences.items(): | ||
| if identifier not in properties: | ||
| continue | ||
|
|
||
| prop = properties[identifier] | ||
| if prop.type == 'COLLECTION': | ||
| if not isinstance(value, list): | ||
| continue | ||
| collection = getattr(data, identifier) | ||
| collection.clear() | ||
| for item_data in value: | ||
| if not isinstance(item_data, dict): | ||
| continue | ||
| item = collection.add() | ||
| _apply_preferences(item, item_data) | ||
| else: | ||
| try: | ||
| data[identifier] = value | ||
| except (AttributeError, KeyError, TypeError, ValueError): | ||
| try: | ||
| setattr(data, identifier, value) | ||
| except (TypeError, ValueError): | ||
| continue | ||
|
|
||
|
|
||
| def ensure_display_event_text_aliases(prefs): | ||
|
|
||
|
|
||
| event_aliases = { | ||
| item.event_id: item | ||
| for item in prefs.display_event_text_aliases_props | ||
| } | ||
| for event in list(ops.EventType): | ||
| if event in ops.SK_OT_ScreencastKeys.MODIFIER_EVENT_TYPES: | ||
| default_text = common.fix_modifier_display_text( | ||
| ops.EventType.names[event.name] | ||
| ) | ||
| else: | ||
| default_text = ops.EventType.names[event.name] | ||
|
|
||
| if event.name in event_aliases: | ||
| event_aliases[event.name].default_text = default_text | ||
| else: | ||
| item = prefs.display_event_text_aliases_props.add() | ||
| item.event_id = event.name | ||
| item.default_text = default_text | ||
|
|
||
|
|
||
| def load_persisted_preferences(prefs): | ||
| filepath = persisted_preferences_filepath() | ||
| if not filepath or not os.path.exists(filepath): | ||
| return | ||
|
|
||
| try: | ||
| with open(filepath, "r", encoding="utf-8") as file: | ||
| data = json.load(file) | ||
| except (OSError, json.JSONDecodeError): | ||
| return | ||
|
|
||
| if not data.get("persist_preferences", False): | ||
| return | ||
|
|
||
| _apply_preferences(prefs, data.get("preferences", {})) | ||
| prefs.persist_preferences = True | ||
|
|
||
|
|
||
| def save_persisted_preferences(prefs): | ||
| filepath = persisted_preferences_filepath(create=True) | ||
| if not filepath: | ||
| return | ||
|
|
||
| data = { | ||
| "version": "1.0.0", | ||
| "persist_preferences": True, | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this item mean?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is the entry point for the persist Boolean of the new setting. It has to be written explicitly the first time a preferences file is created to keep track if the user want to restore this setting file or delete it. Maybe I can think of aa different name. |
||
| "preferences": _serialize_preferences(prefs), | ||
| } | ||
|
|
||
| try: | ||
| tmp_filepath = "{}.tmp".format(filepath) | ||
| with open(tmp_filepath, "w", encoding="utf-8") as file: | ||
| json.dump(data, file, ensure_ascii=False, indent=2) | ||
| os.replace(tmp_filepath, filepath) | ||
| except (OSError, TypeError): | ||
| return | ||
|
|
||
|
|
||
| def remove_persisted_preferences_file(): | ||
| filepath = persisted_preferences_filepath() | ||
| if not filepath: | ||
| return | ||
|
|
||
| try: | ||
| os.remove(filepath) | ||
| except OSError: | ||
| return | ||
|
|
||
|
|
||
| # pylint: disable=W0613 | ||
| def remove_custom_mouse_image(self, _): | ||
| def remove_image(image_name): | ||
|
|
@@ -525,6 +689,13 @@ def ui_in_overlay_update_fn(self, _): | |
| update=ui_in_overlay_update_fn, | ||
| ) | ||
|
|
||
| persist_preferences: bpy.props.BoolProperty( | ||
| name="Persist Preferences", | ||
| description="Store add-on preferences in Blender user configuration " | ||
| "and restore them when this add-on is enabled again", | ||
| default=False, | ||
| ) | ||
|
|
||
| display_event_text_aliases_props: bpy.props.CollectionProperty( | ||
| type=DisplayEventTextAliasProperties | ||
| ) | ||
|
|
@@ -547,6 +718,7 @@ def draw(self, _): | |
| layout.separator() | ||
|
|
||
| layout.prop(self, "enable_on_startup") | ||
| layout.prop(self, "persist_preferences") | ||
|
|
||
| column = layout.column() | ||
| split = column.split() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is my understanding correct, this is still valid when there is no configuration file,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I tested and there's no traces of that.
The fix is now part of the function def ensure_display_event_text_aliases(prefs) in preferences.py.
This will create a dictionary containing all the known keys taking from ops.EventType.names[event.name] same ones used by default and guarantees a single entry per item.
Then it reads from the user overrides which don't need to be persisted on the save file for this to work, keeping the fix.