Customize Components
Create custom components with fields, buttons, and groups
Overview#
Components are the building blocks of scene objects in WiTwin. They define properties that can be edited in the Properties panel and behaviors that can be triggered via buttons.
Basic Component#
Use the @component decorator to create a component:
from witwin.components import Component, component, float_field, bool_field
@component(name="MyComponent")
class MyComponent(Component):
intensity = float_field(1.0, min=0.0, max=10.0)
enabled = bool_field(True)Component Decorator#
@component(
name="ComponentName", # Internal name (required)
display_name="Display Name" # UI display name (optional)
)
class MyComponent(Component):
passField Types#
Numeric Fields#
from witwin.components import float_field, int_field
# Float field with range and step
brightness = float_field(
1.0, # Default value
min=0.0, # Minimum value
max=2.0, # Maximum value
step=0.1, # Step increment
description="Brightness level"
)
# Float field with units
frequency = float_field(
2.4e9,
units=[
{"label": "Hz", "multiplier": 1},
{"label": "MHz", "multiplier": 1e6},
{"label": "GHz", "multiplier": 1e9},
],
default_unit="GHz"
)
# Integer field
count = int_field(10, min=0, max=100, description="Number of samples")Boolean Field#
from witwin.components import bool_field
enabled = bool_field(True, description="Enable this feature")
visible = bool_field(False)String Field#
from witwin.components import string_field
# Text input
name = string_field("Untitled", description="Object name")
# Dropdown selection
mode = string_field("auto", options=["auto", "manual", "advanced"])
# Toggle buttons (enum_toggle)
quality = string_field("medium", options=["low", "medium", "high"], enum_toggle=True)Vector Fields#
from witwin.components import vector2_field, vector3_field, vector4_field
uv_offset = vector2_field([0.0, 0.0])
position = vector3_field([0.0, 0.0, 0.0])
quaternion = vector4_field([0.0, 0.0, 0.0, 1.0])Color Field#
from witwin.components import color_field
# RGBA color (0-1 range)
base_color = color_field([0.5, 0.5, 0.5, 1.0])
highlight = color_field([1.0, 0.0, 0.0, 1.0])Component Reference Field#
from witwin.components import component_field
# Reference to another component
target_mesh = component_field("Mesh", description="Target mesh to render")
material_ref = component_field("Material")Image Field#
from witwin.components import image_field
import numpy as np
preview = image_field(hide_label=True, title="Render Preview")
# Assign image data directly
# Supports: numpy array, torch tensor, PIL Image
# self.preview = np.random.rand(256, 256, 3)List Field#
from witwin.components import list_field, float_field, vector3_field
# List of floats
weights = list_field(float_field(1.0, min=0, max=1))
# List of waypoints
waypoints = list_field(
vector3_field(),
default=[[0, 0, 0], [1, 0, 0]],
description="Path waypoints"
)Plot Field#
from witwin.components import plot_field
from witwin.components.plot import PlotBuilder
output = plot_field(title="Visualization")
def on_value_change(self, field_name, old_val, new_val):
if field_name == 'scale':
x = [1, 2, 3, 4, 5]
y = [i * new_val for i in x]
self.output = PlotBuilder.line(x, y, title="Scaled Data")PlotBuilder Methods
from witwin.components.plot import PlotBuilder
# Line chart
PlotBuilder.line(x, y, title=None, xlabel=None, ylabel=None)
# Scatter plot
PlotBuilder.scatter(x, y, title=None, size=None, color=None)
# Bar chart
PlotBuilder.bar(x, heights, title=None)
# Heatmap/Image
PlotBuilder.imshow(data, title=None)
# Histogram
PlotBuilder.hist(data, bins=30, title=None)Common Field Parameters#
All fields support these common parameters:
field(
default, # Default value
description=None, # Tooltip description
readonly=False, # Prevent editing
title=None, # Override label text
tooltip=None, # Additional tooltip
hide_label=False, # Hide the field label
group=None, # Assign to a group
horizontal_group=None, # Horizontal layout group
no_sync=False, # Don't sync to frontend
)Buttons#
Use the @button decorator to add clickable actions:
from witwin.components import button
@component(name="Physics")
class PhysicsComponent(Component):
velocity = vector3_field([0, 0, 0])
@button(display_name="Reset Velocity")
def reset(self):
self.velocity = [0, 0, 0]
return "Velocity reset"
@button(display_name="Apply Force", description="Apply upward force")
def apply_force(self):
self.velocity[1] += 10
return "Force applied"
@button(display_name="Save", group="Settings")
def save_settings(self):
# Button rendered at end of "Settings" group
passButton Decorator Options#
@button(
display_name="Button Text", # Button label
description=None, # Tooltip
icon=None, # Lucide icon name
style=None, # Button style
group=None # Place in group
)
def method_name(self, params=None):
# params contains additional data from frontend
return "Result message"Field Groups#
Groups organize fields visually in the Properties panel.
Foldout Group (Collapsible)#
from witwin.components import foldout_group, define_group
@component(name="MyComponent")
class MyComponent(Component):
define_group(foldout_group("Settings", collapsed=False))
define_group(foldout_group("Advanced", collapsed=True))
mode = string_field("auto", group="Settings")
quality = float_field(0.5, group="Settings")
debug = bool_field(False, group="Advanced")Tab Group#
from witwin.components import tab_group, define_group
@component(name="Material")
class MaterialComponent(Component):
define_group(tab_group("Appearance", "Colors", "Textures"))
# Fields in tabs use dot notation: "GroupName.TabName"
base_color = color_field([1, 1, 1, 1], group="Appearance.Colors")
emission = color_field([0, 0, 0, 1], group="Appearance.Colors")
albedo = string_field("", group="Appearance.Textures")
normal = string_field("", group="Appearance.Textures")Box Group (Simple Container)#
from witwin.components import box_group, define_group
@component(name="Info")
class InfoComponent(Component):
define_group(box_group("Status", display_name="Current Status"))
state = string_field("Ready", group="Status", readonly=True)
progress = float_field(0.0, group="Status", readonly=True)Toggle Group#
from witwin.components import toggle_group, define_group
@component(name="Debug")
class DebugComponent(Component):
define_group(toggle_group("Debug", enabled=False, display_name="Debug Options"))
log_level = string_field("INFO", group="Debug")
show_bounds = bool_field(False, group="Debug")Conditional Display#
Control field visibility based on other field values:
@component(name="Renderer")
class RendererComponent(Component):
show_advanced = bool_field(False, description="Show advanced options")
# Show when checkbox is True
advanced_option = float_field(
0.5,
show_if="show_advanced",
description="Only visible when Show Advanced is checked"
)
# Hide when checkbox is True
basic_option = string_field(
"simple",
hide_if="show_advanced"
)
# Enable based on condition
quality = string_field("low", options=["low", "medium", "high"])
hdr_enabled = bool_field(
False,
enable_if={"field_name": "quality", "operator": "eq", "value": "high"}
)Condition Operators#
| Operator | Description |
|---|---|
eq | Equals |
neq | Not equals |
gt | Greater than |
lt | Less than |
gte | Greater or equal |
lte | Less or equal |
is_true | Field is truthy |
is_false | Field is falsy |
Value Change Callbacks#
React to field value changes:
@component(name="Processor")
class ProcessorComponent(Component):
input_value = float_field(0.0)
output_value = float_field(0.0, readonly=True)
def on_value_change(self, field_name, old_value, new_value):
if field_name == 'input_value':
result = new_value * 2.0
self.set_field('output_value', result)Component Methods#
class Component:
# Set field value
def set_field(self, field_name, value, silent=False, force=False):
"""Set field value. Use silent=True to skip callbacks."""
pass
# Manual sync to frontend
def update(self, field_name=None):
"""Trigger sync. If field_name is None, syncs all fields."""
pass
# Get referenced component
def get_referenced_component(self, field_name):
"""Get component instance from a component_field."""
pass
# Toggle group state
def is_group_enabled(self, group_name):
"""Check if toggle group is enabled."""
pass
def set_group_enabled(self, group_name, enabled, notify=True):
"""Enable/disable a toggle group."""
pass
# Properties
@property
def owner(self):
"""Get the SceneObject that owns this component."""
pass
@property
def scene(self):
"""Get the Scene this component belongs to."""
passComplete Example#
from witwin.components import (
Component, component, button,
float_field, bool_field, string_field, vector3_field, color_field,
foldout_group, define_group, plot_field
)
from witwin.components.plot import PlotBuilder
from witwin import Console
import numpy as np
@component(name="WaveGenerator")
class WaveGeneratorComponent(Component):
# Define groups
define_group(foldout_group("Parameters"))
define_group(foldout_group("Output", collapsed=True))
# Parameters group
frequency = float_field(1.0, min=0.1, max=10.0, group="Parameters")
amplitude = float_field(1.0, min=0.0, max=5.0, group="Parameters")
wave_type = string_field("sine", options=["sine", "square", "sawtooth"], group="Parameters")
# Output group
preview = plot_field(title="Waveform", group="Output")
current_value = float_field(0.0, readonly=True, group="Output")
@button(display_name="Generate Preview", group="Parameters")
def generate(self):
x = np.linspace(0, 2 * np.pi, 100)
if self.wave_type == "sine":
y = self.amplitude * np.sin(self.frequency * x)
elif self.wave_type == "square":
y = self.amplitude * np.sign(np.sin(self.frequency * x))
else:
y = self.amplitude * (x % (2 * np.pi / self.frequency))
self.preview = PlotBuilder.line(x.tolist(), y.tolist(), title=f"{self.wave_type} wave")
Console.log(f"Generated {self.wave_type} wave")
return "Preview generated"
def on_value_change(self, field_name, old_val, new_val):
if field_name in ['frequency', 'amplitude', 'wave_type']:
Console.log(f"{field_name} changed to {new_val}")