diff --git a/README.md b/README.md index a74bfc8..803e757 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Have a comment or question? We'd love to hear from you! The best ways to reach o * github [discussions](https://github.com/py5coding/py5generator/discussions) and [issues](https://github.com/py5coding/py5generator/issues) * Mastodon fosstodon.org/@py5coding * twitter [@py5coding](https://twitter.com/py5coding) -* [processing foundation discourse](https://discourse.processing.org/) +* [processing foundation discourse](https://discourse.processing.org/c/28) [processing]: https://github.com/processing/processing4 [jpype]: https://github.com/jpype-project/jpype diff --git a/py5/__init__.py b/py5/__init__.py index e572a6c..5d90fb4 100644 --- a/py5/__init__.py +++ b/py5/__init__.py @@ -114,7 +114,7 @@ pass -__version__ = "0.10.0a0" +__version__ = "0.10.1a1" _PY5_USE_IMPORTED_MODE = py5_tools.get_imported_mode() py5_tools._lock_imported_mode() @@ -9264,13 +9264,13 @@ def ortho() -> None: bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume @@ -9314,13 +9314,13 @@ def ortho(left: float, right: float, bottom: float, top: float, /) -> None: bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume @@ -9366,13 +9366,13 @@ def ortho( bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume @@ -9415,13 +9415,13 @@ def ortho(*args): bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume @@ -16862,8 +16862,6 @@ def parse_json(serialized_json: Any, **kwargs: dict[str, Any]) -> Any: def load_strings(string_path: Union[str, Path], **kwargs: dict[str, Any]) -> list[str]: """Load a list of strings from a file or URL. - Underlying Processing method: Sketch.loadStrings - Parameters ---------- @@ -16894,8 +16892,6 @@ def save_strings( ) -> None: """Save a list of strings to a file. - Underlying Processing method: Sketch.saveStrings - Parameters ---------- @@ -16928,8 +16924,6 @@ def save_strings( def load_bytes(bytes_path: Union[str, Path], **kwargs: dict[str, Any]) -> bytearray: """Load byte data from a file or URL. - Underlying Processing method: Sketch.loadBytes - Parameters ---------- @@ -16958,8 +16952,6 @@ def load_bytes(bytes_path: Union[str, Path], **kwargs: dict[str, Any]) -> bytear def save_bytes(bytes_data: Union[bytes, bytearray], filename: Union[str, Path]) -> None: """Save byte data to a file. - Underlying Processing method: Sketch.saveBytes - Parameters ---------- @@ -16982,8 +16974,6 @@ def save_bytes(bytes_data: Union[bytes, bytearray], filename: Union[str, Path]) def load_pickle(pickle_path: Union[str, Path]) -> Any: """Load a pickled Python object from a file. - Underlying Processing method: Sketch.loadPickle - Parameters ---------- @@ -16999,6 +16989,15 @@ def load_pickle(pickle_path: Union[str, Path]) -> Any: There are security risks associated with Python pickle files. A pickle file can contain malicious code, so never load a pickle file from an untrusted source. + + When using py5 in imported mode, pickling will not work on objects instantiated + from new classes you have defined yourself on the main sketch file. This applies + to py5's `save_pickle()` and `load_pickle()` methods, as well as the Python's + standard library pickle module methods they depend upon. If you need to pickle + objects from classes you defined, move the class definitions to a different .py + file that you import as a module or import the classes from. Otherwise, you + could also try using module mode if you want to use pickle with your classes and + keep all the sketch code in a single file. """ return _py5sketch.load_pickle(pickle_path) @@ -17006,8 +17005,6 @@ def load_pickle(pickle_path: Union[str, Path]) -> Any: def save_pickle(obj: Any, filename: Union[str, Path]) -> None: """Pickle a Python object to a file. - Underlying Processing method: Sketch.savePickle - Parameters ---------- @@ -17024,210 +17021,622 @@ def save_pickle(obj: Any, filename: Union[str, Path]) -> None: be saved relative to the current working directory (`sketch_path()`). The saved file can be reloaded with `load_pickle()`. - Object "pickling" is a method for serializing objects and saving them to a file - for later retrieval. The recreated objects will be clones of the original + Object "pickling" is a technique for serializing objects and saving them to a + file for later retrieval. The recreated objects will be clones of the original objects. Not all Python objects can be saved to a Python pickle file. This limitation prevents any py5 object from being pickled. + + When using py5 in imported mode, pickling will not work on objects instantiated + from new classes you have defined yourself on the main sketch file. This applies + to py5's `save_pickle()` and `load_pickle()` methods, as well as the Python's + standard library pickle module methods they depend upon. If you need to pickle + objects from classes you defined, move the class definitions to a different .py + file that you import as a module or import the classes from. Otherwise, you + could also try using module mode if you want to use pickle with your classes and + keep all the sketch code in a single file. """ return _py5sketch.save_pickle(obj, filename) ############################################################################## -# module functions from pixels.py +# module functions from print_tools.py ############################################################################## -def load_np_pixels() -> None: - """Loads the pixel data of the current display window into the `np_pixels[]` array. - - Notes - ----- - - Loads the pixel data of the current display window into the `np_pixels[]` array. - This method must always be called before reading from or writing to - `np_pixels[]`. Subsequent changes to the display window will not be reflected in - `np_pixels[]` until `load_np_pixels()` is called again. - - The `load_np_pixels()` method is similar to `load_pixels()` in that - `load_np_pixels()` must be called before reading from or writing to - `np_pixels[]` just as `load_pixels()` must be called before reading from or - writing to `pixels[]`. - - Note that `load_np_pixels()` will as a side effect call `load_pixels()`, so if - your code needs to read `np_pixels[]` and `pixels[]` simultaneously, there is no - need for a separate call to `load_pixels()`. However, be aware that modifying - both `np_pixels[]` and `pixels[]` simultaneously will likely result in the - updates to `pixels[]` being discarded. - """ - return _py5sketch.load_np_pixels() +def set_println_stream(println_stream: Any) -> None: + """Customize where the output of `println()` goes. + Parameters + ---------- -def update_np_pixels() -> None: - """Updates the display window with the data in the `np_pixels[]` array. + println_stream: Any + println stream object to be used by println method Notes ----- - Updates the display window with the data in the `np_pixels[]` array. Use in - conjunction with `load_np_pixels()`. If you're only reading pixels from the - array, there's no need to call `update_np_pixels()` — updating is only necessary - to apply changes. + Customize where the output of `println()` goes. - The `update_np_pixels()` method is similar to `update_pixels()` in that - `update_np_pixels()` must be called after modifying `np_pixels[]` just as - `update_pixels()` must be called after modifying `pixels[]`. + When running a Sketch asynchronously through Jupyter Notebook, any `print` + statements using Python's builtin function will always appear in the output of + the currently active cell. This will rarely be desirable, as the active cell + will keep changing as the user executes code elsewhere in the notebook. The + `println()` method was created to provide users with print functionality in a + Sketch without having to cope with output moving from one cell to the next. Use + `set_println_stream` to change how the output is handled. The `println_stream` + object must provide `init()` and `print()` methods, as shown in the example. The + example demonstrates how to configure py5 to output text to an IPython Widget. """ - return _py5sketch.update_np_pixels() - - -np_pixels: npt.NDArray[np.uint8] = None + return _py5sketch.set_println_stream(println_stream) -def set_np_pixels(array: npt.NDArray[np.uint8], bands: str = "ARGB") -> None: - """Set the entire contents of `np_pixels[]` to the contents of another properly - sized and typed numpy array. +def println(*args, sep: str = " ", end: str = "\n", stderr: bool = False) -> None: + """Print text or other values to the screen. Parameters ---------- - array: npt.NDArray[np.uint8] - properly sized numpy array to be copied to np_pixels[] + args + values to be printed - bands: str = "ARGB" - color channels in the array's third dimension + end: str = "\\n" + string appended after the last value, defaults to newline character + + sep: str = " " + string inserted between values, defaults to a space + + stderr: bool = False + use stderr instead of stdout Notes ----- - Set the entire contents of `np_pixels[]` to the contents of another properly - sized and typed numpy array. The size of `array`'s first and second dimensions - must match the height and width of the Sketch window, respectively. The array's - `dtype` must be `np.uint8`. - - The `bands` parameter is used to interpret the `array`'s color channel dimension - (the array's third dimension). It can be one of `'L'` (single-channel - grayscale), `'ARGB'`, `'RGB'`, or `'RGBA'`. If there is no alpha channel, - `array` is assumed to have no transparency, but recall that the display window's - pixels can never be transparent so any transparency in `array` will have no - effect. If the `bands` parameter is `'L'`, `array`'s third dimension is - optional. + Print text or other values to the screen. For a Sketch running outside of a + Jupyter Notebook, this method will behave the same as the Python's builtin + `print` method. For Sketches running in a Jupyter Notebook, this will place text + in the output of the cell that made the `run_sketch()` call. - This method makes its own calls to `load_np_pixels()` and `update_np_pixels()` - so there is no need to call either explicitly. + When running a Sketch asynchronously through Jupyter Notebook, any `print` + statements using Python's builtin function will always appear in the output of + the currently active cell. This will rarely be desirable, as the active cell + will keep changing as the user executes code elsewhere in the notebook. This + method was created to provide users with print functionality in a Sketch without + having to cope with output moving from one cell to the next. - This method exists because setting the array contents with the code - `py5.np_pixels = array` will cause an error, while the correct syntax, - `py5.np_pixels[:] = array`, might also be unintuitive for beginners. + Use `set_println_stream()` to customize the behavior of `println()`. """ - return _py5sketch.set_np_pixels(array, bands=bands) - + return _py5sketch.println(*args, sep=sep, end=end, stderr=stderr) -@overload -def get_np_pixels( - *, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None -) -> npt.NDArray[np.uint8]: - """Get the contents of `np_pixels[]` as a numpy array. - Methods - ------- +############################################################################## +# module functions from threads.py +############################################################################## - You can use any of the following signatures: - * get_np_pixels(*, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None) -> npt.NDArray[np.uint8] - * get_np_pixels(x: int, y: int, w: int, h: int, /, *, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None, ) -> npt.NDArray[np.uint8] +def launch_thread( + f: Callable, + name: str = None, + *, + daemon: bool = True, + args: tuple = None, + kwargs: dict = None, +) -> str: + """Launch a new thread to execute a function in parallel with your Sketch code. Parameters ---------- - bands: str = "ARGB" - color channels in output array - - dst: npt.NDArray[np.uint8] = None - destination array to copy pixel data into + args: tuple = None + positional arguments to pass to the given function - h: int - source height + daemon: bool = True + if the thread should be a daemon thread - w: int - source width + f: Callable + function to call in the launched thread - x: int - x-coordinate of the source's upper left corner + kwargs: dict = None + keyword arguments to pass to the given function - y: int - y-coordinate of the source's upper left corner + name: str = None + name of thread to be created Notes ----- - Get the contents of `np_pixels[]` as a numpy array. The returned numpy array can - be the entirety of `np_pixels[]` or a rectangular subsection. Use the `x`, `y`, - `h`, and `w` parameters to specify the bounds of a rectangular subsection. - - The `bands` parameter is used to determine the ordering of the returned numpy - array's color channel. It can be one of `'L'` (single-channel grayscale), - `'ARGB'`, `'RGB'`, or `'RGBA'`. If the `bands` parameter is `'L'`, the returned - array will have two dimensions, and each pixel value will be calculated as - `0.299 * red + 0.587 * green + 0.114 * blue`. The alpha channel will also be - ignored. For all other `bands` parameter values, the returned array will have - three dimensions, with the third dimension representing the different color - channels specified by the `bands` value. + Launch a new thread to execute a function in parallel with your Sketch code. + This can be useful for executing non-py5 code that would otherwise slow down the + animation thread and reduce the Sketch's frame rate. - The returned array will always be a copy of the data in `np_pixels[]` and not a - view into that array or any other array. Use the `dst` parameter to provide the - numpy array to copy the pixel data into. The provided array must be sized - correctly. The array's `dtype` should `np.uint8`, but this isn't required. - """ - pass + The `name` parameter is optional but useful if you want to monitor the thread + with other methods such as `has_thread()`. If the provided `name` is identical + to an already running thread, the running thread will first be stopped with a + call to `stop_thread()` with the `wait` parameter equal to `True`. + Use the `args` and `kwargs` parameters to pass positional and keyword arguments + to the function. -@overload -def get_np_pixels( - x: int, - y: int, - w: int, - h: int, - /, - *, - bands: str = "ARGB", - dst: npt.NDArray[np.uint8] = None, -) -> npt.NDArray[np.uint8]: - """Get the contents of `np_pixels[]` as a numpy array. + Use the `daemon` parameter to make the launched thread a daemon that will run + without blocking Python from exiting. This parameter defaults to `True`, meaning + that function execution can be interupted if the Python process exits. Note that + if the Python process continues running after the Sketch exits, which is + typically the case when using a Jupyter Notebook, this parameter won't have any + effect unless if you try to restart the Notebook kernel. Generally speaking, + setting this parameter to `False` causes problems but it is available for those + who really need it. See `stop_all_threads()` for a better approach to exit + threads. - Methods - ------- + The new thread is a Python thread, so all the usual caveats about the Global + Interpreter Lock (GIL) apply here. + """ + return _py5sketch.launch_thread( + f, + name=name, + daemon=daemon, + args=args, + kwargs=kwargs, + ) - You can use any of the following signatures: - * get_np_pixels(*, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None) -> npt.NDArray[np.uint8] - * get_np_pixels(x: int, y: int, w: int, h: int, /, *, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None, ) -> npt.NDArray[np.uint8] +def launch_promise_thread( + f: Callable, + name: str = None, + *, + daemon: bool = True, + args: tuple = None, + kwargs: dict = None, +) -> Py5Promise: + """Create a `Py5Promise` object that will store the returned result of a function + when that function completes. Parameters ---------- - bands: str = "ARGB" - color channels in output array - - dst: npt.NDArray[np.uint8] = None - destination array to copy pixel data into + args: tuple = None + positional arguments to pass to the given function - h: int - source height + daemon: bool = True + if the thread should be a daemon thread - w: int - source width + f: Callable + function to call in the launched thread - x: int - x-coordinate of the source's upper left corner + kwargs: dict = None + keyword arguments to pass to the given function - y: int - y-coordinate of the source's upper left corner + name: str = None + name of thread to be created Notes ----- - Get the contents of `np_pixels[]` as a numpy array. The returned numpy array can - be the entirety of `np_pixels[]` or a rectangular subsection. Use the `x`, `y`, + Create a `Py5Promise` object that will store the returned result of a function + when that function completes. This can be useful for executing non-py5 code that + would otherwise slow down the animation thread and reduce the Sketch's frame + rate. + + The `Py5Promise` object has an `is_ready` property that will be `True` when the + `result` property contains the value function `f` returned. Before then, the + `result` property will be `None`. + + The `name` parameter is optional but useful if you want to monitor the thread + with other methods such as `has_thread()`. If the provided `name` is identical + to an already running thread, the running thread will first be stopped with a + call to `stop_thread()` with the `wait` parameter equal to `True`. + + Use the `args` and `kwargs` parameters to pass positional and keyword arguments + to the function. + + Use the `daemon` parameter to make the launched thread a daemon that will run + without blocking Python from exiting. This parameter defaults to `True`, meaning + that function execution can be interupted if the Python process exits. Note that + if the Python process continues running after the Sketch exits, which is + typically the case when using a Jupyter Notebook, this parameter won't have any + effect unless if you try to restart the Notebook kernel. Generally speaking, + setting this parameter to `False` causes problems but it is available for those + who really need it. See `stop_all_threads()` for a better approach to exit + threads. + + The new thread is a Python thread, so all the usual caveats about the Global + Interpreter Lock (GIL) apply here. + """ + return _py5sketch.launch_promise_thread( + f, + name=name, + daemon=daemon, + args=args, + kwargs=kwargs, + ) + + +def launch_repeating_thread( + f: Callable, + name: str = None, + *, + time_delay: float = 0, + daemon: bool = True, + args: tuple = None, + kwargs: dict = None, +) -> str: + """Launch a new thread that will repeatedly execute a function in parallel with + your Sketch code. + + Parameters + ---------- + + args: tuple = None + positional arguments to pass to the given function + + daemon: bool = True + if the thread should be a daemon thread + + f: Callable + function to call in the launched thread + + kwargs: dict = None + keyword arguments to pass to the given function + + name: str = None + name of thread to be created + + time_delay: float = 0 + time delay in seconds between calls to the given function + + Notes + ----- + + Launch a new thread that will repeatedly execute a function in parallel with + your Sketch code. This can be useful for executing non-py5 code that would + otherwise slow down the animation thread and reduce the Sketch's frame rate. + + Use the `time_delay` parameter to set the time in seconds between one call to + function `f` and the next call. Set this parameter to `0` if you want each call + to happen immediately after the previous call finishes. If the function `f` + takes longer than expected to finish, py5 will wait for it to finish before + making the next call. There will not be overlapping calls to function `f`. + + The `name` parameter is optional but useful if you want to monitor the thread + with other methods such as `has_thread()`. If the provided `name` is identical + to an already running thread, the running thread will first be stopped with a + call to `stop_thread()` with the `wait` parameter equal to `True`. + + Use the `args` and `kwargs` parameters to pass positional and keyword arguments + to the function. + + Use the `daemon` parameter to make the launched thread a daemon that will run + without blocking Python from exiting. This parameter defaults to `True`, meaning + that function execution can be interupted if the Python process exits. Note that + if the Python process continues running after the Sketch exits, which is + typically the case when using a Jupyter Notebook, this parameter won't have any + effect unless if you try to restart the Notebook kernel. Generally speaking, + setting this parameter to `False` causes problems but it is available for those + who really need it. See `stop_all_threads()` for a better approach to exit + threads. + + The new thread is a Python thread, so all the usual caveats about the Global + Interpreter Lock (GIL) apply here. + """ + return _py5sketch.launch_repeating_thread( + f, + name=name, + time_delay=time_delay, + daemon=daemon, + args=args, + kwargs=kwargs, + ) + + +def has_thread(name: str) -> None: + """Determine if a thread of a given name exists and is currently running. + + Parameters + ---------- + + name: str + name of thread + + Notes + ----- + + Determine if a thread of a given name exists and is currently running. You can + get the list of all currently running threads with `list_threads()`. + """ + return _py5sketch.has_thread(name) + + +def join_thread(name: str, *, timeout: float = None) -> bool: + """Join the Python thread associated with the given thread name. + + Parameters + ---------- + + name: str + name of thread + + timeout: float = None + maximum time in seconds to wait for the thread to join + + Notes + ----- + + Join the Python thread associated with the given thread name. The + `join_thread()` method will wait until the named thread has finished executing + before returning. Use the `timeout` parameter to set an upper limit for the + number of seconds to wait. This method will return right away if the named + thread does not exist or the thread has already finished executing. You can get + the list of all currently running threads with `list_threads()`. + + This method will return `True` if the named thread has completed execution and + `False` if the named thread is still executing. It will only return `False` if + you use the `timeout` parameter and the method is not able to join with the + thread within that time limit. + """ + return _py5sketch.join_thread(name, timeout=timeout) + + +def stop_thread(name: str, wait: bool = False) -> None: + """Stop a thread of a given name. + + Parameters + ---------- + + name: str + name of thread + + wait: bool = False + wait for thread to exit before returning + + Notes + ----- + + Stop a thread of a given name. The `wait` parameter determines if the method + call will return right away or wait for the thread to exit. + + This won't do anything useful if the thread was launched with either + `launch_thread()` or `launch_promise_thread()` and the `wait` parameter is + `False`. Non-repeating threads are executed once and will stop when they + complete execution. Setting the `wait` parameter to `True` will merely block + until the thread exits on its own. Killing off a running thread in Python is + complicated and py5 cannot do that for you. If you want a thread to perform some + action repeatedly and be interuptable, use `launch_repeating_thread()` instead. + + Use `has_thread()` to determine if a thread of a given name exists and + `list_threads()` to get a list of all thread names. Use `stop_all_threads()` to + stop all threads. + """ + return _py5sketch.stop_thread(name, wait=wait) + + +def stop_all_threads(wait: bool = False) -> None: + """Stop all running threads. + + Parameters + ---------- + + wait: bool = False + wait for thread to exit before returning + + Notes + ----- + + Stop all running threads. The `wait` parameter determines if the method call + will return right away or wait for the threads to exit. + + When the Sketch shuts down, `stop_all_threads(wait=False)` is called for you. If + you would rather the Sketch waited for threads to exit, create an `exiting` + method and make a call to `stop_all_threads(wait=True)`. + """ + return _py5sketch.stop_all_threads(wait=wait) + + +def list_threads() -> None: + """List the names of all of the currently running threads. + + Notes + ----- + + List the names of all of the currently running threads. The names of previously + launched threads that have exited will be removed from the list. + """ + return _py5sketch.list_threads() + + +############################################################################## +# module functions from pixels.py +############################################################################## + + +def load_np_pixels() -> None: + """Loads the pixel data of the current display window into the `np_pixels[]` array. + + Notes + ----- + + Loads the pixel data of the current display window into the `np_pixels[]` array. + This method must always be called before reading from or writing to + `np_pixels[]`. Subsequent changes to the display window will not be reflected in + `np_pixels[]` until `load_np_pixels()` is called again. + + The `load_np_pixels()` method is similar to `load_pixels()` in that + `load_np_pixels()` must be called before reading from or writing to + `np_pixels[]` just as `load_pixels()` must be called before reading from or + writing to `pixels[]`. + + Note that `load_np_pixels()` will as a side effect call `load_pixels()`, so if + your code needs to read `np_pixels[]` and `pixels[]` simultaneously, there is no + need for a separate call to `load_pixels()`. However, be aware that modifying + both `np_pixels[]` and `pixels[]` simultaneously will likely result in the + updates to `pixels[]` being discarded. + """ + return _py5sketch.load_np_pixels() + + +def update_np_pixels() -> None: + """Updates the display window with the data in the `np_pixels[]` array. + + Notes + ----- + + Updates the display window with the data in the `np_pixels[]` array. Use in + conjunction with `load_np_pixels()`. If you're only reading pixels from the + array, there's no need to call `update_np_pixels()` — updating is only necessary + to apply changes. + + The `update_np_pixels()` method is similar to `update_pixels()` in that + `update_np_pixels()` must be called after modifying `np_pixels[]` just as + `update_pixels()` must be called after modifying `pixels[]`. + """ + return _py5sketch.update_np_pixels() + + +np_pixels: npt.NDArray[np.uint8] = None + + +def set_np_pixels(array: npt.NDArray[np.uint8], bands: str = "ARGB") -> None: + """Set the entire contents of `np_pixels[]` to the contents of another properly + sized and typed numpy array. + + Parameters + ---------- + + array: npt.NDArray[np.uint8] + properly sized numpy array to be copied to np_pixels[] + + bands: str = "ARGB" + color channels in the array's third dimension + + Notes + ----- + + Set the entire contents of `np_pixels[]` to the contents of another properly + sized and typed numpy array. The size of `array`'s first and second dimensions + must match the height and width of the Sketch window, respectively. The array's + `dtype` must be `np.uint8`. + + The `bands` parameter is used to interpret the `array`'s color channel dimension + (the array's third dimension). It can be one of `'L'` (single-channel + grayscale), `'ARGB'`, `'RGB'`, or `'RGBA'`. If there is no alpha channel, + `array` is assumed to have no transparency, but recall that the display window's + pixels can never be transparent so any transparency in `array` will have no + effect. If the `bands` parameter is `'L'`, `array`'s third dimension is + optional. + + This method makes its own calls to `load_np_pixels()` and `update_np_pixels()` + so there is no need to call either explicitly. + + This method exists because setting the array contents with the code + `py5.np_pixels = array` will cause an error, while the correct syntax, + `py5.np_pixels[:] = array`, might also be unintuitive for beginners. + """ + return _py5sketch.set_np_pixels(array, bands=bands) + + +@overload +def get_np_pixels( + *, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None +) -> npt.NDArray[np.uint8]: + """Get the contents of `np_pixels[]` as a numpy array. + + Methods + ------- + + You can use any of the following signatures: + + * get_np_pixels(*, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None) -> npt.NDArray[np.uint8] + * get_np_pixels(x: int, y: int, w: int, h: int, /, *, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None, ) -> npt.NDArray[np.uint8] + + Parameters + ---------- + + bands: str = "ARGB" + color channels in output array + + dst: npt.NDArray[np.uint8] = None + destination array to copy pixel data into + + h: int + source height + + w: int + source width + + x: int + x-coordinate of the source's upper left corner + + y: int + y-coordinate of the source's upper left corner + + Notes + ----- + + Get the contents of `np_pixels[]` as a numpy array. The returned numpy array can + be the entirety of `np_pixels[]` or a rectangular subsection. Use the `x`, `y`, + `h`, and `w` parameters to specify the bounds of a rectangular subsection. + + The `bands` parameter is used to determine the ordering of the returned numpy + array's color channel. It can be one of `'L'` (single-channel grayscale), + `'ARGB'`, `'RGB'`, or `'RGBA'`. If the `bands` parameter is `'L'`, the returned + array will have two dimensions, and each pixel value will be calculated as + `0.299 * red + 0.587 * green + 0.114 * blue`. The alpha channel will also be + ignored. For all other `bands` parameter values, the returned array will have + three dimensions, with the third dimension representing the different color + channels specified by the `bands` value. + + The returned array will always be a copy of the data in `np_pixels[]` and not a + view into that array or any other array. Use the `dst` parameter to provide the + numpy array to copy the pixel data into. The provided array must be sized + correctly. The array's `dtype` should `np.uint8`, but this isn't required. + """ + pass + + +@overload +def get_np_pixels( + x: int, + y: int, + w: int, + h: int, + /, + *, + bands: str = "ARGB", + dst: npt.NDArray[np.uint8] = None, +) -> npt.NDArray[np.uint8]: + """Get the contents of `np_pixels[]` as a numpy array. + + Methods + ------- + + You can use any of the following signatures: + + * get_np_pixels(*, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None) -> npt.NDArray[np.uint8] + * get_np_pixels(x: int, y: int, w: int, h: int, /, *, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None, ) -> npt.NDArray[np.uint8] + + Parameters + ---------- + + bands: str = "ARGB" + color channels in output array + + dst: npt.NDArray[np.uint8] = None + destination array to copy pixel data into + + h: int + source height + + w: int + source width + + x: int + x-coordinate of the source's upper left corner + + y: int + y-coordinate of the source's upper left corner + + Notes + ----- + + Get the contents of `np_pixels[]` as a numpy array. The returned numpy array can + be the entirety of `np_pixels[]` or a rectangular subsection. Use the `x`, `y`, `h`, and `w` parameters to specify the bounds of a rectangular subsection. The `bands` parameter is used to determine the ordering of the returned numpy @@ -17469,76 +17878,6 @@ def save( ) -############################################################################## -# module functions from print_tools.py -############################################################################## - - -def set_println_stream(println_stream: Any) -> None: - """Customize where the output of `println()` goes. - - Parameters - ---------- - - println_stream: Any - println stream object to be used by println method - - Notes - ----- - - Customize where the output of `println()` goes. - - When running a Sketch asynchronously through Jupyter Notebook, any `print` - statements using Python's builtin function will always appear in the output of - the currently active cell. This will rarely be desirable, as the active cell - will keep changing as the user executes code elsewhere in the notebook. The - `println()` method was created to provide users with print functionality in a - Sketch without having to cope with output moving from one cell to the next. Use - `set_println_stream` to change how the output is handled. The `println_stream` - object must provide `init()` and `print()` methods, as shown in the example. The - example demonstrates how to configure py5 to output text to an IPython Widget. - """ - return _py5sketch.set_println_stream(println_stream) - - -def println(*args, sep: str = " ", end: str = "\n", stderr: bool = False) -> None: - """Print text or other values to the screen. - - Parameters - ---------- - - args - values to be printed - - end: str = "\\n" - string appended after the last value, defaults to newline character - - sep: str = " " - string inserted between values, defaults to a space - - stderr: bool = False - use stderr instead of stdout - - Notes - ----- - - Print text or other values to the screen. For a Sketch running outside of a - Jupyter Notebook, this method will behave the same as the Python's builtin - `print` method. For Sketches running in a Jupyter Notebook, this will place text - in the output of the cell that made the `run_sketch()` call. - - When running a Sketch asynchronously through Jupyter Notebook, any `print` - statements using Python's builtin function will always appear in the output of - the currently active cell. This will rarely be desirable, as the active cell - will keep changing as the user executes code elsewhere in the notebook. This - method was created to provide users with print functionality in a Sketch without - having to cope with output moving from one cell to the next. - - Use `set_println_stream()` to customize the behavior of `println()`. - """ - return _py5sketch.println(*args, sep=sep, end=end, stderr=stderr) - - ############################################################################## # module functions from math.py ############################################################################## @@ -17780,7 +18119,7 @@ def constrain( low: Union[float, npt.NDArray], high: Union[float, npt.NDArray], ) -> Union[float, npt.NDArray]: - """Constrains a value to not exceed a maximum and minimum value. + """Constrains a value between a minimum and maximum value. Parameters ---------- @@ -17789,15 +18128,15 @@ def constrain( the value to constrain high: Union[float, npt.NDArray] - minimum limit + maximum limit low: Union[float, npt.NDArray] - maximum limit + minimum limit Notes ----- - Constrains a value to not exceed a maximum and minimum value. + Constrains a value between a minimum and maximum value. """ return Sketch.constrain( amt, @@ -19318,188 +19657,7 @@ def noise(*args) -> Union[float, npt.NDArray]: Py5's `noise()` method can also accept numpy arrays as parameters. It will use broadcasting when needed and calculate the values efficiently. Using numpy array parameters will be much faster and efficient than calling the `noise()` method - repeatedly in a loop. See the examples to see how this can be done. - - Noise generation is a rich and complex topic, and there are many noise - algorithms and libraries available that are worth learning about. Early versions - of py5 used the Python "noise" library, which can generate noise using the - "Improved Perlin Noise" algorithm (as described in Ken Perlin's 2002 SIGGRAPH - paper) and the Simplex Noise algorithm (also developed by Ken Perlin). That - Python library was removed from py5 because it has some bugs and hasn't had a - release in years. Nevertheless, it might be useful to you, and can be installed - separately like any other Python package. You can also try the Python library - "vnoise", which is a pure Python implementation of the Improved Perlin Noise - algorithm. Note that py5 can also employ Java libraries, so consider "FastNoise - Lite" to experiment with a large selection of noise algorithms with efficient - implementations. - """ - return _py5sketch.noise(*args) - - -@overload -def os_noise( - x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], / -) -> Union[float, npt.NDArray]: - """Generate pseudo-random noise values for specific coodinates using the - OpenSimplex 2 algorithm (smooth version / SuperSimplex). - - Methods - ------- - - You can use any of the following signatures: - - * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], /) -> Union[float, npt.NDArray] - * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] - * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], w: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] - - Parameters - ---------- - - w: Union[float, npt.NDArray] - w-coordinate in noise space - - x: Union[float, npt.NDArray] - x-coordinate in noise space - - y: Union[float, npt.NDArray] - y-coordinate in noise space - - z: Union[float, npt.NDArray] - z-coordinate in noise space - - Notes - ----- - - Generate pseudo-random noise values for specific coodinates using the - OpenSimplex 2 algorithm (smooth version / SuperSimplex). Noise functions are - random sequence generators that produce a more natural, harmonic succession of - numbers compared to the `random()` method. - - In contrast to the `random()` method, noise is defined in an n-dimensional - space, in which each coordinate corresponds to a fixed pseudo-random value - (fixed only for the lifespan of the program). The noise value can be animated by - moving through the noise space, as demonstrated in the examples. Any dimension - can also be interpreted as time. An easy way to animate the noise value is to - pass the `os_noise()` method the `frame_count` divided by a scaling factor, as - is done in a few of the examples. - - The generated noise values for this method will be between -1 and 1, and can be - generated in 2, 3, or 4 dimensions. To generate noise in 1 dimension, add a - constant value as an extra parameter, as shown in a few examples. Py5 also - provides the `noise()` method, which generates noise using Processing's noise - algorithm. That algorithm typically generates noise values between 0 and 1, and - can be generated in 1, 2, or 3 dimensions. Be aware of both of these differences - when modifying your code to switch from one to the other. There are other - differences in the character of the noise values generated by both methods, so - you'll need to do some experimentation to get the results you want. - - The nature of the noise values returned can be adjusted with `os_noise_seed()`. - - Another way to adjust the character of the resulting sequence is the scale of - the input coordinates. As the method works within an infinite space, the value - of the coordinates doesn't matter as such; only the distance between successive - coordinates is important. As a general rule, the smaller the difference between - coordinates, the smoother the resulting noise sequence. Steps of 0.005-0.03 work - best for most applications, but this will differ depending on the use case and - the noise settings. - - Py5's `os_noise()` method can also accept numpy arrays as parameters. It will - use broadcasting when needed and calculate the values efficiently. Using numpy - array parameters will be much faster and efficient than calling the `os_noise()` - method repeatedly in a loop. See the examples to see how this can be done. The - noise algorithm for this method is implemented in Java. - - Noise generation is a rich and complex topic, and there are many noise - algorithms and libraries available that are worth learning about. Early versions - of py5 used the Python "noise" library, which can generate noise using the - "Improved Perlin Noise" algorithm (as described in Ken Perlin's 2002 SIGGRAPH - paper) and the Simplex Noise algorithm (also developed by Ken Perlin). That - Python library was removed from py5 because it has some bugs and hasn't had a - release in years. Nevertheless, it might be useful to you, and can be installed - separately like any other Python package. You can also try the Python library - "vnoise", which is a pure Python implementation of the Improved Perlin Noise - algorithm. Note that py5 can also employ Java libraries, so consider "FastNoise - Lite" to experiment with a large selection of noise algorithms with efficient - implementations. - """ - pass - - -@overload -def os_noise( - x: Union[float, npt.NDArray], - y: Union[float, npt.NDArray], - z: Union[float, npt.NDArray], - /, -) -> Union[float, npt.NDArray]: - """Generate pseudo-random noise values for specific coodinates using the - OpenSimplex 2 algorithm (smooth version / SuperSimplex). - - Methods - ------- - - You can use any of the following signatures: - - * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], /) -> Union[float, npt.NDArray] - * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] - * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], w: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] - - Parameters - ---------- - - w: Union[float, npt.NDArray] - w-coordinate in noise space - - x: Union[float, npt.NDArray] - x-coordinate in noise space - - y: Union[float, npt.NDArray] - y-coordinate in noise space - - z: Union[float, npt.NDArray] - z-coordinate in noise space - - Notes - ----- - - Generate pseudo-random noise values for specific coodinates using the - OpenSimplex 2 algorithm (smooth version / SuperSimplex). Noise functions are - random sequence generators that produce a more natural, harmonic succession of - numbers compared to the `random()` method. - - In contrast to the `random()` method, noise is defined in an n-dimensional - space, in which each coordinate corresponds to a fixed pseudo-random value - (fixed only for the lifespan of the program). The noise value can be animated by - moving through the noise space, as demonstrated in the examples. Any dimension - can also be interpreted as time. An easy way to animate the noise value is to - pass the `os_noise()` method the `frame_count` divided by a scaling factor, as - is done in a few of the examples. - - The generated noise values for this method will be between -1 and 1, and can be - generated in 2, 3, or 4 dimensions. To generate noise in 1 dimension, add a - constant value as an extra parameter, as shown in a few examples. Py5 also - provides the `noise()` method, which generates noise using Processing's noise - algorithm. That algorithm typically generates noise values between 0 and 1, and - can be generated in 1, 2, or 3 dimensions. Be aware of both of these differences - when modifying your code to switch from one to the other. There are other - differences in the character of the noise values generated by both methods, so - you'll need to do some experimentation to get the results you want. - - The nature of the noise values returned can be adjusted with `os_noise_seed()`. - - Another way to adjust the character of the resulting sequence is the scale of - the input coordinates. As the method works within an infinite space, the value - of the coordinates doesn't matter as such; only the distance between successive - coordinates is important. As a general rule, the smaller the difference between - coordinates, the smoother the resulting noise sequence. Steps of 0.005-0.03 work - best for most applications, but this will differ depending on the use case and - the noise settings. - - Py5's `os_noise()` method can also accept numpy arrays as parameters. It will - use broadcasting when needed and calculate the values efficiently. Using numpy - array parameters will be much faster and efficient than calling the `os_noise()` - method repeatedly in a loop. See the examples to see how this can be done. The - noise algorithm for this method is implemented in Java. + repeatedly in a loop. See the examples to see how this can be done. Noise generation is a rich and complex topic, and there are many noise algorithms and libraries available that are worth learning about. Early versions @@ -19514,16 +19672,12 @@ def os_noise( Lite" to experiment with a large selection of noise algorithms with efficient implementations. """ - pass + return _py5sketch.noise(*args) @overload def os_noise( - x: Union[float, npt.NDArray], - y: Union[float, npt.NDArray], - z: Union[float, npt.NDArray], - w: Union[float, npt.NDArray], - /, + x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], / ) -> Union[float, npt.NDArray]: """Generate pseudo-random noise values for specific coodinates using the OpenSimplex 2 algorithm (smooth version / SuperSimplex). @@ -19610,7 +19764,13 @@ def os_noise( pass -def os_noise(*args) -> Union[float, npt.NDArray]: +@overload +def os_noise( + x: Union[float, npt.NDArray], + y: Union[float, npt.NDArray], + z: Union[float, npt.NDArray], + /, +) -> Union[float, npt.NDArray]: """Generate pseudo-random noise values for specific coodinates using the OpenSimplex 2 algorithm (smooth version / SuperSimplex). @@ -19661,372 +19821,218 @@ def os_noise(*args) -> Union[float, npt.NDArray]: algorithm. That algorithm typically generates noise values between 0 and 1, and can be generated in 1, 2, or 3 dimensions. Be aware of both of these differences when modifying your code to switch from one to the other. There are other - differences in the character of the noise values generated by both methods, so - you'll need to do some experimentation to get the results you want. - - The nature of the noise values returned can be adjusted with `os_noise_seed()`. - - Another way to adjust the character of the resulting sequence is the scale of - the input coordinates. As the method works within an infinite space, the value - of the coordinates doesn't matter as such; only the distance between successive - coordinates is important. As a general rule, the smaller the difference between - coordinates, the smoother the resulting noise sequence. Steps of 0.005-0.03 work - best for most applications, but this will differ depending on the use case and - the noise settings. - - Py5's `os_noise()` method can also accept numpy arrays as parameters. It will - use broadcasting when needed and calculate the values efficiently. Using numpy - array parameters will be much faster and efficient than calling the `os_noise()` - method repeatedly in a loop. See the examples to see how this can be done. The - noise algorithm for this method is implemented in Java. - - Noise generation is a rich and complex topic, and there are many noise - algorithms and libraries available that are worth learning about. Early versions - of py5 used the Python "noise" library, which can generate noise using the - "Improved Perlin Noise" algorithm (as described in Ken Perlin's 2002 SIGGRAPH - paper) and the Simplex Noise algorithm (also developed by Ken Perlin). That - Python library was removed from py5 because it has some bugs and hasn't had a - release in years. Nevertheless, it might be useful to you, and can be installed - separately like any other Python package. You can also try the Python library - "vnoise", which is a pure Python implementation of the Improved Perlin Noise - algorithm. Note that py5 can also employ Java libraries, so consider "FastNoise - Lite" to experiment with a large selection of noise algorithms with efficient - implementations. - """ - return _py5sketch.os_noise(*args) - - -############################################################################## -# module functions from threads.py -############################################################################## - - -def launch_thread( - f: Callable, - name: str = None, - *, - daemon: bool = True, - args: tuple = None, - kwargs: dict = None, -) -> str: - """Launch a new thread to execute a function in parallel with your Sketch code. - - Parameters - ---------- - - args: tuple = None - positional arguments to pass to the given function - - daemon: bool = True - if the thread should be a daemon thread - - f: Callable - function to call in the launched thread - - kwargs: dict = None - keyword arguments to pass to the given function - - name: str = None - name of thread to be created - - Notes - ----- - - Launch a new thread to execute a function in parallel with your Sketch code. - This can be useful for executing non-py5 code that would otherwise slow down the - animation thread and reduce the Sketch's frame rate. - - The `name` parameter is optional but useful if you want to monitor the thread - with other methods such as `has_thread()`. If the provided `name` is identical - to an already running thread, the running thread will first be stopped with a - call to `stop_thread()` with the `wait` parameter equal to `True`. - - Use the `args` and `kwargs` parameters to pass positional and keyword arguments - to the function. - - Use the `daemon` parameter to make the launched thread a daemon that will run - without blocking Python from exiting. This parameter defaults to `True`, meaning - that function execution can be interupted if the Python process exits. Note that - if the Python process continues running after the Sketch exits, which is - typically the case when using a Jupyter Notebook, this parameter won't have any - effect unless if you try to restart the Notebook kernel. Generally speaking, - setting this parameter to `False` causes problems but it is available for those - who really need it. See `stop_all_threads()` for a better approach to exit - threads. - - The new thread is a Python thread, so all the usual caveats about the Global - Interpreter Lock (GIL) apply here. - """ - return _py5sketch.launch_thread( - f, - name=name, - daemon=daemon, - args=args, - kwargs=kwargs, - ) - - -def launch_promise_thread( - f: Callable, - name: str = None, - *, - daemon: bool = True, - args: tuple = None, - kwargs: dict = None, -) -> Py5Promise: - """Create a `Py5Promise` object that will store the returned result of a function - when that function completes. - - Parameters - ---------- - - args: tuple = None - positional arguments to pass to the given function - - daemon: bool = True - if the thread should be a daemon thread - - f: Callable - function to call in the launched thread - - kwargs: dict = None - keyword arguments to pass to the given function - - name: str = None - name of thread to be created - - Notes - ----- - - Create a `Py5Promise` object that will store the returned result of a function - when that function completes. This can be useful for executing non-py5 code that - would otherwise slow down the animation thread and reduce the Sketch's frame - rate. - - The `Py5Promise` object has an `is_ready` property that will be `True` when the - `result` property contains the value function `f` returned. Before then, the - `result` property will be `None`. - - The `name` parameter is optional but useful if you want to monitor the thread - with other methods such as `has_thread()`. If the provided `name` is identical - to an already running thread, the running thread will first be stopped with a - call to `stop_thread()` with the `wait` parameter equal to `True`. - - Use the `args` and `kwargs` parameters to pass positional and keyword arguments - to the function. - - Use the `daemon` parameter to make the launched thread a daemon that will run - without blocking Python from exiting. This parameter defaults to `True`, meaning - that function execution can be interupted if the Python process exits. Note that - if the Python process continues running after the Sketch exits, which is - typically the case when using a Jupyter Notebook, this parameter won't have any - effect unless if you try to restart the Notebook kernel. Generally speaking, - setting this parameter to `False` causes problems but it is available for those - who really need it. See `stop_all_threads()` for a better approach to exit - threads. - - The new thread is a Python thread, so all the usual caveats about the Global - Interpreter Lock (GIL) apply here. - """ - return _py5sketch.launch_promise_thread( - f, - name=name, - daemon=daemon, - args=args, - kwargs=kwargs, - ) - - -def launch_repeating_thread( - f: Callable, - name: str = None, - *, - time_delay: float = 0, - daemon: bool = True, - args: tuple = None, - kwargs: dict = None, -) -> str: - """Launch a new thread that will repeatedly execute a function in parallel with - your Sketch code. - - Parameters - ---------- - - args: tuple = None - positional arguments to pass to the given function - - daemon: bool = True - if the thread should be a daemon thread - - f: Callable - function to call in the launched thread - - kwargs: dict = None - keyword arguments to pass to the given function - - name: str = None - name of thread to be created - - time_delay: float = 0 - time delay in seconds between calls to the given function - - Notes - ----- - - Launch a new thread that will repeatedly execute a function in parallel with - your Sketch code. This can be useful for executing non-py5 code that would - otherwise slow down the animation thread and reduce the Sketch's frame rate. - - Use the `time_delay` parameter to set the time in seconds between one call to - function `f` and the next call. Set this parameter to `0` if you want each call - to happen immediately after the previous call finishes. If the function `f` - takes longer than expected to finish, py5 will wait for it to finish before - making the next call. There will not be overlapping calls to function `f`. - - The `name` parameter is optional but useful if you want to monitor the thread - with other methods such as `has_thread()`. If the provided `name` is identical - to an already running thread, the running thread will first be stopped with a - call to `stop_thread()` with the `wait` parameter equal to `True`. - - Use the `args` and `kwargs` parameters to pass positional and keyword arguments - to the function. - - Use the `daemon` parameter to make the launched thread a daemon that will run - without blocking Python from exiting. This parameter defaults to `True`, meaning - that function execution can be interupted if the Python process exits. Note that - if the Python process continues running after the Sketch exits, which is - typically the case when using a Jupyter Notebook, this parameter won't have any - effect unless if you try to restart the Notebook kernel. Generally speaking, - setting this parameter to `False` causes problems but it is available for those - who really need it. See `stop_all_threads()` for a better approach to exit - threads. + differences in the character of the noise values generated by both methods, so + you'll need to do some experimentation to get the results you want. - The new thread is a Python thread, so all the usual caveats about the Global - Interpreter Lock (GIL) apply here. - """ - return _py5sketch.launch_repeating_thread( - f, - name=name, - time_delay=time_delay, - daemon=daemon, - args=args, - kwargs=kwargs, - ) + The nature of the noise values returned can be adjusted with `os_noise_seed()`. + Another way to adjust the character of the resulting sequence is the scale of + the input coordinates. As the method works within an infinite space, the value + of the coordinates doesn't matter as such; only the distance between successive + coordinates is important. As a general rule, the smaller the difference between + coordinates, the smoother the resulting noise sequence. Steps of 0.005-0.03 work + best for most applications, but this will differ depending on the use case and + the noise settings. -def has_thread(name: str) -> None: - """Determine if a thread of a given name exists and is currently running. + Py5's `os_noise()` method can also accept numpy arrays as parameters. It will + use broadcasting when needed and calculate the values efficiently. Using numpy + array parameters will be much faster and efficient than calling the `os_noise()` + method repeatedly in a loop. See the examples to see how this can be done. The + noise algorithm for this method is implemented in Java. - Parameters - ---------- + Noise generation is a rich and complex topic, and there are many noise + algorithms and libraries available that are worth learning about. Early versions + of py5 used the Python "noise" library, which can generate noise using the + "Improved Perlin Noise" algorithm (as described in Ken Perlin's 2002 SIGGRAPH + paper) and the Simplex Noise algorithm (also developed by Ken Perlin). That + Python library was removed from py5 because it has some bugs and hasn't had a + release in years. Nevertheless, it might be useful to you, and can be installed + separately like any other Python package. You can also try the Python library + "vnoise", which is a pure Python implementation of the Improved Perlin Noise + algorithm. Note that py5 can also employ Java libraries, so consider "FastNoise + Lite" to experiment with a large selection of noise algorithms with efficient + implementations. + """ + pass - name: str - name of thread - Notes - ----- +@overload +def os_noise( + x: Union[float, npt.NDArray], + y: Union[float, npt.NDArray], + z: Union[float, npt.NDArray], + w: Union[float, npt.NDArray], + /, +) -> Union[float, npt.NDArray]: + """Generate pseudo-random noise values for specific coodinates using the + OpenSimplex 2 algorithm (smooth version / SuperSimplex). - Determine if a thread of a given name exists and is currently running. You can - get the list of all currently running threads with `list_threads()`. - """ - return _py5sketch.has_thread(name) + Methods + ------- + You can use any of the following signatures: -def join_thread(name: str, *, timeout: float = None) -> bool: - """Join the Python thread associated with the given thread name. + * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], /) -> Union[float, npt.NDArray] + * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] + * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], w: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] Parameters ---------- - name: str - name of thread + w: Union[float, npt.NDArray] + w-coordinate in noise space - timeout: float = None - maximum time in seconds to wait for the thread to join + x: Union[float, npt.NDArray] + x-coordinate in noise space + + y: Union[float, npt.NDArray] + y-coordinate in noise space + + z: Union[float, npt.NDArray] + z-coordinate in noise space Notes ----- - Join the Python thread associated with the given thread name. The - `join_thread()` method will wait until the named thread has finished executing - before returning. Use the `timeout` parameter to set an upper limit for the - number of seconds to wait. This method will return right away if the named - thread does not exist or the thread has already finished executing. You can get - the list of all currently running threads with `list_threads()`. - - This method will return `True` if the named thread has completed execution and - `False` if the named thread is still executing. It will only return `False` if - you use the `timeout` parameter and the method is not able to join with the - thread within that time limit. - """ - return _py5sketch.join_thread(name, timeout=timeout) + Generate pseudo-random noise values for specific coodinates using the + OpenSimplex 2 algorithm (smooth version / SuperSimplex). Noise functions are + random sequence generators that produce a more natural, harmonic succession of + numbers compared to the `random()` method. + In contrast to the `random()` method, noise is defined in an n-dimensional + space, in which each coordinate corresponds to a fixed pseudo-random value + (fixed only for the lifespan of the program). The noise value can be animated by + moving through the noise space, as demonstrated in the examples. Any dimension + can also be interpreted as time. An easy way to animate the noise value is to + pass the `os_noise()` method the `frame_count` divided by a scaling factor, as + is done in a few of the examples. -def stop_thread(name: str, wait: bool = False) -> None: - """Stop a thread of a given name. + The generated noise values for this method will be between -1 and 1, and can be + generated in 2, 3, or 4 dimensions. To generate noise in 1 dimension, add a + constant value as an extra parameter, as shown in a few examples. Py5 also + provides the `noise()` method, which generates noise using Processing's noise + algorithm. That algorithm typically generates noise values between 0 and 1, and + can be generated in 1, 2, or 3 dimensions. Be aware of both of these differences + when modifying your code to switch from one to the other. There are other + differences in the character of the noise values generated by both methods, so + you'll need to do some experimentation to get the results you want. - Parameters - ---------- + The nature of the noise values returned can be adjusted with `os_noise_seed()`. - name: str - name of thread + Another way to adjust the character of the resulting sequence is the scale of + the input coordinates. As the method works within an infinite space, the value + of the coordinates doesn't matter as such; only the distance between successive + coordinates is important. As a general rule, the smaller the difference between + coordinates, the smoother the resulting noise sequence. Steps of 0.005-0.03 work + best for most applications, but this will differ depending on the use case and + the noise settings. - wait: bool = False - wait for thread to exit before returning + Py5's `os_noise()` method can also accept numpy arrays as parameters. It will + use broadcasting when needed and calculate the values efficiently. Using numpy + array parameters will be much faster and efficient than calling the `os_noise()` + method repeatedly in a loop. See the examples to see how this can be done. The + noise algorithm for this method is implemented in Java. - Notes - ----- + Noise generation is a rich and complex topic, and there are many noise + algorithms and libraries available that are worth learning about. Early versions + of py5 used the Python "noise" library, which can generate noise using the + "Improved Perlin Noise" algorithm (as described in Ken Perlin's 2002 SIGGRAPH + paper) and the Simplex Noise algorithm (also developed by Ken Perlin). That + Python library was removed from py5 because it has some bugs and hasn't had a + release in years. Nevertheless, it might be useful to you, and can be installed + separately like any other Python package. You can also try the Python library + "vnoise", which is a pure Python implementation of the Improved Perlin Noise + algorithm. Note that py5 can also employ Java libraries, so consider "FastNoise + Lite" to experiment with a large selection of noise algorithms with efficient + implementations. + """ + pass - Stop a thread of a given name. The `wait` parameter determines if the method - call will return right away or wait for the thread to exit. - This won't do anything useful if the thread was launched with either - `launch_thread()` or `launch_promise_thread()` and the `wait` parameter is - `False`. Non-repeating threads are executed once and will stop when they - complete execution. Setting the `wait` parameter to `True` will merely block - until the thread exits on its own. Killing off a running thread in Python is - complicated and py5 cannot do that for you. If you want a thread to perform some - action repeatedly and be interuptable, use `launch_repeating_thread()` instead. +def os_noise(*args) -> Union[float, npt.NDArray]: + """Generate pseudo-random noise values for specific coodinates using the + OpenSimplex 2 algorithm (smooth version / SuperSimplex). - Use `has_thread()` to determine if a thread of a given name exists and - `list_threads()` to get a list of all thread names. Use `stop_all_threads()` to - stop all threads. - """ - return _py5sketch.stop_thread(name, wait=wait) + Methods + ------- + You can use any of the following signatures: -def stop_all_threads(wait: bool = False) -> None: - """Stop all running threads. + * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], /) -> Union[float, npt.NDArray] + * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] + * os_noise(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], w: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray] Parameters ---------- - wait: bool = False - wait for thread to exit before returning + w: Union[float, npt.NDArray] + w-coordinate in noise space + + x: Union[float, npt.NDArray] + x-coordinate in noise space + + y: Union[float, npt.NDArray] + y-coordinate in noise space + + z: Union[float, npt.NDArray] + z-coordinate in noise space Notes ----- - Stop all running threads. The `wait` parameter determines if the method call - will return right away or wait for the threads to exit. + Generate pseudo-random noise values for specific coodinates using the + OpenSimplex 2 algorithm (smooth version / SuperSimplex). Noise functions are + random sequence generators that produce a more natural, harmonic succession of + numbers compared to the `random()` method. - When the Sketch shuts down, `stop_all_threads(wait=False)` is called for you. If - you would rather the Sketch waited for threads to exit, create an `exiting` - method and make a call to `stop_all_threads(wait=True)`. - """ - return _py5sketch.stop_all_threads(wait=wait) + In contrast to the `random()` method, noise is defined in an n-dimensional + space, in which each coordinate corresponds to a fixed pseudo-random value + (fixed only for the lifespan of the program). The noise value can be animated by + moving through the noise space, as demonstrated in the examples. Any dimension + can also be interpreted as time. An easy way to animate the noise value is to + pass the `os_noise()` method the `frame_count` divided by a scaling factor, as + is done in a few of the examples. + The generated noise values for this method will be between -1 and 1, and can be + generated in 2, 3, or 4 dimensions. To generate noise in 1 dimension, add a + constant value as an extra parameter, as shown in a few examples. Py5 also + provides the `noise()` method, which generates noise using Processing's noise + algorithm. That algorithm typically generates noise values between 0 and 1, and + can be generated in 1, 2, or 3 dimensions. Be aware of both of these differences + when modifying your code to switch from one to the other. There are other + differences in the character of the noise values generated by both methods, so + you'll need to do some experimentation to get the results you want. -def list_threads() -> None: - """List the names of all of the currently running threads. + The nature of the noise values returned can be adjusted with `os_noise_seed()`. - Notes - ----- + Another way to adjust the character of the resulting sequence is the scale of + the input coordinates. As the method works within an infinite space, the value + of the coordinates doesn't matter as such; only the distance between successive + coordinates is important. As a general rule, the smaller the difference between + coordinates, the smoother the resulting noise sequence. Steps of 0.005-0.03 work + best for most applications, but this will differ depending on the use case and + the noise settings. - List the names of all of the currently running threads. The names of previously - launched threads that have exited will be removed from the list. + Py5's `os_noise()` method can also accept numpy arrays as parameters. It will + use broadcasting when needed and calculate the values efficiently. Using numpy + array parameters will be much faster and efficient than calling the `os_noise()` + method repeatedly in a loop. See the examples to see how this can be done. The + noise algorithm for this method is implemented in Java. + + Noise generation is a rich and complex topic, and there are many noise + algorithms and libraries available that are worth learning about. Early versions + of py5 used the Python "noise" library, which can generate noise using the + "Improved Perlin Noise" algorithm (as described in Ken Perlin's 2002 SIGGRAPH + paper) and the Simplex Noise algorithm (also developed by Ken Perlin). That + Python library was removed from py5 because it has some bugs and hasn't had a + release in years. Nevertheless, it might be useful to you, and can be installed + separately like any other Python package. You can also try the Python library + "vnoise", which is a pure Python implementation of the Improved Perlin Noise + algorithm. Note that py5 can also employ Java libraries, so consider "FastNoise + Lite" to experiment with a large selection of noise algorithms with efficient + implementations. """ - return _py5sketch.list_threads() + return _py5sketch.os_noise(*args) PI = np.pi @@ -22806,6 +22812,7 @@ def run_sketch( sketch_args: list[str] = None, sketch_functions: dict[str, Callable] = None, jclassname: str = None, + jclass_params: tuple[Any] = (), _osx_alt_run_method: bool = True, ) -> None: """Run the Sketch. @@ -22816,6 +22823,9 @@ def run_sketch( block: bool = None method returns immediately (False) or blocks until Sketch exits (True) + jclass_params: tuple[Any] = () + parameters to pass to constructor when using py5 in processing mode + jclassname: str = None canonical name of class to instantiate when using py5 in processing mode @@ -22826,7 +22836,7 @@ def run_sketch( command line arguments that become Sketch arguments sketch_functions: dict[str, Callable] = None - sketch methods when using module mode + sketch methods when using [module mode](content-py5-modes-module-mode) Notes ----- @@ -22882,7 +22892,9 @@ class mode. Don't forget you can always replace the `draw()` function in a The `jclassname` parameter should only be used when programming in Processing Mode. This value must be the canonical name of your Processing Sketch class (i.e. `"org.test.MySketch"`). The class must inherit from `py5.core.SketchBase`. - Read py5's online documentation to learn more about Processing Mode.""" + To pass parameters to your Processing Sketch class constructor, use the + `jclass_params` parameter. Read py5's online documentation to learn more about + Processing Mode.""" caller_locals = inspect.stack()[1].frame.f_locals caller_globals = inspect.stack()[1].frame.f_globals functions, function_param_counts = bridge._extract_py5_user_function_data( @@ -22919,7 +22931,7 @@ class mode. Don't forget you can always replace the `draw()` function in a ) return if _py5sketch.is_dead or jclassname: - _py5sketch = Sketch(jclassname=jclassname) + _py5sketch = Sketch(jclassname=jclassname, jclass_params=jclass_params) _prepare_dynamic_variables(caller_locals, caller_globals) diff --git a/py5/jars/py5.jar b/py5/jars/py5.jar index a3ea4f0..c040a31 100644 Binary files a/py5/jars/py5.jar and b/py5/jars/py5.jar differ diff --git a/py5/mixins/data.py b/py5/mixins/data.py index cbc05c9..39eaedc 100644 --- a/py5/mixins/data.py +++ b/py5/mixins/data.py @@ -141,8 +141,6 @@ def load_strings( ) -> list[str]: """Load a list of strings from a file or URL. - Underlying Processing method: Sketch.loadStrings - Parameters ---------- @@ -189,8 +187,6 @@ def save_strings( ) -> None: """Save a list of strings to a file. - Underlying Processing method: Sketch.saveStrings - Parameters ---------- @@ -229,8 +225,6 @@ def load_bytes( ) -> bytearray: """Load byte data from a file or URL. - Underlying Processing method: Sketch.loadBytes - Parameters ---------- @@ -277,8 +271,6 @@ def save_bytes( ) -> None: """Save byte data to a file. - Underlying Processing method: Sketch.saveBytes - Parameters ---------- @@ -305,8 +297,6 @@ def save_bytes( def load_pickle(self, pickle_path: Union[str, Path]) -> Any: """Load a pickled Python object from a file. - Underlying Processing method: Sketch.loadPickle - Parameters ---------- @@ -321,7 +311,16 @@ def load_pickle(self, pickle_path: Union[str, Path]) -> Any: path. There are security risks associated with Python pickle files. A pickle file can - contain malicious code, so never load a pickle file from an untrusted source.""" + contain malicious code, so never load a pickle file from an untrusted source. + + When using py5 in imported mode, pickling will not work on objects instantiated + from new classes you have defined yourself on the main sketch file. This applies + to py5's `save_pickle()` and `load_pickle()` methods, as well as the Python's + standard library pickle module methods they depend upon. If you need to pickle + objects from classes you defined, move the class definitions to a different .py + file that you import as a module or import the classes from. Otherwise, you + could also try using module mode if you want to use pickle with your classes and + keep all the sketch code in a single file.""" path = Path(pickle_path) if not path.is_absolute(): cwd = self.sketch_path() @@ -338,8 +337,6 @@ def load_pickle(self, pickle_path: Union[str, Path]) -> Any: def save_pickle(self, obj: Any, filename: Union[str, Path]) -> None: """Pickle a Python object to a file. - Underlying Processing method: Sketch.savePickle - Parameters ---------- @@ -356,10 +353,19 @@ def save_pickle(self, obj: Any, filename: Union[str, Path]) -> None: be saved relative to the current working directory (`sketch_path()`). The saved file can be reloaded with `load_pickle()`. - Object "pickling" is a method for serializing objects and saving them to a file - for later retrieval. The recreated objects will be clones of the original + Object "pickling" is a technique for serializing objects and saving them to a + file for later retrieval. The recreated objects will be clones of the original objects. Not all Python objects can be saved to a Python pickle file. This - limitation prevents any py5 object from being pickled.""" + limitation prevents any py5 object from being pickled. + + When using py5 in imported mode, pickling will not work on objects instantiated + from new classes you have defined yourself on the main sketch file. This applies + to py5's `save_pickle()` and `load_pickle()` methods, as well as the Python's + standard library pickle module methods they depend upon. If you need to pickle + objects from classes you defined, move the class definitions to a different .py + file that you import as a module or import the classes from. Otherwise, you + could also try using module mode if you want to use pickle with your classes and + keep all the sketch code in a single file.""" path = Path(filename) if not path.is_absolute(): path = self.sketch_path() / filename diff --git a/py5/mixins/math.py b/py5/mixins/math.py index 53f17b0..327443f 100644 --- a/py5/mixins/math.py +++ b/py5/mixins/math.py @@ -277,7 +277,7 @@ def constrain( low: Union[float, npt.NDArray], high: Union[float, npt.NDArray], ) -> Union[float, npt.NDArray]: - """Constrains a value to not exceed a maximum and minimum value. + """Constrains a value between a minimum and maximum value. Parameters ---------- @@ -286,15 +286,15 @@ def constrain( the value to constrain high: Union[float, npt.NDArray] - minimum limit + maximum limit low: Union[float, npt.NDArray] - maximum limit + minimum limit Notes ----- - Constrains a value to not exceed a maximum and minimum value.""" + Constrains a value between a minimum and maximum value.""" return np.where(amt < low, low, np.where(amt > high, high, amt)) @classmethod @@ -733,7 +733,7 @@ def floor(cls, value: Union[float, npt.ArrayLike]) -> Union[int, npt.NDArray]: parameter. This function makes a call to the numpy `floor()` function.""" - return np.floor(value).astype(np.int_) + return np.floor(value).astype(np.int64) @classmethod def ceil(cls, value: Union[float, npt.ArrayLike]) -> Union[int, npt.NDArray]: @@ -753,7 +753,7 @@ def ceil(cls, value: Union[float, npt.ArrayLike]) -> Union[int, npt.NDArray]: the parameter. This function makes a call to the numpy `ceil()` function.""" - return np.ceil(value).astype(np.int_) + return np.ceil(value).astype(np.int64) @classmethod def exp(cls, value: Union[float, npt.ArrayLike]) -> Union[float, npt.NDArray]: diff --git a/py5/mixins/pixels.py b/py5/mixins/pixels.py index 8cccd3b..b14d4aa 100644 --- a/py5/mixins/pixels.py +++ b/py5/mixins/pixels.py @@ -19,6 +19,7 @@ # ***************************************************************************** from __future__ import annotations +import tempfile import threading from io import BytesIO from pathlib import Path @@ -30,6 +31,7 @@ from PIL import Image from PIL.Image import Image as PIL_Image +from .. import bridge from ..decorators import _hex_converter _Sketch = jpype.JClass("py5.core.Sketch") @@ -679,14 +681,36 @@ def save( if isinstance(self._instance, _Sketch) else self._instance.parent ) - if not isinstance(filename, BytesIO): - filename = Path(str(sketch_instance.savePath(str(filename)))) - self.load_np_pixels() - arr = ( - self.np_pixels[:, :, 1:] - if drop_alpha - else np.roll(self.np_pixels, -1, axis=2) - ) + if ( + isinstance(self._instance, _Sketch) + and bridge.check_run_method_callstack() + and self._py5_bridge.current_running_method not in ["setup", "draw"] + ): + if ( + drop_alpha + # and not use_thread # ignore use_thread because the alternative is always slower + and format is None + and isinstance((filename_param := Path(str(filename))), (str, Path)) + and filename_param.suffix.lower() in [".jpg", ".jpeg", ".png"] + ): + self._instance.save(filename) + return + + with tempfile.TemporaryDirectory() as td: + temp_filename = Path(td) / "temp.png" + self._instance.save(temp_filename) + arr = np.asarray(Image.open(temp_filename)) + if not drop_alpha: + arr = np.dstack([arr, np.full(arr.shape[:2], 255, dtype=np.uint8)]) + else: + if not isinstance(filename, BytesIO): + filename = Path(str(sketch_instance.savePath(str(filename)))) + self.load_np_pixels() + arr = ( + self.np_pixels[:, :, 1:] + if drop_alpha + else np.roll(self.np_pixels, -1, axis=2) + ) if use_thread: diff --git a/py5/mouseevent.py b/py5/mouseevent.py index fbb24d8..3c9611a 100644 --- a/py5/mouseevent.py +++ b/py5/mouseevent.py @@ -125,6 +125,9 @@ def get_count(self) -> int: Get the number of mouse clicks. This will be 1 for a single mouse click and 2 for a double click. The value can be much higher if the user clicks fast enough. + + This method also responds to the mouse wheel. It will be 1 when the mouse wheel + is rotating down and -1 when the mouse wheel is rotating up. """ return self._instance.getCount() diff --git a/py5/reference.py b/py5/reference.py index 1fb2431..2ce43f2 100644 --- a/py5/reference.py +++ b/py5/reference.py @@ -25,6 +25,7 @@ settings=[0], setup=[0], draw=[0], + predraw_update=[0], pre_draw=[0], post_draw=[0], key_pressed=[0, 1], @@ -246,14 +247,22 @@ (('Sketch', 'save_bytes'), ['(bytes_data: Union[bytes, bytearray], filename: Union[str, Path]) -> None']), (('Sketch', 'load_pickle'), ['(pickle_path: Union[str, Path]) -> Any']), (('Sketch', 'save_pickle'), ['(obj: Any, filename: Union[str, Path]) -> None']), + (('Sketch', 'set_println_stream'), ['(println_stream: Any) -> None']), + (('Sketch', 'println'), ['(*args, sep: str = " ", end: str = "\\n", stderr: bool = False) -> None']), + (('Sketch', 'launch_thread'), ['(f: Callable, name: str = None, *, daemon: bool = True, args: tuple = None, kwargs: dict = None, ) -> str']), + (('Sketch', 'launch_promise_thread'), ['(f: Callable, name: str = None, *, daemon: bool = True, args: tuple = None, kwargs: dict = None, ) -> Py5Promise']), + (('Sketch', 'launch_repeating_thread'), ['(f: Callable, name: str = None, *, time_delay: float = 0, daemon: bool = True, args: tuple = None, kwargs: dict = None, ) -> str']), + (('Sketch', 'has_thread'), ['(name: str) -> None']), + (('Sketch', 'join_thread'), ['(name: str, *, timeout: float = None) -> bool']), + (('Sketch', 'stop_thread'), ['(name: str, wait: bool = False) -> None']), + (('Sketch', 'stop_all_threads'), ['(wait: bool = False) -> None']), + (('Sketch', 'list_threads'), ['() -> None']), (('Sketch', 'load_np_pixels'), ['() -> None']), (('Sketch', 'update_np_pixels'), ['() -> None']), (('Sketch', 'set_np_pixels'), ['(array: npt.NDArray[np.uint8], bands: str = "ARGB") -> None']), (('Sketch', 'get_np_pixels'), ['(*, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None) -> npt.NDArray[np.uint8]', '(x: int, y: int, w: int, h: int, /, *, bands: str = "ARGB", dst: npt.NDArray[np.uint8] = None, ) -> npt.NDArray[np.uint8]']), (('Sketch', 'to_pil'), ['() -> PIL_Image', '(x: int, y: int, w: int, h: int, /) -> PIL_Image']), (('Sketch', 'save'), ['(filename: Union[str, Path, BytesIO], *, format: str = None, drop_alpha: bool = True, use_thread: bool = False, **params, ) -> None']), - (('Sketch', 'set_println_stream'), ['(println_stream: Any) -> None']), - (('Sketch', 'println'), ['(*args, sep: str = " ", end: str = "\\n", stderr: bool = False) -> None']), (('Sketch', 'hex_color'), ['(color: int) -> str']), (('Sketch', 'sin'), ['(angle: Union[float, npt.ArrayLike]) -> Union[float, npt.NDArray]']), (('Sketch', 'cos'), ['(angle: Union[float, npt.ArrayLike]) -> Union[float, npt.NDArray]']), @@ -284,14 +293,6 @@ (('Sketch', 'random_gaussian'), ['() -> float', '(loc: float, /) -> float', '(loc: float, scale: float, /) -> float']), (('Sketch', 'noise'), ['(x: Union[float, npt.NDArray], /) -> Union[float, npt.NDArray]', '(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], /) -> Union[float, npt.NDArray]', '(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray]']), (('Sketch', 'os_noise'), ['(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], /) -> Union[float, npt.NDArray]', '(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray]', '(x: Union[float, npt.NDArray], y: Union[float, npt.NDArray], z: Union[float, npt.NDArray], w: Union[float, npt.NDArray], /, ) -> Union[float, npt.NDArray]']), - (('Sketch', 'launch_thread'), ['(f: Callable, name: str = None, *, daemon: bool = True, args: tuple = None, kwargs: dict = None, ) -> str']), - (('Sketch', 'launch_promise_thread'), ['(f: Callable, name: str = None, *, daemon: bool = True, args: tuple = None, kwargs: dict = None, ) -> Py5Promise']), - (('Sketch', 'launch_repeating_thread'), ['(f: Callable, name: str = None, *, time_delay: float = 0, daemon: bool = True, args: tuple = None, kwargs: dict = None, ) -> str']), - (('Sketch', 'has_thread'), ['(name: str) -> None']), - (('Sketch', 'join_thread'), ['(name: str, *, timeout: float = None) -> bool']), - (('Sketch', 'stop_thread'), ['(name: str, wait: bool = False) -> None']), - (('Sketch', 'stop_all_threads'), ['(wait: bool = False) -> None']), - (('Sketch', 'list_threads'), ['() -> None']), (('Sketch', 'sketch_path'), ['() -> Path', '(where: str, /) -> Path']), (('Sketch', 'hot_reload_draw'), ['(draw: Callable) -> None']), (('Sketch', 'profile_functions'), ['(function_names: list[str]) -> None']), @@ -609,8 +610,8 @@ (('Py5Vector', 'normalize'), ['() -> Py5Vector']), (('Py5Vector', 'set_limit'), ['(max_mag: float) -> Py5Vector']), (('Py5Vector', 'set_heading'), ['(*heading) -> Py5Vector']), - (('Py5Vector', 'from_heading'), ['(*heading, dtype: int = np.float_) -> Py5Vector']), - (('Py5Vector', 'random'), ['(dim: int, *, dtype: type = np.float_) -> Py5Vector']), + (('Py5Vector', 'from_heading'), ['(*heading, dtype: int = np.float64) -> Py5Vector']), + (('Py5Vector', 'random'), ['(dim: int, *, dtype: type = np.float64) -> Py5Vector']), (('Py5Vector', 'rotate'), ['(angle: float) -> Py5Vector2D', '(angle: float, dim: Union[int, str]) -> Py5Vector3D']), (('Py5Vector', 'rotate_around'), ['(angle: float, v: Py5Vector3D) -> Py5Vector3D']), (('Py5Graphics', 'points'), ['(coordinates: npt.NDArray[np.floating], /) -> None']), @@ -627,7 +628,7 @@ (('Py5Shape', 'bezier_vertices'), ['(coordinates: npt.NDArray[np.floating], /) -> None']), (('Py5Shape', 'curve_vertices'), ['(coordinates: npt.NDArray[np.floating], /) -> None']), (('Py5Shape', 'quadratic_vertices'), ['(coordinates: npt.NDArray[np.floating], /) -> None']), - (('Sketch', 'run_sketch'), ['(block: bool = None, *, py5_options: list[str] = None, sketch_args: list[str] = None, sketch_functions: dict[str, Callable] = None, jclassname: str = None) -> None']), + (('Sketch', 'run_sketch'), ['(block: bool = None, *, py5_options: list[str] = None, sketch_args: list[str] = None, sketch_functions: dict[str, Callable] = None, jclassname: str = None, jclass_params: tuple[Any] = ()) -> None']), (('Py5Functions', 'create_font_file'), ['(font_name: str, font_size: int, filename: str = None, characters: str = None, pause: bool = True) -> None']), (('Py5Functions', 'get_current_sketch'), ['() -> Sketch']), (('Py5Functions', 'reset_py5'), ['(jclassname: str = None) -> bool']), @@ -649,6 +650,6 @@ (('Py5Tools', 'screenshot'), ['(*, sketch: Sketch = None, hook_post_draw: bool = False) -> PIL_Image']), (('Py5Tools', 'save_frames'), ['(dirname: str, *, filename: str = "frame_####.png", period: float = 0.0, start: int = None, limit: int = 0, sketch: Sketch = None, hook_post_draw: bool = False, block: bool = False, display_progress: bool = True) -> None']), (('Py5Tools', 'animated_gif'), ['(filename: str, *, count: int = 0, period: float = 0.0, frame_numbers: Iterable = None, duration: float = 0.0, loop: int = 0, optimize: bool = True, sketch: Sketch = None, hook_post_draw: bool = False, block: bool = False) -> None']), - (('Py5Tools', 'offline_frame_processing'), ['(func: Callable[[npt.NDArray[np.uint8]], None], *, limit: int = 0, period: float = 0.0, batch_size: int = 1, complete_func: Callable[[], None] = None, stop_processing_func: Callable[[], bool] = None, sketch: Sketch = None, hook_post_draw: bool = False, queue_limit: int = None, block: bool = False) -> None']), + (('Py5Tools', 'offline_frame_processing'), ['(func: Callable[[npt.NDArray[np.uint8]], None], *, limit: int = 0, period: float = 0.0, batch_size: int = 1, complete_func: Callable[[], None] = None, stop_processing_func: Callable[[], bool] = None, sketch: Sketch = None, hook_post_draw: bool = False, queue_limit: int = None, block: bool = False, display_progress: bool = True) -> None']), (('Py5Tools', 'capture_frames'), ['(*, count: float = 0, period: float = 0.0, frame_numbers: Iterable = None, sketch: Sketch = None, hook_post_draw: bool = False, block: bool = False) -> list[PIL_Image]']), (('Py5Tools', 'sketch_portal'), ['(*, time_limit: float = 0.0, throttle_frame_rate: float = 30, scale: float = 1.0, quality: int = 75, portal_widget: Py5SketchPortal = None, sketch: Sketch = None, hook_post_draw: bool = False) -> None']),]) diff --git a/py5/sketch.py b/py5/sketch.py index f8eaf90..52d28bf 100644 --- a/py5/sketch.py +++ b/py5/sketch.py @@ -183,10 +183,10 @@ class Sketch(MathMixin, DataMixin, ThreadsMixin, PixelMixin, PrintlnStream, Py5B Core py5 class for leveraging py5's functionality. This is analogous to the PApplet class in Processing. Launch the Sketch with the `run_sketch()` method. - The core functions to be implemented by the py5 coder are `settings`, `setup`, - and `draw`. The first two will be run once at Sketch initialization and the - third will be run in an animation thread, once per frame. The following event - functions are also supported: + The core functions to be implemented by the py5 coder are `setup` and `draw`. + The first will be run once at Sketch initialization and the second will be run + in an animation thread, once per frame. The following event functions are also + supported: * `exiting` * `key_pressed` @@ -201,13 +201,16 @@ class Sketch(MathMixin, DataMixin, ThreadsMixin, PixelMixin, PrintlnStream, Py5B * `mouse_released` * `mouse_wheel` * `movie_event` + * `predraw_update` * `post_draw` * `pre_draw` - When coding in class mode, all of the above functions should be class methods. - When coding in module mode or imported mode, the above functions should be - stand-alone functions available in the local namespace in which `run_sketch()` - was called.""" + When coding in class mode, all of the above functions should be instance + methods. When coding in module mode or imported mode, the above functions should + be stand-alone functions available in the local namespace in which + `run_sketch()` was called. + + For more information, look at the online "User Functions" documentation.""" _py5_object_cache = set() _cls = _Sketch @@ -237,6 +240,7 @@ def __new__(cls, *args, **kwargs): def __init__(self, *args, **kwargs): _instance = kwargs.get("_instance") jclassname = kwargs.get("jclassname") + jclass_params = kwargs.get("jclass_params", ()) if _instance: if _instance == getattr(self, "_instance", None): @@ -248,7 +252,7 @@ def __init__(self, *args, **kwargs): ) Sketch._cls = JClass(jclassname) if jclassname else _Sketch - instance = Sketch._cls() + instance = Sketch._cls(*jclass_params) if not isinstance(instance, _SketchBase): raise RuntimeError("Java instance must inherit from py5.core.SketchBase") @@ -308,6 +312,9 @@ def run_sketch( block: bool = None method returns immediately (False) or blocks until Sketch exits (True) + jclass_params: tuple[Any] = () + parameters to pass to constructor when using py5 in processing mode + jclassname: str = None canonical name of class to instantiate when using py5 in processing mode @@ -318,7 +325,7 @@ def run_sketch( command line arguments that become Sketch arguments sketch_functions: dict[str, Callable] = None - sketch methods when using module mode + sketch methods when using [module mode](content-py5-modes-module-mode) Notes ----- @@ -374,7 +381,9 @@ class mode. Don't forget you can always replace the `draw()` function in a The `jclassname` parameter should only be used when programming in Processing Mode. This value must be the canonical name of your Processing Sketch class (i.e. `"org.test.MySketch"`). The class must inherit from `py5.core.SketchBase`. - Read py5's online documentation to learn more about Processing Mode.""" + To pass parameters to your Processing Sketch class constructor, use the + `jclass_params` parameter. Read py5's online documentation to learn more about + Processing Mode.""" if not hasattr(self, "_instance"): raise RuntimeError( ( @@ -1312,6 +1321,8 @@ def convert_image( for more information. You can also create your own custom integrations. Look at the online "Custom Integrations" Python Ecosystem Integration tutorial to learn more.""" + if isinstance(obj, (Py5Image, Py5Graphics)): + return obj result = image_conversion._convert(self, obj, **kwargs) if isinstance(result, (Path, str)): return self.load_image(result, dst=dst) @@ -1352,6 +1363,8 @@ def convert_shape(self, obj: Any, **kwargs: dict[str, Any]) -> Py5Shape: and Trimesh" Python Ecosystem Integration tutorials for more information. You can also create your own custom integrations. Look at the online "Custom Integrations" Python Ecosystem Integration tutorial to learn more.""" + if isinstance(obj, Py5Shape): + return obj return shape_conversion._convert(self, obj, **kwargs) def load_image( @@ -4659,7 +4672,7 @@ def _get_rheight(self) -> int: ) def _get_rmouse_x(self) -> int: - """The current horizonal coordinate of the mouse after activating scale invariant + """The current horizontal coordinate of the mouse after activating scale invariant drawing. Underlying Processing field: Sketch.rmouseX @@ -4667,7 +4680,7 @@ def _get_rmouse_x(self) -> int: Notes ----- - The current horizonal coordinate of the mouse after activating scale invariant + The current horizontal coordinate of the mouse after activating scale invariant drawing. See `window_ratio()` for more information about how to activate this and why it is useful. @@ -4681,7 +4694,7 @@ def _get_rmouse_x(self) -> int: rmouse_x: int = property( fget=_get_rmouse_x, - doc="""The current horizonal coordinate of the mouse after activating scale invariant + doc="""The current horizontal coordinate of the mouse after activating scale invariant drawing. Underlying Processing field: Sketch.rmouseX @@ -4689,7 +4702,7 @@ def _get_rmouse_x(self) -> int: Notes ----- - The current horizonal coordinate of the mouse after activating scale invariant + The current horizontal coordinate of the mouse after activating scale invariant drawing. See `window_ratio()` for more information about how to activate this and why it is useful. @@ -13716,13 +13729,13 @@ def ortho(self) -> None: bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume @@ -13765,13 +13778,13 @@ def ortho(self, left: float, right: float, bottom: float, top: float, /) -> None bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume @@ -13823,13 +13836,13 @@ def ortho( bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume @@ -13871,13 +13884,13 @@ def ortho(self, *args): bottom plane of the clipping volume far: float - maximum distance from the origin away from the viewer + distance from the viewer to the farthest clipping plane left: float left plane of the clipping volume near: float - maximum distance from the origin to the viewer + distance from the viewer to the nearest clipping plane right: float right plane of the clipping volume diff --git a/py5/vector.py b/py5/vector.py index dbc7dbd..ec69e78 100644 --- a/py5/vector.py +++ b/py5/vector.py @@ -85,7 +85,7 @@ def __new__(cls, *args, dim: int = None, dtype: type = None, copy: bool = True): used_default_dim = len(args) == 0 and dim is None dim = Py5Vector._DEFAULT_DIM if dim is None else dim - dtype = np.float_ if dtype is None else dtype + dtype = np.float64 if dtype is None else dtype if not isinstance(dtype, (type, np.dtype)) or not np.issubdtype( dtype, np.floating @@ -1018,13 +1018,13 @@ def set_heading(self, *heading) -> Py5Vector: ) @classmethod - def from_heading(cls, *heading, dtype: int = np.float_) -> Py5Vector: + def from_heading(cls, *heading, dtype: int = np.float64) -> Py5Vector: """Class method to create a new vector with a given heading, measured in radians. Parameters ---------- - dtype: int = np.float_ + dtype: int = np.float64 dtype of new vector to create heading @@ -1068,7 +1068,7 @@ def from_heading(cls, *heading, dtype: int = np.float_) -> Py5Vector: ) @classmethod - def random(cls, dim: int, *, dtype: type = np.float_) -> Py5Vector: + def random(cls, dim: int, *, dtype: type = np.float64) -> Py5Vector: """Create a new vector with random values. Parameters @@ -1077,7 +1077,7 @@ def random(cls, dim: int, *, dtype: type = np.float_) -> Py5Vector: dim: int dimension of the random vector to create - dtype: type = np.float_ + dtype: type = np.float64 dtype of the random vector to create Notes @@ -1164,7 +1164,7 @@ class Py5Vector2D(Py5Vector): to instead use the same numpy array and share its data with provided array, set the `copy` parameter to `False`, such as `v6 = py5.Py5Vector(arr, copy=False)`.""" - def __new__(cls, *args, dtype: type = np.float_): + def __new__(cls, *args, dtype: type = np.float64): return super().__new__(cls, *args, dim=2, dtype=dtype) # *** BEGIN METHODS *** @@ -1214,7 +1214,7 @@ def rotate(self, angle: float) -> Py5Vector2D: # *** END METHODS *** @classmethod - def random(cls, dim: int = 2, *, dtype: type = np.float_) -> Py5Vector2D: + def random(cls, dim: int = 2, *, dtype: type = np.float64) -> Py5Vector2D: """Create a new vector with random values. Parameters @@ -1223,7 +1223,7 @@ def random(cls, dim: int = 2, *, dtype: type = np.float_) -> Py5Vector2D: dim: int dimension of the random vector to create - dtype: type = np.float_ + dtype: type = np.float64 dtype of the random vector to create Notes @@ -1291,7 +1291,7 @@ class Py5Vector3D(Py5Vector): to instead use the same numpy array and share its data with provided array, set the `copy` parameter to `False`, such as `v6 = py5.Py5Vector(arr, copy=False)`.""" - def __new__(cls, *args, dtype: type = np.float_): + def __new__(cls, *args, dtype: type = np.float64): return super().__new__(cls, *args, dim=3, dtype=dtype) def _get_z(self) -> float: @@ -1443,7 +1443,7 @@ def rotate_around(self, angle: float, v: Py5Vector3D) -> Py5Vector3D: # *** END METHODS *** @classmethod - def random(cls, dim: int = 3, *, dtype: type = np.float_) -> Py5Vector3D: + def random(cls, dim: int = 3, *, dtype: type = np.float64) -> Py5Vector3D: """Create a new vector with random values. Parameters @@ -1452,7 +1452,7 @@ def random(cls, dim: int = 3, *, dtype: type = np.float_) -> Py5Vector3D: dim: int dimension of the random vector to create - dtype: type = np.float_ + dtype: type = np.float64 dtype of the random vector to create Notes @@ -1520,7 +1520,7 @@ class Py5Vector4D(Py5Vector): to instead use the same numpy array and share its data with provided array, set the `copy` parameter to `False`, such as `v6 = py5.Py5Vector(arr, copy=False)`.""" - def __new__(cls, *args, dtype: type = np.float_): + def __new__(cls, *args, dtype: type = np.float64): return super().__new__(cls, *args, dim=4, dtype=dtype) def _get_z(self) -> float: @@ -1587,7 +1587,7 @@ def _set_w(self, val: float) -> None: ) @classmethod - def random(cls, dim: int = 4, *, dtype: type = np.float_) -> Py5Vector4D: + def random(cls, dim: int = 4, *, dtype: type = np.float64) -> Py5Vector4D: """Create a new vector with random values. Parameters @@ -1596,7 +1596,7 @@ def random(cls, dim: int = 4, *, dtype: type = np.float_) -> Py5Vector4D: dim: int dimension of the random vector to create - dtype: type = np.float_ + dtype: type = np.float64 dtype of the random vector to create Notes diff --git a/py5_tools/__init__.py b/py5_tools/__init__.py index 554d11b..c4b4469 100644 --- a/py5_tools/__init__.py +++ b/py5_tools/__init__.py @@ -27,4 +27,4 @@ from .jvm import * # noqa from .libraries import * # noqa -__version__ = "0.10.0a0" +__version__ = "0.10.1a1" diff --git a/py5_tools/hooks/frame_hooks.py b/py5_tools/hooks/frame_hooks.py index 960fa1e..78f0567 100644 --- a/py5_tools/hooks/frame_hooks.py +++ b/py5_tools/hooks/frame_hooks.py @@ -65,7 +65,7 @@ def screenshot(*, sketch: Sketch = None, hook_post_draw: bool = False) -> PIL_Im By default the Sketch will be the currently running Sketch, as returned by `get_current_sketch()`. Use the `sketch` parameter to specify a different - running Sketch, such as a Sketch created using Class mode. + running Sketch, such as a Sketch created using class mode. This function will not work on a Sketch with no `draw()` function that uses an OpenGL renderer such as `P2D` or `P3D`. Either add a token `draw()` function or @@ -179,7 +179,7 @@ def save_frames( By default the Sketch will be the currently running Sketch, as returned by `get_current_sketch()`. Use the `sketch` parameter to specify a different - running Sketch, such as a Sketch created using Class mode. + running Sketch, such as a Sketch created using class mode. If the `limit` parameter is used, this function will wait to return a list of the filenames. If not, it will return right away as the frames are saved in the @@ -241,6 +241,7 @@ def offline_frame_processing( hook_post_draw: bool = False, queue_limit: int = None, block: bool = None, + display_progress: bool = True, ) -> None: """Process Sketch frames in a separate thread that will minimize the performance impact on the Sketch's main animation thread. @@ -257,6 +258,9 @@ def offline_frame_processing( complete_func: Callable[[], None] = None function to call when frame processing is complete + display_progress: bool = True + display progress as frames are processed + func: Callable[[npt.NDArray[np.uint8]], None] function to process the Sketch's pixels, one batch at a time @@ -314,8 +318,13 @@ def offline_frame_processing( terminates. This blocking feature is not available on macOS when the Sketch is executed through an IPython kernel. + By default this function will report its progress as frames are processed. If + you are using a Jupyter Notebook and happen to be processing tens of thousands + of frames, this might cause Jupyter to crash. To avoid that fate, set the + `display_progress` parameter to `False`. + Use the `sketch` parameter to specify a different running Sketch, such as a - Sketch created using Class mode. If your Sketch has a `post_draw()` method, use + Sketch created using class mode. If your Sketch has a `post_draw()` method, use the `hook_post_draw` parameter to make this function run after `post_draw()` instead of `draw()`. This is important when using Processing libraries that support `post_draw()` such as Camera3D or ColorBlindness.""" @@ -345,6 +354,7 @@ def offline_frame_processing( complete_func=complete_func, stop_processing_func=stop_processing_func, queue_limit=queue_limit, + display_progress=display_progress, ) sketch._add_post_hook( "post_draw" if hook_post_draw else "draw", hook.hook_name, hook @@ -431,7 +441,7 @@ def animated_gif( By default the Sketch will be the currently running Sketch, as returned by `get_current_sketch()`. Use the `sketch` parameter to specify a different - running Sketch, such as a Sketch created using Class mode. + running Sketch, such as a Sketch created using class mode. If your Sketch has a `post_draw()` method, use the `hook_post_draw` parameter to make this function run after `post_draw()` instead of `draw()`. This is @@ -576,7 +586,7 @@ def capture_frames( By default the Sketch will be the currently running Sketch, as returned by `get_current_sketch()`. Use the `sketch` parameter to specify a different - running Sketch, such as a Sketch created using Class mode. + running Sketch, such as a Sketch created using class mode. If your Sketch has a `post_draw()` method, use the `hook_post_draw` parameter to make this function run after `post_draw()` instead of `draw()`. This is diff --git a/py5_tools/hooks/hooks.py b/py5_tools/hooks/hooks.py index 86034c7..c9a794f 100644 --- a/py5_tools/hooks/hooks.py +++ b/py5_tools/hooks/hooks.py @@ -217,12 +217,14 @@ def __init__( complete_func=None, stop_processing_func=None, queue_limit=0, + display_progress=True, ): super().__init__("py5queued_block_processing_hook") self.period = period self.limit = limit self.batch_size = batch_size self.queue_limit = queue_limit + self.display_progress = display_progress self.continue_grabbing_frames = True self.current_batch = None @@ -305,7 +307,8 @@ def __call__(self, sketch): self.processor.stop_processing = True self.hook_finished(sketch) - self.status_msg(self.msg()) + if self.display_progress: + self.status_msg(self.msg()) except Exception as e: self.hook_error(sketch, e) diff --git a/py5_tools/hooks/zmq_hooks.py b/py5_tools/hooks/zmq_hooks.py index 2c645dc..ec21aa1 100644 --- a/py5_tools/hooks/zmq_hooks.py +++ b/py5_tools/hooks/zmq_hooks.py @@ -90,7 +90,7 @@ def sketch_portal( By default the Sketch will be the currently running Sketch, as returned by `get_current_sketch()`. Use the `sketch` parameter to specify a different - running Sketch, such as a Sketch created using Class mode. + running Sketch, such as a Sketch created using class mode. The Sketch Portal is a custom Jupyter Widget and can handle keyboard or mouse events just like a native window. You will need to click on the portal for it to diff --git a/py5_tools/hooks/zmq_hooks_fail.py b/py5_tools/hooks/zmq_hooks_fail.py index 9048673..6f4244b 100644 --- a/py5_tools/hooks/zmq_hooks_fail.py +++ b/py5_tools/hooks/zmq_hooks_fail.py @@ -75,7 +75,7 @@ def sketch_portal( By default the Sketch will be the currently running Sketch, as returned by `get_current_sketch()`. Use the `sketch` parameter to specify a different - running Sketch, such as a Sketch created using Class mode. + running Sketch, such as a Sketch created using class mode. The Sketch Portal is a custom Jupyter Widget and can handle keyboard or mouse events just like a native window. You will need to click on the portal for it to diff --git a/py5_tools/utilities.py b/py5_tools/utilities.py index 38f962c..150ec5c 100644 --- a/py5_tools/utilities.py +++ b/py5_tools/utilities.py @@ -61,21 +61,21 @@ py5 py5-processing4 - 0.10.0a0 + 0.10.1a1 system ${{jarlocation}}/core.jar py5 py5-jogl - 0.10.0a0 + 0.10.1a1 system ${{jarlocation}}/jogl-all.jar py5 py5 - 0.10.0a0 + 0.10.1a1 system ${{jarlocation}}/py5.jar diff --git a/pyproject.toml b/pyproject.toml index 04672ae..3c2d1bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "autopep8>=2.0", "jpype1>=1.4", "line_profiler>=4.0", - "numpy>=1.24", + "numpy>=1.24,<2.0", "pillow>=9.5", "pyobjc>=9.0;sys_platform==\"darwin\"", "requests>=2.28",