feat(Blenvy): settings coherency pass

* restructured component settings to be used instead of the registry
 * removed settings from registry
 * fixed data access for the above
 * added saving of component parameters
 * added component settings pointer in blenvy core
 * added loading of both auto_export & component settings in blenvy core's load_settings
This commit is contained in:
kaosat.dev 2024-05-26 18:33:06 +02:00
parent ea982d330f
commit b957f0573b
18 changed files with 223 additions and 212 deletions

View File

@ -80,6 +80,7 @@ General things to solve:
- [x] rename all path stuff using the old naming convention : "blueprints_path_full" - [x] rename all path stuff using the old naming convention : "blueprints_path_full"
- [x] generate the full paths directly when setting them in the UI - [x] generate the full paths directly when setting them in the UI
- [x] problem : how to deal with defaults: do it on start/load ? - [x] problem : how to deal with defaults: do it on start/load ?
- [x] filter out scenes that have already been used in scenes list
General issues: General issues:
- there is no safeguard for naming collisions for naming across blender files - there is no safeguard for naming collisions for naming across blender files
@ -87,6 +88,12 @@ General issues:
- "parents" can only be blueprints - "parents" can only be blueprints
- they normally need/have unique export paths (otherwise, user error, perhaps show it ?) - they normally need/have unique export paths (otherwise, user error, perhaps show it ?)
- perhaps a simple hashing of the parent's path would be enought - perhaps a simple hashing of the parent's path would be enought
- [x] addon-prefs => settings - [x] addon-prefs => settings
- [x] generate_gltf_export_preferences => should not use add-on prefs at all ? since we are not overriding gltf settings that way anymore ? - [x] generate_gltf_export_preferences => should not use add-on prefs at all ? since we are not overriding gltf settings that way anymore ?
- [ ] remove hard coded path for standard gltf settings - [x] remove hard coded path for standard gltf settings
- [ ] load settings on file load
- [x] auto_export
- [ ] components
- [ ] add handling of errors when trying to load settings

View File

@ -26,6 +26,7 @@ from .add_ons.bevy_components.components.lists import GENERIC_LIST_OT_actions, G
from .add_ons.bevy_components.components.maps import GENERIC_MAP_OT_actions from .add_ons.bevy_components.components.maps import GENERIC_MAP_OT_actions
from .add_ons.bevy_components.components.definitions_list import (ComponentDefinitionsList, ClearComponentDefinitionsList) from .add_ons.bevy_components.components.definitions_list import (ComponentDefinitionsList, ClearComponentDefinitionsList)
from .add_ons.bevy_components.components.ui import (BEVY_COMPONENTS_PT_ComponentsPanel) from .add_ons.bevy_components.components.ui import (BEVY_COMPONENTS_PT_ComponentsPanel)
from .add_ons.bevy_components.settings import ComponentsSettings
# auto export # auto export
from .add_ons.auto_export import gltf_post_export_callback from .add_ons.auto_export import gltf_post_export_callback
@ -67,6 +68,7 @@ classes = [
BLENVY_PT_SidePanel, BLENVY_PT_SidePanel,
# bevy components # bevy components
ComponentsSettings,
AddComponentOperator, AddComponentOperator,
CopyComponentOperator, CopyComponentOperator,
PasteComponentOperator, PasteComponentOperator,
@ -110,6 +112,7 @@ classes = [
GENERIC_MAP_OT_actions, GENERIC_MAP_OT_actions,
# gltf auto export # gltf auto export
AutoExportTracker, AutoExportTracker,
AutoExportSettings, AutoExportSettings,
@ -143,9 +146,6 @@ def post_save(scene, depsgraph):
@persistent @persistent
def post_load(file_name): def post_load(file_name):
print("POST LOAD") print("POST LOAD")
registry = bpy.context.window_manager.components_registry
if registry is not None:
registry.load_settings()
blenvy = bpy.context.window_manager.blenvy blenvy = bpy.context.window_manager.blenvy
if blenvy is not None: if blenvy is not None:
blenvy.load_settings() blenvy.load_settings()

View File

@ -26,21 +26,3 @@ def generate_complete_preferences_dict_gltf(settings):
complete_preferences = dict(filter(filter_out, dict(complete_preferences).items())) complete_preferences = dict(filter(filter_out, dict(complete_preferences).items()))
return complete_preferences return complete_preferences
# given the input (actual) auto settings, filters out any invalid/useless params & params that are equal to defaults
def generate_complete_preferences_dict_auto(settings, presets):
complete_preferences = {}
defaults = {}
for k in presets.__annotations__:
item = presets.__annotations__[k]
default = item.keywords.get('default', None)
#complete_preferences[k] = default
defaults[k] = default
for key in list(settings.keys()):
if key in defaults:
if settings[key] != defaults[key]: # only write out values different from defaults
complete_preferences[key] = settings[key]
else:
complete_preferences[key] = settings[key]
return complete_preferences

View File

@ -1,19 +1,30 @@
import bpy import bpy
from bpy_types import (PropertyGroup) from bpy_types import (PropertyGroup)
from bpy.props import (EnumProperty, PointerProperty, StringProperty, BoolProperty, CollectionProperty, IntProperty) from bpy.props import (EnumProperty, PointerProperty, StringProperty, BoolProperty, CollectionProperty, IntProperty)
from blenvy.settings import upsert_settings, generate_complete_preferences_dict, load_settings
def save_settings(settings, context):
print("save settings", settings, context, dict(settings))
bla = generate_complete_preferences_dict(settings, AutoExportSettings, [])
print("bla", bla)
upsert_settings(settings.settings_save_path, dict(settings))
class AutoExportSettings(PropertyGroup): class AutoExportSettings(PropertyGroup):
settings_save_path = ".blenvy_export_settings" # where to store data in bpy.texts
auto_export: BoolProperty( auto_export: BoolProperty(
name='Auto export', name='Auto export',
description='Automatically export to gltf on save', description='Automatically export to gltf on save',
default=False default=False,
update=save_settings
) # type: ignore ) # type: ignore
#### general #### general
change_detection: BoolProperty( change_detection: BoolProperty(
name='Change detection', name='Change detection',
description='Use change detection to determine what/if should be exported', description='Use change detection to determine what/if should be exported',
default=True default=True,
update=save_settings
) # type: ignore ) # type: ignore
# scenes # scenes
@ -22,14 +33,16 @@ class AutoExportSettings(PropertyGroup):
export_scene_settings: BoolProperty( export_scene_settings: BoolProperty(
name='Export scene settings', name='Export scene settings',
description='Export scene settings ie AmbientLighting, Bloom, AO etc', description='Export scene settings ie AmbientLighting, Bloom, AO etc',
default=False default=False,
update=save_settings
) # type: ignore ) # type: ignore
# blueprint settings # blueprint settings
export_blueprints: BoolProperty( export_blueprints: BoolProperty(
name='Export Blueprints', name='Export Blueprints',
description='Replaces collection instances with an Empty with a BlueprintName custom property, and enabled a lot more features !', description='Replaces collection instances with an Empty with a BlueprintName custom property, and enabled a lot more features !',
default=True default=True,
update=save_settings
) # type: ignore ) # type: ignore
export_separate_dynamic_and_static_objects: BoolProperty( export_separate_dynamic_and_static_objects: BoolProperty(
@ -37,13 +50,15 @@ class AutoExportSettings(PropertyGroup):
description="""For MAIN scenes only (aka levels), toggle this to generate 2 files per level: description="""For MAIN scenes only (aka levels), toggle this to generate 2 files per level:
- one with all dynamic data: collection or instances marked as dynamic/ saveable - one with all dynamic data: collection or instances marked as dynamic/ saveable
- one with all static data: anything else that is NOT marked as dynamic""", - one with all static data: anything else that is NOT marked as dynamic""",
default=False default=False,
update=save_settings
) # type: ignore ) # type: ignore
export_materials_library: BoolProperty( export_materials_library: BoolProperty(
name='Export materials library', name='Export materials library',
description='remove materials from blueprints and use the material library instead', description='remove materials from blueprints and use the material library instead',
default=False default=False,
update=save_settings
) # type: ignore ) # type: ignore
@ -63,13 +78,15 @@ class AutoExportSettings(PropertyGroup):
('EmbedExternal', 'EmbedExternal', 'treat instances of external (not specifified in the current blend file) collections (aka assets etc) as embeded objects and do not replace them with empties'), ('EmbedExternal', 'EmbedExternal', 'treat instances of external (not specifified in the current blend file) collections (aka assets etc) as embeded objects and do not replace them with empties'),
#('Inject', 'Inject', 'inject components from sub collection instances into the curent object') #('Inject', 'Inject', 'inject components from sub collection instances into the curent object')
), ),
default='Split' default='Split',
update=save_settings
) # type: ignore ) # type: ignore
export_marked_assets: BoolProperty( export_marked_assets: BoolProperty(
name='Auto export marked assets', name='Auto export marked assets',
description='Collections that have been marked as assets will be systematically exported, even if not in use in another scene', description='Collections that have been marked as assets will be systematically exported, even if not in use in another scene',
default=True default=True,
update=save_settings
) # type: ignore ) # type: ignore
dry_run: EnumProperty( dry_run: EnumProperty(
@ -80,7 +97,14 @@ class AutoExportSettings(PropertyGroup):
("NO_EXPORT", "No export", "do not actually export gltf files"), ("NO_EXPORT", "No export", "do not actually export gltf files"),
("NO_PREPARE", "No prepare", "do not actually export gltf files AND do not prepare the exports either (ie no creating fake scenes etc)"), ("NO_PREPARE", "No prepare", "do not actually export gltf files AND do not prepare the exports either (ie no creating fake scenes etc)"),
), ),
default="DISABLED" default="DISABLED",
) # type: ignore ) # type: ignore
def load_settings(self):
settings = load_settings(self.settings_save_path)
if settings is not None:
for setting in settings:
print("setting", setting, settings[setting])
setattr(self, setting, settings[setting])

View File

@ -11,7 +11,7 @@ from ..propGroups.prop_groups import generate_propertyGroups_for_components
class ReloadRegistryOperator(Operator): class ReloadRegistryOperator(Operator):
"""Reloads registry (schema file) from disk, generates propertyGroups for components & ensures all objects have metadata """ """Reloads registry (schema file) from disk, generates propertyGroups for components & ensures all objects have metadata """
bl_idname = "object.reload_registry" bl_idname = "blenvy.reload_components_registry"
bl_label = "Reload Registry" bl_label = "Reload Registry"
bl_options = {"UNDO"} bl_options = {"UNDO"}
@ -191,10 +191,8 @@ class OT_OpenSchemaFileBrowser(Operator, ImportHelper):
folder_path = os.path.dirname(file_path) folder_path = os.path.dirname(file_path)
relative_path = os.path.relpath(self.filepath, folder_path) relative_path = os.path.relpath(self.filepath, folder_path)
registry = context.window_manager.components_registry
registry.schemaPath = relative_path
blenvy = context.window_manager.blenvy blenvy = context.window_manager.blenvy
blenvy.components.schema_path = relative_path
upsert_settings(blenvy.settings_save_path, {"components_schemaPath": relative_path}) upsert_settings(blenvy.settings_save_path, {"components_schemaPath": relative_path})
return {'FINISHED'} return {'FINISHED'}

View File

@ -5,10 +5,7 @@ import uuid
from pathlib import Path from pathlib import Path
from bpy_types import (PropertyGroup) from bpy_types import (PropertyGroup)
from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty) from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty)
from ..components.metadata import ComponentMetadata
from blenvy.settings import load_settings
from ..propGroups.prop_groups import generate_propertyGroups_for_components
from ..components.metadata import ComponentMetadata, ensure_metadata_for_all_items
# helper class to store missing bevy types information # helper class to store missing bevy types information
class MissingBevyType(bpy.types.PropertyGroup): class MissingBevyType(bpy.types.PropertyGroup):
@ -16,59 +13,9 @@ class MissingBevyType(bpy.types.PropertyGroup):
name="type", name="type",
) # type: ignore ) # type: ignore
# helper function to deal with timer
def toggle_watcher(self, context):
#print("toggling watcher", self.watcher_enabled, watch_schema, self, bpy.app.timers)
if not self.watcher_enabled:
try:
bpy.app.timers.unregister(watch_schema)
except Exception as error:
pass
else:
self.watcher_active = True
bpy.app.timers.register(watch_schema)
def watch_schema():
self = bpy.context.window_manager.components_registry
# print("watching schema file for changes")
try:
stamp = os.stat(self.schemaFullPath).st_mtime
stamp = str(stamp)
if stamp != self.schemaTimeStamp and self.schemaTimeStamp != "":
print("FILE CHANGED !!", stamp, self.schemaTimeStamp)
# see here for better ways : https://stackoverflow.com/questions/11114492/check-if-a-file-is-not-open-nor-being-used-by-another-process
"""try:
os.rename(path, path)
#return False
except OSError: # file is in use
print("in use")
#return True"""
#bpy.ops.object.reload_registry()
# we need to add an additional delay as the file might not have loaded yet
bpy.app.timers.register(lambda: bpy.ops.object.reload_registry(), first_interval=1)
self.schemaTimeStamp = stamp
except Exception as error:
pass
return self.watcher_poll_frequency if self.watcher_enabled else None
# this is where we store the information for all available components # this is where we store the information for all available components
class ComponentsRegistry(PropertyGroup): class ComponentsRegistry(PropertyGroup):
settings_save_path = ".bevy_components_settings" # where to store data in bpy.texts
schemaPath: bpy.props.StringProperty(
name="schema path",
description="path to the registry schema file",
default="registry.json"
)# type: ignore
schemaFullPath : bpy.props.StringProperty(
name="schema full path",
description="path to the registry schema file",
)# type: ignore
registry: bpy.props. StringProperty( registry: bpy.props. StringProperty(
name="registry", name="registry",
description="component registry" description="component registry"
@ -81,25 +28,6 @@ class ComponentsRegistry(PropertyGroup):
disable_all_object_updates: BoolProperty(name="disable_object_updates", default=False) # type: ignore disable_all_object_updates: BoolProperty(name="disable_object_updates", default=False) # type: ignore
## file watcher
watcher_enabled: BoolProperty(name="Watcher_enabled", default=True, update=toggle_watcher)# type: ignore
watcher_active: BoolProperty(name = "Flag for watcher status", default = False)# type: ignore
watcher_poll_frequency: IntProperty(
name="watcher poll frequency",
description="frequency (s) at wich to poll for changes to the registry file",
min=1,
max=10,
default=1
)# type: ignore
schemaTimeStamp: StringProperty(
name="last timestamp of schema file",
description="",
default=""
)# type: ignore
missing_types_list: CollectionProperty(name="missing types list", type=MissingBevyType)# type: ignore missing_types_list: CollectionProperty(name="missing types list", type=MissingBevyType)# type: ignore
missing_types_list_index: IntProperty(name = "Index for missing types list", default = 0)# type: ignore missing_types_list_index: IntProperty(name = "Index for missing types list", default = 0)# type: ignore
@ -218,29 +146,22 @@ class ComponentsRegistry(PropertyGroup):
@classmethod @classmethod
def register(cls): def register(cls):
bpy.types.WindowManager.components_registry = PointerProperty(type=ComponentsRegistry) bpy.types.WindowManager.components_registry = PointerProperty(type=ComponentsRegistry)
bpy.context.window_manager.components_registry.watcher_active = False
@classmethod @classmethod
def unregister(cls): def unregister(cls):
bpy.context.window_manager.components_registry.watcher_active = False
for propgroup_name in cls.component_propertyGroups.keys(): for propgroup_name in cls.component_propertyGroups.keys():
try: try:
delattr(ComponentMetadata, propgroup_name) delattr(ComponentMetadata, propgroup_name)
#print("unregistered propertyGroup", propgroup_name)
except Exception as error: except Exception as error:
pass pass
#print("failed to remove", error, "ComponentMetadata")
try:
bpy.app.timers.unregister(watch_schema)
except Exception as error:
pass
del bpy.types.WindowManager.components_registry del bpy.types.WindowManager.components_registry
def load_schema(self): def load_schema(self):
print("load schema", self) print("load schema", self)
blenvy = bpy.context.window_manager.blenvy
component_settings = blenvy.components
# cleanup previous data if any # cleanup previous data if any
self.propGroupIdCounter = 0 self.propGroupIdCounter = 0
self.long_names_to_propgroup_names.clear() self.long_names_to_propgroup_names.clear()
@ -252,23 +173,13 @@ class ComponentsRegistry(PropertyGroup):
self.invalid_components.clear() self.invalid_components.clear()
# now prepare paths to load data # now prepare paths to load data
file_path = bpy.data.filepath
# Get the folder
folder_path = os.path.dirname(file_path)
path = os.path.join(folder_path, self.schemaPath)
self.schemaFullPath = path
f = Path(bpy.path.abspath(path)) # make a path object of abs path with open(component_settings.schema_path_full) as f:
with open(path) as f:
data = json.load(f) data = json.load(f)
defs = data["$defs"] defs = data["$defs"]
self.registry = json.dumps(defs) # FIXME:meh ? self.registry = json.dumps(defs) # FIXME:meh ?
# start timer component_settings.start_schema_watcher()
if not self.watcher_active and self.watcher_enabled:
self.watcher_active = True
print("registering function", watch_schema)
bpy.app.timers.register(watch_schema)
# we load the json once, so we do not need to do it over & over again # we load the json once, so we do not need to do it over & over again
@ -279,17 +190,6 @@ class ComponentsRegistry(PropertyGroup):
def has_type_infos(self): def has_type_infos(self):
return len(self.type_infos.keys()) != 0 return len(self.type_infos.keys()) != 0
def load_settings(self):
print("loading settings")
settings = load_settings(self.settings_save_path)
if settings!= None:
self.schemaPath = settings["components_schemaPath"]
self.load_schema()
generate_propertyGroups_for_components()
ensure_metadata_for_all_items()
# we keep a list of component propertyGroup around # we keep a list of component propertyGroup around
def register_component_propertyGroup(self, name, propertyGroup): def register_component_propertyGroup(self, name, propertyGroup):
self.component_propertyGroups[name] = propertyGroup self.component_propertyGroups[name] = propertyGroup

View File

@ -5,13 +5,11 @@ from bpy.props import (StringProperty)
from ..utils import get_selection_type from ..utils import get_selection_type
from ..components.operators import OT_rename_component, RemoveComponentFromAllItemsOperator, RemoveComponentOperator from ..components.operators import OT_rename_component, RemoveComponentFromAllItemsOperator
from .operators import( from .operators import(
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL,
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT, COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT,
OT_OpenSchemaFileBrowser, OT_OpenSchemaFileBrowser, ReloadRegistryOperator,
OT_select_component_name_to_replace,
OT_select_object, ReloadRegistryOperator,
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL,
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT) COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT)
@ -34,23 +32,24 @@ class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
registry = context.window_manager.components_registry registry = context.window_manager.components_registry
blenvy = context.window_manager.blenvy
component_settings = blenvy.components
row = layout.row() row = layout.row()
col = row.column() col = row.column()
col.enabled = False col.enabled = False
col.prop(registry, "schemaPath", text="Registry Schema path") col.prop(component_settings, "schema_path", text="Registry Schema path")
col = row.column() col = row.column()
col.operator(OT_OpenSchemaFileBrowser.bl_idname, text="Browse for registry schema file (json)") col.operator("blenvy.open_schemafilebrowser", text="Browse for registry schema file (json)")
layout.separator() layout.separator()
layout.operator(ReloadRegistryOperator.bl_idname, text="reload registry" , icon="FILE_REFRESH") layout.operator("blenvy.reload_components_registry", text="reload registry" , icon="FILE_REFRESH")
layout.separator() layout.separator()
row = layout.row() row = layout.row()
row.prop(registry, "watcher_enabled", text="enable registry file polling") row.prop(component_settings, "watcher_enabled", text="enable registry file polling")
row.prop(registry, "watcher_poll_frequency", text="registry file poll frequency (s)") row.prop(component_settings, "watcher_poll_frequency", text="registry file poll frequency (s)")
layout.separator() layout.separator()
layout.separator() layout.separator()

View File

@ -1,7 +1,18 @@
import os
import bpy import bpy
from bpy_types import (PropertyGroup) from bpy_types import (PropertyGroup)
from bpy.props import (EnumProperty, PointerProperty, StringProperty, BoolProperty, CollectionProperty, IntProperty) from bpy.props import (EnumProperty, PointerProperty, StringProperty, BoolProperty, CollectionProperty, IntProperty)
from blenvy.settings import load_settings, upsert_settings
from .propGroups.prop_groups import generate_propertyGroups_for_components
from .components.metadata import ensure_metadata_for_all_items
# list of settings we do NOT want to save
settings_black_list = ['watcher_active']
def save_settings(settings, context):
print("save settings", settings, context, dict(settings))
settings_dict = dict(settings)
upsert_settings(settings.settings_save_path, {key: settings_dict[key] for key in settings_dict.keys() if key not in settings_black_list})
# helper function to deal with timer # helper function to deal with timer
def toggle_watcher(self, context): def toggle_watcher(self, context):
@ -14,15 +25,17 @@ def toggle_watcher(self, context):
else: else:
self.watcher_active = True self.watcher_active = True
bpy.app.timers.register(watch_schema) bpy.app.timers.register(watch_schema)
save_settings(self, context)
def watch_schema(): def watch_schema():
self = bpy.context.window_manager.components_registry blenvy = bpy.context.window_manager.blenvy
# print("watching schema file for changes") component_settings = blenvy.components
#print("watching schema file for changes")
try: try:
stamp = os.stat(self.schemaFullPath).st_mtime stamp = os.stat(component_settings.schema_path_full).st_mtime
stamp = str(stamp) stamp = str(stamp)
if stamp != self.schemaTimeStamp and self.schemaTimeStamp != "": if stamp != component_settings.schemaTimeStamp and component_settings.schemaTimeStamp != "":
print("FILE CHANGED !!", stamp, self.schemaTimeStamp) print("FILE CHANGED !!", stamp, component_settings.schemaTimeStamp)
# see here for better ways : https://stackoverflow.com/questions/11114492/check-if-a-file-is-not-open-nor-being-used-by-another-process # see here for better ways : https://stackoverflow.com/questions/11114492/check-if-a-file-is-not-open-nor-being-used-by-another-process
"""try: """try:
os.rename(path, path) os.rename(path, path)
@ -34,19 +47,29 @@ def watch_schema():
# we need to add an additional delay as the file might not have loaded yet # we need to add an additional delay as the file might not have loaded yet
bpy.app.timers.register(lambda: bpy.ops.object.reload_registry(), first_interval=1) bpy.app.timers.register(lambda: bpy.ops.object.reload_registry(), first_interval=1)
self.schemaTimeStamp = stamp component_settings.schemaTimeStamp = stamp
except Exception as error: except Exception as error:
pass pass
return self.watcher_poll_frequency if self.watcher_enabled else None return component_settings.watcher_poll_frequency if component_settings.watcher_enabled else None
class ComponentsSettings(PropertyGroup): class ComponentsSettings(PropertyGroup):
schemaPath: StringProperty(
settings_save_path = ".blenvy_components_settings" # where to store data in bpy.texts
schema_path: StringProperty(
name="schema path", name="schema path",
description="path to the registry schema file", description="path to the registry schema file",
default="registry.json" default="registry.json",
update=save_settings
)# type: ignore )# type: ignore
schema_path_full: StringProperty(
name="schema full path",
description="path to the registry schema file",
get=lambda self: os.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), self.schema_path))
) # type: ignore
watcher_enabled: BoolProperty(name="Watcher_enabled", default=True, update=toggle_watcher)# type: ignore watcher_enabled: BoolProperty(name="Watcher_enabled", default=True, update=toggle_watcher)# type: ignore
watcher_active: BoolProperty(name = "Flag for watcher status", default = False)# type: ignore watcher_active: BoolProperty(name = "Flag for watcher status", default = False)# type: ignore
@ -55,11 +78,45 @@ class ComponentsSettings(PropertyGroup):
description="frequency (s) at wich to poll for changes to the registry file", description="frequency (s) at wich to poll for changes to the registry file",
min=1, min=1,
max=10, max=10,
default=1 default=1,
update=save_settings
)# type: ignore )# type: ignore
schemaTimeStamp: StringProperty( schemaTimeStamp: StringProperty(
name="last timestamp of schema file", name="last timestamp of schema file",
description="", description="",
default="" default="",
update=save_settings
)# type: ignore )# type: ignore
@classmethod
def register(cls):
pass
#bpy.context.window_manager.blenvy.components.watcher_active = False
@classmethod
def unregister(cls):
bpy.context.window_manager.blenvy.components.watcher_active = False
try:
bpy.app.timers.unregister(watch_schema)
except Exception as error:
pass
def start_schema_watcher(self):
# start timer
if not self.watcher_active and self.watcher_enabled:
self.watcher_active = True
print("registering function", watch_schema)
bpy.app.timers.register(watch_schema)
def load_settings(self):
settings = load_settings(self.settings_save_path)
if settings is not None:
for setting in settings:
print("setting", setting, settings[setting])
setattr(self, setting, settings[setting])
registry = bpy.context.components_registry
registry.load_schema()
generate_propertyGroups_for_components()
ensure_metadata_for_all_items()

View File

@ -1,19 +1,20 @@
def draw_settings_ui(layout, registry): def draw_settings_ui(layout, component_settings):
row = layout.row() row = layout.row()
col = row.column() col = row.column()
col.enabled = False col.enabled = False
col.prop(registry, "schemaPath", text="Registry Schema path") col.prop(component_settings, "schema_path", text="Registry Schema path")
col = row.column() col = row.column()
col.operator(operator="blenvy.open_schemafilebrowser", text="Browse for registry schema file (json)") col.operator(operator="blenvy.open_schemafilebrowser", text="Browse for registry schema file (json)")
layout.separator() layout.separator()
layout.operator(operator="object.reload_registry", text="reload registry" , icon="FILE_REFRESH") layout.operator(operator="blenvy.reload_components_registry", text="reload registry" , icon="FILE_REFRESH")
layout.separator() layout.separator()
row = layout.row() row = layout.row()
row.prop(registry, "watcher_enabled", text="enable registry file polling") row.prop(component_settings, "watcher_enabled", text="enable registry file polling")
row.prop(registry, "watcher_poll_frequency", text="registry file poll frequency (s)") row.prop(component_settings, "watcher_poll_frequency", text="registry file poll frequency (s)")
layout.separator() layout.separator()
layout.separator() layout.separator()

View File

@ -5,27 +5,36 @@ from bpy.props import (EnumProperty, PointerProperty, StringProperty, Collection
from .scene_helpers import SceneSelector from .scene_helpers import SceneSelector
from ..settings import upsert_settings, load_settings from ..settings import upsert_settings, load_settings
import blenvy.add_ons.auto_export.settings as auto_export_settings import blenvy.add_ons.auto_export.settings as auto_export_settings
import blenvy.add_ons.bevy_components.settings as component_settings
def update_scene_lists(self, context): def update_scene_lists(blenvy, context):
blenvy = self# context.window_manager.blenvy
blenvy.main_scene_names = [scene.name for scene in blenvy.main_scenes] # FIXME: unsure blenvy.main_scene_names = [scene.name for scene in blenvy.main_scenes] # FIXME: unsure
blenvy.library_scene_names = [scene.name for scene in blenvy.library_scenes] # FIXME: unsure blenvy.library_scene_names = [scene.name for scene in blenvy.library_scenes] # FIXME: unsure
upsert_settings(blenvy.settings_save_path, {"common_main_scene_names": [scene.name for scene in blenvy.main_scenes]}) upsert_settings(blenvy.settings_save_path, {"common_main_scene_names": [scene.name for scene in blenvy.main_scenes]})
upsert_settings(blenvy.settings_save_path, {"common_library_scene_names": [scene.name for scene in blenvy.library_scenes]}) upsert_settings(blenvy.settings_save_path, {"common_library_scene_names": [scene.name for scene in blenvy.library_scenes]})
def update_asset_folders(self, context): def update_asset_folders(blenvy, context):
blenvy = self # context.window_manager.blenvy
asset_path_names = ['project_root_path', 'assets_path', 'blueprints_path', 'levels_path', 'materials_path'] asset_path_names = ['project_root_path', 'assets_path', 'blueprints_path', 'levels_path', 'materials_path']
for asset_path_name in asset_path_names: for asset_path_name in asset_path_names:
upsert_settings(blenvy.settings_save_path, {asset_path_name: getattr(blenvy, asset_path_name)}) upsert_settings(blenvy.settings_save_path, {asset_path_name: getattr(blenvy, asset_path_name)})
def update_mode(self, context): def update_mode(blenvy, context):
blenvy = self
upsert_settings(blenvy.settings_save_path, {"mode": blenvy.mode }) upsert_settings(blenvy.settings_save_path, {"mode": blenvy.mode })
def is_scene_ok(self, scene):
try:
print("SELF", self)
return scene.name not in self.main_scenes and scene.name not in self.library_scenes
except:
print("FAILURE")
return True
class BlenvyManager(PropertyGroup): class BlenvyManager(PropertyGroup):
settings_save_path = ".blenvy_settings" # where to store data in bpy.texts settings_save_path = ".blenvy_common_settings" # where to store data in bpy.texts
mode: EnumProperty( mode: EnumProperty(
items=( items=(
@ -109,27 +118,20 @@ class BlenvyManager(PropertyGroup):
# sub ones # sub ones
auto_export: PointerProperty(type=auto_export_settings.AutoExportSettings) # type: ignore auto_export: PointerProperty(type=auto_export_settings.AutoExportSettings) # type: ignore
#components: PointerProperty(type=bevy_component_settings.ComponentSettings) # type: ignore components: PointerProperty(type=component_settings.ComponentsSettings) # type: ignore
def is_scene_ok(self, scene): main_scene_selector: PointerProperty(type=bpy.types.Scene, name="main scene", description="main_scene_picker", poll=is_scene_ok)# type: ignore
try: library_scene_selector: PointerProperty(type=bpy.types.Scene, name="library scene", description="library_scene_picker", poll=is_scene_ok)# type: ignore
operator = bpy.context.space_data.active_operator
return scene.name not in operator.main_scenes and scene.name not in operator.library_scenes
except:
return True
@classmethod @classmethod
def register(cls): def register(cls):
bpy.types.WindowManager.main_scene = bpy.props.PointerProperty(type=bpy.types.Scene, name="main scene", description="main_scene_picker", poll=cls.is_scene_ok)
bpy.types.WindowManager.library_scene = bpy.props.PointerProperty(type=bpy.types.Scene, name="library scene", description="library_scene_picker", poll=cls.is_scene_ok)
bpy.types.WindowManager.blenvy = PointerProperty(type=BlenvyManager) bpy.types.WindowManager.blenvy = PointerProperty(type=BlenvyManager)
@classmethod @classmethod
def unregister(cls): def unregister(cls):
del bpy.types.WindowManager.blenvy del bpy.types.WindowManager.blenvy
del bpy.types.WindowManager.main_scene
del bpy.types.WindowManager.library_scene
def load_settings(self): def load_settings(self):
print("LOAD SETTINGS") print("LOAD SETTINGS")
@ -150,3 +152,9 @@ class BlenvyManager(PropertyGroup):
for asset_path_name in asset_path_names: for asset_path_name in asset_path_names:
if asset_path_name in settings: if asset_path_name in settings:
setattr(self, asset_path_name, settings[asset_path_name]) setattr(self, asset_path_name, settings[asset_path_name])
# now load auto_export settings
self.auto_export.load_settings()
# now load component settings
self.auto_export.load_settings()

View File

@ -95,19 +95,19 @@ class SCENES_LIST_OT_actions(Operator):
if self.action == 'ADD': if self.action == 'ADD':
new_scene_name = None new_scene_name = None
if self.scene_type == "LEVEL": if self.scene_type == "LEVEL":
if context.window_manager.main_scene: if context.window_manager.blenvy.main_scene_selector:
new_scene_name = context.window_manager.main_scene.name new_scene_name = context.window_manager.blenvy.main_scene_selector.name
else: else:
if context.window_manager.library_scene: if context.window_manager.blenvy.library_scene_selector:
new_scene_name = context.window_manager.library_scene.name new_scene_name = context.window_manager.blenvy.library_scene_selector.name
if new_scene_name: if new_scene_name:
item = target.add() item = target.add()
item.name = new_scene_name item.name = new_scene_name
if self.scene_type == "LEVEL": if self.scene_type == "LEVEL":
context.window_manager.main_scene = None context.window_manager.blenvy.main_scene_selector = None
else: else:
context.window_manager.library_scene = None context.window_manager.blenvy.library_scene_selector = None
setattr(source, target_index, len(target) - 1) setattr(source, target_index, len(target) - 1)

View File

@ -104,7 +104,7 @@ class BLENVY_PT_SidePanel(bpy.types.Panel):
rows = 2 rows = 2
row = section.row() row = section.row()
row.label(text="main scenes") row.label(text="main scenes")
row.prop(context.window_manager, "main_scene", text='') row.prop(blenvy, "main_scene_selector", text='')
row = section.row() row = section.row()
row.template_list("SCENE_UL_Blenvy", "level scenes", blenvy, "main_scenes", blenvy, "main_scenes_index", rows=rows) row.template_list("SCENE_UL_Blenvy", "level scenes", blenvy, "main_scenes", blenvy, "main_scenes_index", rows=rows)
@ -115,7 +115,7 @@ class BLENVY_PT_SidePanel(bpy.types.Panel):
add_operator.action = 'ADD' add_operator.action = 'ADD'
add_operator.scene_type = 'LEVEL' add_operator.scene_type = 'LEVEL'
#add_operator.operator = operator #add_operator.operator = operator
sub_row.enabled = context.window_manager.main_scene is not None sub_row.enabled = blenvy.main_scene_selector is not None
sub_row = col.row() sub_row = col.row()
remove_operator = sub_row.operator("scene_list.list_action", icon='REMOVE', text="") remove_operator = sub_row.operator("scene_list.list_action", icon='REMOVE', text="")
@ -126,7 +126,7 @@ class BLENVY_PT_SidePanel(bpy.types.Panel):
# library scenes # library scenes
row = section.row() row = section.row()
row.label(text="library scenes") row.label(text="library scenes")
row.prop(context.window_manager, "library_scene", text='') row.prop(blenvy, "library_scene_selector", text='')
row = section.row() row = section.row()
row.template_list("SCENE_UL_Blenvy", "library scenes", blenvy, "library_scenes", blenvy, "library_scenes_index", rows=rows) row.template_list("SCENE_UL_Blenvy", "library scenes", blenvy, "library_scenes", blenvy, "library_scenes_index", rows=rows)
@ -136,7 +136,7 @@ class BLENVY_PT_SidePanel(bpy.types.Panel):
add_operator = sub_row.operator("scene_list.list_action", icon='ADD', text="") add_operator = sub_row.operator("scene_list.list_action", icon='ADD', text="")
add_operator.action = 'ADD' add_operator.action = 'ADD'
add_operator.scene_type = 'LIBRARY' add_operator.scene_type = 'LIBRARY'
sub_row.enabled = context.window_manager.library_scene is not None sub_row.enabled = blenvy.library_scene_selector is not None
sub_row = col.row() sub_row = col.row()
@ -148,7 +148,7 @@ class BLENVY_PT_SidePanel(bpy.types.Panel):
header, panel = layout.panel("components", default_closed=False) header, panel = layout.panel("components", default_closed=False)
header.label(text="Components") header.label(text="Components")
if panel: if panel:
components_ui.draw_settings_ui(panel, context.window_manager.components_registry) components_ui.draw_settings_ui(panel, blenvy.components)
header, panel = layout.panel("auto_export", default_closed=False) header, panel = layout.panel("auto_export", default_closed=False)
header.label(text="Auto Export") header.label(text="Auto Export")

View File

@ -2,7 +2,7 @@ import json
import bpy import bpy
def upsert_settings(name, data): def upsert_settings(name, data):
stored_settings = bpy.data.texts[name] if name in bpy.data.texts else None#bpy.data.texts.new(name) stored_settings = bpy.data.texts[name] if name in bpy.data.texts else None
if stored_settings is None: if stored_settings is None:
stored_settings = bpy.data.texts.new(name) stored_settings = bpy.data.texts.new(name)
stored_settings.write(json.dumps(data)) stored_settings.write(json.dumps(data))
@ -21,6 +21,41 @@ def load_settings(name):
return None return None
return None return None
# given the input (actual) settings, filters out any invalid/useless params & params that are equal to defaults
def generate_complete_preferences_dict(settings, presets, ignore_list=[]):
complete_preferences = {}
defaults = {}
def filter_out(pair):
key, value = pair
if key in ignore_list:
return False
return True
for k in presets.__annotations__:
item = presets.__annotations__[k]
default = item.keywords.get('default', None)
defaults[k] = default
#complete_preferences[k] = default
# print("defaults", defaults)
for key in list(settings.keys()):
if key in defaults and settings[key] != defaults[key]: # only write out values different from defaults
complete_preferences[key] = getattr(settings, key, None)
print("complete_preferences", complete_preferences)
"""for key in list(settings.keys()):
if key in defaults and settings[key] != defaults[key]: # only write out values different from defaults
complete_preferences[key] = settings[key]"""
complete_preferences = dict(filter(filter_out, dict(complete_preferences).items()))
return complete_preferences
# checks if old & new settings (dicts really) are identical # checks if old & new settings (dicts really) are identical
def are_settings_identical(old, new, white_list=None): def are_settings_identical(old, new, white_list=None):
if old is None and new is None: if old is None and new is None:

View File

@ -5,9 +5,9 @@ import pytest
def setup_data(request): def setup_data(request):
print("\nSetting up resources...") print("\nSetting up resources...")
schemaPath = "../../testing/bevy_example/assets/registry.json" schema_path = "../../testing/bevy_example/assets/registry.json"
yield {"schema_path": schemaPath} yield {"schema_path": schema_path}
def finalizer(): def finalizer():
print("\nPerforming teardown...") print("\nPerforming teardown...")

View File

@ -11,7 +11,7 @@ from .setup_data import setup_data
def test_components_should_generate_correct_custom_properties(setup_data): def test_components_should_generate_correct_custom_properties(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
type_infos = registry.type_infos type_infos = registry.type_infos
@ -58,7 +58,7 @@ def test_components_should_generate_correct_custom_properties(setup_data):
def test_components_should_generate_correct_custom_properties_with_randomized_values(setup_data): def test_components_should_generate_correct_custom_properties_with_randomized_values(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
type_infos = registry.type_infos type_infos = registry.type_infos
@ -108,7 +108,7 @@ def test_components_should_generate_correct_custom_properties_with_randomized_va
def test_components_should_generate_correct_propertyGroup_values_from_custom_properties(setup_data): def test_components_should_generate_correct_propertyGroup_values_from_custom_properties(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
type_infos = registry.type_infos type_infos = registry.type_infos
@ -166,7 +166,7 @@ def test_components_should_generate_correct_propertyGroup_values_from_custom_pro
def test_remove_components(setup_data): def test_remove_components(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
type_infos = registry.type_infos type_infos = registry.type_infos
@ -207,7 +207,7 @@ def test_remove_components(setup_data):
def test_copy_paste_components(setup_data): def test_copy_paste_components(setup_data):
context = bpy.context context = bpy.context
registry = context.window_manager.components_registry registry = context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
long_name = "bevy_example::test_components::BasicTest" long_name = "bevy_example::test_components::BasicTest"

View File

@ -3,7 +3,7 @@ from .setup_data import setup_data
def test_blend(setup_data): def test_blend(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
long_name = "bevy_example::test_components::BasicTest" long_name = "bevy_example::test_components::BasicTest"

View File

@ -24,7 +24,7 @@ def get_component_propGroup(registry, component_name, component_meta):
def test_rename_component_single_unit_struct(setup_data): def test_rename_component_single_unit_struct(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component rename_component_operator = bpy.ops.object.rename_bevy_component
@ -47,7 +47,7 @@ def test_rename_component_single_unit_struct(setup_data):
def test_rename_component_single_complex_struct(setup_data): def test_rename_component_single_complex_struct(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component rename_component_operator = bpy.ops.object.rename_bevy_component
@ -70,7 +70,7 @@ def test_rename_component_single_complex_struct(setup_data):
def test_rename_component_bulk(setup_data): def test_rename_component_bulk(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component rename_component_operator = bpy.ops.object.rename_bevy_component
@ -95,7 +95,7 @@ def test_rename_component_bulk(setup_data):
def test_rename_component_single_error_handling(setup_data): def test_rename_component_single_error_handling(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component rename_component_operator = bpy.ops.object.rename_bevy_component
@ -125,7 +125,7 @@ def test_rename_component_single_error_handling(setup_data):
def test_rename_component_single_error_handling_clean_errors(setup_data): def test_rename_component_single_error_handling_clean_errors(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["components_schemaPath"] registry.schema_path = setup_data["components_schemaPath"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component rename_component_operator = bpy.ops.object.rename_bevy_component

View File

@ -5,7 +5,7 @@ from .setup_data import setup_data
def test_shuffler(setup_data): def test_shuffler(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["schema_path"] registry.schema_path = setup_data["schema_path"]
bpy.ops.object.reload_registry() bpy.ops.object.reload_registry()
type_infos = registry.type_infos type_infos = registry.type_infos