ncca-ngl 0.3.5__tar.gz → 0.5.0__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.
Files changed (117) hide show
  1. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/PKG-INFO +3 -2
  2. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/pyproject.toml +25 -2
  3. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/pack_arrays.py +2 -3
  4. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/__init__.py +3 -4
  5. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/base_mesh.py +28 -20
  6. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/image.py +1 -3
  7. ncca_ngl-0.5.0/src/ncca/ngl/mat2.py +164 -0
  8. ncca_ngl-0.5.0/src/ncca/ngl/mat3.py +375 -0
  9. ncca_ngl-0.5.0/src/ncca/ngl/mat4.py +301 -0
  10. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/prim_data.py +42 -36
  11. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/primitives.py +2 -2
  12. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/pyside_event_handling_mixin.py +0 -108
  13. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/quaternion.py +69 -36
  14. ncca_ngl-0.5.0/src/ncca/ngl/shader.py +113 -0
  15. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shader_program.py +94 -117
  16. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/texture.py +5 -2
  17. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/util.py +0 -2
  18. ncca_ngl-0.5.0/src/ncca/ngl/vec2.py +117 -0
  19. ncca_ngl-0.5.0/src/ncca/ngl/vec2_array.py +175 -0
  20. ncca_ngl-0.5.0/src/ncca/ngl/vec3.py +120 -0
  21. ncca_ngl-0.5.0/src/ncca/ngl/vec3_array.py +181 -0
  22. ncca_ngl-0.5.0/src/ncca/ngl/vec4.py +129 -0
  23. ncca_ngl-0.5.0/src/ncca/ngl/vec4_array.py +175 -0
  24. ncca_ngl-0.5.0/src/ncca/ngl/vector_base.py +542 -0
  25. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/__init__.py +20 -0
  26. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/__main__.py +640 -0
  27. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/__main__.py.backup +640 -0
  28. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/base_webgpu_pipeline.py +354 -0
  29. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/custom_shader_pipeline.py +288 -0
  30. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/instanced_geometry_pipeline.py +594 -0
  31. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/line_pipeline.py +405 -0
  32. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/pipeline_factory.py +190 -0
  33. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/pipeline_shaders.py +497 -0
  34. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/point_list_pipeline.py +349 -0
  35. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/point_pipeline.py +336 -0
  36. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/triangle_pipeline.py +419 -0
  37. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/webgpu_constants.py +31 -0
  38. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/webgpu_widget.py +322 -0
  39. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/REFACTORING_SUMMARY.md +82 -0
  40. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/UNIFIED_SYSTEM.md +314 -0
  41. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/buffer_manager.py +396 -0
  42. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/pipeline_config.py +463 -0
  43. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/shader_constants.py +328 -0
  44. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/shader_templates.py +563 -0
  45. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/unified_examples.py +390 -0
  46. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/unified_factory.py +449 -0
  47. ncca_ngl-0.5.0/src/ncca/ngl/webgpu/wip/unified_pipeline.py +469 -0
  48. ncca_ngl-0.5.0/src/ncca/ngl/widgets/__init__.py +28 -0
  49. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/__main__.py +2 -1
  50. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/lookatwidget.py +2 -1
  51. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/mat4widget.py +2 -2
  52. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/vec2widget.py +1 -1
  53. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/vec3widget.py +1 -0
  54. ncca_ngl-0.3.5/src/ncca/ngl/mat2.py +0 -138
  55. ncca_ngl-0.3.5/src/ncca/ngl/mat3.py +0 -456
  56. ncca_ngl-0.3.5/src/ncca/ngl/mat4.py +0 -466
  57. ncca_ngl-0.3.5/src/ncca/ngl/shader.py +0 -229
  58. ncca_ngl-0.3.5/src/ncca/ngl/vec2.py +0 -360
  59. ncca_ngl-0.3.5/src/ncca/ngl/vec2_array.py +0 -124
  60. ncca_ngl-0.3.5/src/ncca/ngl/vec3.py +0 -410
  61. ncca_ngl-0.3.5/src/ncca/ngl/vec3_array.py +0 -128
  62. ncca_ngl-0.3.5/src/ncca/ngl/vec4.py +0 -239
  63. ncca_ngl-0.3.5/src/ncca/ngl/vec4_array.py +0 -124
  64. ncca_ngl-0.3.5/src/ncca/ngl/widgets/__init__.py +0 -12
  65. ncca_ngl-0.3.5/src/ncca/ngl/widgets/transformation_widget.py +0 -299
  66. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/LICENSE.txt +0 -0
  67. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/.ruff_cache/.gitignore +0 -0
  68. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/.ruff_cache/0.13.0/10564494386971134025 +0 -0
  69. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/.ruff_cache/0.13.0/7783445477288392980 +0 -0
  70. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/.ruff_cache/CACHEDIR.TAG +0 -0
  71. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/NGLDebug.log +0 -0
  72. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/Primitives.npz +0 -0
  73. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/buddah.npy +0 -0
  74. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/bunny.npy +0 -0
  75. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/cube.npy +0 -0
  76. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/dodecahedron.npy +0 -0
  77. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/dragon.npy +0 -0
  78. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/football.npy +0 -0
  79. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/icosahedron.npy +0 -0
  80. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/octahedron.npy +0 -0
  81. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/teapot.npy +0 -0
  82. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/tetrahedron.npy +0 -0
  83. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/PrimData/troll.npy +0 -0
  84. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/abstract_vao.py +0 -0
  85. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/base_mesh.pyi +0 -0
  86. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/bbox.py +0 -0
  87. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/bezier_curve.py +0 -0
  88. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/first_person_camera.py +0 -0
  89. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/log.py +0 -0
  90. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/multi_buffer_vao.py +0 -0
  91. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/obj.py +0 -0
  92. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/plane.py +0 -0
  93. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/random.py +0 -0
  94. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shader_lib.py +0 -0
  95. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/checker_fragment.glsl +0 -0
  96. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/checker_vertex.glsl +0 -0
  97. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/colour_fragment.glsl +0 -0
  98. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/colour_vertex.glsl +0 -0
  99. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/diffuse_fragment.glsl +0 -0
  100. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/diffuse_vertex.glsl +0 -0
  101. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/text_fragment.glsl +0 -0
  102. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/text_geometry.glsl +0 -0
  103. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/shaders/text_vertex.glsl +0 -0
  104. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/simple_index_vao.py +0 -0
  105. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/simple_vao.py +0 -0
  106. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/text.py +0 -0
  107. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/transform.py +0 -0
  108. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/vao_factory.py +0 -0
  109. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/NGLDebug.log +0 -0
  110. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/glsl/phong.frag +0 -0
  111. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/glsl/phong.vert +0 -0
  112. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/glsl/picking.frag +0 -0
  113. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/glsl/picking.vert +0 -0
  114. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/rgbacolourwidget.py +0 -0
  115. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/rgbcolourwidget.py +0 -0
  116. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/transformwidget.py +0 -0
  117. {ncca_ngl-0.3.5 → ncca_ngl-0.5.0}/src/ncca/ngl/widgets/vec4widget.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ncca-ngl
3
- Version: 0.3.5
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
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "ncca-ngl"
3
- version = "0.3.5"
3
+ version = "0.5.0"
4
4
  description = "A Python version of the NGL graphics library."
5
5
  authors = [{ name = "Jon Macey", email = "jmacey@bournemouth.ac.uk" }]
6
- requires-python = ">=3.13"
6
+ requires-python = ">=3.11"
7
7
  license = { file = "LICENSE.txt" }
8
8
 
9
9
  dependencies = [
@@ -13,6 +13,7 @@ dependencies = [
13
13
  "glfw>=2.9.0",
14
14
  "freetype-py>=2.5.1",
15
15
  "pyside6>=6.9.2",
16
+ "wgpu>=0.29.0",
16
17
  ]
17
18
 
18
19
  [project.urls]
@@ -42,4 +43,26 @@ dev = [
42
43
  "mkdocs>=1.6.1",
43
44
  "mkdocstrings[python]>=0.30.1",
44
45
  "mkdocs-material>=9.7.0",
46
+ "pytest-qt>=4.5.0",
47
+ ]
48
+
49
+ [tool.coverage.run]
50
+ source = ["src/ncca/ngl"]
51
+ omit = [
52
+ "*/tests/*",
53
+ "*/test_*",
54
+ "*/__pycache__/*",
55
+ "*/site-packages/*",
56
+ "__main__.py",
57
+ "__init__.py",
58
+ ]
59
+
60
+ [tool.coverage.report]
61
+ exclude_lines = [
62
+ "pragma: no cover",
63
+ "def __repr__",
64
+ "raise AssertionError",
65
+ "raise NotImplementedError",
66
+ "if __name__ == .__main__.:",
67
+ "if TYPE_CHECKING:",
45
68
  ]
@@ -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
 
@@ -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
@@ -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
  """
@@ -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
 
@@ -0,0 +1,164 @@
1
+ """
2
+ Mat2 class with NumPy implementation
3
+ """
4
+
5
+ import numpy as np
6
+
7
+ from .vec2 import Vec2
8
+
9
+
10
+ class Mat2Error(Exception):
11
+ pass
12
+
13
+
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
18
+
19
+
20
+ class Mat2:
21
+ __slots__ = ["m"]
22
+
23
+ def __init__(self):
24
+ """
25
+ Initialize a 2x2 matrix.
26
+
27
+
28
+ """
29
+ self.m = np.eye(2, dtype=np.float64)
30
+
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
48
+
49
+ def get_matrix(self) -> list[float]:
50
+ """
51
+ Get the current matrix representation as a flat list in column-major order.
52
+
53
+ Returns:
54
+ list[float]: A flat list of floats.
55
+ """
56
+ return self.m.flatten("C").tolist()
57
+
58
+ def to_numpy(self):
59
+ """
60
+ Convert the current matrix to a NumPy array.
61
+
62
+ Returns:
63
+ np.ndarray: The matrix as a NumPy array.
64
+ """
65
+ return self.m.astype(np.float32)
66
+
67
+ @classmethod
68
+ def identity(cls) -> "Mat2":
69
+ """
70
+ Create an identity matrix.
71
+
72
+ Returns:
73
+ Mat2: A new identity Mat2 object.
74
+ """
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
97
+
98
+ def __matmul__(self, rhs):
99
+ """
100
+ Matrix multiplication or vector transformation with a 2D matrix.
101
+
102
+ Args:
103
+ rhs (Mat2 | Vec2): The right-hand side operand.
104
+ If Mat2, perform matrix multiplication.
105
+ If Vec2, transform the vector by the matrix.
106
+
107
+ Returns:
108
+ Mat2: Resulting matrix from matrix multiplication.
109
+ Vec2: Transformed vector.
110
+
111
+ Raises:
112
+ ValueError: If rhs is neither a Mat2 nor Vec2 object.
113
+ """
114
+ if isinstance(rhs, Mat2):
115
+ return self._mat_mul(rhs)
116
+ elif isinstance(rhs, Vec2):
117
+ vec = np.array([rhs.x, rhs.y], dtype=np.float64)
118
+ res = self.m @ vec
119
+ return Vec2(res[0], res[1])
120
+ else:
121
+ raise ValueError(f"Can only multiply by Mat2 or Vec2, not {type(rhs)}")
122
+
123
+ def __str__(self) -> str:
124
+ """
125
+ String representation of the matrix.
126
+
127
+ Returns:
128
+ str: The string representation.
129
+ """
130
+ return f"Mat2({self.m[0].tolist()}, {self.m[1].tolist()})"
131
+
132
+ def to_list(self):
133
+ "convert matrix to list in column-major order"
134
+ return self.m.flatten("C").tolist()
135
+
136
+ def copy(self) -> "Mat2":
137
+ """Create a copy of the matrix.
138
+
139
+ Returns:
140
+ A new Mat2 instance with the same values.
141
+ """
142
+ new_mat = Mat2()
143
+ new_mat.m = self.m.copy()
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))