simvx.graphics.draw2d_transform

2D affine transform and clip scopes for Draw2D.

Provides the transform composition every drawing primitive draws through, the screen-pinned state of the scope those primitives draw into, and the two scopes drawing code opens over them: :meth:Draw2DTransformMixin.transform and

meth:

Draw2DTransformMixin.clip, both context managers.

The bare push_* / pop_* pairs stay because the frame driver opens a scope in one call and closes it in another as it walks the tree, which no context manager can express. Drawing code uses the scopes.

Screen placement belongs to a node, never to one drawing call: the retained 2D pipeline captures a node’s drawing free of camera and parent and bakes its placement in afterwards, so a single node cannot have some of its operations pinned to the screen and the rest in the world. A CanvasLayer opens the screen scope for its whole subtree through :meth:push_screen_transform, and every op emitted inside it is tagged with it.

Module Contents

Classes

Draw2DTransformMixin

Mixin providing the 2D affine transform stack and drawing scopes for Draw2D.

Functions

map_rect

The whole-pixel box covering rect (x, y, w, h) after affine.

API

simvx.graphics.draw2d_transform.map_rect(affine: tuple, x: float, y: float, w: float, h: float) → tuple[int, int, int, int]

The whole-pixel box covering rect (x, y, w, h) after affine.

Scissor rects are axis-aligned, so a rect under a rotation clips to the box around its corners.

class simvx.graphics.draw2d_transform.Draw2DTransformMixin

Mixin providing the 2D affine transform stack and drawing scopes for Draw2D.

classmethod push_transform(a, b, c, d, tx, ty)

Push a 2D affine transform, composing with current.

classmethod pop_transform()

Pop the last transform, restoring the previous one.

classmethod push_screen_transform(a, b, c, d, tx, ty)

Push current transform and SET (not compose) the given affine, pinned to the screen.

The active Camera2D is bypassed and replaced by (a, b, c, d, tx, ty). This is the screen-pinned :class:~simvx.core.nodes_2d.canvas.CanvasLayer scope (follow_viewport=False): the layer’s offset/rotation/scale_val apply in screen space, independent of the world camera, and every op emitted until the matching :meth:pop_transform is tagged screen-pinned. An unconfigured layer passes the identity row, which pins without moving anything.

classmethod push_node_transform(a, b, c, d, tx, ty)

Push a node’s own world affine, composed onto the canvas scope.

A node drawing in its local space pushes its world affine here. It composes onto the scope (the camera, or a CanvasLayer’s affine) rather than onto the current transform, so a node drawn from inside another node’s draw lands where it is instead of being offset again by its host. Clips pushed while it is active map through it.

classmethod transform(a, b, c, d, tx, ty)

Draw through an affine, and restore what was there afterwards.

x' = a*x + b*y + tx, y' = c*x + d*y + ty, composed onto whatever transform is already active::

with canvas.transform(cos_r, -sin_r, sin_r, cos_r, x, y):
    canvas.rect((-w / 2, -h / 2), (w, h), fill=colour)

Weights expand before the transform applies, so a stroke width inside the scope is in the scope’s own units and scales with it.

classmethod clip(rect)

Draw with everything outside rect scissored away, then restore.

rect is (x, y, w, h) in the drawing code’s own space; it maps through the active transform to the box it covers. Nested scopes intersect, so an inner clip can only ever shrink an outer one::

with canvas.clip((0, 0, w, h)):
    canvas.text(line, (4, 4), fill=colour)