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:
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)orVisualizer(space_dim=2)), the unmodified left-button drag automatically usesXY_PLANEinstead 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):
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¶
- Active Elements Overview — common behaviour, handler contract, writing custom active elements
- Object Interaction — low-level interaction API
act_point.py— full working example with projection lines