RFDT Solver and WiTwin Simulator are undergoing internal testing as we prepare for public release. The functions are limited. Sign up to receive updates.

Extension System

File structure and manifest configuration for WiTwin extensions

Overview#

The Extension System allows you to extend WiTwin Studio with custom panels, components, nodes, console commands, file loaders, and geometry generators. Extensions are Python packages that integrate seamlessly with the editor's UI and backend.

Extension Structure#

my_extension/
├── manifest.json       # Extension metadata (required)
├── __init__.py        # Python entry point
├── panels.py          # Custom panels
├── components.py      # Custom components
├── nodes.py           # Custom nodes
└── settings.py        # Custom settings

Manifest File#

The manifest.json file is required for every extension and defines its metadata:

{
  "id": "my_extension",
  "name": "My Extension",
  "version": "1.0.0",
  "description": "Description of my extension",
  "author": "Your Name",
  "dependencies": ["numpy", "scipy"],
  "entry_point": "__init__.py"
}

Manifest Fields#

FieldRequiredDescription
idYesUnique identifier (lowercase, no spaces)
nameYesDisplay name shown in UI
versionYesSemantic version (e.g., "1.0.0")
descriptionNoShort description of the extension
authorNoAuthor name or organization
dependenciesNoPython packages to auto-install via pip
entry_pointNoPython entry file (default: __init__.py)

Example Manifest#

{
  "id": "rf_analyzer",
  "name": "RF Analyzer",
  "version": "1.0.0",
  "description": "RF signal analysis tools for wireless simulation",
  "author": "WiTwin Team",
  "dependencies": ["numpy", "scipy", "scikit-rf"]
}

Extension Loading#

When WiTwin starts, extensions are loaded in this order:

  1. Server discovers extensions in extensions/ directory
  2. Manifest is read and validated
  3. Dependencies are installed via pip (if specified)
  4. Entry point module is executed
  5. Decorated classes are auto-registered (components, panels, settings, nodes)

Entry Point#

The __init__.py file is the entry point for your extension. Import all your custom classes here:

# __init__.py
from .components import MyComponent, AnotherComponent
from .panels import MyPanel
from .nodes import MyNode
from .settings import MySettings
 
# All decorated classes are auto-registered on import

Extension Panel UI#

The Extensions panel in the editor shows:

  • List of installed extensions
  • Enable/disable toggle per extension
  • Extension status indicator (loaded, error)
  • Reload button to refresh extensions
  • Settings link (if extension has settings)

What You Can Customize#

Complete Extension Example#

File Structure#

rf_analyzer/
├── manifest.json
├── __init__.py
├── components.py
└── panels.py

manifest.json#

{
  "id": "rf_analyzer",
  "name": "RF Analyzer",
  "version": "1.0.0",
  "description": "RF signal analysis tools",
  "dependencies": ["numpy", "scipy"]
}

init.py#

from .components import RFAnalyzerComponent
from .panels import RFToolsPanel

components.py#

from witwin.components import Component, component, button, float_field
from witwin import Results, Console
 
@component(name="RFAnalyzer")
class RFAnalyzerComponent(Component):
    frequency = float_field(2.4e9, min=1e6, max=100e9)
    bandwidth = float_field(20e6, min=1e3, max=1e9)
 
    @button(display_name="Analyze Spectrum")
    def analyze(self):
        Console.log(f"Analyzing at {self.frequency/1e9:.2f} GHz")
        # Perform analysis...
        Results.plot(freqs, power, title="Spectrum")
        Results.commit(tag="spectrum")
        return "Analysis complete"

panels.py#

from witwin.panels import Panel, panel
from witwin import Console
 
@panel(name="RF Tools", icon="radio")
class RFToolsPanel(Panel):
    def get_ui(self):
        return {
            "type": "container",
            "children": [
                {"type": "label", "text": "RF Analysis Tools"},
                {"type": "button", "label": "Scan All", "action": "scan_all"}
            ]
        }
 
    def scan_all(self):
        Console.log("Scanning all RF components...")
        return {"status": "scanning"}