Custom Nodes
What are Custom Nodes?
Custom nodes allow you to extend GM_Visual with your own Python code. Unlike built-in nodes (which are part of the repository), custom nodes live in a dedicated folder in your user profile. This means:
- No need to modify the GM_Visual source code
- Your nodes persist across updates
- You can share node files with other users simply by copying
.pyfiles
Custom Nodes Folder
The default custom nodes directory is created automatically at installation time:
%APPDATA%\GM_Visual\custom_nodes\
On a typical Windows installation this expands to:
C:\Users\<your_username>\AppData\Roaming\GM_Visual\custom_nodes\
python install.py). If it is missing, run python install.py repair to recreate it.
Folder Structure
Custom nodes are organised the same way as built-in nodes: a subcategory folder inside the root custom nodes directory, containing one .py file per node:
custom_nodes\
├── demo\
│ ├── __init__.py
│ └── hello_custom_node.py ← demo node (auto-generated)
├── my_category\
│ ├── __init__.py
│ ├── my_first_node.py
│ └── my_second_node.py
└── __init__.py
Each subfolder of custom_nodes\ becomes a subcategory visible in the Custom nodes section of the Node Library panel.
Finding Custom Nodes in the Node Library
- Open GM_Visual and look at the Node Library tab in the right panel.
- In the Category dropdown, select Custom nodes.
- The subcategories that correspond to your subfolders appear below.
- Expand a subcategory to see the available nodes.
- Drag a node button onto the canvas — or double-click it — to add it.
Creating a Custom Node
Each node file must follow this template. The file name becomes the node identifier.
Minimum template
"""
MyNode - short description.
"""
from core import NodeBase, PinType
class MyNode(NodeBase):
"""One-line description."""
def __init__(self, node_id: str):
super().__init__(
node_type="MyNode",
name="My Node",
description="What this node does.",
version="1.0.0",
author="Your Name",
category_tags=["custom"]
)
self.node_id = node_id
self._initialize_pins()
def _initialize_pins(self) -> None:
self.add_input_pin("value_in", PinType.FLOAT, description="Input value")
self.add_output_pin("value_out", PinType.FLOAT, description="Output value")
def _execute_impl(self) -> None:
val = self.get_input_value("value_in", default=0.0)
self.set_output_value("value_out", val * 2.0)
def get_display_text(self) -> str:
return "My\nNode"
def create_my_node_node(node_id: str) -> MyNode:
"""Factory function - REQUIRED. Name must be: create_{file_name}_node"""
return MyNode(node_id=node_id)
Key rules
- File name: use
snake_case, e.g.my_node.py. This becomes the node identifier used internally. - Factory function: must be named
create_{file_name}_node. Formy_node.pythe function iscreate_my_node_node. - Class: must inherit from
NodeBase(imported fromcore). - Pins: declare all input and output pins inside
_initialize_pins(). - Execution logic: write computation inside
_execute_impl().
Available Pin Types
Import PinType from core and use the following constants (defined in src/core/pin_system.py):
Basic types
| Constant | Value string | Description |
|---|---|---|
PinType.INTEGER | "int" | Integer number |
PinType.FLOAT | "float" | Floating-point number |
PinType.STRING | "str" | Text string |
PinType.BOOLEAN | "bool" | Boolean (True / False) |
PinType.ANY | "any" | Accepts any data type |
PinType.NONE | "none" | No data — trigger / event pin only |
Container types
| Constant | Value string | Description |
|---|---|---|
PinType.LIST | "list" | Generic Python list |
PinType.DICT | "dict" | Generic Python dictionary |
PinType.LIST_OF_FLOATS | "list_of_floats" | List of float values |
PinType.LIST_OF_INTS | "list_of_ints" | List of integer values |
PinType.LIST_OF_STRINGS | "list_of_strings" | List of strings |
PinType.LIST_MIXED | "list_mixed" | List of mixed types |
Signal / domain types
| Constant | Value string | Description |
|---|---|---|
PinType.DFTM | "dftm" | DataFrame Time — time-series signal (GM_modules.Signals) |
PinType.DFTB | "dftb" | DataFrame Table — tabular signal data (GM_modules.Signals) |
PinType.MATERIAL | "material" | Material object (GM_modules.Materials) |
Ansys DPF types
| Constant | Description |
|---|---|
PinType.ANSYS_DPF_MODEL | Ansys DPF Model object |
PinType.ANSYS_DPF_MESHED_REGION | Ansys DPF Meshed Region object |
PinType.ANSYS_DPF_FIELDS_CONTAINER | Ansys DPF Fields Container object |
PinType.ANSYS_DPF_TIME_SCOPING | Ansys DPF Time Scoping object |
PinType.ANSYS_DPF_SCOPING | Ansys DPF Mesh Scoping object |
PinType.ANY when your node can accept or produce any data type. For most custom nodes, the basic types (INTEGER, FLOAT, STRING, BOOLEAN) and container types (LIST, DICT) are sufficient.
Demo Node
A demo node is automatically generated at:
%APPDATA%\GM_Visual\custom_nodes\demo\hello_custom_node.py
This node outputs the constant string "Hello from Custom Node!" and serves as a working example you can copy and modify.
Troubleshooting
My node does not appear in the library
- Make sure the file is inside a subcategory folder (not directly in
custom_nodes\). - The file name must end in
.pyand must not start with__or end with_gui.py. - Re-select the Custom nodes category in the dropdown to refresh the list.
Error when dropping the node on canvas
- Check that the factory function is correctly named:
create_{file_name}_node. - Make sure the class inherits from
NodeBaseandcoreis importable (GM_Visual must be running from the correct environment). - Look at the terminal / console output for the full Python traceback.
custom_nodes folder is missing
Run the repair command from the GM_Visual directory:
python install.py repair
demo subfolder. It is recreated automatically during repair if deleted.