mirror of
https://github.com/kaosat-dev/Blender_bevy_components_workflow.git
synced 2024-11-22 03:50:52 +00:00
faking named entity in type registry
This commit is contained in:
parent
2bc47ad624
commit
4e50af2453
257
crates/blenvy/src/components/fake_entity.rs
Normal file
257
crates/blenvy/src/components/fake_entity.rs
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
pub(crate) struct Entity {
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
Option<String>: bevy_reflect::FromReflect
|
||||||
|
+ bevy_reflect::TypePath
|
||||||
|
+ bevy_reflect::__macro_exports::RegisterForReflection,
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
#[inline(never)]
|
||||||
|
fn register_type_dependencies(registry: &mut bevy_reflect::TypeRegistry) {
|
||||||
|
<Option<String> as bevy_reflect::__macro_exports::RegisterForReflection>::__register(
|
||||||
|
registry,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl bevy_reflect::Typed for Entity
|
||||||
|
where
|
||||||
|
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
|
||||||
|
Option<String>: bevy_reflect::FromReflect
|
||||||
|
+ bevy_reflect::TypePath
|
||||||
|
+ bevy_reflect::__macro_exports::RegisterForReflection,
|
||||||
|
{
|
||||||
|
fn type_info() -> &'static bevy_reflect::TypeInfo {
|
||||||
|
static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
|
||||||
|
bevy_reflect::utility::NonGenericTypeInfoCell::new();
|
||||||
|
CELL.get_or_set(|| {
|
||||||
|
bevy_reflect::TypeInfo::Struct(
|
||||||
|
bevy_reflect::StructInfo::new::<bevy::ecs::entity::Entity>(&[
|
||||||
|
// TODO: changed here
|
||||||
|
bevy_reflect::NamedField::new::<Option<String>>("name")
|
||||||
|
.with_custom_attributes(
|
||||||
|
bevy_reflect::attributes::CustomAttributes::default(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.with_custom_attributes(bevy_reflect::attributes::CustomAttributes::default()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
fn short_type_path() -> &'static str {
|
||||||
|
"Entity"
|
||||||
|
}
|
||||||
|
fn type_ident() -> Option<&'static str> {
|
||||||
|
::core::option::Option::Some("Entity")
|
||||||
|
}
|
||||||
|
fn crate_name() -> Option<&'static str> {
|
||||||
|
::core::option::Option::Some("bevy_ecs::entity".split(':').next().unwrap())
|
||||||
|
}
|
||||||
|
fn module_path() -> Option<&'static str> {
|
||||||
|
::core::option::Option::Some("bevy_ecs::entity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl bevy_reflect::Struct for Entity
|
||||||
|
where
|
||||||
|
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
|
||||||
|
Option<String>: bevy_reflect::FromReflect
|
||||||
|
+ bevy_reflect::TypePath
|
||||||
|
+ bevy_reflect::__macro_exports::RegisterForReflection,
|
||||||
|
{
|
||||||
|
fn field(&self, name: &str) -> ::core::option::Option<&dyn bevy_reflect::Reflect> {
|
||||||
|
match name {
|
||||||
|
"name" => ::core::option::Option::Some(&self.name),
|
||||||
|
_ => ::core::option::Option::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn field_mut(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
) -> ::core::option::Option<&mut dyn bevy_reflect::Reflect> {
|
||||||
|
match name {
|
||||||
|
"name" => ::core::option::Option::Some(&mut self.name),
|
||||||
|
_ => ::core::option::Option::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn field_at(&self, index: usize) -> ::core::option::Option<&dyn bevy_reflect::Reflect> {
|
||||||
|
match index {
|
||||||
|
0usize => ::core::option::Option::Some(&self.name),
|
||||||
|
_ => ::core::option::Option::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn field_at_mut(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
) -> ::core::option::Option<&mut dyn bevy_reflect::Reflect> {
|
||||||
|
match index {
|
||||||
|
0usize => ::core::option::Option::Some(&mut self.name),
|
||||||
|
_ => ::core::option::Option::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn name_at(&self, index: usize) -> ::core::option::Option<&str> {
|
||||||
|
match index {
|
||||||
|
0usize => ::core::option::Option::Some("name"),
|
||||||
|
_ => ::core::option::Option::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn field_len(&self) -> usize {
|
||||||
|
1usize
|
||||||
|
}
|
||||||
|
fn iter_fields(&self) -> bevy_reflect::FieldIter {
|
||||||
|
bevy_reflect::FieldIter::new(self)
|
||||||
|
}
|
||||||
|
fn clone_dynamic(&self) -> bevy_reflect::DynamicStruct {
|
||||||
|
let mut dynamic: bevy_reflect::DynamicStruct = ::core::default::Default::default();
|
||||||
|
dynamic.set_represented_type(bevy_reflect::Reflect::get_represented_type_info(self));
|
||||||
|
dynamic.insert_boxed("name", bevy_reflect::Reflect::clone_value(&self.name));
|
||||||
|
dynamic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl bevy_reflect::Reflect for Entity
|
||||||
|
where
|
||||||
|
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
|
||||||
|
Option<String>: bevy_reflect::FromReflect
|
||||||
|
+ bevy_reflect::TypePath
|
||||||
|
+ bevy_reflect::__macro_exports::RegisterForReflection,
|
||||||
|
{
|
||||||
|
#[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> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn as_any(&self) -> &dyn ::core::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any {
|
||||||
|
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(bevy_reflect::Struct::clone_dynamic(self))
|
||||||
|
}
|
||||||
|
#[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 try_apply(
|
||||||
|
&mut self,
|
||||||
|
value: &dyn bevy_reflect::Reflect,
|
||||||
|
) -> ::core::result::Result<(), bevy_reflect::ApplyError> {
|
||||||
|
if let bevy_reflect::ReflectRef::Struct(struct_value) =
|
||||||
|
bevy_reflect::Reflect::reflect_ref(value)
|
||||||
|
{
|
||||||
|
for (i, value) in ::core::iter::Iterator::enumerate(
|
||||||
|
bevy_reflect::Struct::iter_fields(struct_value),
|
||||||
|
) {
|
||||||
|
let name = bevy_reflect::Struct::name_at(struct_value, i).unwrap();
|
||||||
|
if let ::core::option::Option::Some(v) =
|
||||||
|
bevy_reflect::Struct::field_mut(self, name)
|
||||||
|
{
|
||||||
|
bevy_reflect::Reflect::try_apply(v, value)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ::core::result::Result::Err(bevy_reflect::ApplyError::MismatchedKinds {
|
||||||
|
from_kind: bevy_reflect::Reflect::reflect_kind(value),
|
||||||
|
to_kind: bevy_reflect::ReflectKind::Struct,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
::core::result::Result::Ok(())
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn reflect_kind(&self) -> bevy_reflect::ReflectKind {
|
||||||
|
bevy_reflect::ReflectKind::Struct
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn reflect_ref(&self) -> bevy_reflect::ReflectRef {
|
||||||
|
bevy_reflect::ReflectRef::Struct(self)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn reflect_mut(&mut self) -> bevy_reflect::ReflectMut {
|
||||||
|
bevy_reflect::ReflectMut::Struct(self)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn reflect_owned(self: ::std::boxed::Box<Self>) -> bevy_reflect::ReflectOwned {
|
||||||
|
bevy_reflect::ReflectOwned::Struct(self)
|
||||||
|
}
|
||||||
|
fn reflect_partial_eq(
|
||||||
|
&self,
|
||||||
|
value: &dyn bevy_reflect::Reflect,
|
||||||
|
) -> ::core::option::Option<bool> {
|
||||||
|
bevy_reflect::struct_partial_eq(self, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl bevy_reflect::FromReflect for Entity
|
||||||
|
where
|
||||||
|
Self: ::core::any::Any + ::core::marker::Send + ::core::marker::Sync,
|
||||||
|
Option<String>: bevy_reflect::FromReflect
|
||||||
|
+ bevy_reflect::TypePath
|
||||||
|
+ bevy_reflect::__macro_exports::RegisterForReflection,
|
||||||
|
{
|
||||||
|
fn from_reflect(reflect: &dyn bevy_reflect::Reflect) -> ::core::option::Option<Self> {
|
||||||
|
if let bevy_reflect::ReflectRef::Struct(__ref_struct) =
|
||||||
|
bevy_reflect::Reflect::reflect_ref(reflect)
|
||||||
|
{
|
||||||
|
::core::option::Option::Some(Self {
|
||||||
|
name: (|| {
|
||||||
|
<Option<String> as bevy_reflect::FromReflect>::from_reflect(
|
||||||
|
bevy_reflect::Struct::field(__ref_struct, "name")?,
|
||||||
|
)
|
||||||
|
})()?,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
::core::option::Option::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -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::{
|
||||||
|
@ -74,8 +74,8 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let type_registry: &AppTypeRegistry = world.resource();
|
let type_registry: &AppTypeRegistry = world.resource();
|
||||||
let type_registry = type_registry.read();
|
let mut type_registry = type_registry.write();
|
||||||
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
|
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) =
|
||||||
@ -90,8 +90,8 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let type_registry: &AppTypeRegistry = world.resource();
|
let type_registry: &AppTypeRegistry = world.resource();
|
||||||
let type_registry = type_registry.read();
|
let mut type_registry = type_registry.write();
|
||||||
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
|
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);
|
||||||
@ -105,8 +105,8 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let type_registry: &AppTypeRegistry = world.resource();
|
let type_registry: &AppTypeRegistry = world.resource();
|
||||||
let type_registry = type_registry.read();
|
let mut type_registry = type_registry.write();
|
||||||
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
|
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);
|
||||||
@ -120,8 +120,8 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let type_registry: &AppTypeRegistry = world.resource();
|
let type_registry: &AppTypeRegistry = world.resource();
|
||||||
let type_registry = type_registry.read();
|
let mut type_registry = type_registry.write();
|
||||||
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
|
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);
|
||||||
|
@ -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!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -3,7 +3,7 @@ use blenvy::{BlenvyPlugin, BlueprintInfo, GameWorldTag, HideUntilReady, SpawnBlu
|
|||||||
|
|
||||||
#[derive(Component, Reflect)]
|
#[derive(Component, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct TupleRelations(Entity);
|
pub struct TupleRelations(Entity); // TODO: Serialization on blender side currently is broken
|
||||||
|
|
||||||
#[derive(Component, Reflect)]
|
#[derive(Component, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
|
@ -26,7 +26,7 @@ conversion_tables = {
|
|||||||
"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" + ')'
|
"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
|
||||||
|
Loading…
Reference in New Issue
Block a user