Import plugins ============== Note: This tutorial assumes you already know how to make generic plugins. If in doubt, refer to the `doc_making_plugins` page. This also assumes you are acquainted with Pandemonium's import system. Introduction ------------ An import plugin is a special type of editor tool that allows custom resources to be imported by Pandemonium and be treated as first-class resources. The editor itself comes bundled with a lot of import plugins to handle the common resources like PNG images, Collada and glTF models, Ogg Vorbis sounds, and many more. This tutorial will show you how to create a simple import plugin to load a custom text file as a material resource. This text file will contain three numeric values separated by comma, which represents the three channels of a color, and the resulting color will be used as the albedo (main color) of the imported material. In this example it will contain the pure blue color (zero red, zero green, and full blue): ``` 0,0,255 ``` Configuration ------------- First we need a generic plugin that will handle the initialization and destruction of our import plugin. Let's add the `plugin.cfg` file first: ``` [plugin] name="Silly Material Importer" description="Imports a 3D Material from an external text file." author="Yours Truly" version="1.0" script="material_import.gd" ``` Then we need the `material_import.gd` file to add and remove the import plugin when needed: ``` # material_import.gd tool extends EditorPlugin var import_plugin func _enter_tree(): import_plugin = preload("import_plugin.gd").new() add_import_plugin(import_plugin) func _exit_tree(): remove_import_plugin(import_plugin) import_plugin = null ``` When this plugin is activated, it will create a new instance of the import plugin (which we'll soon make) and add it to the editor using the `add_import_plugin()` method. We store a reference to it in a class member `import_plugin` so we can refer to it later when removing it. The `remove_import_plugin()` method is called when the plugin is deactivated to clean up the memory and let the editor know the import plugin isn't available anymore. Note that the import plugin is a reference type, so it doesn't need to be explicitly released from memory with the `free()` function. It will be released automatically by the engine when it goes out of scope. The EditorImportPlugin class ---------------------------- The main character of the show is the `EditorImportPlugin class`. It is responsible for implementing the methods that are called by Pandemonium when it needs to know how to deal with files. Let's begin to code our plugin, one method at time: ``` # import_plugin.gd tool extends EditorImportPlugin func get_importer_name(): return "demos.sillymaterial" ``` The first method is the `get_importer_name()( EditorImportPlugin_method_get_importer_name )`. This is a unique name for your plugin that is used by Pandemonium to know which import was used in a certain file. When the files needs to be reimported, the editor will know which plugin to call. ``` func get_visible_name(): return "Silly Material" ``` The `get_visible_name()( EditorImportPlugin_method_get_visible_name )` method is responsible for returning the name of the type it imports and it will be shown to the user in the Import dock. You should choose this name as a continuation to "Import as", e.g. *"Import as Silly Material"*. You can name it whatever you want but we recommend a descriptive name for your plugin. ``` func get_recognized_extensions(): return ["mtxt"] ``` Pandemonium's import system detects file types by their extension. In the `get_recognized_extensions()( EditorImportPlugin_method_get_recognized_extensions )` method you return an array of strings to represent each extension that this plugin can understand. If an extension is recognized by more than one plugin, the user can select which one to use when importing the files. Tip: Common extensions like `.json` and `.txt` might be used by many plugins. Also, there could be files in the project that are just data for the game and should not be imported. You have to be careful when importing to validate the data. Never expect the file to be well-formed. ``` func get_save_extension(): return "material" ``` The imported files are saved in the `.import` folder at the project's root. Their extension should match the type of resource you are importing, but since Pandemonium can't tell what you'll use (because there might be multiple valid extensions for the same resource), you need to declare what will be used in the import. Since we're importing a Material, we'll use the special extension for such resource types. If you are importing a scene, you can use `scn`. Generic resources can use the `res` extension. However, this is not enforced in any way by the engine. ``` func get_resource_type(): return "SpatialMaterial" ``` The imported resource has a specific type, so the editor can know which property slot it belongs to. This allows drag and drop from the FileSystem dock to a property in the Inspector. In our case it's a `SpatialMaterial`, which can be applied to 3D objects. Note: If you need to import different types from the same extension, you have to create multiple import plugins. You can abstract the import code on another file to avoid duplication in this regard. Options and presets ------------------- Your plugin can provide different options to allow the user to control how the resource will be imported. If a set of selected options is common, you can also create different presets to make it easier for the user. The following image shows how the options will appear in the editor: ![](img/import_plugin_options.png) Since there might be many presets and they are identified with a number, it's a good practice to use an enum so you can refer to them using names. ``` tool extends EditorImportPlugin enum Presets { DEFAULT } ... ``` Now that the enum is defined, let's keep looking at the methods of an import plugin: ``` func get_preset_count(): return Presets.size() ``` The `get_preset_count()` method returns the amount of presets that this plugins defines. We only have one preset now, but we can make this method future-proof by returning the size of our `Presets` enumeration. ``` func get_preset_name(preset): match preset: Presets.DEFAULT: return "Default" _: return "Unknown" ``` Here we have the `get_preset_name()` method, which gives names to the presets as they will be presented to the user, so be sure to use short and clear names. We can use the `match` statement here to make the code more structured. This way it's easy to add new presets in the future. We use the catch all pattern to return something too. Although Pandemonium won't ask for presets beyond the preset count you defined, it's always better to be on the safe side. If you have only one preset you could simply return its name directly, but if you do this you have to be careful when you add more presets. ``` func get_import_options(preset): match preset: Presets.DEFAULT: return [{ "name": "use_red_anyway", "default_value": false }] _: return [] ``` This is the method which defines the available options. `get_import_options()` returns an array of dictionaries, and each dictionary contains a few keys that are checked to customize the option as its shown to the user. The following table shows the possible keys: +-------------------+------------+----------------------------------------------------------------------------------------------------------+ | Key | Type | Description | +===================+============+==========================================================================================================+ | `name` | String | The name of the option. When showed, underscores become spaces and first letters are capitalized. | +-------------------+------------+----------------------------------------------------------------------------------------------------------+ | `default_value` | Any | The default value of the option for this preset. | +-------------------+------------+----------------------------------------------------------------------------------------------------------+ | `property_hint` | Enum value | One of the `PropertyHint