Skip to content

Probe

FluxRender.probes

DataProbe

DataProbe(target_region, target_entity: VectorMathEngine | VectorField | ParticleSystem, measured_property: Property = None)

A measurement instrument that tracks a spatial target and evaluates mathematical properties.

The DataProbe acts as an observer within the simulation. It continuously monitors a specified target region (e.g., an interactive cursor or a moving entity) and queries the underlying mathematical engine. It can track either a specific scalar field property (like divergence or curl) or evaluate the raw 2D vector field at that location. The evaluated results are then dispatched to all registered listener functions via a callback system.

Parameters:

Name Type Description Default
target_region SpatialRegion

The spatial entity to be tracked. This object must provide a mechanism to retrieve its current coordinates (e.g., a center property or method).

required
target_entity VectorMathEngine | VectorField | ParticleSystem

The core mathematical engine or entity responsible for parsing and evaluating the vector field logic at the given coordinates.

required
measured_property Property

The specific mathematical property to measure at the target's location (e.g., Property.DIVERGENCE). If set to None, the probe evaluates the raw underlying vector field, returning a tuple of two values (X and Y vector components).

Important Note on Callback Signatures:

  • If measured_property is None, registered callback functions must accept exactly two positional arguments (e.g., def my_callback(vector_x, vector_y):).
  • If measured_property is provided (evaluating to a scalar), registered callback functions must accept exactly one positional argument (e.g., def my_callback(value):).

Defaults to None.

None
Example

Creating a DataProbe to track velocity at cursor position:

import FluxRender as fr

# [Initialization of scene and coordinate system]

math_engine = fr.VectorMathEngine(scene=scene, primary_vector_function=lambda x, y: (y, -x))
interactive_cursor = fr.CursorRegion(radius=30) # A source of coordinates that moves with user input

# Define a function that will be called by DataProbe on each frame (if the region is active)
def velocity_callback(velocity_value):
    print(f"Current velocity at cursor: {velocity_value}", end="\r")

probe = fr.DataProbe(
    target_region = interactive_cursor,
    target_entity = math_engine, # You can also use a VectorField or ParticleSystem instance here since they also implement the necessary evaluation interface
    measured_property = fr.Property.VELOCITY
)
probe.add_listener(velocity_callback) # Registers the callback to receive velocity updates at the cursor's position

A comprehensive example of using DataProbe to display velocity:

import FluxRender as fr

scene = fr.create_workspace()

def swirling_vortex(x, y):
    return y, -x

vector_field = fr.VectorField(swirling_vortex)

# To pass data (coordinates) to DataProbe we need a region
cursor = fr.CursorRegion(always_active=True)

# We are creating a DataProbe that will measure velocity
probe = fr.DataProbe(
    target_region=cursor,
    target_entity=vector_field,
    measured_property=fr.Property.VELOCITY
)

# We display velocity using DynamicText
text = fr.DynamicText(text=lambda: f"Velocity: {probe.value}",)


scene.add(vector_field,cursor, probe, text)
scene.run()

Source code in FluxRender/probes.py
def __init__(self, target_region, target_entity: me.VectorMathEngine | en.VectorField | en.ParticleSystem, measured_property: Property = None):
    """
    Args:
        target_region (SpatialRegion): The spatial entity to be tracked. This object must provide
            a mechanism to retrieve its current coordinates (e.g., a `center` property or method).
        target_entity (me.VectorMathEngine | en.VectorField | en.ParticleSystem): The core mathematical engine or entity responsible for parsing and
            evaluating the vector field logic at the given coordinates.
        measured_property (Property, optional): The specific mathematical property to measure
            at the target's location (e.g., Property.DIVERGENCE). If set to None, the probe evaluates
            the raw underlying vector field, returning a tuple of two values (X and Y vector components).

            **Important Note on Callback Signatures:**

            - If `measured_property` is None, registered callback functions must accept exactly
                two positional arguments (e.g., `def my_callback(vector_x, vector_y):`).
            - If `measured_property` is provided (evaluating to a scalar), registered callback
                functions must accept exactly one positional argument (e.g., `def my_callback(value):`).

            Defaults to None.

    Example:
        Creating a DataProbe to track velocity at cursor position:
        ```python
        import FluxRender as fr

        # [Initialization of scene and coordinate system]

        math_engine = fr.VectorMathEngine(scene=scene, primary_vector_function=lambda x, y: (y, -x))
        interactive_cursor = fr.CursorRegion(radius=30) # A source of coordinates that moves with user input

        # Define a function that will be called by DataProbe on each frame (if the region is active)
        def velocity_callback(velocity_value):
            print(f"Current velocity at cursor: {velocity_value}", end="\\r")

        probe = fr.DataProbe(
            target_region = interactive_cursor,
            target_entity = math_engine, # You can also use a VectorField or ParticleSystem instance here since they also implement the necessary evaluation interface
            measured_property = fr.Property.VELOCITY
        )
        probe.add_listener(velocity_callback) # Registers the callback to receive velocity updates at the cursor's position
        ```

        A comprehensive example of using DataProbe to display velocity:
        ```python
        import FluxRender as fr

        scene = fr.create_workspace()

        def swirling_vortex(x, y):
            return y, -x

        vector_field = fr.VectorField(swirling_vortex)

        # To pass data (coordinates) to DataProbe we need a region
        cursor = fr.CursorRegion(always_active=True)

        # We are creating a DataProbe that will measure velocity
        probe = fr.DataProbe(
            target_region=cursor,
            target_entity=vector_field,
            measured_property=fr.Property.VELOCITY
        )

        # We display velocity using DynamicText
        text = fr.DynamicText(text=lambda: f"Velocity: {probe.value}",)


        scene.add(vector_field,cursor, probe, text)
        scene.run()
        ```
    """

    self.target_region = target_region
    self.math_engine = target_entity
    self.measured_property = measured_property

    self.value = 0 if measured_property is not None else (0, 0)  # Initialize value based on property type

    self._callbacks = []

add_listener

add_listener(callback_function)

Adds a function that will be called on every frame update with the latest measurement results. The callback function must have a specific signature based on whether a property is being measured or not:

  • If measured_property is None: The callback must accept exactly two positional arguments or *args (e.g., def my_callback(vector_x, vector_y):).
  • If measured_property is set: The callback must accept exactly one positional argument (e.g., def my_callback(value):).
Source code in FluxRender/probes.py
def add_listener(self, callback_function):
    """
    Adds a function that will be called on every frame update with the latest measurement results.
    The callback function must have a specific signature based on whether a property is being measured or not:

    - If `measured_property` is None: The callback must accept exactly two positional arguments or *args (e.g., `def my_callback(vector_x, vector_y):`).
    - If `measured_property` is set: The callback must accept exactly one positional argument (e.g., `def my_callback(value):`).
    """

    if not callable(callback_function):
        _fatal_error("Listener must be a callable function.", "TypeError")

    function_name = getattr(callback_function, '__name__', str(callback_function))
    expected_parameters_count = _count_function_parameters(callback_function)

    if self.measured_property is None:
        if expected_parameters_count != 2 and expected_parameters_count != float('inf'):
            _fatal_error(
                f"Callback function '{function_name}' must accept exactly two arguments "
                f"(vector_x, vector_y) or *args when measured_property is None. "
                f"Currently it accepts {expected_parameters_count}.",
                "ValueError"
            )
    else:
        if expected_parameters_count != 1 and expected_parameters_count != float('inf'):
            _fatal_error(
                f"Callback function '{function_name}' must accept exactly one argument "
                f"(value) or *args when measured_property is set. "
                f"Currently it accepts {expected_parameters_count}.",
                "ValueError"
            )

    self._callbacks.append(callback_function)