Overhaul silly material creator plugin demo (#1261)

This commit is contained in:
Aaron Franke
2025-10-11 03:22:04 -07:00
committed by GitHub
parent 0343cedd48
commit 0ae09b7e5a
56 changed files with 897 additions and 493 deletions

9
plugins/.gitignore vendored
View File

@@ -1,2 +1,7 @@
# "Silly Material" files written by the editor plugin
*.silly_mat
# "Silly Material" files written by the editor plugin.
# 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

View File

@@ -21,15 +21,17 @@ This project contains 4 plugins:
* The custom node plugin shows how to create a custom node type
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
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
folders to the `addons/` folder in a Godot project, and then
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
![Heart Plugin](screenshots/heart_plugin.png)
![Heart Custom Node](screenshots/heart_custom_node.webp)
![Main Screen Plugin](screenshots/main_screen_plugin.png)
![Main Screen Plugin](screenshots/main_screen_plugin.webp)
![Material Import Plugin](screenshots/material_import_plugin.png)
![Material Creator Plugin](screenshots/material_creator_plugin_applied.webp)
![Material Creator Plugin 1](screenshots/material_creator_plugin_1.png)
![Material Creator Plugin 2](screenshots/material_creator_plugin_2.png)
![Simple Import Plugin](screenshots/simple_import_plugin.webp)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

View File

@@ -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

View File

@@ -4,7 +4,8 @@ extends EditorPlugin
func _enter_tree() -> void:
# 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:

View File

@@ -1,25 +1,142 @@
# Material Creator Plugin Demo
This plugin demo contains a custom material creation dock
inside the Godot editor.
This plugin demo demonstrates these things:
Custom docks are made of Control nodes, they run in the
editor, and any behavior must be done through `tool` scripts.
For more information, see this documentation article:
https://docs.godotengine.org/en/latest/tutorials/plugins/editor/making_plugins.html#a-custom-dock
- How to create a custom dock in the Godot editor with basic functionality.
Custom docks are made of Control nodes, they run in the
editor, and any behavior must be done through `@tool` scripts.
For more information, see this documentation article:
https://docs.godotengine.org/en/latest/tutorials/plugins/editor/making_plugins.html#a-custom-dock
## Features
- Adjust albedo color, metallic, and rouphness values interactively.
- 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).
- How to create a custom Resource type, and provide logic for
serializing, deserializing, and converting this Resource type.
## Implementation notes
- `.silly_mat` format is registered through `SillyMatFormatSaver` and
`SillyMatFormatLoader` in the plugin.
- Custm docks are built from `Control` nodes and run as `@tool` scripts.
- Editor integration with classes for loading, saving, and importing
this Resource type, including optional import customization.
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).
![Material Creator Plugin Imported File is Read-Only](../../screenshots/material_creator_plugin_imported_file_is_read_only.webp)
## 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.
![Material Creator Plugin Dock](../../screenshots/material_creator_plugin_dock.webp)

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://u2esoq3eygve

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://cmgoxx63wybil

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://b004gkmug1qlt

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://cvaleef5ekitp

View 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())

View 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"]

View 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

View File

@@ -0,0 +1 @@
{"albedo_color":[0.0666666701436043,0.933333337306976,0.933333337306976],"metallic_strength":0.8,"roughness_strength":0.2}

View File

@@ -0,0 +1 @@
uid://fhh5emd0x6wb

View File

@@ -0,0 +1 @@
{"albedo_color":[0.0666666701436043,0.933333337306976,0.0666666701436043],"metallic_strength":0.35,"roughness_strength":0.65}

View File

@@ -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

View File

@@ -0,0 +1 @@
{"albedo_color":[0.933333337306976,0.933333337306976,0.0666666701436043],"metallic_strength":0.65,"roughness_strength":0.35}

View File

@@ -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

View File

@@ -0,0 +1 @@
{"albedo_color":[0.933333337306976,0.933333337306976,0.0666666701436043],"metallic_strength":0.65,"roughness_strength":0.35}

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -1,38 +1,46 @@
# A simple (and silly) material resource plugin. Allows you to make a really simple material
# from a custom dock, that you can save and load, and apply to selected MeshInstances.
#
# 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
# custom resources in Godot, so instead we're using JSON files instead.
#
# This example should be replaced when EditorImportPlugin and EditorExportPlugin are both
# fully working and you can save custom resources.
## A simple (and silly) material resource plugin. Allows you to make a really
## simple material from a custom dock, which can be applied to meshes,
## saved to files, loaded from files, imported from files, and more.
##
## See the documentation in the `README.md` file for more information,
## and also the documentation in each class (Ctrl+Click on these in Godot):
## - SillyMaterialResource
## - ImportSillyMatAsSillyMaterialResource
## - ImportSillyMatAsStandardMaterial3D
## - SillyMatFormatLoader
## - SillyMatFormatSaver
@tool
extends EditorPlugin
var io_material_dialog: Panel
var _loader: SillyMatFormatLoader
var _saver: SillyMatFormatSaver
var _material_creator_dock: Panel
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:
_loader = SillyMatFormatLoader.new()
_saver = SillyMatFormatSaver.new()
ResourceLoader.add_resource_format_loader(_loader)
ResourceSaver.add_resource_format_saver(_saver)
io_material_dialog = preload("res://addons/material_creator/material_dock.tscn").instantiate()
io_material_dialog.editor_interface = get_editor_interface()
add_control_to_dock(DOCK_SLOT_LEFT_UL, io_material_dialog)
# Set up the loader and saver.
ResourceLoader.add_resource_format_loader(_silly_mat_loader)
ResourceSaver.add_resource_format_saver(_silly_mat_saver)
# Set up the importers.
add_import_plugin(_import_as_silly_mat_res)
add_import_plugin(_import_as_standard_mat)
# Set up the silly material creator dock.
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:
remove_control_from_docks(io_material_dialog)
if _loader:
ResourceLoader.remove_resource_format_loader(_loader)
_loader = null
if _saver:
ResourceSaver.remove_resource_format_saver(_saver)
_saver = null
remove_control_from_docks(_material_creator_dock)
ResourceLoader.remove_resource_format_loader(_silly_mat_loader)
ResourceSaver.remove_resource_format_saver(_silly_mat_saver)
remove_import_plugin(_import_as_silly_mat_res)
remove_import_plugin(_import_as_standard_mat)

View File

@@ -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

View File

@@ -2,6 +2,6 @@
name="Material Creator Plugin Demo"
description="Loads and saves a 3D Material from an external text file"
author="TwistedTwigleg"
author="Aaron Franke, TwistedTwigleg, Šarūnas Ramonas"
version="1.0"
script="material_plugin.gd"

View 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

View File

@@ -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

View File

@@ -7,7 +7,7 @@ enum Preset {
func _get_importer_name() -> String:
return "demos.sillymaterial"
return "demos.mtxt"
func _get_visible_name() -> String:

View File

@@ -1,6 +1,6 @@
[plugin]
name="Material Importer Plugin Demo"
name="Simple Importer Plugin Demo"
description="Imports a 3D Material from an external text file"
author="George Marques"
version="1.0"

View 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

View File

@@ -31,7 +31,7 @@ gdscript/warnings/untyped_declaration=1
[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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB