2024-02-05 22:01:19 +00:00
import bpy
import json
import os
2024-02-18 16:14:31 +00:00
import uuid
2024-02-05 22:01:19 +00:00
from pathlib import Path
from bpy_types import ( PropertyGroup )
from bpy . props import ( StringProperty , BoolProperty , FloatProperty , FloatVectorProperty , IntProperty , IntVectorProperty , EnumProperty , PointerProperty , CollectionProperty )
2024-02-18 16:14:31 +00:00
from . . helpers import load_settings
from . . propGroups . prop_groups import generate_propertyGroups_for_components
from . . components . metadata import ComponentMetadata , ensure_metadata_for_all_objects
2024-02-05 22:01:19 +00:00
# helper class to store missing bevy types information
class MissingBevyType ( bpy . types . PropertyGroup ) :
type_name : bpy . props . StringProperty (
name = " type " ,
2024-02-18 16:14:31 +00:00
) # 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 :
print ( " failed to unregister " , error )
pass
else :
self . watcher_active = True
bpy . app . timers . register ( watch_schema )
def watch_schema ( ) :
self = bpy . context . window_manager . components_registry
2024-03-04 07:20:00 +00:00
# print("watching schema file for changes")
2024-02-18 16:14:31 +00:00
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
2024-02-05 22:01:19 +00:00
# this is where we store the information for all available components
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 "
2024-02-18 16:14:31 +00:00
) # type: ignore
schemaFullPath : bpy . props . StringProperty (
name = " schema full path " ,
description = " path to the registry schema file " ,
) # type: ignore
2024-02-05 22:01:19 +00:00
registry : bpy . props . StringProperty (
name = " registry " ,
description = " component registry "
2024-02-18 16:14:31 +00:00
) # type: ignore
2024-02-05 22:01:19 +00:00
missing_type_infos : StringProperty (
name = " missing type infos " ,
description = " unregistered/missing type infos "
2024-02-18 16:14:31 +00:00
) # 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
2024-02-05 22:01:19 +00:00
2024-02-18 16:14:31 +00:00
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_index : IntProperty ( name = " Index for missing types list " , default = 0 ) # type: ignore
2024-02-05 22:01:19 +00:00
blender_property_mapping = {
" bool " : dict ( type = BoolProperty , presets = dict ( ) ) ,
" u8 " : dict ( type = IntProperty , presets = dict ( min = 0 , max = 255 ) ) ,
" u16 " : dict ( type = IntProperty , presets = dict ( min = 0 , max = 65535 ) ) ,
" u32 " : dict ( type = IntProperty , presets = dict ( min = 0 ) ) ,
" u64 " : dict ( type = IntProperty , presets = dict ( min = 0 ) ) ,
" u128 " : dict ( type = IntProperty , presets = dict ( min = 0 ) ) ,
" u64 " : dict ( type = IntProperty , presets = dict ( min = 0 ) ) ,
" usize " : dict ( type = IntProperty , presets = dict ( min = 0 ) ) ,
" i8 " : dict ( type = IntProperty , presets = dict ( ) ) ,
" i16 " : dict ( type = IntProperty , presets = dict ( ) ) ,
" i32 " : dict ( type = IntProperty , presets = dict ( ) ) ,
" i64 " : dict ( type = IntProperty , presets = dict ( ) ) ,
" i128 " : dict ( type = IntProperty , presets = dict ( ) ) ,
2024-02-18 16:14:31 +00:00
" isize " : dict ( type = IntProperty , presets = dict ( ) ) ,
2024-02-05 22:01:19 +00:00
" f32 " : dict ( type = FloatProperty , presets = dict ( ) ) ,
" f64 " : dict ( type = FloatProperty , presets = dict ( ) ) ,
" glam::Vec2 " : { " type " : FloatVectorProperty , " presets " : dict ( size = 2 ) } ,
" glam::DVec2 " : { " type " : FloatVectorProperty , " presets " : dict ( size = 2 ) } ,
" glam::UVec2 " : { " type " : FloatVectorProperty , " presets " : dict ( size = 2 ) } ,
" glam::Vec3 " : { " type " : FloatVectorProperty , " presets " : { " size " : 3 } } ,
" glam::Vec3A " : { " type " : FloatVectorProperty , " presets " : { " size " : 3 } } ,
" glam::DVec3 " : { " type " : FloatVectorProperty , " presets " : { " size " : 3 } } ,
" glam::UVec3 " : { " type " : FloatVectorProperty , " presets " : { " size " : 3 } } ,
" glam::Vec4 " : { " type " : FloatVectorProperty , " presets " : { " size " : 4 } } ,
" glam::Vec4A " : { " type " : FloatVectorProperty , " presets " : { " size " : 4 } } ,
" glam::DVec4 " : { " type " : FloatVectorProperty , " presets " : { " size " : 4 } } ,
" glam::UVec4 " : { " type " : FloatVectorProperty , " presets " : { " size " : 4 , " min " : 0.0 } } ,
" glam::Quat " : { " type " : FloatVectorProperty , " presets " : { " size " : 4 } } ,
" bevy_render::color::Color " : dict ( type = FloatVectorProperty , presets = dict ( subtype = ' COLOR ' , size = 4 ) ) ,
" char " : dict ( type = StringProperty , presets = dict ( ) ) ,
" str " : dict ( type = StringProperty , presets = dict ( ) ) ,
" alloc::string::String " : dict ( type = StringProperty , presets = dict ( ) ) ,
2024-02-18 16:14:31 +00:00
" alloc::borrow::Cow<str> " : dict ( type = StringProperty , presets = dict ( ) ) ,
2024-02-05 22:01:19 +00:00
" enum " : dict ( type = EnumProperty , presets = dict ( ) ) ,
2024-02-18 16:14:31 +00:00
' bevy_ecs::Entity ' : { " type " : IntProperty , " presets " : { " min " : 0 } } ,
' bevy_utils::Uuid ' : dict ( type = StringProperty , presets = dict ( ) ) ,
2024-02-05 22:01:19 +00:00
}
value_types_defaults = {
" string " : " " ,
" boolean " : True ,
" float " : 0.0 ,
" uint " : 0 ,
" int " : 0 ,
# todo : we are re-doing the work of the bevy /rust side here, but it seems more pratical to alway look for the same field name on the blender side for matches
" bool " : True ,
" u8 " : 0 ,
" u16 " : 0 ,
" u32 " : 0 ,
" u64 " : 0 ,
" u128 " : 0 ,
2024-02-18 16:14:31 +00:00
" usize " : 0 ,
2024-02-05 22:01:19 +00:00
" i8 " : 0 ,
" i16 " : 0 ,
" i32 " : 0 ,
" i64 " : 0 ,
" i128 " : 0 ,
2024-02-18 16:14:31 +00:00
" isize " : 0 ,
2024-02-05 22:01:19 +00:00
" f32 " : 0.0 ,
" f64 " : 0.0 ,
" char " : " " ,
" str " : " " ,
" alloc::string::String " : " " ,
2024-02-18 16:14:31 +00:00
" alloc::borrow::Cow<str> " : " " ,
2024-02-05 22:01:19 +00:00
" glam::Vec2 " : [ 0.0 , 0.0 ] ,
" glam::DVec2 " : [ 0.0 , 0.0 ] ,
" glam::UVec2 " : [ 0 , 0 ] ,
" glam::Vec3 " : [ 0.0 , 0.0 , 0.0 ] ,
" glam::Vec3A " : [ 0.0 , 0.0 , 0.0 ] ,
" glam::UVec3 " : [ 0 , 0 , 0 ] ,
" glam::Vec4 " : [ 0.0 , 0.0 , 0.0 , 0.0 ] ,
" glam::DVec4 " : [ 0.0 , 0.0 , 0.0 , 0.0 ] ,
" glam::UVec4 " : [ 0 , 0 , 0 , 0 ] ,
" glam::Quat " : [ 0.0 , 0.0 , 0.0 , 0.0 ] ,
" bevy_render::color::Color " : [ 1.0 , 1.0 , 0.0 , 1.0 ] ,
2024-02-18 16:14:31 +00:00
' bevy_ecs::Entity ' : 0 , #4294967295, # this is the same as Bevy's Entity::Placeholder, too big for Blender..sigh
' bevy_utils::Uuid ' : ' " ' + str ( uuid . uuid4 ( ) ) + ' " '
2024-02-05 22:01:19 +00:00
}
2024-02-18 16:14:31 +00:00
type_infos = { }
2024-02-05 22:01:19 +00:00
type_infos_missing = [ ]
component_propertyGroups = { }
short_names_to_long_names = { }
2024-02-18 16:14:31 +00:00
custom_types_to_add = { }
invalid_components = [ ]
2024-02-05 22:01:19 +00:00
@classmethod
def register ( cls ) :
bpy . types . WindowManager . components_registry = PointerProperty ( type = ComponentsRegistry )
2024-02-18 16:14:31 +00:00
bpy . context . window_manager . components_registry . watcher_active = False
2024-02-05 22:01:19 +00:00
@classmethod
def unregister ( cls ) :
2024-02-18 16:14:31 +00:00
bpy . context . window_manager . components_registry . watcher_active = False
2024-02-05 22:01:19 +00:00
for propgroup_name in cls . component_propertyGroups . keys ( ) :
try :
2024-02-18 16:14:31 +00:00
delattr ( ComponentMetadata , propgroup_name )
#print("unregistered propertyGroup", propgroup_name)
2024-02-05 22:01:19 +00:00
except Exception as error :
pass
2024-02-18 16:14:31 +00:00
#print("failed to remove", error, "ComponentMetadata")
2024-02-05 22:01:19 +00:00
2024-02-18 16:14:31 +00:00
try :
bpy . app . timers . unregister ( watch_schema )
except Exception as error :
print ( " failed to unregister " , error )
pass
2024-02-05 22:01:19 +00:00
del bpy . types . WindowManager . components_registry
2024-02-18 16:14:31 +00:00
2024-02-05 22:01:19 +00:00
def load_schema ( self ) :
2024-02-18 16:14:31 +00:00
print ( " load schema " , self )
# cleanup previous data if any
self . propGroupIdCounter = 0
self . short_names_to_propgroup_names . clear ( )
2024-02-05 22:01:19 +00:00
self . missing_types_list . clear ( )
2024-02-18 16:14:31 +00:00
self . type_infos . clear ( )
2024-02-05 22:01:19 +00:00
self . type_infos_missing . clear ( )
2024-02-18 16:14:31 +00:00
self . component_propertyGroups . clear ( )
self . short_names_to_long_names . clear ( )
self . custom_types_to_add . clear ( )
self . invalid_components . clear ( )
2024-02-05 22:01:19 +00:00
2024-02-18 16:14:31 +00:00
# now prepare paths to load data
file_path = bpy . data . filepath
2024-02-05 22:01:19 +00:00
# Get the folder
folder_path = os . path . dirname ( file_path )
path = os . path . join ( folder_path , self . schemaPath )
2024-02-18 16:14:31 +00:00
self . schemaFullPath = path
2024-02-05 22:01:19 +00:00
f = Path ( bpy . path . abspath ( path ) ) # make a path object of abs path
with open ( path ) as f :
data = json . load ( f )
defs = data [ " $defs " ]
self . registry = json . dumps ( defs ) # FIXME:meh ?
2024-02-18 16:14:31 +00:00
# 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 )
2024-02-05 22:01:19 +00:00
# we load the json once, so we do not need to do it over & over again
def load_type_infos ( self ) :
2024-02-18 16:14:31 +00:00
print ( " load type infos " )
2024-02-05 22:01:19 +00:00
ComponentsRegistry . type_infos = json . loads ( self . registry )
2024-02-18 16:14:31 +00:00
def has_type_infos ( self ) :
return len ( self . type_infos . keys ( ) ) != 0
def load_settings ( self ) :
print ( " loading settings " )
settings = load_settings ( self . settings_save_path )
if settings != None :
print ( " settings " , settings )
self . schemaPath = settings [ " schemaPath " ]
self . load_schema ( )
generate_propertyGroups_for_components ( )
ensure_metadata_for_all_objects ( )
2024-02-05 22:01:19 +00:00
# we keep a list of component propertyGroup around
def register_component_propertyGroup ( self , name , propertyGroup ) :
self . component_propertyGroups [ name ] = propertyGroup
#for practicality, we add an entry for a reverse lookup (short => long name, since we already have long_name => short_name with the keys of the raw registry)
def add_shortName_to_longName ( self , short_name , long_name ) :
self . short_names_to_long_names [ short_name ] = long_name
# to be able to give the user more feedback on any missin/unregistered types in their schema file
def add_missing_typeInfo ( self , type_name ) :
if not type_name in self . type_infos_missing :
self . type_infos_missing . append ( type_name )
setattr ( self , " missing_type_infos " , str ( self . type_infos_missing ) )
item = self . missing_types_list . add ( )
item . type_name = type_name
def add_custom_type ( self , type_name , type_definition ) :
self . custom_types_to_add [ type_name ] = type_definition
def process_custom_types ( self ) :
for type_name in self . custom_types_to_add :
self . type_infos [ type_name ] = self . custom_types_to_add [ type_name ]
self . custom_types_to_add . clear ( )
def add_invalid_component ( self , component_name ) :
self . invalid_components . append ( component_name )
2024-02-18 16:14:31 +00:00
###########
propGroupIdCounter : IntProperty (
name = " propGroupIdCounter " ,
description = " " ,
min = 0 ,
max = 1000000000 ,
default = 0
) # type: ignore
short_names_to_propgroup_names = { }
# generate propGroup name from nesting level & shortName: each shortName + nesting is unique
def generate_propGroup_name ( self , nesting , shortName ) :
#print("gen propGroup name for", shortName, nesting)
#if shortName in self.short_names_to_propgroup_names and len(nesting) == 0:
# return self.get_propertyGroupName_from_shortName(shortName)
self . propGroupIdCounter + = 1
propGroupIndex = str ( self . propGroupIdCounter )
propGroupName = propGroupIndex + " _ui "
key = str ( nesting ) + shortName if len ( nesting ) > 0 else shortName
self . short_names_to_propgroup_names [ key ] = propGroupName
return propGroupName
def get_propertyGroupName_from_shortName ( self , shortName ) :
return self . short_names_to_propgroup_names . get ( shortName , None )
2024-02-05 22:01:19 +00:00
"""
object [ component_definition . name ] = 0.5
property_manager = object . id_properties_ui ( component_definition . name )
property_manager . update ( min = - 10 , max = 10 , soft_min = - 5 , soft_max = 5 )
print ( " property_manager " , property_manager )
object [ component_definition . name ] = [ 0.8 , 0.2 , 1.0 ]
property_manager = object . id_properties_ui ( component_definition . name )
property_manager . update ( subtype = ' COLOR ' )
#IDPropertyUIManager
#rna_ui = object[component_definition.name].get('_RNA_UI')
"""