ncca-ngl 0.3.5__py3-none-any.whl → 0.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. ncca/ngl/PrimData/pack_arrays.py +2 -3
  2. ncca/ngl/__init__.py +5 -5
  3. ncca/ngl/base_mesh.py +28 -20
  4. ncca/ngl/image.py +1 -3
  5. ncca/ngl/mat2.py +79 -53
  6. ncca/ngl/mat3.py +104 -185
  7. ncca/ngl/mat4.py +144 -309
  8. ncca/ngl/prim_data.py +42 -36
  9. ncca/ngl/primitives.py +2 -2
  10. ncca/ngl/pyside_event_handling_mixin.py +0 -108
  11. ncca/ngl/quaternion.py +69 -36
  12. ncca/ngl/shader.py +0 -116
  13. ncca/ngl/shader_program.py +94 -117
  14. ncca/ngl/texture.py +5 -2
  15. ncca/ngl/util.py +50 -0
  16. ncca/ngl/vec2.py +59 -302
  17. ncca/ngl/vec2_array.py +79 -28
  18. ncca/ngl/vec3.py +60 -350
  19. ncca/ngl/vec3_array.py +76 -23
  20. ncca/ngl/vec4.py +90 -200
  21. ncca/ngl/vec4_array.py +78 -27
  22. ncca/ngl/vector_base.py +548 -0
  23. ncca/ngl/webgpu/__init__.py +20 -0
  24. ncca/ngl/webgpu/__main__.py +640 -0
  25. ncca/ngl/webgpu/__main__.py.backup +640 -0
  26. ncca/ngl/webgpu/base_webgpu_pipeline.py +354 -0
  27. ncca/ngl/webgpu/custom_shader_pipeline.py +288 -0
  28. ncca/ngl/webgpu/instanced_geometry_pipeline.py +594 -0
  29. ncca/ngl/webgpu/line_pipeline.py +405 -0
  30. ncca/ngl/webgpu/pipeline_factory.py +190 -0
  31. ncca/ngl/webgpu/pipeline_shaders.py +497 -0
  32. ncca/ngl/webgpu/point_list_pipeline.py +349 -0
  33. ncca/ngl/webgpu/point_pipeline.py +336 -0
  34. ncca/ngl/webgpu/triangle_pipeline.py +419 -0
  35. ncca/ngl/webgpu/webgpu_constants.py +33 -0
  36. ncca/ngl/webgpu/webgpu_widget.py +322 -0
  37. ncca/ngl/webgpu/wip/REFACTORING_SUMMARY.md +82 -0
  38. ncca/ngl/webgpu/wip/UNIFIED_SYSTEM.md +314 -0
  39. ncca/ngl/webgpu/wip/buffer_manager.py +396 -0
  40. ncca/ngl/webgpu/wip/pipeline_config.py +463 -0
  41. ncca/ngl/webgpu/wip/shader_constants.py +328 -0
  42. ncca/ngl/webgpu/wip/shader_templates.py +563 -0
  43. ncca/ngl/webgpu/wip/unified_examples.py +390 -0
  44. ncca/ngl/webgpu/wip/unified_factory.py +449 -0
  45. ncca/ngl/webgpu/wip/unified_pipeline.py +469 -0
  46. ncca/ngl/widgets/__init__.py +18 -2
  47. ncca/ngl/widgets/__main__.py +2 -1
  48. ncca/ngl/widgets/lookatwidget.py +2 -1
  49. ncca/ngl/widgets/mat4widget.py +2 -2
  50. ncca/ngl/widgets/vec2widget.py +1 -1
  51. ncca/ngl/widgets/vec3widget.py +1 -0
  52. {ncca_ngl-0.3.5.dist-info → ncca_ngl-0.5.1.dist-info}/METADATA +3 -2
  53. ncca_ngl-0.5.1.dist-info/RECORD +105 -0
  54. ncca/ngl/widgets/transformation_widget.py +0 -299
  55. ncca_ngl-0.3.5.dist-info/RECORD +0 -82
  56. {ncca_ngl-0.3.5.dist-info → ncca_ngl-0.5.1.dist-info}/WHEEL +0 -0
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- import numpy as np
4
3
  import pathlib
5
4
 
5
+ import numpy as np
6
+
6
7
  files = pathlib.Path(".").glob("*.npy")
7
8
 
8
9
  data = {}
9
10
  for f in files:
10
11
  data[str(f.stem)] = np.load(f)
11
- # arrays.append(np.load(f))
12
- # names.append(str(f.stem))
13
12
  print(data.keys())
14
13
 
15
14
 
ncca/ngl/__init__.py CHANGED
@@ -2,10 +2,9 @@
2
2
  from importlib.metadata import PackageNotFoundError, version
3
3
 
4
4
  try:
5
- __version__ = version("ncca-ngl")
6
- except PackageNotFoundError:
5
+ __version__ = version("ncca-ngl") # pragma: no cover
6
+ except PackageNotFoundError: # pragma: no cover
7
7
  __version__ = "0.0.0"
8
-
9
8
  __author__ = "Jon Macey jmacey@bournemouth.ac.uk"
10
9
  __license__ = "MIT"
11
10
 
@@ -16,7 +15,7 @@ from .bezier_curve import BezierCurve
16
15
  from .first_person_camera import FirstPersonCamera
17
16
  from .image import Image, ImageModes
18
17
  from .log import logger
19
- from .mat2 import Mat2
18
+ from .mat2 import Mat2, Mat2Error, Mat2NotSquare
20
19
  from .mat3 import Mat3, Mat3Error, Mat3NotSquare
21
20
  from .mat4 import Mat4, Mat4Error, Mat4NotSquare
22
21
  from .multi_buffer_vao import MultiBufferVAO
@@ -41,7 +40,7 @@ from .simple_vao import SimpleVAO
41
40
  from .text import Text
42
41
  from .texture import Texture
43
42
  from .transform import Transform, TransformRotationOrder
44
- from .util import PerspMode, calc_normal, clamp, frustum, lerp, look_at, ortho, perspective
43
+ from .util import PerspMode, calc_normal, clamp, frustum, lerp, look_at, ortho, perspective, renderman_look_at
45
44
  from .vao_factory import VAOFactory, VAOType
46
45
  from .vec2 import Vec2
47
46
  from .vec2_array import Vec2Array
@@ -111,4 +110,5 @@ all = [
111
110
  FirstPersonCamera,
112
111
  PySideEventHandlingMixin,
113
112
  PerspMode,
113
+ renderman_look_at,
114
114
  ]
ncca/ngl/base_mesh.py CHANGED
@@ -54,6 +54,24 @@ class BaseMesh:
54
54
  """
55
55
  return all(len(f.vertex) == 3 for f in self.faces)
56
56
 
57
+ def _should_skip_vao_creation(self, reset_vao: bool) -> bool:
58
+ """Check if VAO creation should be skipped."""
59
+ if self.vao is None:
60
+ return False
61
+
62
+ if reset_vao:
63
+ logger.warning("VAO exist so returning")
64
+ return True
65
+
66
+ logger.warning("Creating new VAO")
67
+ return False
68
+
69
+ def _validate_triangular_mesh(self) -> None:
70
+ """Validate that the mesh is composed of triangles."""
71
+ if not self.is_triangular():
72
+ logger.error("Can only create VBO from all Triangle data at present")
73
+ raise RuntimeError("Can only create VBO from all Triangle data at present")
74
+
57
75
  def create_vao(self, reset_vao: bool = False) -> None:
58
76
  """
59
77
  Create a Vertex Array Object (VAO) for the mesh.
@@ -64,20 +82,14 @@ class BaseMesh:
64
82
  Raises:
65
83
  RuntimeError: If the mesh is not composed entirely of triangles.
66
84
  """
67
- if reset_vao:
68
- if self.vao is not None:
69
- logger.warning("VAO exist so returning")
70
- return
71
- else:
72
- if self.vao is not None:
73
- logger.warning("Creating new VAO")
74
-
75
- data_pack_type = 0
76
- if self.is_triangular():
77
- data_pack_type = gl.GL_TRIANGLES
78
- if data_pack_type == 0:
79
- logger.error("Can only create VBO from all Triangle data at present")
80
- raise RuntimeError("Can only create VBO from all Triangle data at present")
85
+
86
+ # Handle existing VAO based on reset_vao flag
87
+ if self._should_skip_vao_creation(reset_vao):
88
+ return
89
+ # Validate mesh is triangular
90
+ self._validate_triangular_mesh()
91
+
92
+ data_pack_type = gl.GL_TRIANGLES
81
93
 
82
94
  @dataclass
83
95
  class VertData:
@@ -123,9 +135,7 @@ class BaseMesh:
123
135
  vbo_mesh.append(d)
124
136
 
125
137
  mesh_data = np.concatenate([v.as_array() for v in vbo_mesh]).astype(np.float32)
126
- self.vao = vao_factory.VAOFactory.create_vao(
127
- vao_factory.VAOType.SIMPLE, data_pack_type
128
- )
138
+ self.vao = vao_factory.VAOFactory.create_vao(vao_factory.VAOType.SIMPLE, data_pack_type)
129
139
  with self.vao as vao:
130
140
  mesh_size = len(mesh_data) // 8
131
141
  vao.set_data(VertexData(mesh_data, mesh_size))
@@ -137,9 +147,7 @@ class BaseMesh:
137
147
  vao.set_vertex_attribute_pointer(2, 2, gl.GL_FLOAT, 8 * 4, 6 * 4)
138
148
  vao.set_num_indices(mesh_size)
139
149
  self.calc_dimensions()
140
- self.bbox = BBox.from_extents(
141
- self.min_x, self.max_x, self.min_y, self.max_y, self.min_z, self.max_z
142
- )
150
+ self.bbox = BBox.from_extents(self.min_x, self.max_x, self.min_y, self.max_y, self.min_z, self.max_z)
143
151
 
144
152
  def calc_dimensions(self) -> None:
145
153
  """
ncca/ngl/image.py CHANGED
@@ -34,9 +34,7 @@ class Image:
34
34
  if mode == ImageModes.GRAY:
35
35
  self._data = np.zeros((height, width), dtype=np.uint8)
36
36
  else:
37
- self._data = np.zeros(
38
- (height, width, len(mode.value)), dtype=np.uint8
39
- )
37
+ self._data = np.zeros((height, width, len(mode.value)), dtype=np.uint8)
40
38
  else:
41
39
  self._data = None
42
40
 
ncca/ngl/mat2.py CHANGED
@@ -1,4 +1,8 @@
1
- import copy
1
+ """
2
+ Mat2 class with NumPy implementation
3
+ """
4
+
5
+ import numpy as np
2
6
 
3
7
  from .vec2 import Vec2
4
8
 
@@ -7,36 +11,40 @@ class Mat2Error(Exception):
7
11
  pass
8
12
 
9
13
 
10
- _identity = [[1.0, 0.0], [0.0, 1.0]]
14
+ class Mat2NotSquare(Exception):
15
+ """If we try to construct from a non square (2x2) value or 4 elements this exception will be thrown"""
16
+
17
+ pass
11
18
 
12
19
 
13
20
  class Mat2:
14
21
  __slots__ = ["m"]
15
22
 
16
- def __init__(self, m=None):
23
+ def __init__(self):
17
24
  """
18
25
  Initialize a 2x2 matrix.
19
26
 
20
- Args:
21
- m (list): A 2D list representing the matrix.
22
- If not provided, an identity matrix is created.
23
- """
24
- if m is None:
25
- self.m = copy.deepcopy(_identity)
26
- elif isinstance(m, list) and len(m) == 4 and not isinstance(m[0], list):
27
- self.m = [m[0:2], m[2:4]]
28
- else:
29
- self.m = m
30
27
 
31
- @classmethod
32
- def from_list(cls, m: list[float]):
33
28
  """
34
- Initialize a 2x2 matrix from a flat list.
29
+ self.m = np.eye(2, dtype=np.float64)
35
30
 
36
- Args:
37
- m (list[float]): A flat list representing the matrix.
38
- """
39
- return cls([m[0:2], m[2:4]])
31
+ @classmethod
32
+ def from_list(cls, lst):
33
+ "class method to create mat2 from list"
34
+ v = Mat2()
35
+ if isinstance(lst, list) and len(lst) == 2 and all(isinstance(row, list) for row in lst):
36
+ # 2D list
37
+ if all(len(row) == 2 for row in lst):
38
+ v.m = np.array(lst, dtype=np.float64)
39
+ return v
40
+ elif any(len(row) != 2 for row in lst):
41
+ raise Mat2NotSquare
42
+ elif isinstance(lst, list) and len(lst) == 4:
43
+ # flat list - reshape to 2x2 in row-major order
44
+ v.m = np.array(lst, dtype=np.float64).reshape(2, 2, order="C")
45
+ return v
46
+ else:
47
+ raise Mat2NotSquare
40
48
 
41
49
  def get_matrix(self) -> list[float]:
42
50
  """
@@ -45,7 +53,7 @@ class Mat2:
45
53
  Returns:
46
54
  list[float]: A flat list of floats.
47
55
  """
48
- return [item for sublist in zip(*self.m, strict=False) for item in sublist]
56
+ return self.m.flatten("C").tolist()
49
57
 
50
58
  def to_numpy(self):
51
59
  """
@@ -54,9 +62,7 @@ class Mat2:
54
62
  Returns:
55
63
  np.ndarray: The matrix as a NumPy array.
56
64
  """
57
- import numpy as np
58
-
59
- return np.array(self.get_matrix()).reshape([2, 2])
65
+ return self.m.astype(np.float32)
60
66
 
61
67
  @classmethod
62
68
  def identity(cls) -> "Mat2":
@@ -66,9 +72,28 @@ class Mat2:
66
72
  Returns:
67
73
  Mat2: A new identity Mat2 object.
68
74
  """
69
- ret = cls()
70
- ret.m = copy.deepcopy(_identity)
71
- return ret
75
+ return cls()
76
+
77
+ @classmethod
78
+ def zero(cls):
79
+ """class method to return a new zero matrix
80
+
81
+ Returns
82
+ -------
83
+ Mat2
84
+ new Mat2 matrix as all zeros
85
+
86
+ """
87
+ v = Mat2()
88
+ v.m = np.zeros((2, 2), dtype=np.float64)
89
+ return v
90
+
91
+ def _mat_mul(self, rhs):
92
+ "matrix mult for 3D OpenGL style graphics"
93
+ result = Mat2()
94
+ # Use numpy's @ operator which does standard matrix multiplication
95
+ result.m = rhs.m @ self.m
96
+ return result
72
97
 
73
98
  def __matmul__(self, rhs):
74
99
  """
@@ -77,8 +102,7 @@ class Mat2:
77
102
  Args:
78
103
  rhs (Mat2 | Vec2): The right-hand side operand.
79
104
  If Mat2, perform matrix multiplication.
80
- If Vec2, transform the vec
81
- r by the matrix.
105
+ If Vec2, transform the vector by the matrix.
82
106
 
83
107
  Returns:
84
108
  Mat2: Resulting matrix from matrix multiplication.
@@ -90,29 +114,12 @@ class Mat2:
90
114
  if isinstance(rhs, Mat2):
91
115
  return self._mat_mul(rhs)
92
116
  elif isinstance(rhs, Vec2):
93
- return Vec2(
94
- rhs.x * self.m[0][0] + rhs.y * self.m[0][1],
95
- rhs.x * self.m[1][0] + rhs.y * self.m[1][1],
96
- )
117
+ vec = np.array([rhs.x, rhs.y], dtype=np.float64)
118
+ res = self.m @ vec
119
+ return Vec2(res[0], res[1])
97
120
  else:
98
121
  raise ValueError(f"Can only multiply by Mat2 or Vec2, not {type(rhs)}")
99
122
 
100
- def _mat_mul(self, other):
101
- """
102
- Internal method to perform matrix multiplication.
103
-
104
- Args:
105
- other (Mat2): The right-hand side matrix.
106
-
107
- Returns:
108
- Mat2: Result of matrix multiplication.
109
- """
110
- ret = Mat2()
111
- for i in range(2):
112
- for j in range(2):
113
- ret.m[i][j] = sum(self.m[i][k] * other.m[k][j] for k in range(2))
114
- return ret
115
-
116
123
  def __str__(self) -> str:
117
124
  """
118
125
  String representation of the matrix.
@@ -120,12 +127,11 @@ class Mat2:
120
127
  Returns:
121
128
  str: The string representation.
122
129
  """
123
- return f"Mat2({self.m[0]}, {self.m[1]})"
130
+ return f"Mat2({self.m[0].tolist()}, {self.m[1].tolist()})"
124
131
 
125
132
  def to_list(self):
126
133
  "convert matrix to list in column-major order"
127
- # flatten to single array
128
- return [item for sublist in zip(*self.m, strict=False) for item in sublist]
134
+ return self.m.flatten("C").tolist()
129
135
 
130
136
  def copy(self) -> "Mat2":
131
137
  """Create a copy of the matrix.
@@ -134,5 +140,25 @@ class Mat2:
134
140
  A new Mat2 instance with the same values.
135
141
  """
136
142
  new_mat = Mat2()
137
- new_mat.m = copy.deepcopy(self.m)
143
+ new_mat.m = self.m.copy()
138
144
  return new_mat
145
+
146
+ def __eq__(self, rhs):
147
+ """Value-based equality for Mat2: compare underlying matrices numerically.
148
+ Returns NotImplemented for non-Mat2 types so Python can try reflected comparisons
149
+ or handle it appropriately.
150
+ """
151
+ if not isinstance(rhs, Mat2):
152
+ return NotImplemented
153
+ # self.m and other.m should be numpy arrays; compare with tolerance
154
+ return bool(np.allclose(self.m, rhs.m, rtol=1e-8, atol=1e-12))
155
+
156
+ def __ne__(self, rhs):
157
+ """Value-based equality for Mat2: compare underlying matrices numerically.
158
+ Returns NotImplemented for non-Mat2 types so Python can try reflected comparisons
159
+ or handle it appropriately.
160
+ """
161
+ if not isinstance(rhs, Mat2):
162
+ return NotImplemented
163
+ # self.m and other.m should be numpy arrays; compare with tolerance
164
+ return not bool(np.allclose(self.m, rhs.m, rtol=1e-8, atol=1e-12))