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
@@ -0,0 +1,469 @@
1
+ """
2
+ Unified WebGPU pipeline with user-defined shader support.
3
+ Provides a single, configurable pipeline class that can handle all rendering scenarios.
4
+ """
5
+
6
+ from typing import Any, Dict, List, Optional, Tuple, Union
7
+
8
+ import numpy as np
9
+ import wgpu
10
+
11
+ from .base_webgpu_pipeline import BaseWebGPUPipeline
12
+ from .buffer_manager import UnifiedResourceManager
13
+ from .pipeline_config import (
14
+ BufferLayout,
15
+ PipelineConfig,
16
+ VertexLayoutBuilder,
17
+ )
18
+ from .shader_templates import generate_shader_code
19
+ from .webgpu_constants import NGLToWebGPU
20
+
21
+
22
+ class UnifiedWebGPUPipeline(BaseWebGPUPipeline):
23
+ """
24
+ Unified WebGPU pipeline that supports all primitive types, color modes, and custom shaders.
25
+
26
+ Features:
27
+ - Configurable primitive types (points, lines, triangles)
28
+ - Single and multi-color rendering modes
29
+ - User-defined shader support
30
+ - Billboarding for points
31
+ - Flexible vertex layouts
32
+ - MSAA support
33
+ - Depth testing
34
+ - Resource management
35
+ """
36
+
37
+ def __init__(self, device: wgpu.GPUDevice, config: PipelineConfig):
38
+ """
39
+ Initialize unified pipeline with configuration.
40
+
41
+ Args:
42
+ device: WebGPU device
43
+ config: Pipeline configuration
44
+ """
45
+ self.config = config
46
+
47
+ # Calculate stride if not provided
48
+ if config.stride == 0:
49
+ stride = NGLToWebGPU.stride_from_type(config.data_type)
50
+ else:
51
+ stride = config.stride
52
+
53
+ # Initialize resource manager
54
+ self.resources = UnifiedResourceManager(device)
55
+
56
+ # Track vertex data
57
+ self._vertex_buffers: Dict[str, wgpu.GPUBuffer] = {}
58
+ self._vertex_counts: Dict[str, int] = {}
59
+ self._num_vertices: int = 0
60
+
61
+ # Initialize base pipeline
62
+ super().__init__(
63
+ device=device,
64
+ texture_format=config.texture_format,
65
+ depth_format=config.depth_format,
66
+ msaa_sample_count=config.msaa_sample_count,
67
+ data_type=config.data_type,
68
+ stride=stride,
69
+ )
70
+
71
+ def _get_shader_code(self) -> str:
72
+ """Get shader code from configuration."""
73
+ if self.config.custom_vertex_shader or self.config.custom_fragment_shader:
74
+ # Use custom shaders
75
+ shader_config = self.config.to_shader_config()
76
+ shader_code = generate_shader_code(shader_config)
77
+ else:
78
+ # Use template-based shader generation
79
+ shader_config = self.config.to_shader_config()
80
+ shader_code = generate_shader_code(shader_config)
81
+
82
+ # Debug: Print shader code for triangles
83
+ if self.config.primitive_type.value == "triangle":
84
+ print(f"Generated triangle shader ({self.config.color_mode.value}):")
85
+ print("=" * 50)
86
+ print(shader_code)
87
+ print("=" * 50)
88
+
89
+ return shader_code
90
+
91
+ def _get_vertex_buffer_layouts(self) -> List[Dict[str, Any]]:
92
+ """Get vertex buffer layouts from configuration."""
93
+ if self.config.buffer_layouts:
94
+ # Use custom buffer layouts
95
+ return [
96
+ {
97
+ "array_stride": layout.stride,
98
+ "step_mode": layout.step_mode,
99
+ "attributes": layout.attributes,
100
+ }
101
+ for layout in self.config.buffer_layouts
102
+ ]
103
+ else:
104
+ # Generate default layouts based on configuration
105
+ return self._generate_default_vertex_layouts()
106
+
107
+ def _generate_default_vertex_layouts(self) -> List[Dict[str, Any]]:
108
+ """Generate default vertex buffer layouts based on configuration."""
109
+ layouts = []
110
+
111
+ # Position buffer layout
112
+ position_layout = (
113
+ VertexLayoutBuilder(
114
+ stride=self._stride,
115
+ step_mode="instance",
116
+ )
117
+ .with_position(self.config.data_type, offset=0, location=0)
118
+ .build()
119
+ )
120
+
121
+ layouts.append(
122
+ {
123
+ "array_stride": position_layout.stride,
124
+ "step_mode": position_layout.step_mode,
125
+ "attributes": position_layout.attributes,
126
+ }
127
+ )
128
+
129
+ # Color buffer layout for multi-color mode
130
+ if self.config.color_mode.value == "multi":
131
+ color_layout = (
132
+ VertexLayoutBuilder(
133
+ stride=NGLToWebGPU.stride_from_type("Vec3"),
134
+ step_mode="instance",
135
+ )
136
+ .with_color("Vec3", offset=0, location=1)
137
+ .build()
138
+ )
139
+
140
+ layouts.append(
141
+ {
142
+ "array_stride": color_layout.stride,
143
+ "step_mode": color_layout.step_mode,
144
+ "attributes": color_layout.attributes,
145
+ }
146
+ )
147
+
148
+ # Add custom attribute layouts
149
+ if self.config.custom_attributes:
150
+ for i, attr in enumerate(self.config.custom_attributes):
151
+ # Start layout after position and potential color buffer
152
+ layout_index = len(layouts)
153
+ custom_layout = (
154
+ VertexLayoutBuilder(
155
+ stride=NGLToWebGPU.stride_from_type(attr.data_type),
156
+ step_mode=attr.step_mode,
157
+ )
158
+ .with_custom_attribute(
159
+ attr.data_type,
160
+ offset=0,
161
+ location=attr.location,
162
+ )
163
+ .build()
164
+ )
165
+
166
+ layouts.append(
167
+ {
168
+ "array_stride": custom_layout.stride,
169
+ "step_mode": custom_layout.step_mode,
170
+ "attributes": custom_layout.attributes,
171
+ }
172
+ )
173
+
174
+ return layouts
175
+
176
+ def _get_primitive_topology(self) -> wgpu.PrimitiveTopology:
177
+ """Get primitive topology from configuration."""
178
+ return self.config.topology
179
+
180
+ def _set_default_uniforms(self) -> None:
181
+ """Set default uniform values based on configuration."""
182
+ shader_config = self.config.to_shader_config()
183
+
184
+ # Set default MVP matrix
185
+ self.uniform_data["MVP"] = np.eye(4, dtype=np.float32)
186
+
187
+ # Set defaults based on primitive type and color mode
188
+ if self.config.primitive_type.value == "point":
189
+ if self.config.render_mode.value == "billboarded":
190
+ # View matrix for billboarding
191
+ self.uniform_data["ViewMatrix"] = np.eye(4, dtype=np.float32)
192
+
193
+ if self.config.color_mode.value == "single":
194
+ # Default white color and size
195
+ self.uniform_data["ColourSize"] = np.array(
196
+ [1.0, 1.0, 1.0, 1.0], dtype=np.float32
197
+ )
198
+ else:
199
+ # Default size for multi-color
200
+ self.uniform_data["size"] = 1.0
201
+
202
+ elif self.config.color_mode.value == "single":
203
+ # Default white color for non-point primitives
204
+ self.uniform_data["colour"] = np.array([1.0, 1.0, 1.0], dtype=np.float32)
205
+
206
+ def _get_pipeline_label(self) -> str:
207
+ """Get pipeline label based on configuration."""
208
+ return (
209
+ f"unified_pipeline_{self.config.primitive_type.value}_"
210
+ f"{self.config.color_mode.value}_{self.config.render_mode.value}"
211
+ )
212
+
213
+ def _create_pipeline(self) -> None:
214
+ """Create pipeline with custom depth stencil and rasterization state."""
215
+ # Create shader module
216
+ shader_code = self._get_shader_code()
217
+ shader_module = self.device.create_shader_module(code=shader_code)
218
+
219
+ # Get vertex buffer layouts
220
+ vertex_layouts = self._get_vertex_buffer_layouts()
221
+
222
+ # Create render pipeline
223
+ self.pipeline = self.device.create_render_pipeline(
224
+ label=self._get_pipeline_label(),
225
+ layout="auto",
226
+ vertex={
227
+ "module": shader_module,
228
+ "entry_point": "vertex_main",
229
+ "buffers": vertex_layouts,
230
+ },
231
+ fragment={
232
+ "module": shader_module,
233
+ "entry_point": "fragment_main",
234
+ "targets": [{"format": self.config.texture_format}],
235
+ },
236
+ primitive={
237
+ "topology": self.config.topology,
238
+ "cull_mode": self.config.cull_mode,
239
+ "front_face": self.config.front_face,
240
+ },
241
+ depth_stencil={
242
+ "format": self.config.depth_format,
243
+ "depth_write_enabled": True,
244
+ "depth_compare": wgpu.CompareFunction.less,
245
+ },
246
+ multisample={"count": self.config.msaa_sample_count},
247
+ )
248
+
249
+ # Initialize uniform manager
250
+ bind_group_layout = self.pipeline.get_bind_group_layout(0)
251
+ self.resources.uniform_manager.initialize(
252
+ self.get_dtype(),
253
+ bind_group_layout,
254
+ )
255
+
256
+ # Store reference to uniform buffer and bind group
257
+ self.uniform_buffer = self.resources.uniform_manager.get_uniform_buffer()
258
+ self.bind_group = self.resources.uniform_manager.get_bind_group()
259
+
260
+ def get_dtype(self) -> np.dtype:
261
+ """Get uniform buffer data type based on configuration."""
262
+ fields = [("MVP", "float32", (4, 4))]
263
+
264
+ # Add fields based on configuration
265
+ if self.config.primitive_type.value == "point":
266
+ if self.config.render_mode.value == "billboarded":
267
+ fields.append(("ViewMatrix", "float32", (4, 4)))
268
+
269
+ if self.config.color_mode.value == "single":
270
+ fields.append(("ColourSize", "float32", 4))
271
+ else:
272
+ fields.append(("size", "float32"))
273
+ fields.extend([(f"padding{i}", "uint32") for i in range(3)])
274
+
275
+ else: # Line or Triangle
276
+ if self.config.color_mode.value == "single":
277
+ fields.extend(
278
+ [
279
+ ("colour", "float32", 3),
280
+ ("padding", "float32"),
281
+ ]
282
+ )
283
+ else:
284
+ fields.extend([(f"padding{i}", "float32") for i in range(4)])
285
+
286
+ # Add custom uniform fields
287
+ if self.config.custom_uniforms:
288
+ for uniform in self.config.custom_uniforms:
289
+ if uniform.array_size:
290
+ fields.append((uniform.name, uniform.data_type, uniform.array_size))
291
+ else:
292
+ fields.append((uniform.name, uniform.data_type))
293
+
294
+ return np.dtype(fields)
295
+
296
+ def set_data(
297
+ self,
298
+ positions: Union[np.ndarray, wgpu.GPUBuffer],
299
+ colours: Optional[Union[np.ndarray, wgpu.GPUBuffer]] = None,
300
+ **kwargs,
301
+ ) -> None:
302
+ """
303
+ Set vertex data for rendering.
304
+
305
+ Args:
306
+ positions: Vertex positions (numpy array or GPU buffer)
307
+ colours: Vertex colors for multi-color mode (numpy array or GPU buffer)
308
+ **kwargs: Additional vertex data based on custom attributes
309
+ """
310
+ # Process positions
311
+ if isinstance(positions, wgpu.GPUBuffer):
312
+ self._vertex_buffers["position"] = positions
313
+ self._num_vertices = positions.size // self._stride
314
+ else:
315
+ self._num_vertices = len(positions)
316
+ self._vertex_buffers["position"] = (
317
+ self.resources.buffer_manager.update_buffer(
318
+ "position",
319
+ positions,
320
+ wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
321
+ f"{self._get_pipeline_label()}_position_buffer",
322
+ )
323
+ )
324
+
325
+ # Process colors for multi-color mode
326
+ if self.config.color_mode.value == "multi":
327
+ if colours is None:
328
+ # Create default white colors
329
+ default_colours = np.ones((self._num_vertices, 3), dtype=np.float32)
330
+ self._vertex_buffers["colour"] = (
331
+ self.resources.buffer_manager.process_vertex_data(
332
+ "colour",
333
+ None,
334
+ default_colours,
335
+ padding_size=4, # Pad to vec4 for alignment
336
+ buffer_label=f"{self._get_pipeline_label()}_colour_buffer",
337
+ )
338
+ )
339
+ else:
340
+ self._vertex_buffers["colour"] = (
341
+ self.resources.buffer_manager.process_vertex_data(
342
+ "colour",
343
+ colours,
344
+ None,
345
+ padding_size=4,
346
+ buffer_label=f"{self._get_pipeline_label()}_colour_buffer",
347
+ )
348
+ )
349
+
350
+ # Process custom attributes
351
+ if self.config.custom_attributes:
352
+ for attr in self.config.custom_attributes:
353
+ attr_name = attr.name
354
+ attr_data = kwargs.get(attr_name)
355
+ if attr_data is not None:
356
+ self._vertex_buffers[attr_name] = (
357
+ self.resources.buffer_manager.process_vertex_data(
358
+ attr_name,
359
+ attr_data,
360
+ None,
361
+ buffer_label=f"{self._get_pipeline_label()}_{attr_name}_buffer",
362
+ )
363
+ )
364
+
365
+ def update_uniforms(self, **kwargs) -> None:
366
+ """
367
+ Update uniform buffer values.
368
+
369
+ Args:
370
+ **kwargs: Uniform parameters
371
+ - mvp: 4x4 model view projection matrix
372
+ - view_matrix: 4x4 view matrix (for billboarding)
373
+ - colour: 3-element RGB color (single color mode)
374
+ - point_size: Point size (for points)
375
+ - Custom uniforms based on configuration
376
+ """
377
+ uniforms = {}
378
+
379
+ # Map common parameter names to uniform field names
380
+ if "mvp" in kwargs:
381
+ uniforms["MVP"] = kwargs["mvp"]
382
+
383
+ if "view_matrix" in kwargs:
384
+ uniforms["ViewMatrix"] = kwargs["view_matrix"]
385
+
386
+ if "colour" in kwargs and self.config.color_mode.value == "single":
387
+ if self.config.primitive_type.value == "point":
388
+ uniforms["ColourSize"] = self.uniform_data["ColourSize"].copy()
389
+ uniforms["ColourSize"][:3] = kwargs["colour"]
390
+ else:
391
+ uniforms["colour"] = kwargs["colour"]
392
+
393
+ if "point_size" in kwargs and self.config.primitive_type.value == "point":
394
+ if self.config.color_mode.value == "single":
395
+ uniforms["ColourSize"] = self.uniform_data["ColourSize"].copy()
396
+ uniforms["ColourSize"][3] = kwargs["point_size"]
397
+ else:
398
+ uniforms["size"] = kwargs["point_size"]
399
+
400
+ # Add custom uniforms
401
+ if self.config.custom_uniforms:
402
+ for uniform in self.config.custom_uniforms:
403
+ if uniform.name in kwargs:
404
+ uniforms[uniform.name] = kwargs[uniform.name]
405
+
406
+ # Update uniform buffer
407
+ if uniforms:
408
+ self.resources.uniform_manager.update_uniforms(uniforms)
409
+ # Update base class reference
410
+ self.uniform_data = self.resources.uniform_manager.get_uniform_data()
411
+
412
+ def render(
413
+ self,
414
+ render_pass: wgpu.GPURenderPassEncoder,
415
+ num_vertices: Optional[int] = None,
416
+ **kwargs,
417
+ ) -> None:
418
+ """
419
+ Render using this pipeline.
420
+
421
+ Args:
422
+ render_pass: Active render pass encoder
423
+ num_vertices: Number of vertices to render (defaults to all)
424
+ **kwargs: Additional render parameters
425
+ """
426
+ if not self._vertex_buffers.get("position"):
427
+ return
428
+
429
+ # Determine vertex count
430
+ count = num_vertices if num_vertices is not None else self._num_vertices
431
+
432
+ # Set pipeline and bind group
433
+ render_pass.set_pipeline(self.pipeline)
434
+ if self.bind_group:
435
+ render_pass.set_bind_group(0, self.bind_group, [], 0, 999999)
436
+
437
+ # Set vertex buffers
438
+ slot = 0
439
+ for buffer_name in ["position", "colour"]:
440
+ if buffer_name in self._vertex_buffers:
441
+ render_pass.set_vertex_buffer(slot, self._vertex_buffers[buffer_name])
442
+ slot += 1
443
+
444
+ # Set custom attribute buffers
445
+ if self.config.custom_attributes:
446
+ for attr in self.config.custom_attributes:
447
+ if attr.name in self._vertex_buffers:
448
+ render_pass.set_vertex_buffer(slot, self._vertex_buffers[attr.name])
449
+ slot += 1
450
+
451
+ # Determine vertex count for different primitives
452
+ if (
453
+ self.config.primitive_type.value == "point"
454
+ and self.config.render_mode.value == "billboarded"
455
+ ):
456
+ # Points are rendered as 4 vertices (quad) per point
457
+ vertex_count = 4
458
+ instance_count = count
459
+ else:
460
+ vertex_count = count
461
+ instance_count = 1
462
+
463
+ # Issue draw call
464
+ render_pass.draw(vertex_count, instance_count)
465
+
466
+ def cleanup(self) -> None:
467
+ """Release pipeline resources."""
468
+ self.resources.cleanup()
469
+ super().cleanup()
@@ -1,4 +1,12 @@
1
- __version__ = "0.1.0"
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("ncca-ngl") # pragma: no cover
5
+ except PackageNotFoundError: # pragma: no cover
6
+ __version__ = "0.0.0"
7
+
8
+ __author__ = "Jon Macey jmacey@bournemouth.ac.uk"
9
+ __license__ = "MIT"
2
10
 
3
11
 
4
12
  from .lookatwidget import LookAtWidget
@@ -9,4 +17,12 @@ from .vec2widget import Vec2Widget
9
17
  from .vec3widget import Vec3Widget
10
18
  from .vec4widget import Vec4Widget
11
19
 
12
- __all__ = ["Vec2Widget", "Vec3Widget", "Vec4Widget", "TransformWidget", "LookAtWidget", "RGBColourWidget"]
20
+ __all__ = [
21
+ "Vec2Widget",
22
+ "Vec3Widget",
23
+ "Vec4Widget",
24
+ "TransformWidget",
25
+ "LookAtWidget",
26
+ "RGBColourWidget",
27
+ "RGBAColourWidget",
28
+ ]
@@ -11,13 +11,14 @@ from ncca.ngl.widgets import (
11
11
  Vec2Widget,
12
12
  Vec3Widget,
13
13
  Vec4Widget,
14
+ __version__,
14
15
  )
15
16
 
16
17
 
17
18
  class SimpleDialog(QDialog):
18
19
  def __init__(self, parent=None):
19
20
  super(SimpleDialog, self).__init__(parent)
20
- self.setWindowTitle("PyNGL ncca.widgets library ")
21
+ self.setWindowTitle(f"PyNGL ncca.widgets library {__version__}")
21
22
  self.setMinimumWidth(200)
22
23
  layout = QGridLayout()
23
24
 
@@ -53,6 +53,7 @@ class LookAtWidget(QFrame):
53
53
  content_layout.addWidget(self._up)
54
54
  main_layout.addWidget(self._toggle_button)
55
55
  main_layout.addWidget(self._content_widget)
56
+ self._update_matrix()
56
57
 
57
58
  def set_eye(self, eye):
58
59
  self._eye.set_value(eye)
@@ -68,7 +69,7 @@ class LookAtWidget(QFrame):
68
69
  self._toggle_button.setText(name)
69
70
 
70
71
  def get_name(self):
71
- return self._name.text()
72
+ return self._name
72
73
 
73
74
  def get_eye(self):
74
75
  return self._eye.value
@@ -1,7 +1,7 @@
1
1
  from PySide6.QtCore import Property, QSignalBlocker, Signal
2
2
  from PySide6.QtWidgets import QDoubleSpinBox, QFrame, QHBoxLayout, QLabel, QWidget
3
3
 
4
- from ncca.ngl import Mat4
4
+ from ncca.ngl import Mat4
5
5
 
6
6
 
7
- class Mat4ViewWidget(QFrame):
7
+ class Mat4ViewWidget(QFrame): ...
@@ -78,7 +78,7 @@ class Vec2Widget(QFrame):
78
78
  Args:
79
79
  value: The new value of the widget.
80
80
  """
81
- with QSignalBlocker(self.x_spinbox), QSignalBlocker(self.y_spinbox), QSignalBlocker(self.z_spinbox):
81
+ with QSignalBlocker(self.x_spinbox), QSignalBlocker(self.y_spinbox):
82
82
  self.x_spinbox.setValue(value.x)
83
83
  self.y_spinbox.setValue(value.y)
84
84
  self._value = value
@@ -22,6 +22,7 @@ class Vec3Widget(QFrame):
22
22
  super().__init__(parent)
23
23
  self.setFrameShape(QFrame.Shape.StyledPanel)
24
24
  self._value = value
25
+
25
26
  self._name = name
26
27
  layout = QHBoxLayout()
27
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ncca-ngl
3
- Version: 0.3.4
3
+ Version: 0.5.0
4
4
  Summary: A Python version of the NGL graphics library.
5
5
  Author: Jon Macey
6
6
  Author-email: Jon Macey <jmacey@bournemouth.ac.uk>
@@ -17,6 +17,7 @@ Requires-Dist: pillow
17
17
  Requires-Dist: glfw>=2.9.0
18
18
  Requires-Dist: freetype-py>=2.5.1
19
19
  Requires-Dist: pyside6>=6.9.2
20
- Requires-Python: >=3.13
20
+ Requires-Dist: wgpu>=0.29.0
21
+ Requires-Python: >=3.11
21
22
  Project-URL: Homepage, https://github.com/NCCA/PyNGL
22
23
  Project-URL: Issues, https://github.com/NCCA/PyNGL/issues