refactor pyray into separate module

This commit is contained in:
richard 2021-10-08 04:47:58 +01:00
parent 8666f0cff8
commit ec752bdab7
24 changed files with 2394 additions and 2055 deletions

View file

@ -84,7 +84,7 @@ jobs:
cd raylib-c
mkdir build
cd build
cmake -DINCLUDE_EVERYTHING=on -DSUPPORT_FILEFORMAT_JPG -DWITH_PIC=on -DCMAKE_BUILD_TYPE=Release ..
cmake -DINCLUDE_EVERYTHING=on -DSUPPORT_FILEFORMAT_JPG=on -DWITH_PIC=on -DCMAKE_BUILD_TYPE=Release ..
make -j2
sudo make install
- name: Build raylib-python-cffi
@ -131,7 +131,7 @@ jobs:
cd raylib-c
mkdir build
cd build
cmake -DINCLUDE_EVERYTHING=on -DSUPPORT_FILEFORMAT_JPG -DWITH_PIC=on -DCMAKE_BUILD_TYPE=Release ..
cmake -DINCLUDE_EVERYTHING=on -DSUPPORT_FILEFORMAT_JPG=on -DWITH_PIC=on -DCMAKE_BUILD_TYPE=Release ..
msbuild raylib.sln /target:raylib /property:Configuration=Release
copy raylib\Release\raylib.lib ..\..
cd ..\..

View file

@ -1,23 +1,35 @@
# Python Bindings for Raylib 3.7
New CFFI API static bindings. Automatically generated to be as close as possible to
original Raylib. Faster, fewer bugs and easier to maintain than ctypes.
original Raylib. Faster, fewer bugs and easier to maintain than ctypes. Commercial-friendly license.
Docstrings and auto-completion.
[Full documentation](http://electronstudio.github.io/raylib-python-cffi)
# License (updated)
# Quickstart
`pip3 install raylib`
from pyray import *
init_window(800, 450, "Hello")
while not window_should_close():
begin_drawing()
clear_background(WHITE)
draw_text("Hello world", 190, 200, 20, VIOLET)
end_drawing()
close_window()
The bindings are now under the Eclipse Public License, so you are free to
statically link and use in non-free / proprietary / commercial projects!
# Installation
python3 -m pip install raylib
If it doesn't work, first make sure you have latest pip installed:
First make sure you have latest pip installed:
python3 -m pip install --upgrade pip
Then install
python3 -m pip install raylib
On most platforms it should install a binary wheel (Windows 10 x64, MacOS 10.15 x64, Linux Ubuntu1804 x64).
If yours isn't available then pip will attempt to build from source, in which case you will need to have Raylib development libs installed, e.g.
@ -51,18 +63,23 @@ Use [the Python API](https://electronstudio.github.io/raylib-python-cffi/pyray.h
# RLZero
Work in progress:
A related library (that is a work in progress!):
[A simplified API for Raylib for use in education and to enable beginners to create 3d games](https://github.com/electronstudio/rlzero)
# Help wanted
* converting more examples from C to python
* testing and building on more platforms
* Converting more examples from C to Python
* Testing on more platforms
# License (updated)
The bindings are now under the Eclipse Public License, so you are free to
statically link and use in non-free / proprietary / commercial projects!
# Performance
For fastest performance use Pypy rather than standard python.
For fastest performance use Pypy rather than standard Python.
Every call to C is costly, so it's slightly faster if you use Python data structures and functions when calculating
in your update loop

View file

@ -44,9 +44,9 @@ def ctype_to_python_type(t):
print("""from typing import Any
class PyRay:
def pointer(self, struct):
return ffi.addressof(struct)
def pointer(struct):
...
""")
@ -69,7 +69,7 @@ for name, attr in getmembers(rl):
param_name = list(p)[i]
param_type = ctype_to_python_type(arg.cname)
sig += f", {param_name}: {param_type}"
sig += f"{param_name}: {param_type},"
return_type = ffi.typeof(attr).result.cname
@ -79,15 +79,15 @@ for name, attr in getmembers(rl):
description = json_object['description']
print(
f' def {uname}(self{sig}) -> {ctype_to_python_type(return_type)}:\n """{description}"""\n ...')
f'def {uname}({sig}) -> {ctype_to_python_type(return_type)}:\n """{description}"""\n ...')
elif str(type(attr)) == "<class '_cffi_backend._CDataBase'>":
return_type = ffi.typeof(attr).result.cname
print(
f' def {uname}(self, *args) -> {ctype_to_python_type(return_type)}:\n """VARARG FUNCTION - MAY NOT BE SUPPORTED BY CFFI"""\n ...')
f'def {uname}(*args) -> {ctype_to_python_type(return_type)}:\n """VARARG FUNCTION - MAY NOT BE SUPPORTED BY CFFI"""\n ...')
else:
#print("*****", str(type(attr)))
print(f" {name}: {str(type(attr))[8:-2]}") # this isolates the type
print(f"{name}: {str(type(attr))[8:-2]}") # this isolates the type
for struct in ffi.list_types()[0]:
print("processing", struct, file=sys.stderr)
@ -99,18 +99,18 @@ for struct in ffi.list_types()[0]:
if ffi.typeof(struct).fields is None:
print("weird empty struct, skipping", file=sys.stderr)
break
print(f" class {struct}:")
print(f' """ comment """')
print(f"class {struct}:")
print(f' """ struct """')
sig = ""
for arg in ffi.typeof(struct).fields:
sig += ", " + arg[0]
print(f" def __init__(self{sig}):")
print(f" def __init__(self{sig}):")
for arg in ffi.typeof(struct).fields:
print(f" self.{arg[0]}={arg[0]}")
print(f" self.{arg[0]}={arg[0]}")
elif ffi.typeof(struct).kind == "enum":
print(f" {struct}: int")
print(f"{struct}: int")
else:
print("ERROR UNKNOWN TYPE", ffi.typeof(struct), file=sys.stderr)

View file

@ -27,9 +27,9 @@ See https://github.com/electronstudio/raylib-python-cffi/blob/master/dynamic/tes
If you use the ``rl.`` prefix then code will work on both static and dynamic bindings.
.. warning::
.. tip::
If you access functions via ``raylib.pyray`` then there is no difference at all, but be warned this hasn't been tested.
If you access functions via ``import pyray`` then there is no difference at all, but be warned this hasn't been tested much.
.. important::

View file

@ -58,4 +58,29 @@ try:
except Exception as e:
print(e)
from .pyray import PyRay
LIGHTGRAY =( 200, 200, 200, 255 )
GRAY =( 130, 130, 130, 255 )
DARKGRAY =( 80, 80, 80, 255 )
YELLOW =( 253, 249, 0, 255 )
GOLD =( 255, 203, 0, 255 )
ORANGE =( 255, 161, 0, 255 )
PINK =( 255, 109, 194, 255 )
RED =( 230, 41, 55, 255 )
MAROON =( 190, 33, 55, 255 )
GREEN =( 0, 228, 48, 255 )
LIME =( 0, 158, 47, 255 )
DARKGREEN =( 0, 117, 44, 255 )
SKYBLUE =( 102, 191, 255, 255 )
BLUE =( 0, 121, 241, 255 )
DARKBLUE =( 0, 82, 172, 255 )
PURPLE =( 200, 122, 255, 255 )
VIOLET =( 135, 60, 190, 255 )
DARKPURPLE =( 112, 31, 126, 255 )
BEIGE =( 211, 176, 131, 255 )
BROWN =( 127, 106, 79, 255 )
DARKBROWN =( 76, 63, 47, 255 )
WHITE =( 255, 255, 255, 255 )
BLACK =( 0, 0, 0, 255 )
BLANK =( 0, 0, 0, 0 )
MAGENTA =( 255, 0, 255, 255 )
RAYWHITE =( 245, 245, 245, 255 )

View file

@ -1 +0,0 @@
../../raylib/pyray.py

View file

@ -1 +0,0 @@
../../raylib/pyray.pyi

View file

@ -30,7 +30,7 @@ setup(
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
packages=["raylib"],
packages=["raylib", "pyray"],
include_package_data=True,
install_requires=["cffi>=1.14.5","inflection"],
)

View file

@ -1 +0,0 @@
../test_pyray.py

45
dynamic/test_pyray.py Normal file
View file

@ -0,0 +1,45 @@
"""
This shows how to use the Pyray wrapper around the static binding.
"""
import pyray as pr
pr.init_window(800, 450, "Raylib texture test")
pr.set_target_fps(60)
image = pr.gen_image_color(800, 400, (0,0,0,255) )
texture = pr.load_texture_from_image(image)
pr.update_texture(texture, image.data)
camera = pr.Camera3D([18.0, 16.0, 18.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 45.0, 0)
image = pr.load_image("examples/models/resources/heightmap.png")
texture = pr.load_texture_from_image(image)
mesh = pr.gen_mesh_heightmap(image, (16, 8, 16))
model = pr.load_model_from_mesh(mesh)
model.materials.maps[pr.MATERIAL_MAP_DIFFUSE].texture = texture
pr.unload_image(image)
pr.set_camera_mode(camera, pr.CAMERA_ORBITAL)
pos = pr.get_mouse_position()
ray = pr.get_mouse_ray(pos, camera)
rayhit = pr.get_collision_ray_ground(ray, 0)
print(str(rayhit.position.x))
while not pr.window_should_close():
pr.update_camera(camera)
pr.begin_drawing()
pr.clear_background(pr.RAYWHITE)
pr.begin_mode_3d(camera)
pr.draw_model(model, (-8.0, 0.0, -8.0), 1.0, pr.RED)
pr.draw_grid(20, 1.0)
pr.end_mode_3d()
pr.draw_text("This mesh should be textured", 190, 200, 20, pr.VIOLET)
pr.end_drawing()
pos = pr.get_mouse_position()
ray = pr.get_mouse_ray(pos, camera)
rayhit = pr.get_collision_ray_ground(ray, 0)
#print(str(rayhit.position.x))
pr.close_window()

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
pip3 install sphinx-autoapi myst_parser sphinx_rtd_theme
python3 create_stub_pyray.py > raylib/pyray.pyi
python3 create_stub_pyray.py > pyray/__init__.pyi
python3 create_stub_static.py >raylib/__init__.pyi
cd docs-src
make clean ; make html ; cp -a _build/html/. ../docs/

97
pyray/__init__.py Normal file
View file

@ -0,0 +1,97 @@
# Copyright (c) 2021 Richard Smith and others
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# This Source Code may also be made available under the following Secondary
# licenses when the conditions for such availability set forth in the Eclipse
# Public License, v. 2.0 are satisfied: GNU General Public License, version 2
# with the GNU Classpath Exception which is
# available at https://www.gnu.org/software/classpath/license.html.
#
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
from raylib import rl, ffi
from inspect import ismethod,getmembers,isbuiltin
import inflection
current_module = __import__(__name__)
def pointer(self, struct):
return ffi.addressof(struct)
LIGHTGRAY =( 200, 200, 200, 255 )
GRAY =( 130, 130, 130, 255 )
DARKGRAY =( 80, 80, 80, 255 )
YELLOW =( 253, 249, 0, 255 )
GOLD =( 255, 203, 0, 255 )
ORANGE =( 255, 161, 0, 255 )
PINK =( 255, 109, 194, 255 )
RED =( 230, 41, 55, 255 )
MAROON =( 190, 33, 55, 255 )
GREEN =( 0, 228, 48, 255 )
LIME =( 0, 158, 47, 255 )
DARKGREEN =( 0, 117, 44, 255 )
SKYBLUE =( 102, 191, 255, 255 )
BLUE =( 0, 121, 241, 255 )
DARKBLUE =( 0, 82, 172, 255 )
PURPLE =( 200, 122, 255, 255 )
VIOLET =( 135, 60, 190, 255 )
DARKPURPLE =( 112, 31, 126, 255 )
BEIGE =( 211, 176, 131, 255 )
BROWN =( 127, 106, 79, 255 )
DARKBROWN =( 76, 63, 47, 255 )
WHITE =( 255, 255, 255, 255 )
BLACK =( 0, 0, 0, 255 )
BLANK =( 0, 0, 0, 0 )
MAGENTA =( 255, 0, 255, 255 )
RAYWHITE =( 245, 245, 245, 255 )
def makefunc(a):
#print("makefunc ",a, ffi.typeof(a).args)
def func(*args):
modified_args = []
for (c_arg, arg) in zip(ffi.typeof(a).args, args):
#print(arg, c_arg.kind)
if type(arg) == str:
encoded = arg.encode('utf-8')
modified_args.append(encoded)
elif c_arg.kind == 'pointer' and str(type(arg)) == "<class '_cffi_backend.__CDataOwn'>":
modified_args.append(ffi.addressof(arg))
else:
modified_args.append(arg)
return a(*modified_args)
return func
def makeStructHelper(struct):
def func(*args):
return ffi.new(f"struct {struct} *", args)[0]
return func
for name, attr in getmembers(rl):
#print(name, attr)
uname = inflection.underscore(name).replace('3_d','_3d').replace('2_d','_2d')
if isbuiltin(attr) or str(type(attr)) == "<class '_cffi_backend.__FFIFunctionWrapper'>" or str(type(attr)) == "<class '_cffi_backend._CDataBase'>":
#print(attr.__call__)
#print(attr.__doc__)
#print(attr.__text_signature__)
#print(dir(attr))
#print(dir(attr.__repr__))
f = makefunc(attr)
setattr(current_module, uname, f)
#def wrap(*args):
# print("call to ",attr)
#setattr(PyRay, uname, lambda *args: print("call to ",attr))
else:
setattr(current_module, name, attr)
for struct in ffi.list_types()[0]:
f = makeStructHelper(struct)
setattr(current_module, struct, f)

2012
pyray/__init__.pyi Normal file

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ from raylib._raylib_cffi.lib import *
from raylib.colors import *
import cffi
import sys
from raylib.pyray import PyRay
#from raylib.pyray import PyRay
from .version import __version__
print("RAYLIB STATIC "+__version__+" LOADED", file=sys.stderr)

View file

@ -18,6 +18,30 @@ from inspect import ismethod,getmembers,isbuiltin
import inflection
class PyRay:
print("""
***********************************************
WARNING
Class raylib.PyRay() is depreciated.
Instead please use module, e.g.
import pyray
pyray.init_window(500, 500, '')
or even:
import * from pyray
init_window(500, 500, '')
**********************************************
""")
def pointer(self, struct):
return ffi.addressof(struct)

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,7 @@ setup(
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
packages=["raylib"],
packages=["raylib", "pyray"],
include_package_data=True,
install_requires=["cffi>=1.14.5","inflection"],
distclass=BinaryDistribution,

View file

@ -1,5 +1,5 @@
from raylib.pyray import PyRay
pyray = PyRay()
import pyray
pyray.init_window(800, 400, 'demo')

8
tests/hello_world.py Normal file
View file

@ -0,0 +1,8 @@
from pyray import *
init_window(800, 450, "Hello")
while not window_should_close():
begin_drawing()
clear_background(WHITE)
draw_text("Hello world", 190, 200, 20, VIOLET)
end_drawing()
close_window()

View file

@ -2,10 +2,7 @@
This shows how to use the Pyray wrapper around the static binding.
"""
import raylib
pr = raylib.PyRay()
import pyray as pr
pr.init_window(800, 450, "Raylib texture test")
pr.set_target_fps(60)

View file

@ -0,0 +1,45 @@
"""
This shows how to use the Pyray wrapper around the static binding.
"""
from pyray import *
init_window(800, 450, "Raylib texture test")
set_target_fps(60)
image = gen_image_color(800, 400, (0,0,0,255) )
texture = load_texture_from_image(image)
update_texture(texture, image.data)
camera = Camera3D([18.0, 16.0, 18.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 45.0, 0)
image = load_image("examples/models/resources/heightmap.png")
texture = load_texture_from_image(image)
mesh = gen_mesh_heightmap(image, (16, 8, 16))
model = load_model_from_mesh(mesh)
model.materials.maps[MATERIAL_MAP_DIFFUSE].texture = texture
unload_image(image)
set_camera_mode(camera, CAMERA_ORBITAL)
pos = get_mouse_position()
ray = get_mouse_ray(pos, camera)
rayhit = get_collision_ray_ground(ray, 0)
print(str(rayhit.position.x))
while not window_should_close():
update_camera(camera)
begin_drawing()
clear_background(RAYWHITE)
begin_mode_3d(camera)
draw_model(model, (-8.0, 0.0, -8.0), 1.0, RED)
draw_grid(20, 1.0)
end_mode_3d()
draw_text("This mesh should be textured", 190, 200, 20, VIOLET)
end_drawing()
pos = get_mouse_position()
ray = get_mouse_ray(pos, camera)
rayhit = get_collision_ray_ground(ray, 0)
#print(str(rayhit.position.x))
close_window()

View file

@ -0,0 +1,47 @@
"""
This shows how to use the Pyray wrapper around the static binding.
"""
from raylib import pyray
pr = pyray.PyRay()
pr.init_window(800, 450, "Raylib texture test")
pr.set_target_fps(60)
image = pr.gen_image_color(800, 400, (0,0,0,255) )
texture = pr.load_texture_from_image(image)
pr.update_texture(texture, image.data)
camera = pr.Camera3D([18.0, 16.0, 18.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 45.0, 0)
image = pr.load_image("examples/models/resources/heightmap.png")
texture = pr.load_texture_from_image(image)
mesh = pr.gen_mesh_heightmap(image, (16, 8, 16))
model = pr.load_model_from_mesh(mesh)
model.materials.maps[pr.MATERIAL_MAP_DIFFUSE].texture = texture
pr.unload_image(image)
pr.set_camera_mode(camera, pr.CAMERA_ORBITAL)
pos = pr.get_mouse_position()
ray = pr.get_mouse_ray(pos, camera)
rayhit = pr.get_collision_ray_ground(ray, 0)
print(str(rayhit.position.x))
while not pr.window_should_close():
pr.update_camera(camera)
pr.begin_drawing()
pr.clear_background(pr.RAYWHITE)
pr.begin_mode_3d(camera)
pr.draw_model(model, (-8.0, 0.0, -8.0), 1.0, pr.RED)
pr.draw_grid(20, 1.0)
pr.end_mode_3d()
pr.draw_text("This mesh should be textured", 190, 200, 20, pr.VIOLET)
pr.end_drawing()
pos = pr.get_mouse_position()
ray = pr.get_mouse_ray(pos, camera)
rayhit = pr.get_collision_ray_ground(ray, 0)
#print(str(rayhit.position.x))
pr.close_window()

View file

@ -1,6 +1,6 @@
"""
This shows how to use the CFFI static (API) binding. It should be fast and code be as close as possible to original
C code.
This shows how to use the CFFI dynamic (ABI) binding. Note that is slower and more likely to run into silent errors and segfaults.
But it doesnt require any C compiler to build.
"""
from raylib import ffi, rl, colors
@ -30,3 +30,8 @@ while not rl.WindowShouldClose():
rl.DrawText(b"This mesh should be textured", 190, 200, 20, colors.VIOLET)
rl.EndDrawing()
rl.CloseWindow()
"""
Previously this failed to work in the same place the ctypes binding fails, accessing
materials of a model. I though it was because Python can't dynamically tell the difference between a pointer and an array.
"""

View file

@ -0,0 +1,32 @@
"""
This shows how to use the CFFI static (API) binding. It should be fast and code be as close as possible to original
C code.
"""
import raylib as rl
rl.InitWindow(800, 450, b"Raylib static binding test with prefix")
rl.SetTargetFPS(60)
camera = rl.ffi.new("struct Camera3D *", [[18.0, 16.0, 18.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 45.0, 0])
image = rl.LoadImage(b"examples/models/resources/heightmap.png")
texture = rl.LoadTextureFromImage(image)
mesh = rl.GenMeshHeightmap(image, [16, 8, 16])
model = rl.LoadModelFromMesh(mesh)
print(model.materials) # SHOULD BE A pointer to a 'struct Material' but some is NULL pointer to 'Material' ?
model.materials.maps[rl.MATERIAL_MAP_DIFFUSE].texture = texture
rl.UnloadImage(image)
rl.SetCameraMode(camera[0], rl.CAMERA_ORBITAL)
while not rl.WindowShouldClose():
rl.UpdateCamera(camera)
rl.BeginDrawing()
rl.ClearBackground(rl.RAYWHITE)
rl.BeginMode3D(camera[0])
rl.DrawModel(model, (-8.0, 0.0, -8.0), 1.0, rl.RED)
rl.DrawGrid(20, 1.0)
rl.EndMode3D()
rl.DrawText(b"This mesh should be textured", 190, 200, 20, rl.VIOLET)
rl.EndDrawing()
rl.CloseWindow()