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.
- ncca/ngl/PrimData/pack_arrays.py +2 -3
- ncca/ngl/__init__.py +5 -5
- ncca/ngl/base_mesh.py +28 -20
- ncca/ngl/image.py +1 -3
- ncca/ngl/mat2.py +79 -53
- ncca/ngl/mat3.py +104 -185
- ncca/ngl/mat4.py +144 -309
- ncca/ngl/prim_data.py +42 -36
- ncca/ngl/primitives.py +2 -2
- ncca/ngl/pyside_event_handling_mixin.py +0 -108
- ncca/ngl/quaternion.py +69 -36
- ncca/ngl/shader.py +0 -116
- ncca/ngl/shader_program.py +94 -117
- ncca/ngl/texture.py +5 -2
- ncca/ngl/util.py +50 -0
- ncca/ngl/vec2.py +59 -302
- ncca/ngl/vec2_array.py +79 -28
- ncca/ngl/vec3.py +60 -350
- ncca/ngl/vec3_array.py +76 -23
- ncca/ngl/vec4.py +90 -200
- ncca/ngl/vec4_array.py +78 -27
- ncca/ngl/vector_base.py +548 -0
- ncca/ngl/webgpu/__init__.py +20 -0
- ncca/ngl/webgpu/__main__.py +640 -0
- ncca/ngl/webgpu/__main__.py.backup +640 -0
- ncca/ngl/webgpu/base_webgpu_pipeline.py +354 -0
- ncca/ngl/webgpu/custom_shader_pipeline.py +288 -0
- ncca/ngl/webgpu/instanced_geometry_pipeline.py +594 -0
- ncca/ngl/webgpu/line_pipeline.py +405 -0
- ncca/ngl/webgpu/pipeline_factory.py +190 -0
- ncca/ngl/webgpu/pipeline_shaders.py +497 -0
- ncca/ngl/webgpu/point_list_pipeline.py +349 -0
- ncca/ngl/webgpu/point_pipeline.py +336 -0
- ncca/ngl/webgpu/triangle_pipeline.py +419 -0
- ncca/ngl/webgpu/webgpu_constants.py +33 -0
- ncca/ngl/webgpu/webgpu_widget.py +322 -0
- ncca/ngl/webgpu/wip/REFACTORING_SUMMARY.md +82 -0
- ncca/ngl/webgpu/wip/UNIFIED_SYSTEM.md +314 -0
- ncca/ngl/webgpu/wip/buffer_manager.py +396 -0
- ncca/ngl/webgpu/wip/pipeline_config.py +463 -0
- ncca/ngl/webgpu/wip/shader_constants.py +328 -0
- ncca/ngl/webgpu/wip/shader_templates.py +563 -0
- ncca/ngl/webgpu/wip/unified_examples.py +390 -0
- ncca/ngl/webgpu/wip/unified_factory.py +449 -0
- ncca/ngl/webgpu/wip/unified_pipeline.py +469 -0
- ncca/ngl/widgets/__init__.py +18 -2
- ncca/ngl/widgets/__main__.py +2 -1
- ncca/ngl/widgets/lookatwidget.py +2 -1
- ncca/ngl/widgets/mat4widget.py +2 -2
- ncca/ngl/widgets/vec2widget.py +1 -1
- ncca/ngl/widgets/vec3widget.py +1 -0
- {ncca_ngl-0.3.5.dist-info → ncca_ngl-0.5.1.dist-info}/METADATA +3 -2
- ncca_ngl-0.5.1.dist-info/RECORD +105 -0
- ncca/ngl/widgets/transformation_widget.py +0 -299
- ncca_ngl-0.3.5.dist-info/RECORD +0 -82
- {ncca_ngl-0.3.5.dist-info → ncca_ngl-0.5.1.dist-info}/WHEEL +0 -0
ncca/ngl/vector_base.py
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for all vector types providing common functionality and eliminating code duplication.
|
|
3
|
+
|
|
4
|
+
This module implements a generic base class that provides all common vector operations
|
|
5
|
+
while allowing dimension-specific behavior through abstract methods.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ctypes
|
|
9
|
+
import math
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Any, ClassVar, Generic, Self, Tuple, TypeVar
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from .util import clamp, hash_combine
|
|
16
|
+
|
|
17
|
+
# Note this has been changed so I can use python 3.11
|
|
18
|
+
# as renderman want this. Once >3.11 is used we can get rid and just use
|
|
19
|
+
# class VectorBase[T](ABC):
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T", bound="VectorBase")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VectorBase(ABC, Generic[T]):
|
|
25
|
+
"""
|
|
26
|
+
Base class for all vector types providing common functionality.
|
|
27
|
+
|
|
28
|
+
This class implements all common vector operations using numpy for efficiency
|
|
29
|
+
while maintaining the same API as the original vector classes.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
DIMENSION: The dimension of the vector (2, 3, or 4)
|
|
33
|
+
COMPONENT_NAMES: Tuple of component names ('x', 'y', 'z', 'w')
|
|
34
|
+
DEFAULT_VALUES: Tuple of default values for each component
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Class attributes to be defined by subclasses
|
|
38
|
+
DIMENSION: ClassVar[int]
|
|
39
|
+
COMPONENT_NAMES: ClassVar[Tuple[str, ...]]
|
|
40
|
+
DEFAULT_VALUES: ClassVar[Tuple[float, ...]]
|
|
41
|
+
|
|
42
|
+
def __init__(self, *args: float, **kwargs: float) -> None:
|
|
43
|
+
"""Initialize vector with components."""
|
|
44
|
+
if kwargs:
|
|
45
|
+
self._init_from_kwargs(args, kwargs)
|
|
46
|
+
else:
|
|
47
|
+
self._init_from_args(args)
|
|
48
|
+
|
|
49
|
+
def _init_from_kwargs(self, args: tuple[float, ...], kwargs: dict[str, float]) -> None:
|
|
50
|
+
"""Initialize vector from keyword arguments."""
|
|
51
|
+
self._validate_component_count(len(args) + len(kwargs))
|
|
52
|
+
|
|
53
|
+
values = list(self.DEFAULT_VALUES)
|
|
54
|
+
self._set_positional_args(values, args)
|
|
55
|
+
self._set_keyword_args(values, kwargs)
|
|
56
|
+
|
|
57
|
+
self._data = np.array(values, dtype=np.float64)
|
|
58
|
+
|
|
59
|
+
def _init_from_args(self, args: tuple[float, ...]) -> None:
|
|
60
|
+
"""Initialize vector from positional arguments only."""
|
|
61
|
+
if not args:
|
|
62
|
+
self._data = np.array(self.DEFAULT_VALUES, dtype=np.float64)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
self._validate_component_count(len(args))
|
|
66
|
+
|
|
67
|
+
values = list(self.DEFAULT_VALUES)
|
|
68
|
+
self._set_positional_args(values, args)
|
|
69
|
+
|
|
70
|
+
self._data = np.array(values, dtype=np.float64)
|
|
71
|
+
|
|
72
|
+
def _validate_component_count(self, count: int) -> None:
|
|
73
|
+
"""Validate the number of components doesn't exceed dimension."""
|
|
74
|
+
if count > self.DIMENSION:
|
|
75
|
+
raise ValueError(f"{self.__class__.__name__} requires at most {self.DIMENSION} components")
|
|
76
|
+
|
|
77
|
+
def _set_positional_args(self, values: list[float], args: tuple[float, ...]) -> None:
|
|
78
|
+
"""Set positional argument values."""
|
|
79
|
+
for i, arg in enumerate(args):
|
|
80
|
+
values[i] = float(arg)
|
|
81
|
+
|
|
82
|
+
def _set_keyword_args(self, values: list[float], kwargs: dict[str, float]) -> None:
|
|
83
|
+
"""Set keyword argument values."""
|
|
84
|
+
for key, value in kwargs.items():
|
|
85
|
+
if key in self.COMPONENT_NAMES:
|
|
86
|
+
idx = self.COMPONENT_NAMES.index(key)
|
|
87
|
+
values[idx] = float(value)
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(f"Unknown component name: {key}")
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def sizeof(cls) -> int:
|
|
93
|
+
"""Return the size of the vector in bytes (for OpenGL compatibility)."""
|
|
94
|
+
return cls.DIMENSION * ctypes.sizeof(ctypes.c_float)
|
|
95
|
+
|
|
96
|
+
def __iter__(self):
|
|
97
|
+
"""
|
|
98
|
+
Make the vector class iterable.
|
|
99
|
+
|
|
100
|
+
Yields:
|
|
101
|
+
float: The components of the vector.
|
|
102
|
+
"""
|
|
103
|
+
for i in range(self.DIMENSION):
|
|
104
|
+
yield self._data[i]
|
|
105
|
+
|
|
106
|
+
def __getitem__(self, index: int) -> float:
|
|
107
|
+
"""
|
|
108
|
+
Get the component of the vector at the given index.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
index (int): The index of the component.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
float: The value of the component at the given index.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
IndexError: If the index is out of range.
|
|
118
|
+
"""
|
|
119
|
+
if index < 0 or index >= self.DIMENSION:
|
|
120
|
+
valid_indices = ", ".join(str(i) for i in range(self.DIMENSION))
|
|
121
|
+
raise IndexError(f"Index out of range. Valid indices are {valid_indices}.")
|
|
122
|
+
return self._data[index]
|
|
123
|
+
|
|
124
|
+
def __hash__(self) -> int:
|
|
125
|
+
"""
|
|
126
|
+
Compute hash for use in sets and dictionaries.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
int: Hash value for the vector.
|
|
130
|
+
"""
|
|
131
|
+
# Use 32-bit float element hashes, then combine
|
|
132
|
+
seed = 0
|
|
133
|
+
for v in self._data:
|
|
134
|
+
# ensure 32-bit float semantics
|
|
135
|
+
h = hash(float(np.float32(v)))
|
|
136
|
+
seed = hash_combine(seed, h)
|
|
137
|
+
return seed
|
|
138
|
+
|
|
139
|
+
def copy(self) -> Self:
|
|
140
|
+
"""
|
|
141
|
+
Create a copy of the vector.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
VectorBase: A new vector instance with the same values.
|
|
145
|
+
"""
|
|
146
|
+
return self.__class__(*self._data)
|
|
147
|
+
|
|
148
|
+
def __add__(self, rhs: Self) -> Self:
|
|
149
|
+
"""
|
|
150
|
+
Vector addition a+b.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
rhs (VectorBase): The right-hand side vector to add.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
VectorBase: A new vector that is the result of adding this vector and the rhs vector.
|
|
157
|
+
"""
|
|
158
|
+
if not isinstance(rhs, self.__class__):
|
|
159
|
+
raise ValueError(f"Can only add {self.__class__.__name__} to {self.__class__.__name__}")
|
|
160
|
+
result = self.__class__()
|
|
161
|
+
result._data = self._data + rhs._data
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
def __iadd__(self, rhs: Self) -> Self:
|
|
165
|
+
"""
|
|
166
|
+
Vector addition a+=b.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
rhs (VectorBase): The right-hand side vector to add.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
VectorBase: Returns this vector after adding the rhs vector.
|
|
173
|
+
"""
|
|
174
|
+
if not isinstance(rhs, self.__class__):
|
|
175
|
+
raise ValueError(f"Can only add {self.__class__.__name__} to {self.__class__.__name__}")
|
|
176
|
+
self._data += rhs._data
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def __sub__(self, rhs: Self) -> Self:
|
|
180
|
+
"""
|
|
181
|
+
Vector subtraction a-b.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
rhs (VectorBase): The right-hand side vector to subtract.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
VectorBase: A new vector that is the result of subtracting this vector and the rhs vector.
|
|
188
|
+
"""
|
|
189
|
+
if not isinstance(rhs, self.__class__):
|
|
190
|
+
raise ValueError(f"Can only subtract {self.__class__.__name__} from {self.__class__.__name__}")
|
|
191
|
+
result = self.__class__()
|
|
192
|
+
result._data = self._data - rhs._data
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
def __isub__(self, rhs: Self) -> Self:
|
|
196
|
+
"""
|
|
197
|
+
Vector subtraction a-=b.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
rhs (VectorBase): The right-hand side vector to subtract.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
VectorBase: Returns this vector after subtracting the rhs vector.
|
|
204
|
+
"""
|
|
205
|
+
if not isinstance(rhs, self.__class__):
|
|
206
|
+
raise ValueError(f"Can only subtract {self.__class__.__name__} from {self.__class__.__name__}")
|
|
207
|
+
self._data -= rhs._data
|
|
208
|
+
return self
|
|
209
|
+
|
|
210
|
+
def __eq__(self, rhs: Any) -> bool:
|
|
211
|
+
"""
|
|
212
|
+
Vector comparison a==b using numpy.allclose.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
rhs (VectorBase): The right-hand side vector to compare.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
bool: True if the vectors are close, False otherwise.
|
|
219
|
+
NotImplemented: If the right-hand side is not a VectorBase.
|
|
220
|
+
"""
|
|
221
|
+
if not isinstance(rhs, self.__class__):
|
|
222
|
+
return NotImplemented
|
|
223
|
+
return np.allclose(self._data, rhs._data)
|
|
224
|
+
|
|
225
|
+
def __ne__(self, rhs: Any) -> bool:
|
|
226
|
+
"""
|
|
227
|
+
Vector comparison a!=b using numpy.allclose.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
rhs (VectorBase): The right-hand side vector to compare.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
bool: True if the vectors are not close, False otherwise.
|
|
234
|
+
NotImplemented: If the right-hand side is not a VectorBase.
|
|
235
|
+
"""
|
|
236
|
+
if not isinstance(rhs, self.__class__):
|
|
237
|
+
return NotImplemented
|
|
238
|
+
return not np.allclose(self._data, rhs._data)
|
|
239
|
+
|
|
240
|
+
def __neg__(self) -> Self:
|
|
241
|
+
"""
|
|
242
|
+
Negate a vector -a.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
VectorBase: The negated vector.
|
|
246
|
+
"""
|
|
247
|
+
result = self.__class__()
|
|
248
|
+
result._data = -self._data
|
|
249
|
+
return result
|
|
250
|
+
|
|
251
|
+
def __mul__(self, rhs: float | int) -> Self:
|
|
252
|
+
"""
|
|
253
|
+
Piecewise scalar multiplication.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
rhs (float): The scalar to multiply by.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
VectorBase: A new vector that is the result of multiplying this vector by the scalar.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
ValueError: If the right-hand side is not a float or int.
|
|
263
|
+
"""
|
|
264
|
+
if isinstance(rhs, (float, int)):
|
|
265
|
+
result = self.__class__()
|
|
266
|
+
result._data = self._data * rhs
|
|
267
|
+
return result
|
|
268
|
+
else:
|
|
269
|
+
raise ValueError(f"Can only do piecewise multiplication with a scalar, got {type(rhs)}")
|
|
270
|
+
|
|
271
|
+
def __rmul__(self, rhs: float | int) -> Self:
|
|
272
|
+
"""
|
|
273
|
+
Piecewise scalar multiplication (right operand).
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
rhs (float): The scalar to multiply by.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
VectorBase: A new vector that is the result of multiplying this vector by the scalar.
|
|
280
|
+
"""
|
|
281
|
+
return self * rhs
|
|
282
|
+
|
|
283
|
+
def __truediv__(self, rhs: float | int | Self) -> Self:
|
|
284
|
+
"""
|
|
285
|
+
Piecewise scalar or vector division.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
rhs (Union[float, int, VectorBase]): The scalar or vector to divide by.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
VectorBase: A new vector that is the result of division.
|
|
292
|
+
|
|
293
|
+
Raises:
|
|
294
|
+
ZeroDivisionError: If division by zero is attempted.
|
|
295
|
+
ValueError: If the right-hand side is not a valid type.
|
|
296
|
+
"""
|
|
297
|
+
if isinstance(rhs, (float, int)):
|
|
298
|
+
if rhs == 0.0:
|
|
299
|
+
raise ZeroDivisionError("division by zero")
|
|
300
|
+
result = self.__class__()
|
|
301
|
+
result._data = self._data / rhs
|
|
302
|
+
return result
|
|
303
|
+
elif isinstance(rhs, self.__class__):
|
|
304
|
+
if np.any(np.isclose(rhs._data, 0.0)):
|
|
305
|
+
raise ZeroDivisionError("division by zero")
|
|
306
|
+
result = self.__class__()
|
|
307
|
+
result._data = self._data / rhs._data
|
|
308
|
+
return result
|
|
309
|
+
else:
|
|
310
|
+
raise ValueError(
|
|
311
|
+
f"Can only do piecewise division with a scalar or {self.__class__.__name__}, got {type(rhs)}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def dot(self, rhs: Self) -> float:
|
|
315
|
+
"""
|
|
316
|
+
Dot product of two vectors a.b.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
rhs (VectorBase): The right-hand side vector to dot product with.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
float: The dot product of the two vectors.
|
|
323
|
+
"""
|
|
324
|
+
if not isinstance(rhs, self.__class__):
|
|
325
|
+
raise ValueError(f"Can only compute dot product with {self.__class__.__name__}")
|
|
326
|
+
return np.dot(self._data, rhs._data)
|
|
327
|
+
|
|
328
|
+
def length(self) -> float:
|
|
329
|
+
"""
|
|
330
|
+
Length of vector.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
float: The length of the vector.
|
|
334
|
+
"""
|
|
335
|
+
return np.linalg.norm(self._data)
|
|
336
|
+
|
|
337
|
+
def length_squared(self) -> float:
|
|
338
|
+
"""
|
|
339
|
+
Length of vector squared (sometimes used to avoid the sqrt for performance).
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
float: The length of the vector squared.
|
|
343
|
+
"""
|
|
344
|
+
return np.dot(self._data, self._data)
|
|
345
|
+
|
|
346
|
+
def inner(self, rhs: Self) -> float:
|
|
347
|
+
"""
|
|
348
|
+
Inner product of two vectors a.b (alias for dot product).
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
rhs (VectorBase): The right-hand side vector to inner product with.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
float: The inner product of the two vectors.
|
|
355
|
+
"""
|
|
356
|
+
return self.dot(rhs)
|
|
357
|
+
|
|
358
|
+
def normalize(self) -> Self:
|
|
359
|
+
"""
|
|
360
|
+
Normalize the vector to unit length.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
VectorBase: This vector after normalization.
|
|
364
|
+
|
|
365
|
+
Raises:
|
|
366
|
+
ZeroDivisionError: If the length of the vector is zero.
|
|
367
|
+
"""
|
|
368
|
+
vector_length = self.length()
|
|
369
|
+
if math.isclose(vector_length, 0.0):
|
|
370
|
+
raise ZeroDivisionError(
|
|
371
|
+
f"{self.__class__.__name__}.normalize: length is zero, most likely calling normalize on a zero vector"
|
|
372
|
+
)
|
|
373
|
+
self._data /= vector_length
|
|
374
|
+
return self
|
|
375
|
+
|
|
376
|
+
def null(self) -> None:
|
|
377
|
+
"""Set the vector to zero."""
|
|
378
|
+
self._data[:] = 0.0
|
|
379
|
+
|
|
380
|
+
def clamp(self, low: float, high: float) -> None:
|
|
381
|
+
"""
|
|
382
|
+
Clamp the vector to a range.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
low (float): The low end of the range.
|
|
386
|
+
high (float): The high end of the range.
|
|
387
|
+
"""
|
|
388
|
+
for i in range(self.DIMENSION):
|
|
389
|
+
self._data[i] = clamp(self._data[i], low, high)
|
|
390
|
+
|
|
391
|
+
def to_list(self) -> list:
|
|
392
|
+
"""
|
|
393
|
+
Convert vector to list.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
list: List of vector components.
|
|
397
|
+
"""
|
|
398
|
+
return self._data.tolist()
|
|
399
|
+
|
|
400
|
+
def to_numpy(self) -> np.ndarray:
|
|
401
|
+
"""
|
|
402
|
+
Convert vector to numpy array.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
np.ndarray: Copy of the vector as numpy array.
|
|
406
|
+
"""
|
|
407
|
+
return self._data.copy()
|
|
408
|
+
|
|
409
|
+
def to_tuple(self) -> tuple:
|
|
410
|
+
"""
|
|
411
|
+
Convert vector to tuple.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
tuple: Tuple of vector components.
|
|
415
|
+
"""
|
|
416
|
+
return tuple(self._data)
|
|
417
|
+
|
|
418
|
+
# Abstract methods that must be implemented by subclasses
|
|
419
|
+
@abstractmethod
|
|
420
|
+
def cross(self, rhs: Self) -> Self | float:
|
|
421
|
+
"""
|
|
422
|
+
Cross product of two vectors a x b.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
rhs (VectorBase): The right-hand side vector to cross product with.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Union[VectorBase, float]: The cross product (scalar for 2D, vector for 3D/4D).
|
|
429
|
+
"""
|
|
430
|
+
pass # pragma: no cover
|
|
431
|
+
|
|
432
|
+
@abstractmethod
|
|
433
|
+
def reflect(self, n: Self) -> Self:
|
|
434
|
+
"""
|
|
435
|
+
Reflect a vector about a normal.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
n (VectorBase): The normal to reflect about.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
VectorBase: A new vector that is the result of reflecting this vector about the normal.
|
|
442
|
+
"""
|
|
443
|
+
pass # pragma: no cover
|
|
444
|
+
|
|
445
|
+
@abstractmethod
|
|
446
|
+
def outer(self, rhs: Self) -> Any:
|
|
447
|
+
"""
|
|
448
|
+
Outer product of two vectors a x b.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
rhs (VectorBase): The right-hand side vector to outer product with.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Any: A matrix that is the result of the outer product.
|
|
455
|
+
"""
|
|
456
|
+
pass # pragma: no cover
|
|
457
|
+
|
|
458
|
+
@abstractmethod
|
|
459
|
+
def __matmul__(self, rhs: Any) -> Self:
|
|
460
|
+
"""
|
|
461
|
+
Matrix multiplication Vector @ Matrix.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
rhs (Any): The matrix to multiply by.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
VectorBase: A new vector that is the result of multiplying this vector by the matrix.
|
|
468
|
+
"""
|
|
469
|
+
pass # pragma: no cover
|
|
470
|
+
|
|
471
|
+
@abstractmethod
|
|
472
|
+
def set(self, *args: float) -> None:
|
|
473
|
+
"""
|
|
474
|
+
Set the components of the vector.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
*args: Component values to set.
|
|
478
|
+
|
|
479
|
+
Raises:
|
|
480
|
+
ValueError: If wrong number of arguments is provided or they are not floats.
|
|
481
|
+
"""
|
|
482
|
+
pass # pragma: no cover
|
|
483
|
+
|
|
484
|
+
@abstractmethod
|
|
485
|
+
def __repr__(self) -> str:
|
|
486
|
+
"""Object representation for debugging."""
|
|
487
|
+
pass # pragma: no cover
|
|
488
|
+
|
|
489
|
+
@abstractmethod
|
|
490
|
+
def __str__(self) -> str:
|
|
491
|
+
"""String representation of the vector."""
|
|
492
|
+
pass # pragma: no cover
|
|
493
|
+
|
|
494
|
+
def __getattr__(self, name: str):
|
|
495
|
+
"""
|
|
496
|
+
Handle access to non-existent attributes.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
name (str): The name of the attribute.
|
|
500
|
+
|
|
501
|
+
Raises:
|
|
502
|
+
AttributeError: If the attribute does not exist.
|
|
503
|
+
"""
|
|
504
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
|
505
|
+
|
|
506
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
507
|
+
"""
|
|
508
|
+
Control attribute setting to prevent non-component attributes.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
name: The attribute name to set
|
|
512
|
+
value: The value to set
|
|
513
|
+
|
|
514
|
+
Raises:
|
|
515
|
+
AttributeError: If trying to set non-component attributes
|
|
516
|
+
"""
|
|
517
|
+
# Allow setting internal _data
|
|
518
|
+
if name == "_data":
|
|
519
|
+
super().__setattr__(name, value)
|
|
520
|
+
# Allow setting component properties (x, y, z, w) during initialization
|
|
521
|
+
elif name in self.COMPONENT_NAMES:
|
|
522
|
+
super().__setattr__(name, value)
|
|
523
|
+
else:
|
|
524
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _create_properties(cls: type) -> None:
|
|
528
|
+
"""
|
|
529
|
+
Dynamically add properties for vector components.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
cls: The vector class to add properties to.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
def make_property(index: int):
|
|
536
|
+
def getter(self):
|
|
537
|
+
return self._data[index]
|
|
538
|
+
|
|
539
|
+
def setter(self, value):
|
|
540
|
+
if not isinstance(value, (int, float, np.float32)):
|
|
541
|
+
raise ValueError("need float or int")
|
|
542
|
+
self._data[index] = float(value)
|
|
543
|
+
|
|
544
|
+
return property(getter, setter)
|
|
545
|
+
|
|
546
|
+
# Add properties for each component (x, y, z, w)
|
|
547
|
+
for i, attr in enumerate(cls.COMPONENT_NAMES):
|
|
548
|
+
setattr(cls, attr, make_property(i))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = version("ncca-ngl") # pragma: no cover
|
|
5
|
+
except PackageNotFoundError:
|
|
6
|
+
__version__ = "0.0.0" # pragma: no cover
|
|
7
|
+
|
|
8
|
+
__author__ = "Jon Macey jmacey@bournemouth.ac.uk"
|
|
9
|
+
__license__ = "MIT"
|
|
10
|
+
|
|
11
|
+
from .pipeline_factory import PipelineFactory, PipelineType
|
|
12
|
+
from .webgpu_constants import NGLToWebGPU
|
|
13
|
+
from .webgpu_widget import WebGPUWidget
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"WebGPUWidget",
|
|
17
|
+
"NGLToWebGPU",
|
|
18
|
+
"PipelineFactory",
|
|
19
|
+
"PipelineType",
|
|
20
|
+
]
|