Compare commits

...

20 Commits

Author SHA1 Message Date
Lixou 844176f297
Merge e464c49592 into d5f14bc037 2024-09-09 12:28:29 +02:00
DasLixou e464c49592 Getting rid of most of unsafe stuff 2024-09-09 12:28:23 +02:00
DasLixou 20f37715e4 Update blender example file 2024-08-22 19:18:13 +02:00
DasLixou e47fe1a4bd didn't meant to commit that 2024-08-22 19:03:58 +02:00
DasLixou 3e24601f1d remove todo from example 2024-08-22 18:09:19 +02:00
DasLixou 4aa2ef382c HOLY SHIT THAT WORKS 2024-08-22 18:09:18 +02:00
DasLixou 3e14cc06bf better example 2024-08-22 18:09:18 +02:00
DasLixou 4d9c451de8 Fix type shenanigans 2024-08-22 18:09:18 +02:00
DasLixou cc71436187 The deserialzation hack.. 2024-08-22 18:09:18 +02:00
DasLixou 1b6dec8f5a fix downcast 2024-08-22 18:09:18 +02:00
DasLixou 0ad9f13f1d I cracked reflection? 2024-08-22 18:09:18 +02:00
DasLixou 72681ee9f0 oh look i didn't change anything but it works now :p 2024-08-22 18:09:18 +02:00
DasLixou 4e50af2453 faking named entity in type registry 2024-08-22 18:09:18 +02:00
DasLixou 2bc47ad624 Add relations example 2024-08-22 18:09:17 +02:00
DasLixou 7f4c8d34f3 fix for all propgroups other than tuple, that already worked 2024-08-22 18:09:17 +02:00
DasLixou 0e62da4d19 better serialization 2024-08-22 18:09:17 +02:00
DasLixou bf075b41ef serialize with double quotes 2024-08-22 18:09:17 +02:00
DasLixou ec42c6d2bd save and load? 2024-08-22 18:09:17 +02:00
DasLixou 2ffadc19eb only pick objects in same scene 2024-08-22 18:09:17 +02:00
DasLixou 6b39b5f2b1 Replace number with selector field 2024-08-22 18:09:17 +02:00
21 changed files with 15179 additions and 55 deletions

View File

@ -0,0 +1,272 @@
use std::{alloc::Layout, cell::Cell, num::NonZeroU32};
use bevy::{
core::Name,
ecs::system::SystemParam,
gltf::GltfExtras,
log::{info, warn},
prelude::{HierarchyQueryExt, Parent, Query, With},
reflect::ReflectDeserialize,
scene::{InstanceId, SceneInstance},
};
use serde::Deserialize;
#[derive(SystemParam)]
pub(crate) struct BadWorldAccess<'w, 's> {
pub(crate) names: Query<'w, 's, (bevy::ecs::entity::Entity, &'static Name), With<GltfExtras>>,
pub(crate) hierarchy: Query<'w, 's, &'static Parent, ()>,
pub(crate) scene_instances: Query<'w, 's, &'static SceneInstance, ()>,
}
thread_local! {
pub(crate) static BAD_WORLD_ACCESS: Cell<Option<BadWorldAccess<'static, 'static>>> = Cell::new(None);
pub(crate) static INSTANCE_ID: Cell<Option<InstanceId>> = Cell::new(None);
}
const _: () = {
let real = Layout::new::<bevy::ecs::entity::Entity>();
let fake = Layout::new::<Entity>();
assert!(real.size() == fake.size());
assert!(real.align() == fake.align());
};
#[derive(Clone)]
#[repr(C, align(8))]
pub(crate) struct Entity {
// Do not reorder the fields here. The ordering is equivalent to bevy's `Entity`
#[cfg(target_endian = "little")]
index: u32,
generation: NonZeroU32,
#[cfg(target_endian = "big")]
index: u32,
}
impl<'de> Deserialize<'de> for Entity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename = "Entity")]
struct EntityData {
name: Option<String>,
}
let entity_data = EntityData::deserialize(deserializer)?;
let entity = if let Some(name) = entity_data.name {
info!("Found name {name}");
let BadWorldAccess {
names,
hierarchy,
scene_instances,
} = BAD_WORLD_ACCESS.take().expect("No bad world access :c");
let instance = INSTANCE_ID.get().expect("No instance id set :c");
let mut target = None;
'search: for (e, n) in names.iter() {
if !name.eq(n.as_str()) {
continue;
}
for parent in hierarchy.iter_ancestors(e) {
let Ok(id) = scene_instances.get(parent) else {
continue;
};
if instance.eq(id) {
target = Some(e);
break 'search;
}
}
}
BAD_WORLD_ACCESS.set(Some(BadWorldAccess {
names,
hierarchy,
scene_instances,
}));
target.unwrap_or_else(|| {
warn!("No entity found for '{name}' - perhaps it doesn't contain any components from blender?");
bevy::ecs::entity::Entity::PLACEHOLDER
})
} else {
warn!("No object was specified for Entity relation, using `Entity::PLACEHOLDER`.");
bevy::ecs::entity::Entity::PLACEHOLDER
};
Ok(unsafe {
// SAFETY: both have the same layout
core::mem::transmute(entity)
})
}
}
// This is expanded and modified from
// ```
// #[derive(Clone, Reflect)]
// #[reflect_value(Deserialize)]
// ```
const _: () = {
use bevy::reflect as bevy_reflect;
#[allow(unused_mut)]
impl bevy_reflect::GetTypeRegistration for Entity
where
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
{
fn get_type_registration() -> bevy_reflect::TypeRegistration {
let mut registration = bevy_reflect::TypeRegistration::of::<Self>();
registration
.insert::<
bevy_reflect::ReflectFromPtr,
>(bevy_reflect::FromType::<Self>::from_type());
registration.insert::<bevy_reflect::ReflectFromReflect>(
bevy_reflect::FromType::<Self>::from_type(),
);
registration.insert::<ReflectDeserialize>(bevy_reflect::FromType::<Self>::from_type());
registration
}
}
impl bevy_reflect::TypePath for Entity
where
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
{
fn type_path() -> &'static str {
"bevy_ecs::entity::Entity" // this is changed
}
fn short_type_path() -> &'static str {
"Entity"
}
fn type_ident() -> Option<&'static str> {
::core::option::Option::Some("Entity")
}
fn crate_name() -> Option<&'static str> {
// this is changed
::core::option::Option::Some("bevy_ecs::entity".split(':').next().unwrap())
}
fn module_path() -> Option<&'static str> {
::core::option::Option::Some("bevy_ecs::entity") // this is changed
}
}
impl bevy_reflect::Typed for Entity
where
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
{
fn type_info() -> &'static bevy_reflect::TypeInfo {
static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
bevy_reflect::utility::NonGenericTypeInfoCell::new();
CELL.get_or_set(|| {
let info = bevy_reflect::ValueInfo::new::<bevy::ecs::entity::Entity>(); // this is changed
bevy_reflect::TypeInfo::Value(info)
})
}
}
impl bevy_reflect::Reflect for Entity
where
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
{
#[inline]
fn get_represented_type_info(
&self,
) -> ::core::option::Option<&'static bevy_reflect::TypeInfo> {
::core::option::Option::Some(<Self as bevy_reflect::Typed>::type_info())
}
#[inline]
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn ::core::any::Any> {
// this is changed
unsafe {
core::mem::transmute::<
::std::boxed::Box<Entity>,
::std::boxed::Box<bevy::ecs::entity::Entity>,
>(self)
}
}
#[inline]
fn as_any(&self) -> &dyn ::core::any::Any {
// this is changed
unsafe { core::mem::transmute::<&Entity, &bevy::ecs::entity::Entity>(self) }
}
#[inline]
fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any {
// this is changed
unsafe { core::mem::transmute::<&mut Entity, &mut bevy::ecs::entity::Entity>(self) }
}
#[inline]
fn into_reflect(
self: ::std::boxed::Box<Self>,
) -> ::std::boxed::Box<dyn bevy_reflect::Reflect> {
self
}
#[inline]
fn as_reflect(&self) -> &dyn bevy_reflect::Reflect {
self
}
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn bevy_reflect::Reflect {
self
}
#[inline]
fn clone_value(&self) -> ::std::boxed::Box<dyn bevy_reflect::Reflect> {
::std::boxed::Box::new(::core::clone::Clone::clone(self))
}
#[inline]
fn try_apply(
&mut self,
value: &dyn bevy_reflect::Reflect,
) -> ::core::result::Result<(), bevy_reflect::ApplyError> {
let any = bevy_reflect::Reflect::as_any(value);
if let ::core::option::Option::Some(value) =
<dyn ::core::any::Any>::downcast_ref::<Self>(any)
{
*self = ::core::clone::Clone::clone(value);
} else {
return ::core::result::Result::Err(bevy_reflect::ApplyError::MismatchedTypes {
from_type: ::core::convert::Into::into(
bevy_reflect::DynamicTypePath::reflect_type_path(value),
),
to_type: ::core::convert::Into::into(
<Self as bevy_reflect::TypePath>::type_path(),
),
});
}
::core::result::Result::Ok(())
}
#[inline]
fn set(
&mut self,
value: ::std::boxed::Box<dyn bevy_reflect::Reflect>,
) -> ::core::result::Result<(), ::std::boxed::Box<dyn bevy_reflect::Reflect>> {
*self = <dyn bevy_reflect::Reflect>::take(value)?;
::core::result::Result::Ok(())
}
#[inline]
fn reflect_kind(&self) -> bevy_reflect::ReflectKind {
bevy_reflect::ReflectKind::Value
}
#[inline]
fn reflect_ref(&self) -> bevy_reflect::ReflectRef {
bevy_reflect::ReflectRef::Value(self)
}
#[inline]
fn reflect_mut(&mut self) -> bevy_reflect::ReflectMut {
bevy_reflect::ReflectMut::Value(self)
}
#[inline]
fn reflect_owned(self: ::std::boxed::Box<Self>) -> bevy_reflect::ReflectOwned {
bevy_reflect::ReflectOwned::Value(self)
}
}
impl bevy_reflect::FromReflect for Entity
where
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
{
fn from_reflect(reflect: &dyn bevy_reflect::Reflect) -> ::core::option::Option<Self> {
::core::option::Option::Some(::core::clone::Clone::clone(
<dyn ::core::any::Any>::downcast_ref::<Entity>(
<dyn bevy_reflect::Reflect>::as_any(reflect),
)?,
))
}
}
};

View File

@ -7,6 +7,8 @@ pub use ronstring_to_reflect_component::*;
pub mod process_gltfs; pub mod process_gltfs;
pub use process_gltfs::*; pub use process_gltfs::*;
mod fake_entity;
pub mod blender_settings; pub mod blender_settings;
use bevy::{ use bevy::{

View File

@ -1,20 +1,27 @@
use std::ops::Deref;
use bevy::{ use bevy::{
core::Name, core::Name,
ecs::{ ecs::{
entity::Entity, entity::Entity,
query::{Added, Without}, query::{Added, Without},
reflect::{AppTypeRegistry, ReflectComponent}, reflect::{AppTypeRegistry, ReflectComponent},
system::{SystemParam, SystemState},
world::World, world::World,
}, },
gltf::{GltfExtras, GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras}, gltf::{GltfExtras, GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras},
hierarchy::Parent, hierarchy::Parent,
log::{debug, warn}, log::{debug, warn},
prelude::{HierarchyQueryExt, Local, Query, Res},
reflect::{Reflect, TypeRegistration}, reflect::{Reflect, TypeRegistration},
scene::SceneInstance,
utils::HashMap, utils::HashMap,
}; };
use crate::{ronstring_to_reflect_component, GltfProcessed}; use crate::{ronstring_to_reflect_component, GltfProcessed};
use super::fake_entity::{self, BadWorldAccess};
// , mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> // , mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>>
fn find_entity_components( fn find_entity_components(
entity: Entity, entity: Entity,
@ -55,79 +62,172 @@ fn find_entity_components(
(target_entity, reflect_components) (target_entity, reflect_components)
} }
#[derive(SystemParam)]
#[doc(hidden)]
pub struct ExtrasComponentQueries<'w, 's> {
extras: Query<
'w,
's,
(Entity, Option<&'static Name>, &'static GltfExtras),
(Added<GltfExtras>, Without<GltfProcessed>),
>,
scene_extras: Query<
'w,
's,
(Entity, Option<&'static Name>, &'static GltfSceneExtras),
(Added<GltfSceneExtras>, Without<GltfProcessed>),
>,
mesh_extras: Query<
'w,
's,
(Entity, Option<&'static Name>, &'static GltfMeshExtras),
(Added<GltfMeshExtras>, Without<GltfProcessed>),
>,
material_extras: Query<
'w,
's,
(Entity, Option<&'static Name>, &'static GltfMaterialExtras),
(Added<GltfMaterialExtras>, Without<GltfProcessed>),
>,
// Hierarchy and Scene instances are both here and in BadWorldAccess, but they don't clash because read-only.
bad_world_access: BadWorldAccess<'w, 's>,
hierarchy: Query<'w, 's, &'static Parent>,
scene_instances: Query<'w, 's, &'static SceneInstance>,
type_registry: Res<'w, AppTypeRegistry>,
}
/// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection /// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection
pub fn add_components_from_gltf_extras(world: &mut World) { pub fn add_components_from_gltf_extras(
let mut extras = world.query_filtered::<(Entity, Option<&Name>, &GltfExtras, Option<&Parent>), (Added<GltfExtras>, Without<GltfProcessed>)>(); world: &mut World,
let mut scene_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfSceneExtras, Option<&Parent>), (Added<GltfSceneExtras>, Without<GltfProcessed>)>(); mut queries_state: Local<Option<SystemState<ExtrasComponentQueries<'_, '_>>>>,
let mut mesh_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMeshExtras, Option<&Parent>), (Added<GltfMeshExtras>, Without<GltfProcessed>)>(); ) {
let mut material_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMaterialExtras, Option<&Parent>), (Added<GltfMaterialExtras>, Without<GltfProcessed>)>(); let state = queries_state.get_or_insert_with(|| SystemState::new(world));
let ExtrasComponentQueries {
extras,
scene_extras,
mesh_extras,
material_extras,
bad_world_access,
hierarchy,
scene_instances,
type_registry,
} = state.get_mut(world);
let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> = let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> =
HashMap::new(); HashMap::new();
// let gltf_components_config = world.resource::<GltfComponentsConfig>(); // let gltf_components_config = world.resource::<GltfComponentsConfig>();
for (entity, name, extra, parent) in extras.iter(world) { unsafe {
// SAFETY: we set this to `None` again before using world again and fake_entity just uses it in that time.
fake_entity::BAD_WORLD_ACCESS.set(Some(core::mem::transmute(bad_world_access)));
}
for (entity, name, extra) in extras.iter() {
let parent = hierarchy.get(entity).ok();
debug!( debug!(
"Gltf Extra: Name: {:?}, entity {:?}, parent: {:?}, extras {:?}", "Gltf Extra: Name: {:?}, entity {:?}, parent: {:?}, extras {:?}",
name, entity, parent, extra name, entity, parent, extra
); );
let type_registry: &AppTypeRegistry = world.resource(); if let Some(instance) = hierarchy
let type_registry = type_registry.read(); .iter_ancestors(entity)
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry); .find_map(|p| scene_instances.get(p).ok())
{
fake_entity::INSTANCE_ID.set(Some(*instance.deref()));
} else {
warn!("Can't find higher-hierarchy `SceneInstance` for entity '{name:?}'");
fake_entity::INSTANCE_ID.set(None);
};
let mut type_registry = type_registry.write();
let reflect_components = ronstring_to_reflect_component(&extra.value, &mut type_registry);
// let name = name.unwrap_or(&Name::new("")); // let name = name.unwrap_or(&Name::new(""));
let (target_entity, updated_components) = let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components); find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components); entity_components.insert(target_entity, updated_components);
} }
for (entity, name, extra, parent) in scene_extras.iter(world) { for (entity, name, extra) in scene_extras.iter() {
let parent = hierarchy.get(entity).ok();
debug!( debug!(
"Gltf Scene Extra: Name: {:?}, entity {:?}, parent: {:?}, scene_extras {:?}", "Gltf Scene Extra: Name: {:?}, entity {:?}, parent: {:?}, scene_extras {:?}",
name, entity, parent, extra name, entity, parent, extra
); );
let type_registry: &AppTypeRegistry = world.resource(); if let Some(instance) = hierarchy
let type_registry = type_registry.read(); .iter_ancestors(entity)
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry); .find_map(|p| scene_instances.get(p).ok())
{
fake_entity::INSTANCE_ID.set(Some(*instance.deref()));
} else {
warn!("Can't find higher-hierarchy `SceneInstance` for entity '{name:?}'");
fake_entity::INSTANCE_ID.set(None);
};
let mut type_registry = type_registry.write();
let reflect_components = ronstring_to_reflect_component(&extra.value, &mut type_registry);
let (target_entity, updated_components) = let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components); find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components); entity_components.insert(target_entity, updated_components);
} }
for (entity, name, extra, parent) in mesh_extras.iter(world) { for (entity, name, extra) in mesh_extras.iter() {
let parent = hierarchy.get(entity).ok();
debug!( debug!(
"Gltf Mesh Extra: Name: {:?}, entity {:?}, parent: {:?}, mesh_extras {:?}", "Gltf Mesh Extra: Name: {:?}, entity {:?}, parent: {:?}, mesh_extras {:?}",
name, entity, parent, extra name, entity, parent, extra
); );
let type_registry: &AppTypeRegistry = world.resource(); if let Some(instance) = hierarchy
let type_registry = type_registry.read(); .iter_ancestors(entity)
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry); .find_map(|p| scene_instances.get(p).ok())
{
fake_entity::INSTANCE_ID.set(Some(*instance.deref()));
} else {
warn!("Can't find higher-hierarchy `SceneInstance` for entity '{name:?}'");
fake_entity::INSTANCE_ID.set(None);
};
let mut type_registry = type_registry.write();
let reflect_components = ronstring_to_reflect_component(&extra.value, &mut type_registry);
let (target_entity, updated_components) = let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components); find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components); entity_components.insert(target_entity, updated_components);
} }
for (entity, name, extra, parent) in material_extras.iter(world) { for (entity, name, extra) in material_extras.iter() {
let parent = hierarchy.get(entity).ok();
debug!( debug!(
"Name: {:?}, entity {:?}, parent: {:?}, material_extras {:?}", "Name: {:?}, entity {:?}, parent: {:?}, material_extras {:?}",
name, entity, parent, extra name, entity, parent, extra
); );
let type_registry: &AppTypeRegistry = world.resource(); if let Some(instance) = hierarchy
let type_registry = type_registry.read(); .iter_ancestors(entity)
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry); .find_map(|p| scene_instances.get(p).ok())
{
fake_entity::INSTANCE_ID.set(Some(*instance.deref()));
} else {
warn!("Can't find higher-hierarchy `SceneInstance` for entity '{name:?}'");
fake_entity::INSTANCE_ID.set(None);
};
let mut type_registry = type_registry.write();
let reflect_components = ronstring_to_reflect_component(&extra.value, &mut type_registry);
let (target_entity, updated_components) = let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components); find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components); entity_components.insert(target_entity, updated_components);
} }
fake_entity::BAD_WORLD_ACCESS.set(None);
fake_entity::INSTANCE_ID.set(None);
for (entity, components) in entity_components { for (entity, components) in entity_components {
let type_registry: &AppTypeRegistry = world.resource(); let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.clone(); let type_registry = type_registry.clone();

View File

@ -1,15 +1,17 @@
use std::any::TypeId;
use bevy::log::{debug, warn}; use bevy::log::{debug, warn};
use bevy::reflect::serde::ReflectDeserializer; use bevy::reflect::serde::ReflectDeserializer;
use bevy::reflect::{Reflect, TypeRegistration, TypeRegistry}; use bevy::reflect::{GetTypeRegistration, Reflect, TypeRegistration, TypeRegistry};
use bevy::utils::HashMap; use bevy::utils::HashMap;
use ron::Value; use ron::Value;
use serde::de::DeserializeSeed; use serde::de::DeserializeSeed;
use super::capitalize_first_letter; use super::{capitalize_first_letter, fake_entity};
pub fn ronstring_to_reflect_component( pub fn ronstring_to_reflect_component(
ron_string: &str, ron_string: &str,
type_registry: &TypeRegistry, type_registry: &mut TypeRegistry,
) -> Vec<(Box<dyn Reflect>, TypeRegistration)> { ) -> Vec<(Box<dyn Reflect>, TypeRegistration)> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap(); let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap();
let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new(); let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
@ -96,10 +98,16 @@ fn components_string_to_components(
fn bevy_components_string_to_components( fn bevy_components_string_to_components(
parsed_value: String, parsed_value: String,
type_registry: &TypeRegistry, type_registry: &mut TypeRegistry,
components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>, components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>,
) { ) {
let lookup: HashMap<String, Value> = ron::from_str(&parsed_value).unwrap(); let lookup: HashMap<String, Value> = ron::from_str(&parsed_value).unwrap();
let recovery_entity_type = type_registry
.get(TypeId::of::<bevy::ecs::entity::Entity>())
.cloned();
type_registry.overwrite_registration(fake_entity::Entity::get_type_registration());
for (key, value) in lookup.into_iter() { for (key, value) in lookup.into_iter() {
let parsed_value: String = match value.clone() { let parsed_value: String = match value.clone() {
Value::String(str) => str, Value::String(str) => str,
@ -121,10 +129,10 @@ fn bevy_components_string_to_components(
let reflect_deserializer = ReflectDeserializer::new(type_registry); let reflect_deserializer = ReflectDeserializer::new(type_registry);
let component = reflect_deserializer let component = reflect_deserializer
.deserialize(&mut deserializer) .deserialize(&mut deserializer)
.unwrap_or_else(|_| { .unwrap_or_else(|e| {
panic!( panic!(
"failed to deserialize component {} with value: {:?}", "failed to deserialize component '{}' with value '{:?}': {:?}",
key, value key, value, e
) )
}); });
@ -136,4 +144,10 @@ fn bevy_components_string_to_components(
warn!("no type registration for {}", key); warn!("no type registration for {}", key);
} }
} }
if let Some(original_entity) = recovery_entity_type {
type_registry.overwrite_registration(original_entity);
} else {
warn!("There isn't an original type registration for `bevy_ecs::entity::Entity` but it was overwriten. Stuff may break and/or panic. Make sure that you register it!");
}
} }

View File

@ -0,0 +1,10 @@
[package]
name = "blenvy_relations_example"
version = "0.0.1"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
bevy-inspector-egui = "0.25.2"
blenvy = { path = "../../crates/blenvy" }

View File

@ -0,0 +1,41 @@
# Basic relations example/demo
This example showcases how to refer to another `Entity` inside Bevy components added to objects/ blueprints/ meshes and materials extracted from the gltf files
## Running this example
```
cargo run --features bevy/dynamic_linking
```
## Wasm instructions
### Setup
as per the bevy documentation:
```shell
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
```
### Building this example
navigate to the current folder , and then
```shell
cargo build --release --target wasm32-unknown-unknown --target-dir ./target
wasm-bindgen --out-name wasm_example \
--out-dir ./target/wasm \
--target web target/wasm32-unknown-unknown/release/bevy_gltf_blueprints_basic_wasm_example.wasm
```
### Running this example
run a web server in the current folder, and navigate to the page, you should see the example in your browser
## Additional notes
- You usually define either the Components directly or use `Proxy components` that get replaced in Bevy systems with the actual Components that you want (usually when for some reason, ie external crates with unregistered components etc) you cannot use the components directly.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
(
assets:
[
]
)

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
use bevy::prelude::*;
use bevy_inspector_egui::quick::WorldInspectorPlugin;
use blenvy::{BlenvyPlugin, BlueprintInfo, GameWorldTag, HideUntilReady, SpawnBlueprint};
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct TupleRelations(Entity);
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct BigRelations {
main: Entity,
other: Vec<Entity>,
}
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(AssetPlugin::default()),
BlenvyPlugin::default(),
WorldInspectorPlugin::new(),
))
.register_type::<TupleRelations>()
.register_type::<BigRelations>()
.add_systems(Startup, setup_game)
.add_systems(Update, (print_names, print_tuple_relations))
.run();
}
fn setup_game(mut commands: Commands) {
// here we actually spawn our game world/level
commands.spawn((
BlueprintInfo::from_path("levels/World.glb"), // all we need is a Blueprint info...
SpawnBlueprint, // and spawnblueprint to tell blenvy to spawn the blueprint now
HideUntilReady, // only reveal the level once it is ready
GameWorldTag,
));
}
fn print_names(query: Query<(Entity, &Name), Added<Name>>) {
for (entity, name) in &query {
info!("[EXAMPLE] {name} is {entity}");
}
}
fn print_tuple_relations(query: Query<(&Name, &TupleRelations), Added<TupleRelations>>) {
for (name, r) in &query {
info!("[EXAMPLE] {name} has the relation {r:?}")
}
}

View File

@ -25,6 +25,8 @@ conversion_tables = {
"bevy_color::srgba::Srgba": lambda value: "Srgba(red:"+str(value[0])+ ", green:"+str(value[1])+ ", blue:"+str(value[2])+ ", alpha:"+str(value[3])+ ")", "bevy_color::srgba::Srgba": lambda value: "Srgba(red:"+str(value[0])+ ", green:"+str(value[1])+ ", blue:"+str(value[2])+ ", alpha:"+str(value[3])+ ")",
"bevy_color::linear_rgba::LinearRgba": lambda value: "LinearRgba(red:"+str(value[0])+ ", green:"+str(value[1])+ ", blue:"+str(value[2])+ ", alpha:"+str(value[3])+ ")", "bevy_color::linear_rgba::LinearRgba": lambda value: "LinearRgba(red:"+str(value[0])+ ", green:"+str(value[1])+ ", blue:"+str(value[2])+ ", alpha:"+str(value[3])+ ")",
"bevy_color::hsva::Hsva": lambda value: "Hsva(hue:"+str(value[0])+ ", saturation:"+str(value[1])+ ", value:"+str(value[2])+ ", alpha:"+str(value[3])+ ")", "bevy_color::hsva::Hsva": lambda value: "Hsva(hue:"+str(value[0])+ ", saturation:"+str(value[1])+ ", value:"+str(value[2])+ ", alpha:"+str(value[3])+ ")",
"bevy_ecs::entity::Entity": lambda value: 'Entity(name: ' + (('Some("' + str(value.name) + '")') if value is not None else "None") + ')',
} }
#converts the value of a property group(no matter its complexity) into a single custom property value #converts the value of a property group(no matter its complexity) into a single custom property value

View File

@ -1,3 +1,4 @@
import bpy
from bpy_types import PropertyGroup from bpy_types import PropertyGroup
import re import re
@ -126,6 +127,16 @@ def parse_color_hsva(value, caster, typeName):
parsed = parse_struct_string(value.replace(typeName,"").replace("(", "").replace(")","") ) parsed = parse_struct_string(value.replace(typeName,"").replace("(", "").replace(")","") )
return [caster(parsed['hue']), caster(parsed['saturation']), caster(parsed['value']), caster(parsed['alpha'])] return [caster(parsed['hue']), caster(parsed['saturation']), caster(parsed['value']), caster(parsed['alpha'])]
def parse_entity(value):
# strip 'Entity(name: <VAL>)' to just '<VAL>'
value = value[13:-1]
if value.startswith("Some"):
# strip 'Some("<VAL>")' to just '<VAL>'
value = value[6:-2]
return bpy.context.scene.objects[value]
else:
return None
def to_int(input): def to_int(input):
return int(float(input)) return int(float(input))
@ -171,7 +182,7 @@ type_mappings = {
"bevy_color::linear_rgba::LinearRgba": lambda value: parse_color_rgba(value, float, "LinearRgba"), "bevy_color::linear_rgba::LinearRgba": lambda value: parse_color_rgba(value, float, "LinearRgba"),
"bevy_color::hsva::Hsva": lambda value: parse_color_hsva(value, float, "Hsva"), "bevy_color::hsva::Hsva": lambda value: parse_color_hsva(value, float, "Hsva"),
'bevy_ecs::entity::Entity': lambda value: int(value), "bevy_ecs::entity::Entity": parse_entity,
} }
def is_def_value_type(definition, registry): def is_def_value_type(definition, registry):

View File

@ -3,6 +3,7 @@ from .utils import generate_wrapper_propertyGroup
from . import process_component from . import process_component
def process_list(registry, definition, update, nesting_long_names=[]): def process_list(registry, definition, update, nesting_long_names=[]):
blender_property_mapping = registry.blender_property_mapping
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
type_infos = registry.type_infos type_infos = registry.type_infos
@ -13,7 +14,7 @@ def process_list(registry, definition, update, nesting_long_names=[]):
item_definition = type_infos[ref_name] item_definition = type_infos[ref_name]
item_long_name = item_definition["long_name"] item_long_name = item_definition["long_name"]
is_item_value_type = item_long_name in value_types_defaults is_item_value_type = item_long_name in blender_property_mapping
property_group_class = None property_group_class = None
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName ! #if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !

View File

@ -3,6 +3,7 @@ from .utils import generate_wrapper_propertyGroup
from . import process_component from . import process_component
def process_map(registry, definition, update, nesting_long_names=[]): def process_map(registry, definition, update, nesting_long_names=[]):
blender_property_mapping = registry.blender_property_mapping
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
type_infos = registry.type_infos type_infos = registry.type_infos
@ -19,7 +20,7 @@ def process_map(registry, definition, update, nesting_long_names=[]):
if key_ref_name in type_infos: if key_ref_name in type_infos:
key_definition = type_infos[key_ref_name] key_definition = type_infos[key_ref_name]
original_long_name = key_definition["long_name"] original_long_name = key_definition["long_name"]
is_key_value_type = original_long_name in value_types_defaults is_key_value_type = original_long_name in blender_property_mapping
definition_link = definition["keyType"]["type"]["$ref"] definition_link = definition["keyType"]["type"]["$ref"]
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName ! #if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !
@ -38,7 +39,7 @@ def process_map(registry, definition, update, nesting_long_names=[]):
if value_ref_name in type_infos: if value_ref_name in type_infos:
value_definition = type_infos[value_ref_name] value_definition = type_infos[value_ref_name]
original_long_name = value_definition["long_name"] original_long_name = value_definition["long_name"]
is_value_value_type = original_long_name in value_types_defaults is_value_value_type = original_long_name in blender_property_mapping
definition_link = definition["valueType"]["type"]["$ref"] definition_link = definition["valueType"]["type"]["$ref"]
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName ! #if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !

View File

@ -17,12 +17,15 @@ def process_structs(registry, definition, properties, update, nesting_long_names
if ref_name in type_infos: if ref_name in type_infos:
original = type_infos[ref_name] original = type_infos[ref_name]
original_long_name = original["long_name"] original_long_name = original["long_name"]
is_value_type = original_long_name in value_types_defaults
value = value_types_defaults[original_long_name] if is_value_type else None is_value_type = original_long_name in blender_property_mapping
has_default_value = original_long_name in value_types_defaults
value = value_types_defaults[original_long_name] if has_default_value else None
default_values[property_name] = value default_values[property_name] = value
if is_value_type: if is_value_type:
if original_long_name in blender_property_mapping: if has_default_value:
blender_property_def = blender_property_mapping[original_long_name] blender_property_def = blender_property_mapping[original_long_name]
blender_property = blender_property_def["type"]( blender_property = blender_property_def["type"](
**blender_property_def["presets"],# we inject presets first **blender_property_def["presets"],# we inject presets first
@ -31,6 +34,14 @@ def process_structs(registry, definition, properties, update, nesting_long_names
update = update update = update
) )
__annotations__[property_name] = blender_property __annotations__[property_name] = blender_property
else:
blender_property_def = blender_property_mapping[original_long_name]
blender_property = blender_property_def["type"](
**blender_property_def["presets"],# we inject presets first
name = property_name,
update = update
)
__annotations__[property_name] = blender_property
else: else:
original_long_name = original["long_name"] original_long_name = original["long_name"]
(sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "long_name": original_long_name}, nesting_long_names+[property_name]) (sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "long_name": original_long_name}, nesting_long_names+[property_name])

View File

@ -20,14 +20,16 @@ def process_tupples(registry, definition, prefixItems, update, nesting_long_name
if ref_name in type_infos: if ref_name in type_infos:
original = type_infos[ref_name] original = type_infos[ref_name]
original_long_name = original["long_name"] original_long_name = original["long_name"]
is_value_type = original_long_name in value_types_defaults
value = value_types_defaults[original_long_name] if is_value_type else None is_value_type = original_long_name in blender_property_mapping
has_default_value = original_long_name in value_types_defaults
value = value_types_defaults[original_long_name] if has_default_value else None
default_values.append(value) default_values.append(value)
prefix_infos.append(original) prefix_infos.append(original)
if is_value_type: if is_value_type:
if original_long_name in blender_property_mapping: if has_default_value:
blender_property_def = blender_property_mapping[original_long_name] blender_property_def = blender_property_mapping[original_long_name]
blender_property = blender_property_def["type"]( blender_property = blender_property_def["type"](
**blender_property_def["presets"],# we inject presets first **blender_property_def["presets"],# we inject presets first
@ -36,6 +38,15 @@ def process_tupples(registry, definition, prefixItems, update, nesting_long_name
update= update update= update
) )
__annotations__[property_name] = blender_property
else:
blender_property_def = blender_property_mapping[original_long_name]
blender_property = blender_property_def["type"](
**blender_property_def["presets"],# we inject presets first
name = property_name,
update= update
)
__annotations__[property_name] = blender_property __annotations__[property_name] = blender_property
else: else:
original_long_name = original["long_name"] original_long_name = original["long_name"]

View File

@ -11,7 +11,9 @@ from bpy_types import PropertyGroup
def generate_wrapper_propertyGroup(wrapped_type_long_name, item_long_name, definition_link, registry, update, nesting_long_names=[]): def generate_wrapper_propertyGroup(wrapped_type_long_name, item_long_name, definition_link, registry, update, nesting_long_names=[]):
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
blender_property_mapping = registry.blender_property_mapping blender_property_mapping = registry.blender_property_mapping
is_item_value_type = item_long_name in value_types_defaults
is_item_value_type = item_long_name in blender_property_mapping
has_item_default_value = item_long_name in value_types_defaults
wrapper_name = "wrapper_" + wrapped_type_long_name wrapper_name = "wrapper_" + wrapped_type_long_name
@ -42,15 +44,23 @@ def generate_wrapper_propertyGroup(wrapped_type_long_name, item_long_name, defin
blender_property = StringProperty(default="", update=update) blender_property = StringProperty(default="", update=update)
if item_long_name in blender_property_mapping: if is_item_value_type:
value = value_types_defaults[item_long_name] if is_item_value_type else None value = value_types_defaults[item_long_name] if has_item_default_value else None
blender_property_def = blender_property_mapping[item_long_name] if has_item_default_value:
blender_property = blender_property_def["type"]( blender_property_def = blender_property_mapping[item_long_name]
**blender_property_def["presets"],# we inject presets first blender_property = blender_property_def["type"](
name = "property_name", **blender_property_def["presets"],# we inject presets first
default = value, name = "property_name",
update = update default = value,
) update = update
)
else:
blender_property_def = blender_property_mapping[item_long_name]
blender_property = blender_property_def["type"](
**blender_property_def["presets"],# we inject presets first
name = "property_name",
update = update
)
wrapper_annotations = { wrapper_annotations = {
'0' : blender_property '0' : blender_property

View File

@ -25,6 +25,9 @@ def property_group_from_infos(property_group_name, property_group_parameters):
return (property_group_pointer, property_group_class) return (property_group_pointer, property_group_class)
def is_entity_poll(self, object):
return bpy.context.scene in object.users_scene # TODO: only select `object.type`s that get converted to entities and maybe something against other collection(instances)?
# 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):
registry: bpy.props. StringProperty( registry: bpy.props. StringProperty(
@ -50,7 +53,6 @@ class ComponentsRegistry(PropertyGroup):
"u32": dict(type=IntProperty, presets=dict(min=0)), "u32": dict(type=IntProperty, presets=dict(min=0)),
"u64": dict(type=IntProperty, presets=dict(min=0)), "u64": dict(type=IntProperty, presets=dict(min=0)),
"u128": dict(type=IntProperty, presets=dict(min=0)), "u128": dict(type=IntProperty, presets=dict(min=0)),
"u64": dict(type=IntProperty, presets=dict(min=0)),
"usize": dict(type=IntProperty, presets=dict(min=0)), "usize": dict(type=IntProperty, presets=dict(min=0)),
"i8": dict(type=IntProperty, presets=dict()), "i8": dict(type=IntProperty, presets=dict()),
@ -90,19 +92,19 @@ class ComponentsRegistry(PropertyGroup):
"enum": dict(type=EnumProperty, presets=dict()), "enum": dict(type=EnumProperty, presets=dict()),
'bevy_ecs::entity::Entity': {"type": IntProperty, "presets": {"min":0} }, "bevy_ecs::entity::Entity": dict(type = PointerProperty, presets=dict(type = bpy.types.Object, poll = is_entity_poll)),
'bevy_utils::Uuid': dict(type=StringProperty, presets=dict()), "bevy_utils::Uuid": dict(type = StringProperty, presets=dict()),
} }
value_types_defaults = { value_types_defaults = {
"string":" ", "string":" ",
"boolean": True, "boolean": False,
"float": 0.0, "float": 0.0,
"uint": 0, "uint": 0,
"int":0, "int":0,
# todo : we are re-doing the work of the bevy /rust side here, but it seems more pratical to alway look for the same field name on the blender side for matches # todo : we are re-doing the work of the bevy /rust side here, but it seems more pratical to alway look for the same field name on the blender side for matches
"bool": True, "bool": False,
"u8": 0, "u8": 0,
"u16":0, "u16":0,
@ -144,8 +146,7 @@ class ComponentsRegistry(PropertyGroup):
"bevy_color::linear_rgba::LinearRgba": [1.0, 1.0, 0.0, 1.0], "bevy_color::linear_rgba::LinearRgba": [1.0, 1.0, 0.0, 1.0],
"bevy_color::hsva::Hsva": [1.0, 1.0, 0.0, 1.0], "bevy_color::hsva::Hsva": [1.0, 1.0, 0.0, 1.0],
'bevy_ecs::entity::Entity': 0,#4294967295, # this is the same as Bevy's Entity::Placeholder, too big for Blender..sigh "bevy_utils::Uuid": '"'+str(uuid.uuid4())+'"'
'bevy_utils::Uuid': '"'+str(uuid.uuid4())+'"'
} }