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/common/",
"examples/bevy_gltf_components/basic/", "examples/bevy_gltf_components/basic/",
"examples/bevy_gltf_blueprints/basic/", "examples/bevy_gltf_blueprints/basic/",
"examples/bevy_gltf_blueprints/basic_xpbd_physics/", "examples/bevy_gltf_blueprints/basic_xpbd_physics/",
"examples/bevy_gltf_blueprints/animation/", "examples/bevy_gltf_blueprints/animation/",
"examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles", "examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles",
"examples/bevy_gltf_blueprints/materials/", "examples/bevy_gltf_blueprints/materials/",
"examples/bevy_gltf_save_load/basic/", "examples/bevy_gltf_save_load/basic/",
"examples/bevy_registry_export/basic",
"examples/bevy_registry_export/basic"
"testing/bevy_registry_export/basic"
] ]
resolver = "2" 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: > Important:
the tooling is still in the early stages, even if it is feature complete : use with caution!. 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: ## Installation:
* grab the latest release zip file from the releases tab (choose the bevy_components releases !) * 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) ![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 ### copy & pasting
- you can also copy & paste components between objects - you can also copy & paste components between objects
@ -161,21 +182,39 @@ It will add the component to the select object
## advanced configuration ## 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 corresponding custom property values
![update custom properties](./docs/other_options.png) ![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 corresponding custom property values
![update custom properties for all](./docs/other_options2.png) ![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 ## 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/) 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 ## License
This tool, all its code, contents & assets is Dual-licensed under either of This tool, all its code, contents & assets is Dual-licensed under either of

View File

@ -133,7 +133,77 @@ UI:
- [x] update version - [x] update version
- [x] add ability to set legacy mode for bevy_gltf_components ? - [x] add ability to set legacy mode for bevy_gltf_components ?
- [ ] release all versions - [x] release all versions
- [ ] update main documentation, add compatibility version grid - [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 = { bl_info = {
"name": "bevy_components", "name": "bevy_components",
"author": "kaosigh", "author": "kaosigh",
"version": (0, 1, 0), "version": (0, 2, 0),
"blender": (3, 4, 0), "blender": (3, 4, 0),
"location": "VIEW_3D", "location": "VIEW_3D",
"description": "UI to help create Bevy blueprints and components", "description": "UI to help create Bevy blueprints and components",
@ -16,13 +16,13 @@ from bpy.props import (StringProperty)
from .helpers import load_settings from .helpers import load_settings
from .blueprints import CreateBlueprintOperator 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.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 .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 .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.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) from .components.definitions_list import (ComponentDefinitionsList, ClearComponentDefinitionsList)
@ -86,14 +86,14 @@ classes = [
AddComponentOperator, AddComponentOperator,
CopyComponentOperator, CopyComponentOperator,
PasteComponentOperator, PasteComponentOperator,
DeleteComponentOperator, RemoveComponentOperator,
GenerateComponent_From_custom_property_Operator, GenerateComponent_From_custom_property_Operator,
Toggle_ComponentVisibility, Toggle_ComponentVisibility,
ComponentDefinitionsList, ComponentDefinitionsList,
ClearComponentDefinitionsList, ClearComponentDefinitionsList,
ComponentInfos, ComponentMetadata,
ComponentsMeta, ComponentsMeta,
MissingBevyType, MissingBevyType,
ComponentsRegistry, ComponentsRegistry,
@ -102,6 +102,9 @@ classes = [
ReloadRegistryOperator, ReloadRegistryOperator,
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL,
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT,
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL,
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT,
BEVY_COMPONENTS_PT_MainPanel, BEVY_COMPONENTS_PT_MainPanel,
BEVY_COMPONENTS_PT_ComponentsPanel, BEVY_COMPONENTS_PT_ComponentsPanel,
@ -119,19 +122,14 @@ from bpy.app.handlers import persistent
@persistent @persistent
def post_load(file_name): def post_load(file_name):
print("post load", file_name)
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
registry.schemaPath = load_settings(registry.settings_save_path)["schemaPath"] if registry != None:
registry.load_schema() registry.load_settings()
generate_propertyGroups_for_components()
ensure_metadata_for_all_objects()
def register(): def register():
for cls in classes: for cls in classes:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
bpy.types.WindowManager.blueprint_name = StringProperty() bpy.types.WindowManager.blueprint_name = StringProperty()
bpy.app.handlers.load_post.append(post_load) bpy.app.handlers.load_post.append(post_load)
def unregister(): def unregister():

View File

@ -1,9 +1,10 @@
import bpy import bpy
from bpy.props import (StringProperty, BoolProperty, PointerProperty) from bpy.props import (StringProperty, BoolProperty, PointerProperty)
from bpy_types import (PropertyGroup) 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 : bpy.props.StringProperty(
name = "name", name = "name",
default = "" default = ""
@ -51,7 +52,7 @@ class ComponentsMeta(PropertyGroup):
name="infos per component", name="infos per component",
description="component" description="component"
) )
components: bpy.props.CollectionProperty(type = ComponentInfos) components: bpy.props.CollectionProperty(type = ComponentMetadata)
@classmethod @classmethod
def register(cls): 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): def add_component_to_object(object, component_definition, value=None):
cleanup_invalid_metadata(object) cleanup_invalid_metadata(object)
if object is not None: if object is not None:
print("add_component_to_object", component_definition) # print("add_component_to_object", component_definition)
long_name = component_definition["title"] long_name = component_definition["title"]
short_name = component_definition["short_name"] short_name = component_definition["short_name"]
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
if registry.type_infos == None: if not registry.has_type_infos():
raise Exception('registry type infos have not been loaded yet or ar missing !') raise Exception('registry type infos have not been loaded yet or are missing !')
definition = registry.type_infos[long_name] definition = registry.type_infos[long_name]
# now we use our pre_generated property groups to set the initial value of our custom property # 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) (_, 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: if component_definition != None:
short_name = component_definition["short_name"] short_name = component_definition["short_name"]
long_name = component_definition["title"] long_name = component_definition["title"]
property_group_name = short_name+"_ui" property_group_name = registry.get_propertyGroupName_from_shortName(short_name)
propertyGroup = None propertyGroup = None
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), 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: if property_group_name in registry.component_propertyGroups:
# we have found a matching property_group, so try to inject it # we have found a matching property_group, so try to inject it
# now inject property group # 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) propertyGroup = getattr(component_meta, property_group_name, None)
# now deal with property groups details # now deal with property groups details
@ -196,13 +197,14 @@ def upsert_component_in_object(object, component_name, registry):
return(None, None) 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: if source_object == None or target_object == None or component_name == None:
raise Exception('missing input data, cannot copy component propertryGroup') raise Exception('missing input data, cannot copy component propertryGroup')
component_definition = find_component_definition_from_short_name(component_name) component_definition = find_component_definition_from_short_name(component_name)
short_name = component_definition["short_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 registry = bpy.context.window_manager.components_registry
source_components_metadata = source_object.components_meta.components source_components_metadata = source_object.components_meta.components
@ -234,3 +236,46 @@ def apply_propertyGroup_values_to_object_customProperties(object):
if component_definition != None: if component_definition != None:
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None) value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object[component_name] = value 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 import bpy
from bpy_types import Operator from bpy_types import Operator
from bpy.props import (StringProperty) 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): class AddComponentOperator(Operator):
"""Add component to blueprint""" """Add component to blueprint"""
bl_idname = "object.addblueprint_to_component" bl_idname = "object.add_bevy_component"
bl_label = "Add component to blueprint Operator" bl_label = "Add component to blueprint Operator"
bl_options = {"UNDO"} bl_options = {"UNDO"}
component_type: StringProperty( component_type: StringProperty(
name="component_type", name="component_type",
description="component type to add", description="component type to add",
) ) # type: ignore
def execute(self, context): def execute(self, context):
print("adding component to blueprint", self.component_type)
object = context.object object = context.object
print("adding component ", self.component_type, "to object '"+object.name+"'")
has_component_type = self.component_type != "" has_component_type = self.component_type != ""
if has_component_type and object != None: if has_component_type and object != None:
@ -30,19 +30,19 @@ class AddComponentOperator(Operator):
class CopyComponentOperator(Operator): class CopyComponentOperator(Operator):
"""Copy component from blueprint""" """Copy component from blueprint"""
bl_idname = "object.copy_component" bl_idname = "object.copy_bevy_component"
bl_label = "Copy component Operator" bl_label = "Copy component Operator"
bl_options = {"UNDO"} bl_options = {"UNDO"}
source_component_name: StringProperty( source_component_name: StringProperty(
name="source component_name", name="source component_name",
description="name of the component to copy", description="name of the component to copy",
) ) # type: ignore
source_object_name: StringProperty( source_object_name: StringProperty(
name="source object name", name="source object name",
description="name of the object to copy the component from", description="name of the object to copy the component from",
) ) # type: ignore
@classmethod @classmethod
def register(cls): def register(cls):
@ -67,7 +67,7 @@ class CopyComponentOperator(Operator):
class PasteComponentOperator(Operator): class PasteComponentOperator(Operator):
"""Paste component to blueprint""" """Paste component to blueprint"""
bl_idname = "object.paste_component" bl_idname = "object.paste_bevy_component"
bl_label = "Paste component to blueprint Operator" bl_label = "Paste component to blueprint Operator"
bl_options = {"UNDO"} bl_options = {"UNDO"}
@ -80,67 +80,79 @@ class PasteComponentOperator(Operator):
else: else:
component_name = context.window_manager.copied_source_component_name component_name = context.window_manager.copied_source_component_name
if not component_name in source_object: 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: else:
component_value = source_object[component_name] component_value = source_object[component_name]
print("pasting component to object: component name:", str(component_name), "component value:" + str(component_value)) print("pasting component to object: component name:", str(component_name), "component value:" + str(component_value))
print (context.object) 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'} return {'FINISHED'}
class DeleteComponentOperator(Operator): class RemoveComponentOperator(Operator):
"""Delete component from blueprint""" """Delete component from blueprint"""
bl_idname = "object.delete_component" bl_idname = "object.remove_bevy_component"
bl_label = "Delete component from blueprint Operator" bl_label = "Delete component from blueprint Operator"
bl_options = {"UNDO"} bl_options = {"UNDO"}
component_name: StringProperty( component_name: StringProperty(
name="component name", name="component name",
description="component to delete", description="component to delete",
) ) # type: ignore
def execute(self, context): def execute(self, context):
object = context.object object = context.object
print("removing component ", self.component_name, "from object '"+object.name+"'")
if object is not None and self.component_name in object: 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: 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'} return {'FINISHED'}
class GenerateComponent_From_custom_property_Operator(Operator): class GenerateComponent_From_custom_property_Operator(Operator):
"""generate components from custom property""" """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_label = "Generate component from custom_property Operator"
bl_options = {"UNDO"} bl_options = {"UNDO"}
component_name: StringProperty( component_name: StringProperty(
name="component name", name="component name",
description="component to generate custom properties for", description="component to generate custom properties for",
) ) # type: ignore
def execute(self, context): def execute(self, context):
object = context.object 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'} return {'FINISHED'}
class Toggle_ComponentVisibility(Operator): class Toggle_ComponentVisibility(Operator):
"""toggles components visibility""" """toggles components visibility"""
bl_idname = "object.toggle_component_visibility" bl_idname = "object.toggle_bevy_component_visibility"
bl_label = "Toggle component visibility" bl_label = "Toggle component visibility"
bl_options = {"UNDO"} bl_options = {"UNDO"}
component_name: StringProperty( component_name: StringProperty(
name="component name", name="component name",
description="component to toggle", description="component to toggle",
) ) # type: ignore
def execute(self, context): def execute(self, context):
object = context.object object = context.object

View File

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

View File

@ -1,7 +1,5 @@
import json
from bpy_types import PropertyGroup from bpy_types import PropertyGroup
conversion_tables = { conversion_tables = {
"bool": lambda value: value, "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) value = getattr(property_group, variant_name)
is_property_group = isinstance(value, PropertyGroup) is_property_group = isinstance(value, PropertyGroup)
child_property_group = value if is_property_group else None 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 = 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: elif "properties" in variant_definition:
value = getattr(property_group, variant_name) value = getattr(property_group, variant_name)
is_property_group = isinstance(value, PropertyGroup) 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 = 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,)
else: else:
print("basic enum stuff") value = getattr(property_group, variant_name)
value = selected # here the value of the enum is just the name of the variant 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: else:
value = selected value = selected
@ -131,9 +135,13 @@ def property_group_value_to_custom_property_value(property_group, definition, re
value.append(item_value) value.append(item_value)
else: else:
value = conversion_tables[type_name](value) if is_value_type else value 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)) #print("generating custom property value", value, type(value))
if isinstance(value, str):
value = value.replace("'", "")
if parent == None: if parent == None:
value = str(value).replace("'", "") value = str(value).replace("'", "")
value = 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") value = value.replace("True", "true").replace("False", "false")
return value 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 root_component = nesting[0] if len(nesting) > 0 else component_name
# print("DONE:",short_name,"__annotations__", __annotations__) # print("DONE:",short_name,"__annotations__", __annotations__)
# print("") # print("")
# property_group_name = short_name+"_ui"
property_group_params = { property_group_params = {
**extras, **extras,
'__annotations__': __annotations__, '__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" -BasicTest => the registration & update callback of this one overwrites the first "basicTest"
have not found a cleaner workaround so far 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) (property_group_pointer, property_group_class) = property_group_from_infos(property_group_name, property_group_params)
# add our component propertyGroup to the registry # add our component propertyGroup to the registry
registry.register_component_propertyGroup(property_group_name, property_group_pointer) 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 type_def = definition["type"] if "type" in definition else None
values = definition["oneOf"] values = definition["oneOf"]
nesting = nesting+ [short_name] nesting = nesting + [short_name]
__annotations__ = {} __annotations__ = {}
original_type_name = "enum" original_type_name = "enum"

View File

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

View File

@ -1,5 +1,5 @@
import bpy 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 .process_component import process_component
from .utils import update_calback_helper 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 registry = bpy.context.window_manager.components_registry
current_object = bpy.context.object current_object = bpy.context.object
update_disabled = current_object["__disable__update"] if "__disable__update" in current_object else False 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: if update_disabled:
return return
print("") 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 components_in_object = current_object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == component_name, components_in_object), None) component_meta = next(filter(lambda component: component["name"] == component_name, components_in_object), None)
if component_meta != 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 # we use our helper to set the values
context.object[component_name] = property_group_value_to_custom_property_value(self, definition, registry, None) context.object[component_name] = property_group_value_to_custom_property_value(self, definition, registry, None)
def generate_propertyGroups_for_components(): def generate_propertyGroups_for_components():
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
if registry.type_infos == None: if not registry.has_type_infos():
registry.load_type_infos() registry.load_type_infos()
type_infos = registry.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 is_component = definition['isComponent'] if "isComponent" in definition else False
root_property_name = short_name if is_component else None root_property_name = short_name if is_component else None
process_component(registry, definition, update_calback_helper(definition, update_component, root_property_name), 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 # if we had to add any wrapper types on the fly, process them now
registry.process_custom_types() 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 bpy_extras.io_utils import ImportHelper
from ..helpers import upsert_settings from ..helpers import upsert_settings
from ..components.metadata import apply_propertyGroup_values_to_object_customProperties, ensure_metadata_for_all_objects from ..components.metadata import apply_customProperty_values_to_object_propertyGroups, apply_propertyGroup_values_to_object_customProperties, ensure_metadata_for_all_objects
from ..components.operators import GenerateComponent_From_custom_property_Operator
from ..propGroups.prop_groups import generate_propertyGroups_for_components from ..propGroups.prop_groups import generate_propertyGroups_for_components
class ReloadRegistryOperator(Operator): class ReloadRegistryOperator(Operator):
@ -18,7 +17,7 @@ class ReloadRegistryOperator(Operator):
component_type: StringProperty( component_type: StringProperty(
name="component_type", name="component_type",
description="component type to add", description="component type to add",
) ) # type: ignore
def execute(self, context): def execute(self, context):
print("reload registry") print("reload registry")
@ -28,7 +27,12 @@ class ReloadRegistryOperator(Operator):
print("") print("")
print("") print("")
ensure_metadata_for_all_objects() 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'} return {'FINISHED'}
@ -47,7 +51,7 @@ class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL(Operator):
return {'FINISHED'} return {'FINISHED'}
class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT(Operator): 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_idname = "object.refresh_custom_properties_current"
bl_label = "Apply Registry to current object" bl_label = "Apply Registry to current object"
bl_options = {"UNDO"} bl_options = {"UNDO"}
@ -57,6 +61,53 @@ class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT(Operator):
object = context.object object = context.object
apply_propertyGroup_values_to_object_customProperties(object) apply_propertyGroup_values_to_object_customProperties(object)
return {'FINISHED'} 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): class OT_OpenFilebrowser(Operator, ImportHelper):
"""Browse for registry json file""" """Browse for registry json file"""
@ -66,7 +117,7 @@ class OT_OpenFilebrowser(Operator, ImportHelper):
filter_glob: StringProperty( filter_glob: StringProperty(
default='*.json', default='*.json',
options={'HIDDEN'} options={'HIDDEN'}
) ) # type: ignore
def execute(self, context): def execute(self, context):
"""Do something with the selected file(s).""" """Do something with the selected file(s)."""
#filename, extension = os.path.splitext(self.filepath) #filename, extension = os.path.splitext(self.filepath)

View File

@ -1,16 +1,58 @@
import bpy import bpy
import json import json
import os import os
import uuid
from pathlib import Path from pathlib import Path
from bpy_types import (PropertyGroup) from bpy_types import (PropertyGroup)
from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty) from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty)
from ..components.metadata import 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 # helper class to store missing bevy types information
class MissingBevyType(bpy.types.PropertyGroup): class MissingBevyType(bpy.types.PropertyGroup):
type_name: bpy.props.StringProperty( type_name: bpy.props.StringProperty(
name="type", 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 # this is where we store the information for all available components
class ComponentsRegistry(PropertyGroup): class ComponentsRegistry(PropertyGroup):
@ -21,20 +63,45 @@ class ComponentsRegistry(PropertyGroup):
name="schema path", name="schema path",
description="path to the registry schema file", description="path to the registry schema file",
default="registry.json" default="registry.json"
) )# type: ignore
schemaFullPath : bpy.props.StringProperty(
name="schema full path",
description="path to the registry schema file",
)# type: ignore
registry: bpy.props. StringProperty( registry: bpy.props. StringProperty(
name="registry", name="registry",
description="component registry" description="component registry"
) )# type: ignore
missing_type_infos: StringProperty( missing_type_infos: StringProperty(
name="missing type infos", name="missing type infos",
description="unregistered/missing type infos" description="unregistered/missing type infos"
) )# type: ignore
missing_types_list: CollectionProperty(name="missing types list", type=MissingBevyType) disable_all_object_updates: BoolProperty(name="disable_object_updates", default=False) # type: ignore
missing_types_list_index: IntProperty(name = "Index for missing types list", default = 0)
## 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 = { blender_property_mapping = {
"bool": dict(type=BoolProperty, presets=dict()), "bool": dict(type=BoolProperty, presets=dict()),
@ -52,6 +119,7 @@ class ComponentsRegistry(PropertyGroup):
"i32":dict(type=IntProperty, presets=dict()), "i32":dict(type=IntProperty, presets=dict()),
"i64":dict(type=IntProperty, presets=dict()), "i64":dict(type=IntProperty, presets=dict()),
"i128":dict(type=IntProperty, presets=dict()), "i128":dict(type=IntProperty, presets=dict()),
"isize": dict(type=IntProperty, presets=dict()),
"f32": dict(type=FloatProperty, presets=dict()), "f32": dict(type=FloatProperty, presets=dict()),
"f64": dict(type=FloatProperty, presets=dict()), "f64": dict(type=FloatProperty, presets=dict()),
@ -77,9 +145,14 @@ class ComponentsRegistry(PropertyGroup):
"char": dict(type=StringProperty, presets=dict()), "char": dict(type=StringProperty, presets=dict()),
"str": dict(type=StringProperty, presets=dict()), "str": dict(type=StringProperty, presets=dict()),
"alloc::string::String": 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()), "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, "u32":0,
"u64":0, "u64":0,
"u128":0, "u128":0,
"usize":0,
"i8": 0, "i8": 0,
"i16":0, "i16":0,
"i32":0, "i32":0,
"i64":0, "i64":0,
"i128":0, "i128":0,
"isize":0,
"f32": 0.0, "f32": 0.0,
"f64":0.0, "f64":0.0,
@ -111,6 +186,7 @@ class ComponentsRegistry(PropertyGroup):
"char": " ", "char": " ",
"str": " ", "str": " ",
"alloc::string::String": " ", "alloc::string::String": " ",
"alloc::borrow::Cow<str>": " ",
"glam::Vec2": [0.0, 0.0], "glam::Vec2": [0.0, 0.0],
"glam::DVec2": [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], "glam::Quat": [0.0, 0.0, 0.0, 0.0],
"bevy_render::color::Color": [1.0, 1.0, 0.0, 1.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 = [] type_infos_missing = []
component_propertyGroups = {} component_propertyGroups = {}
short_names_to_long_names = {} short_names_to_long_names = {}
custom_types_to_add = {}
invalid_components = []
@classmethod @classmethod
def register(cls): def register(cls):
bpy.types.WindowManager.components_registry = PointerProperty(type=ComponentsRegistry) bpy.types.WindowManager.components_registry = PointerProperty(type=ComponentsRegistry)
bpy.context.window_manager.components_registry.watcher_active = False
@classmethod @classmethod
def unregister(cls): def unregister(cls):
bpy.context.window_manager.components_registry.watcher_active = False
for propgroup_name in cls.component_propertyGroups.keys(): for propgroup_name in cls.component_propertyGroups.keys():
try: try:
delattr(ComponentInfos, propgroup_name) delattr(ComponentMetadata, propgroup_name)
print("unregistered propertyGroup", propgroup_name) #print("unregistered propertyGroup", propgroup_name)
except Exception as error: except Exception as error:
pass pass
#print("failed to remove", error, "ComponentInfos") #print("failed to remove", error, "ComponentMetadata")
del bpy.types.WindowManager.components_registry try:
bpy.app.timers.unregister(watch_schema)
def load_schema(self): except Exception as error:
# cleanup missing types list print("failed to unregister", error)
self.missing_types_list.clear() pass
self.type_infos = None
self.type_infos_missing.clear()
file_path = bpy.data.filepath
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 # Get the folder
folder_path = os.path.dirname(file_path) folder_path = os.path.dirname(file_path)
path = os.path.join(folder_path, self.schemaPath) path = os.path.join(folder_path, self.schemaPath)
self.schemaFullPath = path
f = Path(bpy.path.abspath(path)) # make a path object of abs path f = Path(bpy.path.abspath(path)) # make a path object of abs path
with open(path) as f: with open(path) as f:
@ -168,9 +270,32 @@ class ComponentsRegistry(PropertyGroup):
defs = data["$defs"] defs = data["$defs"]
self.registry = json.dumps(defs) # FIXME:meh ? 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 # we load the json once, so we do not need to do it over & over again
def load_type_infos(self): def load_type_infos(self):
print("load type infos")
ComponentsRegistry.type_infos = json.loads(self.registry) 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 # we keep a list of component propertyGroup around
def register_component_propertyGroup(self, name, propertyGroup): def register_component_propertyGroup(self, name, propertyGroup):
@ -188,7 +313,6 @@ class ComponentsRegistry(PropertyGroup):
item = self.missing_types_list.add() item = self.missing_types_list.add()
item.type_name = type_name item.type_name = type_name
custom_types_to_add = {}
def add_custom_type(self, type_name, type_definition): def add_custom_type(self, type_name, type_definition):
self.custom_types_to_add[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.type_infos[type_name] = self.custom_types_to_add[type_name]
self.custom_types_to_add.clear() self.custom_types_to_add.clear()
invalid_components = []
def add_invalid_component(self, component_name): def add_invalid_component(self, component_name):
self.invalid_components.append(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 object[component_definition.name] = 0.5
property_manager = object.id_properties_ui(component_definition.name) property_manager = object.id_properties_ui(component_definition.name)

View File

@ -1,6 +1,11 @@
import bpy import bpy
from bpy_types import (UIList) 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): class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
bl_idname = "BEVY_COMPONENTS_PT_Configuration" bl_idname = "BEVY_COMPONENTS_PT_Configuration"
@ -16,6 +21,7 @@ class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
registry = context.window_manager.components_registry registry = context.window_manager.components_registry
registry_has_type_infos = registry.has_type_infos()
selected_object = context.selected_objects[0] if len(context.selected_objects) > 0 else None selected_object = context.selected_objects[0] if len(context.selected_objects) > 0 else None
row = layout.row() row = layout.row()
@ -28,6 +34,12 @@ class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
layout.separator() layout.separator()
layout.operator(ReloadRegistryOperator.bl_idname, text="reload registry" , icon="FILE_REFRESH") 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()
layout.separator() layout.separator()
@ -36,13 +48,26 @@ class BEVY_COMPONENTS_PT_Configuration(bpy.types.Panel):
row.alert = True row.alert = True
row = layout.row() row = layout.row()
row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT.bl_idname, text="update custom properties of current object" , icon="FILE_REFRESH") row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT.bl_idname, text="update custom properties of current object" , icon="LOOP_FORWARDS")
row.enabled = registry.type_infos != None and selected_object is not None row.enabled = registry_has_type_infos and selected_object is not None
layout.separator() layout.separator()
row = layout.row() row = layout.row()
row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL.bl_idname, text="update custom properties of ALL objects" , icon="FILE_REFRESH") row.operator(COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL.bl_idname, text="update custom properties of ALL objects" , icon="LOOP_FORWARDS")
row.enabled = registry.type_infos != None 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): 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])'