Skip to content

ActPoint

ActPoint is a self-registering interactive point that can be dragged with the mouse. It creates a Point geometry entity and registers its own interaction handler automatically. In 3D it exposes four standard drag-mode triggers; in 2D (space_dim=2) the unmodified drag defaults to the XY plane.

Quick Start

from pytanga.viz import ActPoint, Visualizer
from pytanga.geometry import Point

viz = Visualizer()
ap = ActPoint(Point(0, 0, 2))
viz.add(ap, color="#ff4444")
viz.run()

The point can be dragged with the left mouse button. Modifier keys switch the drag constraint plane:

Modifier Drag Plane
(none) View plane (screen-parallel)
Shift XY plane (Z-locked)
Ctrl XZ plane (Y-locked)
Ctrl+Shift YZ plane (X-locked)

Constructor

ActPoint(
    x: float | Point,
    y: float = 0.0,
    z: float = 0.0,
    *,
    drag_mode: DragMode | None = None,
    act_style: ActPointStyle | None = None,
    handler: ActHandler | None = None,
    on_drag_start: ActEventHandler | None = None,
    on_drag_end: ActEventHandler | None = None,
    on_click: ActClickHandler | None = None,
)
Parameter Type Default Description
x float \| Point (required) X coordinate, or a Point instance (then y/z are ignored)
y float 0.0 Y coordinate
z float 0.0 Z coordinate
drag_mode DragMode \| None None Constrains the unmodified left-button drag to a single plane
act_style ActPointStyle \| None None Hover highlighting / interactive feedback
handler ActHandler \| None None Move-phase callback invoked before the default movement
on_drag_start ActEventHandler \| None None Callback invoked when a drag starts
on_drag_end ActEventHandler \| None None Callback invoked when a drag ends
on_click ActClickHandler \| None None Callback invoked when the point is clicked

The point's visual style (colour, size, opacity) and an optional text label are set via viz.add(ap, color=..., style=..., label=...), not on the constructor.

Drag Mode

Pass drag_mode= to constrain the unmodified left-button drag to a single plane instead of the four standard modifier-switched planes. This keeps the point on that plane throughout the gesture:

from pytanga.viz import DragMode

ap = ActPoint(Point(1.0, 2.0, 0.0), drag_mode=DragMode.XY_PLANE)

When drag_mode is set, the primary unmodified left-button trigger uses that plane and no modifier-based alternate triggers are registered.

When drag_mode is omitted (the default None), the behaviour depends on the scene dimension:

  • In a 3D visualizer, the four standard triggers remain available, as shown in the table above.
  • In a 2D visualizer (VisualizerApp(space_dim=2) or Visualizer(space_dim=2)), the unmodified left-button drag automatically uses XY_PLANE instead of the view plane. This prevents an unmodified drag on the view plane of a tilted camera from changing the point's Z coordinate.

Ideal Drag Anchor

When a drag starts, the point is grabbed at its ideal anchor — the point's centre — rather than the raw ray/mesh hit point. ActPoint.drag_anchor returns self._point, so the mesh-surface offset no longer appears: because the rendered point is a sphere, the raw hit lands on the sphere's surface (up to its radius away from the centre), and that offset used to leak into the drag as a spurious out-of-plane component — e.g. a growing z-component when dragging on XY_PLANE while looking down the z-axis.

The pixel→world drag scale is re-anchored onto the same ideal point: the frontend buffers raw pixel deltas until the backend replies with the anchor, then converts them to world space once at the anchor's depth. For a point this is hardly noticeable, but it is the mechanism that keeps positive-dimensional actives (a circle drawn as a torus, a sphere) dragging at the correct speed.

The hook is the extension point for future active entities: a circle would anchor on the nearest point of the ideal circle (so the torus tube radius never leaks into the drag), and a sphere on its centre.

Labels

Pass label= to viz.add() to attach a text label to the point, just like any other entity (supporting label_style, attach_to, and parent_id):

ap = ActPoint(Point(0, 0, 2))
eid = viz.add(ap, color="#ff4444", label="P")

Removing the point also removes its attached label.

Custom Handler

from pytanga.viz import ActHandler

async def my_handler(event, ap):
    # event: DragEvent — carries world_position (Point), world_delta (Direction),
    #        camera (Camera), drag_mode, modifiers, etc.
    # ap:    ActPoint — has .point (current Point), .entity_id, .viz_handle

    # Update other scene objects based on the drag:
    new_pos = event.world_position
    ap.viz_handle.update_entity(some_line_id, Line.from_points(ap.point, new_pos))

    return False   # let ActPoint move the point and flush
    # return True  # fully handled; no default move, no automatic flush

ap = ActPoint(Point(0, 0, 2), handler=my_handler)

Drag Lifecycle Handlers

The handler callback runs on every drag move. To observe the start and end of a drag, pass on_drag_start and/or on_drag_end:

async def on_start(event, ap):
    # Drag began — e.g. remember the initial position or highlight the point.

async def on_end(event, ap):
    # Drag finished — e.g. commit the final position or clear the highlight.

ap = ActPoint(
    Point(0, 0, 2),
    handler=my_handler,
    on_drag_start=on_start,
    on_drag_end=on_end,
)

These lifecycle handlers receive the same (event, ap) arguments as the move handler, but their return value is ignored — they are pure notifications and never override the default movement. event.event_type is InteractionEventType.DRAG_START / DRAG_END respectively.

Click Handler

Pass on_click to be notified when the point is clicked (a press-and-release without moving the pointer):

async def on_click(event, ap):
    # event: ClickEvent — event.world_position is the point's centre (the
    #        ideal anchor), not the ray/mesh hit; also carries
    #        screen_position, world_normal, modifiers, and camera.

ap = ActPoint(Point(0, 0, 2), on_click=on_click)

Providing on_click also registers a CLICK trigger so the frontend emits interaction:click for the point. Like the drag lifecycle handlers, its return value is ignored.

Properties

Property Type Description
point Point Current position (updated on every drag move)
entity Point Same as point — the geometry entity rendered in the scene
entity_id str The scene entity ID assigned by the visualizer
viz_handle VizSceneHandle \| None Handle for scene operations (update, flush, etc.)
interaction_config InteractionConfig Drag triggers (standard four, or a single drag_mode-constrained trigger) with throttle_ms=40

Interaction Configuration

In a 3D visualizer with drag_mode=None, the config uses four drag triggers on the left mouse button:

InteractionConfig(
    enabled=True,
    triggers=[
        InteractionTrigger(event_type=DRAG, mouse_button=LEFT,
                          drag_mode=VIEW_PLANE),
        InteractionTrigger(event_type=DRAG, mouse_button=LEFT,
                          modifiers={SHIFT}, drag_mode=XY_PLANE),
        InteractionTrigger(event_type=DRAG, mouse_button=LEFT,
                          modifiers={CTRL}, drag_mode=XZ_PLANE),
        InteractionTrigger(event_type=DRAG, mouse_button=LEFT,
                          modifiers={CTRL, SHIFT}, drag_mode=YZ_PLANE),
    ],
    throttle_ms=40,
)

When drag_mode is set, the config instead registers a single unmodified left-button trigger with that mode:

InteractionConfig(
    enabled=True,
    triggers=[
        InteractionTrigger(event_type=DRAG, mouse_button=LEFT,
                          drag_mode=XY_PLANE),
    ],
    throttle_ms=40,
)

To customise the triggers further (e.g., use right button instead, or different modifier keys), subclass ActPoint and override interaction_config.

See Also