Skip to content

Commit 0dbe0ca

Browse files
committed
release 0.8.1a1
1 parent 689e318 commit 0dbe0ca

37 files changed

+3633
-2330
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
py5 is a new version of [**Processing**][processing] for Python 3.8+. It makes the Java [**Processing**][processing] jars available to the CPython interpreter using [**JPype**][jpype]. It can do just about all of the 2D and 3D drawing [**Processing**][processing] can do, except with Python instead of Java code.
1010

11-
The goal of py5 is to create a new version of Processing that is integrated into the Python ecosystem. Built into the library are thoughtful choices about how to best get py5 to work with other popular Python libraries and tools such as [Jupyter](https://jupyter.org/), [numpy](https://www.numpy.org/), and [Pillow](https://python-pillow.org/).
11+
The goal of py5 is to create a new version of Processing that is integrated into the Python ecosystem. Built into the library are thoughtful choices about how to best get py5 to work with other popular Python libraries and tools such as [Jupyter][jupyter], [numpy][numpy], and [Pillow][pillow].
1212

1313
## Simple Example
1414

@@ -69,3 +69,7 @@ Have a comment or question? We'd love to hear from you! The best ways to reach o
6969
[py5_generator_repo]: https://github.com/py5coding/py5generator
7070
[processing]: https://github.com/processing/processing4
7171
[jpype]: https://github.com/jpype-project/jpype
72+
73+
[jupyter]: https://jupyter.org/
74+
[numpy]: https://numpy.org/
75+
[pillow]: https://python-pillow.org/

py5/__init__.py

Lines changed: 2148 additions & 1804 deletions
Large diffs are not rendered by default.

py5/methods.py renamed to py5/bridge.py

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@
2424
from typing import Union
2525
import inspect
2626
import line_profiler
27+
import traceback
2728

28-
from jpype import JImplements, JOverride, JString
29+
from jpype import JClass, JImplements, JOverride, JString
2930

3031
import stackprinter
3132

3233
import py5_tools
3334
from . import reference
3435
from . import custom_exceptions
36+
# from . import java_conversion
3537

3638
# *** stacktrace configuration ***
3739
# set stackprinter color style. Default is plaintext. Other choices are darkbg,
@@ -50,6 +52,16 @@
5052
**custom_exceptions.CUSTOM_EXCEPTION_MSGS,
5153
}
5254

55+
_JAVA_RUNTIMEEXCEPTION = JClass('java.lang.RuntimeException')
56+
57+
58+
def check_run_method_callstack():
59+
for t in traceback.extract_stack():
60+
if t.filename == __file__ and t.name == 'run_method':
61+
return True
62+
else:
63+
return False
64+
5365

5466
def _exception_msg(println, exc_type_name, exc_msg, py5info):
5567
try:
@@ -149,8 +161,8 @@ def _extract_py5_user_function_data(d: dict):
149161
return functions, function_param_counts
150162

151163

152-
@JImplements('py5.core.Py5Methods')
153-
class Py5Methods:
164+
@JImplements('py5.core.Py5Bridge')
165+
class Py5Bridge:
154166

155167
def __init__(self, sketch):
156168
self._sketch = sketch
@@ -161,6 +173,9 @@ def __init__(self, sketch):
161173
self._profiler = line_profiler.LineProfiler()
162174
self._is_terminated = False
163175

176+
from .java_conversion import convert_to_python_types
177+
self._convert_to_python_types = convert_to_python_types
178+
164179
def set_functions(self, functions, function_param_counts):
165180
self._function_param_counts = dict()
166181
self._functions = dict()
@@ -232,6 +247,10 @@ def get_function_list(self):
232247
return [JString(f'{name}:{self._function_param_counts[name]}')
233248
for name in self._functions.keys()]
234249

250+
@JOverride
251+
def terminate_sketch(self):
252+
self._sketch._terminate_sketch()
253+
235254
@JOverride
236255
def run_method(self, method_name, params):
237256
try:
@@ -242,8 +261,8 @@ def run_method(self, method_name, params):
242261
hook(self._sketch)
243262

244263
# now run the actual method
245-
from .java_conversion import convert_to_python_types
246-
self._functions[method_name](*convert_to_python_types(params))
264+
self._functions[method_name](
265+
*self._convert_to_python_types(params))
247266

248267
# finally, post-hooks
249268
if method_name in self._post_hooks:
@@ -252,9 +271,47 @@ def run_method(self, method_name, params):
252271
return True
253272
except Exception:
254273
handle_exception(self._sketch.println, *sys.exc_info())
255-
self._sketch._terminate_sketch()
274+
self.terminate_sketch()
256275
return False
257276

277+
@JOverride
278+
def call_function(self, key, params):
279+
d = py5_tools.config._PY5_PROCESSING_MODE_KEYS
280+
try:
281+
*str_hierarchy, c = str(key).split('.')
282+
283+
for s in str_hierarchy:
284+
if s in d:
285+
subd = d[s]
286+
if isinstance(subd, dict):
287+
d = subd
288+
elif hasattr(subd, '__dict__'):
289+
d = subd.__dict__
290+
else:
291+
return _JAVA_RUNTIMEEXCEPTION(
292+
f'{s} in key {key} does map to dict or object with __dict__ attribute')
293+
else:
294+
return _JAVA_RUNTIMEEXCEPTION(
295+
f'{s} not found with key {key}')
296+
297+
if c not in d or not callable(func := d[c]):
298+
return _JAVA_RUNTIMEEXCEPTION(
299+
f'callable {c} not found with key {key}')
300+
301+
try:
302+
retval = func(*self._convert_to_python_types(params))
303+
if key in py5_tools.config._PY5_PROCESSING_MODE_CALLBACK_ONCE:
304+
py5_tools.config._PY5_PROCESSING_MODE_CALLBACK_ONCE.remove(
305+
key)
306+
if key in py5_tools.config._PY5_PROCESSING_MODE_KEYS:
307+
py5_tools.config._PY5_PROCESSING_MODE_KEYS.pop(key)
308+
return retval
309+
except Exception as e:
310+
handle_exception(self._sketch.println, *sys.exc_info())
311+
return _JAVA_RUNTIMEEXCEPTION(str(e))
312+
except Exception as e:
313+
return _JAVA_RUNTIMEEXCEPTION(str(e))
314+
258315
@JOverride
259316
def py5_println(self, text, stderr):
260317
self._sketch.println(text, stderr=stderr)

py5/decorators.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
from jpype.types import JString, JInt
2525

2626

27-
HEX_COLOR_REGEX = re.compile(r'#[0-9A-F]{6}' + chr(36))
2827
HEX_3DIGIT_COLOR_REGEX = re.compile(r'#[0-9A-F]{3}' + chr(36))
28+
HEX_4DIGIT_COLOR_REGEX = re.compile(r'#[0-9A-F]{4}' + chr(36))
29+
HEX_6DIGIT_COLOR_REGEX = re.compile(r'#[0-9A-F]{6}' + chr(36))
30+
HEX_8DIGIT_COLOR_REGEX = re.compile(r'#[0-9A-F]{8}' + chr(36))
2931

3032

3133
def _text_fix_str(f):
@@ -50,12 +52,27 @@ def decorated(self_, *args):
5052

5153

5254
def _hex_converter(arg):
53-
if isinstance(arg, str) and HEX_COLOR_REGEX.match(arg.upper()):
54-
return JInt(int("0xFF" + arg[1:], base=16))
55-
elif isinstance(arg, str) and HEX_3DIGIT_COLOR_REGEX.match(arg.upper()):
56-
return JInt(int("0xFF" + ''.join([c + c for c in arg[1:]]), base=16))
55+
if isinstance(arg, str):
56+
if arg.startswith('#'):
57+
if HEX_3DIGIT_COLOR_REGEX.match(arg.upper()):
58+
return JInt(
59+
int("0xFF" + ''.join([c + c for c in arg[1:]]), base=16))
60+
elif HEX_4DIGIT_COLOR_REGEX.match(arg.upper()):
61+
return JInt(
62+
int("0x" + ''.join([arg[i] + arg[i] for i in [4, 1, 2, 3]]), base=16))
63+
elif HEX_6DIGIT_COLOR_REGEX.match(arg.upper()):
64+
return JInt(int("0xFF" + arg[1:], base=16))
65+
elif HEX_8DIGIT_COLOR_REGEX.match(arg.upper()):
66+
return JInt(int("0x" + arg[7:] + arg[1:7], base=16))
67+
else:
68+
try:
69+
import matplotlib.colors as mcolors
70+
return JInt(int("0xFF" + mcolors.to_hex(arg)[1:], base=16))
71+
except BaseException:
72+
return None
5773
elif isinstance(arg, (int, np.integer)) and 0x7FFFFFFF < arg <= 0xFFFFFFFF:
5874
return JInt(arg)
75+
5976
return None
6077

6178

py5/font.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import functools
2323
from typing import overload # noqa
24+
import weakref
2425

2526
import jpype
2627
from jpype import JException, JArray, JString # noqa
@@ -82,12 +83,20 @@ class Py5Font:
8283
To create a new font dynamically, use the ``create_font()`` function. Do not use
8384
the syntax ``Py5Font()``.
8485
"""
85-
8686
_cls = jpype.JClass('processing.core.PFont')
8787
CHARSET = _cls.CHARSET
8888

89-
def __init__(self, pfont):
90-
self._instance = pfont
89+
_py5_object_cache = weakref.WeakSet()
90+
91+
def __new__(cls, pfont):
92+
for o in cls._py5_object_cache:
93+
if pfont == o._instance:
94+
return o
95+
else:
96+
o = object.__new__(Py5Font)
97+
o._instance = pfont
98+
cls._py5_object_cache.add(o)
99+
return o
91100

92101
def ascent(self) -> float:
93102
"""Get the ascent of this font from the baseline.

0 commit comments

Comments
 (0)