Initial version

This commit is contained in:
Ruben van de Ven 2024-10-23 16:39:16 +02:00
commit 01e6b0f672
6 changed files with 280 additions and 0 deletions

48
README.md Normal file
View file

@ -0,0 +1,48 @@
# Pyglet CornerPin
This is a little utility that adds corner pin transforms to a pyglet window.
## Installation
```bash
pip install pyglet-cornerpin
```
## Usage
Create instance for a window, and register the event handlers for dragging the pins.
```python
pins = PygletCornerPin(window)
# event handlers for dragging:
window.push_handlers(pins)
```
Then you can draw the pins in your `on_draw()` event.
```python
@window.event
def on_draw():
window.clear()
...
# draw corner pins
pins.draw()
```
Optionally you can provide initial positions for the pins.
```python
corners = [
(0, 0), # Bottom left
(window.width), # Bottom right
(0, window.height), # Top left
(window.width, window.height), # Top right
]
pins = PygletCornerPin(window, corners)
```
Run [pattern.py](examples/pattern.py) in the examples folder for a demo.

45
examples/pattern.py Normal file
View file

@ -0,0 +1,45 @@
import pyglet
from pyglet_corner_pin import PygletCornerPin
window = pyglet.window.Window()
# Generate a test pattern
batch = pyglet.graphics.Batch()
pattern = []
d = 20
y_steps = int(window.height / d)
x_steps = int(window.width / d)
colors = [
(255,255,255,255),
(180,180,180,180)
]
for i in range(y_steps+3):
y = int(i * d)
for j in range(x_steps+3):
x = int(j * d)
pattern.append(
pyglet.shapes.Rectangle(x,y, d,d, colors[(i+j) % len(colors)], batch=batch)
)
@window.event
def on_key_press(symbol, modifiers):
if symbol == pyglet.window.key.Q or symbol == pyglet.window.key.ESCAPE:
window.close()
exit()
pins = PygletCornerPin(window)
# event handlers for dragging:
window.push_handlers(pins)
@window.event
def on_draw():
window.clear()
# draw test pattern
batch.draw()
# draw corner pins
pins.draw()
pyglet.app.run()

17
poetry.lock generated Normal file
View file

@ -0,0 +1,17 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "pyglet"
version = "2.0.18"
description = "pyglet is a cross-platform games and multimedia package."
optional = false
python-versions = ">=3.8"
files = [
{file = "pyglet-2.0.18-py3-none-any.whl", hash = "sha256:e592952ae0297e456c587b6486ed8c3e5f9d0c3519d517bb92dde5fdf4c26b41"},
{file = "pyglet-2.0.18.tar.gz", hash = "sha256:7cf9238d70082a2da282759679f8a011cc979753a32224a8ead8ed80e48f99dc"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "872c0fb02f6495f3797bcf31fc98f0616221db89bc16b072bcefd0227543cc0a"

View file

@ -0,0 +1 @@
from .cornerpin import PygletCornerPin

View file

@ -0,0 +1,154 @@
"""
This little library provides functions to add corner pin functionality
into python in general, and pyglet specifically.
Long time ago I used a set of javascript functions from Cihad Turhan (see
https://stackoverflow.com/a/30646172), which I now ported to python.
These computations might be faster with numpy, but for me these run only
incidently so won't really hurt performance.
"""
import pyglet
from typing import Optional
import logging
def adj(m): # Compute the adjugate of m
return [
m[4]*m[8]-m[5]*m[7], m[2]*m[7]-m[1]*m[8], m[1]*m[5]-m[2]*m[4],
m[5]*m[6]-m[3]*m[8], m[0]*m[8]-m[2]*m[6], m[2]*m[3]-m[0]*m[5],
m[3]*m[7]-m[4]*m[6], m[1]*m[6]-m[0]*m[7], m[0]*m[4]-m[1]*m[3]
]
def multmm(a, b) -> list[float]: # multiply two matrices
c = [0.]*9
for i in range(3):
for j in range(3):
cij = 0
for k in range(3):
cij += a[3*i + k]*b[3*k + j]
c[3 * i + j] = cij
return c
def multmv(m, v): # multiply matrix and vector
return [
m[0]*v[0] + m[1]*v[1] + m[2]*v[2],
m[3]*v[0] + m[4]*v[1] + m[5]*v[2],
m[6]*v[0] + m[7]*v[1] + m[8]*v[2]
]
# def pdbg(m, v) :
# r = multmv(m, v)
# return r + " (" + r[0]/r[2] + ", " + r[1]/r[2] + ")"
def basisToPoints(x1, y1, x2, y2, x3, y3, x4, y4) :
m = [
x1, x2, x3,
y1, y2, y3,
1, 1, 1
]
v = multmv(adj(m), [x4, y4, 1])
return multmm(m, [
v[0], 0, 0,
0, v[1], 0,
0, 0, v[2]
])
def general2DProjection(
x1s, y1s, x1d, y1d,
x2s, y2s, x2d, y2d,
x3s, y3s, x3d, y3d,
x4s, y4s, x4d, y4d
) :
s = basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s)
d = basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d)
return multmm(d, adj(s))
def transform2d(w, h, x1, y1, x2, y2, x3, y3, x4, y4):
t = general2DProjection(0, 0, x1, y1, w, 0, x2, y2, 0, h, x3, y3, w, h, x4, y4)
for i in range(9):
t[i] = t[i] / t[8]
t = [t[0], t[3], 0, t[6],
t[1], t[4], 0, t[7],
0 , 0 , 1, 0 ,
t[2], t[5], 0, t[8]]
return t
# Below are all pyglet specific functions
class PygletCornerPin(pyglet.event.EventDispatcher):
def __init__(self, window: pyglet.window.Window, corners: Optional[list[list[float]]] = None) -> None:
"""
Creates CornerPin utility for a given Pyglet window. If not given, corners default to the
bottom left, bottom right, top left, top right corners of the window (i.e. no transform)
Do not forget to register event handlers by running `window.register_handlers(pins)`.
"""
self.window = window
self.corners = corners if corners else [
[0, 0],
[self.window.width, 0],
[0, self.window.height],
[self.window.width, self.window.height],
]
self.batch = pyglet.graphics.Batch()
self.dragging: Optional[int] = None
self.handles = [
pyglet.shapes.Arc(c[0],c[1],20, thickness=2, color=(0,0,255,255), batch=self.batch) for c in self.corners
]
def update_handles(self):
for i, c in enumerate(self.corners):
self.handles[i].x = c[0]
self.handles[i].y = c[1]
def draw(self):
"""
Draw the handles in the on_draw loop
"""
self.batch.draw()
def transform2d_matrix(self, x1, y1, x2, y2, x3, y3, x4, y4):
"""
Calculate the transform matrix
"""
t = transform2d(
self.window.width,
self.window.height,
x1, y1, x2, y2, x3, y3, x4, y4 )
return pyglet.math.Mat4(t)
# event handlers
def on_mouse_press(self, x, y, button, modifiers):
r = 20
currentcorner = None
dist = r * r
for i in range(4):
dx = x - self.corners[i][0]
dy = y - self.corners[i][1]
if dist > dx**2 + dy ** 2:
dist = dx**2 + dy ** 2
currentcorner = i
self.dragging = currentcorner
return pyglet.event.EVENT_HANDLED if self.dragging else pyglet.event.EVENT_UNHANDLED
def on_mouse_release(self, x, y, button, modifiers):
if self.dragging is None:
return pyglet.event.EVENT_UNHANDLED
self.dragging = None
logging.debug(f"Corner pins set to {self.corners}")
return pyglet.event.EVENT_HANDLED
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
if self.dragging is None:
return pyglet.event.EVENT_UNHANDLED
self.corners[self.dragging][0] = x
self.corners[self.dragging][1] = y
self.update_view()
def update_view(self):
self.window.view = self.transform2d_matrix(*[x for c in self.corners for x in c])

15
pyproject.toml Normal file
View file

@ -0,0 +1,15 @@
[tool.poetry]
name = "pyglet-cornerpin"
version = "0.1.0"
description = ""
authors = ["Ruben van de Ven <git@rubenvandeven.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
pyglet = "^2.0.18"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"