ncca-ngl 0.3.4__py3-none-any.whl → 0.5.0__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 +3 -4
  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 +7 -0
  16. ncca/ngl/vec2.py +58 -292
  17. ncca/ngl/vec2_array.py +79 -28
  18. ncca/ngl/vec3.py +59 -340
  19. ncca/ngl/vec3_array.py +76 -23
  20. ncca/ngl/vec4.py +90 -190
  21. ncca/ngl/vec4_array.py +78 -27
  22. ncca/ngl/vector_base.py +542 -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 +31 -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.4.dist-info → ncca_ngl-0.5.0.dist-info}/METADATA +3 -2
  53. ncca_ngl-0.5.0.dist-info/RECORD +105 -0
  54. ncca/ngl/widgets/transformation_widget.py +0 -299
  55. ncca_ngl-0.3.4.dist-info/RECORD +0 -82
  56. {ncca_ngl-0.3.4.dist-info → ncca_ngl-0.5.0.dist-info}/WHEEL +0 -0
ncca/ngl/vec4.py CHANGED
@@ -1,229 +1,129 @@
1
1
  """
2
- Simple Float only Vec3 class for 3D graphics, very similar to the pyngl ones
2
+ Simple Float only Vec4 class for 3D graphics, very similar to the pyngl ones
3
+ NumPy-based implementation with VectorBase inheritance for code reuse.
3
4
  """
4
5
 
5
- import ctypes
6
6
  import math
7
+ from typing import Union
7
8
 
8
9
  import numpy as np
9
10
 
10
- from .log import logger
11
+ from .util import clamp
12
+ from .vector_base import VectorBase, _create_properties
11
13
 
12
14
 
13
- class Vec4:
14
- __slots__ = ["_x", "_y", "_z", "_w"]
15
- "by using slots we fix our class attributes to x,y,z,w"
15
+ class Vec4(VectorBase["Vec4"]):
16
+ """
17
+ A simple 4D vector class for graphics, using numpy for efficient operations.
16
18
 
17
- def __init__(self, x=0.0, y=0.0, z=0.0, w=1.0):
18
- """simple ctor"""
19
- self._x = x # x component of vector : float
20
- self._y = y # y component of vector : float
21
- self._z = z # z component of vector : float
22
- self._w = w # w component of vector : float
19
+ Attributes:
20
+ x (float): The x-coordinate of the vector.
21
+ y (float): The y-coordinate of the vector.
22
+ z (float): The z-coordinate of the vector.
23
+ w (float): The w-coordinate of the vector.
24
+ """
23
25
 
24
- @classmethod
25
- def sizeof(cls):
26
- return 4 * ctypes.sizeof(ctypes.c_float)
26
+ DIMENSION = 4
27
+ COMPONENT_NAMES = ("x", "y", "z", "w")
28
+ DEFAULT_VALUES = (0.0, 0.0, 0.0, 1.0)
27
29
 
28
- def _validate_and_set(self, v, name):
30
+ __slots__ = ["_data"]
31
+
32
+ def cross(self, rhs: "Vec4") -> "Vec4":
29
33
  """
30
- check if v is a float or int
34
+ Cross product of two vectors a x b (4D version uses first 3 components).
35
+
31
36
  Args:
32
- v (number): The value to check.
33
- Raises:
34
- ValueError: If v is not a float or int.
35
- """
36
- if not isinstance(v, (int, float, np.float32)):
37
- raise ValueError("need float or int")
38
- else:
39
- setattr(self, name, v)
37
+ rhs (Vec4): The right-hand side vector to cross product with.
40
38
 
41
- def __iter__(self):
42
- """
43
- Make the Vec3 class iterable.
44
- Yields:
45
- float: The x, y, and z components of the vector.
39
+ Returns:
40
+ Vec4: A new vector that is the result of the cross product.
46
41
  """
47
- yield self.x
48
- yield self.y
49
- yield self.z
50
- yield self.w
42
+ result = Vec4()
43
+ # Cross product only makes sense for 3D vectors, use first 3 components
44
+ result._data[:3] = np.cross(self._data[:3], rhs._data[:3])
45
+ result._data[3] = 0.0
46
+ return result
51
47
 
52
- def __getitem__(self, index):
48
+ def reflect(self, n: "Vec4") -> "Vec4":
53
49
  """
54
- Get the component of the vector at the given index.
50
+ Reflect a vector about a normal.
51
+
55
52
  Args:
56
- index (int): The index of the component (0 for x, 1 for y, 2 for z).
53
+ n (Vec4): The normal to reflect about.
54
+
57
55
  Returns:
58
- float: The value of the component at the given index.
59
- Raises:
60
- IndexError: If the index is out of range.
56
+ Vec4: A new vector that is the result of reflecting this vector about the normal.
61
57
  """
62
- components = [self.x, self.y, self.z, self.w]
63
- try:
64
- return components[index]
65
- except IndexError:
66
- raise IndexError("Index out of range. Valid indices are 0, 1, 2, and 3.")
58
+ d = self.dot(n)
59
+ # I - 2.0 * dot(N, I) * N
60
+ result = Vec4()
61
+ result._data = self._data - 2.0 * d * n._data
62
+ return result
67
63
 
68
- def copy(self) -> "Vec4":
64
+ def outer(self, rhs: "Vec4"):
69
65
  """
70
- Create a copy of the vector.
66
+ Outer product of two vectors a x b.
67
+
68
+ Args:
69
+ rhs (Vec4): The right-hand side vector to outer product with.
70
+
71
71
  Returns:
72
- Vec4: A new Vec4 instance with the same values.
72
+ Mat4: A new 4x4 matrix that is the result of the outer product.
73
73
  """
74
- return Vec4(self.x, self.y, self.z, self.w)
75
-
76
- def __add__(self, rhs):
77
- "return a+b vector addition"
78
- r = Vec4()
79
- r.x = self.x + rhs.x
80
- r.y = self.y + rhs.y
81
- r.z = self.z + rhs.z
82
- r.w = self.w + rhs.w
83
- return r
84
-
85
- def __iadd__(self, rhs):
86
- "return a+=b vector addition"
87
- self.x += rhs.x
88
- self.y += rhs.y
89
- self.z += rhs.z
90
- self.w += rhs.w
91
-
92
- return self
93
-
94
- def __sub__(self, rhs):
95
- "return a+b vector addition"
96
- r = Vec4()
97
- r.x = self.x - rhs.x
98
- r.y = self.y - rhs.y
99
- r.z = self.z - rhs.z
100
- r.w = self.w - rhs.w
101
- return r
102
-
103
- def __isub__(self, rhs):
104
- "return a+=b vector addition"
105
- self.x -= rhs.x
106
- self.y -= rhs.y
107
- self.z -= rhs.z
108
- self.w -= rhs.w
109
- return self
110
-
111
- def set(self, x, y, z, w=1.0):
112
- "set from x,y,z,w will convert to float an raise value error if problem"
113
- try:
114
- self.x = float(x)
115
- self.y = float(y)
116
- self.z = float(z)
117
- self.w = float(w)
118
- except ValueError:
119
- logger.warning("need float values")
120
- raise
74
+ from .mat4 import Mat4
121
75
 
122
- def dot(self, rhs):
123
- return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) + (self.w * rhs.w)
76
+ result = Mat4()
77
+ result.m = np.outer(self._data, rhs._data).astype(np.float64)
78
+ return result
124
79
 
125
- def length(self):
126
- "length of vector"
127
- return math.sqrt(self.x**2 + self.y**2 + self.z**2 + self.w**2)
128
-
129
- def length_squared(self):
130
- "square length of vector"
131
- return self.x**2 + self.y**2 + self.z**2 + self.w**2
80
+ def __matmul__(self, rhs):
81
+ """
82
+ Vec4 @ Mat4 matrix multiplication.
132
83
 
133
- def normalize(self):
134
- "normalize this vector"
135
- length = self.length()
136
- try:
137
- self.x /= length
138
- self.y /= length
139
- self.z /= length
140
- self.w /= length
141
- except ZeroDivisionError:
142
- raise ZeroDivisionError("cannot normalize the zero vector")
143
- return self
144
-
145
- def __eq__(self, rhs):
146
- "test a==b using math.isclose"
147
- if not isinstance(rhs, Vec4):
148
- return NotImplemented
149
- return (
150
- math.isclose(self.x, rhs.x)
151
- and math.isclose(self.y, rhs.y)
152
- and math.isclose(self.z, rhs.z)
153
- and math.isclose(self.w, rhs.w)
154
- )
155
-
156
- def __neq__(self, rhs):
157
- "test a!=b using math.isclose"
158
- if not isinstance(rhs, Vec4):
159
- return NotImplemented
160
- return not (
161
- math.isclose(self.x, rhs.x)
162
- and math.isclose(self.y, rhs.y)
163
- and math.isclose(self.z, rhs.z)
164
- and math.isclose(self.w, rhs.w)
165
- )
166
-
167
- def __neg__(self):
168
- self.x = -self.x
169
- self.y = -self.y
170
- self.z = -self.z
171
- self.w = -self.w
172
- return self
173
-
174
- def __mul__(self, rhs):
175
- if isinstance(rhs, (float, int)):
176
- "Vec4 * scalar multiplication"
177
- return Vec4(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs)
178
- else:
179
- raise ValueError
180
-
181
- def __rmul__(self, rhs):
182
- return self * rhs
183
-
184
- def __truediv__(self, rhs):
185
- if isinstance(rhs, (float, int)):
186
- return Vec4(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs)
187
- elif isinstance(rhs, Vec4):
188
- return Vec4(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z, self.w / rhs.w)
189
- else:
190
- raise ValueError(f"can only do piecewise division with a scalar {rhs=}")
84
+ Args:
85
+ rhs (Mat4): The matrix to multiply by.
191
86
 
192
- def __matmul__(self, rhs):
193
- "Vec4 @ Mat4 matrix multiplication"
194
- return Vec4(
195
- self.x * rhs.m[0][0] + self.y * rhs.m[1][0] + self.z * rhs.m[2][0] + self.w * rhs.m[3][0],
196
- self.x * rhs.m[0][1] + self.y * rhs.m[1][1] + self.z * rhs.m[2][1] + self.w * rhs.m[3][1],
197
- self.x * rhs.m[0][2] + self.y * rhs.m[1][2] + self.z * rhs.m[2][2] + self.w * rhs.m[3][2],
198
- self.x * rhs.m[0][3] + self.y * rhs.m[1][3] + self.z * rhs.m[2][3] + self.w * rhs.m[3][3],
199
- )
87
+ Returns:
88
+ Vec4: A new vector that is the result of multiplying this vector by the matrix.
89
+ """
90
+ return Vec4(*self._data @ rhs.m)
200
91
 
201
- def __repr__(self):
202
- "repr for debugging purposes"
203
- return f"Vec4 [{self.x},{self.y},{self.z},{self.w}]"
92
+ def set(self, *args: float) -> None:
93
+ """
94
+ Set the x,y,z,w values of the vector.
204
95
 
205
- def __str__(self):
206
- "print out the vector as a string"
207
- return f"[{self.x},{self.y},{self.z},{self.w}]"
96
+ Args:
97
+ *args: Component values (x, y, z, w). w defaults to 1.0 if not provided.
208
98
 
209
- def to_list(self):
210
- return [self.x, self.y, self.z, self.w]
99
+ Raises:
100
+ ValueError: If wrong number of arguments or they are not floats.
101
+ """
102
+ if len(args) == 3:
103
+ # Allow (x, y, z) with default w=1.0 for backward compatibility
104
+ args = args + (1.0,)
105
+ elif len(args) != 4:
106
+ raise ValueError(f"Vec4.set requires 3 or 4 arguments, got {len(args)}")
211
107
 
212
- def to_numpy(self):
213
- return np.array([self.x, self.y, self.z, self.w])
108
+ try:
109
+ for i in range(4):
110
+ self._data[i] = float(args[i])
111
+ except ValueError:
112
+ raise ValueError(f"Vec4.set {args=} all need to be float")
214
113
 
114
+ def __repr__(self) -> str:
115
+ """Object representation for debugging."""
116
+ return f"Vec4 [{self._data[0]},{self._data[1]},{self._data[2]},{self._data[3]}]"
215
117
 
216
- # Helper function to create properties
217
- def _create_property(attr_name):
218
- def getter(self):
219
- return getattr(self, f"_{attr_name}")
118
+ def __str__(self) -> str:
119
+ """String representation of the vector."""
220
120
 
221
- def setter(self, value):
222
- self._validate_and_set(value, f"_{attr_name}")
121
+ # Format numbers without decimal point if they're whole numbers
122
+ def fmt(val):
123
+ return str(int(val)) if val == int(val) else str(val)
223
124
 
224
- return property(getter, setter)
125
+ return f"[{fmt(self._data[0])},{fmt(self._data[1])},{fmt(self._data[2])},{fmt(self._data[3])}]"
225
126
 
226
127
 
227
- # Dynamically add properties for x, y, z,w
228
- for attr in ["x", "y", "z", "w"]:
229
- setattr(Vec4, attr, _create_property(attr))
128
+ # Add properties for x, y, z, w components
129
+ _create_properties(Vec4)
ncca/ngl/vec4_array.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """
2
2
  A container for ngl.Vec4 objects that mimics some of the behavior of a std::vector
3
+ Optimized for graphics APIs with contiguous numpy storage
3
4
  """
4
5
 
5
6
  import numpy as np
@@ -9,52 +10,67 @@ from .vec4 import Vec4
9
10
 
10
11
  class Vec4Array:
11
12
  """
12
- A class to hold a list of Vec4 objects and perform operations on them.
13
+ A class to hold Vec4 data in contiguous memory for efficient GPU transfer.
14
+ Internally uses a numpy array of shape (N, 4) for optimal performance.
13
15
  """
14
16
 
15
17
  def __init__(self, values=None):
16
18
  """
17
- Initializes the Vec3Array.
19
+ Initializes the Vec4Array.
18
20
 
19
21
  Args:
20
22
  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
+ If an integer, the array is initialized with that many default Vec4s.
24
+ If an iterable, it's initialized with the Vec4s from the iterable.
23
25
  Defaults to None (an empty array).
24
26
  """
25
- self._data = []
26
- if values is not None:
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)
27
+ if values is None:
28
+ # Empty array - start with shape (0, 4)
29
+ self._data = np.zeros((0, 4), dtype=np.float64)
30
+ elif isinstance(values, int):
31
+ # Initialize N default Vec4s (0, 0, 0, 1)
32
+ self._data = np.zeros((values, 4), dtype=np.float64)
33
+ self._data[:, 3] = 1.0 # Set w component to 1.0
34
+ else:
35
+ # Initialize from iterable of Vec4 objects
36
+ vec_list = []
37
+ for v in values:
38
+ if not isinstance(v, Vec4):
39
+ raise TypeError("All elements must be of type Vec4")
40
+ vec_list.append([v.x, v.y, v.z, v.w])
41
+ self._data = np.array(vec_list, dtype=np.float64)
34
42
 
35
43
  def __getitem__(self, index):
36
44
  """
37
45
  Get the Vec4 at the specified index.
38
46
 
39
47
  Args:
40
- index (int): The index of the element.
48
+ index (int | slice): The index or slice of the element(s).
41
49
 
42
50
  Returns:
43
51
  Vec4: The Vec4 object at the given index.
44
52
  """
45
- return self._data[index]
53
+ if isinstance(index, slice):
54
+ # Return a new Vec4Array with sliced data
55
+ result = Vec4Array()
56
+ result._data = self._data[index].copy()
57
+ return result
58
+ else:
59
+ # Return a single Vec4
60
+ row = self._data[index]
61
+ return Vec4(row[0], row[1], row[2], row[3])
46
62
 
47
63
  def __setitem__(self, index, value):
48
64
  """
49
- Set the Vec3 at the specified index.
65
+ Set the Vec4 at the specified index.
50
66
 
51
67
  Args:
52
68
  index (int): The index of the element to set.
53
- value (Vec4): The new Vec3 object.
69
+ value (Vec4): The new Vec4 object.
54
70
  """
55
71
  if not isinstance(value, Vec4):
56
72
  raise TypeError("Only Vec4 objects can be assigned")
57
- self._data[index] = value
73
+ self._data[index] = [value.x, value.y, value.z, value.w]
58
74
 
59
75
  def __len__(self):
60
76
  """
@@ -64,9 +80,25 @@ class Vec4Array:
64
80
 
65
81
  def __iter__(self):
66
82
  """
67
- Return an iterator for the array.
83
+ Return an iterator that yields Vec4 objects.
68
84
  """
69
- return iter(self._data)
85
+ for i in range(len(self._data)):
86
+ row = self._data[i]
87
+ yield Vec4(row[0], row[1], row[2], row[3])
88
+
89
+ def __eq__(self, other):
90
+ """
91
+ Compare two Vec4Array instances for equality.
92
+
93
+ Args:
94
+ other: Another Vec4Array instance to compare with.
95
+
96
+ Returns:
97
+ bool: True if the arrays contain the same data, False otherwise.
98
+ """
99
+ if not isinstance(other, Vec4Array):
100
+ return NotImplemented
101
+ return np.array_equal(self._data, other._data)
70
102
 
71
103
  def append(self, value):
72
104
  """
@@ -77,7 +109,8 @@ class Vec4Array:
77
109
  """
78
110
  if not isinstance(value, Vec4):
79
111
  raise TypeError("Only Vec4 objects can be appended")
80
- self._data.append(value)
112
+ new_row = np.array([[value.x, value.y, value.z, value.w]], dtype=np.float64)
113
+ self._data = np.vstack([self._data, new_row])
81
114
 
82
115
  def extend(self, values):
83
116
  """
@@ -88,7 +121,12 @@ class Vec4Array:
88
121
  """
89
122
  if not all(isinstance(v, Vec4) for v in values):
90
123
  raise TypeError("All elements must be of type Vec4")
91
- self._data.extend(values)
124
+
125
+ new_rows = np.array([[v.x, v.y, v.z, v.w] for v in values], dtype=np.float64)
126
+ if len(self._data) == 0:
127
+ self._data = new_rows
128
+ else:
129
+ self._data = np.vstack([self._data, new_rows])
92
130
 
93
131
  def to_list(self):
94
132
  """
@@ -97,22 +135,35 @@ class Vec4Array:
97
135
  Returns:
98
136
  list: A list of x, y, z, w components concatenated.
99
137
  """
100
- return [comp for vec in self._data for comp in vec]
138
+ return self._data.flatten().tolist()
101
139
 
102
140
  def to_numpy(self):
103
141
  """
104
142
  Convert the array of Vec4 objects to a numpy array.
143
+ This is the primary method for GPU data transfer.
144
+
145
+ Returns:
146
+ numpy.ndarray: A float32 numpy array of shape (N*4,) for GPU transfer.
147
+ """
148
+ return self._data.astype(np.float32).flatten()
149
+
150
+ def get_array(self):
151
+ """
152
+ Get the underlying numpy array in shape (N, 4).
153
+ Useful for vectorized operations.
105
154
 
106
155
  Returns:
107
- numpy.ndarray: A numpy array of the vector data.
156
+ numpy.ndarray: The internal float64 array of shape (N, 4).
108
157
  """
109
- return np.array(self.to_list(), dtype=np.float32)
158
+ return self._data
110
159
 
111
160
  def __repr__(self):
112
- return f"Vec4Array({self._data!r})"
161
+ vec_list = [Vec4(row[0], row[1], row[2], row[3]) for row in self._data]
162
+ return f"Vec4Array({vec_list!r})"
113
163
 
114
164
  def __str__(self):
115
- return str(self._data)
165
+ vec_list = [Vec4(row[0], row[1], row[2], row[3]) for row in self._data]
166
+ return str(vec_list)
116
167
 
117
168
  def sizeof(self):
118
169
  """