ncca-ngl 0.1.1__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ncca_ngl-0.1.4/PKG-INFO +22 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/pyproject.toml +6 -16
- ncca_ngl-0.1.4/src/ncca/ngl/.ruff_cache/.gitignore +2 -0
- ncca_ngl-0.1.4/src/ncca/ngl/.ruff_cache/0.13.0/10564494386971134025 +0 -0
- ncca_ngl-0.1.4/src/ncca/ngl/.ruff_cache/0.13.0/7783445477288392980 +0 -0
- ncca_ngl-0.1.4/src/ncca/ngl/.ruff_cache/CACHEDIR.TAG +1 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shader_program.py +31 -62
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/text_fragment.glsl +2 -2
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/text.py +5 -9
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/vec2_array.py +24 -6
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/vec3_array.py +23 -5
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/vec4_array.py +24 -6
- ncca_ngl-0.1.1/.github/workflows/sonar-scan.yml +0 -35
- ncca_ngl-0.1.1/.github/workflows/uv.yml +0 -30
- ncca_ngl-0.1.1/.gitignore +0 -72
- ncca_ngl-0.1.1/.pre-commit-config.yaml +0 -12
- ncca_ngl-0.1.1/NGLDebug.log +0 -497
- ncca_ngl-0.1.1/PKG-INFO +0 -23
- ncca_ngl-0.1.1/README.md +0 -22
- ncca_ngl-0.1.1/TODO.md +0 -13
- ncca_ngl-0.1.1/sonar-project.properties +0 -4
- ncca_ngl-0.1.1/tests/__init__.py +0 -0
- ncca_ngl-0.1.1/tests/conftest.py +0 -25
- ncca_ngl-0.1.1/tests/files/Arial.ttf +0 -0
- ncca_ngl-0.1.1/tests/files/BrokenFloats.obj +0 -10
- ncca_ngl-0.1.1/tests/files/BrokenNormals.obj +0 -10
- ncca_ngl-0.1.1/tests/files/BrokenUV.obj +0 -10
- ncca_ngl-0.1.1/tests/files/CubeNegativeIndex.obj +0 -35
- ncca_ngl-0.1.1/tests/files/EditFrag.glsl +0 -10
- ncca_ngl-0.1.1/tests/files/EditVert.glsl +0 -7
- ncca_ngl-0.1.1/tests/files/SimpleNegativeAll.obj +0 -9
- ncca_ngl-0.1.1/tests/files/Tri.xml +0 -24
- ncca_ngl-0.1.1/tests/files/TriColour.obj +0 -17
- ncca_ngl-0.1.1/tests/files/TriMessedFormat.obj +0 -17
- ncca_ngl-0.1.1/tests/files/Triangle1.mtl +0 -6
- ncca_ngl-0.1.1/tests/files/Triangle1.obj +0 -17
- ncca_ngl-0.1.1/tests/files/Triangle3UV.obj +0 -17
- ncca_ngl-0.1.1/tests/files/TriangleVertNormal.obj +0 -14
- ncca_ngl-0.1.1/tests/files/TriangleVertsOnly.obj +0 -12
- ncca_ngl-0.1.1/tests/files/TriangleVertsUV.obj +0 -15
- ncca_ngl-0.1.1/tests/files/failParseVertex.obj +0 -17
- ncca_ngl-0.1.1/tests/files/frag.glsl +0 -8
- ncca_ngl-0.1.1/tests/files/fragErr.glsl +0 -8
- ncca_ngl-0.1.1/tests/files/fragLinkErr.glsl +0 -10
- ncca_ngl-0.1.1/tests/files/geo.glsl +0 -8
- ncca_ngl-0.1.1/tests/files/geom.glsl +0 -8
- ncca_ngl-0.1.1/tests/files/simpleRGB.bmp +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGB.exr +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGB.png +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGB.tga +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGB.tiff +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGBA.bmp +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGBA.exr +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGBA.png +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGBA.tga +0 -0
- ncca_ngl-0.1.1/tests/files/simpleRGBA.tiff +0 -0
- ncca_ngl-0.1.1/tests/files/testUniformBufferFragment.glsl +0 -9
- ncca_ngl-0.1.1/tests/files/testUniformBufferVertex.glsl +0 -17
- ncca_ngl-0.1.1/tests/files/testUniformFragment.glsl +0 -19
- ncca_ngl-0.1.1/tests/files/testUniformVertex.glsl +0 -49
- ncca_ngl-0.1.1/tests/files/vert.glsl +0 -6
- ncca_ngl-0.1.1/tests/files/vertErr.glsl +0 -6
- ncca_ngl-0.1.1/tests/files/vertLinkErr.glsl +0 -7
- ncca_ngl-0.1.1/tests/mat3Data.py +0 -685
- ncca_ngl-0.1.1/tests/mat4Data.py +0 -1105
- ncca_ngl-0.1.1/tests/tempColourObj.obj +0 -9
- ncca_ngl-0.1.1/tests/tempObj.obj +0 -9
- ncca_ngl-0.1.1/tests/test_base_mesh.py +0 -249
- ncca_ngl-0.1.1/tests/test_bbox.py +0 -153
- ncca_ngl-0.1.1/tests/test_bezier_curve.py +0 -78
- ncca_ngl-0.1.1/tests/test_first_person_camera.py +0 -98
- ncca_ngl-0.1.1/tests/test_image.py +0 -66
- ncca_ngl-0.1.1/tests/test_logging.py +0 -9
- ncca_ngl-0.1.1/tests/test_mat2.py +0 -70
- ncca_ngl-0.1.1/tests/test_mat3.py +0 -241
- ncca_ngl-0.1.1/tests/test_mat4.py +0 -287
- ncca_ngl-0.1.1/tests/test_obj.py +0 -313
- ncca_ngl-0.1.1/tests/test_plane.py +0 -74
- ncca_ngl-0.1.1/tests/test_primitives.py +0 -121
- ncca_ngl-0.1.1/tests/test_pyside_event_handling_mixin.py +0 -644
- ncca_ngl-0.1.1/tests/test_quaternion.py +0 -133
- ncca_ngl-0.1.1/tests/test_random.py +0 -130
- ncca_ngl-0.1.1/tests/test_shaderlib.py +0 -1207
- ncca_ngl-0.1.1/tests/test_text.py +0 -15
- ncca_ngl-0.1.1/tests/test_texture.py +0 -75
- ncca_ngl-0.1.1/tests/test_transform.py +0 -160
- ncca_ngl-0.1.1/tests/test_util.py +0 -103
- ncca_ngl-0.1.1/tests/test_vao.py +0 -150
- ncca_ngl-0.1.1/tests/test_vec2.py +0 -216
- ncca_ngl-0.1.1/tests/test_vec2_array.py +0 -112
- ncca_ngl-0.1.1/tests/test_vec3.py +0 -324
- ncca_ngl-0.1.1/tests/test_vec3_array.py +0 -112
- ncca_ngl-0.1.1/tests/test_vec4.py +0 -281
- ncca_ngl-0.1.1/tests/test_vec4_array.py +0 -112
- ncca_ngl-0.1.1/uv.lock +0 -1028
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/LICENSE.txt +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/Primitives.npz +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/buddah.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/bunny.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/cube.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/dodecahedron.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/dragon.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/football.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/icosahedron.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/octahedron.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/pack_arrays.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/teapot.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/tetrahedron.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/PrimData/troll.npy +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/__init__.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/abstract_vao.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/base_mesh.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/base_mesh.pyi +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/bbox.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/bezier_curve.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/first_person_camera.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/image.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/log.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/mat2.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/mat3.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/mat4.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/multi_buffer_vao.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/obj.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/plane.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/primitives.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/pyside_event_handling_mixin.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/quaternion.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/random.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shader.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shader_lib.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/checker_fragment.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/checker_vertex.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/colour_fragment.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/colour_vertex.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/diffuse_fragment.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/diffuse_vertex.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/text_geometry.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/shaders/text_vertex.glsl +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/simple_index_vao.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/simple_vao.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/texture.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/transform.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/util.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/vao_factory.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/vec2.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/vec3.py +0 -0
- {ncca_ngl-0.1.1 → ncca_ngl-0.1.4}/src/ncca/ngl/vec4.py +0 -0
ncca_ngl-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ncca-ngl
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: A Python version of the NGL graphics library.
|
|
5
|
+
Author: Jon Macey
|
|
6
|
+
Author-email: Jon Macey <jmacey@bournemouth.ac.uk>
|
|
7
|
+
License: Copyright 2024 Jon Macey
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
14
|
+
Requires-Dist: numpy>=2.3.3
|
|
15
|
+
Requires-Dist: pyopengl
|
|
16
|
+
Requires-Dist: pillow
|
|
17
|
+
Requires-Dist: glfw>=2.9.0
|
|
18
|
+
Requires-Dist: freetype-py>=2.5.1
|
|
19
|
+
Requires-Dist: pyside6>=6.9.2
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Project-URL: Homepage, https://github.com/NCCA/PyNGL
|
|
22
|
+
Project-URL: Issues, https://github.com/NCCA/PyNGL/issues
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ncca-ngl"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "A Python version of the NGL graphics library."
|
|
5
5
|
authors = [{ name = "Jon Macey", email = "jmacey@bournemouth.ac.uk" }]
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -13,33 +13,23 @@ dependencies = [
|
|
|
13
13
|
"glfw>=2.9.0",
|
|
14
14
|
"freetype-py>=2.5.1",
|
|
15
15
|
"pyside6>=6.9.2",
|
|
16
|
-
"hatch>=1.14.2",
|
|
17
16
|
]
|
|
18
17
|
|
|
19
18
|
[project.urls]
|
|
20
19
|
Homepage = "https://github.com/NCCA/PyNGL"
|
|
21
20
|
Issues = "https://github.com/NCCA/PyNGL/issues"
|
|
22
21
|
|
|
23
|
-
[tool.setuptools.packages.find]
|
|
24
|
-
where = ["src"]
|
|
25
|
-
include = ["ncca.*"]
|
|
26
22
|
|
|
27
23
|
[tool.pytest.ini_options]
|
|
28
24
|
pythonpath = ["src","tests"]
|
|
29
25
|
|
|
30
|
-
|
|
31
26
|
[build-system]
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
requires = ["hatchling"]
|
|
35
|
-
build-backend = "hatchling.build"
|
|
36
|
-
[tool.hatch.build.targets.wheel]
|
|
37
|
-
packages = ["src/ncca/*"]
|
|
38
|
-
exclude = [
|
|
39
|
-
"tests",
|
|
40
|
-
]
|
|
41
|
-
|
|
27
|
+
requires = ["uv_build>=0.8.13,<0.9.0"]
|
|
28
|
+
build-backend = "uv_build"
|
|
42
29
|
|
|
30
|
+
[tool.uv.build-backend]
|
|
31
|
+
module-name = "ncca.ngl"
|
|
32
|
+
module-root = "src"
|
|
43
33
|
|
|
44
34
|
|
|
45
35
|
[dependency-groups]
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Signature: 8a477f597d28d172789f06886806bc55
|
|
@@ -105,9 +105,7 @@ class ShaderProgram:
|
|
|
105
105
|
if is_array:
|
|
106
106
|
for element_idx in range(size):
|
|
107
107
|
element_name = f"{base_name}[{element_idx}]"
|
|
108
|
-
element_location = gl.glGetUniformLocation(
|
|
109
|
-
self._id, element_name.encode("utf-8")
|
|
110
|
-
)
|
|
108
|
+
element_location = gl.glGetUniformLocation(self._id, element_name.encode("utf-8"))
|
|
111
109
|
if element_location != -1:
|
|
112
110
|
# Store individual array element: (location, shader_type, 1, False)
|
|
113
111
|
self._uniforms[element_name] = (
|
|
@@ -122,9 +120,7 @@ class ShaderProgram:
|
|
|
122
120
|
logger.info(
|
|
123
121
|
f"Registered array uniform: {base_name}[{size}] (type: {self.get_gl_type_string(shader_type)}, location: {location})"
|
|
124
122
|
)
|
|
125
|
-
logger.info(
|
|
126
|
-
f" Also registered individual elements: {base_name}[0] to {base_name}[{size - 1}]"
|
|
127
|
-
)
|
|
123
|
+
logger.info(f" Also registered individual elements: {base_name}[0] to {base_name}[{size - 1}]")
|
|
128
124
|
else:
|
|
129
125
|
logger.info(
|
|
130
126
|
f"Registered uniform: {base_name} (type: {self.get_gl_type_string(shader_type)}, location: {location})"
|
|
@@ -147,14 +143,8 @@ class ShaderProgram:
|
|
|
147
143
|
name_buffer = (ctypes.c_char * 256)()
|
|
148
144
|
length = ctypes.c_int()
|
|
149
145
|
|
|
150
|
-
gl.glGetActiveUniformBlockName(
|
|
151
|
-
|
|
152
|
-
)
|
|
153
|
-
name_str = (
|
|
154
|
-
name_buffer.value.decode("utf-8")
|
|
155
|
-
if name_buffer.value
|
|
156
|
-
else f"UniformBlock_{i}"
|
|
157
|
-
)
|
|
146
|
+
gl.glGetActiveUniformBlockName(self._id, i, 256, ctypes.byref(length), name_buffer)
|
|
147
|
+
name_str = name_buffer.value.decode("utf-8") if name_buffer.value else f"UniformBlock_{i}"
|
|
158
148
|
|
|
159
149
|
# Create uniform block data structure
|
|
160
150
|
data = {
|
|
@@ -252,9 +242,7 @@ class ShaderProgram:
|
|
|
252
242
|
bool: True if successful, False otherwise
|
|
253
243
|
"""
|
|
254
244
|
if uniform_block_name not in self._registered_uniform_blocks:
|
|
255
|
-
logger.error(
|
|
256
|
-
f"Uniform block '{uniform_block_name}' not found in shader '{self._name}'"
|
|
257
|
-
)
|
|
245
|
+
logger.error(f"Uniform block '{uniform_block_name}' not found in shader '{self._name}'")
|
|
258
246
|
return False
|
|
259
247
|
|
|
260
248
|
block = self._registered_uniform_blocks[uniform_block_name]
|
|
@@ -307,17 +295,11 @@ class ShaderProgram:
|
|
|
307
295
|
elif isinstance(val, float):
|
|
308
296
|
gl.glUniform1f(loc, val)
|
|
309
297
|
elif isinstance(val, Mat2):
|
|
310
|
-
gl.glUniformMatrix2fv(
|
|
311
|
-
loc, 1, gl.GL_FALSE, (ctypes.c_float * 4)(*val.get_matrix())
|
|
312
|
-
)
|
|
298
|
+
gl.glUniformMatrix2fv(loc, 1, gl.GL_FALSE, (ctypes.c_float * 4)(*val.get_matrix()))
|
|
313
299
|
elif isinstance(val, Mat3):
|
|
314
|
-
gl.glUniformMatrix3fv(
|
|
315
|
-
loc, 1, gl.GL_FALSE, (ctypes.c_float * 9)(*val.get_matrix())
|
|
316
|
-
)
|
|
300
|
+
gl.glUniformMatrix3fv(loc, 1, gl.GL_FALSE, (ctypes.c_float * 9)(*val.get_matrix()))
|
|
317
301
|
elif isinstance(val, Mat4):
|
|
318
|
-
gl.glUniformMatrix4fv(
|
|
319
|
-
loc, 1, gl.GL_FALSE, (ctypes.c_float * 16)(*val.get_matrix())
|
|
320
|
-
)
|
|
302
|
+
gl.glUniformMatrix4fv(loc, 1, gl.GL_FALSE, (ctypes.c_float * 16)(*val.get_matrix()))
|
|
321
303
|
elif isinstance(val, Vec2):
|
|
322
304
|
gl.glUniform2f(loc, *val)
|
|
323
305
|
elif isinstance(val, Vec3):
|
|
@@ -328,28 +310,29 @@ class ShaderProgram:
|
|
|
328
310
|
try:
|
|
329
311
|
val = list(value[0])
|
|
330
312
|
if len(val) == 4:
|
|
331
|
-
gl.glUniformMatrix2fv(
|
|
332
|
-
loc, 1, gl.GL_FALSE, (ctypes.c_float * 4)(*val)
|
|
333
|
-
)
|
|
313
|
+
gl.glUniformMatrix2fv(loc, 1, gl.GL_FALSE, (ctypes.c_float * 4)(*val))
|
|
334
314
|
elif len(val) == 9:
|
|
335
|
-
gl.glUniformMatrix3fv(
|
|
336
|
-
loc, 1, gl.GL_FALSE, (ctypes.c_float * 9)(*val)
|
|
337
|
-
)
|
|
315
|
+
gl.glUniformMatrix3fv(loc, 1, gl.GL_FALSE, (ctypes.c_float * 9)(*val))
|
|
338
316
|
elif len(val) == 16:
|
|
339
|
-
gl.glUniformMatrix4fv(
|
|
340
|
-
loc, 1, gl.GL_FALSE, (ctypes.c_float * 16)(*val)
|
|
341
|
-
)
|
|
317
|
+
gl.glUniformMatrix4fv(loc, 1, gl.GL_FALSE, (ctypes.c_float * 16)(*val))
|
|
342
318
|
except TypeError:
|
|
343
|
-
logger.warning(
|
|
344
|
-
f"Warning: uniform '{name}' has unknown type: {type(val)}"
|
|
345
|
-
)
|
|
319
|
+
logger.warning(f"Warning: uniform '{name}' has unknown type: {type(val)}")
|
|
346
320
|
|
|
347
321
|
elif len(value) == 2:
|
|
348
|
-
|
|
322
|
+
if isinstance(value[0], int):
|
|
323
|
+
gl.glUniform2i(loc, *value)
|
|
324
|
+
else:
|
|
325
|
+
gl.glUniform2f(loc, *value)
|
|
349
326
|
elif len(value) == 3:
|
|
350
|
-
|
|
327
|
+
if isinstance(value[0], int):
|
|
328
|
+
gl.glUniform3i(loc, *value)
|
|
329
|
+
else:
|
|
330
|
+
gl.glUniform3f(loc, *value)
|
|
351
331
|
elif len(value) == 4:
|
|
352
|
-
|
|
332
|
+
if isinstance(value[0], int):
|
|
333
|
+
gl.glUniform4i(loc, *value)
|
|
334
|
+
else:
|
|
335
|
+
gl.glUniform4f(loc, *value)
|
|
353
336
|
|
|
354
337
|
def set_uniform_1fv(self, name: str, values: List[float]) -> None:
|
|
355
338
|
"""
|
|
@@ -376,9 +359,7 @@ class ShaderProgram:
|
|
|
376
359
|
loc = self.get_uniform_location(name)
|
|
377
360
|
if loc != -1:
|
|
378
361
|
flat_values = [item for vec in values for item in vec]
|
|
379
|
-
gl.glUniform2fv(
|
|
380
|
-
loc, len(values), (ctypes.c_float * len(flat_values))(*flat_values)
|
|
381
|
-
)
|
|
362
|
+
gl.glUniform2fv(loc, len(values), (ctypes.c_float * len(flat_values))(*flat_values))
|
|
382
363
|
|
|
383
364
|
def set_uniform_3fv(self, name: str, values: List[List[float]]) -> None:
|
|
384
365
|
"""
|
|
@@ -392,9 +373,7 @@ class ShaderProgram:
|
|
|
392
373
|
loc = self.get_uniform_location(name)
|
|
393
374
|
if loc != -1:
|
|
394
375
|
flat_values = [item for vec in values for item in vec]
|
|
395
|
-
gl.glUniform3fv(
|
|
396
|
-
loc, len(values), (ctypes.c_float * len(flat_values))(*flat_values)
|
|
397
|
-
)
|
|
376
|
+
gl.glUniform3fv(loc, len(values), (ctypes.c_float * len(flat_values))(*flat_values))
|
|
398
377
|
|
|
399
378
|
def set_uniform_4fv(self, name: str, values: List[List[float]]) -> None:
|
|
400
379
|
"""
|
|
@@ -408,9 +387,7 @@ class ShaderProgram:
|
|
|
408
387
|
loc = self.get_uniform_location(name)
|
|
409
388
|
if loc != -1:
|
|
410
389
|
flat_values = [item for vec in values for item in vec]
|
|
411
|
-
gl.glUniform4fv(
|
|
412
|
-
loc, len(values), (ctypes.c_float * len(flat_values))(*flat_values)
|
|
413
|
-
)
|
|
390
|
+
gl.glUniform4fv(loc, len(values), (ctypes.c_float * len(flat_values))(*flat_values))
|
|
414
391
|
|
|
415
392
|
def set_uniform_1iv(self, name: str, values: List[int]) -> None:
|
|
416
393
|
"""
|
|
@@ -750,9 +727,7 @@ class ShaderProgram:
|
|
|
750
727
|
base_name = name.split("[")[0]
|
|
751
728
|
if base_name not in array_elements:
|
|
752
729
|
array_elements[base_name] = []
|
|
753
|
-
array_elements[base_name].append(
|
|
754
|
-
(name, location, uniform_type, size, is_array)
|
|
755
|
-
)
|
|
730
|
+
array_elements[base_name].append((name, location, uniform_type, size, is_array))
|
|
756
731
|
else:
|
|
757
732
|
base_uniforms[name] = (location, uniform_type, size, is_array)
|
|
758
733
|
|
|
@@ -760,9 +735,7 @@ class ShaderProgram:
|
|
|
760
735
|
for name, (location, uniform_type, size, is_array) in base_uniforms.items():
|
|
761
736
|
type_str = self.get_gl_type_string(uniform_type)
|
|
762
737
|
if is_array:
|
|
763
|
-
logger.info(
|
|
764
|
-
f" {name}[{size}] (type: {type_str}, location: {location})"
|
|
765
|
-
)
|
|
738
|
+
logger.info(f" {name}[{size}] (type: {type_str}, location: {location})")
|
|
766
739
|
else:
|
|
767
740
|
logger.info(f" {name} (type: {type_str}, location: {location})")
|
|
768
741
|
|
|
@@ -771,9 +744,7 @@ class ShaderProgram:
|
|
|
771
744
|
logger.info(f" Array elements for {base_name}:")
|
|
772
745
|
for element_name, location, uniform_type, size, is_array in elements:
|
|
773
746
|
type_str = self.get_gl_type_string(uniform_type)
|
|
774
|
-
logger.info(
|
|
775
|
-
f" {element_name} (type: {type_str}, location: {location})"
|
|
776
|
-
)
|
|
747
|
+
logger.info(f" {element_name} (type: {type_str}, location: {location})")
|
|
777
748
|
|
|
778
749
|
def print_registered_uniform_blocks(self) -> None:
|
|
779
750
|
"""
|
|
@@ -811,6 +782,4 @@ class ShaderProgram:
|
|
|
811
782
|
if self._registered_uniform_blocks:
|
|
812
783
|
logger.info(" Registered uniform blocks:")
|
|
813
784
|
for name, data in self._registered_uniform_blocks.items():
|
|
814
|
-
logger.info(
|
|
815
|
-
f" {name} (index: {data['loc']}, buffer: {data['buffer']})"
|
|
816
|
-
)
|
|
785
|
+
logger.info(f" {name} (index: {data['loc']}, buffer: {data['buffer']})")
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#version 410 core
|
|
2
2
|
in vec2 v_uv;
|
|
3
3
|
uniform sampler2D textureID;
|
|
4
|
-
uniform vec4
|
|
4
|
+
uniform vec4 textColour;
|
|
5
5
|
out vec4 fragColor;
|
|
6
6
|
void main()
|
|
7
7
|
{
|
|
8
8
|
float a = texture(textureID, v_uv).a;
|
|
9
|
-
fragColor = vec4(
|
|
9
|
+
fragColor = vec4(textColour.rgb, textColour.a * a);
|
|
10
10
|
}
|
|
@@ -223,13 +223,11 @@ class _Text:
|
|
|
223
223
|
"""
|
|
224
224
|
ShaderLib.use(DefaultShader.TEXT)
|
|
225
225
|
ShaderLib.set_uniform("textureID", 0)
|
|
226
|
-
ShaderLib.set_uniform("screenSize", w, h)
|
|
226
|
+
ShaderLib.set_uniform("screenSize", float(w), float(h))
|
|
227
227
|
ShaderLib.set_uniform("fontSize", 1.0)
|
|
228
|
-
ShaderLib.set_uniform("
|
|
228
|
+
ShaderLib.set_uniform("textColour", 1.0, 1.0, 1.0, 1.0)
|
|
229
229
|
|
|
230
|
-
def render_text(
|
|
231
|
-
self, font: str, x: int, y: int, text: str, colour: Vec3 = Vec3(1, 1, 1)
|
|
232
|
-
) -> None:
|
|
230
|
+
def render_text(self, font: str, x: int, y: int, text: str, colour: Vec3 = Vec3(1.0, 1.0, 1.0)) -> None:
|
|
233
231
|
"""
|
|
234
232
|
Renders a string of text to the screen.
|
|
235
233
|
|
|
@@ -276,7 +274,7 @@ class _Text:
|
|
|
276
274
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
277
275
|
gl.glBindTexture(gl.GL_TEXTURE_2D, atlas.texture)
|
|
278
276
|
ShaderLib.use(DefaultShader.TEXT)
|
|
279
|
-
ShaderLib.set_uniform("
|
|
277
|
+
ShaderLib.set_uniform("textColour", float(colour.x), float(colour.y), float(colour.z), 1.0)
|
|
280
278
|
# We are drawing one point per character
|
|
281
279
|
vao.set_num_indices(len(render_data) // 8)
|
|
282
280
|
vao.draw()
|
|
@@ -288,9 +286,7 @@ class _Text:
|
|
|
288
286
|
if polygon_mode != gl.GL_FILL:
|
|
289
287
|
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, polygon_mode)
|
|
290
288
|
|
|
291
|
-
def _build_instances(
|
|
292
|
-
self, font: str, text: str, start_x: int, start_y: int
|
|
293
|
-
) -> List[float]:
|
|
289
|
+
def _build_instances(self, font: str, text: str, start_x: int, start_y: int) -> List[float]:
|
|
294
290
|
"""
|
|
295
291
|
Generates vertex attribute data for each character in a string.
|
|
296
292
|
|
|
@@ -14,17 +14,23 @@ class Vec2Array:
|
|
|
14
14
|
|
|
15
15
|
def __init__(self, values=None):
|
|
16
16
|
"""
|
|
17
|
-
Initializes the
|
|
17
|
+
Initializes the Vec3Array.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
|
-
values (iterable, optional): An iterable of
|
|
20
|
+
values (iterable | int, optional): An iterable of Vec3 objects or an integer.
|
|
21
|
+
If an integer, the array is initialized with that many default Vec3s.
|
|
22
|
+
If an iterable, it's initialized with the Vec3s from the iterable.
|
|
23
|
+
Defaults to None (an empty array).
|
|
21
24
|
"""
|
|
22
25
|
self._data = []
|
|
23
26
|
if values is not None:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if isinstance(values, int):
|
|
28
|
+
self._data = [Vec2() for _ in range(values)]
|
|
29
|
+
else:
|
|
30
|
+
for v in values:
|
|
31
|
+
if not isinstance(v, Vec2):
|
|
32
|
+
raise TypeError("All elements must be of type Vec2")
|
|
33
|
+
self._data.append(v)
|
|
28
34
|
|
|
29
35
|
def __getitem__(self, index):
|
|
30
36
|
"""
|
|
@@ -38,6 +44,18 @@ class Vec2Array:
|
|
|
38
44
|
"""
|
|
39
45
|
return self._data[index]
|
|
40
46
|
|
|
47
|
+
def __setitem__(self, index, value):
|
|
48
|
+
"""
|
|
49
|
+
Set the Vec2 at the specified index.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
index (int): The index of the element to set.
|
|
53
|
+
value (Vec3): The new Vec3 object.
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(value, Vec2):
|
|
56
|
+
raise TypeError("Only Vec2 objects can be assigned")
|
|
57
|
+
self._data[index] = value
|
|
58
|
+
|
|
41
59
|
def __len__(self):
|
|
42
60
|
"""
|
|
43
61
|
Return the number of elements in the array.
|
|
@@ -17,14 +17,20 @@ class Vec3Array:
|
|
|
17
17
|
Initializes the Vec3Array.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
|
-
values (iterable, optional): An iterable of Vec3 objects
|
|
20
|
+
values (iterable | int, optional): An iterable of Vec3 objects or an integer.
|
|
21
|
+
If an integer, the array is initialized with that many default Vec3s.
|
|
22
|
+
If an iterable, it's initialized with the Vec3s from the iterable.
|
|
23
|
+
Defaults to None (an empty array).
|
|
21
24
|
"""
|
|
22
25
|
self._data = []
|
|
23
26
|
if values is not None:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if isinstance(values, int):
|
|
28
|
+
self._data = [Vec3() for _ in range(values)]
|
|
29
|
+
else:
|
|
30
|
+
for v in values:
|
|
31
|
+
if not isinstance(v, Vec3):
|
|
32
|
+
raise TypeError("All elements must be of type Vec3")
|
|
33
|
+
self._data.append(v)
|
|
28
34
|
|
|
29
35
|
def __getitem__(self, index):
|
|
30
36
|
"""
|
|
@@ -38,6 +44,18 @@ class Vec3Array:
|
|
|
38
44
|
"""
|
|
39
45
|
return self._data[index]
|
|
40
46
|
|
|
47
|
+
def __setitem__(self, index, value):
|
|
48
|
+
"""
|
|
49
|
+
Set the Vec3 at the specified index.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
index (int): The index of the element to set.
|
|
53
|
+
value (Vec3): The new Vec3 object.
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(value, Vec3):
|
|
56
|
+
raise TypeError("Only Vec3 objects can be assigned")
|
|
57
|
+
self._data[index] = value
|
|
58
|
+
|
|
41
59
|
def __len__(self):
|
|
42
60
|
"""
|
|
43
61
|
Return the number of elements in the array.
|
|
@@ -14,17 +14,23 @@ class Vec4Array:
|
|
|
14
14
|
|
|
15
15
|
def __init__(self, values=None):
|
|
16
16
|
"""
|
|
17
|
-
Initializes the
|
|
17
|
+
Initializes the Vec3Array.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
|
-
values (iterable, optional): An iterable of Vec4 objects
|
|
20
|
+
values (iterable | int, optional): An iterable of Vec4 objects or an integer.
|
|
21
|
+
If an integer, the array is initialized with that many default Vec3s.
|
|
22
|
+
If an iterable, it's initialized with the Vec3s from the iterable.
|
|
23
|
+
Defaults to None (an empty array).
|
|
21
24
|
"""
|
|
22
25
|
self._data = []
|
|
23
26
|
if values is not None:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if isinstance(values, int):
|
|
28
|
+
self._data = [Vec4() for _ in range(values)]
|
|
29
|
+
else:
|
|
30
|
+
for v in values:
|
|
31
|
+
if not isinstance(v, Vec4):
|
|
32
|
+
raise TypeError("All elements must be of type Vec4")
|
|
33
|
+
self._data.append(v)
|
|
28
34
|
|
|
29
35
|
def __getitem__(self, index):
|
|
30
36
|
"""
|
|
@@ -38,6 +44,18 @@ class Vec4Array:
|
|
|
38
44
|
"""
|
|
39
45
|
return self._data[index]
|
|
40
46
|
|
|
47
|
+
def __setitem__(self, index, value):
|
|
48
|
+
"""
|
|
49
|
+
Set the Vec3 at the specified index.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
index (int): The index of the element to set.
|
|
53
|
+
value (Vec4): The new Vec3 object.
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(value, Vec4):
|
|
56
|
+
raise TypeError("Only Vec4 objects can be assigned")
|
|
57
|
+
self._data[index] = value
|
|
58
|
+
|
|
41
59
|
def __len__(self):
|
|
42
60
|
"""
|
|
43
61
|
Return the number of elements in the array.
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: Sonar Scanner
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
branches:
|
|
5
|
-
- main
|
|
6
|
-
pull_request:
|
|
7
|
-
types: [opened, synchronize, reopened]
|
|
8
|
-
jobs:
|
|
9
|
-
sonarcloud:
|
|
10
|
-
name: SonarCloud
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
strategy:
|
|
13
|
-
matrix:
|
|
14
|
-
python-version: ["3.13"]
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v3
|
|
17
|
-
with:
|
|
18
|
-
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
|
19
|
-
- name: Install uv
|
|
20
|
-
uses: astral-sh/setup-uv@v6
|
|
21
|
-
with:
|
|
22
|
-
python-version: ${{ matrix.python-version }}
|
|
23
|
-
|
|
24
|
-
- name: Install the project
|
|
25
|
-
run: uv sync --locked --all-extras --dev
|
|
26
|
-
|
|
27
|
-
- name: Install dependencies and run coverag
|
|
28
|
-
run: |
|
|
29
|
-
uv run coverage run --source=src/ngl -m pytest -v tests && uv run coverage report -m
|
|
30
|
-
uv run coverage xml
|
|
31
|
-
- name: SonarCloud Scan
|
|
32
|
-
uses: SonarSource/sonarqube-scan-action@v5.0.0
|
|
33
|
-
env:
|
|
34
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
|
35
|
-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
name: UV Tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
name: UV tests
|
|
12
|
-
runs-on: ${{ matrix.os }}
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
16
|
-
python-version:
|
|
17
|
-
- "3.13"
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v4
|
|
20
|
-
|
|
21
|
-
- name: Install uv
|
|
22
|
-
uses: astral-sh/setup-uv@v6
|
|
23
|
-
with:
|
|
24
|
-
python-version: ${{ matrix.python-version }}
|
|
25
|
-
|
|
26
|
-
- name: Install the project
|
|
27
|
-
run: uv sync --locked --all-extras --dev
|
|
28
|
-
|
|
29
|
-
- name: Run tests
|
|
30
|
-
run: uv run pytest tests --ignore=tests/test_shaderlib.py --ignore=tests/test_texture.py --ignore=tests/test_text.py --ignore=tests/test_base_mesh.py --ignore=tests/test_primitives.py --ignore=tests/test_obj.py --ignore=tests/test_vao.py
|
ncca_ngl-0.1.1/.gitignore
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Byte-compiled / optimized / DLL files
|
|
2
|
-
__pycache__/
|
|
3
|
-
*.py[cod]
|
|
4
|
-
*$py.class
|
|
5
|
-
.vscode/
|
|
6
|
-
.idea
|
|
7
|
-
.ropeproject
|
|
8
|
-
# stubgen files and MyPY
|
|
9
|
-
out/
|
|
10
|
-
.mypy_cache/
|
|
11
|
-
# C extensions
|
|
12
|
-
*.so
|
|
13
|
-
|
|
14
|
-
# Distribution / packaging
|
|
15
|
-
.Python
|
|
16
|
-
build/
|
|
17
|
-
develop-eggs/
|
|
18
|
-
dist/
|
|
19
|
-
downloads/
|
|
20
|
-
eggs/
|
|
21
|
-
.eggs/
|
|
22
|
-
lib/
|
|
23
|
-
lib64/
|
|
24
|
-
parts/
|
|
25
|
-
sdist/
|
|
26
|
-
var/
|
|
27
|
-
wheels/
|
|
28
|
-
share/python-wheels/
|
|
29
|
-
*.egg-info/
|
|
30
|
-
.installed.cfg
|
|
31
|
-
*.egg
|
|
32
|
-
MANIFEST
|
|
33
|
-
.pytest_cache/
|
|
34
|
-
docs/build
|
|
35
|
-
# PyInstaller
|
|
36
|
-
# Usually these files are written by a python script from a template
|
|
37
|
-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
38
|
-
*.manifest
|
|
39
|
-
*.spec
|
|
40
|
-
|
|
41
|
-
# Installer logs
|
|
42
|
-
pip-log.txt
|
|
43
|
-
pip-delete-this-directory.txt
|
|
44
|
-
|
|
45
|
-
# Unit test / coverage reports
|
|
46
|
-
htmlcov/
|
|
47
|
-
.tox/
|
|
48
|
-
.nox/
|
|
49
|
-
.coverage
|
|
50
|
-
.coverage.*
|
|
51
|
-
.cache
|
|
52
|
-
nosetests.xml
|
|
53
|
-
coverage.xml
|
|
54
|
-
*.cover
|
|
55
|
-
*.py,cover
|
|
56
|
-
.hypothesis/
|
|
57
|
-
.pytest_cache/
|
|
58
|
-
cover/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# Environments
|
|
62
|
-
.env
|
|
63
|
-
.venv
|
|
64
|
-
env/
|
|
65
|
-
venv/
|
|
66
|
-
ENV/
|
|
67
|
-
env.bak/
|
|
68
|
-
venv.bak/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# Cython debug symbols
|
|
72
|
-
cython_debug/
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
repos:
|
|
2
|
-
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
-
# Ruff version.
|
|
4
|
-
rev: v0.12.10
|
|
5
|
-
hooks:
|
|
6
|
-
# Run the linter.
|
|
7
|
-
- id: ruff-check
|
|
8
|
-
types_or: [python, pyi]
|
|
9
|
-
args: ["check", "--select", "I", "--fix"]
|
|
10
|
-
# Run the formatter.
|
|
11
|
-
- id: ruff-format
|
|
12
|
-
types_or: [python, pyi]
|