feat(tools/bevy_blueprints): bevy plugin + blender addon for components UI to more easily create components (#70)
* adds a new crate: ```bevy_registry_export``` to be able to create a json import of the registered component/type definitions * adds a new Blender addon: ```bevy_components``` that takes that json data to generate custom UIs for components , to be to add & edit components easily in Blender * also adds component metadata per object for more advanced features * etc * updates to bevy_gltf_components & bevy_gltf_blueprints to add legacy_mode to support the "old"/current style component definitions * same with gltf_auto_export Blender add_on * closes #60
|
@ -3,6 +3,7 @@ members = [
|
|||
"crates/bevy_gltf_components",
|
||||
"crates/bevy_gltf_blueprints",
|
||||
"crates/bevy_gltf_save_load",
|
||||
"crates/bevy_registry_export",
|
||||
"examples/bevy_gltf_components/basic/",
|
||||
"examples/bevy_gltf_components/basic_wasm/",
|
||||
"examples/bevy_gltf_blueprints/basic/",
|
||||
|
@ -15,6 +16,7 @@ members = [
|
|||
"examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles",
|
||||
"examples/bevy_gltf_blueprints/materials/",
|
||||
"examples/bevy_gltf_save_load/basic/",
|
||||
"examples/bevy_registry_export/basic"
|
||||
|
||||
]
|
||||
resolver = "2"
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# Workflow: classic
|
||||
|
||||
The workflow goes as follows (once you got your Bevy code setup)
|
||||
|
||||
- create & register all your components you want to be able to set from the Blender side (this is basic Bevy, no specific work needed)
|
||||
|
||||
![component registration](./docs/component_registration.png)
|
||||
|
||||
## Component creation
|
||||
- Create an object / collection (for reuse) in Blender
|
||||
- Go to object properties => add a property, and add your component data
|
||||
- unit structs, enums, and more complex strucs / components are all supported, (if the fields are basic data types at least,
|
||||
have not tried more complex ones yet, but should also work)
|
||||
- for structs with no params (unit structs): use a **STRING** property & an empty value
|
||||
- for structs with params: use a RON representation of your fields (see below)
|
||||
- for tupple strucs you can use any of the built in Blender custom property types: Strings, Booleans, floats, Vectors, etc
|
||||
|
||||
![unit struct components in Blender](./docs/components_blender.png)
|
||||
|
||||
In rust:
|
||||
|
||||
![unit struct components in Bevy](./docs/demo_simple_components.png)
|
||||
|
||||
(the Rust struct for these components for reference is [here](./examples/basic/game.rs#34) )
|
||||
|
||||
|
||||
![complex components in Blender](./docs/components_blender_parameters.png)
|
||||
|
||||
In rust:
|
||||
|
||||
![complex components in Blender](./docs/camera_tracking_component.png)
|
||||
|
||||
(the Rust struct for this component for reference is [here](./examples/basic/core/camera/camera_tracking.rs#21) )
|
||||
|
||||
There is an other examples of using various Component types: Enums, Tupple structs, strucs with fields etc [here](./examples/basic/test_components.rs),
|
||||
even colors, Vecs (arrays), Vec2, Vec3 etc are all supported
|
||||
|
||||
![complex components in Blender](./docs/components_blender_parameters2.png)
|
||||
|
||||
- for collections & their instances:
|
||||
* I usually create a library scene with nested collections
|
||||
* the leaf collections are the assets you use in your level
|
||||
* add an empty called xxxx_components
|
||||
* add the components as explained in the previous part
|
||||
|
||||
![blender collection asset](./docs/blender_collections.png)
|
||||
|
||||
* In the Level/world itself, just create an instance of the collection (standard Blender, ie Shift+A -> collection instance -> pick the collection)
|
||||
|
||||
## Exporting to gltf
|
||||
|
||||
- export your level as a glb/gltf file :
|
||||
- using Blender's default gltf exporter
|
||||
!!**IMPORTANT** you need to check the following:
|
||||
- custom properties
|
||||
- cameras & lights if you want a complete level (as in this example)
|
||||
![gltf_export](./docs/gltf_export.png)
|
||||
- or much better, using [gltf_auto_export](./tools/gltf_auto_export/)
|
||||
|
||||
## Now use your gltf files in Bevy
|
||||
|
||||
- load it in Bevy (see the various examples for this)
|
||||
- you should see the components attached to your entities in Bevy
|
||||
|
||||
![components in bevy](./docs/components_bevy.png)
|
||||
![components in bevy](./docs/components_bevy2.png)
|
||||
![components in bevy](./docs/components_bevy3.png)
|
||||
|
||||
|
||||
> note: you get a warning if there are any unregistered components in your gltf file (they get ignored)
|
||||
you will get a warning **per entity**
|
||||
|
||||
![missing components warnings](./docs/component_warnings.png)
|
|
@ -0,0 +1,40 @@
|
|||
# Workflow: UI
|
||||
|
||||
The workflow goes as follows (once you got your Bevy code setup)
|
||||
|
||||
## Bevy side
|
||||
- create & register all your components you want to be able to set from the Blender side (this is basic Bevy, no specific work needed)
|
||||
- follow the instructions in the [bevy_registry_export](./crates/bevy_registry_export/) to generate a registry export
|
||||
|
||||
## Component creation
|
||||
|
||||
Setup the Blender [bevy_components](./tools/bevy_components/README.md) add-on
|
||||
to add & edit your components visually & reliably
|
||||
|
||||
![bevy_components](./docs/bevy_components.png)
|
||||
|
||||
|
||||
## Exporting to gltf
|
||||
|
||||
- export your level as a glb/gltf file :
|
||||
- using Blender's default gltf exporter
|
||||
!!**IMPORTANT** you need to check the following:
|
||||
- custom properties
|
||||
- cameras & lights if you want a complete level (as in this example)
|
||||
![gltf_export](./docs/gltf_export.png)
|
||||
- or much better, using [gltf_auto_export](./tools/gltf_auto_export/)
|
||||
|
||||
## Now use your gltf files in Bevy
|
||||
|
||||
- load it in Bevy (see the various examples for this)
|
||||
- you should see the components attached to your entities in Bevy
|
||||
|
||||
![components in bevy](./docs/components_bevy.png)
|
||||
![components in bevy](./docs/components_bevy2.png)
|
||||
![components in bevy](./docs/components_bevy3.png)
|
||||
|
||||
|
||||
> note: you get a warning if there are any unregistered components in your gltf file (they get ignored)
|
||||
you will get a warning **per entity**
|
||||
|
||||
![missing components warnings](./docs/component_warnings.png)
|
104
README.md
|
@ -18,7 +18,8 @@ It also allows you to setup 'blueprints' in Blender by using collections (the re
|
|||
|
||||
* Useful if you want to use Blender (or any editor allowing to export gltf with configurable gltf_extras) as your Editor
|
||||
* define Bevy components as custom properties in Blender (some visually , some using RON, though an older JSON version is also available)
|
||||
* no plugin or extra tools needed in Blender (but I provide a [little Blender plugin](./tools/gltf_auto_export/README.md) to auto-export to gltf on save (and more !) if you want !)
|
||||
* no plugin or extra tools needed in Blender (but I provide a [little Blender add-on](./tools/gltf_auto_export/README.md) to auto-export to gltf on save (and more !) if you want !)
|
||||
* now also with a nice UI tool to add & edit Bevy components in [Blender](./tools/bevy_components/README.md)
|
||||
* define components in Blender Collections & override any of them in your collection instances if you want
|
||||
* ability to automatically turn your Blender collections into [gltf Blueprints](./crates/bevy_gltf_blueprints/README.md) for reuse
|
||||
* minimal setup & code, you can have something basic running fast
|
||||
|
@ -29,18 +30,23 @@ It also allows you to setup 'blueprints' in Blender by using collections (the re
|
|||
## Crates
|
||||
|
||||
- [bevy_gltf_components](./crates/bevy_gltf_components/) This crate allows you to define components direclty inside gltf files and instanciate/inject the components on the Bevy side.
|
||||
There is a [video tutorial/explanation](https://youtu.be/-lcScjQCA3c) if you want, or you can read the crate docs.
|
||||
The examples for the crate are [here](./examples/bevy_gltf_components/)
|
||||
|
||||
There is a [video tutorial/explanation](https://youtu.be/-lcScjQCA3c) if you want, or you can read the crate docs.
|
||||
The examples for the crate are [here](./examples/bevy_gltf_components/)
|
||||
|
||||
- [bevy_gltf_blueprints](./crates/bevy_gltf_blueprints/) This crate adds the ability to define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning, efficient "level" loading etc
|
||||
There is a [video tutorial/explanation](https://youtu.be/CgyNtwgYwdM) for this one too, or you can read the crate docs
|
||||
The examples for the crate are [here](./examples/bevy_gltf_blueprints/)
|
||||
> Note: this is the recomended crate to use and uses ```bevy_gltf_components``` under the hood
|
||||
|
||||
There is a [video tutorial/explanation](https://youtu.be/CgyNtwgYwdM) for this one too, or you can read the crate docs
|
||||
The examples for the crate are [here](./examples/bevy_gltf_blueprints/)
|
||||
> Note: this is the recomended crate to use and uses ```bevy_gltf_components``` under the hood
|
||||
|
||||
- [bevy_gltf_save_load](./crates/bevy_gltf_save_load/) This crate adds the ability to save & load your game state in a relatively simple way, by leveraging the blueprint functionality of
|
||||
bevy_gltf_blueprints to only save a minimal subset of dynamic data, seperating dynamic & static parts of levels etc.
|
||||
The examples for the crate are [here](./examples/bevy_gltf_save_load/)
|
||||
> Note: this uses ```bevy_gltf_blueprints``` under the hood
|
||||
> Note: this uses ```bevy_gltf_blueprints``` under the hood
|
||||
|
||||
- [bevy_registry_export](./crates/bevy_registry_export/) This crate adds the ability to export your project's Bevy registry to json, in order to be able to generate custom component UIs on the Blender side in the Blender [bevy_components](./tools/bevy_components/README.md) add-on
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
|
@ -51,6 +57,13 @@ The examples for the crate are [here](./examples/bevy_gltf_save_load/)
|
|||
|
||||
Please read the [README]((./tools/gltf_auto_export/README.md)) of the add-on for installation & use instructions
|
||||
|
||||
### Blender: bevy_components
|
||||
|
||||
- an add-on for Blender to allow easilly adding & editing Bevy components , using automatically generated UIs for each component
|
||||
|
||||
Please read the [README]((./tools/bevy_components/README.md)) of the add-on for installation & use instructions
|
||||
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -62,6 +75,12 @@ you can find all examples, by crate as seperate crates for clearer dependencies
|
|||
- [bevy_gltf_blueprints](./examples/bevy_gltf_blueprints/)
|
||||
* [basic](./examples/bevy_gltf_blueprints/basic/) more advanced example : use of ```bevy_gltf_blueprints``` to spawn a level and then populate it with entities coming from different gltf files, live (at runtime) spawning of entities etc
|
||||
* [animation](./examples/bevy_gltf_blueprints/animation/) how to use and trigger animations from gltf files (a feature of ```bevy_gltf_blueprints```)
|
||||
* & lots more
|
||||
|
||||
- [bevy_gltf_save_load](./examples/bevy_gltf_save_load/)
|
||||
|
||||
- [bevy_registry_export](./examples/bevy_registry_export/)
|
||||
|
||||
|
||||
## Workflow
|
||||
|
||||
|
@ -70,74 +89,12 @@ The workflow goes as follows (once you got your Bevy code setup)
|
|||
- create & register all your components you want to be able to set from the Blender side (this is basic Bevy, no specific work needed)
|
||||
|
||||
![component registration](./docs/component_registration.png)
|
||||
- Create an object / collection (for reuse) in Blender
|
||||
- Go to object properties => add a property, and add your component data
|
||||
- unit structs, enums, and more complex strucs / components are all supported, (if the fields are basic data types at least,
|
||||
have not tried more complex ones yet, but should also work)
|
||||
- for structs with no params (unit structs): use a **STRING** property & an empty value
|
||||
- for structs with params: use a RON representation of your fields (see below)
|
||||
- for tupple strucs you can use any of the built in Blender custom property types: Strings, Booleans, floats, Vectors, etc
|
||||
|
||||
![unit struct components in Blender](./docs/components_blender.png)
|
||||
|
||||
In rust:
|
||||
|
||||
![unit struct components in Bevy](./docs/demo_simple_components.png)
|
||||
|
||||
(the Rust struct for these components for reference is [here](./examples/basic/game.rs#34) )
|
||||
|
||||
|
||||
![complex components in Blender](./docs/components_blender_parameters.png)
|
||||
|
||||
In rust:
|
||||
|
||||
![complex components in Blender](./docs/camera_tracking_component.png)
|
||||
|
||||
(the Rust struct for this component for reference is [here](./examples/basic/core/camera/camera_tracking.rs#21) )
|
||||
|
||||
There is an other examples of using various Component types: Enums, Tupple structs, strucs with fields etc [here](./examples/basic/test_components.rs),
|
||||
even colors, Vecs (arrays), Vec2, Vec3 etc are all supported
|
||||
|
||||
![complex components in Blender](./docs/components_blender_parameters2.png)
|
||||
|
||||
- for collections & their instances:
|
||||
* I usually create a library scene with nested collections
|
||||
* the leaf collections are the assets you use in your level
|
||||
* add an empty called xxxx_components
|
||||
* add the components as explained in the previous part
|
||||
|
||||
![blender collection asset](./docs/blender_collections.png)
|
||||
|
||||
* In the Level/world itself, just create an instance of the collection (standard Blender, ie Shift+A -> collection instance -> pick the collection)
|
||||
|
||||
|
||||
- export your level as a glb/gltf file :
|
||||
- using Blender's default gltf exporter
|
||||
!!**IMPORTANT** you need to check the following:
|
||||
- custom properties
|
||||
- cameras & lights if you want a complete level (as in this example)
|
||||
![gltf_export](./docs/gltf_export.png)
|
||||
- or much better, using [gltf_auto_export](./tools/gltf_auto_export/)
|
||||
|
||||
|
||||
|
||||
- load it in Bevy (see the demo main file for this)
|
||||
- you should see the components attached to your entities in Bevy
|
||||
|
||||
![components in bevy](./docs/components_bevy.png)
|
||||
![components in bevy](./docs/components_bevy2.png)
|
||||
![components in bevy](./docs/components_bevy3.png)
|
||||
|
||||
|
||||
> note: you get a warning if there are any unregistered components in your gltf file (they get ignored)
|
||||
you will get a warning **per entity**
|
||||
|
||||
![missing components warnings](./docs/component_warnings.png)
|
||||
|
||||
|
||||
- then you have two options
|
||||
- add your components to objects in Blender **manually** as **custom properties** : see [here](./README-workflow-classic.md) for more details
|
||||
- add your components to objects in Blender **with a nice UI** see [here](./README-workflow-ui.md) for more details
|
||||
|
||||
## Limitations / issues
|
||||
- some components have to be defined in ```text``` in Blender, might try using the AppTypeRegistry and some Python code on the Blender side for a nicer UI (although this loses the "fast & easy, no tooling" approach)
|
||||
- Some of `bevy_rapier`/physics code / ways to define colliders could perhaps be done better/visually within Blender (currently it also goes via RON)
|
||||
|
||||
## Future work
|
||||
|
@ -153,6 +110,9 @@ Thanks to all the contributors helping out with this project ! Big kudos to you,
|
|||
|
||||
- [GitGhillie](https://github.com/GitGhillie)
|
||||
- [Azorlogh](https://github.com/Azorlogh)
|
||||
- [BSDGuyShawn](https://github.com/BSDGuyShawn)
|
||||
- [yukkop](https://github.com/yukkop)
|
||||
- [killercup](https://github.com/killercup)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bevy_gltf_blueprints"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
authors = ["Mark 'kaosat-dev' Moissette"]
|
||||
description = "Adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy."
|
||||
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
|
||||
|
@ -17,5 +17,6 @@ workspace = true
|
|||
bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] }
|
||||
|
||||
[dependencies]
|
||||
bevy_gltf_components = "0.2"
|
||||
bevy_gltf_components = "0.3"
|
||||
#bevy_gltf_components = { path = "../bevy_gltf_components" }
|
||||
bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] }
|
||||
|
|
|
@ -26,7 +26,7 @@ Here's a minimal usage example:
|
|||
# Cargo.toml
|
||||
[dependencies]
|
||||
bevy="0.12"
|
||||
bevy_gltf_blueprints = { version = "0.6"}
|
||||
bevy_gltf_blueprints = { version = "0.7"}
|
||||
|
||||
```
|
||||
|
||||
|
@ -64,7 +64,7 @@ fn spawn_blueprint(
|
|||
Add the following to your `[dependencies]` section in `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
bevy_gltf_blueprints = "0.6"
|
||||
bevy_gltf_blueprints = "0.7"
|
||||
```
|
||||
|
||||
Or use `cargo add`:
|
||||
|
@ -307,6 +307,25 @@ see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/exa
|
|||
|
||||
Generating optimised blueprints and material libraries can be automated using the latests version of the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export)
|
||||
|
||||
|
||||
## Legacy mode
|
||||
|
||||
Starting in version 0.7 there is a new parameter ```legacy_mode``` for backwards compatibility
|
||||
|
||||
To disable the legacy mode: (enabled by default)
|
||||
|
||||
```rust no_run
|
||||
BlueprintsPlugin{legacy_mode: false}
|
||||
```
|
||||
|
||||
|
||||
You **need** to disable legacy mode if you want to use the [```bevy_components```](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/tools_bevy_blueprints/tools/bevy_components) Blender addon + the [```bevy_registry_export crate```](https://crates.io/crates/bevy_registry_export) !
|
||||
As it create custom properties that are writen in real **ron** file format instead of a simplified version (the one in the legacy mode)
|
||||
|
||||
|
||||
> Note: the legacy mode support will be dropped in future versions, and the default behaviour will be NO legacy mode
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic
|
||||
|
@ -333,7 +352,7 @@ The main branch is compatible with the latest Bevy release, while the branch `be
|
|||
Compatibility of `bevy_gltf_blueprints` versions:
|
||||
| `bevy_gltf_blueprints` | `bevy` |
|
||||
| :-- | :-- |
|
||||
| `0.3 - 0.6` | `0.12` |
|
||||
| `0.3 - 0.7` | `0.12` |
|
||||
| `0.1 - 0.2` | `0.11` |
|
||||
| branch `main` | `0.12` |
|
||||
| branch `bevy_main` | `main` |
|
||||
|
|
|
@ -78,6 +78,8 @@ impl fmt::Display for GltfFormat {
|
|||
#[derive(Debug, Clone)]
|
||||
/// Plugin for gltf blueprints
|
||||
pub struct BlueprintsPlugin {
|
||||
pub legacy_mode: bool, // flag that gets passed on to bevy_gltf_components
|
||||
|
||||
pub format: GltfFormat,
|
||||
/// The base folder where library/blueprints assets are loaded from, relative to the executable.
|
||||
pub library_folder: PathBuf,
|
||||
|
@ -91,6 +93,7 @@ pub struct BlueprintsPlugin {
|
|||
impl Default for BlueprintsPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
legacy_mode: true,
|
||||
format: GltfFormat::GLB,
|
||||
library_folder: PathBuf::from("models/library"),
|
||||
aabbs: false,
|
||||
|
@ -110,7 +113,9 @@ fn materials_library_enabled(blueprints_config: Res<BluePrintsConfig>) -> bool {
|
|||
|
||||
impl Plugin for BlueprintsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(ComponentsFromGltfPlugin)
|
||||
app.add_plugins(ComponentsFromGltfPlugin{
|
||||
legacy_mode: self.legacy_mode
|
||||
})
|
||||
.register_type::<BlueprintName>()
|
||||
.register_type::<MaterialInfo>()
|
||||
.register_type::<SpawnHere>()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bevy_gltf_components"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
authors = ["Mark 'kaosat-dev' Moissette"]
|
||||
description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side."
|
||||
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
|
||||
|
|
|
@ -24,7 +24,7 @@ Here's a minimal usage example:
|
|||
# Cargo.toml
|
||||
[dependencies]
|
||||
bevy="0.12"
|
||||
bevy_gltf_components = { version = "0.2"}
|
||||
bevy_gltf_components = { version = "0.3"}
|
||||
|
||||
```
|
||||
|
||||
|
@ -33,7 +33,7 @@ bevy_gltf_components = { version = "0.2"}
|
|||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugin(ComponentsFromGltfPlugin)
|
||||
.add_plugin(ComponentsFromGltfPlugin::default())
|
||||
.add_system(spawn_level)
|
||||
.run();
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ bevy_gltf_components = { version = "0.2"}
|
|||
Add the following to your `[dependencies]` section in `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
bevy_gltf_components = "0.2"
|
||||
bevy_gltf_components = "0.3"
|
||||
```
|
||||
|
||||
Or use `cargo add`:
|
||||
|
@ -69,6 +69,27 @@ Or use `cargo add`:
|
|||
cargo add bevy_gltf_components
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
starting with version 0.3, this plugin is configurable
|
||||
Use the default configuration:
|
||||
|
||||
```rust no_run
|
||||
ComponentsFromGltfPlugin::default()
|
||||
```
|
||||
|
||||
Or disable the legacy mode: (enabled by default)
|
||||
|
||||
```rust no_run
|
||||
ComponentsFromGltfPlugin{legacy_mode: false}
|
||||
```
|
||||
|
||||
You **need** to disable legacy mode if you want to use the [```bevy_components```](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/tools_bevy_blueprints/tools/bevy_components) Blender addon + the [```bevy_registry_export crate```](https://crates.io/crates/bevy_registry_export) !
|
||||
As it create custom properties that are writen in real **ron** file format
|
||||
instead of a simplified version (the one in the legacy mode)
|
||||
|
||||
> Note: the legacy mode support will be dropped in future versions, and the default behaviour will be NO legacy mode
|
||||
|
||||
## SystemSet
|
||||
|
||||
the ordering of systems is very important !
|
||||
|
@ -96,7 +117,7 @@ The main branch is compatible with the latest Bevy release, while the branch `be
|
|||
Compatibility of `bevy_gltf_components` versions:
|
||||
| `bevy_gltf_components` | `bevy` |
|
||||
| :-- | :-- |
|
||||
| `0.2` | `0.12` |
|
||||
| `0.2 - 0.3` | `0.12` |
|
||||
| `0.1` | `0.11` |
|
||||
| branch `main` | `0.12` |
|
||||
| branch `bevy_main` | `main` |
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use bevy::ecs::component::Component;
|
||||
use bevy::render::color::Color;
|
||||
use core::ops::Deref;
|
||||
|
||||
use ron::Value;
|
||||
use serde::de::DeserializeSeed;
|
||||
|
||||
use bevy::ecs::{entity::Entity, reflect::ReflectComponent};
|
||||
use bevy::gltf::{Gltf, GltfExtras};
|
||||
use bevy::reflect::serde::UntypedReflectDeserializer; // ReflectSerializer
|
||||
use bevy::reflect::serde::UntypedReflectDeserializer;
|
||||
use bevy::reflect::{Reflect, TypeInfo, TypeRegistry};
|
||||
use bevy::scene::Scene;
|
||||
use bevy::utils::HashMap;
|
||||
|
@ -16,9 +17,18 @@ use bevy::{
|
|||
|
||||
use super::capitalize_first_letter;
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleTestColor(Color);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct VecOfColors(Vec<Color>);
|
||||
|
||||
pub fn ronstring_to_reflect_component(
|
||||
ron_string: &String,
|
||||
type_registry: &TypeRegistry,
|
||||
simplified_types: bool,
|
||||
) -> Vec<Box<dyn Reflect>> {
|
||||
let lookup: HashMap<String, Value> = ron::from_str(ron_string.as_str()).unwrap();
|
||||
let mut components: Vec<Box<dyn Reflect>> = Vec::new();
|
||||
|
@ -38,82 +48,72 @@ pub fn ronstring_to_reflect_component(
|
|||
type_registry.get_with_short_type_path(capitalized_type_name.as_str())
|
||||
{
|
||||
debug!("TYPE INFO {:?}", type_registration.type_info());
|
||||
match type_registration.type_info() {
|
||||
TypeInfo::TupleStruct(info) => {
|
||||
// we handle tupple strucs with only one field differently, as Blender's custom properties with custom ui (float, int, bool, etc) always give us a tupple struct
|
||||
if info.field_len() == 1 {
|
||||
let field = info
|
||||
.field_at(0)
|
||||
.expect("we should always have at least one field here");
|
||||
let field_name = field.type_path();
|
||||
// TODO: find a way to cast with typeId instead of this matching
|
||||
/*match field.type_id(){
|
||||
TypeId::of::<f32>() => {
|
||||
println!("WE HAVE A f32");
|
||||
}
|
||||
}
|
||||
Vec3 => {
|
||||
println!("WE HAVE A VEC3");
|
||||
let bla:Vec3 = ron::from_str(&parsed_value).unwrap();
|
||||
println!("bla {}", bla)
|
||||
}
|
||||
_ =>{}
|
||||
}*/
|
||||
|
||||
let mut formated = parsed_value.clone();
|
||||
match field_name {
|
||||
"f32" => {
|
||||
formated = parsed_value.parse::<f32>().unwrap().to_string();
|
||||
}
|
||||
"f64" => {
|
||||
formated = parsed_value.parse::<f64>().unwrap().to_string();
|
||||
}
|
||||
"u8" => {
|
||||
formated = parsed_value.parse::<u8>().unwrap().to_string();
|
||||
}
|
||||
"u16" => {
|
||||
formated = parsed_value.parse::<u16>().unwrap().to_string();
|
||||
}
|
||||
"u32" => {
|
||||
formated = parsed_value.parse::<u32>().unwrap().to_string();
|
||||
}
|
||||
"u64" => {
|
||||
formated = parsed_value.parse::<u64>().unwrap().to_string();
|
||||
}
|
||||
"u128" => {
|
||||
formated = parsed_value.parse::<u128>().unwrap().to_string();
|
||||
}
|
||||
"glam::Vec2" => {
|
||||
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
|
||||
formated = format!("(x:{},y:{})", parsed[0], parsed[1]);
|
||||
}
|
||||
"glam::Vec3" => {
|
||||
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
|
||||
formated =
|
||||
format!("(x:{},y:{},z:{})", parsed[0], parsed[1], parsed[2]);
|
||||
}
|
||||
"bevy_render::color::Color" => {
|
||||
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
|
||||
if parsed.len() == 3 {
|
||||
if simplified_types {
|
||||
match type_registration.type_info() {
|
||||
TypeInfo::TupleStruct(info) => {
|
||||
// we handle tupple strucs with only one field differently, as Blender's custom properties with custom ui (float, int, bool, etc) always give us a tupple struct
|
||||
if info.field_len() == 1 {
|
||||
let field = info
|
||||
.field_at(0)
|
||||
.expect("we should always have at least one field here");
|
||||
let field_name = field.type_path();
|
||||
let mut formated = parsed_value.clone();
|
||||
match field_name {
|
||||
"f32" => {
|
||||
formated = parsed_value.parse::<f32>().unwrap().to_string();
|
||||
}
|
||||
"f64" => {
|
||||
formated = parsed_value.parse::<f64>().unwrap().to_string();
|
||||
}
|
||||
"u8" => {
|
||||
formated = parsed_value.parse::<u8>().unwrap().to_string();
|
||||
}
|
||||
"u16" => {
|
||||
formated = parsed_value.parse::<u16>().unwrap().to_string();
|
||||
}
|
||||
"u32" => {
|
||||
formated = parsed_value.parse::<u32>().unwrap().to_string();
|
||||
}
|
||||
"u64" => {
|
||||
formated = parsed_value.parse::<u64>().unwrap().to_string();
|
||||
}
|
||||
"u128" => {
|
||||
formated = parsed_value.parse::<u128>().unwrap().to_string();
|
||||
}
|
||||
"glam::Vec2" => {
|
||||
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
|
||||
formated = format!("(x:{},y:{})", parsed[0], parsed[1]);
|
||||
}
|
||||
"glam::Vec3" => {
|
||||
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
|
||||
formated = format!(
|
||||
"Rgba(red:{},green:{},blue:{}, alpha: 1.0)",
|
||||
"(x:{},y:{},z:{})",
|
||||
parsed[0], parsed[1], parsed[2]
|
||||
);
|
||||
}
|
||||
if parsed.len() == 4 {
|
||||
formated = format!(
|
||||
"Rgba(red:{},green:{},blue:{}, alpha:{})",
|
||||
parsed[0], parsed[1], parsed[2], parsed[3]
|
||||
);
|
||||
"bevy_render::color::Color" => {
|
||||
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
|
||||
if parsed.len() == 3 {
|
||||
formated = format!(
|
||||
"Rgba(red:{},green:{},blue:{}, alpha: 1.0)",
|
||||
parsed[0], parsed[1], parsed[2]
|
||||
);
|
||||
}
|
||||
if parsed.len() == 4 {
|
||||
formated = format!(
|
||||
"Rgba(red:{},green:{},blue:{}, alpha:{})",
|
||||
parsed[0], parsed[1], parsed[2], parsed[3]
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
parsed_value = format!("({formated})");
|
||||
parsed_value = format!("({formated})");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// println!("parsed value {}",parsed_value);
|
||||
|
@ -128,7 +128,7 @@ pub fn ronstring_to_reflect_component(
|
|||
);
|
||||
|
||||
// usefull to determine what an entity looks like Serialized
|
||||
/*let test_struct = Enemy::default();
|
||||
/*let test_struct = VecOfColors(vec![Color::Rgba { red: 0., green: 0.0, blue: 0.0, alpha: 0.0 }]);//TuppleTestColor(Color::Rgba { red: 0., green: 0.0, blue: 0.0, alpha: 0.0 });
|
||||
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
|
||||
let serialized =
|
||||
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
|
||||
|
@ -162,8 +162,13 @@ pub fn gltf_extras_to_components(
|
|||
gltf: &mut Gltf,
|
||||
scenes: &mut ResMut<Assets<Scene>>,
|
||||
type_registry: impl Deref<Target = TypeRegistry>,
|
||||
legacy_mode: bool
|
||||
) {
|
||||
let mut added_components = 0;
|
||||
let simplified_types = legacy_mode;
|
||||
if simplified_types {
|
||||
warn!("using simplified component definitions is deprecated since 0.3, prefer defining components with real ron values (use the bevy_components tool for Blender for simplicity) ");
|
||||
}
|
||||
for (_name, scene) in &gltf.named_scenes {
|
||||
debug!("gltf: scene name {:?}", _name);
|
||||
|
||||
|
@ -173,7 +178,8 @@ pub fn gltf_extras_to_components(
|
|||
let mut entity_components: HashMap<Entity, Vec<Box<dyn Reflect>>> = HashMap::new();
|
||||
for (entity, name, extras, parent) in query.iter(&scene.world) {
|
||||
debug!("Name: {}, entity {:?}, parent: {:?}", name, entity, parent);
|
||||
let reflect_components = ronstring_to_reflect_component(&extras.value, &type_registry);
|
||||
let reflect_components =
|
||||
ronstring_to_reflect_component(&extras.value, &type_registry, simplified_types);
|
||||
added_components = reflect_components.len();
|
||||
debug!("Found components {}", added_components);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ pub use gltf_to_components::*;
|
|||
pub mod process_gltfs;
|
||||
pub use process_gltfs::*;
|
||||
|
||||
use bevy::prelude::{App, IntoSystemConfigs, Plugin, SystemSet, Update};
|
||||
use bevy::{ecs::system::Resource, prelude::{App, IntoSystemConfigs, Plugin, SystemSet, Update}};
|
||||
|
||||
/// A Bevy plugin for extracting components from gltf files and automatically adding them to the relevant entities
|
||||
/// It will automatically run every time you load a gltf file
|
||||
|
@ -34,7 +34,7 @@ use bevy::prelude::{App, IntoSystemConfigs, Plugin, SystemSet, Update};
|
|||
/// ){
|
||||
/// if keycode.just_pressed(KeyCode::Return) {
|
||||
/// commands.spawn(SceneBundle {
|
||||
/// scene: asset_server.load("basic/models/level1.glb#Scene0"),
|
||||
/// scene: asset_server.load("basic/models/level1.glb"),
|
||||
/// transform: Transform::from_xyz(2.0, 0.0, -5.0),
|
||||
/// ..Default::default()
|
||||
/// });
|
||||
|
@ -48,11 +48,29 @@ pub enum GltfComponentsSet {
|
|||
Injection,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ComponentsFromGltfPlugin;
|
||||
#[derive(Clone, Resource)]
|
||||
pub struct GltfComponentsConfig {
|
||||
pub(crate) legacy_mode: bool,
|
||||
}
|
||||
|
||||
pub struct ComponentsFromGltfPlugin {
|
||||
pub legacy_mode: bool
|
||||
}
|
||||
|
||||
impl Default for ComponentsFromGltfPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
legacy_mode: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for ComponentsFromGltfPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(GltfLoadingTracker::new())
|
||||
.insert_resource(GltfComponentsConfig {
|
||||
legacy_mode: self.legacy_mode
|
||||
})
|
||||
.add_systems(Update, (track_new_gltf, process_loaded_scenes))
|
||||
.add_systems(
|
||||
Update,
|
||||
|
|
|
@ -4,20 +4,20 @@ use bevy::gltf::Gltf;
|
|||
use bevy::utils::HashSet;
|
||||
use bevy::{asset::LoadState, prelude::*};
|
||||
|
||||
use crate::gltf_extras_to_components;
|
||||
use crate::{gltf_extras_to_components, GltfComponentsConfig};
|
||||
|
||||
#[derive(Resource)]
|
||||
/// component to keep track of gltfs' loading state
|
||||
pub struct GltfLoadingTracker {
|
||||
pub loading_gltfs: HashSet<Handle<Gltf>>,
|
||||
pub loaded_gltfs: HashSet<Handle<Gltf>>,
|
||||
pub processed_gltfs: HashSet<String>,
|
||||
}
|
||||
|
||||
impl GltfLoadingTracker {
|
||||
pub fn new() -> GltfLoadingTracker {
|
||||
GltfLoadingTracker {
|
||||
loaded_gltfs: HashSet::new(),
|
||||
loading_gltfs: HashSet::new(),
|
||||
processed_gltfs: HashSet::new(),
|
||||
}
|
||||
}
|
||||
pub fn add_gltf(&mut self, handle: Handle<Gltf>) {
|
||||
|
@ -55,6 +55,7 @@ pub fn process_loaded_scenes(
|
|||
mut scenes: ResMut<Assets<Scene>>,
|
||||
app_type_registry: Res<AppTypeRegistry>,
|
||||
asset_server: Res<AssetServer>,
|
||||
gltf_components_config: Res<GltfComponentsConfig>,
|
||||
) {
|
||||
let mut loaded_gltfs = Vec::new();
|
||||
for gltf in &tracker.loading_gltfs {
|
||||
|
@ -75,10 +76,15 @@ pub fn process_loaded_scenes(
|
|||
|
||||
for gltf_handle in &loaded_gltfs {
|
||||
if let Some(gltf) = gltfs.get_mut(gltf_handle) {
|
||||
gltf_extras_to_components(gltf, &mut scenes, &*type_registry);
|
||||
gltf_extras_to_components(gltf, &mut scenes, &*type_registry, gltf_components_config.legacy_mode);
|
||||
|
||||
if let Some(path) = gltf_handle.path() {
|
||||
tracker.processed_gltfs.insert(path.to_string());
|
||||
}
|
||||
} else {
|
||||
warn!("could not find gltf asset, cannot process it");
|
||||
}
|
||||
tracker.loading_gltfs.remove(gltf_handle);
|
||||
tracker.loaded_gltfs.insert(gltf_handle.clone());
|
||||
debug!("Done loading scene");
|
||||
debug!("Done loading gltf file");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bevy_gltf_save_load"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Mark 'kaosat-dev' Moissette"]
|
||||
description = "Save & load your bevy games"
|
||||
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
|
||||
|
@ -18,4 +18,4 @@ bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "
|
|||
|
||||
[dependencies]
|
||||
bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
|
||||
bevy_gltf_blueprints = "0.6"
|
||||
bevy_gltf_blueprints = "0.7"
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "bevy_registry_export"
|
||||
version = "0.1.0"
|
||||
authors = ["Mark 'kaosat-dev' Moissette"]
|
||||
description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side."
|
||||
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
|
||||
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
|
||||
keywords = ["gamedev", "bevy", "assets", "gltf", "components"]
|
||||
categories = ["game-development"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.12", default-features = false, features = ["bevy_scene"] }
|
||||
bevy_reflect = { version = "0.12.1", default-features = false }
|
||||
bevy_app = { version = "0.12.1", default-features = false, features = ["bevy_reflect"] }
|
||||
bevy_ecs = { version = "0.12.1", default-features = false, features = ["bevy_reflect"] }
|
||||
serde_json = "1.0.108"
|
|
@ -0,0 +1,4 @@
|
|||
This crate is available under either:
|
||||
|
||||
* The [MIT License](./LICENSE_MIT)
|
||||
* The [Apache License, Version 2.0](./LICENSE_APACHE)
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [2024] [Mark "kaosat-dev" Moissette]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Mark "kaosat-dev" Moissette
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,130 @@
|
|||
[![Crates.io](https://img.shields.io/crates/v/bevy_registry_export)](https://crates.io/crates/bevy_registry_export)
|
||||
[![Docs](https://img.shields.io/docsrs/bevy_registry_export)](https://docs.rs/bevy_registry_export/latest/bevy_registry_export/)
|
||||
[![License](https://img.shields.io/crates/l/bevy_registry_export)](https://github.com/kaosat-dev/Blender_bevy_components_workflow/blob/main/crates/bevy_registry_export/License.md)
|
||||
[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking)
|
||||
|
||||
# bevy_registry_export
|
||||
|
||||
This plugin allows you to create a Json export of all your components/ registered types.
|
||||
Its main use case is as a backbone for the [```bevy_components``` Blender add-on](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/bevy_components), that allows you to add & edit components directly in Blender, using the actual type definitions from Bevy
|
||||
(and any of your custom types & components that you register in Bevy).
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Here's a minimal usage example:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[dependencies]
|
||||
bevy="0.12"
|
||||
bevy_registry_export = "0.1"
|
||||
```
|
||||
|
||||
```rust no_run
|
||||
use bevy::prelude::*;
|
||||
use bevy_registry_export::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
ExportRegistryPlugin::default() // will save your registry schema json file to assets/registry.json
|
||||
))
|
||||
.run();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
take a look at the [example]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/basic/src/core/mod.rs) for more clarity
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Add the following to your `[dependencies]` section in `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
bevy_registry_export = "0.1"
|
||||
|
||||
```
|
||||
|
||||
Or use `cargo add`:
|
||||
|
||||
```toml
|
||||
cargo add bevy_registry_export
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
```rust no_run
|
||||
use bevy::prelude::*;
|
||||
use bevy_registry_export::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins
|
||||
ExportRegistryPlugin::default()
|
||||
))
|
||||
.run();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
you can also configure the output path
|
||||
|
||||
```rust no_run
|
||||
use bevy::prelude::*;
|
||||
use bevy_registry_export::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins
|
||||
ExportRegistryPlugin {
|
||||
save_path: "assets/registry.json".into(),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
.run();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
- The output file will be generated in the ```Startup``` schedule whenever you run your app.
|
||||
- Every time you compile & run your app, the output json file will be updated.
|
||||
|
||||
## Examples
|
||||
|
||||
All examples are here:
|
||||
|
||||
> the examples use ```bevy_gltf_blueprints``` with the **legacy_mode** set to **FALSE** as the new custom properties generated by the Blender add-on require newer/ non legacy logic.
|
||||
|
||||
- https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/basic
|
||||
|
||||
|
||||
## Compatible Bevy versions
|
||||
|
||||
The main branch is compatible with the latest Bevy release, while the branch `bevy_main` tries to track the `main` branch of Bevy (PRs updating the tracked commit are welcome).
|
||||
|
||||
Compatibility of `bevy_registry_export` versions:
|
||||
| `bevy_registry_export` | `bevy` | `bevy_components (Blender add-on)` |
|
||||
| :-- | :-- |:-- |
|
||||
| `0.1 ` | `0.12` | `0.1.0` |
|
||||
| branch `main` | `0.12` | `0.1.0` |
|
||||
| branch `bevy_main` | `main` | `n/a` |
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks to all the contributors helping out with this project ! Big kudos to you, contributions are always appreciated ! :)
|
||||
A big shout out to [killercup](https://github.com/killercup), that did the bulk of the Bevy side code !
|
||||
|
||||
## License
|
||||
|
||||
This crate, all its code, contents & assets is Dual-licensed under either of
|
||||
|
||||
- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](./LICENSE_MIT.md) or https://opensource.org/licenses/MIT)
|
|
@ -0,0 +1,267 @@
|
|||
use std::fs::File;
|
||||
|
||||
use bevy::log::info;
|
||||
use bevy_ecs::{
|
||||
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
|
||||
world::World,
|
||||
};
|
||||
use bevy_reflect::{TypeInfo, TypeRegistration, VariantInfo}; // TypePath // DynamicTypePath
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::ExportComponentsConfig;
|
||||
|
||||
pub fn export_types(world: &mut World) {
|
||||
let config = world
|
||||
.get_resource::<ExportComponentsConfig>()
|
||||
.expect("ExportComponentsConfig should exist at this stage");
|
||||
|
||||
let writer =
|
||||
File::create(config.save_path.to_path_buf()).expect("should have created schema file");
|
||||
|
||||
let types = world.resource_mut::<AppTypeRegistry>();
|
||||
let types = types.read();
|
||||
let schemas = types.iter().map(export_type).collect::<Map<_, _>>();
|
||||
|
||||
serde_json::to_writer_pretty(
|
||||
writer,
|
||||
&json!({
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "bevy component registry schema",
|
||||
"$defs": schemas,
|
||||
}),
|
||||
)
|
||||
.expect("valid json");
|
||||
|
||||
info!("Done exporting registry schema")
|
||||
}
|
||||
|
||||
pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
|
||||
let t = reg.type_info();
|
||||
let binding = t.type_path_table();
|
||||
let short_name = binding.short_path();
|
||||
let mut schema = match t {
|
||||
TypeInfo::Struct(info) => {
|
||||
let properties = info
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, field)| {
|
||||
(
|
||||
field.name().to_owned(),
|
||||
add_min_max(json!({ "type": typ(field.type_path()) }), reg, idx, None),
|
||||
)
|
||||
})
|
||||
.collect::<Map<_, _>>();
|
||||
|
||||
json!({
|
||||
"type": "object",
|
||||
"typeInfo": "Struct",
|
||||
"title": t.type_path(),
|
||||
"properties": properties,
|
||||
"additionalProperties": false,
|
||||
"required": info
|
||||
.iter()
|
||||
.filter(|field| !field.type_path().starts_with("core::option::Option"))
|
||||
.map(|field| field.name())
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
TypeInfo::Enum(info) => {
|
||||
let simple = info
|
||||
.iter()
|
||||
.all(|variant| matches!(variant, VariantInfo::Unit(_)));
|
||||
if simple {
|
||||
json!({
|
||||
"type": "string",
|
||||
"typeInfo": "Enum",
|
||||
"title": t.type_path(),
|
||||
"oneOf": info
|
||||
.iter()
|
||||
.map(|variant| match variant {
|
||||
VariantInfo::Unit(v) => v.name(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
} else {
|
||||
let variants = info
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(field_idx, variant)| match variant {
|
||||
//let binding = t.type_path_table();
|
||||
//let short_name = binding.short_path();
|
||||
VariantInfo::Struct(v) => json!({
|
||||
"type": "object",
|
||||
"typeInfo": "Struct",
|
||||
"title": v.name(),
|
||||
"short_name": v.name().split("::").last().unwrap_or(v.name()),
|
||||
"properties": v
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(variant_idx, field)| (field.name().to_owned(), add_min_max(json!({"type": typ(field.type_path()), "title": field.name()}), reg, field_idx, Some(variant_idx))))
|
||||
.collect::<Map<_, _>>(),
|
||||
"additionalProperties": false,
|
||||
"required": v
|
||||
.iter()
|
||||
.filter(|field| !field.type_path().starts_with("core::option::Option"))
|
||||
.map(|field| field.name())
|
||||
.collect::<Vec<_>>(),
|
||||
}),
|
||||
VariantInfo::Tuple(v) => json!({
|
||||
"type": "array",
|
||||
"typeInfo": "Tuple",
|
||||
"title": v.name(),
|
||||
"short_name":v.name(),
|
||||
"prefixItems": v
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(variant_idx, field)| add_min_max(json!({"type": typ(field.type_path())}), reg, field_idx, Some(variant_idx)))
|
||||
.collect::<Vec<_>>(),
|
||||
"items": false,
|
||||
}),
|
||||
VariantInfo::Unit(v) => json!({
|
||||
"title": v.name(),
|
||||
}),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
json!({
|
||||
"type": "object",
|
||||
"typeInfo": "Enum",
|
||||
"title": t.type_path(),
|
||||
"oneOf": variants,
|
||||
})
|
||||
}
|
||||
}
|
||||
TypeInfo::TupleStruct(info) => json!({
|
||||
"title": t.type_path(),
|
||||
"type": "array",
|
||||
"typeInfo": "TupleStruct",
|
||||
"prefixItems": info
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, field)| add_min_max(json!({"type": typ(field.type_path())}), reg, idx, None))
|
||||
.collect::<Vec<_>>(),
|
||||
"items": false,
|
||||
}),
|
||||
TypeInfo::List(info) => {
|
||||
json!({
|
||||
"title": t.type_path(),
|
||||
"type": "array",
|
||||
"typeInfo": "List",
|
||||
"items": json!({"type": typ(info.item_type_path_table().path())}),
|
||||
})
|
||||
}
|
||||
TypeInfo::Array(info) => json!({
|
||||
"title": t.type_path(),
|
||||
"type": "array",
|
||||
"typeInfo": "Array",
|
||||
"items": json!({"type": typ(info.item_type_path_table().path())}),
|
||||
}),
|
||||
TypeInfo::Map(info) => json!({
|
||||
"title": t.type_path(),
|
||||
"type": "object",
|
||||
"typeInfo": "Map",
|
||||
"additionalProperties": json!({"type": typ(info.value_type_path_table().path())}),
|
||||
}),
|
||||
TypeInfo::Tuple(info) => json!({
|
||||
"title": t.type_path(),
|
||||
"type": "array",
|
||||
"typeInfo": "Tuple",
|
||||
"prefixItems": info
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, field)| add_min_max(json!({"type": typ(field.type_path())}), reg, idx, None))
|
||||
.collect::<Vec<_>>(),
|
||||
"items": false,
|
||||
}),
|
||||
TypeInfo::Value(info) => json!({
|
||||
"title": t.type_path(),
|
||||
"type": map_json_type(info.type_path()),
|
||||
"typeInfo": "Value",
|
||||
}),
|
||||
};
|
||||
schema.as_object_mut().unwrap().insert(
|
||||
"isComponent".to_owned(),
|
||||
reg.data::<ReflectComponent>().is_some().into(),
|
||||
);
|
||||
schema.as_object_mut().unwrap().insert(
|
||||
"isResource".to_owned(),
|
||||
reg.data::<ReflectResource>().is_some().into(),
|
||||
);
|
||||
|
||||
schema
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("short_name".to_owned(), short_name.into());
|
||||
|
||||
(t.type_path().to_owned(), schema)
|
||||
}
|
||||
|
||||
fn typ(t: &str) -> Value {
|
||||
json!({ "$ref": format!("#/$defs/{t}") })
|
||||
}
|
||||
|
||||
fn map_json_type(t: &str) -> Value {
|
||||
match t {
|
||||
"bool" => "boolean",
|
||||
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" => "uint",
|
||||
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" => "int",
|
||||
"f32" | "f64" => "float",
|
||||
"char" | "str" | "alloc::string::String" => "string",
|
||||
_ => "object",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn add_min_max(
|
||||
mut val: Value,
|
||||
reg: &TypeRegistration,
|
||||
field_index: usize,
|
||||
variant_index: Option<usize>,
|
||||
) -> Value {
|
||||
#[cfg(feature = "support-inspector")]
|
||||
fn get_min_max(
|
||||
reg: &TypeRegistration,
|
||||
field_index: usize,
|
||||
variant_index: Option<usize>,
|
||||
) -> Option<(Option<f32>, Option<f32>)> {
|
||||
use bevy_inspector_egui::inspector_options::{
|
||||
std_options::NumberOptions, ReflectInspectorOptions, Target,
|
||||
};
|
||||
|
||||
reg.data::<ReflectInspectorOptions>()
|
||||
.and_then(|ReflectInspectorOptions(o)| {
|
||||
o.get(if let Some(variant_index) = variant_index {
|
||||
Target::VariantField {
|
||||
variant_index,
|
||||
field_index,
|
||||
}
|
||||
} else {
|
||||
Target::Field(field_index)
|
||||
})
|
||||
})
|
||||
.and_then(|o| o.downcast_ref::<NumberOptions<f32>>())
|
||||
.map(|num| (num.min, num.max))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "support-inspector"))]
|
||||
fn get_min_max(
|
||||
_reg: &TypeRegistration,
|
||||
_field_index: usize,
|
||||
_variant_index: Option<usize>,
|
||||
) -> Option<(Option<f32>, Option<f32>)> {
|
||||
None
|
||||
}
|
||||
|
||||
let Some((min, max)) = get_min_max(reg, field_index, variant_index) else {
|
||||
return val;
|
||||
};
|
||||
let obj = val.as_object_mut().unwrap();
|
||||
if let Some(min) = min {
|
||||
obj.insert("minimum".to_owned(), min.into());
|
||||
}
|
||||
if let Some(max) = max {
|
||||
obj.insert("maximum".to_owned(), max.into());
|
||||
}
|
||||
val
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
pub mod export_types;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bevy_app::Startup;
|
||||
use bevy_ecs::system::Resource;
|
||||
pub use export_types::*;
|
||||
|
||||
use bevy::{
|
||||
prelude::{App, Plugin},
|
||||
scene::SceneFilter,
|
||||
};
|
||||
|
||||
// Plugin configuration
|
||||
#[derive(Clone, Resource)]
|
||||
pub struct ExportComponentsConfig {
|
||||
pub(crate) save_path: PathBuf,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) component_filter: SceneFilter, // unused for now
|
||||
#[allow(dead_code)]
|
||||
pub(crate) resource_filter: SceneFilter, // unused for now
|
||||
}
|
||||
|
||||
pub struct ExportRegistryPlugin {
|
||||
pub component_filter: SceneFilter,
|
||||
pub resource_filter: SceneFilter,
|
||||
pub save_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for ExportRegistryPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component_filter: SceneFilter::default(), // unused for now
|
||||
resource_filter: SceneFilter::default(), // unused for now
|
||||
save_path: PathBuf::from("assets/registry.json"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for ExportRegistryPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(ExportComponentsConfig {
|
||||
save_path: self.save_path.clone(),
|
||||
component_filter: self.component_filter.clone(),
|
||||
resource_filter: self.resource_filter.clone(),
|
||||
})
|
||||
.add_systems(Startup, export_types);
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 53 KiB |
|
@ -34,7 +34,7 @@ fn main() {
|
|||
RapierPhysicsPlugin::<NoUserData>::default(),
|
||||
RapierDebugRenderPlugin::default(),
|
||||
// our custom plugins
|
||||
ComponentsFromGltfPlugin,
|
||||
ComponentsFromGltfPlugin::default(),
|
||||
CorePlugin, // reusable plugins
|
||||
DemoPlugin, // specific to our game
|
||||
ComponentsTestPlugin, // Showcases different type of components /structs
|
||||
|
|
|
@ -34,7 +34,7 @@ fn main() {
|
|||
RapierPhysicsPlugin::<NoUserData>::default(),
|
||||
RapierDebugRenderPlugin::default(),
|
||||
// our custom plugins
|
||||
ComponentsFromGltfPlugin,
|
||||
ComponentsFromGltfPlugin::default(),
|
||||
CorePlugin, // reusable plugins
|
||||
DemoPlugin, // specific to our game
|
||||
ComponentsTestPlugin, // Showcases different type of components /structs
|
||||
|
|
|
@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[dependencies]
|
||||
bevy="0.12"
|
||||
bevy_gltf_blueprints = "0.6"
|
||||
bevy_gltf_blueprints = "0.7"
|
||||
bevy_gltf_save_load = { path = "../../../crates/bevy_gltf_save_load" }
|
||||
|
||||
bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "bevy_bevy_registry_export_basic_example"
|
||||
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_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"
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
({})
|
|
@ -0,0 +1,6 @@
|
|||
({
|
||||
"world":File (path: "models/World.glb"),
|
||||
"models": Folder (
|
||||
path: "models/library",
|
||||
),
|
||||
})
|
|
@ -0,0 +1,5 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_asset_loader::prelude::*;
|
||||
|
||||
#[derive(AssetCollection, Resource)]
|
||||
pub struct CoreAssets {}
|
|
@ -0,0 +1,13 @@
|
|||
use bevy::gltf::Gltf;
|
||||
use bevy::prelude::*;
|
||||
use bevy::utils::HashMap;
|
||||
use bevy_asset_loader::prelude::*;
|
||||
|
||||
#[derive(AssetCollection, Resource)]
|
||||
pub struct GameAssets {
|
||||
#[asset(key = "world")]
|
||||
pub world: Handle<Gltf>,
|
||||
|
||||
#[asset(key = "models", collection(typed, mapped))]
|
||||
pub models: HashMap<String, Handle<Gltf>>,
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
pub mod assets_core;
|
||||
pub use assets_core::*;
|
||||
|
||||
pub mod assets_game;
|
||||
pub use assets_game::*;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_asset_loader::prelude::*;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
pub struct AssetsPlugin;
|
||||
impl Plugin for AssetsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app
|
||||
// load core assets (ie assets needed in the main menu, and everywhere else before loading more assets in game)
|
||||
.add_loading_state(
|
||||
LoadingState::new(AppState::CoreLoading).continue_to_state(AppState::MenuRunning),
|
||||
)
|
||||
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
|
||||
AppState::CoreLoading,
|
||||
"assets_core.assets.ron",
|
||||
)
|
||||
.add_collection_to_loading_state::<_, CoreAssets>(AppState::CoreLoading)
|
||||
// load game assets
|
||||
.add_loading_state(
|
||||
LoadingState::new(AppState::AppLoading).continue_to_state(AppState::AppRunning),
|
||||
)
|
||||
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
|
||||
AppState::AppLoading,
|
||||
"assets_game.assets.ron",
|
||||
)
|
||||
.add_collection_to_loading_state::<_, GameAssets>(AppState::AppLoading);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
|
||||
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::CameraTrackingOffset;
|
||||
|
||||
pub fn camera_replace_proxies(
|
||||
mut commands: Commands,
|
||||
mut added_cameras: Query<(Entity, &mut Camera), (Added<Camera>, With<CameraTrackingOffset>)>,
|
||||
) {
|
||||
for (entity, mut camera) in added_cameras.iter_mut() {
|
||||
info!("detected added camera, updating proxy");
|
||||
camera.hdr = true;
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(DebandDither::Enabled)
|
||||
.insert(Tonemapping::BlenderFilmic)
|
||||
.insert(BloomSettings {
|
||||
intensity: 0.01,
|
||||
composite_mode: BloomCompositeMode::Additive,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[reflect(Component)]
|
||||
/// Component for cameras, with an offset from the Trackable target
|
||||
///
|
||||
pub struct CameraTracking {
|
||||
pub offset: Vec3,
|
||||
}
|
||||
impl Default for CameraTracking {
|
||||
fn default() -> Self {
|
||||
CameraTracking {
|
||||
offset: Vec3::new(0.0, 6.0, 8.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Debug, Deref, DerefMut)]
|
||||
#[reflect(Component)]
|
||||
/// Component for cameras, with an offset from the Trackable target
|
||||
pub struct CameraTrackingOffset(Vec3);
|
||||
impl Default for CameraTrackingOffset {
|
||||
fn default() -> Self {
|
||||
CameraTrackingOffset(Vec3::new(0.0, 6.0, 8.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
/// Add this component to an entity if you want it to be tracked by a Camera
|
||||
pub struct CameraTrackable;
|
||||
|
||||
pub fn camera_track(
|
||||
mut tracking_cameras: Query<
|
||||
(&mut Transform, &CameraTrackingOffset),
|
||||
(
|
||||
With<Camera>,
|
||||
With<CameraTrackingOffset>,
|
||||
Without<CameraTrackable>,
|
||||
),
|
||||
>,
|
||||
camera_tracked: Query<&Transform, With<CameraTrackable>>,
|
||||
) {
|
||||
for (mut camera_transform, tracking_offset) in tracking_cameras.iter_mut() {
|
||||
for tracked_transform in camera_tracked.iter() {
|
||||
let target_position = tracked_transform.translation + tracking_offset.0;
|
||||
let eased_position = camera_transform.translation.lerp(target_position, 0.1);
|
||||
camera_transform.translation = eased_position; // + tracking.offset;// tracked_transform.translation + tracking.offset;
|
||||
*camera_transform = camera_transform.looking_at(tracked_transform.translation, Vec3::Y);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
pub mod camera_tracking;
|
||||
pub use camera_tracking::*;
|
||||
|
||||
pub mod camera_replace_proxies;
|
||||
pub use camera_replace_proxies::*;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||
|
||||
pub struct CameraPlugin;
|
||||
impl Plugin for CameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<CameraTrackable>()
|
||||
.register_type::<CameraTracking>()
|
||||
.register_type::<CameraTrackingOffset>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
camera_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
|
||||
camera_track,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use bevy::pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder};
|
||||
|
||||
// fixme might be too specific to might needs, should it be moved out ? also these are all for lights, not models
|
||||
pub fn lighting_replace_proxies(
|
||||
mut added_dirights: Query<(Entity, &mut DirectionalLight), Added<DirectionalLight>>,
|
||||
mut added_spotlights: Query<&mut SpotLight, Added<SpotLight>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (entity, mut light) in added_dirights.iter_mut() {
|
||||
light.illuminance *= 5.0;
|
||||
light.shadows_enabled = true;
|
||||
let shadow_config: CascadeShadowConfig = CascadeShadowConfigBuilder {
|
||||
first_cascade_far_bound: 15.0,
|
||||
maximum_distance: 135.0,
|
||||
..default()
|
||||
}
|
||||
.into();
|
||||
commands.entity(entity).insert(shadow_config);
|
||||
}
|
||||
for mut light in added_spotlights.iter_mut() {
|
||||
light.shadows_enabled = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
mod lighting_replace_proxies;
|
||||
use lighting_replace_proxies::*;
|
||||
|
||||
use bevy::pbr::{DirectionalLightShadowMap, NotShadowCaster};
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct LightingPlugin;
|
||||
impl Plugin for LightingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app
|
||||
.insert_resource(DirectionalLightShadowMap { size: 4096 })
|
||||
// FIXME: adding these since they are missing
|
||||
.register_type::<NotShadowCaster>()
|
||||
|
||||
.add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely
|
||||
;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
pub mod camera;
|
||||
pub use camera::*;
|
||||
|
||||
pub mod lighting;
|
||||
pub use lighting::*;
|
||||
|
||||
pub mod relationships;
|
||||
pub use relationships::*;
|
||||
|
||||
pub mod physics;
|
||||
pub use physics::*;
|
||||
|
||||
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()
|
||||
},
|
||||
LightingPlugin,
|
||||
CameraPlugin,
|
||||
PhysicsPlugin,
|
||||
BlueprintsPlugin {
|
||||
legacy_mode: false,
|
||||
library_folder: "models/library".into(),
|
||||
format: GltfFormat::GLB,
|
||||
aabbs: true,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
use bevy::{log::info, prelude::ResMut};
|
||||
use bevy_rapier3d::prelude::RapierConfiguration;
|
||||
|
||||
pub fn pause_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||
info!("pausing physics");
|
||||
physics_config.physics_pipeline_active = false;
|
||||
}
|
||||
|
||||
pub fn resume_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||
info!("unpausing physics");
|
||||
physics_config.physics_pipeline_active = true;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
pub mod physics_replace_proxies;
|
||||
use bevy_rapier3d::{
|
||||
prelude::{NoUserData, RapierPhysicsPlugin},
|
||||
render::RapierDebugRenderPlugin,
|
||||
};
|
||||
pub use physics_replace_proxies::*;
|
||||
|
||||
pub mod utils;
|
||||
|
||||
pub mod controls;
|
||||
pub use controls::*;
|
||||
|
||||
use crate::state::GameState;
|
||||
use bevy::prelude::*;
|
||||
// use super::blueprints::GltfBlueprintsSet;
|
||||
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||
// use crate::Collider;
|
||||
pub struct PhysicsPlugin;
|
||||
impl Plugin for PhysicsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((
|
||||
RapierPhysicsPlugin::<NoUserData>::default(),
|
||||
RapierDebugRenderPlugin::default(),
|
||||
))
|
||||
.register_type::<AutoAABBCollider>()
|
||||
.register_type::<physics_replace_proxies::Collider>()
|
||||
// find a way to make serde's stuff serializable
|
||||
// .register_type::<bevy_rapier3d::dynamics::CoefficientCombineRule>()
|
||||
//bevy_rapier3d::dynamics::CoefficientCombineRule
|
||||
.add_systems(
|
||||
Update,
|
||||
physics_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
|
||||
)
|
||||
.add_systems(OnEnter(GameState::InGame), resume_physics)
|
||||
.add_systems(OnExit(GameState::InGame), pause_physics);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
use bevy::prelude::*;
|
||||
// use bevy::render::primitives::Aabb;
|
||||
use bevy_rapier3d::geometry::Collider as RapierCollider;
|
||||
use bevy_rapier3d::prelude::{ActiveCollisionTypes, ActiveEvents, ComputedColliderShape};
|
||||
|
||||
use super::utils::*;
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub enum Collider {
|
||||
Ball(f32),
|
||||
Cuboid(Vec3),
|
||||
Capsule(Vec3, Vec3, f32),
|
||||
#[default]
|
||||
Mesh,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub enum AutoAABBCollider {
|
||||
#[default]
|
||||
Cuboid,
|
||||
Ball,
|
||||
Capsule,
|
||||
}
|
||||
|
||||
// replaces all physics stand-ins with the actual rapier types
|
||||
pub fn physics_replace_proxies(
|
||||
meshes: Res<Assets<Mesh>>,
|
||||
mesh_handles: Query<&Handle<Mesh>>,
|
||||
mut proxy_colliders: Query<
|
||||
(Entity, &Collider, &Name, &mut Visibility),
|
||||
(Without<RapierCollider>, Added<Collider>),
|
||||
>,
|
||||
// needed for tri meshes
|
||||
children: Query<&Children>,
|
||||
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for proxy_colider in proxy_colliders.iter_mut() {
|
||||
let (entity, collider_proxy, name, mut visibility) = proxy_colider;
|
||||
// we hide the collider meshes: perhaps they should be removed altogether once processed ?
|
||||
if name.ends_with("_collider") || name.ends_with("_sensor") {
|
||||
*visibility = Visibility::Hidden;
|
||||
}
|
||||
|
||||
let mut rapier_collider: RapierCollider;
|
||||
match collider_proxy {
|
||||
Collider::Ball(radius) => {
|
||||
info!("generating collider from proxy: ball");
|
||||
rapier_collider = RapierCollider::ball(*radius);
|
||||
commands.entity(entity)
|
||||
.insert(rapier_collider)
|
||||
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||
;
|
||||
}
|
||||
Collider::Cuboid(size) => {
|
||||
info!("generating collider from proxy: cuboid");
|
||||
rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z);
|
||||
commands.entity(entity)
|
||||
.insert(rapier_collider)
|
||||
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||
;
|
||||
}
|
||||
Collider::Capsule(a, b, radius) => {
|
||||
info!("generating collider from proxy: capsule");
|
||||
rapier_collider = RapierCollider::capsule(*a, *b, *radius);
|
||||
commands.entity(entity)
|
||||
.insert(rapier_collider)
|
||||
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||
;
|
||||
}
|
||||
Collider::Mesh => {
|
||||
info!("generating collider from proxy: mesh");
|
||||
for (_, collider_mesh) in
|
||||
Mesh::search_in_children(entity, &children, &meshes, &mesh_handles)
|
||||
{
|
||||
rapier_collider = RapierCollider::from_bevy_mesh(
|
||||
collider_mesh,
|
||||
&ComputedColliderShape::TriMesh,
|
||||
)
|
||||
.unwrap();
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(rapier_collider)
|
||||
// FIXME: this is just for demo purposes !!!
|
||||
.insert(
|
||||
ActiveCollisionTypes::default()
|
||||
| ActiveCollisionTypes::KINEMATIC_STATIC
|
||||
| ActiveCollisionTypes::STATIC_STATIC
|
||||
| ActiveCollisionTypes::DYNAMIC_STATIC,
|
||||
)
|
||||
.insert(ActiveEvents::COLLISION_EVENTS);
|
||||
// .insert(ActiveEvents::COLLISION_EVENTS)
|
||||
// break;
|
||||
// RapierCollider::convex_hull(points)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/src/util/trait_extension.rs
|
||||
|
||||
pub(crate) trait Vec3Ext: Copy {
|
||||
fn is_approx_zero(self) -> bool;
|
||||
fn split(self, up: Vec3) -> SplitVec3;
|
||||
}
|
||||
impl Vec3Ext for Vec3 {
|
||||
#[inline]
|
||||
fn is_approx_zero(self) -> bool {
|
||||
self.length_squared() < 1e-5
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn split(self, up: Vec3) -> SplitVec3 {
|
||||
let vertical = up * self.dot(up);
|
||||
let horizontal = self - vertical;
|
||||
SplitVec3 {
|
||||
vertical,
|
||||
horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct SplitVec3 {
|
||||
pub(crate) vertical: Vec3,
|
||||
pub(crate) horizontal: Vec3,
|
||||
}
|
||||
|
||||
pub(crate) trait Vec2Ext: Copy {
|
||||
fn is_approx_zero(self) -> bool;
|
||||
fn x0y(self) -> Vec3;
|
||||
}
|
||||
impl Vec2Ext for Vec2 {
|
||||
#[inline]
|
||||
fn is_approx_zero(self) -> bool {
|
||||
self.length_squared() < 1e-5
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn x0y(self) -> Vec3 {
|
||||
Vec3::new(self.x, 0., self.y)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait MeshExt {
|
||||
fn transform(&mut self, transform: Transform);
|
||||
fn transformed(&self, transform: Transform) -> Mesh;
|
||||
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||
fn search_in_children<'a>(
|
||||
parent: Entity,
|
||||
children: &'a Query<&Children>,
|
||||
meshes: &'a Assets<Mesh>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> Vec<(Entity, &'a Mesh)>;
|
||||
}
|
||||
|
||||
impl MeshExt for Mesh {
|
||||
fn transform(&mut self, transform: Transform) {
|
||||
for coords in self.read_coords_mut(Mesh::ATTRIBUTE_POSITION.clone()) {
|
||||
let vec3 = (*coords).into();
|
||||
let transformed = transform.transform_point(vec3);
|
||||
*coords = transformed.into();
|
||||
}
|
||||
for normal in self.read_coords_mut(Mesh::ATTRIBUTE_NORMAL.clone()) {
|
||||
let vec3 = (*normal).into();
|
||||
let transformed = transform.rotation.mul_vec3(vec3);
|
||||
*normal = transformed.into();
|
||||
}
|
||||
}
|
||||
|
||||
fn transformed(&self, transform: Transform) -> Mesh {
|
||||
let mut mesh = self.clone();
|
||||
mesh.transform(transform);
|
||||
mesh
|
||||
}
|
||||
|
||||
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||
// Guaranteed by Bevy for the current usage
|
||||
match self
|
||||
.attribute_mut(id)
|
||||
.expect("Failed to read unknown mesh attribute")
|
||||
{
|
||||
VertexAttributeValues::Float32x3(values) => values,
|
||||
// Guaranteed by Bevy for the current usage
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn search_in_children<'a>(
|
||||
parent: Entity,
|
||||
children_query: &'a Query<&Children>,
|
||||
meshes: &'a Assets<Mesh>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> Vec<(Entity, &'a Mesh)> {
|
||||
if let Ok(children) = children_query.get(parent) {
|
||||
let mut result: Vec<_> = children
|
||||
.iter()
|
||||
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||
.map(|(entity, mesh_handle)| {
|
||||
(
|
||||
entity,
|
||||
meshes
|
||||
.get(mesh_handle)
|
||||
.expect("Failed to get mesh from handle"),
|
||||
)
|
||||
})
|
||||
.map(|(entity, mesh)| {
|
||||
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||
(entity, mesh)
|
||||
})
|
||||
.collect();
|
||||
let mut inner_result = children
|
||||
.iter()
|
||||
.flat_map(|entity| {
|
||||
Self::search_in_children(*entity, children_query, meshes, mesh_handles)
|
||||
})
|
||||
.collect();
|
||||
result.append(&mut inner_result);
|
||||
result
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait F32Ext: Copy {
|
||||
fn is_approx_zero(self) -> bool;
|
||||
fn squared(self) -> f32;
|
||||
fn lerp(self, other: f32, ratio: f32) -> f32;
|
||||
}
|
||||
|
||||
impl F32Ext for f32 {
|
||||
#[inline]
|
||||
fn is_approx_zero(self) -> bool {
|
||||
self.abs() < 1e-5
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn squared(self) -> f32 {
|
||||
self * self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn lerp(self, other: f32, ratio: f32) -> f32 {
|
||||
self.mul_add(1. - ratio, other * ratio)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait TransformExt: Copy {
|
||||
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform;
|
||||
fn lerp(self, other: Transform, ratio: f32) -> Transform;
|
||||
}
|
||||
|
||||
impl TransformExt for Transform {
|
||||
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform {
|
||||
let direction = target - self.translation;
|
||||
let horizontal_direction = direction - up * direction.dot(up);
|
||||
let look_target = self.translation + horizontal_direction;
|
||||
self.looking_at(look_target, up)
|
||||
}
|
||||
|
||||
fn lerp(self, other: Transform, ratio: f32) -> Transform {
|
||||
let translation = self.translation.lerp(other.translation, ratio);
|
||||
let rotation = self.rotation.slerp(other.rotation, ratio);
|
||||
let scale = self.scale.lerp(other.scale, ratio);
|
||||
Transform {
|
||||
translation,
|
||||
rotation,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/6e31fc02652fc9d085a4adde0a73ab007dbbb0dc/src/util/trait_extension.rs
|
||||
|
||||
pub trait Vec3Ext {
|
||||
#[allow(clippy::wrong_self_convention)] // Because [`Vec3`] is [`Copy`]
|
||||
fn is_approx_zero(self) -> bool;
|
||||
fn x0z(self) -> Vec3;
|
||||
}
|
||||
impl Vec3Ext for Vec3 {
|
||||
fn is_approx_zero(self) -> bool {
|
||||
[self.x, self.y, self.z].iter().all(|&x| x.abs() < 1e-5)
|
||||
}
|
||||
fn x0z(self) -> Vec3 {
|
||||
Vec3::new(self.x, 0., self.z)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MeshExt {
|
||||
fn transform(&mut self, transform: Transform);
|
||||
fn transformed(&self, transform: Transform) -> Mesh;
|
||||
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||
fn search_in_children<'a>(
|
||||
children: &'a Children,
|
||||
meshes: &'a Assets<Mesh>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> (Entity, &'a Mesh);
|
||||
}
|
||||
|
||||
impl MeshExt for Mesh {
|
||||
fn transform(&mut self, transform: Transform) {
|
||||
for attribute in [Mesh::ATTRIBUTE_POSITION, Mesh::ATTRIBUTE_NORMAL] {
|
||||
for coords in self.read_coords_mut(attribute.clone()) {
|
||||
let vec3 = (*coords).into();
|
||||
let transformed = transform.transform_point(vec3);
|
||||
*coords = transformed.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transformed(&self, transform: Transform) -> Mesh {
|
||||
let mut mesh = self.clone();
|
||||
mesh.transform(transform);
|
||||
mesh
|
||||
}
|
||||
|
||||
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||
match self.attribute_mut(id).unwrap() {
|
||||
VertexAttributeValues::Float32x3(values) => values,
|
||||
// Guaranteed by Bevy
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn search_in_children<'a>(
|
||||
children: &'a Children,
|
||||
meshes: &'a Assets<Mesh>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> (Entity, &'a Mesh) {
|
||||
let entity_handles: Vec<_> = children
|
||||
.iter()
|
||||
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
entity_handles.len(),
|
||||
1,
|
||||
"Collider must contain exactly one mesh, but found {}",
|
||||
entity_handles.len()
|
||||
);
|
||||
let (entity, mesh_handle) = entity_handles.first().unwrap();
|
||||
let mesh = meshes.get(mesh_handle).unwrap();
|
||||
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||
(*entity, mesh)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
pub mod relationships_insert_dependant_components;
|
||||
pub use relationships_insert_dependant_components::*;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct EcsRelationshipsPlugin;
|
||||
impl Plugin for EcsRelationshipsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub fn insert_dependant_component<
|
||||
Dependant: Component,
|
||||
Dependency: Component + std::default::Default,
|
||||
>(
|
||||
mut commands: Commands,
|
||||
entities_without_depency: Query<(Entity, &Name), (With<Dependant>, Without<Dependency>)>,
|
||||
) {
|
||||
for (entity, name) in entities_without_depency.iter() {
|
||||
let name = name.clone().to_string();
|
||||
commands.entity(entity).insert(Dependency::default());
|
||||
warn!("found an entity called {} with a {} component but without an {}, please check your assets", name.clone(), std::any::type_name::<Dependant>(), std::any::type_name::<Dependency>());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
assets::GameAssets,
|
||||
state::{GameState, InAppRunning},
|
||||
};
|
||||
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::state::{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() })
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
pub mod in_game;
|
||||
pub use in_game::*;
|
||||
|
||||
pub mod in_main_menu;
|
||||
pub use in_main_menu::*;
|
||||
|
||||
pub mod picking;
|
||||
pub use picking::*;
|
||||
|
||||
use crate::state::{AppState, GameState};
|
||||
use bevy::prelude::*;
|
||||
|
||||
// this file is just for demo purposes, contains various types of components, systems etc
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub enum SoundMaterial {
|
||||
Metal,
|
||||
Wood,
|
||||
Rock,
|
||||
Cloth,
|
||||
Squishy,
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
/// Demo marker component
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
/// Demo component showing auto injection of components
|
||||
pub struct ShouldBeWithPlayer;
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
/// Demo marker component
|
||||
pub struct Interactible;
|
||||
|
||||
fn player_move_demo(
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
mut players: Query<&mut Transform, With<Player>>,
|
||||
) {
|
||||
let speed = 0.2;
|
||||
if let Ok(mut player) = players.get_single_mut() {
|
||||
if keycode.pressed(KeyCode::Left) {
|
||||
player.translation.x += speed;
|
||||
}
|
||||
if keycode.pressed(KeyCode::Right) {
|
||||
player.translation.x -= speed;
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Up) {
|
||||
player.translation.z += speed;
|
||||
}
|
||||
if keycode.pressed(KeyCode::Down) {
|
||||
player.translation.z -= speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GamePlugin;
|
||||
impl Plugin for GamePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(PickingPlugin)
|
||||
.register_type::<Interactible>()
|
||||
.register_type::<SoundMaterial>()
|
||||
.register_type::<Player>()
|
||||
// little helper utility, to automatically inject components that are dependant on an other component
|
||||
// ie, here an Entity with a Player component should also always have a ShouldBeWithPlayer component
|
||||
// you get a warning if you use this, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
// insert_dependant_component::<Player, ShouldBeWithPlayer>,
|
||||
player_move_demo, //.run_if(in_state(AppState::Running)),
|
||||
// test_collision_events
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
use super::Player;
|
||||
use bevy::prelude::*;
|
||||
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct Pickable;
|
||||
|
||||
// very simple, crude picking (as in picking up objects) implementation
|
||||
|
||||
pub fn picking(
|
||||
players: Query<&GlobalTransform, With<Player>>,
|
||||
pickables: Query<(Entity, &GlobalTransform), With<Pickable>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for player_transforms in players.iter() {
|
||||
for (pickable, pickable_transforms) in pickables.iter() {
|
||||
let distance = player_transforms
|
||||
.translation()
|
||||
.distance(pickable_transforms.translation());
|
||||
if distance < 2.5 {
|
||||
commands.entity(pickable).despawn_recursive();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PickingPlugin;
|
||||
impl Plugin for PickingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<Pickable>()
|
||||
.add_systems(Update, (picking.after(GltfBlueprintsSet::AfterSpawn),));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_editor_pls::prelude::*;
|
||||
|
||||
mod core;
|
||||
use crate::core::*;
|
||||
|
||||
pub mod assets;
|
||||
use assets::*;
|
||||
|
||||
pub mod state;
|
||||
use state::*;
|
||||
|
||||
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
|
||||
StatePlugin,
|
||||
AssetsPlugin,
|
||||
CorePlugin, // reusable plugins
|
||||
GamePlugin, // specific to our game
|
||||
ComponentsTestPlugin, // Showcases different type of components /structs
|
||||
))
|
||||
.run();
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
use bevy::app::AppExit;
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
|
||||
pub enum AppState {
|
||||
#[default]
|
||||
CoreLoading,
|
||||
MenuRunning,
|
||||
AppLoading,
|
||||
AppRunning,
|
||||
AppEnding,
|
||||
|
||||
// FIXME: not sure
|
||||
LoadingGame,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
|
||||
pub enum GameState {
|
||||
#[default]
|
||||
None,
|
||||
|
||||
InMenu,
|
||||
InGame,
|
||||
|
||||
InGameOver,
|
||||
|
||||
InSaving,
|
||||
InLoading,
|
||||
}
|
||||
|
||||
// tag components for all entities within a certain state (for despawning them if needed) , FIXME: seems kinda hack-ish
|
||||
#[derive(Component)]
|
||||
pub struct InCoreLoading;
|
||||
#[derive(Component, Default)]
|
||||
pub struct InMenuRunning;
|
||||
#[derive(Component)]
|
||||
pub struct InAppLoading;
|
||||
#[derive(Component)]
|
||||
pub struct InAppRunning;
|
||||
|
||||
// components for tagging in game vs in game menu stuff
|
||||
#[derive(Component, Default)]
|
||||
pub struct InMainMenu;
|
||||
#[derive(Component, Default)]
|
||||
pub struct InMenu;
|
||||
#[derive(Component, Default)]
|
||||
pub struct InGame;
|
||||
|
||||
pub struct StatePlugin;
|
||||
impl Plugin for StatePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_state::<AppState>().add_state::<GameState>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct UnitTest;
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleTestF32(f32);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleTestU64(u64);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||
#[reflect(Component)]
|
||||
pub struct TuppleTestStr(String);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleTest2(f32, u64, String);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleTestBool(bool);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleVec2(Vec2);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleVec3(Vec3);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleVec(Vec<String>);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleVecF32F32(Vec<(f32, f32)>);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct TuppleTestColor(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: TuppleTestColor,
|
||||
toggle: TuppleTestBool,
|
||||
basic: BasicTest,
|
||||
pub nested: NestingTestLevel3,
|
||||
colors_list: VecOfColors,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct NestingTestLevel3 {
|
||||
vec: TuppleVec3,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct NestedTuppleStuff(f32, u64, NestingTestLevel2);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub enum EnumComplex {
|
||||
Float(f32),
|
||||
Wood(String),
|
||||
Vec(BasicTest),
|
||||
SomeThing,
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct VecOfVec3s2(Vec<TuppleVec3>);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct VecOfColors(Vec<Color>);
|
||||
pub struct ComponentsTestPlugin;
|
||||
impl Plugin for ComponentsTestPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<BasicTest>()
|
||||
.register_type::<UnitTest>()
|
||||
.register_type::<TuppleTestF32>()
|
||||
.register_type::<TuppleTestU64>()
|
||||
.register_type::<TuppleTestStr>()
|
||||
.register_type::<TuppleTestBool>()
|
||||
.register_type::<TuppleTest2>()
|
||||
.register_type::<TuppleVec2>()
|
||||
.register_type::<TuppleVec3>()
|
||||
.register_type::<EnumTest>()
|
||||
.register_type::<TuppleTestColor>()
|
||||
.register_type::<TuppleVec>()
|
||||
.register_type::<Vec<String>>()
|
||||
.register_type::<NestingTestLevel2>()
|
||||
.register_type::<NestingTestLevel3>()
|
||||
.register_type::<NestedTuppleStuff>()
|
||||
.register_type::<EnumComplex>()
|
||||
.register_type::<VecOfVec3s2>()
|
||||
.register_type::<TuppleVecF32F32>()
|
||||
.register_type::<(f32, f32)>()
|
||||
.register_type::<Vec<(f32, f32)>>()
|
||||
.register_type::<Vec<TuppleVec3>>()
|
||||
.register_type::<Vec<Color>>()
|
||||
.register_type::<VecOfColors>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
# Bevy components
|
||||
|
||||
This [Blender addon](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/bevy_components) gives you an easy to use UI to add and configure your [Bevy](https://bevyengine.org/) components inside Blender !
|
||||
|
||||
- **automatically generates a simple UI** to add/configure components based on a **registry schema** file (an export of all your Bevy components's information, generated)
|
||||
by the [bevy_registry_export](https://crates.io/crates/bevy_registry_export) crate/plugin
|
||||
- no more need to specify components manually using custom_properties, with error prone naming etc
|
||||
- adds **metadata** to objects containing information about what components it uses + some extra information
|
||||
- uses Blender's **PropertyGroups** to generate custom UIs & connects those groups with the custom properties so that no matter the complexity
|
||||
of your Bevy components you get a nicely packed custom_property to use with ...
|
||||
- the ideal companion to the [gltf_auto_export](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export) to embed your Bevy components inside your gltf files
|
||||
<!-- - adds the ability to **toggle components** on/off without having to remove the component from the object -->
|
||||
|
||||
|
||||
> Important:
|
||||
the tooling is still in the early stages, even if it is feature complete : use with caution!.
|
||||
|
||||
## Installation:
|
||||
|
||||
* grab the latest release zip file from the releases tab (choose the bevy_components releases !)
|
||||
|
||||
<!--![blender addon install](./docs/blender_addon_install_zip.png)-->
|
||||
|
||||
* in Blender go to edit => preferences => install
|
||||
|
||||
![blender addon install](./docs/blender_addon_install.png)
|
||||
|
||||
* choose the path where ```bevy_components.zip``` is stored
|
||||
|
||||
![blender addon install](./docs/blender_addon_install2.png)
|
||||
|
||||
|
||||
## Configuration & overview
|
||||
|
||||
Before you can use the add-on you need to configure it
|
||||
|
||||
### Bevy side
|
||||
|
||||
- setup [bevy_registry_export](https://crates.io/crates/bevy_registry_export) for your project (see the crate's documentation for that), and compile/run it to get the ```registry.json``` file
|
||||
|
||||
### Blender side
|
||||
|
||||
- Go to the new Bevy Components tab in the 3D view
|
||||
|
||||
![configuration](./docs/configuration.png)
|
||||
|
||||
- click on the button to select your registry.json file (in the "configuration" panel)
|
||||
|
||||
![configuration 2](./docs/configuration2.png)
|
||||
|
||||
- the list of available components will appear
|
||||
|
||||
![configuration 3](./docs/configuration3.png)
|
||||
|
||||
|
||||
|
||||
## Use
|
||||
|
||||
|
||||
### Existing components & custom properties
|
||||
|
||||
* If you already have components defined manualy in Blender inside **custom properties** you will need to define them again using the UI!
|
||||
* avoid mixing & matching: if you change the values of **custom properties** that also have a component, the custom property will be **overriden** every time
|
||||
you change the component's value
|
||||
* you can of course still use non component custom properties as always, this add-on will only impact those that have corresponding Bevy components
|
||||
|
||||
### adding components
|
||||
|
||||
- to add a component, select an object and then select the component from the components list: (the full type information will be displayed as tooltip)
|
||||
|
||||
![components list](./docs/components_list.png)
|
||||
|
||||
- click on the dropdown to get the full list of available components
|
||||
|
||||
![components list](./docs/components_list2.png)
|
||||
|
||||
- you can also filter components by name for convenience
|
||||
|
||||
![filter components](./docs/filter_components.png)
|
||||
|
||||
|
||||
- add a component by clicking on the "add component" button once you have selected your desired component
|
||||
|
||||
it will appear in the component list for that object
|
||||
|
||||
![add component](./docs/add_component.png)
|
||||
|
||||
### edit components
|
||||
|
||||
- to edit a component's value just use the UI:
|
||||
|
||||
![edit component](./docs/edit_component.png)
|
||||
|
||||
it will automatically update the value of the corresponding custom property
|
||||
|
||||
![edit component](./docs/edit_component2.png)
|
||||
|
||||
### copy & pasting
|
||||
|
||||
- you can also copy & paste components between objects
|
||||
|
||||
- click on the "copy component button" of the component you want to copy
|
||||
|
||||
![copy component](./docs/copy_component.png)
|
||||
|
||||
- then select the object you want to copy the component (& its value) to, and click on the paste button.
|
||||
|
||||
It will add the component to the select object
|
||||
|
||||
![paste component](./docs/paste_component.png)
|
||||
|
||||
> if the target object already has the same component, its values will be overwritten
|
||||
|
||||
|
||||
## Additional components UI features
|
||||
|
||||
- for large/ complex components you can toggle the details of that component:
|
||||
|
||||
![toggle details](./docs/toggle_details.png)
|
||||
|
||||
|
||||
## Supported components
|
||||
|
||||
- normally (minus any bugs, please report those!) all components using **registered** types should be useable and editable
|
||||
- this includes (non exhaustive list):
|
||||
* enums (even complex ones !)
|
||||
|
||||
![enums](./docs/enums.png)
|
||||
|
||||
![enums](./docs/enums2.png)
|
||||
|
||||
|
||||
* complex structs, with various types of fields (including nested ones)
|
||||
|
||||
![complex](./docs/complex_components2.png)
|
||||
|
||||
* lists/ vecs (here a vec of tuples)
|
||||
|
||||
![lists](./docs/vecs_lists.png)
|
||||
|
||||
* etc !
|
||||
|
||||
## Unregistered types & error handling
|
||||
|
||||
- non registered types can be viewed in this panel : (can be practical to see if you have any missing registrations too!)
|
||||
|
||||
![unregistered types](./docs/unregistered_types.png)
|
||||
|
||||
- if you have a component made up of unregistered structs/enums etc, you will get visual feedback & the component will be deactivated
|
||||
|
||||
![invalid component](./docs/invalid_components.png)
|
||||
|
||||
> important ! ```gltf_auto_export``` currently has no way of filtering out components, so you need to delete invalid components like these before exporting
|
||||
this will be adress in the future
|
||||
|
||||
- if you are encountering this type of view: don't panic your component data is not gone ! It just means you need to reload the registry data by clicking on the relevant button
|
||||
|
||||
![missing registry data](./docs/missing_registry_data.png)
|
||||
|
||||
|
||||
|
||||
## advanced configuration
|
||||
|
||||
- there are also additional QOL features, that you should not need most of the time
|
||||
|
||||
- "update custom properties of current object" : will go over **all components** that you have defined for the **currently selected object**, and re-generate the
|
||||
|
||||
corresponding custom property values
|
||||
|
||||
![update custom properties](./docs/other_options.png)
|
||||
|
||||
|
||||
- "update custom properties of ALL objects" : same as above but it will do so for the **ALL objects in your blend file** (so can be slow!), and re-generate the
|
||||
|
||||
corresponding custom property values
|
||||
|
||||
![update custom properties for all](./docs/other_options2.png)
|
||||
|
||||
|
||||
## Additional important information
|
||||
|
||||
|
||||
- for the components to work correctly with [```bevy_gltf_components```](https://crates.io/crates/bevy_gltf_components) or [```bevy_gltf_blueprints```](https://crates.io/crates/bevy_gltf_blueprints) you will need to set the ```legacy_mode``` for those plugins to **FALSE**
|
||||
as the component data generated by this add on is a complete, clean **ron** data that is incompatible with the previous (legacy versions).
|
||||
Please see the documentation of those crates for more information.
|
||||
|
||||
> Note: the legacy mode support will be dropped in future versions, and the default behaviour will be NO legacy mode
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
you can find an example [here](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/)
|
||||
|
||||
## License
|
||||
|
||||
This tool, all its code, contents & assets is Dual-licensed under either of
|
||||
|
||||
- Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](../LICENSE_MIT.md) or https://opensource.org/licenses/MIT)
|
|
@ -0,0 +1,139 @@
|
|||
Basics
|
||||
- [x] add panel
|
||||
- [x] add a "create blueprint" button
|
||||
- [x] when clicked:
|
||||
- [x] create collection
|
||||
- [x] add an empty inside collection and name it <COLLECTION_NAME>_components
|
||||
- [x] add a **AutoExport** Boolean property to collection
|
||||
- [x] add name imput(popup for name input ?)
|
||||
|
||||
- [x] add a list of existing components/custom properties
|
||||
- [x] add an "edit blueprint" section
|
||||
- [x] only filled when there is ONE selection, and that selection is a collection
|
||||
- [x] add a dropdown of possible components
|
||||
- [x] add a checkbox for enabling disabling a component (enabled by default)
|
||||
- [x] add a button for copying a component
|
||||
- [x] add a button for pasting a component
|
||||
|
||||
|
||||
UI:
|
||||
- [x] filterable list of components to DISPLAY for selection : ComponentDefinitionsList
|
||||
|
||||
- Filter out invalid objects for components that have no _components suffix ? (that is too limiting I think)
|
||||
- -[x] How to deal with pre-existing custom properties that have NO metadata
|
||||
* if there is one without metadata: find if there is an available component with the same name & type ?
|
||||
* if there is , insert metadata
|
||||
* otherwise, mark it in some way visually ?
|
||||
|
||||
- [x] for OBJECT enums: add two ui pieces
|
||||
- [x] one for selecting the TYPE to choose (ie normal enum)
|
||||
- [x] one for setting the VALUE inside that
|
||||
|
||||
|
||||
- [x] vecs => (not vec2, vec3 etc) more complex UI to add items in a list
|
||||
- [x] generate contained CollectionGroup
|
||||
- [x] CollectionProperty => type = the above
|
||||
- [x] find ways to "collapse" the different levels of nested data of structs/tupples into a single custom property (ideally on the fly, but we can do without)
|
||||
|
||||
- [x] for single tupple components that represent a single unit type, re_use the base type's UIPropertyGroup instead of creating specific ones (ie TuppleTestF32_ui...) => will not work, would cause overriden "update callback"
|
||||
- [x] pre_generate default values/values for each main type
|
||||
|
||||
- [x] fix issues with vec2 etc not having the correct number of items
|
||||
- [x] fix bad defaults in ui group
|
||||
- [x] fix object enums handling on updates (??)
|
||||
- [x] fix issues with lambads in loops
|
||||
|
||||
- [x] object enum should be <EntryName>(params)
|
||||
ie *Collider:
|
||||
* Cuboid(Vec3)
|
||||
* Sphere(radius)
|
||||
- [x] deal with enums variants that do not have any data: ex {
|
||||
"title": "Mesh"
|
||||
}
|
||||
|
||||
- [x] remove / change use of ComponentDefinitionsList
|
||||
- when filling the list, use the long_name as index ie items.append((str(index), item.name, item.long_name)) => items.append((item.long_name, item.name, item.long_name))
|
||||
- [x] when removing a component, reset the value of the attribute in the property group (or not ? could be a feature)
|
||||
- [x] deal correctly with fields of types that are NOT in the schema.json (for ex PlayingAnimation in AnimationPlayer)
|
||||
- [ ] deal correctly with complex types
|
||||
CascadeShadowConfig: has an array/list
|
||||
ClusterConfig: one of the enum variants is an object
|
||||
- [ ] possibly allow Color to be an enum as it should be ?
|
||||
- [x] for sub items , the update functions "Name" should be the one of the root object
|
||||
- [x] fix copy & pasting
|
||||
- it actually works, but the value of the custom property are not copied back to the UI, need to implement property_group_value_from_custom_property_value
|
||||
- [ ] we need a notion of "root propertyGroup" =?
|
||||
- [x] notify user of missing entries in schema (ie , unregistered data types)
|
||||
- [x] clarify propgroup_ui vs named nested fields
|
||||
- [x] fix basic enums handling
|
||||
- [x] add a list of not found components to the registry, add to them on the fly
|
||||
- [x] add configuration panel (open the first time, closed on further user once configured)
|
||||
|
||||
- [x] add limits to ixxx types vs utypes
|
||||
- [x] only display the "generate components xx" when relevant ie:
|
||||
- go through list of custom properties in current object
|
||||
- if one does not have metadata and / or propgroup:
|
||||
break
|
||||
|
||||
- [x] remove custom property of disabled component ? => NOpe, as we need custom properties to iterate over
|
||||
- [x] what to do with components with n/a fields ? perhaps disable the component ? add a "invalid" field to meta ?
|
||||
- [x] format output as correct RON
|
||||
- [x] fix issue with empty strings
|
||||
- [x] change custom property => propGroup to convert RON => Json first => obsolete
|
||||
- [x] cleanup process_lists
|
||||
|
||||
- [x] fix issues with enum variants with only a title
|
||||
|
||||
- [x] display single item enums inline, others in a seperate row
|
||||
|
||||
- [x] add button to "apply all" (in configuration), to apply/update all custom properties to ALL objects where relevant
|
||||
- [x] add button to "apply to current" to do the same with current
|
||||
- [x] add warning sign to the above
|
||||
|
||||
- [x] what about metadata ?
|
||||
- [x] only upgrade custom properties to metadata when asked/relevant
|
||||
- [x] implement move list up/down
|
||||
- [ ] change property_group_value_from_custom_property_value => just disregard it for now, its point is very limited (helping people with old custom properties by attempting to generate real values)
|
||||
and give the change to a real ron format, it is too limiting
|
||||
- [x] fix reload registry clearing list of missing types
|
||||
- [x] clean up metadata module, a lot of repeated code
|
||||
- [x] some fields when original is 0 or 0.0 are not copyable ? (seems like a bad boolean check )
|
||||
- [x] fix issues with object variants in enums (see clusterconfig)
|
||||
|
||||
|
||||
- perhaps directly export default values within the schema.json ?
|
||||
- for most types , it is straighforward, but others, not so much: like the default color in Bevy , etc
|
||||
|
||||
- [x] change default schema.json to registry.json
|
||||
- [x] pasted components do not get updated value in custom_property
|
||||
- [x] finish documentation
|
||||
- [x] add storage of registry path
|
||||
- [x] save after setting the data (browse for)
|
||||
- [x] load after each reload ?
|
||||
|
||||
# Additional
|
||||
- [x] check if output "string" in custom properties are correct
|
||||
|
||||
- gltf_auto_export
|
||||
- [ ] add support for "enabled" flag
|
||||
- [ ] add special components
|
||||
- "AutoExport" => Needed
|
||||
- "Dynamic" ? naah wait that should be exported by the Bevy side
|
||||
- [ ] filter out Components_meta ??
|
||||
- [x] add legacy mode to the persisted parameters
|
||||
|
||||
- bevy_gltf_components:
|
||||
- [x] first release patch for current issues
|
||||
- [x] make configurable
|
||||
- [x] add "compatibility mode" and deprecation warnings for the current hack-ish conversion of fake ron
|
||||
- [x] update docs to show we need to use ComponentsFromGltfPlugin::default
|
||||
|
||||
- bevy_gltf_blueprints
|
||||
- [x] update dependency
|
||||
- [x] update version
|
||||
- [x] add ability to set legacy mode for bevy_gltf_components ?
|
||||
|
||||
- [ ] release all versions
|
||||
- [ ] update main documentation, add compatibility version grid
|
||||
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
bl_info = {
|
||||
"name": "bevy_components",
|
||||
"author": "kaosigh",
|
||||
"version": (0, 1, 0),
|
||||
"blender": (3, 4, 0),
|
||||
"location": "VIEW_3D",
|
||||
"description": "UI to help create Bevy blueprints and components",
|
||||
"warning": "",
|
||||
"wiki_url": "https://github.com/kaosat-dev/Blender_bevy_components_workflow",
|
||||
"tracker_url": "https://github.com/kaosat-dev/Blender_bevy_components_workflow/issues/new",
|
||||
"category": "User Interface"
|
||||
}
|
||||
|
||||
import bpy
|
||||
from bpy.props import (StringProperty)
|
||||
|
||||
from .helpers import load_settings
|
||||
from .blueprints import CreateBlueprintOperator
|
||||
from .components.operators import CopyComponentOperator, DeleteComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, AddComponentOperator, Toggle_ComponentVisibility
|
||||
|
||||
from .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.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 .propGroups.prop_groups import (generate_propertyGroups_for_components)
|
||||
from .components.lists import GENERIC_LIST_OT_actions, Generic_LIST_OT_AddItem, Generic_LIST_OT_RemoveItem, Generic_LIST_OT_SelectItem
|
||||
from .components.definitions_list import (ComponentDefinitionsList, ClearComponentDefinitionsList)
|
||||
from .components.ui import (BEVY_COMPONENTS_PT_ComponentsPanel)
|
||||
|
||||
|
||||
# just a test, remove
|
||||
def scan_item(item, nesting=0):
|
||||
try:
|
||||
for sub in dict(item).keys():
|
||||
print("--", sub, getattr(item[sub], "type_name", None), item[sub], nesting)
|
||||
try:
|
||||
scan_item(item[sub], nesting+1)
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
class BEVY_COMPONENTS_PT_MainPanel(bpy.types.Panel):
|
||||
bl_idname = "BEVY_COMPONENTS_PT_MainPanel"
|
||||
bl_label = ""
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Bevy Components"
|
||||
bl_context = "objectmode"
|
||||
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
name = context.object.name if context.object != None else ''
|
||||
layout.label(text="Components For "+ name)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
object = context.object
|
||||
collection = context.collection
|
||||
|
||||
|
||||
"""row.prop(bpy.context.window_manager, "blueprint_name")
|
||||
op = row.operator(CreateBlueprintOperator.bl_idname, text="Create blueprint", icon="CONSOLE")
|
||||
op.blueprint_name = bpy.context.window_manager.blueprint_name
|
||||
layout.separator()
|
||||
|
||||
current_components_container = None
|
||||
has_components = False
|
||||
for child in collection.objects:
|
||||
if child.name.endswith("_components"):
|
||||
has_components = True
|
||||
current_components_container= child
|
||||
|
||||
if collection is not None and has_components:
|
||||
layout.label(text="Edit blueprint: "+ collection.name)
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
#_register, _unregister = bpy.utils.register_classes_factory(classes)
|
||||
classes = [
|
||||
CreateBlueprintOperator,
|
||||
AddComponentOperator,
|
||||
CopyComponentOperator,
|
||||
PasteComponentOperator,
|
||||
DeleteComponentOperator,
|
||||
GenerateComponent_From_custom_property_Operator,
|
||||
Toggle_ComponentVisibility,
|
||||
|
||||
ComponentDefinitionsList,
|
||||
ClearComponentDefinitionsList,
|
||||
|
||||
ComponentInfos,
|
||||
ComponentsMeta,
|
||||
MissingBevyType,
|
||||
ComponentsRegistry,
|
||||
|
||||
OT_OpenFilebrowser,
|
||||
ReloadRegistryOperator,
|
||||
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL,
|
||||
COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT,
|
||||
|
||||
BEVY_COMPONENTS_PT_MainPanel,
|
||||
BEVY_COMPONENTS_PT_ComponentsPanel,
|
||||
BEVY_COMPONENTS_PT_Configuration,
|
||||
MISSING_TYPES_UL_List,
|
||||
BEVY_COMPONENTS_PT_MissingTypesPanel,
|
||||
|
||||
Generic_LIST_OT_SelectItem,
|
||||
Generic_LIST_OT_AddItem,
|
||||
Generic_LIST_OT_RemoveItem,
|
||||
GENERIC_LIST_OT_actions
|
||||
]
|
||||
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
@persistent
|
||||
def post_load(file_name):
|
||||
print("post load", file_name)
|
||||
registry = bpy.context.window_manager.components_registry
|
||||
registry.schemaPath = load_settings(registry.settings_save_path)["schemaPath"]
|
||||
registry.load_schema()
|
||||
generate_propertyGroups_for_components()
|
||||
ensure_metadata_for_all_objects()
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.types.WindowManager.blueprint_name = StringProperty()
|
||||
|
||||
bpy.app.handlers.load_post.append(post_load)
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
del bpy.types.WindowManager.blueprint_name
|
||||
|
||||
bpy.app.handlers.load_post.remove(post_load)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
import json
|
||||
import bpy
|
||||
from bpy_types import Operator
|
||||
from bpy.props import (StringProperty)
|
||||
|
||||
from .helpers import make_empty
|
||||
|
||||
class CreateBlueprintOperator(Operator):
|
||||
"""Creates blueprint"""
|
||||
bl_idname = "object.simple_operator"
|
||||
bl_label = "Simple Object Operator"
|
||||
|
||||
blueprint_name: StringProperty(
|
||||
name="blueprint name",
|
||||
description="blueprint name to add",
|
||||
default="NewBlueprint"
|
||||
)
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
blueprint_name = self.blueprint_name
|
||||
if blueprint_name == '':
|
||||
blueprint_name = "NewBlueprint"
|
||||
collection = bpy.data.collections.new(blueprint_name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
collection['AutoExport'] = True
|
||||
|
||||
# this is in order to deal with automatic naming
|
||||
blueprint_name = collection.name
|
||||
|
||||
components_empty = make_empty(blueprint_name + "_components", [0,0,0], [0,0,0], [0,0,0], bpy.context.scene.collection)
|
||||
bpy.ops.collection.objects_remove_all()
|
||||
|
||||
collection.objects.link(components_empty)
|
||||
|
||||
components_empty.select_set(True)
|
||||
bpy.context.view_layer.objects.active = components_empty
|
||||
|
||||
return {'FINISHED'}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import bpy
|
||||
from bpy.props import (StringProperty)
|
||||
|
||||
# this one is for UI only, and its inner list contains a useable list of shortnames of components
|
||||
class ComponentDefinitionsList(bpy.types.PropertyGroup):
|
||||
|
||||
# FIXME: not sure, hard coded exclude list, feels wrong
|
||||
exclude = ['Parent', 'Children']
|
||||
|
||||
def add_component_to_ui_list(self, context):
|
||||
#print("add components to ui_list")
|
||||
items = []
|
||||
type_infos = context.window_manager.components_registry.type_infos
|
||||
short_names = context.window_manager.components_registry.short_names_to_long_names
|
||||
for short_name in sorted(short_names.keys()):
|
||||
long_name = short_names[short_name]
|
||||
definition = type_infos[long_name]
|
||||
is_component = definition['isComponent'] if "isComponent" in definition else False
|
||||
|
||||
if self.filter in short_name and is_component:
|
||||
if not 'Handle' in short_name and not "Cow" in short_name and not "AssetId" in short_name and short_name not in self.exclude: # FIXME: hard coded, seems wrong
|
||||
items.append((long_name, short_name, long_name))
|
||||
return items
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
bpy.types.WindowManager.components_list = bpy.props.PointerProperty(type=ComponentDefinitionsList)
|
||||
|
||||
@classmethod
|
||||
def unregister(cls):
|
||||
del bpy.types.WindowManager.components_list
|
||||
|
||||
list : bpy.props.EnumProperty(
|
||||
name="list",
|
||||
description="list",
|
||||
# items argument required to initialize, just filled with empty values
|
||||
items = add_component_to_ui_list,
|
||||
)
|
||||
filter: StringProperty(
|
||||
name="component filter",
|
||||
description="filter for the components list",
|
||||
options={'TEXTEDIT_UPDATE'}
|
||||
)
|
||||
|
||||
|
||||
class ClearComponentDefinitionsList(bpy.types.Operator):
|
||||
''' clear list of bpy.context.collection.component_definitions '''
|
||||
bl_label = "clear component definitions"
|
||||
bl_idname = "components.clear_component_definitions"
|
||||
|
||||
def execute(self, context):
|
||||
# create a new item, assign its properties
|
||||
bpy.context.collection.component_definitions.clear()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
import json
|
||||
from bpy_types import Operator, UIList
|
||||
from bpy.props import (StringProperty, EnumProperty, PointerProperty, FloatVectorProperty, IntProperty)
|
||||
|
||||
class Generic_LIST_OT_AddItem(Operator):
|
||||
"""Add a new item to the list."""
|
||||
bl_idname = "generic_list.add_item"
|
||||
bl_label = "Add a new item"
|
||||
|
||||
property_group_path: StringProperty(
|
||||
name="property group path",
|
||||
description="",
|
||||
)
|
||||
|
||||
component_name: StringProperty(
|
||||
name="component name",
|
||||
description="",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
print("")
|
||||
object = context.object
|
||||
# information is stored in component meta
|
||||
components_in_object = object.components_meta.components
|
||||
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
|
||||
|
||||
propertyGroup = component_meta
|
||||
for path_item in json.loads(self.property_group_path):
|
||||
propertyGroup = getattr(propertyGroup, path_item)
|
||||
|
||||
print("list container", propertyGroup, dict(propertyGroup))
|
||||
target_list = getattr(propertyGroup, "list")
|
||||
index = getattr(propertyGroup, "list_index")
|
||||
item = target_list.add()
|
||||
propertyGroup.list_index = index + 1 # we use this to force the change detection
|
||||
|
||||
print("added item", item, item.field_names, getattr(item, "field_names"))
|
||||
print("")
|
||||
return{'FINISHED'}
|
||||
|
||||
|
||||
class Generic_LIST_OT_RemoveItem(Operator):
|
||||
"""Remove an item to the list."""
|
||||
bl_idname = "generic_list.remove_item"
|
||||
bl_label = "Remove selected item"
|
||||
|
||||
property_group_path: StringProperty(
|
||||
name="property group path",
|
||||
description="",
|
||||
)
|
||||
|
||||
component_name: StringProperty(
|
||||
name="component name",
|
||||
description="",
|
||||
)
|
||||
def execute(self, context):
|
||||
print("remove from list", context.object)
|
||||
|
||||
object = context.object
|
||||
# information is stored in component meta
|
||||
components_in_object = object.components_meta.components
|
||||
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
|
||||
|
||||
propertyGroup = component_meta
|
||||
for path_item in json.loads(self.property_group_path):
|
||||
propertyGroup = getattr(propertyGroup, path_item)
|
||||
|
||||
target_list = getattr(propertyGroup, "list")
|
||||
index = getattr(propertyGroup, "list_index")
|
||||
target_list.remove(index)
|
||||
propertyGroup.list_index = min(max(0, index - 1), len(target_list) - 1)
|
||||
return{'FINISHED'}
|
||||
|
||||
|
||||
class Generic_LIST_OT_SelectItem(Operator):
|
||||
"""Remove an item to the list."""
|
||||
bl_idname = "generic_list.select_item"
|
||||
bl_label = "select an item"
|
||||
|
||||
|
||||
property_group_path: StringProperty(
|
||||
name="property group path",
|
||||
description="",
|
||||
)
|
||||
|
||||
component_name: StringProperty(
|
||||
name="component name",
|
||||
description="",
|
||||
)
|
||||
|
||||
selection_index: IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
print("select in list", context.object)
|
||||
|
||||
object = context.object
|
||||
# information is stored in component meta
|
||||
components_in_object = object.components_meta.components
|
||||
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
|
||||
|
||||
propertyGroup = component_meta
|
||||
for path_item in json.loads(self.property_group_path):
|
||||
propertyGroup = getattr(propertyGroup, path_item)
|
||||
|
||||
target_list = getattr(propertyGroup, "list")
|
||||
index = getattr(propertyGroup, "list_index")
|
||||
|
||||
propertyGroup.list_index = self.selection_index
|
||||
return{'FINISHED'}
|
||||
|
||||
|
||||
class GENERIC_LIST_OT_actions(Operator):
|
||||
"""Move items up and down, add and remove"""
|
||||
bl_idname = "generic_list.list_action"
|
||||
bl_label = "List Actions"
|
||||
bl_description = "Move items up and down, add and remove"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
action: EnumProperty(
|
||||
items=(
|
||||
('UP', "Up", ""),
|
||||
('DOWN', "Down", ""),
|
||||
('REMOVE', "Remove", ""),
|
||||
('ADD', "Add", "")))
|
||||
|
||||
property_group_path: StringProperty(
|
||||
name="property group path",
|
||||
description="",
|
||||
)
|
||||
|
||||
component_name: StringProperty(
|
||||
name="component name",
|
||||
description="",
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
object = context.object
|
||||
# information is stored in component meta
|
||||
components_in_object = object.components_meta.components
|
||||
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
|
||||
|
||||
propertyGroup = component_meta
|
||||
for path_item in json.loads(self.property_group_path):
|
||||
propertyGroup = getattr(propertyGroup, path_item)
|
||||
|
||||
target_list = getattr(propertyGroup, "list")
|
||||
index = getattr(propertyGroup, "list_index")
|
||||
|
||||
|
||||
if self.action == 'DOWN' and index < len(target_list) - 1:
|
||||
#item_next = scn.rule_list[index + 1].name
|
||||
target_list.move(index, index + 1)
|
||||
propertyGroup.list_index += 1
|
||||
|
||||
elif self.action == 'UP' and index >= 1:
|
||||
#item_prev = scn.rule_list[index - 1].name
|
||||
target_list.move(index, index - 1)
|
||||
propertyGroup.list_index -= 1
|
||||
|
||||
elif self.action == 'REMOVE':
|
||||
target_list.remove(index)
|
||||
propertyGroup.list_index = min(max(0, index - 1), len(target_list) - 1)
|
||||
|
||||
if self.action == 'ADD':
|
||||
item = target_list.add()
|
||||
propertyGroup.list_index = index + 1 # we use this to force the change detection
|
||||
#info = '"%s" added to list' % (item.name)
|
||||
#self.report({'INFO'}, info)
|
||||
|
||||
return {"FINISHED"}
|
|
@ -0,0 +1,236 @@
|
|||
import bpy
|
||||
from bpy.props import (StringProperty, BoolProperty, PointerProperty)
|
||||
from bpy_types import (PropertyGroup)
|
||||
from ..propGroups.conversions import property_group_value_from_custom_property_value, property_group_value_to_custom_property_value
|
||||
|
||||
class ComponentInfos(bpy.types.PropertyGroup):
|
||||
name : bpy.props.StringProperty(
|
||||
name = "name",
|
||||
default = ""
|
||||
)
|
||||
|
||||
long_name : bpy.props.StringProperty(
|
||||
name = "long name",
|
||||
default = ""
|
||||
)
|
||||
|
||||
type_name : bpy.props.StringProperty(
|
||||
name = "Type",
|
||||
default = ""
|
||||
)
|
||||
|
||||
values: bpy.props.StringProperty(
|
||||
name = "Value",
|
||||
default = ""
|
||||
)
|
||||
|
||||
enabled: BoolProperty(
|
||||
name="enabled",
|
||||
description="component enabled",
|
||||
default=True
|
||||
)
|
||||
|
||||
invalid: BoolProperty(
|
||||
name="invalid",
|
||||
description="component is invalid, because of missing registration/ other issues",
|
||||
default=False
|
||||
)
|
||||
|
||||
invalid_details: StringProperty(
|
||||
name="invalid details",
|
||||
description="detailed information about why the component is invalid",
|
||||
default=""
|
||||
)
|
||||
|
||||
visible: BoolProperty( # REALLY dislike doing this for UI control, but ok hack for now
|
||||
default=True
|
||||
)
|
||||
|
||||
class ComponentsMeta(PropertyGroup):
|
||||
infos_per_component: StringProperty(
|
||||
name="infos per component",
|
||||
description="component"
|
||||
)
|
||||
components: bpy.props.CollectionProperty(type = ComponentInfos)
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
bpy.types.Object.components_meta = PointerProperty(type=ComponentsMeta)
|
||||
|
||||
@classmethod
|
||||
def unregister(cls):
|
||||
del bpy.types.Object.components_meta
|
||||
|
||||
# We need a collection property of components PER object
|
||||
def get_component_metadata_by_short_name(object, short_name):
|
||||
if not "components_meta" in object:
|
||||
return None
|
||||
return next(filter(lambda component: component["name"] == short_name, object.components_meta.components), None)
|
||||
|
||||
# remove no longer valid metadata from object
|
||||
def cleanup_invalid_metadata(object):
|
||||
components_metadata = object.components_meta.components
|
||||
to_remove = []
|
||||
for index, component_meta in enumerate(components_metadata):
|
||||
short_name = component_meta.name
|
||||
if short_name not in object.keys():
|
||||
print("component:", short_name, "present in metadata, but not in object")
|
||||
to_remove.append(index)
|
||||
for index in to_remove:
|
||||
components_metadata.remove(index)
|
||||
|
||||
|
||||
# returns a component definition ( an entry in registry's type_infos) with matching short name or None if nothing has been found
|
||||
def find_component_definition_from_short_name(short_name):
|
||||
registry = bpy.context.window_manager.components_registry
|
||||
long_name = registry.short_names_to_long_names.get(short_name, None)
|
||||
if long_name != None:
|
||||
return registry.type_infos.get(long_name, None)
|
||||
return None
|
||||
|
||||
# FIXME: feels a bit heavy duty, should only be done
|
||||
# if the components panel is active ?
|
||||
def ensure_metadata_for_all_objects():
|
||||
for object in bpy.data.objects:
|
||||
add_metadata_to_components_without_metadata(object)
|
||||
|
||||
# returns whether an object has custom properties without matching metadata
|
||||
def do_object_custom_properties_have_missing_metadata(object):
|
||||
components_metadata = getattr(object, "components_meta", None)
|
||||
if components_metadata == None:
|
||||
return True
|
||||
|
||||
components_metadata = components_metadata.components
|
||||
|
||||
missing_metadata = False
|
||||
for component_name in dict(object) :
|
||||
if component_name == "components_meta":
|
||||
continue
|
||||
component_meta = next(filter(lambda component: component["name"] == component_name, components_metadata), None)
|
||||
if component_meta == None:
|
||||
# current component has no metadata but is there even a compatible type in the registry ?
|
||||
# if not ignore it
|
||||
component_definition = find_component_definition_from_short_name(component_name)
|
||||
if component_definition != None:
|
||||
missing_metadata = True
|
||||
break
|
||||
|
||||
return missing_metadata
|
||||
|
||||
|
||||
# adds metadata to object only if it is missing
|
||||
def add_metadata_to_components_without_metadata(object):
|
||||
registry = bpy.context.window_manager.components_registry
|
||||
|
||||
for component_name in dict(object) :
|
||||
if component_name == "components_meta":
|
||||
continue
|
||||
upsert_component_in_object(object, component_name, registry)
|
||||
|
||||
# adds a component to an object (including metadata) using the provided component definition & optional value
|
||||
def add_component_to_object(object, component_definition, value=None):
|
||||
cleanup_invalid_metadata(object)
|
||||
if object is not None:
|
||||
print("add_component_to_object", component_definition)
|
||||
long_name = component_definition["title"]
|
||||
short_name = component_definition["short_name"]
|
||||
registry = bpy.context.window_manager.components_registry
|
||||
if registry.type_infos == None:
|
||||
raise Exception('registry type infos have not been loaded yet or ar missing !')
|
||||
definition = registry.type_infos[long_name]
|
||||
# now we use our pre_generated property groups to set the initial value of our custom property
|
||||
(_, propertyGroup) = upsert_component_in_object(object, component_name=short_name, registry=registry)
|
||||
if value == None:
|
||||
value = property_group_value_to_custom_property_value(propertyGroup, definition, registry, None)
|
||||
else: # we have provided a value, that is a raw , custom property value, to set the value of the propertyGroup
|
||||
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, definition, registry, value)
|
||||
del object["__disable__update"]
|
||||
|
||||
object[short_name] = value
|
||||
|
||||
def upsert_component_in_object(object, component_name, registry):
|
||||
# print("upsert_component_in_object", object, "component name", component_name)
|
||||
# TODO: upsert this part too ?
|
||||
target_components_metadata = object.components_meta.components
|
||||
component_definition = find_component_definition_from_short_name(component_name)
|
||||
if component_definition != None:
|
||||
short_name = component_definition["short_name"]
|
||||
long_name = component_definition["title"]
|
||||
property_group_name = short_name+"_ui"
|
||||
propertyGroup = None
|
||||
|
||||
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
|
||||
if not component_meta:
|
||||
component_meta = target_components_metadata.add()
|
||||
component_meta.name = short_name
|
||||
component_meta.long_name = long_name
|
||||
propertyGroup = getattr(component_meta, property_group_name, None)
|
||||
else: # this one has metadata but we check that the relevant property group is present
|
||||
propertyGroup = getattr(component_meta, property_group_name, None)
|
||||
|
||||
# try to inject propertyGroup if not present
|
||||
if propertyGroup == None:
|
||||
#print("propertygroup not found in metadata attempting to inject")
|
||||
if property_group_name in registry.component_propertyGroups:
|
||||
# we have found a matching property_group, so try to inject it
|
||||
# now inject property group
|
||||
setattr(ComponentInfos, property_group_name, registry.component_propertyGroups[property_group_name]) # FIXME: not ideal as all ComponentInfos get the propGroup, but have not found a way to assign it per instance
|
||||
propertyGroup = getattr(component_meta, property_group_name, None)
|
||||
|
||||
# now deal with property groups details
|
||||
if propertyGroup != None:
|
||||
if short_name in registry.invalid_components:
|
||||
component_meta.enabled = False
|
||||
component_meta.invalid = True
|
||||
component_meta.invalid_details = "component contains fields that are not in the schema, disabling"
|
||||
else:
|
||||
# if we still have not found the property group, mark it as invalid
|
||||
component_meta.enabled = False
|
||||
component_meta.invalid = True
|
||||
component_meta.invalid_details = "component not present in the schema, possibly renamed? Disabling for now"
|
||||
# property_group_value_from_custom_property_value(propertyGroup, component_definition, registry, object[component_name])
|
||||
|
||||
return (component_meta, propertyGroup)
|
||||
else:
|
||||
return(None, None)
|
||||
|
||||
|
||||
def copy_propertyGroup_values_to_another_object(source_object, target_object, component_name):
|
||||
if source_object == None or target_object == None or component_name == None:
|
||||
raise Exception('missing input data, cannot copy component propertryGroup')
|
||||
|
||||
component_definition = find_component_definition_from_short_name(component_name)
|
||||
short_name = component_definition["short_name"]
|
||||
property_group_name = short_name+"_ui"
|
||||
registry = bpy.context.window_manager.components_registry
|
||||
|
||||
source_components_metadata = source_object.components_meta.components
|
||||
source_componentMeta = next(filter(lambda component: component["name"] == short_name, source_components_metadata), None)
|
||||
# matching component means we already have this type of component
|
||||
source_propertyGroup = getattr(source_componentMeta, property_group_name)
|
||||
|
||||
# now deal with the target object
|
||||
(_, target_propertyGroup) = upsert_component_in_object(target_object, component_name, registry)
|
||||
# add to object
|
||||
value = property_group_value_to_custom_property_value(target_propertyGroup, component_definition, registry, None)
|
||||
target_object[short_name] = value
|
||||
|
||||
# copy the values over
|
||||
for field_name in source_propertyGroup.field_names:
|
||||
if field_name in source_propertyGroup:
|
||||
target_propertyGroup[field_name] = source_propertyGroup[field_name]
|
||||
apply_propertyGroup_values_to_object_customProperties(target_object)
|
||||
|
||||
# TODO: move to propgroups ?
|
||||
def apply_propertyGroup_values_to_object_customProperties(object):
|
||||
cleanup_invalid_metadata(object)
|
||||
registry = bpy.context.window_manager.components_registry
|
||||
for component_name in dict(object) :
|
||||
if component_name == "components_meta":
|
||||
continue
|
||||
(_, propertyGroup) = upsert_component_in_object(object, component_name, registry)
|
||||
component_definition = find_component_definition_from_short_name(component_name)
|
||||
if component_definition != None:
|
||||
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
|
||||
object[component_name] = value
|
|
@ -0,0 +1,153 @@
|
|||
import ast
|
||||
import json
|
||||
import bpy
|
||||
from bpy_types import Operator
|
||||
from bpy.props import (StringProperty)
|
||||
from .metadata import add_component_to_object, add_metadata_to_components_without_metadata, copy_propertyGroup_values_to_another_object, find_component_definition_from_short_name
|
||||
|
||||
class AddComponentOperator(Operator):
|
||||
"""Add component to blueprint"""
|
||||
bl_idname = "object.addblueprint_to_component"
|
||||
bl_label = "Add component to blueprint Operator"
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
component_type: StringProperty(
|
||||
name="component_type",
|
||||
description="component type to add",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
print("adding component to blueprint", self.component_type)
|
||||
object = context.object
|
||||
|
||||
has_component_type = self.component_type != ""
|
||||
if has_component_type and object != None:
|
||||
type_infos = context.window_manager.components_registry.type_infos
|
||||
component_definition = type_infos[self.component_type]
|
||||
add_component_to_object(object, component_definition)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class CopyComponentOperator(Operator):
|
||||
"""Copy component from blueprint"""
|
||||
bl_idname = "object.copy_component"
|
||||
bl_label = "Copy component Operator"
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
source_component_name: StringProperty(
|
||||
name="source component_name",
|
||||
description="name of the component to copy",
|
||||
)
|
||||
|
||||
source_object_name: StringProperty(
|
||||
name="source object name",
|
||||
description="name of the object to copy the component from",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
bpy.types.WindowManager.copied_source_component_name = StringProperty()
|
||||
bpy.types.WindowManager.copied_source_object = StringProperty()
|
||||
|
||||
@classmethod
|
||||
def unregister(cls):
|
||||
del bpy.types.WindowManager.copied_source_component_name
|
||||
del bpy.types.WindowManager.copied_source_object
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
if self.source_component_name != '' and self.source_object_name != "":
|
||||
context.window_manager.copied_source_component_name = self.source_component_name
|
||||
context.window_manager.copied_source_object = self.source_object_name
|
||||
else:
|
||||
self.report({"ERROR"}, "The source object name / component name to copy a component from have not been specified")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class PasteComponentOperator(Operator):
|
||||
"""Paste component to blueprint"""
|
||||
bl_idname = "object.paste_component"
|
||||
bl_label = "Paste component to blueprint Operator"
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
source_object_name = context.window_manager.copied_source_object
|
||||
source_object = bpy.data.objects.get(source_object_name, None)
|
||||
print("source object", source_object)
|
||||
if source_object == None:
|
||||
self.report({"ERROR"}, "The source object to copy a component from does not exist")
|
||||
else:
|
||||
component_name = context.window_manager.copied_source_component_name
|
||||
if not component_name in source_object:
|
||||
self.report({"ERROR"}, "The source component to copy a component from does not exist")
|
||||
else:
|
||||
component_value = source_object[component_name]
|
||||
print("pasting component to object: component name:", str(component_name), "component value:" + str(component_value))
|
||||
print (context.object)
|
||||
copy_propertyGroup_values_to_another_object(source_object, context.object, component_name)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
|
||||
class DeleteComponentOperator(Operator):
|
||||
"""Delete component from blueprint"""
|
||||
bl_idname = "object.delete_component"
|
||||
bl_label = "Delete component from blueprint Operator"
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
component_name: StringProperty(
|
||||
name="component name",
|
||||
description="component to delete",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
object = context.object
|
||||
if object is not None and self.component_name in object:
|
||||
del object[self.component_name]
|
||||
else:
|
||||
self.report({"ERROR"}, "The object/ component to remove does not exist")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class GenerateComponent_From_custom_property_Operator(Operator):
|
||||
"""generate components from custom property"""
|
||||
bl_idname = "object.generate_component"
|
||||
bl_label = "Generate component from custom_property Operator"
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
component_name: StringProperty(
|
||||
name="component name",
|
||||
description="component to generate custom properties for",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
object = context.object
|
||||
add_metadata_to_components_without_metadata(object)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
|
||||
class Toggle_ComponentVisibility(Operator):
|
||||
"""toggles components visibility"""
|
||||
bl_idname = "object.toggle_component_visibility"
|
||||
bl_label = "Toggle component visibility"
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
component_name: StringProperty(
|
||||
name="component name",
|
||||
description="component to toggle",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
object = context.object
|
||||
components_in_object = object.components_meta.components
|
||||
component_meta = next(filter(lambda component: component["name"] == self.component_name, components_in_object), None)
|
||||
if component_meta != None:
|
||||
component_meta.visible = not component_meta.visible
|
||||
|
||||
return {'FINISHED'}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
import json
|
||||
import bpy
|
||||
from .metadata import do_object_custom_properties_have_missing_metadata
|
||||
from .operators import AddComponentOperator, CopyComponentOperator, DeleteComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, Toggle_ComponentVisibility
|
||||
|
||||
def draw_propertyGroup( propertyGroup, layout, nesting =[], rootName=None):
|
||||
is_enum = getattr(propertyGroup, "with_enum")
|
||||
is_list = getattr(propertyGroup, "with_list")
|
||||
#current_short_name = getattr(propertyGroup, "short_name", "") + "_ui"
|
||||
#nesting = nesting + [current_short_name] # we need this convoluted "nested path strings " workaround so that operators working on a given
|
||||
# item in our components hierarchy can get the correct propertyGroup by STRINGS because of course, we cannot pass objects to operators...sigh
|
||||
|
||||
# if it is an enum, the first field name is always the list of enum variants, the others are the variants
|
||||
field_names = propertyGroup.field_names
|
||||
#print("")
|
||||
#print("drawing", propertyGroup, nesting, "component_name", rootName)
|
||||
#type_name = getattr(propertyGroup, "type_name", None)#propertyGroup.type_name if "type_name" in propertyGroup else ""
|
||||
#print("type name", type_name)
|
||||
#print("name", propertyGroup.name, "name2", getattr(propertyGroup, "name"), "short_name", getattr(propertyGroup, "short_name", None), "nesting", nesting)
|
||||
if is_enum:
|
||||
subrow = layout.row()
|
||||
display_name = field_names[0] if propertyGroup.tupple_or_struct == "struct" else ""
|
||||
subrow.prop(propertyGroup, field_names[0], text=display_name)
|
||||
subrow.separator()
|
||||
selection = getattr(propertyGroup, field_names[0])
|
||||
|
||||
for fname in field_names[1:]:
|
||||
if fname == "variant_" + selection:
|
||||
subrow = layout.row()
|
||||
display_name = fname if propertyGroup.tupple_or_struct == "struct" else ""
|
||||
|
||||
nestedPropertyGroup = getattr(propertyGroup, fname)
|
||||
nested = getattr(nestedPropertyGroup, "nested", False)
|
||||
#print("nestedPropertyGroup", nestedPropertyGroup, fname, nested)
|
||||
if nested:
|
||||
draw_propertyGroup(nestedPropertyGroup, subrow.column(), nesting + [fname], rootName )
|
||||
# if an enum variant is not a propertyGroup
|
||||
break
|
||||
elif is_list:
|
||||
#print("show list", propertyGroup, dict(propertyGroup), propertyGroup.type_name)
|
||||
item_list = getattr(propertyGroup, "list")
|
||||
item_type = getattr(propertyGroup, "type_name_short")
|
||||
list_index = getattr(propertyGroup, "list_index")
|
||||
box = layout.box()
|
||||
split = box.split(factor=0.9)
|
||||
list_column, buttons_column = (split.column(),split.column())
|
||||
|
||||
list_column = list_column.box()
|
||||
for index, item in enumerate(item_list):
|
||||
row = list_column.row()
|
||||
draw_propertyGroup(item, row, nesting, rootName)
|
||||
icon = 'CHECKBOX_HLT' if list_index == index else 'CHECKBOX_DEHLT'
|
||||
op = row.operator('generic_list.select_item', icon=icon, text="")
|
||||
op.component_name = rootName
|
||||
op.property_group_path = json.dumps(nesting)
|
||||
op.selection_index = index
|
||||
|
||||
#various control buttons
|
||||
buttons_column.separator()
|
||||
row = buttons_column.row()
|
||||
op = row.operator('generic_list.list_action', icon='ADD', text="")
|
||||
op.action = 'ADD'
|
||||
op.component_name = rootName
|
||||
op.property_group_path = json.dumps(nesting)
|
||||
|
||||
row = buttons_column.row()
|
||||
op = row.operator('generic_list.list_action', icon='REMOVE', text="")
|
||||
op.action = 'REMOVE'
|
||||
op.component_name = rootName
|
||||
op.property_group_path = json.dumps(nesting)
|
||||
|
||||
buttons_column.separator()
|
||||
row = buttons_column.row()
|
||||
op = row.operator('generic_list.list_action', icon='TRIA_UP', text="")
|
||||
op.action = 'UP'
|
||||
op.component_name = rootName
|
||||
op.property_group_path = json.dumps(nesting)
|
||||
|
||||
row = buttons_column.row()
|
||||
op = row.operator('generic_list.list_action', icon='TRIA_DOWN', text="")
|
||||
op.action = 'DOWN'
|
||||
op.component_name = rootName
|
||||
op.property_group_path = json.dumps(nesting)
|
||||
|
||||
else:
|
||||
for fname in field_names:
|
||||
subrow = layout.row()
|
||||
nestedPropertyGroup = getattr(propertyGroup, fname)
|
||||
nested = getattr(nestedPropertyGroup, "nested", False)
|
||||
display_name = fname if propertyGroup.tupple_or_struct == "struct" else ""
|
||||
|
||||
if nested:
|
||||
layout.separator()
|
||||
layout.label(text=display_name) # this is the name of the field/sub field
|
||||
layout.separator()
|
||||
draw_propertyGroup(nestedPropertyGroup, subrow.column(), nesting + [fname], rootName )
|
||||
else:
|
||||
subrow.prop(propertyGroup, fname, text=display_name)
|
||||
subrow.separator()
|
||||
|
||||
|
||||
class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
|
||||
bl_idname = "BEVY_COMPONENTS_PT_ComponentsPanel"
|
||||
bl_label = "Components"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Bevy Components"
|
||||
bl_context = "objectmode"
|
||||
bl_parent_id = "BEVY_COMPONENTS_PT_MainPanel"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.object is not None)
|
||||
|
||||
def draw(self, context):
|
||||
object = context.object
|
||||
layout = self.layout
|
||||
|
||||
# we get & load our component registry
|
||||
registry = bpy.context.window_manager.components_registry
|
||||
available_components = bpy.context.window_manager.components_list
|
||||
|
||||
|
||||
if object is not None:
|
||||
row = layout.row(align=True)
|
||||
row.prop(available_components, "list", text="Component")
|
||||
row.prop(available_components, "filter",text="Filter")
|
||||
|
||||
# add components
|
||||
row = layout.row(align=True)
|
||||
op = row.operator(AddComponentOperator.bl_idname, text="Add", icon="ADD")
|
||||
op.component_type = available_components.list
|
||||
row.enabled = available_components.list != ''
|
||||
|
||||
layout.separator()
|
||||
|
||||
# paste components
|
||||
row = layout.row(align=True)
|
||||
row.operator(PasteComponentOperator.bl_idname, text="Paste component ("+bpy.context.window_manager.copied_source_component_name+")", icon="PASTEDOWN")
|
||||
row.enabled = registry.type_infos != None and context.window_manager.copied_source_object != ''
|
||||
|
||||
layout.separator()
|
||||
|
||||
# upgrate custom props to components
|
||||
upgradeable_customProperties = registry.type_infos != None and do_object_custom_properties_have_missing_metadata(context.object)
|
||||
if upgradeable_customProperties:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator(GenerateComponent_From_custom_property_Operator.bl_idname, text="generate components from custom properties" , icon="LOOP_FORWARDS")
|
||||
layout.separator()
|
||||
|
||||
|
||||
components_in_object = object.components_meta.components
|
||||
for component_name in sorted(dict(object)) : # sorted by component name, practical
|
||||
if component_name == "components_meta":
|
||||
continue
|
||||
# anything withouth metadata gets skipped, we only want to see real components, not all custom props
|
||||
component_meta = next(filter(lambda component: component["name"] == component_name, components_in_object), None)
|
||||
if component_meta == None:
|
||||
continue
|
||||
|
||||
component_invalid = getattr(component_meta, "invalid")
|
||||
invalid_details = getattr(component_meta, "invalid_details")
|
||||
component_visible = getattr(component_meta, "visible")
|
||||
single_field = False
|
||||
|
||||
# our whole row
|
||||
box = layout.box()
|
||||
row = box.row(align=True)
|
||||
# "header"
|
||||
row.alert = component_invalid
|
||||
row.prop(component_meta, "enabled", text="")
|
||||
row.label(text=component_name)
|
||||
|
||||
# we fetch the matching ui property group
|
||||
root_propertyGroup_name = component_name+"_ui"
|
||||
propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
|
||||
if propertyGroup:
|
||||
# if the component has only 0 or 1 field names, display inline, otherwise change layout
|
||||
single_field = len(propertyGroup.field_names) < 2
|
||||
prop_group_location = box.row(align=True).column()
|
||||
if single_field:
|
||||
prop_group_location = row.column(align=True)#.split(factor=0.9)#layout.row(align=False)
|
||||
|
||||
if component_visible:
|
||||
if component_invalid:
|
||||
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
|
||||
prop_group_location.label(text=error_message)
|
||||
draw_propertyGroup(propertyGroup, prop_group_location, [root_propertyGroup_name], component_name)
|
||||
else :
|
||||
row.label(text="details hidden, click on toggle to display")
|
||||
else:
|
||||
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
|
||||
row.label(text=error_message)
|
||||
|
||||
# "footer" with additional controls
|
||||
op = row.operator(DeleteComponentOperator.bl_idname, text="", icon="X")
|
||||
op.component_name = component_name
|
||||
row.separator()
|
||||
|
||||
op = row.operator(CopyComponentOperator.bl_idname, text="", icon="COPYDOWN")
|
||||
op.source_component_name = component_name
|
||||
op.source_object_name = object.name
|
||||
row.separator()
|
||||
|
||||
#if not single_field:
|
||||
toggle_icon = "TRIA_DOWN" if component_visible else "TRIA_RIGHT"
|
||||
op = row.operator(Toggle_ComponentVisibility.bl_idname, text="", icon=toggle_icon)
|
||||
op.component_name = component_name
|
||||
#row.separator()
|
||||
|
||||
else:
|
||||
layout.label(text ="Select an object to edit its components")
|
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,31 @@
|
|||
import bpy
|
||||
import json
|
||||
|
||||
# Makes an empty, at the specified location, rotation, scale stores it in existing collection, from https://blender.stackexchange.com/questions/51290/how-to-add-empty-object-not-using-bpy-ops
|
||||
def make_empty(name, location, rotation, scale, collection):
|
||||
object_data = None
|
||||
empty_obj = bpy.data.objects.new( name, object_data )
|
||||
|
||||
empty_obj.empty_display_size = 2
|
||||
empty_obj.empty_display_type = 'PLAIN_AXES'
|
||||
|
||||
empty_obj.name = name
|
||||
empty_obj.location = location
|
||||
empty_obj.scale = scale
|
||||
empty_obj.rotation_euler = rotation
|
||||
|
||||
collection.objects.link( empty_obj )
|
||||
#bpy.context.view_layer.update()
|
||||
return empty_obj
|
||||
|
||||
#".gltf_auto_export_settings"
|
||||
def upsert_settings(name, data):
|
||||
stored_settings = bpy.data.texts[name] if name in bpy.data.texts else bpy.data.texts.new(name)
|
||||
stored_settings.clear()
|
||||
stored_settings.write(json.dumps(data))
|
||||
|
||||
def load_settings(name):
|
||||
stored_settings = bpy.data.texts[name] if name in bpy.data.texts else None
|
||||
if stored_settings != None:
|
||||
return json.loads(stored_settings.as_string())
|
||||
return None
|
|
@ -0,0 +1,221 @@
|
|||
import json
|
||||
from bpy_types import PropertyGroup
|
||||
|
||||
|
||||
conversion_tables = {
|
||||
"bool": lambda value: value,
|
||||
|
||||
"char": lambda value: '"'+value+'"',
|
||||
"str": lambda value: '"'+value+'"',
|
||||
"alloc::string::String": lambda value: '"'+value+'"',
|
||||
|
||||
"glam::Vec2": lambda value: "Vec2(x:"+str(value[0])+ ", y:"+str(value[1])+")",
|
||||
"glam::DVec2": lambda value: "DVec2(x:"+str(value[0])+ ", y:"+str(value[1])+")",
|
||||
"glam::UVec2": lambda value: "UVec2(x:"+str(value[0])+ ", y:"+str(value[1])+")",
|
||||
|
||||
"glam::Vec3": lambda value: "Vec3(x:"+str(value[0])+ ", y:"+str(value[1])+ ", z:"+str(value[2])+")",
|
||||
"glam::Vec3A": lambda value: "Vec3A(x:"+str(value[0])+ ", y:"+str(value[1])+ ", z:"+str(value[2])+")",
|
||||
"glam::UVec3": lambda value: "UVec3(x:"+str(value[0])+ ", y:"+str(value[1])+ ", z:"+str(value[2])+")",
|
||||
|
||||
"glam::Vec4": lambda value: "Vec4(x:"+str(value[0])+ ", y:"+str(value[1])+ ", z:"+str(value[2])+ ", w:"+str(value[3])+")",
|
||||
"glam::DVec4": lambda value: "DVec4(x:"+str(value[0])+ ", y:"+str(value[1])+ ", z:"+str(value[2])+ ", w:"+str(value[3])+")",
|
||||
"glam::UVec4": lambda value: "UVec4(x:"+str(value[0])+ ", y:"+str(value[1])+ ", z:"+str(value[2])+ ", w:"+str(value[3])+")",
|
||||
|
||||
"glam::Quat": lambda value: "Quat(x:"+str(value[0])+ ", y:"+str(value[1])+ ", z:"+str(value[2])+ ", w:"+str(value[3])+")",
|
||||
|
||||
"bevy_render::color::Color": lambda value: "Rgba(red:"+str(value[0])+ ", green:"+str(value[1])+ ", blue:"+str(value[2])+ ", alpha:"+str(value[3])+ ")",
|
||||
}
|
||||
|
||||
#converts the value of a property group(no matter its complexity) into a single custom property value
|
||||
# this is more or less a glorified "to_ron()" method (not quite but close to)
|
||||
def property_group_value_to_custom_property_value(property_group, definition, registry, parent=None, value=None):
|
||||
component_name = definition["short_name"]
|
||||
type_info = definition["typeInfo"] if "typeInfo" in definition else None
|
||||
type_def = definition["type"] if "type" in definition else None
|
||||
type_name = definition["title"]
|
||||
is_value_type = type_name in conversion_tables
|
||||
#print("computing custom property", component_name, type_info, type_def, type_name)
|
||||
|
||||
if is_value_type:
|
||||
value = conversion_tables[type_name](value)
|
||||
elif type_info == "Struct":
|
||||
values = {}
|
||||
if len(property_group.field_names) ==0:
|
||||
value = ''
|
||||
else:
|
||||
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 = property_group_value_to_custom_property_value(child_property_group, item_definition, registry, parent=component_name, value=value)
|
||||
else:
|
||||
value = '""'
|
||||
values[field_name] = value
|
||||
value = values
|
||||
elif type_info == "Tuple":
|
||||
values = {}
|
||||
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 = property_group_value_to_custom_property_value(child_property_group, item_definition, registry, parent=component_name, value=value)
|
||||
else:
|
||||
value = '""'
|
||||
values[field_name] = value
|
||||
value = tuple(e for e in list(values.values()))
|
||||
|
||||
elif type_info == "TupleStruct":
|
||||
values = {}
|
||||
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 = property_group_value_to_custom_property_value(child_property_group, item_definition, registry, parent=component_name, value=value)
|
||||
else:
|
||||
value = '""'
|
||||
values[field_name] = value
|
||||
|
||||
value = tuple(e for e in list(values.values()))
|
||||
elif type_info == "Enum":
|
||||
selected = getattr(property_group, component_name)
|
||||
|
||||
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 = property_group_value_to_custom_property_value(child_property_group, variant_definition, registry, parent=component_name, value=value)
|
||||
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 = property_group_value_to_custom_property_value(child_property_group, variant_definition, registry, parent=component_name, value=value)
|
||||
value = selected + str(value,)
|
||||
else:
|
||||
print("basic enum stuff")
|
||||
value = selected # here the value of the enum is just the name of the variant
|
||||
else:
|
||||
value = selected
|
||||
|
||||
elif type_info == "List":
|
||||
item_list = getattr(property_group, "list")
|
||||
#item_type = getattr(property_group, "type_name_short")
|
||||
value = []
|
||||
for item in item_list:
|
||||
item_type_name = getattr(item, "type_name")
|
||||
definition = registry.type_infos[item_type_name] if item_type_name in registry.type_infos else None
|
||||
if definition != None:
|
||||
item_value = property_group_value_to_custom_property_value(item, definition, registry, component_name, None)
|
||||
if item_type_name.startswith("wrapper_"): #if we have a "fake" tupple for aka for value types, we need to remove one nested level
|
||||
item_value = item_value[0]
|
||||
else:
|
||||
item_value = '""'
|
||||
value.append(item_value)
|
||||
else:
|
||||
value = conversion_tables[type_name](value) if is_value_type else value
|
||||
|
||||
#print("VALUE", value, type(value))
|
||||
#print("generating custom property value", value, type(value))
|
||||
if parent == None:
|
||||
value = str(value).replace("'", "")
|
||||
value = value.replace(",)",")")
|
||||
value = value.replace("{", "(").replace("}", ")")
|
||||
value = value.replace("True", "true").replace("False", "false")
|
||||
return value
|
||||
|
||||
import re
|
||||
#converts the value of a single custom property into a value (values) of a property group
|
||||
def property_group_value_from_custom_property_value(property_group, definition, registry, custom_property_value):
|
||||
#print(" ")
|
||||
#print("setting property group value", property_group, definition, custom_property_value)
|
||||
type_infos = registry.type_infos
|
||||
value_types_defaults = registry.value_types_defaults
|
||||
print("custom_property_value", custom_property_value)
|
||||
|
||||
def parse_field(item, property_group, definition, field_name):
|
||||
type_info = definition["typeInfo"] if "typeInfo" in definition else None
|
||||
type_def = definition["type"] if "type" in definition else None
|
||||
properties = definition["properties"] if "properties" in definition else {}
|
||||
prefixItems = definition["prefixItems"] if "prefixItems" in definition else []
|
||||
has_properties = len(properties.keys()) > 0
|
||||
has_prefixItems = len(prefixItems) > 0
|
||||
is_enum = type_info == "Enum"
|
||||
is_list = type_info == "List"
|
||||
is_value_type = type_def in value_types_defaults
|
||||
|
||||
print("parsing field", item, "type infos", type_info, "type_def", type_def)
|
||||
if type_info == "Struct":
|
||||
print("is object")
|
||||
for field_name in property_group.field_names:
|
||||
print("field name", field_name)
|
||||
# sub field
|
||||
if isinstance(getattr(property_group, field_name), PropertyGroup):
|
||||
sub_prop_group = getattr(property_group, field_name)
|
||||
ref_name = properties[field_name]["type"]["$ref"].replace("#/$defs/", "")
|
||||
sub_definition = type_infos[ref_name]
|
||||
parse_field(item[field_name], sub_prop_group, sub_definition, field_name)
|
||||
else:
|
||||
setattr(property_group, field_name, item[field_name])
|
||||
|
||||
if has_prefixItems:
|
||||
if len(property_group.field_names) == 1:
|
||||
setattr(property_group, "0", item) # FIXME: not ideal
|
||||
else:
|
||||
for field_name in property_group.field_names:
|
||||
setattr(property_group, field_name, item)
|
||||
if is_enum:
|
||||
if type_def == "object":
|
||||
regexp = re.search('(^[^\(]+)(\((.*)\))', item)
|
||||
chosen_variant = regexp.group(1)
|
||||
chosen_variant_value = regexp.group(3).replace("'", '"').replace("(", "[").replace(")","]")
|
||||
chosen_variant_value = json.loads(chosen_variant_value)
|
||||
|
||||
# first set chosen selection
|
||||
field_name = property_group.field_names[0]
|
||||
setattr(property_group, field_name, chosen_variant)
|
||||
|
||||
# thenlook for the information about the matching variant
|
||||
sub_definition= None
|
||||
for variant in definition["oneOf"]:
|
||||
if variant["title"] == chosen_variant:
|
||||
ref_name = variant["prefixItems"][0]["type"]["$ref"].replace("#/$defs/", "")
|
||||
sub_definition = type_infos[ref_name]
|
||||
break
|
||||
variant_name = "variant_"+chosen_variant
|
||||
if isinstance(getattr(property_group, variant_name), PropertyGroup):
|
||||
sub_prop_group = getattr(property_group, variant_name)
|
||||
parse_field(chosen_variant_value, sub_prop_group, sub_definition, variant_name)
|
||||
else:
|
||||
setattr(property_group, variant_name, chosen_variant_value)
|
||||
else:
|
||||
field_name = property_group.field_names[0]
|
||||
setattr(property_group, field_name, item)
|
||||
|
||||
if is_list:
|
||||
print("is list")
|
||||
|
||||
if is_value_type:
|
||||
print("is value type")
|
||||
|
||||
try:
|
||||
parse_field(custom_property_value, property_group, definition, None)
|
||||
except Exception as error:
|
||||
print("failed to parse raw custom property data", error)
|
|
@ -0,0 +1,95 @@
|
|||
import bpy
|
||||
from bpy_types import PropertyGroup
|
||||
from bpy.props import (PointerProperty)
|
||||
from . import process_structs
|
||||
from . import process_tupples
|
||||
from . import process_enum
|
||||
from . import process_list
|
||||
|
||||
def process_component(registry, definition, update, extras=None, nesting = []):
|
||||
component_name = definition['title']
|
||||
short_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"
|
||||
|
||||
# print("processing", short_name, component_name, type_def, type_info)
|
||||
|
||||
__annotations__ = {}
|
||||
tupple_or_struct = None
|
||||
|
||||
with_properties = False
|
||||
with_items = False
|
||||
with_enum = False
|
||||
with_list = False
|
||||
|
||||
|
||||
if has_properties:
|
||||
__annotations__ = __annotations__ | process_structs.process_structs(registry, definition, properties, update, nesting)
|
||||
with_properties = True
|
||||
tupple_or_struct = "struct"
|
||||
|
||||
if has_prefixItems:
|
||||
__annotations__ = __annotations__ | process_tupples.process_tupples(registry, definition, prefixItems, update, nesting)
|
||||
with_items = True
|
||||
tupple_or_struct = "tupple"
|
||||
|
||||
if is_enum:
|
||||
__annotations__ = __annotations__ | process_enum.process_enum(registry, definition, update, nesting)
|
||||
with_enum = True
|
||||
|
||||
if is_list:
|
||||
__annotations__ = __annotations__ | process_list.process_list(registry, definition, update, nesting)
|
||||
with_list= True
|
||||
|
||||
field_names = []
|
||||
for a in __annotations__:
|
||||
field_names.append(a)
|
||||
|
||||
|
||||
extras = extras if extras is not None else {
|
||||
"type_name": component_name
|
||||
}
|
||||
root_component = nesting[0] if len(nesting) > 0 else component_name
|
||||
# print("DONE:",short_name,"__annotations__", __annotations__)
|
||||
# print("")
|
||||
# property_group_name = short_name+"_ui"
|
||||
property_group_params = {
|
||||
**extras,
|
||||
'__annotations__': __annotations__,
|
||||
'tupple_or_struct': tupple_or_struct,
|
||||
'field_names': field_names,
|
||||
**dict(with_properties = with_properties, with_items= with_items, with_enum= with_enum, with_list= with_list, short_name= short_name),
|
||||
'root_component': root_component
|
||||
}
|
||||
#FIXME: YIKES, but have not found another way:
|
||||
""" Withouth this ; the following does not work
|
||||
-BasicTest
|
||||
- NestingTestLevel2
|
||||
-BasicTest => the registration & update callback of this one overwrites the first "basicTest"
|
||||
have not found a cleaner workaround so far
|
||||
"""
|
||||
property_group_name = str(hash(str(nesting))) + short_name+"_ui" if len(nesting) > 0 else short_name+"_ui"
|
||||
|
||||
(property_group_pointer, property_group_class) = property_group_from_infos(property_group_name, property_group_params)
|
||||
# add our component propertyGroup to the registry
|
||||
registry.register_component_propertyGroup(property_group_name, property_group_pointer)
|
||||
# for practicality, we add an entry for a reverse lookup (short => long name, since we already have long_name => short_name with the keys of the raw registry)
|
||||
registry.add_shortName_to_longName(short_name, component_name)
|
||||
|
||||
return (property_group_pointer, property_group_class)
|
||||
|
||||
def property_group_from_infos(property_group_name, property_group_parameters):
|
||||
# print("creating property group", property_group_name)
|
||||
property_group_class = type(property_group_name, (PropertyGroup,), property_group_parameters)
|
||||
|
||||
bpy.utils.register_class(property_group_class)
|
||||
property_group_pointer = PointerProperty(type=property_group_class)
|
||||
|
||||
return (property_group_pointer, property_group_class)
|