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¶
Mixin providing the 2D affine transform stack and drawing scopes for Draw2D. |
Functions¶
The whole-pixel box covering rect |
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)afteraffine.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.CanvasLayerscope (follow_viewport=False): the layer’soffset/rotation/scale_valapply in screen space, independent of the world camera, and every op emitted until the matching :meth:pop_transformis 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
rectscissored away, then restore.rectis(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)