Compare commits

...

6 Commits

Author SHA1 Message Date
kaosat.dev
cf87ace91f docs(): preparing future release notes 2024-05-03 00:28:00 +02:00
kaosat.dev
e7118f2565 chore(testing): updated registry export 2024-05-03 00:27:43 +02:00
kaosat.dev
f224148e1d chore(auto_export): cleanups 2024-05-03 00:27:23 +02:00
kaosat.dev
ff68109f0f feat(bevy_gltf_components): added support for the new 'nested' bevy_components hashmap from
the bevy_components add-on ! this means support for full component paths is working
 * no more need for short_names in that case: long names work !
 * support for short names for other components (for simplicity & compatibility) is still in but cleaned up
2024-05-03 00:25:41 +02:00
kaosat.dev
98a654095b feat(bevy_components): added basic of hashmap/map support
* added handling of additional map flags
 * added map handling in conversion from prop group
 * added basic (& clunky) UI
 * related boilerplate
 * added various testing components to try this out & experiment with
2024-05-03 00:22:51 +02:00
kaosat.dev
ca02c1df8c feat(bevy_components): continued changes to base logic on long_names 2024-05-02 10:24:09 +02:00
30 changed files with 773 additions and 168 deletions

36
RELEASE_NOTES.md Normal file
View File

@ -0,0 +1,36 @@
This new release has a lot of breaking changes in the Blender tooling as well as in the Bevy crates
Here is a rundown + rationale behing the changes:
- Blender add-ons
- Auto_export:
- spliting out of gltf exporter settings
up until now , the auto export add-on provided a *subset* of gltf settings, causing issues due to lack of features (in particular for animation settings),
and it required me to play catch up every time there where added / changed settings
the Ui of Blender's gltf exporter is NOT reusable in any form or way inside another add-on . I tried various solutions such as turning the auto exporter
into a 'extension' of the standard exporter, but none of them worked in a reliable or 'nice to use' way
So I decided to split the gltf settings from the auto_export setting, this has multiple advantages:
* access to ALL gltf settings
* the gltf UI & settings will always keep up with the official releases
* less maintenance work
The only disadvantage is that the standard gltf exporter is a normal exporter, so it generates a 'fake' gltf file that immediatly gets deleted after export,
so you will need to use the new side panel to set your gltf settings:
* this is also done to ensure that the gltf settings settings used for auto export are NOT interfering with the ones you might use when exporting gltf files normally
- change detection :
after spending MANY hours analysing the issues with change detection (using some of Blender's built in logic) I came to the conclusion that it was not going to be reliable enough, so I opted for a 'hand made' brute force approach to change detection:
* every time you save (& thus export), all your scenes are serialized / hashed and that hashed version is compared to the one from the last save to determine what changed and what did not
- handling of external/ embeded / split etc collections
while adding tests I also realised that the detection of which main scenes & blueprints that needed to be exported was faulty, so I rewrote all the code in charge of that : this means that in general , based on your settings, the add-on will more accuratly export only those levels/blueprints that really NEED to be exported
- improved handling of multi-blend file projects
Up until now, all export paths where relative ** to the blend file itself** which could lead to issues when working with multiple blend files
Also for future improvements regarding assets managment, I changed the export paths to be relative to a new "project root" folder which is your *Bevy project's root folder*
- the levels/worlds now also got a seperate setting so you can easilly set where to export them too (they are not dumped out into the main export folder anymore), giving you more control over your non blueprint exports
- bevy_components
Up until now , it was not possible to have multiple components with the same name (ie ) as all the logic was based on short names
This required completely changing HOW/WHERE components are stored in objects, and they are now stored inside a 'bevy_components' custom property

View File

@ -31,8 +31,7 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
);
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.read();
let type_registry = type_registry.read();
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
// we assign the components specified /xxx_components objects to their parent node

View File

@ -13,10 +13,84 @@ pub fn ronstring_to_reflect_component(
) -> Vec<(Box<dyn Reflect>, TypeRegistration)> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap();
let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
for (key, value) in lookup.into_iter() {
let type_string = key.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str());
// println!("ron_string {:?}", ron_string);
for (name, value) in lookup.into_iter() {
let parsed_value: String;
match value.clone() {
Value::String(str) => {
parsed_value = str;
}
_ => parsed_value = ron::to_string(&value).unwrap().to_string(),
}
if name.as_str() == "bevy_components" {
bevy_components_string_to_components(parsed_value, type_registry, &mut components)
} else {
components_string_to_components(name, value, parsed_value, type_registry, &mut components)
}
}
components
}
fn components_string_to_components(
name: String,
value: Value,
parsed_value: String,
type_registry: &TypeRegistry,
components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>
){
let type_string = name.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str());
if let Some(type_registration) =
type_registry.get_with_short_type_path(capitalized_type_name.as_str())
{
debug!("TYPE INFO {:?}", type_registration.type_info());
let ron_string = format!(
"{{ \"{}\":{} }}",
type_registration.type_info().type_path(),
parsed_value
);
// usefull to determine what an entity looks like Serialized
/*let test_struct = CameraRenderGraph::new("name");
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
println!("serialized Component {}", serialized);*/
debug!("component data ron string {}", ron_string);
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str())
.expect("deserialzer should have been generated from string");
let reflect_deserializer = UntypedReflectDeserializer::new(type_registry);
let component = reflect_deserializer
.deserialize(&mut deserializer)
.unwrap_or_else(|_| {
panic!(
"failed to deserialize component {} with value: {:?}",
name, value
)
});
debug!("component {:?}", component);
debug!("real type {:?}", component.get_represented_type_info());
components.push((component, type_registration.clone()));
debug!("found type registration for {}", capitalized_type_name);
} else {
warn!("no type registration for {}", capitalized_type_name);
}
}
fn bevy_components_string_to_components(
parsed_value: String,
type_registry: &TypeRegistry,
components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>
){
let lookup: HashMap<String, Value> = ron::from_str(&parsed_value).unwrap();
for (key, value) in lookup.into_iter() {
let parsed_value: String;
match value.clone() {
Value::String(str) => {
@ -26,7 +100,7 @@ pub fn ronstring_to_reflect_component(
}
if let Some(type_registration) =
type_registry.get_with_short_type_path(capitalized_type_name.as_str())
type_registry.get_with_type_path(key.as_str())
{
debug!("TYPE INFO {:?}", type_registration.type_info());
@ -36,13 +110,6 @@ pub fn ronstring_to_reflect_component(
parsed_value
);
// usefull to determine what an entity looks like Serialized
/*let test_struct = CameraRenderGraph::new("name");
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
println!("serialized Component {}", serialized);*/
debug!("component data ron string {}", ron_string);
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str())
.expect("deserialzer should have been generated from string");
@ -59,10 +126,9 @@ pub fn ronstring_to_reflect_component(
debug!("component {:?}", component);
debug!("real type {:?}", component.get_represented_type_info());
components.push((component, type_registration.clone()));
debug!("found type registration for {}", capitalized_type_name);
debug!("found type registration for {}", key);
} else {
warn!("no type registration for {}", capitalized_type_name);
warn!("no type registration for {}", key);
}
}
components
}
}

View File

@ -3168,6 +3168,117 @@
"type": "string",
"typeInfo": "Enum"
},
"bevy_example::test_components::HashmapTestIntColor": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {
"inner": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<u32, bevy_render::color::Color, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
},
"required": [
"inner"
],
"short_name": "HashmapTestIntColor",
"title": "bevy_example::test_components::HashmapTestIntColor",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::HashmapTestIntString": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {
"named_animations": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<u32, alloc::string::String, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
},
"required": [
"named_animations"
],
"short_name": "HashmapTestIntString",
"title": "bevy_example::test_components::HashmapTestIntString",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::HashmapTestSimple": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {
"named_animations": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, alloc::string::String, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
},
"required": [
"named_animations"
],
"short_name": "HashmapTestSimple",
"title": "bevy_example::test_components::HashmapTestSimple",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::HashmapTestStringColor": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {
"inner": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_render::color::Color, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
},
"required": [
"inner"
],
"short_name": "HashmapTestStringColor",
"title": "bevy_example::test_components::HashmapTestStringColor",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::HashmapTestStringColorFlat": {
"isComponent": true,
"isResource": false,
"items": false,
"prefixItems": [
{
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_render::color::Color, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
],
"short_name": "HashmapTestStringColorFlat",
"title": "bevy_example::test_components::HashmapTestStringColorFlat",
"type": "array",
"typeInfo": "TupleStruct"
},
"bevy_example::test_components::HashmapTestStringFloat": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {
"named_animations": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, f32, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
},
"required": [
"named_animations"
],
"short_name": "HashmapTestStringFloat",
"title": "bevy_example::test_components::HashmapTestStringFloat",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::NestedTupleStuff": {
"isComponent": true,
"isResource": false,
@ -11026,6 +11137,19 @@
"type": "object",
"typeInfo": "Value"
},
"bevy_utils::hashbrown::HashMap<alloc::string::String, alloc::string::String, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
"$ref": "#/$defs/alloc::string::String"
}
},
"isComponent": false,
"isResource": false,
"short_name": "HashMap<String, String, DefaultHashBuilder>",
"title": "bevy_utils::hashbrown::HashMap<alloc::string::String, alloc::string::String, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<alloc::string::String, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
@ -11039,6 +11163,19 @@
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_render::color::Color, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
"$ref": "#/$defs/bevy_render::color::Color"
}
},
"isComponent": false,
"isResource": false,
"short_name": "HashMap<String, Color, DefaultHashBuilder>",
"title": "bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_render::color::Color, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
@ -11052,6 +11189,32 @@
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<alloc::string::String, f32, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
"$ref": "#/$defs/f32"
}
},
"isComponent": false,
"isResource": false,
"short_name": "HashMap<String, f32, DefaultHashBuilder>",
"title": "bevy_utils::hashbrown::HashMap<alloc::string::String, f32, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<u32, alloc::string::String, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
"$ref": "#/$defs/alloc::string::String"
}
},
"isComponent": false,
"isResource": false,
"short_name": "HashMap<u32, String, DefaultHashBuilder>",
"title": "bevy_utils::hashbrown::HashMap<u32, alloc::string::String, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
@ -11065,6 +11228,19 @@
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<u32, bevy_render::color::Color, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
"$ref": "#/$defs/bevy_render::color::Color"
}
},
"isComponent": false,
"isResource": false,
"short_name": "HashMap<u32, Color, DefaultHashBuilder>",
"title": "bevy_utils::hashbrown::HashMap<u32, bevy_render::color::Color, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::smallvec::SmallVec<[bevy_ecs::entity::Entity; 8]>": {
"isComponent": false,
"isResource": false,

View File

@ -145,11 +145,11 @@ impl Plugin for GamePlugin {
.add_systems(Update, play_animations)
.add_systems(Update, react_to_animation_markers)
.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
/*.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
.add_systems(
Update,
exit_game.run_if(on_timer(Duration::from_secs_f32(0.5))),
) // shut down the app after this time
) // shut down the app after this time*/
;
}
}

View File

@ -152,6 +152,43 @@ impl MaterialExtension for MyExtension {
}
}
use bevy::utils::HashMap;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct HashmapTestSimple {
pub named_animations: HashMap<String, String>,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct HashmapTestStringFloat {
pub named_animations: HashMap<String, f32>,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct HashmapTestIntString {
pub named_animations: HashMap<u32, String>,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct HashmapTestIntColor {
pub inner: HashMap<u32, Color>,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct HashmapTestStringColor {
pub inner: HashMap<String, Color>,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct HashmapTestStringColorFlat(HashMap<String, Color>);
pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin {
fn build(&self, app: &mut App) {
@ -185,6 +222,20 @@ impl Plugin for ComponentsTestPlugin {
.register_type::<Vec<f32>>()
// .register_type::<AAAAddedCOMPONENT>()
.register_type::<AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut>()
.register_type::<HashMap<String, String>>()
.register_type::<HashmapTestSimple>()
.register_type::<HashMap<String, f32>>()
.register_type::<HashmapTestStringFloat>()
.register_type::<HashMap<u32, String>>()
.register_type::<HashmapTestIntString>()
.register_type::<HashMap<u32, Color>>()
.register_type::<HashmapTestIntColor>()
.register_type::<HashMap<String, Color>>()
.register_type::<HashmapTestStringColor>()
.register_type::<HashmapTestStringColorFlat>()
.add_plugins(MaterialPlugin::<
ExtendedMaterial<StandardMaterial, MyExtension>,
>::default());

View File

@ -206,4 +206,14 @@ UI:
- [x] delete => remove
- [x] clean up reloading of registry settings
- [x] clean up file watcher
- [x] clean up file watcher
=========================================
Restructuring of storage of components
- [x] marking of invalid root propgroups/components should be based on long name
- [ ] overhaul & check each prop group type's use of short names => long names
- [ ] lists
- [ ] in conversions from propgroups
component_name = definition["short_name"]

View File

@ -22,9 +22,9 @@ from .registry.registry import ComponentsRegistry,MissingBevyType
from .registry.operators import (COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT, COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT, OT_select_component_name_to_replace, OT_select_object, ReloadRegistryOperator, OT_OpenFilebrowser)
from .registry.ui import (BEVY_COMPONENTS_PT_Configuration, BEVY_COMPONENTS_PT_AdvancedToolsPanel, BEVY_COMPONENTS_PT_MissingTypesPanel, MISSING_TYPES_UL_List)
from .components.metadata import (ComponentMetadata, ComponentsMeta, ensure_metadata_for_all_objects)
from .propGroups.prop_groups import (generate_propertyGroups_for_components)
from .components.metadata import (ComponentMetadata, ComponentsMeta)
from .components.lists import GENERIC_LIST_OT_actions, Generic_LIST_OT_AddItem, Generic_LIST_OT_RemoveItem, Generic_LIST_OT_SelectItem
from .components.maps import GENERIC_MAP_OT_actions
from .components.definitions_list import (ComponentDefinitionsList, ClearComponentDefinitionsList)
from .components.ui import (BEVY_COMPONENTS_PT_ComponentsPanel)
@ -123,7 +123,9 @@ classes = [
Generic_LIST_OT_SelectItem,
Generic_LIST_OT_AddItem,
Generic_LIST_OT_RemoveItem,
GENERIC_LIST_OT_actions
GENERIC_LIST_OT_actions,
GENERIC_MAP_OT_actions
]
from bpy.app.handlers import persistent

View File

@ -47,12 +47,12 @@ class ComponentDefinitionsList(bpy.types.PropertyGroup):
description="list",
# items argument required to initialize, just filled with empty values
items = add_component_to_ui_list,
)
) # type: ignore
filter: StringProperty(
name="component filter",
description="filter for the components list",
options={'TEXTEDIT_UPDATE'}
)
) # type: ignore
class ClearComponentDefinitionsList(bpy.types.Operator):

View File

@ -10,19 +10,19 @@ class Generic_LIST_OT_AddItem(Operator):
property_group_path: StringProperty(
name="property group path",
description="",
)
) # type: ignore
component_name: StringProperty(
name="component name",
description="",
)
) # type: ignore
def execute(self, context):
print("")
object = context.object
# information is stored in component meta
components_in_object = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
component_meta = next(filter(lambda component: component["long_name"] == self.component_name, components_in_object), None)
propertyGroup = component_meta
for path_item in json.loads(self.property_group_path):
@ -47,19 +47,19 @@ class Generic_LIST_OT_RemoveItem(Operator):
property_group_path: StringProperty(
name="property group path",
description="",
)
) # type: ignore
component_name: StringProperty(
name="component name",
description="",
)
) # type: ignore
def execute(self, context):
print("remove from list", context.object)
object = context.object
# information is stored in component meta
components_in_object = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
component_meta = next(filter(lambda component: component["long_name"] == self.component_name, components_in_object), None)
propertyGroup = component_meta
for path_item in json.loads(self.property_group_path):
@ -81,14 +81,14 @@ class Generic_LIST_OT_SelectItem(Operator):
property_group_path: StringProperty(
name="property group path",
description="",
)
) # type: ignore
component_name: StringProperty(
name="component name",
description="",
)
) # type: ignore
selection_index: IntProperty()
selection_index: IntProperty() # type: ignore
def execute(self, context):
print("select in list", context.object)
@ -96,7 +96,7 @@ class Generic_LIST_OT_SelectItem(Operator):
object = context.object
# information is stored in component meta
components_in_object = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
component_meta = next(filter(lambda component: component["long_name"] == self.component_name, components_in_object), None)
propertyGroup = component_meta
for path_item in json.loads(self.property_group_path):
@ -121,23 +121,23 @@ class GENERIC_LIST_OT_actions(Operator):
('UP', "Up", ""),
('DOWN', "Down", ""),
('REMOVE', "Remove", ""),
('ADD', "Add", "")))
('ADD', "Add", ""))) # type: ignore
property_group_path: StringProperty(
name="property group path",
description="",
)
) # type: ignore
component_name: StringProperty(
name="component name",
description="",
)
) # type: ignore
def invoke(self, context, event):
object = context.object
# information is stored in component meta
components_in_object = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
component_meta = next(filter(lambda component: component["long_name"] == self.component_name, components_in_object), None)
propertyGroup = component_meta
for path_item in json.loads(self.property_group_path):

View File

@ -0,0 +1,71 @@
import json
from bpy_types import Operator, UIList
from bpy.props import (StringProperty, EnumProperty, PointerProperty, FloatVectorProperty, IntProperty)
class GENERIC_MAP_OT_actions(Operator):
"""Move items up and down, add and remove"""
bl_idname = "generic_map.map_action"
bl_label = "List Actions"
bl_description = "Move items up and down, add and remove"
bl_options = {'REGISTER', 'UNDO'}
action: EnumProperty(
items=(
('UP', "Up", ""),
('DOWN', "Down", ""),
('REMOVE', "Remove", ""),
('ADD', "Add", ""))) # type: ignore
property_group_path: StringProperty(
name="property group path",
description="",
) # type: ignore
component_name: StringProperty(
name="component name",
description="",
) # type: ignore
def invoke(self, context, event):
object = context.object
# information is stored in component meta
components_in_object = object.components_meta.components
component_meta = next(filter(lambda component: component["long_name"] == self.component_name, components_in_object), None)
propertyGroup = component_meta
for path_item in json.loads(self.property_group_path):
propertyGroup = getattr(propertyGroup, path_item)
target_list = getattr(propertyGroup, "list")
index = getattr(propertyGroup, "list_index")
values_list = getattr(propertyGroup, "values_list")
values_index = getattr(propertyGroup, "values_list_index")
if self.action == 'DOWN' and index < len(target_list) - 1:
#item_next = scn.rule_list[index + 1].name
target_list.move(index, index + 1)
propertyGroup.list_index += 1
elif self.action == 'UP' and index >= 1:
#item_prev = scn.rule_list[index - 1].name
target_list.move(index, index - 1)
propertyGroup.list_index -= 1
elif self.action == 'REMOVE':
target_list.remove(index)
propertyGroup.list_index = min(max(0, index - 1), len(target_list) - 1)
if self.action == 'ADD':
key = target_list.add()
value = values_list.add()
propertyGroup.list_index = index + 1 # we use this to force the change detection
propertyGroup.values_index = index + 1 # we use this to force the change detection
#info = '"%s" added to list' % (item.name)
#self.report({'INFO'}, info)
return {"FINISHED"}

View File

@ -10,51 +10,51 @@ class ComponentMetadata(bpy.types.PropertyGroup):
name : bpy.props.StringProperty(
name = "name",
default = ""
)
) # type: ignore
long_name : bpy.props.StringProperty(
name = "long name",
default = ""
)
) # type: ignore
type_name : bpy.props.StringProperty(
name = "Type",
default = ""
)
) # type: ignore
values: bpy.props.StringProperty(
name = "Value",
default = ""
)
) # type: ignore
enabled: BoolProperty(
name="enabled",
description="component enabled",
default=True
)
) # type: ignore
invalid: BoolProperty(
name="invalid",
description="component is invalid, because of missing registration/ other issues",
default=False
)
) # type: ignore
invalid_details: StringProperty(
name="invalid details",
description="detailed information about why the component is invalid",
default=""
)
) # type: ignore
visible: BoolProperty( # REALLY dislike doing this for UI control, but ok hack for now
default=True
)
) # type: ignore
class ComponentsMeta(PropertyGroup):
infos_per_component: StringProperty(
name="infos per component",
description="component"
)
components: bpy.props.CollectionProperty(type = ComponentMetadata)
) # type: ignore
components: bpy.props.CollectionProperty(type = ComponentMetadata) # type: ignore
@classmethod
def register(cls):
@ -72,7 +72,9 @@ def get_component_metadata_by_short_name(object, short_name):
# remove no longer valid metadata from object
def cleanup_invalid_metadata(object):
bevy_components = json.loads(object['bevy_components']) if 'bevy_components' in object else {}
bevy_components = get_bevy_components(object)
if len(bevy_components.keys()) == 0: # no components, bail out
return
components_metadata = object.components_meta.components
to_remove = []
for index, component_meta in enumerate(components_metadata):
@ -93,6 +95,10 @@ def find_component_definition_from_short_name(short_name):
return registry.type_infos.get(long_name, None)
return None
def find_component_definition_from_long_name(long_name):
registry = bpy.context.window_manager.components_registry
return registry.type_infos.get(long_name, None)
# FIXME: feels a bit heavy duty, should only be done
# if the components panel is active ?
def ensure_metadata_for_all_objects():
@ -123,31 +129,45 @@ def do_object_custom_properties_have_missing_metadata(object):
return missing_metadata
# adds metadata to object only if it is missing
def add_metadata_to_components_without_metadata(object):
registry = bpy.context.window_manager.components_registry
for component_name in dict(object) :
if component_name == "components_meta":
continue
upsert_component_in_object(object, component_name, registry)
import json
def inject_component(object, long_name, value):
def upsert_bevy_component(object, long_name, value):
if not 'bevy_components' in object:
object['bevy_components'] = '{}'
previous = json.loads(object['bevy_components'])
previous[long_name] = value
object['bevy_components'] = json.dumps(previous)
bevy_components = json.loads(object['bevy_components'])
bevy_components[long_name] = value
object['bevy_components'] = json.dumps(bevy_components)
#object['bevy_components'][long_name] = value # Sigh, this does not work, hits Blender's 63 char length limit
def bla_component(object, long_name):
def remove_bevy_component(object, long_name):
if 'bevy_components' in object:
current = json.loads(object['bevy_components'])
del current[long_name]
object['bevy_components'] = json.dumps(current)
def get_bevy_components(object):
if 'bevy_components' in object:
bevy_components = json.loads(object['bevy_components'])
return bevy_components
return {}
def get_bevy_component_value_by_long_name(object, long_name):
bevy_components = get_bevy_components(object)
if len(bevy_components.keys()) == 0 :
return None
return bevy_components.get(long_name, None)
def is_bevy_component_in_object(object, long_name):
return get_bevy_component_value_by_long_name(object, long_name) is not None
# adds metadata to object only if it is missing
def add_metadata_to_components_without_metadata(object):
registry = bpy.context.window_manager.components_registry
for component_name in get_bevy_components(object) :
if component_name == "components_meta":
continue
upsert_component_in_object(object, component_name, registry)
# adds a component to an object (including metadata) using the provided component definition & optional value
def add_component_to_object(object, component_definition, value=None):
cleanup_invalid_metadata(object)
@ -158,7 +178,6 @@ def add_component_to_object(object, component_definition, value=None):
if not registry.has_type_infos():
raise Exception('registry type infos have not been loaded yet or are missing !')
definition = registry.type_infos[long_name]
print("HEAAAY", value)
# now we use our pre_generated property groups to set the initial value of our custom property
(_, propertyGroup) = upsert_component_in_object(object, long_name=long_name, registry=registry)
if value == None:
@ -170,7 +189,7 @@ def add_component_to_object(object, component_definition, value=None):
# object[short_name] = value
print("ADDING VAALUEEE", value)
inject_component(object, long_name, value)
upsert_bevy_component(object, long_name, value)
#ping_depsgraph_update(object)
@ -226,14 +245,14 @@ def copy_propertyGroup_values_to_another_object(source_object, target_object, co
if source_object == None or target_object == None or component_name == None:
raise Exception('missing input data, cannot copy component propertryGroup')
component_definition = find_component_definition_from_short_name(component_name)
short_name = component_definition["short_name"]
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
component_definition = find_component_definition_from_long_name(component_name)
long_name = component_name
property_group_name = registry.get_propertyGroupName_from_longName(long_name)
registry = bpy.context.window_manager.components_registry
source_components_metadata = source_object.components_meta.components
source_componentMeta = next(filter(lambda component: component["name"] == short_name, source_components_metadata), None)
source_componentMeta = next(filter(lambda component: component["long_name"] == long_name, source_components_metadata), None)
# matching component means we already have this type of component
source_propertyGroup = getattr(source_componentMeta, property_group_name)
@ -241,28 +260,27 @@ def copy_propertyGroup_values_to_another_object(source_object, target_object, co
(_, target_propertyGroup) = upsert_component_in_object(target_object, component_name, registry)
# add to object
value = property_group_value_to_custom_property_value(target_propertyGroup, component_definition, registry, None)
target_object[short_name] = value
upsert_bevy_component(target_object, long_name, value)
# copy the values over
for field_name in source_propertyGroup.field_names:
if field_name in source_propertyGroup:
target_propertyGroup[field_name] = source_propertyGroup[field_name]
apply_propertyGroup_values_to_object_customProperties(target_object)
ping_depsgraph_update(object)
# TODO: move to propgroups ?
def apply_propertyGroup_values_to_object_customProperties(object):
cleanup_invalid_metadata(object)
registry = bpy.context.window_manager.components_registry
for component_name in dict(object) :
if component_name == "components_meta":
continue
for component_name in get_bevy_components(object) :
"""if component_name == "components_meta":
continue"""
(_, propertyGroup) = upsert_component_in_object(object, component_name, registry)
component_definition = find_component_definition_from_short_name(component_name)
component_definition = find_component_definition_from_long_name(component_name)
if component_definition != None:
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object[component_name] = value
upsert_bevy_component(object=object, long_name=component_name, value=value)
# apply component value(s) to custom property of a single component
def apply_propertyGroup_values_to_object_customProperties_for_component(object, component_name):
@ -304,7 +322,7 @@ def apply_customProperty_values_to_object_propertyGroups(object):
# removes the given component from the object: removes both the custom property and the matching metadata from the object
def remove_component_from_object(object, component_name):
bla_component(object, component_name)
remove_bevy_component(object, component_name)
components_metadata = getattr(object, "components_meta", None)
if components_metadata == None:
@ -319,16 +337,14 @@ def remove_component_from_object(object, component_name):
break
for index in to_remove:
components_metadata.remove(index)
ping_depsgraph_update(object)
return True
def add_component_from_custom_property(object):
add_metadata_to_components_without_metadata(object)
apply_customProperty_values_to_object_propertyGroups(object)
ping_depsgraph_update(object)
def toggle_component(object, component_name):
components_in_object = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == component_name, components_in_object), None)
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_object), None)
if component_meta != None:
component_meta.visible = not component_meta.visible

View File

@ -4,7 +4,7 @@ import bpy
from bpy_types import Operator
from bpy.props import (StringProperty)
from .metadata import add_component_from_custom_property, add_component_to_object, add_metadata_to_components_without_metadata, apply_customProperty_values_to_object_propertyGroups, apply_propertyGroup_values_to_object_customProperties_for_component, copy_propertyGroup_values_to_another_object, find_component_definition_from_short_name, remove_component_from_object, toggle_component
from .metadata import add_component_from_custom_property, add_component_to_object, add_metadata_to_components_without_metadata, apply_customProperty_values_to_object_propertyGroups, apply_propertyGroup_values_to_object_customProperties_for_component, copy_propertyGroup_values_to_another_object, find_component_definition_from_short_name, get_bevy_component_value_by_long_name, get_bevy_components, is_bevy_component_in_object, remove_component_from_object, toggle_component
class AddComponentOperator(Operator):
"""Add Bevy component to object"""
@ -36,7 +36,7 @@ class CopyComponentOperator(Operator):
bl_options = {"UNDO"}
source_component_name: StringProperty(
name="source component_name",
name="source component_name (long)",
description="name of the component to copy",
) # type: ignore
@ -80,10 +80,10 @@ class PasteComponentOperator(Operator):
self.report({"ERROR"}, "The source object to copy a component from does not exist")
else:
component_name = context.window_manager.copied_source_component_name
if not component_name in source_object:
component_value = get_bevy_component_value_by_long_name(source_object, component_name)
if component_value is None:
self.report({"ERROR"}, "The source component to copy from does not exist")
else:
component_value = source_object[component_name]
print("pasting component to object: component name:", str(component_name), "component value:" + str(component_value))
print (context.object)
registry = context.window_manager.components_registry
@ -114,10 +114,15 @@ class RemoveComponentOperator(Operator):
else:
object = bpy.data.objects[self.object_name]
print("removing component ", self.component_name, "from object '"+object.name+"'")
if object is not None and 'bevy_components' in object and self.component_name in object['bevy_components']:
remove_component_from_object(object, self.component_name)
if object is not None and 'bevy_components' in object :
component_value = get_bevy_component_value_by_long_name(object, self.component_name)
if component_value is not None:
remove_component_from_object(object, self.component_name)
else :
self.report({"ERROR"}, "The component to remove ("+ self.component_name +") does not exist")
else:
self.report({"ERROR"}, "The object/ component to remove ("+ self.component_name +") does not exist")
self.report({"ERROR"}, "The object to remove ("+ self.component_name +") from does not exist")
return {'FINISHED'}
@ -145,7 +150,7 @@ class RemoveComponentFromAllObjectsOperator(Operator):
total = len(bpy.data.objects)
for index, object in enumerate(bpy.data.objects):
if len(object.keys()) > 0:
if object is not None and self.component_name in object:
if object is not None and is_bevy_component_in_object(object, self.component_name):
remove_component_from_object(object, self.component_name)
progress = index / total
@ -222,7 +227,7 @@ class OT_rename_component(Operator):
components_metadata = getattr(object, "components_meta", None)
if components_metadata:
components_metadata = components_metadata.components
component_meta = next(filter(lambda component: component["name"] == new_name, components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == new_name, components_metadata), None)
if component_meta:
component_meta.invalid = True
component_meta.invalid_details = "unknow issue when renaming/transforming component, please remove it & add it back again"
@ -240,7 +245,7 @@ class OT_rename_component(Operator):
components_metadata = getattr(object, "components_meta", None)
if components_metadata:
components_metadata = components_metadata.components
component_meta = next(filter(lambda component: component["name"] == new_name, components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == new_name, components_metadata), None)
if component_meta:
component_meta.invalid = True
component_meta.invalid_details = "wrong custom property value, overwrite them by changing the values in the ui or change them & regenerate"

View File

@ -8,6 +8,7 @@ from .operators import AddComponentOperator, CopyComponentOperator, Fix_Componen
def draw_propertyGroup( propertyGroup, layout, nesting =[], rootName=None):
is_enum = getattr(propertyGroup, "with_enum")
is_list = getattr(propertyGroup, "with_list")
is_map = getattr(propertyGroup, "with_map")
#nesting = nesting + [current_short_name] # we need this convoluted "nested path strings " workaround so that operators working on a given
# item in our components hierarchy can get the correct propertyGroup by STRINGS because of course, we cannot pass objects to operators...sigh
@ -83,6 +84,43 @@ def draw_propertyGroup( propertyGroup, layout, nesting =[], rootName=None):
op.component_name = rootName
op.property_group_path = json.dumps(nesting)
elif is_map:
keys_list = getattr(propertyGroup, "list")
values_list = getattr(propertyGroup, "values_list")
box = layout.box()
row = box.row()
row.label(text="key")
row.label(text="value")
#values_setter = getattr(propertyGroup, "values_setter")
# draw_propertyGroup(values_setter, row, nesting, rootName)
split = box.split(factor=0.9)
list_column, buttons_column = (split.column(),split.column())
list_column = list_column.box()
for index, item in enumerate(keys_list):
row = list_column.row()
#row.label(text=str(index))
draw_propertyGroup(item, row, nesting, rootName)
value = values_list[index]
draw_propertyGroup(value, row, nesting, rootName)
#various control buttons
buttons_column.separator()
row = buttons_column.row()
op = row.operator('generic_map.map_action', icon='ADD', text="")
op.action = 'ADD'
op.component_name = rootName
op.property_group_path = json.dumps(nesting)
row = buttons_column.row()
op = row.operator('generic_map.map_action', icon='REMOVE', text="")
op.action = 'REMOVE'
op.component_name = rootName
op.property_group_path = json.dumps(nesting)
else:
for fname in field_names:
subrow = layout.row()
@ -178,12 +216,12 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
# we fetch the matching ui property group
root_propertyGroup_name = registry.get_propertyGroupName_from_longName(component_name)
print("root_propertyGroup_name", root_propertyGroup_name)
print("component_meta", component_meta, component_invalid)
"""print("root_propertyGroup_name", root_propertyGroup_name)
print("component_meta", component_meta, component_invalid)"""
if root_propertyGroup_name:
propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
print("propertyGroup", propertyGroup)
"""print("propertyGroup", propertyGroup)"""
if propertyGroup:
# if the component has only 0 or 1 field names, display inline, otherwise change layout
single_field = len(propertyGroup.field_names) < 2

View File

@ -5,8 +5,8 @@ conversion_tables = {
"char": lambda value: '"'+value+'"',
"str": lambda value: '"'+value+'"',
"alloc::string::String": lambda value: '"'+value+'"',
"alloc::borrow::Cow<str>": lambda value: '"'+value+'"',
"alloc::string::String": lambda value: '"'+str(value)+'"',
"alloc::borrow::Cow<str>": lambda value: '"'+str(value)+'"',
"glam::Vec2": lambda value: "Vec2(x:"+str(value[0])+ ", y:"+str(value[1])+")",
"glam::DVec2": lambda value: "DVec2(x:"+str(value[0])+ ", y:"+str(value[1])+")",
@ -28,20 +28,22 @@ conversion_tables = {
#converts the value of a property group(no matter its complexity) into a single custom property value
# this is more or less a glorified "to_ron()" method (not quite but close to)
def property_group_value_to_custom_property_value(property_group, definition, registry, parent=None, value=None):
component_name = definition["short_name"]
component_name = definition["short_name"] # FIXME: we should operate based on long names
type_info = definition["typeInfo"] if "typeInfo" in definition else None
type_def = definition["type"] if "type" in definition else None
type_name = definition["title"]
is_value_type = type_name in conversion_tables
#print("computing custom property", component_name, type_info, type_def, type_name)
print("computing custom property: component name:", component_name, "type_info", type_info, "type_def", type_def, "type_name", type_name)
if is_value_type:
value = conversion_tables[type_name](value)
elif type_info == "Struct":
print("generating string for struct")
values = {}
if len(property_group.field_names) ==0:
value = '()'
else:
print("toto", type_def, definition, property_group)
for index, field_name in enumerate(property_group.field_names):
item_type_name = definition["properties"][field_name]["type"]["$ref"].replace("#/$defs/", "")
item_definition = registry.type_infos[item_type_name] if item_type_name in registry.type_infos else None
@ -74,8 +76,10 @@ def property_group_value_to_custom_property_value(property_group, definition, re
elif type_info == "TupleStruct":
values = {}
for index, field_name in enumerate(property_group.field_names):
#print("toto", index, definition["prefixItems"][index]["type"]["$ref"])
item_type_name = definition["prefixItems"][index]["type"]["$ref"].replace("#/$defs/", "")
item_definition = registry.type_infos[item_type_name] if item_type_name in registry.type_infos else None
# print("here", item_type_name, item_definition)
value = getattr(property_group, field_name)
is_property_group = isinstance(value, PropertyGroup)
@ -88,6 +92,7 @@ def property_group_value_to_custom_property_value(property_group, definition, re
value = tuple(e for e in list(values.values()))
elif type_info == "Enum":
# TODO: perhaps use a mapping of (long) component name to a short ID , like we do in get_propertyGroupName_from_longName
selected = getattr(property_group, component_name)
if type_def == "object":
@ -134,6 +139,38 @@ def property_group_value_to_custom_property_value(property_group, definition, re
else:
item_value = '""'
value.append(item_value)
elif type_info == "Map":
print("MAAAAAP", property_group)
keys_list = getattr(property_group, "list", {})
values_list = getattr(property_group, "values_list")
value = {}
for index, key in enumerate(keys_list):
# first get the keys
key_type_name = getattr(key, "type_name")
definition = registry.type_infos[key_type_name] if key_type_name in registry.type_infos else None
if definition != None:
key_value = property_group_value_to_custom_property_value(key, definition, registry, component_name, None)
if key_type_name.startswith("wrapper_"): #if we have a "fake" tupple for aka for value types, we need to remove one nested level
key_value = key_value[0]
else:
key_value = '""'
# and then the values
val = values_list[index]
value_type_name = getattr(val, "type_name")
definition = registry.type_infos[value_type_name] if value_type_name in registry.type_infos else None
print("value definition", definition)
if definition != None:
val_value = property_group_value_to_custom_property_value(val, definition, registry, component_name, None)
if value_type_name.startswith("wrapper_"): #if we have a "fake" tupple for aka for value types, we need to remove one nested level
val_value = val_value[0]
else:
val_value = '""'
value[key_value] = val_value
print("MAP VALUES", value)
else:
value = conversion_tables[type_name](value) if is_value_type else value
value = '""' if isinstance(value, PropertyGroup) else value
@ -145,7 +182,7 @@ def property_group_value_to_custom_property_value(property_group, definition, re
if parent == None:
value = str(value).replace("'", "")
value = value.replace(",)",")")
value = value.replace("{", "(").replace("}", ")")
value = value.replace("{", "(").replace("}", ")") # FIXME: deal with hashmaps
value = value.replace("True", "true").replace("False", "false")
return value

View File

@ -5,8 +5,9 @@ from . import process_structs
from . import process_tupples
from . import process_enum
from . import process_list
from . import process_map
def process_component(registry, definition, update, extras=None, nesting = []):
def process_component(registry, definition, update, extras=None, nesting = [], nesting_long_names = []):
component_name = definition['title']
short_name = definition["short_name"]
type_info = definition["typeInfo"] if "typeInfo" in definition else None
@ -18,6 +19,7 @@ def process_component(registry, definition, update, extras=None, nesting = []):
has_prefixItems = len(prefixItems) > 0
is_enum = type_info == "Enum"
is_list = type_info == "List"
is_map = type_info == "Map"
# print("processing", short_name, component_name, type_def, type_info)
@ -28,25 +30,30 @@ def process_component(registry, definition, update, extras=None, nesting = []):
with_items = False
with_enum = False
with_list = False
with_map = False
if has_properties:
__annotations__ = __annotations__ | process_structs.process_structs(registry, definition, properties, update, nesting)
__annotations__ = __annotations__ | process_structs.process_structs(registry, definition, properties, update, nesting, nesting_long_names)
with_properties = True
tupple_or_struct = "struct"
if has_prefixItems:
__annotations__ = __annotations__ | process_tupples.process_tupples(registry, definition, prefixItems, update, nesting)
__annotations__ = __annotations__ | process_tupples.process_tupples(registry, definition, prefixItems, update, nesting, nesting_long_names)
with_items = True
tupple_or_struct = "tupple"
if is_enum:
__annotations__ = __annotations__ | process_enum.process_enum(registry, definition, update, nesting)
__annotations__ = __annotations__ | process_enum.process_enum(registry, definition, update, nesting, nesting_long_names)
with_enum = True
if is_list:
__annotations__ = __annotations__ | process_list.process_list(registry, definition, update, nesting)
__annotations__ = __annotations__ | process_list.process_list(registry, definition, update, nesting, nesting_long_names)
with_list= True
if is_map:
__annotations__ = __annotations__ | process_map.process_map(registry, definition, update, nesting, nesting_long_names)
with_map = True
field_names = []
for a in __annotations__:
@ -56,7 +63,7 @@ def process_component(registry, definition, update, extras=None, nesting = []):
extras = extras if extras is not None else {
"type_name": component_name
}
root_component = nesting[0] if len(nesting) > 0 else component_name
root_component = nesting_long_names[0] if len(nesting_long_names) > 0 else component_name
# print("DONE:",short_name,"__annotations__", __annotations__)
# print("")
property_group_params = {
@ -64,7 +71,7 @@ def process_component(registry, definition, update, extras=None, nesting = []):
'__annotations__': __annotations__,
'tupple_or_struct': tupple_or_struct,
'field_names': field_names,
**dict(with_properties = with_properties, with_items= with_items, with_enum= with_enum, with_list= with_list, short_name= short_name),
**dict(with_properties = with_properties, with_items= with_items, with_enum= with_enum, with_list= with_list, with_map = with_map, short_name= short_name),
'root_component': root_component
}
#FIXME: YIKES, but have not found another way:
@ -74,7 +81,7 @@ def process_component(registry, definition, update, extras=None, nesting = []):
-BasicTest => the registration & update callback of this one overwrites the first "basicTest"
have not found a cleaner workaround so far
"""
property_group_name = registry.generate_propGroup_name(nesting, short_name, component_name)
property_group_name = registry.generate_propGroup_name(nesting, component_name)
(property_group_pointer, property_group_class) = property_group_from_infos(property_group_name, property_group_params)
# add our component propertyGroup to the registry
registry.register_component_propertyGroup(property_group_name, property_group_pointer)

View File

@ -1,13 +1,17 @@
from bpy.props import (StringProperty)
from . import process_component
def process_enum(registry, definition, update, nesting):
def process_enum(registry, definition, update, nesting, nesting_long_names):
blender_property_mapping = registry.blender_property_mapping
short_name = definition["short_name"]
long_name = definition["title"]
type_def = definition["type"] if "type" in definition else None
values = definition["oneOf"]
nesting = nesting + [short_name]
nesting_long_names = nesting_long_names = [long_name]
__annotations__ = {}
original_type_name = "enum"
@ -25,12 +29,12 @@ def process_enum(registry, definition, update, nesting):
if "prefixItems" in item:
#print("tupple variant in enum", short_name, item)
registry.add_custom_type(item_short_name, item)
(sub_component_group, _) = process_component.process_component(registry, item, update, {"nested": True}, nesting)
(sub_component_group, _) = process_component.process_component(registry, item, update, {"nested": True}, nesting, nesting_long_names)
additional_annotations[variant_name] = sub_component_group
elif "properties" in item:
#print("struct variant in enum", short_name, item)
registry.add_custom_type(item_short_name, item)
(sub_component_group, _) = process_component.process_component(registry, item, update, {"nested": True}, nesting)
(sub_component_group, _) = process_component.process_component(registry, item, update, {"nested": True}, nesting, nesting_long_names)
additional_annotations[variant_name] = sub_component_group
else: # for the cases where it's neither a tupple nor a structs: FIXME: not 100% sure of this
#print("other variant in enum", short_name)

View File

@ -2,12 +2,16 @@ from bpy.props import (StringProperty, IntProperty, CollectionProperty)
from .utils import generate_wrapper_propertyGroup
from . import process_component
def process_list(registry, definition, update, nesting=[]):
def process_list(registry, definition, update, nesting=[], nesting_long_names=[]):
value_types_defaults = registry.value_types_defaults
type_infos = registry.type_infos
short_name = definition["short_name"]
long_name = definition["title"]
ref_name = definition["items"]["type"]["$ref"].replace("#/$defs/", "")
nesting = nesting+[short_name]
nesting_long_names = nesting_long_names + [long_name]
item_definition = type_infos[ref_name]
item_long_name = item_definition["title"]
@ -17,12 +21,11 @@ def process_list(registry, definition, update, nesting=[]):
property_group_class = None
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !
if is_item_value_type:
property_group_class = generate_wrapper_propertyGroup(short_name, item_long_name, definition["items"]["type"]["$ref"],registry, update)
property_group_class = generate_wrapper_propertyGroup(long_name, item_long_name, definition["items"]["type"]["$ref"], registry, update)
else:
(_, list_content_group_class) = process_component.process_component(registry, item_definition, update, {"nested": True, "type_name": item_long_name}, nesting)
property_group_class = list_content_group_class
nesting = nesting+[short_name]
item_collection = CollectionProperty(type=property_group_class)
item_short_name = item_short_name if not is_item_value_type else "wrapper_" + item_short_name

View File

@ -0,0 +1,71 @@
from bpy.props import (StringProperty, IntProperty, CollectionProperty)
from .utils import generate_wrapper_propertyGroup
from . import process_component
def process_map(registry, definition, update, nesting=[], nesting_long_names=[]):
print("IS HASHMAP")
value_types_defaults = registry.value_types_defaults
type_infos = registry.type_infos
short_name = definition["short_name"]
long_name = definition["title"]
nesting = nesting + [short_name]
nesting_long_names = nesting_long_names + [long_name]
#ref_name = definition["items"]["type"]["$ref"].replace("#/$defs/", "")
value_ref_name = definition["additionalProperties"]["type"]["$ref"].replace("#/$defs/", "")
key_ref_name = long_name.split(',')[0].split('<')[1]# FIXME: hack !!!
key_type = ''
value_type = ''
print("infos", short_name, "long name", long_name)
print("value ref", value_ref_name, "key ref", key_ref_name)
#print("definition", definition)
if value_ref_name in type_infos:
value_definition = type_infos[value_ref_name]
original_type_name = value_definition["title"]
original_short_name = value_definition["short_name"]
is_value_value_type = original_type_name in value_types_defaults
definition_link = definition["additionalProperties"]["type"]["$ref"]#f"#/$defs/{value_ref_name}"
print("hashmap VALUE type", original_type_name)
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !
if is_value_value_type:
values_property_group_class = generate_wrapper_propertyGroup(long_name, original_type_name, definition_link, registry, update)
else:
(_, list_content_group_class) = process_component.process_component(registry, value_definition, update, {"nested": True, "type_name": original_type_name}, nesting)
values_property_group_class = list_content_group_class
values_collection = CollectionProperty(type=values_property_group_class)
if key_ref_name in type_infos:
key_definition = type_infos[key_ref_name]
original_type_name = key_definition["title"]
original_short_name = key_definition["short_name"]
is_key_value_type = original_type_name in value_types_defaults
definition_link = f"#/$defs/{key_ref_name}"
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !
if is_key_value_type:
keys_property_group_class = generate_wrapper_propertyGroup(long_name+'_values', original_type_name, definition_link, registry, update)
else:
(_, list_content_group_class) = process_component.process_component(registry, key_definition, update, {"nested": True, "type_name": original_type_name}, nesting)
keys_property_group_class = list_content_group_class
keys_collection = CollectionProperty(type=keys_property_group_class)
__annotations__ = {
"list": keys_collection,
"list_index": IntProperty(name = "Index for keys", default = 0, update=update),
"values_list": values_collection,
"values_list_index": IntProperty(name = "Index for values", default = 0, update=update),
}
'''
"values_setter": values_setter,
"values_list": values_collection,
"values_list_index": IntProperty(name = "Index for values", default = 0, update=update),
'''
return __annotations__

View File

@ -1,19 +1,24 @@
from bpy.props import (StringProperty)
from . import process_component
def process_structs(registry, definition, properties, update, nesting):
def process_structs(registry, definition, properties, update, nesting, nesting_long_names):
value_types_defaults = registry.value_types_defaults
blender_property_mapping = registry.blender_property_mapping
type_infos = registry.type_infos
long_name = definition["title"]
short_name = definition["short_name"]
__annotations__ = {}
default_values = {}
nesting = nesting + [short_name]
nesting_long_names = nesting_long_names + [long_name]
if short_name == "HashmapTestSimple":
print("Struct", short_name)
for property_name in properties.keys():
ref_name = properties[property_name]["type"]["$ref"].replace("#/$defs/", "")
if short_name == "HashmapTestSimple":
print("ref name", ref_name)
if ref_name in type_infos:
original = type_infos[ref_name]
original_type_name = original["title"]
@ -21,6 +26,9 @@ def process_structs(registry, definition, properties, update, nesting):
value = value_types_defaults[original_type_name] if is_value_type else None
default_values[property_name] = value
if short_name == "HashmapTestSimple":
print("original",original, original_type_name, is_value_type, value)
if is_value_type:
if original_type_name in blender_property_mapping:
blender_property_def = blender_property_mapping[original_type_name]
@ -33,7 +41,7 @@ def process_structs(registry, definition, properties, update, nesting):
__annotations__[property_name] = blender_property
else:
original_long_name = original["title"]
(sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "type_name": original_long_name}, nesting)
(sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "type_name": original_long_name}, nesting, nesting_long_names)
__annotations__[property_name] = sub_component_group
# if there are sub fields, add an attribute "sub_fields" possibly a pointer property ? or add a standard field to the type , that is stored under "attributes" and not __annotations (better)
else:
@ -41,6 +49,6 @@ def process_structs(registry, definition, properties, update, nesting):
__annotations__[property_name] = StringProperty(default="N/A")
registry.add_missing_typeInfo(ref_name)
# the root component also becomes invalid (in practice it is not always a component, but good enough)
registry.add_invalid_component(nesting[0])
registry.add_invalid_component(nesting_long_names[0])
return __annotations__

View File

@ -1,13 +1,15 @@
from bpy.props import (StringProperty)
from . import process_component
def process_tupples(registry, definition, prefixItems, update, nesting=[]):
def process_tupples(registry, definition, prefixItems, update, nesting=[], nesting_long_names=[]):
value_types_defaults = registry.value_types_defaults
blender_property_mapping = registry.blender_property_mapping
type_infos = registry.type_infos
long_name = definition["title"]
short_name = definition["short_name"]
nesting = nesting+[short_name]
nesting_long_names = nesting_long_names + [long_name]
__annotations__ = {}
default_values = []
@ -46,7 +48,7 @@ def process_tupples(registry, definition, prefixItems, update, nesting=[]):
__annotations__[property_name] = StringProperty(default="N/A")
registry.add_missing_typeInfo(ref_name)
# the root component also becomes invalid (in practice it is not always a component, but good enough)
registry.add_invalid_component(nesting[0])
registry.add_invalid_component(nesting_long_names[0])
return __annotations__

View File

@ -3,6 +3,7 @@ from .conversions_from_prop_group import property_group_value_to_custom_property
from .process_component import process_component
from .utils import update_calback_helper
import json
## main callback function, fired whenever any property changes, no matter the nesting level
def update_component(self, context, definition, component_name):
registry = bpy.context.window_manager.components_registry
@ -14,12 +15,17 @@ def update_component(self, context, definition, component_name):
print("")
print("update in component", component_name, self, "current_object", current_object.name)
components_in_object = current_object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == component_name, components_in_object), None)
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_object), None)
print("component_meta", component_meta)
if component_meta != None:
property_group_name = registry.get_propertyGroupName_from_shortName(component_name)
self = getattr(component_meta, property_group_name)
property_group_name = registry.get_propertyGroupName_from_longName(component_name)
property_group = getattr(component_meta, property_group_name)
# we use our helper to set the values
context.object[component_name] = property_group_value_to_custom_property_value(self, definition, registry, None)
object = context.object
previous = json.loads(object['bevy_components'])
previous[component_name] = property_group_value_to_custom_property_value(property_group, definition, registry, None)
object['bevy_components'] = json.dumps(previous)
def generate_propertyGroups_for_components():
@ -31,9 +37,8 @@ def generate_propertyGroups_for_components():
for component_name in type_infos:
definition = type_infos[component_name]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
root_property_name = short_name if is_component else None
root_property_name = component_name if is_component else None
process_component(registry, definition, update_calback_helper(definition, update_component, root_property_name), None, [])
# if we had to add any wrapper types on the fly, process them now

View File

@ -8,13 +8,16 @@ from bpy_types import PropertyGroup
# this helper creates a "fake"/wrapper property group that is NOT a real type in the registry
# usefull for things like value types in list items etc
def generate_wrapper_propertyGroup(short_name, item_long_name, definition, registry, update):
def generate_wrapper_propertyGroup(wrapped_type_long_name_name, item_long_name, definition_link, registry, update):
value_types_defaults = registry.value_types_defaults
blender_property_mapping = registry.blender_property_mapping
is_item_value_type = item_long_name in value_types_defaults
wrapper_name = "wrapper_" + short_name
wrapper_name = "wrapper_" + wrapped_type_long_name_name
print("WRAPPER NAME", wrapper_name)
# FIXME: this is not a correct generic value for hashmaps !!?
wrapper_definition = {
"isComponent": False,
"isResource": False,
@ -22,17 +25,21 @@ def generate_wrapper_propertyGroup(short_name, item_long_name, definition, regis
"prefixItems": [
{
"type": {
"$ref": definition
"$ref": definition_link
}
}
],
"short_name": wrapper_name,
"short_name": wrapper_name, # FIXME !!!
"title": wrapper_name,
"type": "array",
"typeInfo": "TupleStruct"
}
# we generate a very small 'hash' for the component name
property_group_name = registry.generate_propGroup_name(nesting=[], longName=wrapper_name)
registry.add_custom_type(wrapper_name, wrapper_definition)
blender_property = StringProperty(default="", update=update)
if item_long_name in blender_property_mapping:
value = value_types_defaults[item_long_name] if is_item_value_type else None
@ -51,10 +58,9 @@ def generate_wrapper_propertyGroup(short_name, item_long_name, definition, regis
'__annotations__': wrapper_annotations,
'tupple_or_struct': "tupple",
'field_names': ['0'],
**dict(with_properties = False, with_items= True, with_enum= False, with_list= False, short_name= wrapper_name, type_name=wrapper_name),
#'root_component': root_component
**dict(with_properties = False, with_items= True, with_enum= False, with_list= False, with_map =False, short_name=wrapper_name, type_name=wrapper_name),
}
property_group_class = type(wrapper_name, (PropertyGroup,), property_group_params)
property_group_class = type(property_group_name, (PropertyGroup,), property_group_params)
bpy.utils.register_class(property_group_class)
return property_group_class

View File

@ -317,6 +317,7 @@ class ComponentsRegistry(PropertyGroup):
self.type_infos[type_name] = self.custom_types_to_add[type_name]
self.custom_types_to_add.clear()
# add an invalid component to the list (long name)
def add_invalid_component(self, component_name):
self.invalid_components.append(component_name)
@ -335,7 +336,7 @@ class ComponentsRegistry(PropertyGroup):
long_names_to_propgroup_names = {}
# generate propGroup name from nesting level & shortName: each shortName + nesting is unique
def generate_propGroup_name(self, nesting, shortName, longName):
def generate_propGroup_name(self, nesting, longName):
#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)
@ -350,7 +351,7 @@ class ComponentsRegistry(PropertyGroup):
self.short_names_to_propgroup_names[key] = propGroupName"""
# FIXME:
key = str(nesting) + longName if len(nesting) > 0 else longName
self.long_names_to_propgroup_names[longName] = propGroupName
self.long_names_to_propgroup_names[key] = propGroupName
return propGroupName
def get_propertyGroupName_from_shortName(self, shortName):
@ -358,7 +359,9 @@ class ComponentsRegistry(PropertyGroup):
def get_propertyGroupName_from_longName(self, longName):
return self.long_names_to_propgroup_names.get(longName, None)
def long_name_to_key():
pass
###########

View File

@ -39,7 +39,7 @@ def test_components_should_generate_correct_custom_properties(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
added_components.append(component_type)
custom_property_values[short_name] = object[short_name]
@ -87,7 +87,7 @@ def test_components_should_generate_correct_custom_properties_with_randomized_va
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
component_values_shuffler(seed= 10, property_group=propertyGroup, definition=definition, registry=registry)
@ -136,7 +136,7 @@ def test_components_should_generate_correct_propertyGroup_values_from_custom_pro
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
added_components.append(component_type)
# randomise values
@ -195,7 +195,7 @@ def test_remove_components(setup_data):
object = bpy.context.object
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
# print("propertyGroup", propertyGroup, propertyGroup.field_names)
added_components.append(component_type)
@ -231,7 +231,7 @@ def test_copy_paste_components(setup_data):
object = context.object
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
setattr(propertyGroup, propertyGroup.field_names[0], 25.0)
@ -246,7 +246,7 @@ def test_copy_paste_components(setup_data):
# change name
new_cube.name = "TargetCube"
target_components_metadata = new_cube.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
# first check that there is no component currently
assert component_meta == None
@ -255,7 +255,7 @@ def test_copy_paste_components(setup_data):
paste_component_operator()
target_components_metadata = new_cube.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
# now after pasting to the new object, it should have component meta
assert component_meta != None

View File

@ -16,7 +16,7 @@ def test_blend(setup_data):
object = bpy.context.object
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)

View File

@ -9,7 +9,7 @@ from .setup_data import setup_data
# small helpers
def get_component_metadata(object, component_name):
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == component_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == component_name, target_components_metadata), None)
return component_meta
def get_component_propGroup(registry, component_name, component_meta):

View File

@ -19,7 +19,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
@ -38,7 +38,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
@ -57,7 +57,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
@ -75,7 +75,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
@ -95,7 +95,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
@ -113,7 +113,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
@ -130,7 +130,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
@ -148,7 +148,7 @@ def test_shuffler(setup_data):
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]

View File

@ -5,9 +5,6 @@ from ..helpers.helpers_scenes import (get_scenes, )
# IF collection_instances_combine_mode is not 'split' check for each scene if any object in changes_per_scene has an instance in the scene
def changed_object_in_scene(scene_name, changes_per_scene, blueprints_data, collection_instances_combine_mode):
# Embed / EmbedExternal
"""if collection_instances_combine_mode == "Split": # 1 => Embed
return False"""
blueprints_from_objects = blueprints_data.blueprints_from_objects
blueprint_instances_in_scene = blueprints_data.blueprint_instances_per_main_scene.get(scene_name, None)
@ -34,7 +31,6 @@ def changed_object_in_scene(scene_name, changes_per_scene, blueprints_data, coll
# changes => list of changed objects (regardless of wether they have been changed in main scene or in lib scene)
# wich of those objects are blueprint instances
# we need a list of changed objects that are blueprint instances
return level_needs_export
return False

View File

@ -178,10 +178,10 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences):#, ExportHelper):
# if there were no setting before, it is new, we need export
changed = False
if previous_auto_settings == None:
print("previous settings missing, exporting")
#print("previous settings missing, exporting")
changed = True
elif previous_gltf_settings == None:
print("previous gltf settings missing, exporting")
#print("previous gltf settings missing, exporting")
previous_gltf_settings = bpy.data.texts.new(".gltf_auto_export_gltf_settings_previous")
previous_gltf_settings.write(json.dumps({}))
if current_gltf_settings == None:
@ -194,18 +194,17 @@ 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
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_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)
print("gltf_settings_changed", gltf_settings_changed)"""
changed = auto_settings_changed or gltf_settings_changed
# now write the current settings to the "previous settings"
if current_auto_settings != None:
print("writing 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
@ -294,15 +293,9 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences):#, ExportHelper):
changes_per_scene[scene][object_name] = bpy.data.objects[object_name]
bubble_up_changes(bpy.data.objects[object_name], changes_per_scene[scene])
# now bubble up for instances & parents
previous_stored.clear()
previous_stored.write(json.dumps(current))
print("changes per scene alternative", changes_per_scene)
return changes_per_scene