{{description>Learn how to create custom Curvy Generator modules: slots, refresh logic, data types, editor scripts, and best practices.}}
====== Creating Custom CG Modules ======
This page explains how to write your own CG modules. Before reading, make sure you understand CG's [[documentation:generator:advancedconcepts]].
===== Creating Module Files =====
The fastest way to start is the module wizard:
- In Unity's Project window, right-click → **Create → Curvy → CG Module**.
- Fill in the four fields and click **Create**.
^ Field ^ Description ^
| Class Name | C# class name (e.g. ''MyModule'') |
| Module Name | Module name in UI (e.g. ''My Module'') |
| Menu Name | Path in the [[documentation:generator:grapheditor|CG Editor]]'s Add menu, using ''/'' as separator (e.g. ''Custom/My Module'') |
| Description | A description of the module |
The wizard will generate two files under your [[documentation:toolbar:preferences#customization_root_path|customization folder]]. These files are auto-discovered via reflection. No manual registration is needed.
=== Runtime Script ===
The runtime script (e.g. ''MyModule.cs'') defines the behaviour of the module.
[ModuleInfo("Custom/My Module", ModuleName = "My Module", Description = "Does something")]
public class MyModule : CGModule
{
// Input and/or output slots, serialized fields, Refresh(), etc.
}
=== Editor Script ===
The editor script (e.g. ''Editor/MyModuleEditor.cs'') defines the UI of the module.
[CustomEditor(typeof(MyModule))]
public class MyModuleEditor : CGModuleEditor
{
// Optional overrides for scene GUI and debug display
}
Every CG module **must** have a matching editor script inheriting ''CGModuleEditor''. Without it, the module will not display correctly in the Curvy Generator Editor.
===== Defining Slots =====
Once your module is created, you will most probably need to define its slots.
[[documentation:generator:modules:start#slots|Slots]] are defined by public fields of type ''CGModuleInputSlot'' or ''CGModuleOutputSlot'', annotated with slot info attributes. You will need to associate each slot with a [[documentation:generator:datatypes|data type]].
Examples of input and output slots:
[HideInInspector]
[InputSlotInfo(typeof(CGPath), Name = "Path", RequestDataOnly = true)]
public CGModuleInputSlot InPath = new CGModuleInputSlot();
[HideInInspector]
[OutputSlotInfo(typeof(CGPath), Name = "Path", DisplayName = "Rasterized Path")]
public CGModuleOutputSlot OutPath = new CGModuleOutputSlot();
=== Slot Info Properties ===
^ Property ^ Default ^ Description ^
| DataType | - | The [[documentation:generator:datatypes|CGData]] subclass this slot accepts/produces. Required. |
| Name | Field name | Internal name used for serialization and linking. |
| DisplayName | Name | Name shown in the UI. |
| Tooltip | null | Hover tooltip on the slot. |
| Array | false | Whether the slot accepts/produces an array of data. |
| ArrayType | Normal | ''Normal'' = multi-link array. ''Hidden'' = array but single-link in UI. |
=== InputSlotInfo-specific ===
^ Property ^ Default ^ Description ^
| RequestDataOnly | false | Slot requests data from on-request modules. See [[documentation:generator:advancedconcepts#on-request_modules|Advanced Concepts]]. |
| Optional | false | Slot does not need to be linked for the module to be configured. |
| ModifiesData | false | Module alters the input data. When set to false, the module will clone the data before passing it, so the original stays intact. |
===== Defining Module Settings =====
Similar to Inspectors, module UIs show serialized fields as Settings.
When a module setting changes, set ''Dirty = true'' so the generator knows to reprocess the module:
[SerializeField]
private int m_Resolution = DefaultResolution;
public int Resolution
{
get => m_Resolution;
set
{
if (m_Resolution != value)
{
m_Resolution = value;
Dirty = true;
}
}
}
You can add one of many attributes on your serialized fields to control their display. This allows for module UI customization without having to write complex code in the module's editor script. Such attributes are:
^ Attribute ^ Purpose ^
| [Tab] | Groups fields under a tab. |
| [Section] | Groups fields under a collapsible section. |
| [RangeEx] | Show a float/int slider. |
| [FieldCondition] | Shows/hides the field based on some condition. |
| [Label] | Sets a label and optionally a tooltip. |
===== Defining Processing Types =====
Now is the time to define the module's behaviour.
Choose one of three processing strategies. See [[documentation:generator:advancedconcepts#module_processing_types|Advanced Concepts]] for full details.
=== Normal Module (default) ===
Override ''Refresh()''. Called each generator pass for dirty modules.
public override void Refresh()
{
base.Refresh();
CGPath path = InPath.GetData(out bool isDisposable);
// data processing
// writing output data if any (see section below)
if (isDisposable)
path.Dispose();
}
=== On-Request Module ===
Implement ''IOnRequestProcessing''. Replace ''Refresh()'' with ''OnSlotDataRequest()''.
public class MyModule : CGModule, IOnRequestProcessing
{
public CGData[] OnSlotDataRequest(
CGModuleInputSlot requestedBy,
CGModuleOutputSlot requestedSlot,
params CGDataRequestParameter[] requests)
{
CGDataRequestRasterization raster =
GetRequestParameter(ref requests);
// ... compute data based on requests ...
return new CGData[] { result };
}
}
=== No-Processing Module ===
Implement ''INoProcessing''. No data processing, used for utility modules like the Note module.
public class MyModule : CGModule, INoProcessing { }
===== Reading Input Data =====
To read data from input slots inside ''Refresh()'' (or ''OnSlotDataRequest()'' for on-request modules):
=== Single data ===
CGPath path = InPath.GetData(out bool isDisposable);
=== Array data ===
List meshes = InVMeshArray.GetAllData(out bool isDisposable);
=== With request parameters (for on-request modules) ===
CGPath path = InPath.GetData(
out bool isDisposable,
new CGDataRequestRasterization(from, length, resolution, angle, mode)
);
The ''isDisposable'' output tells you whether you own the returned data and should dispose it when done. See [[documentation:generator:advancedconcepts#data_disposal_pooling|Advanced Concepts]].
===== Writing Output Data =====
To set your output slot's data, use one of these methods:
^ Method ^ Usage ^
| ''SetDataToElement(data)'' | Single-element output (most common). |
| ''SetDataToCollection(array)'' | Multi-element output (for array slots). |
| ''ClearData()'' | Empty output (module is not configured or has nothing to produce). |
When you call any of these, the previous data on the slot is automatically disposed.
===== Using Custom Data Types =====
If you need a custom [[documentation:generator:datatypes|data type]]:
* Inherit from the most appropriate base (''CGData'', ''CGShape'', ''CGPath'', etc.).
* Decorate with ''[CGDataInfo(r, g, b)]'' to define the associated color in the CG Editor.
* If allocating pooled arrays, override ''Dispose(bool)'' to free them.
* Add the type to ''link.xml'' (see [[https://docs.unity3d.com/6000.3/Documentation/Manual/managed-code-stripping.html|Managed code stripping]]) to avoid it being stripped from builds.
===== Customizing Module UI =====
The editor script controls the module's visual feedback. Override these methods as needed:
^ Method ^ When Called ^
| ''OnModuleSceneGUI()'' | Every Scene repaint. Use for custom scene handles. |
| ''OnModuleSceneDebugGUI()'' | Scene repaint when **Show Debug Visuals** is active. Set ''HasDebugVisuals = true'' in ''OnEnable()'' to enable. |
| ''OnModuleDebugGUI()'' | Inspector repaint. Use for displaying data stats. |
| ''OnCustomInspectorGUI()'' | After the default inspector draws. Use for extra inspector UI. |
| ''OnReadNodes()'' | When the inspector node tree is built. Use to add/remove tabs or sections dynamically. |
Example - showing point count in the inspector debug panel:
[CustomEditor(typeof(MyModule))]
public class MyModuleEditor : CGModuleEditor
{
public override void OnModuleDebugGUI()
{
if (Target.OutPath.Data.Length == 0)
return;
EditorGUILayout.LabelField($"Points: {Target.OutPath.Data[0].Count}");
}
}
===== Tips =====
* **Read existing module implementations.** ''ModifierTRSMesh'' is a simple normal module. ''ConformPath'' is a simple on-request module. Use them as reference.
* **Pool large arrays.** For performance reasons, use ''ArrayPools.Vector3.Allocate(count)'' etc. for [[documentation:generator:advancedconcepts#data_disposal_pooling|pooling]] and free them in ''Dispose(bool)'' of custom CGData subclasses.
* **Ask on the [[https://forum.curvyeditor.com|forum]]** or read the [[https://api.curvyeditor.com/|API reference]] if you get stuck.