rendercanvas 2.2.0__tar.gz → 2.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/PKG-INFO +1 -1
  2. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/_loop.py +1 -1
  3. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/_scheduler.py +21 -8
  4. rendercanvas-2.2.1/rendercanvas/_version.py +194 -0
  5. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/base.py +13 -2
  6. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/glfw.py +12 -18
  7. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/qt.py +0 -2
  8. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/raw.py +1 -0
  9. rendercanvas-2.2.0/rendercanvas/_version.py +0 -113
  10. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/LICENSE +0 -0
  11. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/README.md +0 -0
  12. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/pyproject.toml +0 -0
  13. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/__init__.py +0 -0
  14. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/__pyinstaller/__init__.py +0 -0
  15. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/__pyinstaller/conftest.py +0 -0
  16. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/__pyinstaller/hook-rendercanvas.py +0 -0
  17. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/__pyinstaller/test_rendercanvas.py +0 -0
  18. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/_context.py +0 -0
  19. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/_coreutils.py +0 -0
  20. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/_events.py +0 -0
  21. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/asyncio.py +0 -0
  22. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/auto.py +0 -0
  23. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/jupyter.py +0 -0
  24. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/offscreen.py +0 -0
  25. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/pyqt5.py +0 -0
  26. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/pyqt6.py +0 -0
  27. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/pyside2.py +0 -0
  28. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/pyside6.py +0 -0
  29. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/stub.py +0 -0
  30. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/trio.py +0 -0
  31. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/utils/__init__.py +0 -0
  32. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/utils/asyncadapter.py +0 -0
  33. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/utils/asyncs.py +0 -0
  34. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/utils/bitmappresentadapter.py +0 -0
  35. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/utils/bitmaprenderingcontext.py +0 -0
  36. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/utils/cube.py +0 -0
  37. {rendercanvas-2.2.0 → rendercanvas-2.2.1}/rendercanvas/wx.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rendercanvas
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: One canvas API, multiple backends
5
5
  Keywords: canvas,rendering,graphics,wgpu,qt,wx,glfw,jupyter
6
6
  Author: Almar Klein, Korijn van Golen
@@ -372,7 +372,7 @@ class BaseLoop:
372
372
  This method is optional. A subclass must either implement ``_rc_add_task`` or ``_rc_call_later``.
373
373
 
374
374
  * If you implememt this, make ``_rc_add_task()`` call ``super()._rc_add_task()``.
375
- * If delay is zero, this should behave like "call_later".
375
+ * If delay is zero, this should behave like "call_soon".
376
376
  * No need to catch errors from the callback; that's dealt with internally.
377
377
  * Return None.
378
378
  """
@@ -2,6 +2,7 @@
2
2
  The scheduler class/loop.
3
3
  """
4
4
 
5
+ import sys
5
6
  import time
6
7
  import weakref
7
8
 
@@ -9,6 +10,9 @@ from ._coreutils import BaseEnum
9
10
  from .utils.asyncs import sleep, Event
10
11
 
11
12
 
13
+ IS_WIN = sys.platform.startswith("win")
14
+
15
+
12
16
  class UpdateMode(BaseEnum):
13
17
  """The UpdateMode enum specifies the different modes to schedule draws for the canvas."""
14
18
 
@@ -123,14 +127,23 @@ class Scheduler:
123
127
  delay = 1 / self._max_fps
124
128
  delay = 0 if delay < 0 else delay # 0 means cannot keep up
125
129
 
126
- # Offset delay for time spent on processing events, etc.
127
- time_since_tick_start = time.perf_counter() - last_tick_time
128
- delay -= time_since_tick_start
129
- delay = max(0, delay)
130
-
131
- # Wait. Even if delay is zero, it gives control back to the loop,
132
- # allowing other tasks to do work.
133
- await sleep(delay)
130
+ # Determine amount of sleep
131
+ sleep_time = delay - (time.perf_counter() - last_tick_time)
132
+
133
+ if IS_WIN:
134
+ # On Windows OS-level timers have an in accuracy of 15.6 ms.
135
+ # This can cause sleep to take longer than intended. So we sleep
136
+ # less, and then do a few small sync-sleeps that have high accuracy.
137
+ await sleep(max(0, sleep_time - 0.0156))
138
+ sleep_time = delay - (time.perf_counter() - last_tick_time)
139
+ while sleep_time > 0:
140
+ time.sleep(min(sleep_time, 0.001)) # sleep hard for at most 1ms
141
+ await sleep(0) # Allow other tasks to run but don't wait
142
+ sleep_time = delay - (time.perf_counter() - last_tick_time)
143
+ else:
144
+ # Wait. Even if delay is zero, it gives control back to the loop,
145
+ # allowing other tasks to do work.
146
+ await sleep(max(0, sleep_time))
134
147
 
135
148
  # Below is the "tick"
136
149
 
@@ -0,0 +1,194 @@
1
+ """
2
+ _version.py v1.4
3
+
4
+ Simple version string management, using a hard-coded version string
5
+ for simplicity and compatibility, while adding git info at runtime.
6
+ See https://github.com/pygfx/_version for more info.
7
+ This code is subject to The Unlicense (public domain).
8
+
9
+ Any updates to this file should be done in https://github.com/pygfx/_version
10
+
11
+ Usage in short:
12
+
13
+ * Add this file to the root of your library (next to the `__init__.py`).
14
+ * On a new release, you just update the __version__.
15
+ """
16
+
17
+ # ruff: noqa: RUF100, S310, PLR2004, D212, D400, D415, S603, BLE001, COM812
18
+
19
+ import logging
20
+ import subprocess
21
+ from pathlib import Path
22
+
23
+ # This is the base version number, to be bumped before each release.
24
+ # The build system detects this definition when building a distribution.
25
+ __version__ = "2.2.1"
26
+
27
+ # Set this to your library name
28
+ project_name = "rendercanvas"
29
+
30
+
31
+ logger = logging.getLogger(project_name)
32
+
33
+ # Get whether this is a repo. If so, repo_dir is the path, otherwise None.
34
+ # .git is a dir in a normal repo and a file when in a submodule.
35
+ repo_dir = Path(__file__).parents[1]
36
+ repo_dir = repo_dir if repo_dir.joinpath(".git").exists() else None
37
+
38
+
39
+ def get_version() -> str:
40
+ """Get the version string."""
41
+ if repo_dir:
42
+ return get_extended_version()
43
+ return __version__
44
+
45
+
46
+ def get_extended_version() -> str:
47
+ """Get an extended version string with information from git."""
48
+ release, post, labels = get_version_info_from_git()
49
+
50
+ # Sample first 3 parts of __version__
51
+ base_release = ".".join(__version__.split(".")[:3])
52
+
53
+ # Check release
54
+ if not release:
55
+ release = base_release
56
+ elif release != base_release:
57
+ warning(
58
+ f"{project_name} version from git ({release})"
59
+ f" and __version__ ({base_release}) don't match."
60
+ )
61
+
62
+ # Build the total version
63
+ version = release
64
+ if post and post != "0":
65
+ version += f".post{post}"
66
+ if labels:
67
+ version += "+" + ".".join(labels)
68
+ elif labels and labels[-1] == "dirty":
69
+ version += "+" + ".".join(labels)
70
+
71
+ return version
72
+
73
+
74
+ def get_version_info_from_git() -> str:
75
+ """
76
+ Get (release, post, labels) from Git.
77
+
78
+ With `release` the version number from the latest tag, `post` the
79
+ number of commits since that tag, and `labels` a tuple with the
80
+ git-hash and optionally a dirty flag.
81
+ """
82
+ # Call out to Git
83
+ command = [
84
+ "git",
85
+ "describe",
86
+ "--long",
87
+ "--always",
88
+ "--tags",
89
+ "--dirty",
90
+ "--first-parent",
91
+ ]
92
+ try:
93
+ p = subprocess.run(command, check=False, cwd=repo_dir, capture_output=True)
94
+ except Exception as e:
95
+ warning(f"Could not get {project_name} version: {e}")
96
+ p = None
97
+
98
+ # Parse the result into parts
99
+ if p is None:
100
+ parts = (None, None, "unknown")
101
+ else:
102
+ output = p.stdout.decode(errors="ignore")
103
+ if p.returncode:
104
+ stderr = p.stderr.decode(errors="ignore")
105
+ warning(
106
+ f"Could not get {project_name} version.\n\nstdout: "
107
+ + output
108
+ + "\n\nstderr: "
109
+ + stderr
110
+ )
111
+ parts = (None, None, "unknown")
112
+ else:
113
+ parts = output.strip().lstrip("v").split("-")
114
+ if len(parts) <= 2:
115
+ # No tags (and thus no post). Only git hash and maybe 'dirty'.
116
+ parts = (None, None, *parts)
117
+
118
+ # Return unpacked parts
119
+ release, post, *labels = parts
120
+ return release, post, labels
121
+
122
+
123
+ def version_to_tuple(v: str) -> tuple:
124
+ v = __version__.split("+")[0] # remove hash
125
+ return tuple(int(i) if i.isnumeric() else i for i in v.split("."))
126
+
127
+
128
+ def prnt(m: str) -> None:
129
+ sys.stdout.write(m + "\n")
130
+
131
+
132
+ def warning(m: str) -> None:
133
+ logger.warning(m)
134
+
135
+
136
+ # Apply the versioning
137
+ base_version = __version__
138
+ __version__ = get_version()
139
+ version_info = version_to_tuple(__version__)
140
+
141
+
142
+ # The CLI part
143
+
144
+ CLI_USAGE = """
145
+ _version.py
146
+
147
+ help - Show this message.
148
+ version - Show the current version.
149
+ bump VERSION - Bump the __version__ to the given VERSION.
150
+ update - Self-update the _version.py module by downloading the
151
+ reference code and replacing version number and project name.
152
+ """.lstrip()
153
+
154
+ if __name__ == "__main__":
155
+ import sys
156
+ import urllib.request
157
+
158
+ _, *args = sys.argv
159
+ this_file = Path(__file__)
160
+
161
+ if not args or args[0] == "version":
162
+ prnt(f"{project_name} v{__version__}")
163
+
164
+ elif args[0] == "bump":
165
+ if len(args) != 2:
166
+ sys.exit("Expected a version number to bump to.")
167
+ new_version = args[1].lstrip("v") # allow '1.2.3' and 'v1.2.3'
168
+ if new_version.count(".") != 2:
169
+ sys.exit("Expected two dots in new version string.")
170
+ if not all(s.isnumeric() for s in new_version.split(".")):
171
+ sys.exit("Expected only numbers in new version string.")
172
+ with this_file.open("rb") as f:
173
+ text = ref_text = f.read().decode()
174
+ text = text.replace(base_version, new_version, 1)
175
+ with this_file.open("wb") as f:
176
+ f.write(text.encode())
177
+ prnt(f"Bumped version from '{base_version}' to '{new_version}'.")
178
+
179
+ elif args[0] == "update":
180
+ u = "https://raw.githubusercontent.com/pygfx/_version/main/_version.py"
181
+ with urllib.request.urlopen(u) as f:
182
+ text = ref_text = f.read().decode()
183
+ text = text.replace("0.0.0", base_version, 1)
184
+ text = text.replace("PROJECT_NAME", project_name, 1)
185
+ with this_file.open("wb") as f:
186
+ f.write(text.encode())
187
+ prnt("Updated to the latest _version.py.")
188
+
189
+ elif args[0].lstrip("-") in ["h", "help"]:
190
+ prnt(CLI_USAGE)
191
+
192
+ else:
193
+ prnt(f"Unknown command for _version.py: {args[0]!r}")
194
+ prnt("Use ``python _version.py help`` to see a list of options.")
@@ -476,11 +476,22 @@ class BaseRenderCanvas:
476
476
  # %% Primary canvas management methods
477
477
 
478
478
  def get_logical_size(self) -> Tuple[float]:
479
- """Get the logical size (width, height) in float pixels."""
479
+ """Get the logical size (width, height) in float pixels.
480
+
481
+ The logical size can be smaller than the physical size, e.g. on HiDPI
482
+ monitors or when the user's system has the display-scale set to e.g. 125%.
483
+ """
480
484
  return self._rc_get_logical_size()
481
485
 
482
486
  def get_pixel_ratio(self) -> float:
483
- """Get the float ratio between logical and physical pixels."""
487
+ """Get the float ratio between logical and physical pixels.
488
+
489
+ The pixel ratio is typically 1.0 for normal screens and 2.0 for HiDPI
490
+ screens, but fractional values are also possible if the system
491
+ display-scale is set to e.g. 125%. An HiDPI screen can be assumed if the
492
+ pixel ratio >= 2.0. On MacOS (with a Retina screen) the pixel ratio is
493
+ always 2.0.
494
+ """
484
495
  return self._rc_get_pixel_ratio()
485
496
 
486
497
  def close(self) -> None:
@@ -25,12 +25,12 @@ if glfw_version_info < (1, 9):
25
25
  raise ImportError("rendercanvas requires glfw 1.9 or higher.")
26
26
 
27
27
  # Do checks to prevent pitfalls on hybrid Xorg/Wayland systems
28
- is_wayland = False
28
+ api_is_wayland = False
29
29
  if sys.platform.startswith("linux") and SYSTEM_IS_WAYLAND:
30
30
  if not hasattr(glfw, "get_x11_window"):
31
31
  # Probably glfw was imported before this module, so we missed our chance
32
32
  # to set the env var to make glfw use x11.
33
- is_wayland = True
33
+ api_is_wayland = True
34
34
  logger.warning("Using GLFW with Wayland, which is experimental.")
35
35
 
36
36
 
@@ -135,7 +135,7 @@ def get_glfw_present_methods(window):
135
135
  }
136
136
  }
137
137
  elif sys.platform.startswith("linux"):
138
- if is_wayland:
138
+ if api_is_wayland:
139
139
  return {
140
140
  "screen": {
141
141
  "platform": "wayland",
@@ -262,6 +262,8 @@ class GlfwRenderCanvas(BaseRenderCanvas):
262
262
  def _determine_size(self):
263
263
  if self._window is None:
264
264
  return
265
+ # Note: On Wayland glfw.get_window_content_scale() produces (1.0, 1.0) regardless of the OS settings.
266
+
265
267
  # Because the value of get_window_size is in physical-pixels
266
268
  # on some systems and in logical-pixels on other, we use the
267
269
  # framebuffer size and pixel ratio to derive the logical size.
@@ -305,21 +307,13 @@ class GlfwRenderCanvas(BaseRenderCanvas):
305
307
  psize = glfw.get_framebuffer_size(self._window)
306
308
 
307
309
  # Apply
308
- if is_wayland:
309
- # Not sure why, but on Wayland things work differently
310
- screen_ratio = ssize[0] / new_logical_size[0]
311
- glfw.set_window_size(
312
- self._window,
313
- int(new_logical_size[0] / screen_ratio),
314
- int(new_logical_size[1] / screen_ratio),
315
- )
316
- else:
317
- screen_ratio = ssize[0] / psize[0]
318
- glfw.set_window_size(
319
- self._window,
320
- int(new_logical_size[0] * pixel_ratio * screen_ratio),
321
- int(new_logical_size[1] * pixel_ratio * screen_ratio),
322
- )
310
+ screen_ratio = ssize[0] / psize[0]
311
+ glfw.set_window_size(
312
+ self._window,
313
+ int(new_logical_size[0] * pixel_ratio * screen_ratio),
314
+ int(new_logical_size[1] * pixel_ratio * screen_ratio),
315
+ )
316
+
323
317
  self._screen_size_is_logical = screen_ratio != 1
324
318
  # If this causes the widget size to change, then _on_size_change will
325
319
  # be called, but we may want force redetermining the size.
@@ -31,7 +31,6 @@ if libname:
31
31
  WA_PaintOnScreen = QtCore.Qt.WidgetAttribute.WA_PaintOnScreen
32
32
  WA_DeleteOnClose = QtCore.Qt.WidgetAttribute.WA_DeleteOnClose
33
33
  WA_InputMethodEnabled = QtCore.Qt.WidgetAttribute.WA_InputMethodEnabled
34
- PreciseTimer = QtCore.Qt.TimerType.PreciseTimer
35
34
  KeyboardModifiers = QtCore.Qt.KeyboardModifier
36
35
  FocusPolicy = QtCore.Qt.FocusPolicy
37
36
  CursorShape = QtCore.Qt.CursorShape
@@ -42,7 +41,6 @@ if libname:
42
41
  WA_PaintOnScreen = QtCore.Qt.WA_PaintOnScreen
43
42
  WA_DeleteOnClose = QtCore.Qt.WA_DeleteOnClose
44
43
  WA_InputMethodEnabled = QtCore.Qt.WA_InputMethodEnabled
45
- PreciseTimer = QtCore.Qt.PreciseTimer
46
44
  KeyboardModifiers = QtCore.Qt
47
45
  FocusPolicy = QtCore.Qt
48
46
  CursorShape = QtCore.Qt
@@ -60,6 +60,7 @@ class RawLoop(BaseLoop):
60
60
  break
61
61
  else:
62
62
  # Wait until its time for it to be called
63
+ # Note that on Windows, the accuracy of the timeout is 15.6 ms
63
64
  wait_time = wrapper.time - time.perf_counter()
64
65
  self._event.wait(max(wait_time, 0))
65
66
 
@@ -1,113 +0,0 @@
1
- """
2
- Versioning: we use a hard-coded version number, because it's simple and always
3
- works. For dev installs we add extra version info from Git.
4
- """
5
-
6
- import logging
7
- import subprocess
8
- from pathlib import Path
9
-
10
-
11
- # This is the reference version number, to be bumped before each release.
12
- # The build system detects this definition when building a distribution.
13
- __version__ = "2.2.0"
14
-
15
- # Allow using nearly the same code in different projects
16
- project_name = "rendercanvas"
17
-
18
-
19
- logger = logging.getLogger(project_name.lower())
20
-
21
- # Get whether this is a repo. If so, repo_dir is the path, otherwise repo_dir is None.
22
- repo_dir = Path(__file__).parents[1]
23
- repo_dir = repo_dir if repo_dir.joinpath(".git").is_dir() else None
24
-
25
-
26
- def get_version():
27
- """Get the version string."""
28
- if repo_dir:
29
- return get_extended_version()
30
- else:
31
- return __version__
32
-
33
-
34
- def get_extended_version():
35
- """Get an extended version string with information from git."""
36
-
37
- release, post, labels = get_version_info_from_git()
38
-
39
- # Sample first 3 parts of __version__
40
- base_release = ".".join(__version__.split(".")[:3])
41
-
42
- # Check release
43
- if not release:
44
- release = base_release
45
- elif release != base_release:
46
- logger.warning(
47
- f"{project_name} version from git ({release}) and __version__ ({base_release}) don't match."
48
- )
49
-
50
- # Build the total version
51
- version = release
52
- if post and post != "0":
53
- version += f".post{post}"
54
- if labels:
55
- version += "+" + ".".join(labels)
56
-
57
- return version
58
-
59
-
60
- def get_version_info_from_git():
61
- """Get (release, post, labels) from Git.
62
-
63
- With `release` the version number from the latest tag, `post` the
64
- number of commits since that tag, and `labels` a tuple with the
65
- git-hash and optionally a dirty flag.
66
- """
67
-
68
- # Call out to Git
69
- command = [
70
- "git",
71
- "describe",
72
- "--long",
73
- "--always",
74
- "--tags",
75
- "--dirty",
76
- "--first-parent",
77
- ]
78
- try:
79
- p = subprocess.run(command, cwd=repo_dir, capture_output=True)
80
- except Exception as e:
81
- logger.warning(f"Could not get {project_name} version: {e}")
82
- p = None
83
-
84
- # Parse the result into parts
85
- if p is None:
86
- parts = (None, None, "unknown")
87
- else:
88
- output = p.stdout.decode(errors="ignore")
89
- if p.returncode:
90
- stderr = p.stderr.decode(errors="ignore")
91
- logger.warning(
92
- f"Could not get {project_name} version.\n\nstdout: "
93
- + output
94
- + "\n\nstderr: "
95
- + stderr
96
- )
97
- parts = (None, None, "unknown")
98
- else:
99
- parts = output.strip().lstrip("v").split("-")
100
- if len(parts) <= 2:
101
- # No tags (and thus also no post). Only git hash and maybe 'dirty'
102
- parts = (None, None, *parts)
103
-
104
- # Return unpacked parts
105
- release, post, *labels = parts
106
- return release, post, labels
107
-
108
-
109
- __version__ = get_version()
110
-
111
- version_info = tuple(
112
- int(i) if i.isnumeric() else i for i in __version__.split("+")[0].split(".")
113
- )
File without changes
File without changes