Overhaul silly material creator plugin demo (#1261)
9
plugins/.gitignore
vendored
@@ -1,2 +1,7 @@
|
|||||||
# "Silly Material" files written by the editor plugin
|
# "Silly Material" files written by the editor plugin.
|
||||||
*.silly_mat
|
# The example files already tracked by Git are not affected.
|
||||||
|
*.silly_mat_importable
|
||||||
|
*.silly_mat_importable.import
|
||||||
|
*.silly_mat_loadable
|
||||||
|
*.silly_mat_loadable.uid
|
||||||
|
*.tres
|
||||||
|
|||||||
@@ -21,15 +21,17 @@ This project contains 4 plugins:
|
|||||||
* The custom node plugin shows how to create a custom node type
|
* The custom node plugin shows how to create a custom node type
|
||||||
using `add_custom_type`. [More info](addons/custom_node).
|
using `add_custom_type`. [More info](addons/custom_node).
|
||||||
|
|
||||||
* The material import plugin shows how to make a plugin handle importing
|
|
||||||
a custom file type (mtxt). [More info](addons/material_import_plugin).
|
|
||||||
|
|
||||||
* The material creator plugin shows how to add a custom dock with some
|
|
||||||
simple functionality. [More info](addons/material_creator).
|
|
||||||
|
|
||||||
* The main screen plugin is a minimal example of how to create a plugin
|
* The main screen plugin is a minimal example of how to create a plugin
|
||||||
with a main screen. [More info](addons/main_screen).
|
with a main screen. [More info](addons/main_screen).
|
||||||
|
|
||||||
|
* The material creator plugin shows how to add a custom dock with some
|
||||||
|
simple functionality, and shows how to create a custom Resource type
|
||||||
|
with custom loading, saving, importing, and exporting logic,
|
||||||
|
including editor integrations. [More info](addons/material_creator).
|
||||||
|
|
||||||
|
* The simple import plugin shows how to make a simple plugin handle importing
|
||||||
|
a custom file type (mtxt). [More info](addons/simple_import_plugin).
|
||||||
|
|
||||||
To use these plugins in another project, copy any of these
|
To use these plugins in another project, copy any of these
|
||||||
folders to the `addons/` folder in a Godot project, and then
|
folders to the `addons/` folder in a Godot project, and then
|
||||||
enable them in the project settings menu.
|
enable them in the project settings menu.
|
||||||
@@ -44,12 +46,10 @@ This can be done via the terminal: `zip -r custom_node.zip custom_node/*`
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 809 B |
@@ -1,40 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://c3c8tfihdvaw7"
|
|
||||||
path="res://.godot/imported/heart_icon.png-8f04adf78b3bd1a5c39f790588a1fa78.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://addons/custom_node/heart_icon.png"
|
|
||||||
dest_files=["res://.godot/imported/heart_icon.png-8f04adf78b3bd1a5c39f790588a1fa78.ctex"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=0
|
|
||||||
compress/high_quality=false
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
|
||||||
compress/normal_map=0
|
|
||||||
compress/channel_pack=0
|
|
||||||
mipmaps/generate=false
|
|
||||||
mipmaps/limit=-1
|
|
||||||
roughness/mode=0
|
|
||||||
roughness/src_normal=""
|
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
|
||||||
process/premult_alpha=false
|
|
||||||
process/normal_map_invert_y=false
|
|
||||||
process/hdr_as_srgb=false
|
|
||||||
process/hdr_clamp_exposure=false
|
|
||||||
process/size_limit=0
|
|
||||||
detect_3d/compress_to=1
|
|
||||||
@@ -4,7 +4,8 @@ extends EditorPlugin
|
|||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
# When this plugin node enters tree, add the custom type.
|
# When this plugin node enters tree, add the custom type.
|
||||||
add_custom_type("Heart", "Node2D", preload("res://addons/custom_node/heart.gd"), preload("res://addons/custom_node/heart_icon.png"))
|
var icon: Texture2D = preload("res://addons/custom_node/heart.png")
|
||||||
|
add_custom_type("Heart", "Node2D", preload("res://addons/custom_node/heart.gd"), icon)
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
func _exit_tree() -> void:
|
||||||
|
|||||||
@@ -1,25 +1,142 @@
|
|||||||
# Material Creator Plugin Demo
|
# Material Creator Plugin Demo
|
||||||
|
|
||||||
This plugin demo contains a custom material creation dock
|
This plugin demo demonstrates these things:
|
||||||
inside the Godot editor.
|
|
||||||
|
|
||||||
|
- How to create a custom dock in the Godot editor with basic functionality.
|
||||||
Custom docks are made of Control nodes, they run in the
|
Custom docks are made of Control nodes, they run in the
|
||||||
editor, and any behavior must be done through `tool` scripts.
|
editor, and any behavior must be done through `@tool` scripts.
|
||||||
For more information, see this documentation article:
|
For more information, see this documentation article:
|
||||||
https://docs.godotengine.org/en/latest/tutorials/plugins/editor/making_plugins.html#a-custom-dock
|
https://docs.godotengine.org/en/latest/tutorials/plugins/editor/making_plugins.html#a-custom-dock
|
||||||
|
|
||||||
## Features
|
- How to create a custom Resource type, and provide logic for
|
||||||
- Adjust albedo color, metallic, and rouphness values interactively.
|
serializing, deserializing, and converting this Resource type.
|
||||||
- Apply the generated material to selected 3D nodes in the editor.
|
|
||||||
- Save and load materials in two ways:
|
|
||||||
- `.silly_mat`: Custom Godot Resource type, handled by custom saver/loader
|
|
||||||
included in the plygin.
|
|
||||||
- `.mtxt`: Plain-text format. Useful for external editing or as an
|
|
||||||
interchange format.
|
|
||||||
- `.tres`: Standard Godot resource format (works without the custom
|
|
||||||
loader).
|
|
||||||
|
|
||||||
## Implementation notes
|
- Editor integration with classes for loading, saving, and importing
|
||||||
- `.silly_mat` format is registered through `SillyMatFormatSaver` and
|
this Resource type, including optional import customization.
|
||||||
`SillyMatFormatLoader` in the plugin.
|
|
||||||
- Custm docks are built from `Control` nodes and run as `@tool` scripts.
|
For a more comprehensive example, see the GLTF module in Godot's source code.
|
||||||
|
|
||||||
|
For a less comprehensive example, see the "simple_import_plugin" folder.
|
||||||
|
|
||||||
|
## Importing vs Loading
|
||||||
|
|
||||||
|
The custom Resource type in this demo is supplemented by two different
|
||||||
|
sets of editor classes:
|
||||||
|
|
||||||
|
- [EditorImportPlugin](https://docs.godotengine.org/en/stable/classes/class_editorimportplugin.html)
|
||||||
|
in the `importers/` folder, which allow customizing how
|
||||||
|
files are imported into Godot as Resources of different
|
||||||
|
types and optionally with import settings.
|
||||||
|
Imported files have `.import` files generated next to them.
|
||||||
|
|
||||||
|
- [ResourceFormatLoader](https://docs.godotengine.org/en/stable/classes/class_resourceformatloader.html)
|
||||||
|
and [ResourceFormatSaver](https://docs.godotengine.org/en/stable/classes/class_resourceformatsaver.html)
|
||||||
|
in the `load_and_save/` folder, which allow easily
|
||||||
|
editing files in the inspector and saving them back.
|
||||||
|
Resource files have `.uid` files generated next to them.
|
||||||
|
|
||||||
|
These two approaches are mutually exclusive.
|
||||||
|
You may only use one approach at a time for a given file extension.
|
||||||
|
This demo showcases both by using 2 different file extensions.
|
||||||
|
|
||||||
|
In actual projects, you should either choose [EditorImportPlugin](https://docs.godotengine.org/en/stable/classes/class_editorimportplugin.html)(s)
|
||||||
|
for a configurable import, OR [ResourceFormatLoader](https://docs.godotengine.org/en/stable/classes/class_resourceformatloader.html)
|
||||||
|
for a writeable resource load.
|
||||||
|
The choice depends on if you treat the file as an external source asset
|
||||||
|
which should be imported and may be customized at import
|
||||||
|
([EditorImportPlugin](https://docs.godotengine.org/en/stable/classes/class_editorimportplugin.html)),
|
||||||
|
or if you treat the file as an internal Godot resource meant to be natively
|
||||||
|
and directly edited within Godot
|
||||||
|
([ResourceFormatLoader](https://docs.godotengine.org/en/stable/classes/class_resourceformatloader.html)).
|
||||||
|
|
||||||
|
For example, a glTF file may be generated by Blender, and therefore
|
||||||
|
is not intended to be edited directly in Godot, so it should use
|
||||||
|
[EditorImportPlugin](https://docs.godotengine.org/en/stable/classes/class_editorimportplugin.html).
|
||||||
|
Similarly, a PNG file is typically generated by an image editor, and Godot
|
||||||
|
needs to convert it to a different internal format, like a `.ctex` file
|
||||||
|
for a VRAM-compressed texture, so it should use
|
||||||
|
[EditorImportPlugin](https://docs.godotengine.org/en/stable/classes/class_editorimportplugin.html).
|
||||||
|
However, files like `.tres` and `.tscn` are Godot-native formats meant to be
|
||||||
|
edited directly in Godot, so they should use
|
||||||
|
[ResourceFormatLoader](https://docs.godotengine.org/en/stable/classes/class_resourceformatloader.html).
|
||||||
|
|
||||||
|
Once you choose one approach, create scripts deriving the appropriate classes,
|
||||||
|
override their callback functions, and register them in your plugin's
|
||||||
|
[`*_plugin.gd`](material_plugin.gd) script.
|
||||||
|
|
||||||
|
## Example Files
|
||||||
|
|
||||||
|
The [`examples/`](examples/) folder contains several example files:
|
||||||
|
|
||||||
|
- `blue.tres`: Directly saving a SillyMaterialResource using Godot's built-in `.tres` format,
|
||||||
|
without any custom loader/saver logic or import/export logic, available for all Resource types.
|
||||||
|
This can be edited in Godot's inspector and saved back.
|
||||||
|
|
||||||
|
- `cyan.silly_mat_loadable`: Storing a SillyMaterialResource as a custom format,
|
||||||
|
such as via [ResourceFormatSaver](https://docs.godotengine.org/en/stable/classes/class_resourceformatsaver.html), which is loaded back using a custom [ResourceFormatLoader](https://docs.godotengine.org/en/stable/classes/class_resourceformatloader.html).
|
||||||
|
This can be edited in Godot's inspector and saved back.
|
||||||
|
|
||||||
|
- `green_as_standard_mat.silly_mat_importable`: Storing a SillyMaterialResource as a custom format,
|
||||||
|
which is imported as a StandardMaterial3D using custom import/export logic. This shows how
|
||||||
|
importers can import files as any Resource type, converting custom files to data Godot can use.
|
||||||
|
Imported files are read-only and cannot be edited in the inspector.
|
||||||
|
|
||||||
|
- `yellow.silly_mat_importable`: Storing a SillyMaterialResource as a custom format,
|
||||||
|
which is imported as a SillyMaterialResource using custom import/export logic.
|
||||||
|
Imported files are read-only and cannot be edited in the inspector.
|
||||||
|
|
||||||
|
- `yellow_tinted_red.silly_mat_importable`: Storing a SillyMaterialResource as a custom format,
|
||||||
|
which is imported as a SillyMaterialResource using custom import/export logic.
|
||||||
|
|
||||||
|
- This file has the same exact contents as `yellow.silly_mat_importable`, but the
|
||||||
|
corresponding `.import` file has a flag set to tint the albedo color towards red,
|
||||||
|
which makes the material appear orange instead of yellow.
|
||||||
|
This demonstrates how importers can use import settings to modify data during import.
|
||||||
|
After import, imported files are read-only and cannot be edited in the inspector.
|
||||||
|
|
||||||
|
- If you try to load this file using "Load Imported Material (EditorImportPlugin)" in the editor,
|
||||||
|
or call `ResourceLoader.load()` in GDScript, it will load the imported version, which
|
||||||
|
includes the red tint, so the albedo color will be orange. If you try to import this
|
||||||
|
file using "Import Material (directly at runtime)" in the editor, or call
|
||||||
|
`SillyMaterialResource.read_from_file()` in GDScript, it will directly read the original
|
||||||
|
file's contents, ignoring the import process, so the albedo color will be yellow.
|
||||||
|
This demonstrates how files can be loaded within Godot's import process (editor only),
|
||||||
|
or bypass the import process entirely (works in editor and at runtime).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Editor Buttons
|
||||||
|
|
||||||
|
The material creator dock has these 6 buttons:
|
||||||
|
|
||||||
|
- "Apply Material": Applies the current material to all selected MeshInstance3D nodes in the editor.
|
||||||
|
|
||||||
|
- "Save Material (ResourceFormatSaver)": Saves the current
|
||||||
|
material to a `.silly_mat_loadable` file using the custom
|
||||||
|
[ResourceFormatSaver](https://docs.godotengine.org/en/stable/classes/class_resourceformatsaver.html),
|
||||||
|
or to a `.tres` file using Godot's built-in ResourceFormatSaverText.
|
||||||
|
This can be edited in Godot's inspector and saved back.
|
||||||
|
|
||||||
|
- "Export Material (directly at runtime)": Exports the current material
|
||||||
|
to a `.silly_mat_*` file using the functions on SillyMaterialResource.
|
||||||
|
This works for files outside of the `res://` folder, and can be done at runtime.
|
||||||
|
|
||||||
|
- "Load Material (ResourceFormatLoader)": Loads a
|
||||||
|
`.silly_mat_loadable` file using the custom
|
||||||
|
[ResourceFormatLoader](https://docs.godotengine.org/en/stable/classes/class_resourceformatloader.html),
|
||||||
|
or loads a `.tres` file using Godot's built-in ResourceFormatLoaderText.
|
||||||
|
This can be edited in Godot's inspector and saved back.
|
||||||
|
|
||||||
|
- "Load Imported Material (EditorImportPlugin)": Loads a
|
||||||
|
`.silly_mat_importable` that was imported by an
|
||||||
|
[EditorImportPlugin](https://docs.godotengine.org/en/stable/classes/class_editorimportplugin.html).
|
||||||
|
The loaded data actually comes from the corresponding
|
||||||
|
imported file saved as `res://.godot/imported/something.silly_mat_importable-hash.res`.
|
||||||
|
Imported files are read-only and cannot be edited in the inspector.
|
||||||
|
|
||||||
|
- "Import Material (directly at runtime)": Imports a `.silly_mat_*` directly from the
|
||||||
|
source file, performing an import on request instead of loading data the editor imported earlier.
|
||||||
|
This ignores any editor import settings, works for files outside of the `res://` folder,
|
||||||
|
and can be done at runtime.
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
## Imports a `.silly_mat_importable` as a SillyMaterialResource.
|
||||||
|
## This class needs to be registered in the EditorPlugin to be used.
|
||||||
|
##
|
||||||
|
## Unlike loaders, importers can be configured, and multiple can exist in
|
||||||
|
## the same project. Selecting and configuring is done in the "Import" dock.
|
||||||
|
## However, imported files cannot be modified as easily as loaded files.
|
||||||
|
## See the "load_and_save" folder for an example of how to use loaders and savers.
|
||||||
|
##
|
||||||
|
## In actual projects, you should either choose EditorImportPlugin(s) for a
|
||||||
|
## configurable import, OR ResourceFormatLoader for a writeable resource load.
|
||||||
|
## Only one handling can exist at a given time for a given file extension.
|
||||||
|
## This demo exposes both by using 2 different file extensions.
|
||||||
|
@tool
|
||||||
|
class_name ImportSillyMatAsSillyMaterialResource
|
||||||
|
extends EditorImportPlugin
|
||||||
|
|
||||||
|
|
||||||
|
func _get_importer_name() -> String:
|
||||||
|
return "demos.silly_material_importable.silly_material_resource"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_visible_name() -> String:
|
||||||
|
return "Silly Material Resource"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_recognized_extensions() -> PackedStringArray:
|
||||||
|
return ["silly_mat_importable"]
|
||||||
|
|
||||||
|
|
||||||
|
func _get_save_extension() -> String:
|
||||||
|
return "res"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_resource_type() -> String:
|
||||||
|
# Note: This MUST be a native Godot type, it can't be a GDScript type.
|
||||||
|
# Therefore it has to be "Resource" instead of "SillyMaterialResource".
|
||||||
|
return "Resource"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_preset_count() -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
func _get_preset_name(preset: int) -> String:
|
||||||
|
return "Default"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_import_options(_path: String, preset: int) -> Array[Dictionary]:
|
||||||
|
var ret: Array[Dictionary] = [
|
||||||
|
{
|
||||||
|
"name": "make_more_red",
|
||||||
|
"default_value": false,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
func _get_import_order() -> int:
|
||||||
|
return ResourceImporter.IMPORT_ORDER_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
|
func _get_option_visibility(path: String, option: StringName, options: Dictionary) -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _import(source_file: String, save_path: String, options: Dictionary, r_platform_variants: Array[String], r_gen_files: Array[String]) -> Error:
|
||||||
|
var silly_mat_res := SillyMaterialResource.read_from_file(source_file)
|
||||||
|
if options.has("make_more_red") and options["make_more_red"]:
|
||||||
|
silly_mat_res.albedo_color = silly_mat_res.albedo_color.lerp(Color.RED, 0.5)
|
||||||
|
# This will save to a file path like `res://.godot/imported/something.res`.
|
||||||
|
var imported_path: String = "%s.%s" % [save_path, _get_save_extension()]
|
||||||
|
return ResourceSaver.save(silly_mat_res, imported_path)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://u2esoq3eygve
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
## Imports a `.silly_mat_importable` as a StandardMaterial3D.
|
||||||
|
## This class needs to be registered in the EditorPlugin to be used.
|
||||||
|
##
|
||||||
|
## Unlike loaders, importers can be configured, and multiple can exist in
|
||||||
|
## the same project. Selecting and configuring is done in the "Import" dock.
|
||||||
|
## However, imported files cannot be modified as easily as loaded files.
|
||||||
|
## See the "load_and_save" folder for an example of how to use loaders and savers.
|
||||||
|
##
|
||||||
|
## In actual projects, you should either choose EditorImportPlugin(s) for a
|
||||||
|
## configurable import, OR ResourceFormatLoader for a writeable resource load.
|
||||||
|
## Only one handling can exist at a given time for a given file extension.
|
||||||
|
## This demo exposes both by using 2 different file extensions.
|
||||||
|
@tool
|
||||||
|
class_name ImportSillyMatAsStandardMaterial3D
|
||||||
|
extends EditorImportPlugin
|
||||||
|
|
||||||
|
|
||||||
|
func _get_importer_name() -> String:
|
||||||
|
return "demos.silly_material_importable.standard_material_3d"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_visible_name() -> String:
|
||||||
|
return "Standard Material 3D"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_recognized_extensions() -> PackedStringArray:
|
||||||
|
return ["silly_mat_importable"]
|
||||||
|
|
||||||
|
|
||||||
|
func _get_save_extension() -> String:
|
||||||
|
return "res"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_resource_type() -> String:
|
||||||
|
return "StandardMaterial3D"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_preset_count() -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
func _get_preset_name(preset: int) -> String:
|
||||||
|
return "Default"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_import_options(_path: String, preset: int) -> Array[Dictionary]:
|
||||||
|
var ret: Array[Dictionary] = [
|
||||||
|
{
|
||||||
|
"name": "make_more_red",
|
||||||
|
"default_value": false,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
func _get_import_order() -> int:
|
||||||
|
return ResourceImporter.IMPORT_ORDER_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
|
func _get_option_visibility(path: String, option: StringName, options: Dictionary) -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _import(source_file: String, save_path: String, options: Dictionary, r_platform_variants: Array[String], r_gen_files: Array[String]) -> Error:
|
||||||
|
var silly_mat_res := SillyMaterialResource.read_from_file(source_file)
|
||||||
|
if options.has("make_more_red") and options["make_more_red"]:
|
||||||
|
silly_mat_res.albedo_color = silly_mat_res.albedo_color.lerp(Color.RED, 0.5)
|
||||||
|
var standard_mat: StandardMaterial3D = silly_mat_res.to_material()
|
||||||
|
# This will save to a file path like `res://.godot/imported/something.res`.
|
||||||
|
var imported_path: String = "%s.%s" % [save_path, _get_save_extension()]
|
||||||
|
return ResourceSaver.save(standard_mat, imported_path)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cmgoxx63wybil
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
## Custom loader for the `.silly_mat_loadable` file format.
|
||||||
|
## Works together with `SillyMatFormatSaver` to support saving and loading.
|
||||||
|
## This class needs to be registered in the EditorPlugin to be used.
|
||||||
|
##
|
||||||
|
## Loaders can easily have the loaded data be modified and saved back into
|
||||||
|
## the file. However, only one loader can exist, and the loading cannot be
|
||||||
|
## configured, unlike importers which are configurable in the "Import" dock.
|
||||||
|
## See the "importers" folder for two examples of how to use importers.
|
||||||
|
##
|
||||||
|
## In actual projects, you should either choose ResourceFormatLoader for a
|
||||||
|
## writeable resource load, OR EditorImportPlugin(s) for a configurable import.
|
||||||
|
## Only one handling can exist at a given time for a given file extension.
|
||||||
|
## This demo exposes both by using 2 different file extensions.
|
||||||
|
@tool
|
||||||
|
class_name SillyMatFormatLoader
|
||||||
|
extends ResourceFormatLoader
|
||||||
|
|
||||||
|
|
||||||
|
## Callback to return an array of the file extensions this loader can load.
|
||||||
|
func _get_recognized_extensions() -> PackedStringArray:
|
||||||
|
return PackedStringArray(["silly_mat_loadable"])
|
||||||
|
|
||||||
|
|
||||||
|
## Callback to return the resource type name based on file extension.
|
||||||
|
func _get_resource_type(path: String) -> String:
|
||||||
|
if path.get_extension() == "silly_mat_loadable":
|
||||||
|
return "SillyMaterialResource"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
## Callback to return what resource type this loader handles.
|
||||||
|
func _handles_type(type_name: StringName) -> bool:
|
||||||
|
return type_name == &"SillyMaterialResource"
|
||||||
|
|
||||||
|
|
||||||
|
## Main callback to actually perform the loading.
|
||||||
|
func _load(path: String, original_path: String, use_sub_threads: bool, cache_mode: int) -> Variant:
|
||||||
|
return SillyMaterialResource.read_from_file(original_path)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b004gkmug1qlt
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
## Custom saver for the `.silly_mat_loadable` file format.
|
||||||
|
## Works together with `SillyMatFormatLoader` to support loading and saving.
|
||||||
|
## This class needs to be registered in the EditorPlugin to be used.
|
||||||
|
##
|
||||||
|
## Loaders can easily have the loaded data be modified and saved back into
|
||||||
|
## the file. However, only one loader can exist, and the loading cannot be
|
||||||
|
## configured, unlike importers which are configurable in the "Import" dock.
|
||||||
|
## See the "importers" folder for two examples of how to use importers.
|
||||||
|
##
|
||||||
|
## In actual projects, you should either choose ResourceFormatLoader for a
|
||||||
|
## writeable resource load, OR EditorImportPlugin(s) for a configurable import.
|
||||||
|
## Only one handling can exist at a given time for a given file extension.
|
||||||
|
## This demo exposes both by using 2 different file extensions.
|
||||||
|
@tool
|
||||||
|
class_name SillyMatFormatSaver
|
||||||
|
extends ResourceFormatSaver
|
||||||
|
|
||||||
|
|
||||||
|
## Callback to return an array of the file extensions this saver can write.
|
||||||
|
func _get_recognized_extensions(resource: Resource) -> PackedStringArray:
|
||||||
|
return PackedStringArray(["silly_mat_loadable"])
|
||||||
|
|
||||||
|
|
||||||
|
## Callback to determine if a given Resource is supported by this saver.
|
||||||
|
func _recognize(resource: Resource) -> bool:
|
||||||
|
return resource is SillyMaterialResource
|
||||||
|
|
||||||
|
|
||||||
|
## Main callback to actually perform the saving.
|
||||||
|
func _save(resource: Resource, path: String, flags: int) -> Error:
|
||||||
|
var mat_res: SillyMaterialResource = resource as SillyMaterialResource
|
||||||
|
if mat_res == null:
|
||||||
|
return ERR_INVALID_DATA
|
||||||
|
return mat_res.write_to_file(path)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cvaleef5ekitp
|
||||||
145
plugins/addons/material_creator/editor/material_creator.gd
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# The word "silly" is used to make it obvious that the name is arbitrary.
|
||||||
|
@tool
|
||||||
|
extends Panel
|
||||||
|
|
||||||
|
|
||||||
|
var editor_interface: EditorInterface
|
||||||
|
|
||||||
|
@onready var albedo_color_picker: ColorPickerButton = $VBoxContainer/AlbedoColorPicker
|
||||||
|
@onready var metallic_slider: HSlider = $VBoxContainer/MetallicSlider
|
||||||
|
@onready var roughness_slider: HSlider = $VBoxContainer/RoughnessSlider
|
||||||
|
|
||||||
|
@onready var save_material_dialog: FileDialog = $SaveMaterialDialog
|
||||||
|
@onready var export_material_dialog: FileDialog = $ExportMaterialDialog
|
||||||
|
@onready var load_material_importer_dialog: FileDialog = $LoadMaterialImporterDialog
|
||||||
|
@onready var load_material_loader_dialog: FileDialog = $LoadMaterialLoaderDialog
|
||||||
|
@onready var import_material_directly_dialog: FileDialog = $ImportMaterialDirectlyDialog
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if not name.contains(" "):
|
||||||
|
printerr("Warning: Material Creator dock doesn't have a space in its node name, so it will be displayed without any spacing.")
|
||||||
|
save_material_dialog.current_path = "res://addons/material_creator/example/"
|
||||||
|
save_material_dialog.current_file = "new_material.silly_mat_loadable"
|
||||||
|
export_material_dialog.current_path = "res://addons/material_creator/example/"
|
||||||
|
export_material_dialog.current_file = "new_material.silly_mat_importable"
|
||||||
|
load_material_importer_dialog.current_path = "res://addons/material_creator/example/"
|
||||||
|
load_material_loader_dialog.current_path = "res://addons/material_creator/example/"
|
||||||
|
import_material_directly_dialog.current_path = ProjectSettings.globalize_path("res://addons/material_creator/example/")
|
||||||
|
RenderingServer.canvas_item_set_clip(get_canvas_item(), true)
|
||||||
|
|
||||||
|
|
||||||
|
func _save_or_export_file(path: String) -> void:
|
||||||
|
if path.is_empty():
|
||||||
|
printerr("Material Creator: No path chosen for saving.")
|
||||||
|
return
|
||||||
|
# Ensure directory exists before trying to save to it.
|
||||||
|
var dir: String = path.get_base_dir()
|
||||||
|
if not DirAccess.dir_exists_absolute(dir):
|
||||||
|
var err: Error = DirAccess.make_dir_recursive_absolute(dir)
|
||||||
|
if err != OK:
|
||||||
|
printerr("Material Creator: Can't create folder: %s (%s)" % [dir, error_string(err)])
|
||||||
|
return
|
||||||
|
var silly_mat: SillyMaterialResource = _create_silly_material_from_editor_values()
|
||||||
|
var ext: String = path.get_extension().to_lower()
|
||||||
|
var err: Error
|
||||||
|
var is_in_project: bool = path.begins_with("res://") or path.begins_with("user://")
|
||||||
|
if ext == "tres":
|
||||||
|
err = ResourceSaver.save(silly_mat, path)
|
||||||
|
if not is_in_project:
|
||||||
|
printerr("Material Creator: Warning: When saving outside of the Godot project, "
|
||||||
|
+ "prefer exporting instead. A Godot resource may not be functional "
|
||||||
|
+ "without the context of its original project (ex: script paths).")
|
||||||
|
elif ext == "silly_mat_loadable" and is_in_project:
|
||||||
|
err = ResourceSaver.save(silly_mat, path)
|
||||||
|
else:
|
||||||
|
err = silly_mat.write_to_file(path)
|
||||||
|
if err != OK:
|
||||||
|
printerr("Material Creator: Failed to save to %s, reason: %s" % [path, error_string(err)])
|
||||||
|
else:
|
||||||
|
print("Material Creator: Successfully saved to ", path)
|
||||||
|
# Inform the editor that files have changed on disk.
|
||||||
|
var efs: EditorFileSystem = editor_interface.get_resource_filesystem()
|
||||||
|
efs.scan()
|
||||||
|
|
||||||
|
|
||||||
|
func load_file_resource_loader(path: String) -> void:
|
||||||
|
var loaded_file: Resource = ResourceLoader.load(path)
|
||||||
|
if loaded_file == null:
|
||||||
|
printerr("Material Creator: Failed to load file at %s" % path)
|
||||||
|
return
|
||||||
|
if loaded_file is SillyMaterialResource:
|
||||||
|
edit_silly_material(loaded_file)
|
||||||
|
return
|
||||||
|
if loaded_file is StandardMaterial3D:
|
||||||
|
edit_silly_material(SillyMaterialResource.from_material(loaded_file))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
func load_file_directly(path: String) -> void:
|
||||||
|
var silly_mat := SillyMaterialResource.read_from_file(path)
|
||||||
|
if silly_mat == null:
|
||||||
|
printerr("Material Creator: Failed to directly load file at %s" % path)
|
||||||
|
edit_silly_material(silly_mat)
|
||||||
|
|
||||||
|
|
||||||
|
func edit_silly_material(silly_mat: SillyMaterialResource) -> void:
|
||||||
|
albedo_color_picker.color = silly_mat.albedo_color
|
||||||
|
metallic_slider.value = silly_mat.metallic_strength
|
||||||
|
roughness_slider.value = silly_mat.roughness_strength
|
||||||
|
|
||||||
|
|
||||||
|
func _create_silly_material_from_editor_values() -> SillyMaterialResource:
|
||||||
|
var color: Color = albedo_color_picker.color
|
||||||
|
var metallic: float = metallic_slider.value
|
||||||
|
var roughness: float = roughness_slider.value
|
||||||
|
var silly_res := SillyMaterialResource.new()
|
||||||
|
silly_res.albedo_color = color
|
||||||
|
silly_res.metallic_strength = metallic
|
||||||
|
silly_res.roughness_strength = roughness
|
||||||
|
return silly_res
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_material_to_nodes(selected_nodes: Array[Node]) -> void:
|
||||||
|
if selected_nodes.is_empty():
|
||||||
|
printerr("Material Creator: Can't apply the material because there are no nodes selected!")
|
||||||
|
return
|
||||||
|
var new_material: StandardMaterial3D = _create_silly_material_from_editor_values().to_material()
|
||||||
|
# Go through the selected nodes and see if they are MeshInstance3D nodes.
|
||||||
|
# If they do, then call it to set the material to the silly material.
|
||||||
|
var applied: bool = false
|
||||||
|
for node in selected_nodes:
|
||||||
|
if node is MeshInstance3D:
|
||||||
|
node.set_surface_override_material(0, new_material)
|
||||||
|
applied = true
|
||||||
|
if applied:
|
||||||
|
print("Material Creator: Applied material to selected MeshInstance3D nodes!")
|
||||||
|
else:
|
||||||
|
printerr("Material Creator: Can't apply the material because there are no MeshInstance3D nodes selected!")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_apply_button_pressed() -> void:
|
||||||
|
# Using the passed in editor interface, get the selected nodes in the editor.
|
||||||
|
var editor_selection: EditorSelection = editor_interface.get_selection()
|
||||||
|
var selected_nodes: Array[Node] = editor_selection.get_selected_nodes()
|
||||||
|
_apply_material_to_nodes(selected_nodes)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_save_button_pressed() -> void:
|
||||||
|
save_material_dialog.popup_centered(save_material_dialog.min_size * EditorInterface.get_editor_scale())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_export_button_pressed() -> void:
|
||||||
|
export_material_dialog.popup_centered(export_material_dialog.min_size * EditorInterface.get_editor_scale())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_load_button_importer_pressed() -> void:
|
||||||
|
load_material_importer_dialog.popup_centered(load_material_importer_dialog.min_size * EditorInterface.get_editor_scale())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_load_button_loader_pressed() -> void:
|
||||||
|
load_material_loader_dialog.popup_centered(load_material_loader_dialog.min_size * EditorInterface.get_editor_scale())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_import_button_directly_pressed() -> void:
|
||||||
|
import_material_directly_dialog.popup_centered(import_material_directly_dialog.min_size * EditorInterface.get_editor_scale())
|
||||||
151
plugins/addons/material_creator/editor/material_dock.tscn
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bo31028pgti5e"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dy86u5ti4fb3m" path="res://addons/material_creator/editor/material_creator.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="Material Creator" type="Panel"]
|
||||||
|
custom_minimum_size = Vector2(220, 530)
|
||||||
|
offset_right = 220.0
|
||||||
|
offset_bottom = 491.0
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
custom_minimum_size = Vector2(200, 510)
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = -1
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 10.0
|
||||||
|
offset_top = 10.0
|
||||||
|
offset_right = -10.0
|
||||||
|
offset_bottom = -10.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="AlbedoLabel" type="Label" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(16, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Albedo Color:"
|
||||||
|
|
||||||
|
[node name="AlbedoColorPicker" type="ColorPickerButton" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 32)
|
||||||
|
layout_mode = 2
|
||||||
|
color = Color(1, 1, 1, 1)
|
||||||
|
|
||||||
|
[node name="MetallicLabel" type="Label" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(16, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Metallic Strength:"
|
||||||
|
|
||||||
|
[node name="MetallicSlider" type="HSlider" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(16, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
max_value = 1.0
|
||||||
|
step = 0.05
|
||||||
|
|
||||||
|
[node name="RoughnessLabel" type="Label" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(16, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Roughness Strength:"
|
||||||
|
|
||||||
|
[node name="RoughnessSlider" type="HSlider" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(16, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
max_value = 1.0
|
||||||
|
step = 0.05
|
||||||
|
ticks_on_borders = true
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(8, 8)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ApplyButton" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 35)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Applies this material to all selected MeshInstance3D nodes."
|
||||||
|
text = "Apply Material"
|
||||||
|
|
||||||
|
[node name="SaveButton" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 35)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Save a `.silly_mat_loadable` file using a custom ResourceFormatSaver or save a `.tres` using Godot's built-in ResourceFormatSaverText."
|
||||||
|
text = "Save Material
|
||||||
|
(ResourceFormatSaver)"
|
||||||
|
|
||||||
|
[node name="ExportButton" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 35)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Export a `.silly_mat_*` file using the functions on SillyMaterialResource. This works for files outside of the `res://` folder, and can be done at runtime."
|
||||||
|
text = "Export Material
|
||||||
|
(directly at runtime)"
|
||||||
|
|
||||||
|
[node name="LoadButtonLoader" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 60)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Load a `.silly_mat_loadable` using a custom ResourceFormatLoader or load a `.tres` using Godot's built-in ResourceFormatLoaderText."
|
||||||
|
text = "Load Material
|
||||||
|
(ResourceFormatLoader)"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="LoadButtonImporter" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 60)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Load a `.silly_mat_importable` that was imported by an EditorImportPlugin. The loaded data actually comes from the corresponding imported file saved as `res://.godot/imported/something.silly_mat_importable-hash.res`."
|
||||||
|
text = "Load Imported Material
|
||||||
|
(EditorImportPlugin)"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="ImportButtonDirectly" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 60)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Import a `.silly_mat_*` directly from the source file, performing an import on request instead of loading data the editor imported earlier. This ignores any editor import settings, works for files outside of the `res://` folder, and can be done at runtime."
|
||||||
|
text = "Import Material
|
||||||
|
(directly at runtime)"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="SaveMaterialDialog" type="FileDialog" parent="."]
|
||||||
|
size = Vector2i(1000, 500)
|
||||||
|
min_size = Vector2i(1000, 500)
|
||||||
|
filters = PackedStringArray("*.silly_mat_loadable ; Loadable Silly Material (editable)", "*.tres ; Godot Resource (resource)")
|
||||||
|
|
||||||
|
[node name="ExportMaterialDialog" type="FileDialog" parent="."]
|
||||||
|
size = Vector2i(1000, 500)
|
||||||
|
min_size = Vector2i(1000, 500)
|
||||||
|
access = 2
|
||||||
|
filters = PackedStringArray("*.silly_mat_importable ; Importable Silly Material (customizable)", "*.silly_mat_loadable ; Loadable Silly Material (editable)")
|
||||||
|
|
||||||
|
[node name="LoadMaterialLoaderDialog" type="FileDialog" parent="."]
|
||||||
|
title = "Open a File"
|
||||||
|
size = Vector2i(1000, 500)
|
||||||
|
min_size = Vector2i(1000, 500)
|
||||||
|
ok_button_text = "Open"
|
||||||
|
file_mode = 0
|
||||||
|
filters = PackedStringArray("*.silly_mat_loadable ; Loadable Silly Material (editable)", "*.tres ; Godot Resource (resource)")
|
||||||
|
|
||||||
|
[node name="LoadMaterialImporterDialog" type="FileDialog" parent="."]
|
||||||
|
title = "Open a File"
|
||||||
|
size = Vector2i(1000, 500)
|
||||||
|
min_size = Vector2i(1000, 500)
|
||||||
|
ok_button_text = "Open"
|
||||||
|
file_mode = 0
|
||||||
|
filters = PackedStringArray("*.silly_mat_importable ; Importable Silly Material (customizable)")
|
||||||
|
|
||||||
|
[node name="ImportMaterialDirectlyDialog" type="FileDialog" parent="."]
|
||||||
|
title = "Open a File"
|
||||||
|
size = Vector2i(1000, 500)
|
||||||
|
min_size = Vector2i(1000, 500)
|
||||||
|
ok_button_text = "Open"
|
||||||
|
file_mode = 0
|
||||||
|
access = 2
|
||||||
|
filters = PackedStringArray("*.silly_mat_importable ; Importable Silly Material (customizable)", "*.silly_mat_loadable ; Loadable Silly Material (editable)")
|
||||||
|
|
||||||
|
[connection signal="pressed" from="VBoxContainer/ApplyButton" to="." method="_on_apply_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/SaveButton" to="." method="_on_save_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/ExportButton" to="." method="_on_export_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/LoadButtonLoader" to="." method="_on_load_button_loader_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/LoadButtonImporter" to="." method="_on_load_button_importer_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/ImportButtonDirectly" to="." method="_on_import_button_directly_pressed"]
|
||||||
|
[connection signal="file_selected" from="SaveMaterialDialog" to="." method="_save_or_export_file"]
|
||||||
|
[connection signal="file_selected" from="ExportMaterialDialog" to="." method="_save_or_export_file"]
|
||||||
|
[connection signal="file_selected" from="LoadMaterialLoaderDialog" to="." method="load_file_resource_loader"]
|
||||||
|
[connection signal="file_selected" from="LoadMaterialImporterDialog" to="." method="load_file_resource_loader"]
|
||||||
|
[connection signal="file_selected" from="ImportMaterialDirectlyDialog" to="." method="load_file_directly"]
|
||||||
9
plugins/addons/material_creator/examples/blue.tres
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="SillyMaterialResource" load_steps=2 format=3 uid="uid://d1o64hhg6sxuk"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bjnq25fa3ptjc" path="res://addons/material_creator/silly_material_resource.gd" id="1_l7fpc"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_l7fpc")
|
||||||
|
albedo_color = Color(0.06666667, 0.06666667, 0.93333334, 1)
|
||||||
|
metallic_strength = 0.9
|
||||||
|
roughness_strength = 0.1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"albedo_color":[0.0666666701436043,0.933333337306976,0.933333337306976],"metallic_strength":0.8,"roughness_strength":0.2}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://fhh5emd0x6wb
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"albedo_color":[0.0666666701436043,0.933333337306976,0.0666666701436043],"metallic_strength":0.35,"roughness_strength":0.65}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="demos.silly_material_importable.standard_material_3d"
|
||||||
|
type="StandardMaterial3D"
|
||||||
|
uid="uid://sy27jfukvqij"
|
||||||
|
path="res://.godot/imported/green_as_standard_mat.silly_mat_importable-8bc4ac1c11ef82b73959db611c6e0025.res"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/material_creator/examples/green_as_standard_mat.silly_mat_importable"
|
||||||
|
dest_files=["res://.godot/imported/green_as_standard_mat.silly_mat_importable-8bc4ac1c11ef82b73959db611c6e0025.res"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
make_more_red=false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"albedo_color":[0.933333337306976,0.933333337306976,0.0666666701436043],"metallic_strength":0.65,"roughness_strength":0.35}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="demos.silly_material_importable.silly_material_resource"
|
||||||
|
type="Resource"
|
||||||
|
uid="uid://b38mu7kfwyrt0"
|
||||||
|
path="res://.godot/imported/yellow.silly_mat_importable-3de5551b2840cbea5353335a74390454.res"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/material_creator/examples/yellow.silly_mat_importable"
|
||||||
|
dest_files=["res://.godot/imported/yellow.silly_mat_importable-3de5551b2840cbea5353335a74390454.res"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
make_more_red=false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"albedo_color":[0.933333337306976,0.933333337306976,0.0666666701436043],"metallic_strength":0.65,"roughness_strength":0.35}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="demos.silly_material_importable.silly_material_resource"
|
||||||
|
type="Resource"
|
||||||
|
uid="uid://dhhdox2m0kn0l"
|
||||||
|
path="res://.godot/imported/yellow_tinted_red.silly_mat_importable-d6610364b07d235546934af33df4be98.res"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/material_creator/examples/yellow_tinted_red.silly_mat_importable"
|
||||||
|
dest_files=["res://.godot/imported/yellow_tinted_red.silly_mat_importable-d6610364b07d235546934af33df4be98.res"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
make_more_red=true
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
@tool
|
|
||||||
extends Panel
|
|
||||||
# In this file, the word "silly" is used to make it obvious that the name is arbitrary.
|
|
||||||
|
|
||||||
var silly_material_resource := preload("res://addons/material_creator/material_resource.gd")
|
|
||||||
var editor_interface: EditorInterface
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
# Connect all of the signals we'll need to save and load silly materials.
|
|
||||||
$VBoxContainer/ApplyButton.pressed.connect(apply_pressed)
|
|
||||||
$VBoxContainer/SaveButton.pressed.connect(save_pressed)
|
|
||||||
$VBoxContainer/LoadButton.pressed.connect(load_pressed)
|
|
||||||
|
|
||||||
$SaveMaterialDialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
|
||||||
$SaveMaterialDialog.access = FileDialog.ACCESS_RESOURCES
|
|
||||||
$SaveMaterialDialog.current_dir = "res://materials"
|
|
||||||
$SaveMaterialDialog.current_file = "new_material.silly_mat"
|
|
||||||
$SaveMaterialDialog.filters = PackedStringArray([
|
|
||||||
"*.silly_mat ; Silly Material (resource)",
|
|
||||||
"*.tres ; Godot Resource (resource)",
|
|
||||||
"*.mtxt ; Silly Material (source)",
|
|
||||||
])
|
|
||||||
$SaveMaterialDialog.confirmed.connect(_on_save_confirmed)
|
|
||||||
|
|
||||||
$LoadMaterialDialog.access = FileDialog.ACCESS_RESOURCES
|
|
||||||
$LoadMaterialDialog.filters = PackedStringArray([
|
|
||||||
"*.silly_mat ; Silly Material (resource)",
|
|
||||||
"*.tres ; Godot Resource (resource)",
|
|
||||||
"*.mtxt ; Silly Material (source)",
|
|
||||||
])
|
|
||||||
$LoadMaterialDialog.file_selected.connect(load_file_selected)
|
|
||||||
|
|
||||||
RenderingServer.canvas_item_set_clip(get_canvas_item(), true)
|
|
||||||
|
|
||||||
|
|
||||||
func save_pressed() -> void:
|
|
||||||
$SaveMaterialDialog.popup_centered_ratio()
|
|
||||||
|
|
||||||
|
|
||||||
func load_pressed() -> void:
|
|
||||||
$LoadMaterialDialog.popup_centered_ratio()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_save_confirmed() -> void:
|
|
||||||
var path = $SaveMaterialDialog.get_current_path()
|
|
||||||
if path.is_empty():
|
|
||||||
push_error("Material Creator: No path chosen for saving.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# If user typed no extension, default to .silly_mat (resource path).
|
|
||||||
if not path.get_file().contains("."):
|
|
||||||
path += ".silly_mat"
|
|
||||||
|
|
||||||
var ext = path.get_extension().to_lower()
|
|
||||||
|
|
||||||
# Ensure directory exists under res:// when saving inside project.
|
|
||||||
var dir = path.get_base_dir()
|
|
||||||
if path.begins_with("res://") and not DirAccess.dir_exists_absolute(dir):
|
|
||||||
var mk := DirAccess.make_dir_recursive_absolute(dir)
|
|
||||||
if mk != OK:
|
|
||||||
push_error("Material Creator: Can't create folder: \"%s\" (%s)." % [dir, error_string(mk)])
|
|
||||||
return
|
|
||||||
|
|
||||||
var res: Resource = _silly_resource_from_values()
|
|
||||||
|
|
||||||
match ext:
|
|
||||||
"mtxt":
|
|
||||||
# Write SOURCE file (no ResourceSaver, works anywhere).
|
|
||||||
var ok := _write_source_silly(path, res)
|
|
||||||
if not ok:
|
|
||||||
push_error("Material Creator: Failed to write source .mtxt at \"%s\"." % path)
|
|
||||||
else:
|
|
||||||
print("Material Creator: Wrote source to ", path)
|
|
||||||
"silly_mat", "tres":
|
|
||||||
# Save RESOURCE (requires your custom saver for .silly_mat).
|
|
||||||
res.resource_path = path
|
|
||||||
var err := ResourceSaver.save(res, path)
|
|
||||||
if err != OK:
|
|
||||||
push_error("Material Creator: Failed to save resource: \"%s\" (%s)." % [path, error_string(err)])
|
|
||||||
else:
|
|
||||||
print("Material Creator: Saved resource to ", path)
|
|
||||||
_:
|
|
||||||
push_error("Material Creator: Unsupported extension: ." + ext)
|
|
||||||
|
|
||||||
|
|
||||||
func apply_pressed() -> void:
|
|
||||||
# Using the passed in editor interface, get the selected nodes in the editor.
|
|
||||||
var editor_selection: EditorSelection = editor_interface.get_selection()
|
|
||||||
var selected_nodes := editor_selection.get_selected_nodes()
|
|
||||||
if selected_nodes.is_empty():
|
|
||||||
push_error("Material Creator: Can't apply the material, because there are no nodes selected!")
|
|
||||||
return
|
|
||||||
|
|
||||||
var new_material: StandardMaterial3D = _silly_resource_from_values().make_material()
|
|
||||||
# Go through the selected nodes and see if they have the "set_surface_override_material"
|
|
||||||
# function (which only MeshInstance3D has by default). If they do, then set the material
|
|
||||||
# to the silly material.
|
|
||||||
for node in selected_nodes:
|
|
||||||
if node.has_method(&"set_surface_override_material"):
|
|
||||||
node.set_surface_override_material(0, new_material)
|
|
||||||
|
|
||||||
|
|
||||||
func load_file_selected(path: String) -> bool:
|
|
||||||
var ext := path.get_extension().to_lower()
|
|
||||||
|
|
||||||
if ext == "mtxt":
|
|
||||||
# Load SOURCE by manual parse (works inside/outside res://)
|
|
||||||
var loaded := _read_source_silly(path)
|
|
||||||
if loaded == null:
|
|
||||||
push_error("Material Creator: Failed to parse source at \"%s\"." % path)
|
|
||||||
return false
|
|
||||||
$VBoxContainer/AlbedoColorPicker.color = loaded.albedo_color
|
|
||||||
$VBoxContainer/MetallicSlider.value = loaded.metallic_strength
|
|
||||||
$VBoxContainer/RoughnessSlider.value = loaded.roughness_strength
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
# Load RESOURCE via ResourceLoader (silly_mat via your loader, tres via built-in)
|
|
||||||
var silly_resource: Resource = ResourceLoader.load(path)
|
|
||||||
if silly_resource == null:
|
|
||||||
push_error("Material Creator: Failed to load resource at \"%s\"." % path)
|
|
||||||
return false
|
|
||||||
$VBoxContainer/AlbedoColorPicker.color = silly_resource.albedo_color
|
|
||||||
$VBoxContainer/MetallicSlider.value = silly_resource.metallic_strength
|
|
||||||
$VBoxContainer/RoughnessSlider.value = silly_resource.roughness_strength
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func _silly_resource_from_values() -> Resource:
|
|
||||||
var color: Color = $VBoxContainer/AlbedoColorPicker.color
|
|
||||||
var metallic: float = $VBoxContainer/MetallicSlider.value
|
|
||||||
var roughness: float = $VBoxContainer/RoughnessSlider.value
|
|
||||||
var silly_res: Resource = silly_material_resource.new()
|
|
||||||
silly_res.albedo_color = color
|
|
||||||
silly_res.metallic_strength = metallic
|
|
||||||
silly_res.roughness_strength = roughness
|
|
||||||
return silly_res
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# Source (.mtxt) helpers.
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
|
|
||||||
func _write_source_silly(path: String, res: Resource) -> bool:
|
|
||||||
var mat_file := FileAccess.open(path, FileAccess.WRITE)
|
|
||||||
if mat_file == null:
|
|
||||||
return false
|
|
||||||
mat_file.store_line("SILLY_MAT v1")
|
|
||||||
mat_file.store_line(res.albedo_color.to_html(true)) # RGBA hex
|
|
||||||
mat_file.store_line(str(res.metallic_strength))
|
|
||||||
mat_file.store_line(str(res.roughness_strength))
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func _read_source_silly(path: String) -> Resource:
|
|
||||||
var mat_file := FileAccess.open(path, FileAccess.READ)
|
|
||||||
if mat_file == null:
|
|
||||||
return null
|
|
||||||
var header := mat_file.get_line()
|
|
||||||
if not header.begins_with("SILLY_MAT"):
|
|
||||||
return null
|
|
||||||
var mat_res := silly_material_resource.new()
|
|
||||||
mat_res.albedo_color = Color(mat_file.get_line()) # from hex string
|
|
||||||
mat_res.metallic_strength = float(mat_file.get_line())
|
|
||||||
mat_res.roughness_strength = float(mat_file.get_line())
|
|
||||||
return mat_res
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
[gd_scene load_steps=2 format=3 uid="uid://bo31028pgti5e"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dy86u5ti4fb3m" path="res://addons/material_creator/material_creator.gd" id="1"]
|
|
||||||
|
|
||||||
[node name="Material Creator" type="Panel"]
|
|
||||||
custom_minimum_size = Vector2(208, 0)
|
|
||||||
offset_right = 220.0
|
|
||||||
offset_bottom = 340.0
|
|
||||||
script = ExtResource("1")
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
|
||||||
layout_mode = 0
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = -100.0
|
|
||||||
offset_right = 100.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
|
|
||||||
[node name="AlbedoLabel" type="Label" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Albedo Color:"
|
|
||||||
|
|
||||||
[node name="AlbedoColorPicker" type="ColorPickerButton" parent="VBoxContainer"]
|
|
||||||
custom_minimum_size = Vector2(0, 32)
|
|
||||||
layout_mode = 2
|
|
||||||
color = Color(1, 1, 1, 1)
|
|
||||||
|
|
||||||
[node name="MetallicLabel" type="Label" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Metallic Strength:"
|
|
||||||
|
|
||||||
[node name="MetallicSlider" type="HSlider" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
max_value = 1.0
|
|
||||||
step = 0.05
|
|
||||||
|
|
||||||
[node name="RoughnessLabel" type="Label" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Roughness Strength:"
|
|
||||||
|
|
||||||
[node name="RoughnessSlider" type="HSlider" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
max_value = 1.0
|
|
||||||
step = 0.05
|
|
||||||
ticks_on_borders = true
|
|
||||||
|
|
||||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="ApplyButton" type="Button" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Apply Material"
|
|
||||||
|
|
||||||
[node name="SaveButton" type="Button" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Save Material"
|
|
||||||
|
|
||||||
[node name="LoadButton" type="Button" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Load Material"
|
|
||||||
clip_text = true
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/LoadButton"]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 0
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_bottom = -10.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
text = "Load silly material and
|
|
||||||
apply to selected node(s)"
|
|
||||||
|
|
||||||
[node name="SaveMaterialDialog" type="FileDialog" parent="."]
|
|
||||||
filters = PackedStringArray("*.silly_mat")
|
|
||||||
|
|
||||||
[node name="LoadMaterialDialog" type="FileDialog" parent="."]
|
|
||||||
title = "Open a File"
|
|
||||||
ok_button_text = "Open"
|
|
||||||
file_mode = 0
|
|
||||||
filters = PackedStringArray("*.silly_mat")
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
@tool
|
|
||||||
extends ResourceFormatLoader
|
|
||||||
class_name SillyMatFormatLoader
|
|
||||||
## Custom loader for the .silly_mat file format.
|
|
||||||
## Allows Godot to recognize and load SillyMaterialResource files.
|
|
||||||
## Register this loader in the EditorPlugin to enable saving/loading resources.
|
|
||||||
|
|
||||||
|
|
||||||
## Returns the list of file extensions this loader supports.
|
|
||||||
func _get_recognized_extensions() -> PackedStringArray:
|
|
||||||
# Returns only ".silly_mat"
|
|
||||||
return PackedStringArray(["silly_mat"])
|
|
||||||
|
|
||||||
|
|
||||||
## Returns what resource type this loader handles.
|
|
||||||
func _handles_type(typename: StringName) -> bool:
|
|
||||||
return typename == "SillyMaterialResource"
|
|
||||||
|
|
||||||
|
|
||||||
## Returns the resource type name based on file extension.
|
|
||||||
func _get_resource_type(path: String) -> String:
|
|
||||||
return "SillyMaterialResource" if path.get_extension() == "silly_mat" else ""
|
|
||||||
|
|
||||||
|
|
||||||
## Main load function. Reads .silly_mat and constructs a SillyMaterialResource.
|
|
||||||
func _load(path: String, original_path: String, use_sub_threads, cache_mode):
|
|
||||||
var mat_file = FileAccess.open(path, FileAccess.READ)
|
|
||||||
if mat_file == null:
|
|
||||||
return ERR_CANT_OPEN
|
|
||||||
|
|
||||||
# Check header line to validate file format version.
|
|
||||||
if mat_file.get_line() != "SILLY_MAT v1":
|
|
||||||
return ERR_PARSE_ERROR
|
|
||||||
|
|
||||||
# Create and Fill SillyMaterialResource
|
|
||||||
var mat_res: SillyMaterialResource = SillyMaterialResource.new()
|
|
||||||
mat_res.albedo_color = Color(mat_file.get_line())
|
|
||||||
mat_res.metallic_strength = float(mat_file.get_line())
|
|
||||||
mat_res.roughness_strength = float(mat_file.get_line())
|
|
||||||
return mat_res
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
@tool
|
|
||||||
extends ResourceFormatSaver
|
|
||||||
class_name SillyMatFormatSaver
|
|
||||||
## Custom saver for the .silly_mat file format.
|
|
||||||
## Works together with SillyMatFormatLoader to make SillyMaterialResource.
|
|
||||||
|
|
||||||
|
|
||||||
## This saver only supports SilluMaterialResource.
|
|
||||||
func _recognize(resource: Resource) -> bool:
|
|
||||||
return resource is SillyMaterialResource
|
|
||||||
|
|
||||||
|
|
||||||
## Return list of file extensions this saver will write.
|
|
||||||
func _get_recognized_extensions(resource: Resource) -> PackedStringArray:
|
|
||||||
return PackedStringArray(["silly_mat"])
|
|
||||||
|
|
||||||
|
|
||||||
## Main save function.
|
|
||||||
## Serializes a SillyMaterialResource into .silly_mat format.
|
|
||||||
##
|
|
||||||
## It will write simple text-based format, one property per line.
|
|
||||||
func _save(resource: Resource, path: String, flags: int) -> int:
|
|
||||||
var mat_res: SillyMaterialResource = resource as SillyMaterialResource
|
|
||||||
if mat_res == null:
|
|
||||||
return ERR_INVALID_DATA
|
|
||||||
|
|
||||||
var mat_file := FileAccess.open(path, FileAccess.WRITE)
|
|
||||||
if mat_file == null:
|
|
||||||
return ERR_CANT_OPEN
|
|
||||||
|
|
||||||
mat_file.store_line("SILLY_MAT v1")
|
|
||||||
mat_file.store_line(mat_res.albedo_color.to_html(true)) # Stored in HTML hex.
|
|
||||||
mat_file.store_line(str(mat_res.metallic_strength))
|
|
||||||
mat_file.store_line(str(mat_res.roughness_strength))
|
|
||||||
return OK
|
|
||||||
@@ -1,38 +1,46 @@
|
|||||||
# A simple (and silly) material resource plugin. Allows you to make a really simple material
|
## A simple (and silly) material resource plugin. Allows you to make a really
|
||||||
# from a custom dock, that you can save and load, and apply to selected MeshInstances.
|
## simple material from a custom dock, which can be applied to meshes,
|
||||||
#
|
## saved to files, loaded from files, imported from files, and more.
|
||||||
# SPECIAL NOTE: This technically should be using EditorImportPlugin and EditorExportPlugin
|
##
|
||||||
# to handle the input and output of the silly material. However, currently you cannot export
|
## See the documentation in the `README.md` file for more information,
|
||||||
# custom resources in Godot, so instead we're using JSON files instead.
|
## and also the documentation in each class (Ctrl+Click on these in Godot):
|
||||||
#
|
## - SillyMaterialResource
|
||||||
# This example should be replaced when EditorImportPlugin and EditorExportPlugin are both
|
## - ImportSillyMatAsSillyMaterialResource
|
||||||
# fully working and you can save custom resources.
|
## - ImportSillyMatAsStandardMaterial3D
|
||||||
|
## - SillyMatFormatLoader
|
||||||
|
## - SillyMatFormatSaver
|
||||||
@tool
|
@tool
|
||||||
extends EditorPlugin
|
extends EditorPlugin
|
||||||
|
|
||||||
var io_material_dialog: Panel
|
|
||||||
var _loader: SillyMatFormatLoader
|
var _material_creator_dock: Panel
|
||||||
var _saver: SillyMatFormatSaver
|
var _silly_mat_loader := SillyMatFormatLoader.new()
|
||||||
|
var _silly_mat_saver := SillyMatFormatSaver.new()
|
||||||
|
var _import_as_silly_mat_res := ImportSillyMatAsSillyMaterialResource.new()
|
||||||
|
var _import_as_standard_mat := ImportSillyMatAsStandardMaterial3D.new()
|
||||||
|
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
_loader = SillyMatFormatLoader.new()
|
# Set up the loader and saver.
|
||||||
_saver = SillyMatFormatSaver.new()
|
ResourceLoader.add_resource_format_loader(_silly_mat_loader)
|
||||||
ResourceLoader.add_resource_format_loader(_loader)
|
ResourceSaver.add_resource_format_saver(_silly_mat_saver)
|
||||||
ResourceSaver.add_resource_format_saver(_saver)
|
# Set up the importers.
|
||||||
|
add_import_plugin(_import_as_silly_mat_res)
|
||||||
io_material_dialog = preload("res://addons/material_creator/material_dock.tscn").instantiate()
|
add_import_plugin(_import_as_standard_mat)
|
||||||
io_material_dialog.editor_interface = get_editor_interface()
|
# Set up the silly material creator dock.
|
||||||
add_control_to_dock(DOCK_SLOT_LEFT_UL, io_material_dialog)
|
const dock_scene: PackedScene = preload("res://addons/material_creator/editor/material_dock.tscn")
|
||||||
|
_material_creator_dock = dock_scene.instantiate()
|
||||||
|
_material_creator_dock.editor_interface = get_editor_interface()
|
||||||
|
var dock_scale: float = EditorInterface.get_editor_scale() * 0.85
|
||||||
|
_material_creator_dock.custom_minimum_size *= dock_scale
|
||||||
|
for child in _material_creator_dock.find_children("*", "Control"):
|
||||||
|
child.custom_minimum_size *= dock_scale
|
||||||
|
add_control_to_dock(DOCK_SLOT_LEFT_UL, _material_creator_dock)
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
func _exit_tree() -> void:
|
||||||
remove_control_from_docks(io_material_dialog)
|
remove_control_from_docks(_material_creator_dock)
|
||||||
|
ResourceLoader.remove_resource_format_loader(_silly_mat_loader)
|
||||||
if _loader:
|
ResourceSaver.remove_resource_format_saver(_silly_mat_saver)
|
||||||
ResourceLoader.remove_resource_format_loader(_loader)
|
remove_import_plugin(_import_as_silly_mat_res)
|
||||||
_loader = null
|
remove_import_plugin(_import_as_standard_mat)
|
||||||
if _saver:
|
|
||||||
ResourceSaver.remove_resource_format_saver(_saver)
|
|
||||||
_saver = null
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
@tool
|
|
||||||
extends Resource
|
|
||||||
class_name SillyMaterialResource
|
|
||||||
|
|
||||||
# Use export to make properties visible and serializable in the inspector and for resource saving/loading.
|
|
||||||
@export var albedo_color: Color = Color.BLACK
|
|
||||||
@export var metallic_strength: float = 0.0
|
|
||||||
@export var roughness_strength: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
# Create a StandardMaterial3D from the resource's properties.
|
|
||||||
# Convert our data into a dictionary so we can convert it
|
|
||||||
# into the JSON format.
|
|
||||||
func make_json() -> String:
|
|
||||||
var json_dict := {}
|
|
||||||
|
|
||||||
json_dict["albedo_color"] = {}
|
|
||||||
json_dict["albedo_color"]["r"] = albedo_color.r
|
|
||||||
json_dict["albedo_color"]["g"] = albedo_color.g
|
|
||||||
json_dict["albedo_color"]["b"] = albedo_color.b
|
|
||||||
|
|
||||||
json_dict["metallic_strength"] = metallic_strength
|
|
||||||
json_dict["roughness_strength"] = roughness_strength
|
|
||||||
|
|
||||||
return JSON.stringify(json_dict)
|
|
||||||
|
|
||||||
|
|
||||||
# Convert the passed in string to a JSON dictionary, and then
|
|
||||||
# fill in our data.
|
|
||||||
func from_json(json_dict_as_string: String) -> void:
|
|
||||||
var json_dict: Dictionary = JSON.parse_string(json_dict_as_string)
|
|
||||||
|
|
||||||
albedo_color.r = json_dict["albedo_color"]["r"]
|
|
||||||
albedo_color.g = json_dict["albedo_color"]["g"]
|
|
||||||
albedo_color.b = json_dict["albedo_color"]["b"]
|
|
||||||
|
|
||||||
metallic_strength = json_dict["metallic_strength"]
|
|
||||||
roughness_strength = json_dict["roughness_strength"]
|
|
||||||
|
|
||||||
|
|
||||||
# Make a StandardMaterial3D using our variables.
|
|
||||||
func make_material() -> StandardMaterial3D:
|
|
||||||
var mat = StandardMaterial3D.new()
|
|
||||||
mat.albedo_color = albedo_color
|
|
||||||
mat.metallic = metallic_strength
|
|
||||||
mat.roughness = roughness_strength
|
|
||||||
return mat
|
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
name="Material Creator Plugin Demo"
|
name="Material Creator Plugin Demo"
|
||||||
description="Loads and saves a 3D Material from an external text file"
|
description="Loads and saves a 3D Material from an external text file"
|
||||||
author="TwistedTwigleg"
|
author="Aaron Franke, TwistedTwigleg, Šarūnas Ramonas"
|
||||||
version="1.0"
|
version="1.0"
|
||||||
script="material_plugin.gd"
|
script="material_plugin.gd"
|
||||||
|
|||||||
109
plugins/addons/material_creator/silly_material_resource.gd
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
## Example class that can be imported, exported, loaded, saved, etc, in various ways.
|
||||||
|
##
|
||||||
|
## - To perform an editor import as a `SillyMaterialResource`, the class
|
||||||
|
## `ImportSillyMatAsSillyMaterialResource` will handle files in the `res://`
|
||||||
|
## folder ending in `.silly_mat_importable` and import them.
|
||||||
|
## as long as "Silly Material Resource" is selected in the Import dock.
|
||||||
|
## Then `ResourceLoader.load()` will return a read-only `SillyMaterialResource`.
|
||||||
|
##
|
||||||
|
## - To perform an editor import as a `StandardMaterial3D`, the class
|
||||||
|
## `ImportSillyMatAsStandardMaterial3D` will handle files in the `res://`
|
||||||
|
## folder ending in `.silly_mat_importable` and import them,
|
||||||
|
## as long as "Standard Material 3D" is selected in the Import dock.
|
||||||
|
## Then `ResourceLoader.load()` will return a read-only `StandardMaterial3D`.
|
||||||
|
##
|
||||||
|
## - To perform an editor load as a SillyMaterialResource, the class
|
||||||
|
## `SillyMatFormatLoader` will handle files in the `res://`
|
||||||
|
## folder ending in `.silly_mat_loadable` and load them.
|
||||||
|
## Then `ResourceLoader.load()` will return a writeable `SillyMaterialResource`.
|
||||||
|
## This can then be saved back to a file with `SillyMatFormatSaver`.
|
||||||
|
##
|
||||||
|
## - To perform a runtime (or editor) import into a StandardMaterial3D, run the
|
||||||
|
## `read_from_file` function, which reads the data from a file and runs
|
||||||
|
## `from_json_dictionary`, then run `to_material` to generate a material.
|
||||||
|
##
|
||||||
|
## - To perform a runtime (or editor) export of a StandardMaterial3D, run
|
||||||
|
## `from_material` to convert a material, then run the `write_to_file`
|
||||||
|
## function, which runs `to_json_dictionary` and saves this to a file.
|
||||||
|
##
|
||||||
|
## These functions should be placed in this class to support runtime imports
|
||||||
|
## and exports, but the editor classes can also make use of these functions,
|
||||||
|
## allowing the editor-only classes to be lightweight wrappers.
|
||||||
|
##
|
||||||
|
## For a more comprehensive example, see the GLTF module in Godot's source code.
|
||||||
|
## For a less comprehensive example, see the "simple_import_plugin" folder.
|
||||||
|
@tool
|
||||||
|
class_name SillyMaterialResource
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
|
||||||
|
# Use export to make properties visible in the inspector
|
||||||
|
# and serializable for resource saving/loading.
|
||||||
|
@export var albedo_color: Color = Color.BLACK
|
||||||
|
@export var metallic_strength: float = 0.0
|
||||||
|
@export var roughness_strength: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
## Given a Dictionary parsed from JSON data, read in the data as a new SillyMaterialResource.
|
||||||
|
static func from_json_dictionary(json_dictionary: Dictionary) -> SillyMaterialResource:
|
||||||
|
var ret := SillyMaterialResource.new()
|
||||||
|
# Note: In an actual importer where you need to handle arbitrary user data,
|
||||||
|
# you may wish to do things like checking if the key exists, checking if
|
||||||
|
# the value is an array, checking if the array has a length of 3, checking
|
||||||
|
# if each value in the array is a number, and so on.
|
||||||
|
# For simplicity, these things are omitted from this demo's example code.
|
||||||
|
var albedo_array: Array = json_dictionary["albedo_color"]
|
||||||
|
ret.albedo_color.r = albedo_array[0]
|
||||||
|
ret.albedo_color.g = albedo_array[1]
|
||||||
|
ret.albedo_color.b = albedo_array[2]
|
||||||
|
ret.metallic_strength = json_dictionary["metallic_strength"]
|
||||||
|
ret.roughness_strength = json_dictionary["roughness_strength"]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
## Convert SillyMaterialResource data into a Dictionary for saving as JSON.
|
||||||
|
## To perform a runtime export of a StandardMaterial3D, run this function after `from_material`.
|
||||||
|
func to_json_dictionary() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"albedo_color": [albedo_color.r, albedo_color.g, albedo_color.b],
|
||||||
|
"metallic_strength": metallic_strength,
|
||||||
|
"roughness_strength": roughness_strength,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Given a StandardMaterial3D, copy its data to a new SillyMaterialResource.
|
||||||
|
static func from_material(mat: StandardMaterial3D) -> SillyMaterialResource:
|
||||||
|
var ret := SillyMaterialResource.new()
|
||||||
|
ret.albedo_color = mat.albedo_color
|
||||||
|
ret.metallic_strength = mat.metallic
|
||||||
|
ret.roughness_strength = mat.roughness
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
## Create a new StandardMaterial3D using the data in this SillyMaterialResource.
|
||||||
|
func to_material() -> StandardMaterial3D:
|
||||||
|
var mat = StandardMaterial3D.new()
|
||||||
|
mat.albedo_color = albedo_color
|
||||||
|
mat.metallic = metallic_strength
|
||||||
|
mat.roughness = roughness_strength
|
||||||
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
## Wrapper around `from_json_dictionary` that reads from a file at the given path.
|
||||||
|
static func read_from_file(path: String) -> SillyMaterialResource:
|
||||||
|
var mat_file := FileAccess.open(path, FileAccess.READ)
|
||||||
|
if mat_file == null:
|
||||||
|
return null
|
||||||
|
var json_dict: Dictionary = JSON.parse_string(mat_file.get_as_text())
|
||||||
|
return from_json_dictionary(json_dict)
|
||||||
|
|
||||||
|
|
||||||
|
## Wrapper around `to_json_dictionary` that writes to a file at the given path.
|
||||||
|
func write_to_file(path: String) -> Error:
|
||||||
|
var mat_file := FileAccess.open(path, FileAccess.WRITE)
|
||||||
|
if mat_file == null:
|
||||||
|
return ERR_CANT_OPEN
|
||||||
|
var json_dict: Dictionary = to_json_dictionary()
|
||||||
|
mat_file.store_string(JSON.stringify(json_dict))
|
||||||
|
mat_file.store_string("\n")
|
||||||
|
return OK
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="demos.sillymaterial"
|
|
||||||
type="Material"
|
|
||||||
uid="uid://bqja7mgxfmfqa"
|
|
||||||
path="res://.godot/imported/test.mtxt-32ce4469df24b9f725d1e3476ff3b332.res"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://addons/material_import_plugin/test.mtxt"
|
|
||||||
dest_files=["res://.godot/imported/test.mtxt-32ce4469df24b9f725d1e3476ff3b332.res"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
use_red_anyway=false
|
|
||||||
@@ -7,7 +7,7 @@ enum Preset {
|
|||||||
|
|
||||||
|
|
||||||
func _get_importer_name() -> String:
|
func _get_importer_name() -> String:
|
||||||
return "demos.sillymaterial"
|
return "demos.mtxt"
|
||||||
|
|
||||||
|
|
||||||
func _get_visible_name() -> String:
|
func _get_visible_name() -> String:
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[plugin]
|
[plugin]
|
||||||
|
|
||||||
name="Material Importer Plugin Demo"
|
name="Simple Importer Plugin Demo"
|
||||||
description="Imports a 3D Material from an external text file"
|
description="Imports a 3D Material from an external text file"
|
||||||
author="George Marques"
|
author="George Marques"
|
||||||
version="1.0"
|
version="1.0"
|
||||||
15
plugins/addons/simple_import_plugin/test.mtxt.import
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="demos.mtxt"
|
||||||
|
type="Material"
|
||||||
|
uid="uid://bqja7mgxfmfqa"
|
||||||
|
path="res://.godot/imported/test.mtxt-cc369242bc971647fccdadd6e971f1d0.res"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/simple_import_plugin/test.mtxt"
|
||||||
|
dest_files=["res://.godot/imported/test.mtxt-cc369242bc971647fccdadd6e971f1d0.res"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
use_red_anyway=false
|
||||||
@@ -31,7 +31,7 @@ gdscript/warnings/untyped_declaration=1
|
|||||||
|
|
||||||
[editor_plugins]
|
[editor_plugins]
|
||||||
|
|
||||||
enabled=PackedStringArray("res://addons/custom_node/plugin.cfg", "res://addons/main_screen/plugin.cfg", "res://addons/material_creator/plugin.cfg", "res://addons/material_import_plugin/plugin.cfg")
|
enabled=PackedStringArray("res://addons/custom_node/plugin.cfg", "res://addons/main_screen/plugin.cfg", "res://addons/material_creator/plugin.cfg", "res://addons/simple_import_plugin/plugin.cfg")
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
|
|||||||
BIN
plugins/screenshots/heart_custom_node.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 62 KiB |
BIN
plugins/screenshots/main_screen_plugin.webp
Normal file
|
After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 93 KiB |
BIN
plugins/screenshots/material_creator_plugin_applied.webp
Normal file
|
After Width: | Height: | Size: 496 KiB |
BIN
plugins/screenshots/material_creator_plugin_dock.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 90 KiB |
BIN
plugins/screenshots/simple_import_plugin.webp
Normal file
|
After Width: | Height: | Size: 76 KiB |