feat(bevy_components): set of fixes & improvements (#128)

* fixed issue with "reload registry" not clearing previous data
 * added watcher/ poll system to automatically updated the registry & components list when the registry file has been changed
* BREAKING CHANGE ! changed internal representation of components, incompatible with v0.1, breaks UI values.
* added buttons to regenerate UI to account for/fix the above and to offer the ability to regenerate UI values from custom property values
* lots of cleanups
* added tests 
* closes #127 
* closes #124 
* closes #121 
* closes #130
This commit is contained in:
Mark Moissette 2024-02-18 17:14:31 +01:00 committed by GitHub
parent 0083295a4d
commit 20b6fa6077
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 13296 additions and 210 deletions

View File

@ -8,16 +8,15 @@ members = [
"examples/common/",
"examples/bevy_gltf_components/basic/",
"examples/bevy_gltf_blueprints/basic/",
"examples/bevy_gltf_blueprints/basic_xpbd_physics/",
"examples/bevy_gltf_blueprints/animation/",
"examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles",
"examples/bevy_gltf_blueprints/materials/",
"examples/bevy_gltf_save_load/basic/",
"examples/bevy_registry_export/basic"
"examples/bevy_registry_export/basic",
"testing/bevy_registry_export/basic"
]
resolver = "2"

View File

@ -0,0 +1,16 @@
[package]
name = "bevy_bevy_registry_export_basic_testing"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy="0.12"
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_registry_export = { path = "../../../crates/bevy_registry_export" }
bevy_gltf_worlflow_examples_common = { path = "../../../examples/common" }
bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
bevy_asset_loader = { version = "0.18", features = ["standard_dynamic_assets" ]}
bevy_editor_pls = { version = "0.6" }
rand = "0.8.5"

View File

@ -0,0 +1,15 @@
# Bevy registry export example/demo
This example showcases
* the use of the bevy_registry_export crate to extract all components & types information into a json file.
* That file is then used by the [Blender addon](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/bevy_components) to create Uis for each component,
to be able to add & edit Bevy components easilly in Blender !
## Running this example
```
cargo run --features bevy/dynamic_linking
```
Running the example also regenerates the registry.json file.

View File

@ -0,0 +1 @@
({})

View File

@ -0,0 +1,6 @@
({
"world":File (path: "models/World.glb"),
"models": Folder (
path: "models/library",
),
})

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::*;
use bevy_registry_export::*;
pub struct CorePlugin;
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
ExportRegistryPlugin {
save_path: "assets/registry.json".into(),
..Default::default()
},
BlueprintsPlugin {
legacy_mode: false,
library_folder: "models/library".into(),
format: GltfFormat::GLB,
aabbs: true,
..Default::default()
},
));
}
}

View File

@ -0,0 +1,128 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
use bevy_gltf_worlflow_examples_common::{assets::GameAssets, GameState, InAppRunning};
use bevy_rapier3d::prelude::Velocity;
use rand::Rng;
pub fn setup_game(
mut commands: Commands,
game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>,
) {
println!("setting up all stuff");
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.2,
});
// here we actually spawn our game world/level
commands.spawn((
SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models
.get(game_assets.world.id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default()
},
bevy::prelude::Name::from("world"),
GameWorldTag,
InAppRunning,
));
next_game_state.set(GameState::InGame)
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct UnregisteredComponent;
pub fn spawn_test(
keycode: Res<Input<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::T) {
let world = game_world.single_mut();
let world = world.1[0];
let mut rng = rand::thread_rng();
let range = 5.5;
let x: f32 = rng.gen_range(-range..range);
let y: f32 = rng.gen_range(-range..range);
let mut rng = rand::thread_rng();
let range = 0.8;
let vel_x: f32 = rng.gen_range(-range..range);
let vel_y: f32 = rng.gen_range(2.0..2.5);
let vel_z: f32 = rng.gen_range(-range..range);
let name_index: u64 = rng.gen();
let new_entity = commands
.spawn((
BluePrintBundle {
blueprint: BlueprintName("Health_Pickup".to_string()),
..Default::default()
},
bevy::prelude::Name::from(format!("test{}", name_index)),
// BlueprintName("Health_Pickup".to_string()),
// SpawnHere,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
Velocity {
linvel: Vec3::new(vel_x, vel_y, vel_z),
angvel: Vec3::new(0.0, 0.0, 0.0),
},
))
.id();
commands.entity(world).add_child(new_entity);
}
}
pub fn spawn_test_unregisted_components(
keycode: Res<Input<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::U) {
let world = game_world.single_mut();
let world = world.1[0];
let mut rng = rand::thread_rng();
let range = 5.5;
let x: f32 = rng.gen_range(-range..range);
let y: f32 = rng.gen_range(-range..range);
let mut rng = rand::thread_rng();
let range = 0.8;
let vel_x: f32 = rng.gen_range(-range..range);
let vel_y: f32 = rng.gen_range(2.0..2.5);
let vel_z: f32 = rng.gen_range(-range..range);
let name_index: u64 = rng.gen();
let new_entity = commands
.spawn((
BluePrintBundle {
blueprint: BlueprintName("Health_Pickup".to_string()),
..Default::default()
},
bevy::prelude::Name::from(format!("test{}", name_index)),
// BlueprintName("Health_Pickup".to_string()),
// SpawnHere,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
Velocity {
linvel: Vec3::new(vel_x, vel_y, vel_z),
angvel: Vec3::new(0.0, 0.0, 0.0),
},
UnregisteredComponent,
))
.id();
commands.entity(world).add_child(new_entity);
}
}

View File

@ -0,0 +1,112 @@
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common::{AppState, InMainMenu};
pub fn setup_main_menu(mut commands: Commands) {
commands.spawn((Camera2dBundle::default(), InMainMenu));
commands.spawn((
TextBundle::from_section(
"SOME GAME TITLE !!",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(100.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu,
));
commands.spawn((
TextBundle::from_section(
"New Game (press Enter to start, press T once the game is started for demo spawning)",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(200.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu,
));
/*
commands.spawn((
TextBundle::from_section(
"Load Game",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(250.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu
));
commands.spawn((
TextBundle::from_section(
"Exit Game",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(300.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu
));*/
}
pub fn teardown_main_menu(bla: Query<Entity, With<InMainMenu>>, mut commands: Commands) {
for bli in bla.iter() {
commands.entity(bli).despawn_recursive();
}
}
pub fn main_menu(
keycode: Res<Input<KeyCode>>,
mut next_app_state: ResMut<NextState<AppState>>,
// mut next_game_state: ResMut<NextState<GameState>>,
// mut save_requested_events: EventWriter<SaveRequest>,
// mut load_requested_events: EventWriter<LoadRequest>,
) {
if keycode.just_pressed(KeyCode::Return) {
next_app_state.set(AppState::AppLoading);
// next_game_state.set(GameState::None);
}
if keycode.just_pressed(KeyCode::L) {
next_app_state.set(AppState::AppLoading);
// load_requested_events.send(LoadRequest { path: "toto".into() })
}
if keycode.just_pressed(KeyCode::S) {
// save_requested_events.send(SaveRequest { path: "toto".into() })
}
}

View File

@ -0,0 +1,22 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common::{AppState, GameState};
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(spawn_test, spawn_test_unregisted_components).run_if(in_state(GameState::InGame)),
)
.add_systems(OnEnter(AppState::MenuRunning), setup_main_menu)
.add_systems(OnExit(AppState::MenuRunning), teardown_main_menu)
.add_systems(Update, main_menu.run_if(in_state(AppState::MenuRunning)))
.add_systems(OnEnter(AppState::AppRunning), setup_game);
}
}

View File

@ -0,0 +1,27 @@
use bevy::prelude::*;
use bevy_editor_pls::prelude::*;
use bevy_gltf_worlflow_examples_common::CommonPlugin;
mod core;
use crate::core::*;
mod game;
use game::*;
mod test_components;
use test_components::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(AssetPlugin::default()),
// editor
EditorPlugin::default(),
// our custom plugins
CommonPlugin,
CorePlugin, // reusable plugins
GamePlugin, // specific to our game
ComponentsTestPlugin, // Showcases different type of components /structs
))
.run();
}

View File

@ -0,0 +1,190 @@
use bevy::{
pbr::{ExtendedMaterial, MaterialExtension},
prelude::*,
render::render_resource::*,
};
use std::ops::Range;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct UnitTest;
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
struct TupleTestF32(f32);
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
struct TupleTestU64(u64);
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
pub struct TupleTestStr(String);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleTest2(f32, u64, String);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleTestBool(bool);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleVec2(Vec2);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleVec3(Vec3);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleVec(Vec<String>);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleVecF32F32(Vec<(f32, f32)>);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleTestColor(Color);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct BasicTest {
a: f32,
b: u64,
c: String,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub enum EnumTest {
Metal,
Wood,
Rock,
Cloth,
Squishy,
#[default]
None,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct NestingTestLevel2 {
text: String,
enable: bool,
enum_inner: EnumTest,
color: TupleTestColor,
toggle: TupleTestBool,
basic: BasicTest,
pub nested: NestingTestLevel3,
colors_list: VecOfColors,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct NestingTestLevel3 {
vec: TupleVec3,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct NestedTupleStuff(f32, u64, NestingTestLevel2);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub enum EnumComplex {
Float(f32),
Wood(String),
Vec(BasicTest),
SomeThing,
StructLike {
a: f32,
b: u32,
c: String,
},
#[default]
None,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct VecOfVec3s2(Vec<TupleVec3>);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct VecOfColors(Vec<Color>);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct AAAAddedCOMPONENT;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut;
/* fn toto(){
let bla:core::ops::Range<f32> = Range { start: 0.1, end: 5.0};
} */
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct VecOfF32s(Vec<f32>);
// test for extended materials
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
struct MyExtension {
// We need to ensure that the bindings of the base material and the extension do not conflict,
// so we start from binding slot 100, leaving slots 0-99 for the base material.
#[uniform(100)]
quantize_steps: u32,
}
impl MaterialExtension for MyExtension {
fn fragment_shader() -> ShaderRef {
"shaders/extended_material.wgsl".into()
}
fn deferred_fragment_shader() -> ShaderRef {
"shaders/extended_material.wgsl".into()
}
}
pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin {
fn build(&self, app: &mut App) {
app.register_type::<BasicTest>()
.register_type::<UnitTest>()
.register_type::<TupleTestF32>()
.register_type::<TupleTestU64>()
.register_type::<TupleTestStr>()
.register_type::<TupleTestBool>()
.register_type::<TupleTest2>()
.register_type::<TupleVec2>()
.register_type::<TupleVec3>()
.register_type::<EnumTest>()
.register_type::<TupleTestColor>()
.register_type::<TupleVec>()
.register_type::<Vec<String>>()
.register_type::<NestingTestLevel2>()
.register_type::<NestingTestLevel3>()
.register_type::<NestedTupleStuff>()
.register_type::<EnumComplex>()
.register_type::<VecOfVec3s2>()
.register_type::<TupleVecF32F32>()
.register_type::<(f32, f32)>()
.register_type::<Vec<(f32, f32)>>()
.register_type::<Vec<TupleVec3>>()
.register_type::<Vec<Color>>()
.register_type::<VecOfColors>()
.register_type::<Range<f32>>()
.register_type::<VecOfF32s>()
.register_type::<Vec<f32>>()
// .register_type::<AAAAddedCOMPONENT>()
.register_type::<AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut>()
.add_plugins(MaterialPlugin::<
ExtendedMaterial<StandardMaterial, MyExtension>,
>::default());
}
}

View File

@ -15,6 +15,9 @@ of your Bevy components you get a nicely packed custom_property to use with ...
> Important:
the tooling is still in the early stages, even if it is feature complete : use with caution!.
> IMPORTANT !! if you have previously used v0.1 , v0.2 had a breaking change, please see [this](#regenerate-ui-values) section on how to upgrade your data to v0.2.\
This problem should not be present going forward
## Installation:
* grab the latest release zip file from the releases tab (choose the bevy_components releases !)
@ -95,6 +98,24 @@ it will automatically update the value of the corresponding custom property
![edit component](./docs/edit_component2.png)
### Create components from custom properties
- IF you have a valid component type and the correct corresponding RON string in the custom_property value (this button will not appear if not), this add-on can automatically
generate the corresponding component for you:
- Fill/check your custom property (here for Aabb)
![generate_components 2](./docs/generate_components2.png)
- click on the button
![generate_components](./docs/generate_components.png)
-voila !
![generate_components 3](./docs/generate_components3.png)
### copy & pasting
- you can also copy & paste components between objects
@ -161,21 +182,39 @@ It will add the component to the select object
## advanced configuration
- there are also additional QOL features, that you should not need most of the time
### registry file polling
- "update custom properties of current object" : will go over **all components** that you have defined for the **currently selected object**, and re-generate the
* by default, the add-on will check for changes in your registry file every second, and refresh the UI accordingly
* you can set the polling frequency or turn it off if you do not want auto-refresh
![registry file polling](./docs/registry_polling.png)
### regenerate custom property values
- "update custom properties of current object" : will go over **all components** that you have defined for the **currently selected object**, and re-generate the
corresponding custom property values
![update custom properties](./docs/other_options.png)
- "update custom properties of ALL objects" : same as above but it will do so for the **ALL objects in your blend file** (so can be slow!), and re-generate the
- "update custom properties of ALL objects" : same as above but it will do so for the **ALL objects in your blend file** (so can be slow!), and re-generate the
corresponding custom property values
![update custom properties for all](./docs/other_options2.png)
### regenerate UI values
- since v0.2, you have the option to regenerate (for the selected object or all objects, as above) to regenerate your UI values from the custom property values
![update UI FROM custom properties](./docs/update_ui_from_custom_properties.png)
> IMPORTANT !! use this if you have previously used v0.1 , as v0.2 had a breaking change, that makes it **necessary** to use this **once** to upgrade the UI data
## Additional important information
@ -191,6 +230,11 @@ Please see the documentation of those crates for more information.
you can find an example [here](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/)
## Known issues & limitations:
* **Range** data (ie ```Range<f32>``` etc) are not handled at this time (issue seems to be on the Bevy side)
* **Entity** structs are always set to 0 (setting entity values on the Blender side at this time does not make much sense anyway)
## License
This tool, all its code, contents & assets is Dual-licensed under either of

View File

@ -133,7 +133,77 @@ UI:
- [x] update version
- [x] add ability to set legacy mode for bevy_gltf_components ?
- [ ] release all versions
- [ ] update main documentation, add compatibility version grid
- [x] release all versions
- [x] update main documentation, add compatibility version grid
## Phase 2
- [x] fix handling of long component names
- [x] fix nesting level handling issue for new system : ie basic component DOES NOT work, but nestedLevel2 does
- add goddam tests !
- [ ] verify some weird prop => custom property values (Calculated Clip for example)
- [x] fix "reload registry" not clearing all previous data (reloading registry does not seem to account for added/removed components in the registry )
- add file watcher for registry
- [x] have the watcher work as expected
- [ ] add handling of removed registry file
- [ ] clear & reset handler when the file browser for the registry is used
- [ ] re-enable watcher
- tests
clear && pytest -svv --blender-executable <path_to_blender>/blender/blender-4.0.2-linux-x64/blender
- [x] load registry
- just check list of components vs lists in registry
- [x] try adding all components
- [x] select an object
- [x] call the add_component operator
- [x] change params
- use field names + component definitions to set values
- [x] find a way to shuffle params of ALL components based on a reliable, repeatable seed
- [x] test propgroup values => custom property values
- [x] test custom property value => propgroup value
- check if all went well
- [x] fix issues with incorect custom_property generation
- [x] fix issue with object variants for enums
- [ ] add handling for core::ops::Range<f32> & other ranges
- [x] add handling for alloc::borrow::Cow<str>
- [x] add handling of isize
- [x] indirection level
- currently
- short_name +_"ui => direct lookup
- problem : max 64 chars for propertyGroupNames
- possible solution
- propertyGroupName storage: simple , incremented INT (call it propGroupId for ex)
- lookup shortName => propGroupId
- do a first pass, by replacing manual propGroupNames creation with a function
- in a second pass, replace the innards
- add button to regenerate cutom prop values from custom properties (allows us to sidestep any future issues with internals changing)
- [x] fix lists
- [x] fix enums (see Clusterconfig)
- [x] need an example with one tupple one struct
- [x] projection
- [ ] additionalmassproperties
- [x] fix tupleStructs (see TupleVecF32F32) => always the same problem of having us pre-parse data without knowing what we have inside
- find a way to only split by level 0 (highest level) nesting "," seperators, ignoring any level of nesting until we dig one level deeper
- solve nesting level use issues
- [x] remove metadata when deleting components
- [x] add try catch around custom_prop => propGroup
- [x] enhance the GenerateComponent_From_custom_property_Operator to use the new system to actually generate the stuff
- coherence in operators:
- component_name vs component_type
- [x] delete => remove
- [x] clean up reloading of registry settings
- [x] clean up file watcher

View File

@ -1,7 +1,7 @@
bl_info = {
"name": "bevy_components",
"author": "kaosigh",
"version": (0, 1, 0),
"version": (0, 2, 0),
"blender": (3, 4, 0),
"location": "VIEW_3D",
"description": "UI to help create Bevy blueprints and components",
@ -16,13 +16,13 @@ from bpy.props import (StringProperty)
from .helpers import load_settings
from .blueprints import CreateBlueprintOperator
from .components.operators import CopyComponentOperator, DeleteComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, AddComponentOperator, Toggle_ComponentVisibility
from .components.operators import CopyComponentOperator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, AddComponentOperator, Toggle_ComponentVisibility
from .registry.registry import ComponentsRegistry,MissingBevyType
from .registry.operators import (COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT, ReloadRegistryOperator, OT_OpenFilebrowser)
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, ReloadRegistryOperator, OT_OpenFilebrowser)
from .registry.ui import (BEVY_COMPONENTS_PT_Configuration, BEVY_COMPONENTS_PT_MissingTypesPanel, MISSING_TYPES_UL_List)
from .components.metadata import (ComponentInfos, ComponentsMeta, ensure_metadata_for_all_objects)
from .components.metadata import (ComponentMetadata, ComponentsMeta, ensure_metadata_for_all_objects)
from .propGroups.prop_groups import (generate_propertyGroups_for_components)
from .components.lists import GENERIC_LIST_OT_actions, Generic_LIST_OT_AddItem, Generic_LIST_OT_RemoveItem, Generic_LIST_OT_SelectItem
from .components.definitions_list import (ComponentDefinitionsList, ClearComponentDefinitionsList)
@ -86,14 +86,14 @@ classes = [
AddComponentOperator,
CopyComponentOperator,
PasteComponentOperator,
DeleteComponentOperator,
RemoveComponentOperator,
GenerateComponent_From_custom_property_Operator,
Toggle_ComponentVisibility,
ComponentDefinitionsList,
ClearComponentDefinitionsList,
ComponentInfos,
ComponentMetadata,
ComponentsMeta,
MissingBevyType,
ComponentsRegistry,
@ -102,6 +102,9 @@ classes = [
ReloadRegistryOperator,
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,
BEVY_COMPONENTS_PT_MainPanel,
BEVY_COMPONENTS_PT_ComponentsPanel,
@ -119,19 +122,14 @@ from bpy.app.handlers import persistent
@persistent
def post_load(file_name):
print("post load", file_name)
registry = bpy.context.window_manager.components_registry
registry.schemaPath = load_settings(registry.settings_save_path)["schemaPath"]
registry.load_schema()
generate_propertyGroups_for_components()
ensure_metadata_for_all_objects()
if registry != None:
registry.load_settings()
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.WindowManager.blueprint_name = StringProperty()
bpy.app.handlers.load_post.append(post_load)
def unregister():

View File

@ -1,9 +1,10 @@
import bpy
from bpy.props import (StringProperty, BoolProperty, PointerProperty)
from bpy_types import (PropertyGroup)
from ..propGroups.conversions import property_group_value_from_custom_property_value, property_group_value_to_custom_property_value
from ..propGroups.conversions_from_prop_group import property_group_value_to_custom_property_value
from ..propGroups.conversions_to_prop_group import property_group_value_from_custom_property_value
class ComponentInfos(bpy.types.PropertyGroup):
class ComponentMetadata(bpy.types.PropertyGroup):
name : bpy.props.StringProperty(
name = "name",
default = ""
@ -51,7 +52,7 @@ class ComponentsMeta(PropertyGroup):
name="infos per component",
description="component"
)
components: bpy.props.CollectionProperty(type = ComponentInfos)
components: bpy.props.CollectionProperty(type = ComponentMetadata)
@classmethod
def register(cls):
@ -131,12 +132,12 @@ def add_metadata_to_components_without_metadata(object):
def add_component_to_object(object, component_definition, value=None):
cleanup_invalid_metadata(object)
if object is not None:
print("add_component_to_object", component_definition)
# print("add_component_to_object", component_definition)
long_name = component_definition["title"]
short_name = component_definition["short_name"]
registry = bpy.context.window_manager.components_registry
if registry.type_infos == None:
raise Exception('registry type infos have not been loaded yet or ar missing !')
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]
# now we use our pre_generated property groups to set the initial value of our custom property
(_, propertyGroup) = upsert_component_in_object(object, component_name=short_name, registry=registry)
@ -157,7 +158,7 @@ def upsert_component_in_object(object, component_name, registry):
if component_definition != None:
short_name = component_definition["short_name"]
long_name = component_definition["title"]
property_group_name = short_name+"_ui"
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
propertyGroup = None
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
@ -175,7 +176,7 @@ def upsert_component_in_object(object, component_name, registry):
if property_group_name in registry.component_propertyGroups:
# we have found a matching property_group, so try to inject it
# now inject property group
setattr(ComponentInfos, property_group_name, registry.component_propertyGroups[property_group_name]) # FIXME: not ideal as all ComponentInfos get the propGroup, but have not found a way to assign it per instance
setattr(ComponentMetadata, property_group_name, registry.component_propertyGroups[property_group_name]) # FIXME: not ideal as all ComponentMetadata get the propGroup, but have not found a way to assign it per instance
propertyGroup = getattr(component_meta, property_group_name, None)
# now deal with property groups details
@ -196,13 +197,14 @@ def upsert_component_in_object(object, component_name, registry):
return(None, None)
def copy_propertyGroup_values_to_another_object(source_object, target_object, component_name):
def copy_propertyGroup_values_to_another_object(source_object, target_object, component_name, registry):
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 = short_name+"_ui"
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
registry = bpy.context.window_manager.components_registry
source_components_metadata = source_object.components_meta.components
@ -234,3 +236,46 @@ def apply_propertyGroup_values_to_object_customProperties(object):
if component_definition != None:
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object[component_name] = value
def apply_customProperty_values_to_object_propertyGroups(object):
print("apply custom properties to ", object.name)
registry = bpy.context.window_manager.components_registry
for component_name in dict(object) :
if component_name == "components_meta":
continue
component_definition = find_component_definition_from_short_name(component_name)
if component_definition != None:
property_group_name = registry.get_propertyGroupName_from_shortName(component_name)
components_metadata = object.components_meta.components
source_componentMeta = next(filter(lambda component: component["name"] == component_name, components_metadata), None)
# matching component means we already have this type of component
propertyGroup = getattr(source_componentMeta, property_group_name, None)
customProperty_value = object[component_name]
#value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object["__disable__update"] = True # disable update callback while we set the values of the propertyGroup "tree" (as a propertyGroup can contain other propertyGroups)
property_group_value_from_custom_property_value(propertyGroup, component_definition, registry, customProperty_value)
del object["__disable__update"]
# 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):
del object[component_name]
components_metadata = getattr(object, "components_meta", None)
if components_metadata == None:
return False
components_metadata = components_metadata.components
to_remove = []
for index, component_meta in enumerate(components_metadata):
short_name = component_meta.name
if short_name == component_name:
to_remove.append(index)
break
for index in to_remove:
components_metadata.remove(index)
return True

View File

@ -3,22 +3,22 @@ import json
import bpy
from bpy_types import Operator
from bpy.props import (StringProperty)
from .metadata import add_component_to_object, add_metadata_to_components_without_metadata, copy_propertyGroup_values_to_another_object, find_component_definition_from_short_name
from .metadata import add_component_to_object, add_metadata_to_components_without_metadata, apply_customProperty_values_to_object_propertyGroups, copy_propertyGroup_values_to_another_object, find_component_definition_from_short_name, remove_component_from_object
class AddComponentOperator(Operator):
"""Add component to blueprint"""
bl_idname = "object.addblueprint_to_component"
bl_idname = "object.add_bevy_component"
bl_label = "Add component to blueprint Operator"
bl_options = {"UNDO"}
component_type: StringProperty(
name="component_type",
description="component type to add",
)
) # type: ignore
def execute(self, context):
print("adding component to blueprint", self.component_type)
object = context.object
print("adding component ", self.component_type, "to object '"+object.name+"'")
has_component_type = self.component_type != ""
if has_component_type and object != None:
@ -30,19 +30,19 @@ class AddComponentOperator(Operator):
class CopyComponentOperator(Operator):
"""Copy component from blueprint"""
bl_idname = "object.copy_component"
bl_idname = "object.copy_bevy_component"
bl_label = "Copy component Operator"
bl_options = {"UNDO"}
source_component_name: StringProperty(
name="source component_name",
description="name of the component to copy",
)
) # type: ignore
source_object_name: StringProperty(
name="source object name",
description="name of the object to copy the component from",
)
) # type: ignore
@classmethod
def register(cls):
@ -67,7 +67,7 @@ class CopyComponentOperator(Operator):
class PasteComponentOperator(Operator):
"""Paste component to blueprint"""
bl_idname = "object.paste_component"
bl_idname = "object.paste_bevy_component"
bl_label = "Paste component to blueprint Operator"
bl_options = {"UNDO"}
@ -80,67 +80,79 @@ class PasteComponentOperator(Operator):
else:
component_name = context.window_manager.copied_source_component_name
if not component_name in source_object:
self.report({"ERROR"}, "The source component to copy a component from does not exist")
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)
copy_propertyGroup_values_to_another_object(source_object, context.object, component_name)
registry = context.window_manager.components_registry
copy_propertyGroup_values_to_another_object(source_object, context.object, component_name, registry)
return {'FINISHED'}
class DeleteComponentOperator(Operator):
class RemoveComponentOperator(Operator):
"""Delete component from blueprint"""
bl_idname = "object.delete_component"
bl_idname = "object.remove_bevy_component"
bl_label = "Delete component from blueprint Operator"
bl_options = {"UNDO"}
component_name: StringProperty(
name="component name",
description="component to delete",
)
) # type: ignore
def execute(self, context):
object = context.object
print("removing component ", self.component_name, "from object '"+object.name+"'")
if object is not None and self.component_name in object:
del object[self.component_name]
remove_component_from_object(object, self.component_name)
else:
self.report({"ERROR"}, "The object/ component to remove does not exist")
self.report({"ERROR"}, "The object/ component to remove ("+ self.component_name +") does not exist")
return {'FINISHED'}
class GenerateComponent_From_custom_property_Operator(Operator):
"""generate components from custom property"""
bl_idname = "object.generate_component"
bl_idname = "object.generate_bevy_component_from_custom_property"
bl_label = "Generate component from custom_property Operator"
bl_options = {"UNDO"}
component_name: StringProperty(
name="component name",
description="component to generate custom properties for",
)
) # type: ignore
def execute(self, context):
object = context.object
add_metadata_to_components_without_metadata(object)
error = False
try:
add_metadata_to_components_without_metadata(object)
apply_customProperty_values_to_object_propertyGroups(object)
except Exception as error:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure
error = True
self.report({'ERROR'}, "Failed to update propertyGroup values from custom property: Error:" + str(error))
if not error:
self.report({'INFO'}, "Sucessfully generated UI values for custom properties for selected object")
return {'FINISHED'}
class Toggle_ComponentVisibility(Operator):
"""toggles components visibility"""
bl_idname = "object.toggle_component_visibility"
bl_idname = "object.toggle_bevy_component_visibility"
bl_label = "Toggle component visibility"
bl_options = {"UNDO"}
component_name: StringProperty(
name="component name",
description="component to toggle",
)
) # type: ignore
def execute(self, context):
object = context.object

View File

@ -1,12 +1,11 @@
import json
import bpy
from .metadata import do_object_custom_properties_have_missing_metadata
from .operators import AddComponentOperator, CopyComponentOperator, DeleteComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, Toggle_ComponentVisibility
from .operators import AddComponentOperator, CopyComponentOperator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, Toggle_ComponentVisibility
def draw_propertyGroup( propertyGroup, layout, nesting =[], rootName=None):
is_enum = getattr(propertyGroup, "with_enum")
is_list = getattr(propertyGroup, "with_list")
#current_short_name = getattr(propertyGroup, "short_name", "") + "_ui"
#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
@ -119,7 +118,7 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
# we get & load our component registry
registry = bpy.context.window_manager.components_registry
available_components = bpy.context.window_manager.components_list
registry_has_type_infos = registry.has_type_infos()
if object is not None:
row = layout.row(align=True)
@ -137,12 +136,12 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
# paste components
row = layout.row(align=True)
row.operator(PasteComponentOperator.bl_idname, text="Paste component ("+bpy.context.window_manager.copied_source_component_name+")", icon="PASTEDOWN")
row.enabled = registry.type_infos != None and context.window_manager.copied_source_object != ''
row.enabled = registry_has_type_infos and context.window_manager.copied_source_object != ''
layout.separator()
# upgrate custom props to components
upgradeable_customProperties = registry.type_infos != None and do_object_custom_properties_have_missing_metadata(context.object)
upgradeable_customProperties = registry.has_type_infos() and do_object_custom_properties_have_missing_metadata(context.object)
if upgradeable_customProperties:
row = layout.row(align=True)
op = row.operator(GenerateComponent_From_custom_property_Operator.bl_idname, text="generate components from custom properties" , icon="LOOP_FORWARDS")
@ -172,28 +171,29 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
row.label(text=component_name)
# we fetch the matching ui property group
root_propertyGroup_name = component_name+"_ui"
propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
if propertyGroup:
# if the component has only 0 or 1 field names, display inline, otherwise change layout
single_field = len(propertyGroup.field_names) < 2
prop_group_location = box.row(align=True).column()
if single_field:
prop_group_location = row.column(align=True)#.split(factor=0.9)#layout.row(align=False)
if component_visible:
if component_invalid:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
prop_group_location.label(text=error_message)
draw_propertyGroup(propertyGroup, prop_group_location, [root_propertyGroup_name], component_name)
else :
row.label(text="details hidden, click on toggle to display")
else:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
row.label(text=error_message)
root_propertyGroup_name = registry.get_propertyGroupName_from_shortName(component_name)
if root_propertyGroup_name:
propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
if propertyGroup:
# if the component has only 0 or 1 field names, display inline, otherwise change layout
single_field = len(propertyGroup.field_names) < 2
prop_group_location = box.row(align=True).column()
if single_field:
prop_group_location = row.column(align=True)#.split(factor=0.9)#layout.row(align=False)
if component_visible:
if component_invalid:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
prop_group_location.label(text=error_message)
draw_propertyGroup(propertyGroup, prop_group_location, [root_propertyGroup_name], component_name)
else :
row.label(text="details hidden, click on toggle to display")
else:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
row.label(text=error_message)
# "footer" with additional controls
op = row.operator(DeleteComponentOperator.bl_idname, text="", icon="X")
op = row.operator(RemoveComponentOperator.bl_idname, text="", icon="X")
op.component_name = component_name
row.separator()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -18,7 +18,6 @@ def make_empty(name, location, rotation, scale, collection):
#bpy.context.view_layer.update()
return empty_obj
#".gltf_auto_export_settings"
def upsert_settings(name, data):
stored_settings = bpy.data.texts[name] if name in bpy.data.texts else bpy.data.texts.new(name)
stored_settings.clear()

View File

@ -1,7 +1,5 @@
import json
from bpy_types import PropertyGroup
conversion_tables = {
"bool": lambda value: value,
@ -99,9 +97,9 @@ def property_group_value_to_custom_property_value(property_group, definition, re
value = getattr(property_group, variant_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
value = property_group_value_to_custom_property_value(child_property_group, variant_definition, registry, parent=component_name, value=value)
value = selected + str(value,)
value = selected + str(value,) #"{}{},".format(selected ,value)
elif "properties" in variant_definition:
value = getattr(property_group, variant_name)
is_property_group = isinstance(value, PropertyGroup)
@ -110,8 +108,14 @@ def property_group_value_to_custom_property_value(property_group, definition, re
value = property_group_value_to_custom_property_value(child_property_group, variant_definition, registry, parent=component_name, value=value)
value = selected + str(value,)
else:
print("basic enum stuff")
value = selected # here the value of the enum is just the name of the variant
value = getattr(property_group, variant_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
if child_property_group:
value = property_group_value_to_custom_property_value(child_property_group, variant_definition, registry, parent=component_name, value=value)
value = selected + str(value,)
else:
value = selected # here the value of the enum is just the name of the variant
else:
value = selected
@ -131,9 +135,13 @@ def property_group_value_to_custom_property_value(property_group, definition, re
value.append(item_value)
else:
value = conversion_tables[type_name](value) if is_value_type else value
value = '""' if isinstance(value, PropertyGroup) else value
#print("VALUE", value, type(value))
#print("generating custom property value", value, type(value))
if isinstance(value, str):
value = value.replace("'", "")
if parent == None:
value = str(value).replace("'", "")
value = value.replace(",)",")")
@ -141,81 +149,3 @@ def property_group_value_to_custom_property_value(property_group, definition, re
value = value.replace("True", "true").replace("False", "false")
return value
import re
#converts the value of a single custom property into a value (values) of a property group
def property_group_value_from_custom_property_value(property_group, definition, registry, custom_property_value):
#print(" ")
#print("setting property group value", property_group, definition, custom_property_value)
type_infos = registry.type_infos
value_types_defaults = registry.value_types_defaults
print("custom_property_value", custom_property_value)
def parse_field(item, property_group, definition, field_name):
type_info = definition["typeInfo"] if "typeInfo" in definition else None
type_def = definition["type"] if "type" in definition else None
properties = definition["properties"] if "properties" in definition else {}
prefixItems = definition["prefixItems"] if "prefixItems" in definition else []
has_properties = len(properties.keys()) > 0
has_prefixItems = len(prefixItems) > 0
is_enum = type_info == "Enum"
is_list = type_info == "List"
is_value_type = type_def in value_types_defaults
print("parsing field", item, "type infos", type_info, "type_def", type_def)
if type_info == "Struct":
print("is object")
for field_name in property_group.field_names:
print("field name", field_name)
# sub field
if isinstance(getattr(property_group, field_name), PropertyGroup):
sub_prop_group = getattr(property_group, field_name)
ref_name = properties[field_name]["type"]["$ref"].replace("#/$defs/", "")
sub_definition = type_infos[ref_name]
parse_field(item[field_name], sub_prop_group, sub_definition, field_name)
else:
setattr(property_group, field_name, item[field_name])
if has_prefixItems:
if len(property_group.field_names) == 1:
setattr(property_group, "0", item) # FIXME: not ideal
else:
for field_name in property_group.field_names:
setattr(property_group, field_name, item)
if is_enum:
if type_def == "object":
regexp = re.search('(^[^\(]+)(\((.*)\))', item)
chosen_variant = regexp.group(1)
chosen_variant_value = regexp.group(3).replace("'", '"').replace("(", "[").replace(")","]")
chosen_variant_value = json.loads(chosen_variant_value)
# first set chosen selection
field_name = property_group.field_names[0]
setattr(property_group, field_name, chosen_variant)
# thenlook for the information about the matching variant
sub_definition= None
for variant in definition["oneOf"]:
if variant["title"] == chosen_variant:
ref_name = variant["prefixItems"][0]["type"]["$ref"].replace("#/$defs/", "")
sub_definition = type_infos[ref_name]
break
variant_name = "variant_"+chosen_variant
if isinstance(getattr(property_group, variant_name), PropertyGroup):
sub_prop_group = getattr(property_group, variant_name)
parse_field(chosen_variant_value, sub_prop_group, sub_definition, variant_name)
else:
setattr(property_group, variant_name, chosen_variant_value)
else:
field_name = property_group.field_names[0]
setattr(property_group, field_name, item)
if is_list:
print("is list")
if is_value_type:
print("is value type")
try:
parse_field(custom_property_value, property_group, definition, None)
except Exception as error:
print("failed to parse raw custom property data", error)

View File

@ -0,0 +1,320 @@
from bpy_types import PropertyGroup
import re
def parse_struct_string(string, start_nesting=0):
#print("processing struct string", string, "start_nesting", start_nesting)
fields = {}
buff = []
current_fieldName = None
nesting_level = 0
start_offset = 0
end_offset = 0
for index, char in enumerate(string):
buff.append(char)
if char == "," and nesting_level == start_nesting:
#print("first case", end_offset)
end_offset = index
end_offset = len(string) if end_offset == 0 else end_offset
val = "".join(string[start_offset:end_offset])
fields[current_fieldName] = val.strip()
start_offset = index + 1
#print("done with field name", current_fieldName, "value", fields[current_fieldName])
if char == "[" or char == "(":
nesting_level += 1
if nesting_level == start_nesting:
start_offset = index + 1
#print("nesting & setting start offset", start_offset)
#print("nesting down", nesting_level)
if char == "]" or char == ")" :
#print("nesting up", nesting_level)
if nesting_level == start_nesting:
end_offset = index
#print("unesting & setting end offset", end_offset)
nesting_level -= 1
if char == ":" and nesting_level == start_nesting:
end_offset = index
fieldName = "".join(string[start_offset:end_offset])
current_fieldName = fieldName.strip()
start_offset = index + 1
end_offset = 0 #hack
#print("starting field name", fieldName, "index", index)
buff = []
end_offset = len(string) if end_offset == 0 else end_offset
#print("final start and end offset", start_offset, end_offset, "total length", len(string))
val = "".join(string[start_offset:end_offset])
fields[current_fieldName] = val.strip()
#print("done with all fields", fields)
return fields
def parse_tuplestruct_string(string, start_nesting=0):
#print("processing tuppleStruct", string, "start_nesting", start_nesting)
fields = []
buff = []
nesting_level = 0
field_index = 0
start_offset = 0
end_offset = 0
# todo: strip all stuff before start_nesting
for index, char in enumerate(string):
buff.append(char)
if char == "," and nesting_level == start_nesting:
end_offset = index
end_offset = len(string) if end_offset == 0 else end_offset
val = "".join(string[start_offset:end_offset])
fields.append(val.strip())
field_index += 1
#print("start and end offset", start_offset, end_offset, "total length", len(string))
#print("done with field name", field_index, "value", fields)
start_offset = index + 1
end_offset = 0 # hack
if char == "[" or char == "(":
nesting_level += 1
if nesting_level == start_nesting:
start_offset = index + 1
#print("nesting & setting start offset", start_offset)
#print("nesting down", nesting_level)
if char == "]" or char == ")" :
if nesting_level == start_nesting:
end_offset = index
#print("unesting & setting end offset", end_offset)
#print("nesting up", nesting_level)
nesting_level -= 1
end_offset = len(string) if end_offset == 0 else end_offset
#print("final start and end offset", start_offset, end_offset, "total length", len(string))
val = "".join(string[start_offset:end_offset]) #if end_offset != 0 else buff)
fields.append(val.strip())
fields = list(filter(lambda entry: entry != '', fields))
#print("done with all fields", fields)
return fields
def parse_vec2(value, caster, typeName):
parsed = parse_struct_string(value.replace(typeName,"").replace("(", "").replace(")","") )
return [caster(parsed['x']), caster(parsed['y'])]
def parse_vec3(value, caster, typeName):
parsed = parse_struct_string(value.replace(typeName,"").replace("(", "").replace(")","") )
return [caster(parsed['x']), caster(parsed['y']), caster(parsed['z'])]
def parse_vec4(value, caster, typeName):
parsed = parse_struct_string(value.replace(typeName,"").replace("(", "").replace(")","") )
return [caster(parsed['x']), caster(parsed['y']), caster(parsed['z']), caster(parsed['w'])]
def parse_color(value, caster, typeName):
parsed = parse_struct_string(value.replace(typeName,"").replace("(", "").replace(")","") )
return [caster(parsed['red']), caster(parsed['green']), caster(parsed['blue']), caster(parsed['alpha'])]
def to_int(input):
return int(float(input))
type_mappings = {
"bool": lambda value: True if value == "true" else False,
"u8": lambda value: int(value),
"u16": lambda value: int(value),
"u32": lambda value: int(value),
"u64": lambda value: int(value),
"u128": lambda value: int(value),
"u64": lambda value: int(value),
"usize": lambda value: int(value),
"i8": lambda value: int(value),
"i16": lambda value: int(value),
"i32": lambda value: int(value),
"i64": lambda value: int(value),
"i128": lambda value: int(value),
"isize": lambda value: int(value),
'f32': lambda value: float(value),
'f64': lambda value: float(value),
"glam::Vec2": lambda value: parse_vec2(value, float, "Vec2"),
"glam::DVec2": lambda value: parse_vec2(value, float, "DVec2"),
"glam::UVec2": lambda value: parse_vec2(value, to_int, "UVec2"),
'glam::Vec3': lambda value: parse_vec3(value, float, "Vec3"),
"glam::Vec3A": lambda value: parse_vec3(value, float, "Vec3A"),
"glam::UVec3": lambda value: parse_vec3(value, to_int, "UVec3"),
"glam::Vec4": lambda value: parse_vec4(value, float, "Vec4"),
"glam::DVec4": lambda value: parse_vec4(value, float, "DVec4"),
"glam::UVec4": lambda value: parse_vec4(value, to_int, "UVec4"),
"glam::Quat": lambda value: parse_vec4(value, float, "Quat"),
'alloc::string::String': lambda value: str(value.replace('"', "")),
'bevy_render::color::Color': lambda value: parse_color(value, float, "Rgba"),
'bevy_ecs::Entity': lambda value: int(value),
}
def is_def_value_type(definition, registry):
if definition == None:
return True
value_types_defaults = registry.value_types_defaults
type_name = definition["title"]
is_value_type = type_name in value_types_defaults
return is_value_type
#converts the value of a single custom property into a value (values) of a property group
def property_group_value_from_custom_property_value(property_group, definition, registry, value, nesting = []):
value_types_defaults = registry.value_types_defaults
type_info = definition["typeInfo"] if "typeInfo" in definition else None
type_def = definition["type"] if "type" in definition else None
properties = definition["properties"] if "properties" in definition else {}
prefixItems = definition["prefixItems"] if "prefixItems" in definition else []
has_properties = len(properties.keys()) > 0
has_prefixItems = len(prefixItems) > 0
is_enum = type_info == "Enum"
is_list = type_info == "List"
type_name = definition["title"]
#is_value_type = type_def in value_types_defaults or type_name in value_types_defaults
is_value_type = type_name in value_types_defaults
nesting = nesting + [definition["short_name"]]
"""print(" ")
print("raw value", value, "nesting", nesting)
print("nesting", len(nesting))
print("definition", definition)"""
if is_value_type:
value = value.replace("(", "").replace(")", "")# FIXME: temporary, incoherent use of nesting levels between parse_tuplestruct_string & parse_struct_string
value = type_mappings[type_name](value) if type_name in type_mappings else value
return value
elif type_info == "Struct":
if len(property_group.field_names) != 0 :
custom_property_values = parse_struct_string(value, start_nesting=1 if value.startswith("(") else 0)
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
custom_prop_value = custom_property_values[field_name]
#print("field name", field_name, "value", custom_prop_value)
propGroup_value = getattr(property_group, field_name)
is_property_group = isinstance(propGroup_value, PropertyGroup)
child_property_group = propGroup_value if is_property_group else None
if item_definition != None:
custom_prop_value = property_group_value_from_custom_property_value(child_property_group, item_definition, registry, value=custom_prop_value, nesting=nesting)
else:
custom_prop_value = custom_prop_value
if is_def_value_type(item_definition, registry):
setattr(property_group , field_name, custom_prop_value)
else:
pass
#print("struct with zero fields")
elif type_info == "Tuple":
custom_property_values = parse_tuplestruct_string(value, start_nesting=1 if len(nesting) == 1 else 1)
for index, field_name in enumerate(property_group.field_names):
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
custom_property_value = custom_property_values[index]
propGroup_value = getattr(property_group, field_name)
is_property_group = isinstance(propGroup_value, PropertyGroup)
child_property_group = propGroup_value if is_property_group else None
if item_definition != None:
custom_property_value = property_group_value_from_custom_property_value(child_property_group, item_definition, registry, value=custom_property_value, nesting=nesting)
if is_def_value_type(item_definition, registry):
setattr(property_group , field_name, custom_property_value)
elif type_info == "TupleStruct":
custom_property_values = parse_tuplestruct_string(value, start_nesting=1 if len(nesting) == 1 else 0)
for index, field_name in enumerate(property_group.field_names):
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
custom_prop_value = custom_property_values[index]
value = getattr(property_group, field_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
if item_definition != None:
custom_prop_value = property_group_value_from_custom_property_value(child_property_group, item_definition, registry, value=custom_prop_value, nesting=nesting)
if is_def_value_type(item_definition, registry):
setattr(property_group , field_name, custom_prop_value)
elif type_info == "Enum":
field_names = property_group.field_names
if type_def == "object":
regexp = re.search('(^[^\(]+)(\((.*)\))', value)
try:
chosen_variant_raw = regexp.group(1)
chosen_variant_value = regexp.group(3)
chosen_variant_name = "variant_" + chosen_variant_raw
except:
chosen_variant_raw = value
chosen_variant_value = ""
chosen_variant_name = "variant_" + chosen_variant_raw
selection_index = property_group.field_names.index(chosen_variant_name)
variant_definition = definition["oneOf"][selection_index-1]
# first we set WHAT variant is selected
setattr(property_group, field_names[0], chosen_variant_raw)
# and then we set the value of the variant
if "prefixItems" in variant_definition:
value = getattr(property_group, chosen_variant_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
chosen_variant_value = "(" +chosen_variant_value +")" # needed to handle nesting correctly
value = property_group_value_from_custom_property_value(child_property_group, variant_definition, registry, value=chosen_variant_value, nesting=nesting)
elif "properties" in variant_definition:
value = getattr(property_group, chosen_variant_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
value = property_group_value_from_custom_property_value(child_property_group, variant_definition, registry, value=chosen_variant_value, nesting=nesting)
else:
chosen_variant_raw = value
setattr(property_group, field_names[0], chosen_variant_raw)
elif type_info == "List":
item_list = getattr(property_group, "list")
item_type_name = getattr(property_group, "type_name_short")
custom_property_values = parse_tuplestruct_string(value, start_nesting=2 if item_type_name.startswith("wrapper_") and value.startswith('(') else 1) # TODO : the additional check here is wrong, there is an issue somewhere in higher level stuff
# clear list first
item_list.clear()
#print("custom_property_values", custom_property_values, "value", value, "item_type_name", item_type_name)
for raw_value in custom_property_values:
new_entry = item_list.add()
item_type_name = getattr(new_entry, "type_name") # we get the REAL type name
definition = registry.type_infos[item_type_name] if item_type_name in registry.type_infos else None
if definition != None:
property_group_value_from_custom_property_value(new_entry, definition, registry, value=raw_value, nesting=nesting)
else:
try:
value = value.replace("(", "").replace(")", "")# FIXME: temporary, incoherent use of nesting levels between parse_tuplestruct_string & parse_struct_string
value = type_mappings[type_name](value) if type_name in type_mappings else value
return value
except:
pass

View File

@ -59,7 +59,6 @@ def process_component(registry, definition, update, extras=None, nesting = []):
root_component = nesting[0] if len(nesting) > 0 else component_name
# print("DONE:",short_name,"__annotations__", __annotations__)
# print("")
# property_group_name = short_name+"_ui"
property_group_params = {
**extras,
'__annotations__': __annotations__,
@ -75,8 +74,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 = str(hash(str(nesting))) + short_name+"_ui" if len(nesting) > 0 else short_name+"_ui"
property_group_name = registry.generate_propGroup_name(nesting, short_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

@ -7,7 +7,7 @@ def process_enum(registry, definition, update, nesting):
type_def = definition["type"] if "type" in definition else None
values = definition["oneOf"]
nesting = nesting+ [short_name]
nesting = nesting + [short_name]
__annotations__ = {}
original_type_name = "enum"

View File

@ -11,6 +11,7 @@ def process_list(registry, definition, update, nesting=[]):
item_definition = type_infos[ref_name]
item_long_name = item_definition["title"]
item_short_name = item_definition["short_name"]
is_item_value_type = item_long_name in value_types_defaults
property_group_class = None
@ -24,10 +25,11 @@ def process_list(registry, definition, update, nesting=[]):
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
__annotations__ = {
"list": item_collection,
"list_index": IntProperty(name = "Index for list", default = 0, update=update),
"type_name_short": StringProperty(default=short_name)
"type_name_short": StringProperty(default=item_short_name)
}
return __annotations__

View File

@ -1,5 +1,5 @@
import bpy
from .conversions import property_group_value_to_custom_property_value
from .conversions_from_prop_group import property_group_value_to_custom_property_value
from .process_component import process_component
from .utils import update_calback_helper
@ -8,21 +8,23 @@ def update_component(self, context, definition, component_name):
registry = bpy.context.window_manager.components_registry
current_object = bpy.context.object
update_disabled = current_object["__disable__update"] if "__disable__update" in current_object else False
update_disabled = registry.disable_all_object_updates or update_disabled # global settings
if update_disabled:
return
print("")
print("update in component", component_name, self)
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)
if component_meta != None:
self = getattr(component_meta, component_name+"_ui")
property_group_name = registry.get_propertyGroupName_from_shortName(component_name)
self = 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)
def generate_propertyGroups_for_components():
registry = bpy.context.window_manager.components_registry
if registry.type_infos == None:
if not registry.has_type_infos():
registry.load_type_infos()
type_infos = registry.type_infos
@ -33,6 +35,6 @@ def generate_propertyGroups_for_components():
is_component = definition['isComponent'] if "isComponent" in definition else False
root_property_name = short_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
registry.process_custom_types()

View File

@ -0,0 +1,3 @@
[pytest]
testpaths =
tests

View File

@ -5,8 +5,7 @@ from bpy.props import (StringProperty)
from bpy_extras.io_utils import ImportHelper
from ..helpers import upsert_settings
from ..components.metadata import apply_propertyGroup_values_to_object_customProperties, ensure_metadata_for_all_objects
from ..components.operators import GenerateComponent_From_custom_property_Operator
from ..components.metadata import apply_customProperty_values_to_object_propertyGroups, apply_propertyGroup_values_to_object_customProperties, ensure_metadata_for_all_objects
from ..propGroups.prop_groups import generate_propertyGroups_for_components
class ReloadRegistryOperator(Operator):
@ -18,7 +17,7 @@ class ReloadRegistryOperator(Operator):
component_type: StringProperty(
name="component_type",
description="component type to add",
)
) # type: ignore
def execute(self, context):
print("reload registry")
@ -28,7 +27,12 @@ class ReloadRegistryOperator(Operator):
print("")
print("")
ensure_metadata_for_all_objects()
#add_metadata_to_components_without_metadata(context.object)
# now force refresh the ui
for area in context.screen.areas:
for region in area.regions:
if region.type == "UI":
region.tag_redraw()
return {'FINISHED'}
@ -47,7 +51,7 @@ class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL(Operator):
return {'FINISHED'}
class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT(Operator):
"""Apply registry to CURRENT object: update the custom property values of all objects based on their definition, if any"""
"""Apply registry to CURRENT object: update the custom property values of current object based on their definition, if any"""
bl_idname = "object.refresh_custom_properties_current"
bl_label = "Apply Registry to current object"
bl_options = {"UNDO"}
@ -57,6 +61,53 @@ class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT(Operator):
object = context.object
apply_propertyGroup_values_to_object_customProperties(object)
return {'FINISHED'}
class COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT(Operator):
"""Update UI values from custom properties to CURRENT object"""
bl_idname = "object.refresh_ui_from_custom_properties_current"
bl_label = "Apply custom_properties to current object"
bl_options = {"UNDO"}
def execute(self, context):
print("apply custom properties to current object")
object = context.object
error = False
try:
apply_customProperty_values_to_object_propertyGroups(object)
except Exception as error:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure
error = True
self.report({'ERROR'}, "Failed to update propertyGroup values from custom property: Error:" + str(error))
if not error:
self.report({'INFO'}, "Sucessfully generated UI values for custom properties for selected object")
return {'FINISHED'}
class COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL(Operator):
"""Update UI values from custom properties to ALL object"""
bl_idname = "object.refresh_ui_from_custom_properties_all"
bl_label = "Apply custom_properties to all objects"
bl_options = {"UNDO"}
def execute(self, context):
print("apply custom properties to all object")
bpy.context.window_manager.components_registry.disable_all_object_updates = True
errors = []
for object in bpy.data.objects:
try:
apply_customProperty_values_to_object_propertyGroups(object)
except Exception as error:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure
errors.append( "object: '" + object.name + "', error: " + str(error))
if len(errors) > 0:
self.report({'ERROR'}, "Failed to update propertyGroup values from custom property: Errors:" + str(errors))
else:
self.report({'INFO'}, "Sucessfully generated UI values for custom properties for all objects")
bpy.context.window_manager.components_registry.disable_all_object_updates = False
return {'FINISHED'}
class OT_OpenFilebrowser(Operator, ImportHelper):
"""Browse for registry json file"""
@ -66,7 +117,7 @@ class OT_OpenFilebrowser(Operator, ImportHelper):
filter_glob: StringProperty(
default='*.json',
options={'HIDDEN'}
)
) # type: ignore
def execute(self, context):
"""Do something with the selected file(s)."""
#filename, extension = os.path.splitext(self.filepath)

View File

@ -1,16 +1,58 @@
import bpy
import json
import os
import uuid
from pathlib import Path
from bpy_types import (PropertyGroup)
from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty)
from ..components.metadata import ComponentInfos
from ..helpers import load_settings
from ..propGroups.prop_groups import generate_propertyGroups_for_components
from ..components.metadata import ComponentMetadata, ensure_metadata_for_all_objects
# helper class to store missing bevy types information
class MissingBevyType(bpy.types.PropertyGroup):
type_name: bpy.props.StringProperty(
name="type",
)
) # 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
print("watching schema file for changes")
try:
stamp = os.stat(self.schemaFullPath).st_mtime
stamp = str(stamp)
if stamp != self.schemaTimeStamp and self.schemaTimeStamp != "":
print("FILE CHANGED !!", stamp, self.schemaTimeStamp)
# see here for better ways : https://stackoverflow.com/questions/11114492/check-if-a-file-is-not-open-nor-being-used-by-another-process
"""try:
os.rename(path, path)
#return False
except OSError: # file is in use
print("in use")
#return True"""
#bpy.ops.object.reload_registry()
# we need to add an additional delay as the file might not have loaded yet
bpy.app.timers.register(lambda: bpy.ops.object.reload_registry(), first_interval=1)
self.schemaTimeStamp = stamp
except Exception as error:
pass
return self.watcher_poll_frequency if self.watcher_enabled else None
# this is where we store the information for all available components
class ComponentsRegistry(PropertyGroup):
@ -21,20 +63,45 @@ class ComponentsRegistry(PropertyGroup):
name="schema path",
description="path to the registry schema file",
default="registry.json"
)
)# type: ignore
schemaFullPath : bpy.props.StringProperty(
name="schema full path",
description="path to the registry schema file",
)# type: ignore
registry: bpy.props. StringProperty(
name="registry",
description="component registry"
)
)# type: ignore
missing_type_infos: StringProperty(
name="missing type infos",
description="unregistered/missing type infos"
)
)# type: ignore
missing_types_list: CollectionProperty(name="missing types list", type=MissingBevyType)
missing_types_list_index: IntProperty(name = "Index for missing types list", default = 0)
disable_all_object_updates: BoolProperty(name="disable_object_updates", default=False) # type: ignore
## file watcher
watcher_enabled: BoolProperty(name="Watcher_enabled", default=True, update=toggle_watcher)# type: ignore
watcher_active: BoolProperty(name = "Flag for watcher status", default = False)# type: ignore
watcher_poll_frequency: IntProperty(
name="watcher poll frequency",
description="frequency (s) at wich to poll for changes to the registry file",
min=1,
max=10,
default=1
)# type: ignore
schemaTimeStamp: StringProperty(
name="last timestamp of schema file",
description="",
default=""
)# type: ignore
missing_types_list: CollectionProperty(name="missing types list", type=MissingBevyType)# type: ignore
missing_types_list_index: IntProperty(name = "Index for missing types list", default = 0)# type: ignore
blender_property_mapping = {
"bool": dict(type=BoolProperty, presets=dict()),
@ -52,6 +119,7 @@ class ComponentsRegistry(PropertyGroup):
"i32":dict(type=IntProperty, presets=dict()),
"i64":dict(type=IntProperty, presets=dict()),
"i128":dict(type=IntProperty, presets=dict()),
"isize": dict(type=IntProperty, presets=dict()),
"f32": dict(type=FloatProperty, presets=dict()),
"f64": dict(type=FloatProperty, presets=dict()),
@ -77,9 +145,14 @@ class ComponentsRegistry(PropertyGroup):
"char": dict(type=StringProperty, presets=dict()),
"str": dict(type=StringProperty, presets=dict()),
"alloc::string::String": dict(type=StringProperty, presets=dict()),
"alloc::borrow::Cow<str>": dict(type=StringProperty, presets=dict()),
"enum": dict(type=EnumProperty, presets=dict()),
#"alloc::vec::Vec<alloc::string::String>": dict(type=CollectionProperty, presets=dict(type=PointerProperty(StringProperty))), #FIXME: we need more generic stuff
'bevy_ecs::Entity': {"type": IntProperty, "presets": {"min":0} },
'bevy_utils::Uuid': dict(type=StringProperty, presets=dict()),
}
@ -98,12 +171,14 @@ class ComponentsRegistry(PropertyGroup):
"u32":0,
"u64":0,
"u128":0,
"usize":0,
"i8": 0,
"i16":0,
"i32":0,
"i64":0,
"i128":0,
"isize":0,
"f32": 0.0,
"f64":0.0,
@ -111,6 +186,7 @@ class ComponentsRegistry(PropertyGroup):
"char": " ",
"str": " ",
"alloc::string::String": " ",
"alloc::borrow::Cow<str>": " ",
"glam::Vec2": [0.0, 0.0],
"glam::DVec2": [0.0, 0.0],
@ -127,40 +203,66 @@ class ComponentsRegistry(PropertyGroup):
"glam::Quat": [0.0, 0.0, 0.0, 0.0],
"bevy_render::color::Color": [1.0, 1.0, 0.0, 1.0],
'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())+'"'
}
type_infos = None
type_infos = {}
type_infos_missing = []
component_propertyGroups = {}
short_names_to_long_names = {}
custom_types_to_add = {}
invalid_components = []
@classmethod
def register(cls):
bpy.types.WindowManager.components_registry = PointerProperty(type=ComponentsRegistry)
bpy.context.window_manager.components_registry.watcher_active = False
@classmethod
def unregister(cls):
bpy.context.window_manager.components_registry.watcher_active = False
for propgroup_name in cls.component_propertyGroups.keys():
try:
delattr(ComponentInfos, propgroup_name)
print("unregistered propertyGroup", propgroup_name)
delattr(ComponentMetadata, propgroup_name)
#print("unregistered propertyGroup", propgroup_name)
except Exception as error:
pass
#print("failed to remove", error, "ComponentInfos")
#print("failed to remove", error, "ComponentMetadata")
del bpy.types.WindowManager.components_registry
def load_schema(self):
# cleanup missing types list
self.missing_types_list.clear()
self.type_infos = None
self.type_infos_missing.clear()
file_path = bpy.data.filepath
try:
bpy.app.timers.unregister(watch_schema)
except Exception as error:
print("failed to unregister", error)
pass
del bpy.types.WindowManager.components_registry
def load_schema(self):
print("load schema", self)
# cleanup previous data if any
self.propGroupIdCounter = 0
self.short_names_to_propgroup_names.clear()
self.missing_types_list.clear()
self.type_infos.clear()
self.type_infos_missing.clear()
self.component_propertyGroups.clear()
self.short_names_to_long_names.clear()
self.custom_types_to_add.clear()
self.invalid_components.clear()
# now prepare paths to load data
file_path = bpy.data.filepath
# Get the folder
folder_path = os.path.dirname(file_path)
path = os.path.join(folder_path, self.schemaPath)
self.schemaFullPath = path
f = Path(bpy.path.abspath(path)) # make a path object of abs path
with open(path) as f:
@ -168,9 +270,32 @@ class ComponentsRegistry(PropertyGroup):
defs = data["$defs"]
self.registry = json.dumps(defs) # FIXME:meh ?
# 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)
# we load the json once, so we do not need to do it over & over again
def load_type_infos(self):
print("load type infos")
ComponentsRegistry.type_infos = json.loads(self.registry)
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()
# we keep a list of component propertyGroup around
def register_component_propertyGroup(self, name, propertyGroup):
@ -188,7 +313,6 @@ class ComponentsRegistry(PropertyGroup):
item = self.missing_types_list.add()
item.type_name = type_name
custom_types_to_add = {}
def add_custom_type(self, type_name, type_definition):
self.custom_types_to_add[type_name] = type_definition
@ -197,9 +321,41 @@ class ComponentsRegistry(PropertyGroup):
self.type_infos[type_name] = self.custom_types_to_add[type_name]
self.custom_types_to_add.clear()
invalid_components = []
def add_invalid_component(self, component_name):
self.invalid_components.append(component_name)
###########
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)
"""
object[component_definition.name] = 0.5
property_manager = object.id_properties_ui(component_definition.name)

View File

@ -1,6 +1,11 @@
import bpy
from bpy_types import (UIList)
from .operators import(OT_OpenFilebrowser, ReloadRegistryOperator, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT)
from .operators import(
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL,
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT,
OT_OpenFilebrowser, ReloadRegistryOperator,
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL,
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT)
class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
bl_idname = "BEVY_COMPONENTS_PT_Configuration"
@ -16,6 +21,7 @@ class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
def draw(self, context):
layout = self.layout
registry = context.window_manager.components_registry
registry_has_type_infos = registry.has_type_infos()
selected_object = context.selected_objects[0] if len(context.selected_objects) > 0 else None
row = layout.row()
@ -28,6 +34,12 @@ class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
layout.separator()
layout.operator(ReloadRegistryOperator.bl_idname, text="reload registry" , icon="FILE_REFRESH")
layout.separator()
row = layout.row()
row.prop(registry, "watcher_enabled", text="enable registry file polling")
row.prop(registry, "watcher_poll_frequency", text="registry file poll frequency (s)")
layout.separator()
layout.separator()
@ -36,13 +48,26 @@ class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
row.alert = True
row = layout.row()
row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT.bl_idname, text="update custom properties of current object" , icon="FILE_REFRESH")
row.enabled = registry.type_infos != None and selected_object is not None
row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT.bl_idname, text="update custom properties of current object" , icon="LOOP_FORWARDS")
row.enabled = registry_has_type_infos and selected_object is not None
layout.separator()
row = layout.row()
row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL.bl_idname, text="update custom properties of ALL objects" , icon="FILE_REFRESH")
row.enabled = registry.type_infos != None
row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL.bl_idname, text="update custom properties of ALL objects" , icon="LOOP_FORWARDS")
row.enabled = registry_has_type_infos
row = layout.row()
row.label(text="WARNING ! The following operations will try to overwrite your existing ui values if they have matching types on the bevy side !")
row.alert = True
row = layout.row()
row.operator(COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT.bl_idname, text="update UI FROM custom properties of current object" , icon="LOOP_BACK")
row.enabled = registry_has_type_infos and selected_object is not None
layout.separator()
row = layout.row()
row.operator(COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL.bl_idname, text="update UI FROM custom properties of ALL objects" , icon="LOOP_BACK")
row.enabled = registry_has_type_infos
class BEVY_COMPONENTS_PT_MissingTypesPanel(bpy.types.Panel):

View File

View File

@ -0,0 +1,216 @@
import random
import string
import uuid
from bpy_types import PropertyGroup
def random_bool():
return bool(random.getrandbits(1))
def rand_int():
return random.randint(0, 100)
def rand_float():
return random.random()
def random_word(length):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(length))
def random_vec(length, type,):
value = []
for i in range(0, length):
if type == 'float':
value.append(rand_float())
if type == 'int':
value.append(rand_int())
return value
type_mappings = {
"bool": random_bool,
"u8": rand_int,
"u16": rand_int,
"u32": rand_int,
"u64": rand_int,
"u128": rand_int,
"u64": rand_int,
"usize": rand_int,
"i8": rand_int,
"i16": rand_int,
"i32": rand_int,
"i64": rand_int,
"i128": rand_int,
"isize": rand_int,
'f32': rand_float,
'f64': rand_float,
"glam::Vec2": lambda : random_vec(2, 'float'),
"glam::DVec2": lambda : random_vec(2, 'float'),
"glam::UVec2": lambda : random_vec(2, 'int'),
'glam::Vec3': lambda : random_vec(3, 'float'),
"glam::Vec3A": lambda : random_vec(3, 'float'),
"glam::UVec3": lambda : random_vec(3, 'int'),
"glam::Vec4": lambda : random_vec(4, 'float'),
"glam::DVec4": lambda : random_vec(4, 'float'),
"glam::UVec4": lambda : random_vec(4, 'int'),
"glam::Quat": lambda : random_vec(4, 'float'),
'bevy_render::color::Color': lambda : random_vec(4, 'float'),
'alloc::string::String': lambda : random_word(8),
'alloc::borrow::Cow<str>': lambda : random_word(8),
'bevy_ecs::Entity': lambda: 0, #4294967295, #
'bevy_utils::Uuid': lambda: '"'+str( uuid.UUID("73b3b118-7d01-4778-8bcc-4e79055f5d22") )+'"'
}
#
def is_def_value_type(definition, registry):
if definition == None:
return True
value_types_defaults = registry.value_types_defaults
type_name = definition["title"]
is_value_type = type_name in value_types_defaults
return is_value_type
# see https://docs.python.org/3/library/random.html
def component_values_shuffler(seed=1, property_group=None, definition=None, registry=None, parent=None):
if parent == None:
random.seed(seed)
value_types_defaults = registry.value_types_defaults
component_name = definition["short_name"]
type_info = definition["typeInfo"] if "typeInfo" in definition else None
type_def = definition["type"] if "type" in definition else None
properties = definition["properties"] if "properties" in definition else {}
prefixItems = definition["prefixItems"] if "prefixItems" in definition else []
has_properties = len(properties.keys()) > 0
has_prefixItems = len(prefixItems) > 0
is_enum = type_info == "Enum"
is_list = type_info == "List"
type_name = definition["title"]
#is_value_type = type_def in value_types_defaults or type_name in value_types_defaults
is_value_type = type_name in value_types_defaults
if is_value_type:
fieldValue = type_mappings[type_name]()
return fieldValue
elif type_info == "Struct":
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
value = getattr(property_group, field_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
if item_definition != None:
value = component_values_shuffler(seed, child_property_group, item_definition, registry, parent=component_name)
else:
value = '""'
is_item_value_type = is_def_value_type(item_definition, registry)
if is_item_value_type:
#print("setting attr", field_name , "for", component_name, "to", value, "value type", is_item_value_type)
setattr(property_group , field_name, value)
elif type_info == "Tuple":
#print("tup")
for index, field_name in enumerate(property_group.field_names):
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
value = getattr(property_group, field_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
if item_definition != None:
value = component_values_shuffler(seed, child_property_group, item_definition, registry, parent=component_name)
else:
value = '""'
is_item_value_type = is_def_value_type(item_definition, registry)
if is_item_value_type:
#print("setting attr", field_name , "for", component_name, "to", value, "value type", is_item_value_type)
setattr(property_group , field_name, value)
elif type_info == "TupleStruct":
#print("tupstruct")
for index, field_name in enumerate(property_group.field_names):
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
value = getattr(property_group, field_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
if item_definition != None:
value = component_values_shuffler(seed, child_property_group, item_definition, registry, parent=component_name)
else:
value = '""'
is_item_value_type = is_def_value_type(item_definition, registry)
if is_item_value_type:
setattr(property_group , field_name, value)
elif type_info == "Enum":
available_variants = definition["oneOf"] if type_def != "object" else list(map(lambda x: x["title"], definition["oneOf"]))
selected = random.choice(available_variants)
# set selected variant
setattr(property_group , component_name, selected)
if type_def == "object":
selection_index = property_group.field_names.index("variant_"+selected)
variant_name = property_group.field_names[selection_index]
variant_definition = definition["oneOf"][selection_index-1]
if "prefixItems" in variant_definition:
value = getattr(property_group, variant_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
value = component_values_shuffler(seed, child_property_group, variant_definition, registry, parent=component_name)
value = selected + str(value,)
elif "properties" in variant_definition:
value = getattr(property_group, variant_name)
is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None
value = component_values_shuffler(seed, child_property_group, variant_definition, registry, parent=component_name)
value = selected + str(value,)
else:
value = selected # here the value of the enum is just the name of the variant
else:
value = selected
elif type_info == "List":
item_list = getattr(property_group, "list")
item_list.clear()
item_type_name = getattr(property_group, "type_name_short")
number_of_list_items_to_add = random.randint(1, 2)
for i in range(0, number_of_list_items_to_add):
new_entry = item_list.add()
item_type_name = getattr(new_entry, "type_name") # we get the REAL type name
definition = registry.type_infos[item_type_name] if item_type_name in registry.type_infos else None
if definition != None:
component_values_shuffler(seed, new_entry, definition, registry, parent=component_name)
else:
pass
else:
print("something else")
fieldValue = type_mappings[type_name]()
return fieldValue
#return value

View File

@ -0,0 +1,427 @@
expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut': '',
'Aabb': '(center: Vec3A(x:0.0, y:0.0, z:0.0), half_extents: Vec3A(x:0.0, y:0.0, z:0.0))',
'AdditionalMassProperties': 'Mass(0.0)',
'AmbientLightSettings': '(brightness: 0.0, color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))',
'AnimationPlayer': '(animation: "", paused: true)',
'Animations': '(named_animations: "")',
'AutoAABBCollider': 'Cuboid',
'BackgroundColor': '(Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))',
'BasicTest': '(a: 0.0, b: 0, c: " ")',
'BloomSettings': '(composite_mode: EnergyConserving, high_pass_frequency: 0.0, intensity: 0.0, low_frequency_boost: '
'0.0, low_frequency_boost_curvature: 0.0, prefilter_settings: (threshold: 0.0, threshold_softness: '
'0.0))',
'BlueprintName': '(" ")',
'BorderColor': '(Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))',
'Button': '',
'CalculatedClip': '(clip: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, y:0.0)))',
'Camera': '(hdr: true, is_active: true, msaa_writeback: true, order: 0, viewport: None)',
'Camera2d': '(clear_color: Default)',
'Camera3d': '(clear_color: Default, depth_load_op: Clear(0.0), depth_texture_usages: "", '
'screen_space_specular_transmission_quality: "", screen_space_specular_transmission_steps: 0)',
'CameraRenderGraph': '( )',
'CameraTrackable': '',
'CameraTracking': '(offset: Vec3(x:0.0, y:0.0, z:0.0))',
'CameraTrackingOffset': '(Vec3(x:0.0, y:0.0, z:0.0))',
'CascadeShadowConfig': '(bounds: [], minimum_distance: 0.0, overlap_proportion: 0.0)',
'Cascades': '(cascades: "")',
'CascadesFrusta': '',
'CascadesVisibleEntities': '',
'Ccd': '(enabled: true)',
'Children': '([])',
'ClusterConfig': 'None',
'Collider': 'Ball(0.0)',
'CollidingEntities': '("")',
'CollisionGroups': '(filters: (0), memberships: (0))',
'ColorGrading': '(exposure: 0.0, gamma: 0.0, post_saturation: 0.0, pre_saturation: 0.0)',
'ContactForceEventThreshold': '(0.0)',
'ContentSize': '',
'ContrastAdaptiveSharpeningSettings': '(denoise: true, enabled: true, sharpening_strength: 0.0)',
'CubemapFrusta': '',
'CubemapVisibleEntities': '',
'Damping': '(angular_damping: 0.0, linear_damping: 0.0)',
'DebandDither': 'Disabled',
'DirectionalLight': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), illuminance: 0.0, shadow_depth_bias: 0.0, '
'shadow_normal_bias: 0.0, shadows_enabled: true)',
'Dominance': '(groups: 0)',
'EnumComplex': 'Float(0.0)',
'EnumTest': 'Metal',
'ExternalForce': '(force: Vec3(x:0.0, y:0.0, z:0.0), torque: Vec3(x:0.0, y:0.0, z:0.0))',
'ExternalImpulse': '(impulse: Vec3(x:0.0, y:0.0, z:0.0), torque_impulse: Vec3(x:0.0, y:0.0, z:0.0))',
'FocusPolicy': 'Block',
'FogSettings': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), directional_light_color: Rgba(red:1.0, '
'green:1.0, blue:0.0, alpha:1.0), directional_light_exponent: 0.0, falloff: "")',
'Friction': '(coefficient: 0.0, combine_rule: "")',
'Frustum': '',
'Fxaa': '(edge_threshold: "", edge_threshold_min: "", enabled: true)',
'GlobalTransform': '((matrix3: (x_axis: Vec3A(x:0.0, y:0.0, z:0.0), y_axis: Vec3A(x:0.0, y:0.0, z:0.0), z_axis: '
'Vec3A(x:0.0, y:0.0, z:0.0)), translation: Vec3A(x:0.0, y:0.0, z:0.0)))',
'GltfExtras': '(value: " ")',
'GravityScale': '(0.0)',
'Group': '(0)',
'Handle<()>': 'Strong("")',
'Handle<AnimationClip>': 'Strong("")',
'Handle<AudioSource>': 'Strong("")',
'Handle<ColorMaterial>': 'Strong("")',
'Handle<DynamicScene>': 'Strong("")',
'Handle<ExtendedMaterial<StandardMaterial, MyExtension>>': 'Strong("")',
'Handle<Font>': 'Strong("")',
'Handle<Gltf>': 'Strong("")',
'Handle<GltfMesh>': 'Strong("")',
'Handle<GltfNode>': 'Strong("")',
'Handle<GltfPrimitive>': 'Strong("")',
'Handle<Image>': 'Strong("")',
'Handle<LineGizmo>': 'Strong("")',
'Handle<LoadedFolder>': 'Strong("")',
'Handle<LoadedUntypedAsset>': 'Strong("")',
'Handle<Mesh>': 'Strong("")',
'Handle<Pitch>': 'Strong("")',
'Handle<Scene>': 'Strong("")',
'Handle<Shader>': 'Strong("")',
'Handle<SkinnedMeshInverseBindposes>': 'Strong("")',
'Handle<StandardDynamicAssetCollection>': 'Strong("")',
'Handle<StandardMaterial>': 'Strong("")',
'Handle<TextureAtlas>': 'Strong("")',
'Handle<WireframeMaterial>': 'Strong("")',
'InheritedVisibility': '(true)',
'Interaction': 'Pressed',
'Label': '',
'LockedAxes': '(0)',
'MaterialInfo': '(name: " ", source: " ")',
'Mesh2dHandle': '(Strong(""))',
'MeshMorphWeights': '(weights: [])',
'MorphWeights': '(first_mesh: "", weights: [])',
'Name': '(hash: 0, name: )',
'NestedTupleStuff': '(0.0, 0, (basic: (a: 0.0, b: 0, c: " "), color: (Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0)), '
'colors_list: ([]), enable: true, enum_inner: Metal, nested: (vec: (Vec3(x:0.0, y:0.0, z:0.0))), '
'text: " ", toggle: (true)))',
'NestingTestLevel2': '(basic: (a: 0.0, b: 0, c: " "), color: (Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0)), '
'colors_list: ([]), enable: true, enum_inner: Metal, nested: (vec: (Vec3(x:0.0, y:0.0, z:0.0))), '
'text: " ", toggle: (true))',
'NestingTestLevel3': '(vec: (Vec3(x:0.0, y:0.0, z:0.0)))',
'NoFrustumCulling': '',
'NoWireframe': '',
'Node': '(calculated_size: Vec2(x:0.0, y:0.0), outline_offset: 0.0, outline_width: 0.0, stack_index: 0, '
'unrounded_size: Vec2(x:0.0, y:0.0))',
'NotShadowCaster': '',
'NotShadowReceiver': '',
'OrthographicProjection': '(area: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, y:0.0)), far: 0.0, near: 0.0, scale: '
'0.0, scaling_mode: Fixed(height: 0.0, width: 0.0), viewport_origin: Vec2(x:0.0, y:0.0))',
'Outline': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), offset: Auto, width: Auto)',
'Parent': '(0)',
'PerspectiveProjection': '(aspect_ratio: 0.0, far: 0.0, fov: 0.0, near: 0.0)',
'Pickable': '',
'Player': '',
'PointLight': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), intensity: 0.0, radius: 0.0, range: 0.0, '
'shadow_depth_bias: 0.0, shadow_normal_bias: 0.0, shadows_enabled: true)',
'PrimaryWindow': '',
'Projection': 'Perspective((aspect_ratio: 0.0, far: 0.0, fov: 0.0, near: 0.0))',
'RelativeCursorPosition': '(normalized: "", normalized_visible_node_rect: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, '
'y:0.0)))',
'RenderLayers': '(0)',
'Restitution': '(coefficient: 0.0, combine_rule: "")',
'RigidBody': 'Dynamic',
'SSAOSettings': '',
'ScreenSpaceAmbientOcclusionSettings': '(quality_level: "")',
'Sensor': '',
'ShadowFilteringMethod': 'Hardware2x2',
'ShadowmapSettings': '(size: 0)',
'SkinnedMesh': '(inverse_bindposes: Strong(""), joints: [])',
'Sleeping': '(angular_threshold: 0.0, linear_threshold: 0.0, sleeping: true)',
'SolverGroups': '(filters: (0), memberships: (0))',
'SpawnHere': '',
'SpotLight': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), inner_angle: 0.0, intensity: 0.0, outer_angle: '
'0.0, radius: 0.0, range: 0.0, shadow_depth_bias: 0.0, shadow_normal_bias: 0.0, shadows_enabled: true)',
'Sprite': '(anchor: Center, color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), custom_size: "", flip_x: true, '
'flip_y: true, rect: "")',
'Style': '(align_content: Default, align_items: Default, align_self: Auto, aspect_ratio: None, border: (bottom: Auto, '
'left: Auto, right: Auto, top: Auto), bottom: Auto, column_gap: Auto, direction: Inherit, display: Flex, '
'flex_basis: Auto, flex_direction: Row, flex_grow: 0.0, flex_shrink: 0.0, flex_wrap: NoWrap, '
'grid_auto_columns: "", grid_auto_flow: Row, grid_auto_rows: "", grid_column: (end: "", span: "", start: '
'""), grid_row: (end: "", span: "", start: ""), grid_template_columns: "", grid_template_rows: "", height: '
'Auto, justify_content: Default, justify_items: Default, justify_self: Auto, left: Auto, margin: (bottom: '
'Auto, left: Auto, right: Auto, top: Auto), max_height: Auto, max_width: Auto, min_height: Auto, min_width: '
'Auto, overflow: (x: Visible, y: Visible), padding: (bottom: Auto, left: Auto, right: Auto, top: Auto), '
'position_type: Relative, right: Auto, row_gap: Auto, top: Auto, width: Auto)',
'Text': '(alignment: Left, linebreak_behavior: WordBoundary, sections: [])',
'Text2dBounds': '(size: Vec2(x:0.0, y:0.0))',
'TextFlags': '(needs_new_measure_func: true, needs_recompute: true)',
'TextLayoutInfo': '(glyphs: "", logical_size: Vec2(x:0.0, y:0.0))',
'TextureAtlasSprite': '(anchor: Center, color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), custom_size: "", '
'flip_x: true, flip_y: true, index: 0)',
'Tonemapping': 'None',
'Transform': '(rotation: Quat(x:0.0, y:0.0, z:0.0, w:0.0), scale: Vec3(x:0.0, y:0.0, z:0.0), translation: Vec3(x:0.0, '
'y:0.0, z:0.0))',
'TupleTest2': '(0.0, 0, " ")',
'TupleTestBool': '(true)',
'TupleTestColor': '(Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))',
'TupleTestF32': '(0.0)',
'TupleTestStr': '(" ")',
'TupleTestU64': '(0)',
'TupleVec': '([])',
'TupleVec2': '(Vec2(x:0.0, y:0.0))',
'TupleVec3': '(Vec3(x:0.0, y:0.0, z:0.0))',
'TupleVecF32F32': '([])',
'UiCameraConfig': '(show_ui: true)',
'UiImage': '(flip_x: true, flip_y: true, texture: Strong(""))',
'UiImageSize': '(size: Vec2(x:0.0, y:0.0))',
'UiTextureAtlasImage': '(flip_x: true, flip_y: true, index: 0)',
'UnitTest': '',
'VecOfColors': '([])',
'VecOfF32s': '([])',
'VecOfVec3s2': '([])',
'Velocity': '(angvel: Vec3(x:0.0, y:0.0, z:0.0), linvel: Vec3(x:0.0, y:0.0, z:0.0))',
'ViewVisibility': '(true)',
'Visibility': 'Inherited',
'VisibleEntities': '',
'Window': '(canvas: None, composite_alpha_mode: Auto, cursor: (grab_mode: None, hit_test: true, icon: Default, '
'visible: true), decorations: true, enabled_buttons: (close: true, maximize: true, minimize: true), '
'fit_canvas_to_parent: true, focused: true, ime_enabled: true, ime_position: Vec2(x:0.0, y:0.0), internal: '
'(maximize_request: None, minimize_request: None, physical_cursor_position: None), mode: Windowed, '
'position: Automatic, present_mode: AutoVsync, prevent_default_event_handling: true, resizable: true, '
'resize_constraints: (max_height: 0.0, max_width: 0.0, min_height: 0.0, min_width: 0.0), resolution: '
'(physical_height: 0, physical_width: 0, scale_factor: 0.0, scale_factor_override: None), title: " ", '
'transparent: true, visible: true, window_level: AlwaysOnBottom, window_theme: "")',
'Wireframe': '',
'ZIndex': 'Local(0)'}
expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut': '',
'Aabb': '(center: Vec3A(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019), half_extents: '
'Vec3A(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137))',
'AdditionalMassProperties': 'Mass(0.42888906598091125)',
'AmbientLightSettings': '(brightness: 0.5714026093482971, color: Rgba(red:0.42888906598091125, '
'green:0.5780913233757019, blue:0.20609822869300842, alpha:0.8133212327957153))',
'AnimationPlayer': '(animation: "", paused: true)',
'Animations': '(named_animations: "")',
'AutoAABBCollider': 'Capsule',
'BackgroundColor': '(Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842))',
'BasicTest': '(a: 0.5714026093482971, b: 54, c: "psagopiu")',
'BloomSettings': '(composite_mode: EnergyConserving, high_pass_frequency: 0.42888906598091125, intensity: '
'0.5780913233757019, low_frequency_boost: 0.20609822869300842, low_frequency_boost_curvature: '
'0.8133212327957153, prefilter_settings: (threshold: 0.8235888481140137, threshold_softness: '
'0.6534725427627563))',
'BlueprintName': '("sbnpsago")',
'BorderColor': '(Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842))',
'Button': '',
'CalculatedClip': '(clip: (max: Vec2(x:0.5714026093482971, y:0.42888906598091125), min: Vec2(x:0.5780913233757019, '
'y:0.20609822869300842)))',
'Camera': '(hdr: true, is_active: false, msaa_writeback: false, order: 61, viewport: None)',
'Camera2d': '(clear_color: None)',
'Camera3d': '(clear_color: None, depth_load_op: Clear(0.42888906598091125), depth_texture_usages: "", '
'screen_space_specular_transmission_quality: "", screen_space_specular_transmission_steps: 73)',
'CameraRenderGraph': '(sbnpsago)',
'CameraTrackable': '',
'CameraTracking': '(offset: Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019))',
'CameraTrackingOffset': '(Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019))',
'CascadeShadowConfig': '(bounds: [0.42888906598091125], minimum_distance: 0.5780913233757019, overlap_proportion: '
'0.20609822869300842)',
'Cascades': '(cascades: "")',
'CascadesFrusta': '',
'CascadesVisibleEntities': '',
'Ccd': '(enabled: true)',
'Children': '([0])',
'ClusterConfig': 'None',
'Collider': 'Ball(0.42888906598091125)',
'CollidingEntities': '("")',
'CollisionGroups': '(filters: (73), memberships: (4))',
'ColorGrading': '(exposure: 0.5714026093482971, gamma: 0.42888906598091125, post_saturation: 0.5780913233757019, '
'pre_saturation: 0.20609822869300842)',
'ContactForceEventThreshold': '(0.5714026093482971)',
'ContentSize': '',
'ContrastAdaptiveSharpeningSettings': '(denoise: true, enabled: false, sharpening_strength: 0.42888906598091125)',
'CubemapFrusta': '',
'CubemapVisibleEntities': '',
'Damping': '(angular_damping: 0.5714026093482971, linear_damping: 0.42888906598091125)',
'DebandDither': 'Disabled',
'DirectionalLight': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842), illuminance: 0.8133212327957153, shadow_depth_bias: '
'0.8235888481140137, shadow_normal_bias: 0.6534725427627563, shadows_enabled: false)',
'Dominance': '(groups: 73)',
'EnumComplex': 'StructLike(a: 0.03258506581187248, b: 61, c: "sagopiuz")',
'EnumTest': 'Squishy',
'ExternalForce': '(force: Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019), torque: '
'Vec3(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137))',
'ExternalImpulse': '(impulse: Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019), '
'torque_impulse: Vec3(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137))',
'FocusPolicy': 'Block',
'FogSettings': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842), directional_light_color: Rgba(red:0.8133212327957153, '
'green:0.8235888481140137, blue:0.6534725427627563, alpha:0.16022956371307373), '
'directional_light_exponent: 0.5206693410873413, falloff: "")',
'Friction': '(coefficient: 0.5714026093482971, combine_rule: "")',
'Frustum': '',
'Fxaa': '(edge_threshold: "", edge_threshold_min: "", enabled: true)',
'GlobalTransform': '((matrix3: (x_axis: Vec3A(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019), '
'y_axis: Vec3A(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137), z_axis: '
'Vec3A(x:0.6534725427627563, y:0.16022956371307373, z:0.5206693410873413)), translation: '
'Vec3A(x:0.3277728259563446, y:0.24999667704105377, z:0.952816903591156)))',
'GltfExtras': '(value: "sbnpsago")',
'GravityScale': '(0.5714026093482971)',
'Group': '(73)',
'Handle<()>': 'Strong("")',
'Handle<AnimationClip>': 'Strong("")',
'Handle<AudioSource>': 'Strong("")',
'Handle<ColorMaterial>': 'Strong("")',
'Handle<DynamicScene>': 'Strong("")',
'Handle<ExtendedMaterial<StandardMaterial, MyExtension>>': 'Strong("")',
'Handle<Font>': 'Strong("")',
'Handle<Gltf>': 'Strong("")',
'Handle<GltfMesh>': 'Strong("")',
'Handle<GltfNode>': 'Strong("")',
'Handle<GltfPrimitive>': 'Strong("")',
'Handle<Image>': 'Strong("")',
'Handle<LineGizmo>': 'Strong("")',
'Handle<LoadedFolder>': 'Strong("")',
'Handle<LoadedUntypedAsset>': 'Strong("")',
'Handle<Mesh>': 'Strong("")',
'Handle<Pitch>': 'Strong("")',
'Handle<Scene>': 'Strong("")',
'Handle<Shader>': 'Strong("")',
'Handle<SkinnedMeshInverseBindposes>': 'Strong("")',
'Handle<StandardDynamicAssetCollection>': 'Strong("")',
'Handle<StandardMaterial>': 'Strong("")',
'Handle<TextureAtlas>': 'Strong("")',
'Handle<WireframeMaterial>': 'Strong("")',
'InheritedVisibility': '(true)',
'Interaction': 'None',
'Label': '',
'LockedAxes': '(73)',
'MaterialInfo': '(name: "sbnpsago", source: "piuzfbqp")',
'Mesh2dHandle': '(Strong(""))',
'MeshMorphWeights': '(weights: [0.42888906598091125])',
'MorphWeights': '(first_mesh: "", weights: [0.42888906598091125])',
'Name': '(hash: 73, name: bnpsagop)',
'NestedTupleStuff': '(0.5714026093482971, 54, (basic: (a: 0.4825616776943207, b: 1, c: "gopiuzfb"), color: '
'(Rgba(red:0.5206693410873413, green:0.3277728259563446, blue:0.24999667704105377, '
'alpha:0.952816903591156)), colors_list: ([Rgba(red:0.0445563830435276, green:0.8601610660552979, '
'blue:0.6031906008720398, alpha:0.38160598278045654), Rgba(red:0.2836182117462158, '
'green:0.6749648451805115, blue:0.456831157207489, alpha:0.6858614683151245)]), enable: true, '
'enum_inner: Rock, nested: (vec: (Vec3(x:0.1329781413078308, y:0.7678378224372864, '
'z:0.9824132323265076))), text: "otmbsahe", toggle: (false)))',
'NestingTestLevel2': '(basic: (a: 0.5714026093482971, b: 54, c: "psagopiu"), color: (Rgba(red:0.8106188178062439, '
'green:0.03440357372164726, blue:0.49008557200431824, alpha:0.07608934491872787)), colors_list: '
'([Rgba(red:0.0445563830435276, green:0.8601610660552979, blue:0.6031906008720398, '
'alpha:0.38160598278045654), Rgba(red:0.2836182117462158, green:0.6749648451805115, '
'blue:0.456831157207489, alpha:0.6858614683151245)]), enable: true, enum_inner: Rock, nested: '
'(vec: (Vec3(x:0.1329781413078308, y:0.7678378224372864, z:0.9824132323265076))), text: '
'"otmbsahe", toggle: (false))',
'NestingTestLevel3': '(vec: (Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019)))',
'NoFrustumCulling': '',
'NoWireframe': '',
'Node': '(calculated_size: Vec2(x:0.5714026093482971, y:0.42888906598091125), outline_offset: 0.5780913233757019, '
'outline_width: 0.20609822869300842, stack_index: 62, unrounded_size: Vec2(x:0.8235888481140137, '
'y:0.6534725427627563))',
'NotShadowCaster': '',
'NotShadowReceiver': '',
'OrthographicProjection': '(area: (max: Vec2(x:0.5714026093482971, y:0.42888906598091125), min: '
'Vec2(x:0.5780913233757019, y:0.20609822869300842)), far: 0.8133212327957153, near: '
'0.8235888481140137, scale: 0.6534725427627563, scaling_mode: '
'WindowSize(0.03440357372164726), viewport_origin: Vec2(x:0.49008557200431824, '
'y:0.07608934491872787))',
'Outline': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842), offset: VMax(0.4912964105606079), width: Percent(0.6534725427627563))',
'Parent': '(0)',
'PerspectiveProjection': '(aspect_ratio: 0.5714026093482971, far: 0.42888906598091125, fov: 0.5780913233757019, near: '
'0.20609822869300842)',
'Pickable': '',
'Player': '',
'PointLight': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842), intensity: 0.8133212327957153, radius: 0.8235888481140137, range: '
'0.6534725427627563, shadow_depth_bias: 0.16022956371307373, shadow_normal_bias: 0.5206693410873413, '
'shadows_enabled: false)',
'PrimaryWindow': '',
'Projection': 'Perspective((aspect_ratio: 0.42888906598091125, far: 0.5780913233757019, fov: 0.20609822869300842, '
'near: 0.8133212327957153))',
'RelativeCursorPosition': '(normalized: "", normalized_visible_node_rect: (max: Vec2(x:0.5714026093482971, '
'y:0.42888906598091125), min: Vec2(x:0.5780913233757019, y:0.20609822869300842)))',
'RenderLayers': '(73)',
'Restitution': '(coefficient: 0.5714026093482971, combine_rule: "")',
'RigidBody': 'Dynamic',
'SSAOSettings': '',
'ScreenSpaceAmbientOcclusionSettings': '(quality_level: "")',
'Sensor': '',
'ShadowFilteringMethod': 'Jimenez14',
'ShadowmapSettings': '(size: 73)',
'SkinnedMesh': '(inverse_bindposes: Strong(""), joints: [0, 0])',
'Sleeping': '(angular_threshold: 0.5714026093482971, linear_threshold: 0.42888906598091125, sleeping: true)',
'SolverGroups': '(filters: (73), memberships: (4))',
'SpawnHere': '',
'SpotLight': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842), inner_angle: 0.8133212327957153, intensity: 0.8235888481140137, '
'outer_angle: 0.6534725427627563, radius: 0.16022956371307373, range: 0.5206693410873413, '
'shadow_depth_bias: 0.3277728259563446, shadow_normal_bias: 0.24999667704105377, shadows_enabled: true)',
'Sprite': '(anchor: Custom(Vec2(x:0.03258506581187248, y:0.4825616776943207)), color: Rgba(red:0.014832446351647377, '
'green:0.46258050203323364, blue:0.4912964105606079, alpha:0.27752065658569336), custom_size: "", flip_x: '
'true, flip_y: false, rect: "")',
'Style': '(align_content: SpaceAround, align_items: Default, align_self: Baseline, aspect_ratio: '
'Some(0.5780913233757019), border: (bottom: Px(0.46258050203323364), left: Vw(0.8235888481140137), right: '
'VMin(0.8106188178062439), top: Auto), bottom: Vh(0.49008557200431824), column_gap: Auto, direction: '
'Inherit, display: None, flex_basis: Percent(0.0445563830435276), flex_direction: Column, flex_grow: '
'0.6031906008720398, flex_shrink: 0.38160598278045654, flex_wrap: Wrap, grid_auto_columns: "", '
'grid_auto_flow: RowDense, grid_auto_rows: "", grid_column: (end: "", span: "", start: ""), grid_row: (end: '
'"", span: "", start: ""), grid_template_columns: "", grid_template_rows: "", height: '
'Vw(0.17467059195041656), justify_content: FlexEnd, justify_items: Stretch, justify_self: End, left: '
'Px(0.45692843198776245), margin: (bottom: VMax(0.9824132323265076), left: Vw(0.6133268475532532), right: '
'Auto, top: Vh(0.004055144265294075)), max_height: Px(0.1949533075094223), max_width: '
'Percent(0.5363451838493347), min_height: VMax(0.8981962203979492), min_width: Percent(0.666689932346344), '
'overflow: (x: Clip, y: Clip), padding: (bottom: Vw(0.06499417871236801), left: Vh(0.32468828558921814), '
'right: Vh(0.15641891956329346), top: Px(0.9697836637496948)), position_type: Relative, right: Auto, '
'row_gap: Auto, top: Vw(0.3011642396450043), width: Vh(0.6578909158706665))',
'Text': '(alignment: Right, linebreak_behavior: WordBoundary, sections: [(style: (color: Rgba(red:0.4825616776943207, '
'green:0.014832446351647377, blue:0.46258050203323364, alpha:0.4912964105606079), font: Weak(Index(index: '
'"")), font_size: 0.03440357372164726), value: "pkchxlbn"), (style: (color: Rgba(red:0.8601610660552979, '
'green:0.6031906008720398, blue:0.38160598278045654, alpha:0.2836182117462158), font: Weak(Uuid(uuid: '
'"73b3b118-7d01-4778-8bcc-4e79055f5d22")), font_size: 0.17467059195041656), value: "jvleoyho")])',
'Text2dBounds': '(size: Vec2(x:0.5714026093482971, y:0.42888906598091125))',
'TextFlags': '(needs_new_measure_func: true, needs_recompute: false)',
'TextLayoutInfo': '(glyphs: "", logical_size: Vec2(x:0.5714026093482971, y:0.42888906598091125))',
'TextureAtlasSprite': '(anchor: Custom(Vec2(x:0.03258506581187248, y:0.4825616776943207)), color: '
'Rgba(red:0.014832446351647377, green:0.46258050203323364, blue:0.4912964105606079, '
'alpha:0.27752065658569336), custom_size: "", flip_x: true, flip_y: false, index: 4)',
'Tonemapping': 'None',
'Transform': '(rotation: Quat(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019, '
'w:0.20609822869300842), scale: Vec3(x:0.8133212327957153, y:0.8235888481140137, z:0.6534725427627563), '
'translation: Vec3(x:0.16022956371307373, y:0.5206693410873413, z:0.3277728259563446))',
'TupleTest2': '(0.5714026093482971, 54, "psagopiu")',
'TupleTestBool': '(true)',
'TupleTestColor': '(Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842))',
'TupleTestF32': '(0.5714026093482971)',
'TupleTestStr': '("sbnpsago")',
'TupleTestU64': '(73)',
'TupleVec': '(["npsagopi"])',
'TupleVec2': '(Vec2(x:0.5714026093482971, y:0.42888906598091125))',
'TupleVec3': '(Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019))',
'TupleVecF32F32': '([(0.42888906598091125, 0.5780913233757019)])',
'UiCameraConfig': '(show_ui: true)',
'UiImage': '(flip_x: true, flip_y: false, texture: Weak(Uuid(uuid: "73b3b118-7d01-4778-8bcc-4e79055f5d22")))',
'UiImageSize': '(size: Vec2(x:0.5714026093482971, y:0.42888906598091125))',
'UiTextureAtlasImage': '(flip_x: true, flip_y: false, index: 54)',
'UnitTest': '',
'VecOfColors': '([Rgba(red:0.42888906598091125, green:0.5780913233757019, blue:0.20609822869300842, '
'alpha:0.8133212327957153)])',
'VecOfF32s': '([0.42888906598091125])',
'VecOfVec3s2': '([(Vec3(x:0.42888906598091125, y:0.5780913233757019, z:0.20609822869300842))])',
'Velocity': '(angvel: Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019), linvel: '
'Vec3(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137))',
'ViewVisibility': '(true)',
'Visibility': 'Visible',
'VisibleEntities': '',
'Window': '(canvas: None, composite_alpha_mode: PostMultiplied, cursor: (grab_mode: Confined, hit_test: true, icon: '
'Default, visible: false), decorations: false, enabled_buttons: (close: true, maximize: false, minimize: '
'true), fit_canvas_to_parent: false, focused: true, ime_enabled: true, ime_position: '
'Vec2(x:0.16022956371307373, y:0.5206693410873413), internal: (maximize_request: Some(false), '
'minimize_request: None, physical_cursor_position: Some(DVec2(x:0.0445563830435276, '
'y:0.8601610660552979))), mode: SizedFullscreen, position: Centered(Primary), present_mode: Fifo, '
'prevent_default_event_handling: true, resizable: true, resize_constraints: (max_height: '
'0.2623211145401001, max_width: 0.17467059195041656, min_height: 0.30310511589050293, min_width: '
'0.36258742213249207), resolution: (physical_height: 58, physical_width: 98, scale_factor: '
'0.8600491285324097, scale_factor_override: None), title: "otmbsahe", transparent: false, visible: true, '
'window_level: Normal, window_theme: "")',
'Wireframe': '',
'ZIndex': 'Local(54)'}

View File

@ -0,0 +1,297 @@
import bpy
import pytest
import pprint
from ..propGroups.conversions_to_prop_group import property_group_value_from_custom_property_value
from ..propGroups.conversions_from_prop_group import property_group_value_to_custom_property_value
from .component_values_shuffler import component_values_shuffler
from .expected_component_values import (expected_custom_property_values, expected_custom_property_values_randomized)
@pytest.fixture
def setup_data(request):
print("\nSetting up resources...")
def finalizer():
print("\nPerforming teardown...")
registry = bpy.context.window_manager.components_registry
#registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
#bpy.ops.object.reload_registry()
type_infos = registry.type_infos
object = bpy.context.object
remove_component_operator = bpy.ops.object.remove_bevy_component
for type_name in type_infos:
definition = type_infos[type_name]
component_name = definition["short_name"]
if component_name in object:
try:
remove_component_operator(component_name=component_name)
except Exception as error:
pass
request.addfinalizer(finalizer)
return None
def test_components_should_generate_correct_custom_properties(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
object = bpy.context.object
add_component_operator = bpy.ops.object.add_bevy_component
errors = []
addable_components = []
added_components = []
custom_property_values = {}
for type_name in type_infos:
definition = type_infos[type_name]
component_type = definition["title"]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
if not is_component:
continue
addable_components.append(component_type)
try:
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
added_components.append(component_type)
custom_property_values[short_name] = object[short_name]
assert object[short_name] == expected_custom_property_values[short_name]
except Exception as error:
errors.append(error)
"""pp = pprint.PrettyPrinter(depth=14, width=120)
print("CUSTOM PROPERTY VALUES")
pp.pprint(custom_property_values)"""
assert len(errors) == 0
assert len(added_components) == 152
def test_components_should_generate_correct_custom_properties_with_randomozied_values(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
object = bpy.context.object
add_component_operator = bpy.ops.object.add_bevy_component
errors = []
error_components = []
addable_components = []
added_components = []
custom_property_values = {}
for type_name in type_infos:
definition = type_infos[type_name]
component_type = definition["title"]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
if not is_component:
continue
addable_components.append(component_type)
try:
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
component_values_shuffler(seed= 10, property_group=propertyGroup, definition=definition, registry=registry)
added_components.append(component_type)
custom_property_values[short_name] = object[short_name]
assert object[short_name] == expected_custom_property_values_randomized[short_name]
except Exception as error:
errors.append(error)
error_components.append(short_name)
pp = pprint.PrettyPrinter(depth=14, width=120)
print("CUSTOM PROPERTY VALUES")
pp.pprint(custom_property_values)
print("error_components", error_components)
assert len(errors) == 0
assert len(added_components) == 152
def test_components_should_generate_correct_propertyGroup_values_from_custom_properties(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
object = bpy.context.object
add_component_operator = bpy.ops.object.add_bevy_component
errors = []
addable_components = []
added_components = []
failing_components = []
for type_name in type_infos:
definition = type_infos[type_name]
component_type = definition["title"]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
if not is_component:
continue
addable_components.append(component_type)
try:
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
added_components.append(component_type)
# randomise values
component_values_shuffler(seed= 10, property_group=propertyGroup, definition=definition, registry=registry)
custom_property_value = object[short_name]
# first check if custom property value matches what we expect
assert custom_property_value == expected_custom_property_values_randomized[short_name]
# we update propgroup values from custom property values
property_group_value_from_custom_property_value(propertyGroup, definition, registry, custom_property_value, nesting = [])
# and then generate it back
custom_property_value_regen = property_group_value_to_custom_property_value(propertyGroup, definition, registry, None)
assert custom_property_value_regen == expected_custom_property_values_randomized[short_name]
# custom_property_values[short_name] = object[short_name]
#assert object[short_name] == expected_custom_property_values[short_name]
#print("CUSTOM PROPERTY ", object[short_name])
except Exception as error:
errors.append(error)
failing_components.append(short_name)
for index, error in enumerate(errors):
print("ERROR", error, failing_components[index])
assert len(errors) == 0
assert len(added_components) == 152
def test_remove_components(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
add_component_operator = bpy.ops.object.add_bevy_component
errors = []
addable_components = []
added_components = []
for type_name in type_infos:
definition = type_infos[type_name]
component_type = definition["title"]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
if not is_component:
continue
addable_components.append(component_type)
try:
add_component_operator(component_type=component_type)
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
# print("propertyGroup", propertyGroup, propertyGroup.field_names)
added_components.append(component_type)
except Exception as error:
errors.append(error)
assert len(errors) == 0
# now test component removal
errors.clear()
remove_component_operator = bpy.ops.object.remove_bevy_component
for component_type in added_components:
component_name = type_infos[component_type]["short_name"]
try:
remove_component_operator(component_name=component_name)
except Exception as error:
errors.append(error)
assert len(errors) == 0
def test_copy_paste_components(setup_data):
context = bpy.context
registry = context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
bpy.ops.object.reload_registry()
#component_type = "bevy_bevy_registry_export_basic_example::test_components::BasicTest"
short_name = "BasicTest"
component_type = registry.short_names_to_long_names[short_name]
# SOURCE object setup
add_component_operator = bpy.ops.object.add_bevy_component
add_component_operator(component_type=component_type)
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
object = context.object
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
setattr(propertyGroup, propertyGroup.field_names[0], 25.0)
copy_component_operator = bpy.ops.object.copy_bevy_component
copy_component_operator(source_component_name=short_name, source_object_name=object.name)
# ---------------------------------------
# TARGET object
bpy.ops.mesh.primitive_cube_add()
new_cube = bpy.context.selected_objects[0]
# 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)
# first check that there is no component currently
assert component_meta == None
paste_component_operator = bpy.ops.object.paste_bevy_component
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)
# now after pasting to the new object, it should have component meta
assert component_meta != None
# and then check if the propertyGroup of the target object is correct
propertyGroup = getattr(component_meta, property_group_name, None)
assert propertyGroup.field_names == ['a', 'b', 'c']
a_fieldValue = getattr(propertyGroup, propertyGroup.field_names[0])
assert a_fieldValue == 25.0

View File

@ -0,0 +1,51 @@
from ..propGroups.conversions_to_prop_group import parse_struct_string, parse_tuplestruct_string
def test_parse_tuplestruct_string():
assert parse_tuplestruct_string("(A)", start_nesting=1) == ['A']
assert parse_tuplestruct_string("[(A)]", start_nesting=1) == ['(A)']
assert parse_tuplestruct_string("(a: 45, b: 65)", start_nesting=1) == ['a: 45', 'b: 65']
assert parse_tuplestruct_string("[(a: 45, b: 65)]", start_nesting=1) == ['(a: 45, b: 65)']
assert parse_tuplestruct_string("45, 65, 'bla'", start_nesting=0) == ['45', '65', "'bla'"]
assert parse_tuplestruct_string("[(A), (B)]", start_nesting=1) == ['(A)', '(B)']
assert parse_tuplestruct_string("([(-1.8, 2.9), (0.0, -62)])", start_nesting=1) == ['[(-1.8, 2.9), (0.0, -62)]']
assert parse_tuplestruct_string("([(-1.8, 2.9), (0.0, -62)])", start_nesting=2) == ['(-1.8, 2.9)', '(0.0, -62)']
assert parse_tuplestruct_string("([(-1.8, 2.9), (0.0, -62), (25)])", start_nesting=2) == ['(-1.8, 2.9)', '(0.0, -62)', '(25)']
assert parse_tuplestruct_string("(Vec3(x:-2.0, y:120.0, z:1.0))", start_nesting=2) == ['x:-2.0', 'y:120.0', 'z:1.0']
assert parse_tuplestruct_string("(9)", start_nesting=1) == ['9']
assert parse_tuplestruct_string('("toto")', start_nesting=1) == ['"toto"']
assert parse_tuplestruct_string("(Rgba(red:0.0, green:0.2, blue:0.9, alpha:1.0))", start_nesting=1) == ['Rgba(red:0.0, green:0.2, blue:0.9, alpha:1.0)']
assert parse_tuplestruct_string("(Rgba(red:0.0, green:0.2, blue:0.9, alpha:1.0))", start_nesting=2) == ['red:0.0', 'green:0.2', 'blue:0.9', 'alpha:1.0']
assert parse_tuplestruct_string("([(-1.2, 2.9), (0.0, -62)])", start_nesting=2) == ['(-1.2, 2.9)', '(0.0, -62)']
assert parse_tuplestruct_string("([Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), Rgba(red:1.0, green:0.0, blue:0.5, alpha:1.0)])", start_nesting=2) == ['Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0)', 'Rgba(red:1.0, green:0.0, blue:0.5, alpha:1.0)']
assert parse_tuplestruct_string('(7.2, 2607, "sdf")', start_nesting=1) == ['7.2', '2607', '"sdf"']
assert parse_tuplestruct_string('[a, b]', start_nesting=1) == ['a', 'b']
assert parse_tuplestruct_string('[]', start_nesting=1) == []
def test_parse_struct_string():
assert parse_struct_string("a: 45, b:65") == {'a': '45', 'b':'65'}
assert parse_struct_string("x:-2.0, y:120.0, z:1.0") == {'x': '-2.0', 'y':'120.0', 'z':'1.0'}
assert parse_struct_string("enabled: true") == {'enabled': 'true'}
assert parse_struct_string("(enabled: true)", start_nesting=1) == {'enabled': 'true'}
assert parse_struct_string("(filters: (25), memberships: (5))", start_nesting=1) == {'filters': '(25)', 'memberships':'(5)'}
assert parse_struct_string("groups: 0", start_nesting=0) == {'groups': '0'}
assert parse_struct_string("(groups: 0)", start_nesting=1) == {'groups': '0'}
assert parse_struct_string("(composite_mode: EnergyConserving, high_pass_frequency: 4.0, intensity: 0.0, low_frequency_boost: -6.0, low_frequency_boost_curvature: 4.1, prefilter_settings: (threshold: -5.1, threshold_softness: 2.1))", start_nesting=1) == {'composite_mode': 'EnergyConserving', 'high_pass_frequency': '4.0', 'intensity': '0.0', 'low_frequency_boost': '-6.0', 'low_frequency_boost_curvature': '4.1', 'prefilter_settings': '(threshold: -5.1, threshold_softness: 2.1)'}
assert parse_struct_string("dimensions: UVec3(x:0.0, y:0.0, z:0.0), dynamic_resizing: true, z_config: (far_z_mode: MaxLightRange, first_slice_depth: 0.0)") == {'dimensions': 'UVec3(x:0.0, y:0.0, z:0.0)', 'dynamic_resizing': 'true', 'z_config': '(far_z_mode: MaxLightRange, first_slice_depth: 0.0)'}
assert parse_struct_string('(inverse_bindposes: Strong(""), joints: [4294967295, 4294967295, 4294967295])', start_nesting=1) == {'inverse_bindposes': 'Strong("")', 'joints': '[4294967295, 4294967295, 4294967295]'}

View File

@ -0,0 +1,29 @@
import bpy
def test_blend():
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
bpy.ops.object.reload_registry()
#print("registry type infos", registry.type_infos)
short_name = "BasicTest"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator = bpy.ops.object.add_bevy_component
add_component_operator(component_type=component_type)
property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
print("propertyGroup", propertyGroup, propertyGroup.field_names)
"""copy_component_operator = bpy.ops.object.copy_bevy_component
copy_component_operator()"""
assert propertyGroup.field_names == ['a', 'b', 'c']

View File

@ -0,0 +1,141 @@
import bpy
from .component_values_shuffler import component_values_shuffler
def test_shuffler():
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
object = bpy.context.object
add_component_operator = bpy.ops.object.add_bevy_component
short_name = "BasicTest"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
component_values_shuffler(seed= 10, property_group=propertyGroup, definition=definition, registry=registry)
assert getattr(propertyGroup, 'a') == 0.5714026093482971
assert getattr(propertyGroup, 'b') == 54
assert getattr(propertyGroup, 'c') == "psagopiu"
# Testing a more complex component
short_name = "NestingTestLevel2"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
component_values_shuffler(seed= 17, property_group=propertyGroup, definition=definition, registry=registry)
print("propertyGroup", object[short_name])
# cheating / making things easier for us for complex types: we use the custom property value
assert object[short_name] == '(basic: (a: 0.5219839215278625, b: 38, c: "ljfywwrv"), color: (Rgba(red:0.2782765030860901, green:0.9174930453300476, blue:0.24890311062335968, alpha:0.815186083316803)), colors_list: ([Rgba(red:0.2523837685585022, green:0.5016026496887207, blue:0.317435085773468, alpha:0.8463277816772461), Rgba(red:0.945193886756897, green:0.4015909433364868, blue:0.9984470009803772, alpha:0.06219279021024704)]), enable: true, enum_inner: Wood, nested: (vec: (Vec3(x:0.1509154736995697, y:0.7055686116218567, z:0.5588918924331665))), text: "vgkrdwuc", toggle: (false))'
# And another complex component
short_name = "EnumComplex"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
component_values_shuffler(seed= 17, property_group=propertyGroup, definition=definition, registry=registry)
print("propertyGroup", object[short_name])
# cheating / making things easier for us for complex types: we use the custom property value
assert object[short_name] == 'StructLike(a: 0.41416797041893005, b: 38, c: "ljfywwrv")'
# And another complex component
short_name = "AnimationPlayer"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
component_values_shuffler(seed= 17, property_group=propertyGroup, definition=definition, registry=registry)
print("propertyGroup", object[short_name])
# cheating / making things easier for us for complex types: we use the custom property value
assert object[short_name] == '(animation: "", paused: true)'
# And another complex component
short_name = "VecOfColors"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
component_values_shuffler(seed= 17, property_group=propertyGroup, definition=definition, registry=registry)
print("propertyGroup", object[short_name])
# cheating / making things easier for us for complex types: we use the custom property value
assert object[short_name] == '([Rgba(red:0.8066907525062561, green:0.9604947566986084, blue:0.2896253764629364, alpha:0.766107439994812), Rgba(red:0.7042198777198792, green:0.6613830327987671, blue:0.11016204953193665, alpha:0.02693677879869938)])'
# And another complex component
short_name = "VecOfF32s"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
component_values_shuffler(seed= 17, property_group=propertyGroup, definition=definition, registry=registry)
print("propertyGroup", object[short_name])
# cheating / making things easier for us for complex types: we use the custom property value
assert object[short_name] == '([0.8066907525062561, 0.9604947566986084])'
# And another complex component
short_name = "SkinnedMesh"
component_type = registry.short_names_to_long_names[short_name]
add_component_operator(component_type=component_type)
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)
propertyGroup = getattr(component_meta, property_group_name, None)
definition = type_infos[component_type]
component_values_shuffler(seed= 17, property_group=propertyGroup, definition=definition, registry=registry)
print("propertyGroup", object[short_name])
# cheating / making things easier for us for complex types: we use the custom property value
assert object[short_name] == '(inverse_bindposes: Weak(Uuid(uuid: "73b3b118-7d01-4778-8bcc-4e79055f5d22")), joints: [0, 0])'