ncca-ngl 0.1.0__py3-none-any.whl → 0.1.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.
@@ -1,318 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Event Handling Mixin for PyNGL Applications
4
-
5
- This module provides a reusable mixin class that implements common event handling
6
- patterns used across PyNGL applications, including mouse-based camera control,
7
- keyboard shortcuts, and wheel-based zooming.
8
-
9
- Usage:
10
- class MyWindow(EventHandlingMixin, QOpenGLWindow):
11
- def __init__(self):
12
- super().__init__()
13
- self.setup_event_handling() # Initialize mixin attributes
14
- # ... rest of your initialization
15
- """
16
-
17
- from typing import Protocol
18
-
19
- import OpenGL.GL as gl
20
- from PySide6.QtCore import Qt
21
-
22
- from .vec3 import Vec3
23
-
24
-
25
- class EventHandlingTarget(Protocol):
26
- """
27
- Protocol defining the interface that classes using EventHandlingMixin must implement.
28
-
29
- This ensures that the mixin has access to the necessary methods and attributes.
30
- """
31
-
32
- def update(self) -> None:
33
- """Trigger a redraw of the window."""
34
- ...
35
-
36
- def close(self) -> None:
37
- """Close the window."""
38
- ...
39
-
40
-
41
- class PySideEventHandlingMixin:
42
- """
43
- Mixin class providing standard event handling for PyNGL applications.
44
-
45
- This mixin provides common functionality for:
46
- - Mouse-based camera control (rotation with left button, translation with right button)
47
- - Keyboard shortcuts (wireframe/solid mode, reset, escape)
48
- - Mouse wheel zooming
49
-
50
- Classes using this mixin should call setup_event_handling() in their __init__ method.
51
- """
52
-
53
- # Default sensitivity values
54
- DEFAULT_ROTATION_SENSITIVITY = 0.5
55
- DEFAULT_TRANSLATION_SENSITIVITY = 0.01
56
- DEFAULT_ZOOM_SENSITIVITY = 0.1
57
-
58
- def setup_event_handling(
59
- self,
60
- rotation_sensitivity: float = DEFAULT_ROTATION_SENSITIVITY,
61
- translation_sensitivity: float = DEFAULT_TRANSLATION_SENSITIVITY,
62
- zoom_sensitivity: float = DEFAULT_ZOOM_SENSITIVITY,
63
- initial_position: Vec3 = None,
64
- ) -> None:
65
- """
66
- Initialize event handling attributes.
67
-
68
- Args:
69
- rotation_sensitivity: Mouse sensitivity for rotation (default: 0.5)
70
- translation_sensitivity: Mouse sensitivity for translation (default: 0.01)
71
- zoom_sensitivity: Mouse wheel sensitivity for zooming (default: 0.1)
72
- initial_position: Initial model position (default: Vec3(0,0,0))
73
- """
74
- # Mouse control state
75
- self.rotate: bool = False
76
- self.translate: bool = False
77
-
78
- # Rotation state
79
- self.spin_x_face: int = 0
80
- self.spin_y_face: int = 0
81
-
82
- # Mouse position tracking for rotation
83
- self.original_x_rotation: float = 0.0
84
- self.original_y_rotation: float = 0.0
85
-
86
- # Mouse position tracking for translation
87
- self.original_x_pos: float = 0.0
88
- self.original_y_pos: float = 0.0
89
-
90
- # Model position and sensitivity settings
91
- self.model_position: Vec3 = initial_position or Vec3(0, 0, 0)
92
- self.rotation_sensitivity: float = rotation_sensitivity
93
- self.translation_sensitivity: float = translation_sensitivity
94
- self.zoom_sensitivity: float = zoom_sensitivity
95
-
96
- self.INCREMENT = self.translation_sensitivity
97
- self.ZOOM = self.zoom_sensitivity
98
-
99
- # def sync_legacy_attributes(self) -> None:
100
- # """
101
- # Synchronize legacy attribute names with new ones.
102
- # Call this if you modify the legacy attributes directly.
103
- # """
104
- # self.spin_x_face = self.spinXFace
105
- # self.spin_y_face = self.spinYFace
106
- # self.model_position = self.modelPos
107
- # self.original_x_rotation = self.origX
108
- # self.original_y_rotation = self.origY
109
- # self.original_x_pos = self.origXPos
110
- # self.original_y_pos = self.origYPos
111
- # self.translation_sensitivity = self.INCREMENT
112
- # self.zoom_sensitivity = self.ZOOM
113
-
114
- def reset_camera(self) -> None:
115
- """Reset camera rotation and model position to defaults."""
116
- self.spin_x_face = 0
117
- self.spin_y_face = 0
118
- self.model_position.set(0, 0, 0)
119
-
120
- # # Sync legacy attributes
121
- # self.spinXFace = 0
122
- # self.spinYFace = 0
123
- # self.modelPos.set(0, 0, 0)
124
-
125
- def keyPressEvent(self, event) -> None:
126
- """
127
- Handle keyboard press events with common shortcuts.
128
-
129
- Shortcuts:
130
- - Escape: Close application
131
- - W: Switch to wireframe mode
132
- - S: Switch to solid fill mode
133
- - Space: Reset camera rotation and position
134
-
135
- Args:
136
- event: The QKeyEvent object
137
- """
138
- key = event.key()
139
-
140
- if key == Qt.Key_Escape:
141
- self.close()
142
- elif key == Qt.Key_W:
143
- gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
144
- elif key == Qt.Key_S:
145
- gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
146
- elif key == Qt.Key_Space:
147
- self.reset_camera()
148
- else:
149
- # Let subclasses handle other keys
150
- super().keyPressEvent(event)
151
- return
152
-
153
- self.update()
154
-
155
- def mouseMoveEvent(self, event) -> None:
156
- """
157
- Handle mouse movement for camera control.
158
-
159
- - Left button: Rotate the scene
160
- - Right button: Translate (pan) the scene
161
-
162
- Args:
163
- event: The QMouseEvent object
164
- """
165
- position = event.position()
166
-
167
- # Handle rotation with left mouse button
168
- if self.rotate and event.buttons() == Qt.LeftButton:
169
- diff_x = position.x() - self.original_x_rotation
170
- diff_y = position.y() - self.original_y_rotation
171
-
172
- self.spin_x_face += int(self.rotation_sensitivity * diff_y)
173
- self.spin_y_face += int(self.rotation_sensitivity * diff_x)
174
-
175
- self.original_x_rotation = position.x()
176
- self.original_y_rotation = position.y()
177
-
178
- # # Sync legacy attributes
179
- # self.spinXFace = self.spin_x_face
180
- # self.spinYFace = self.spin_y_face
181
- # self.origX = self.original_x_rotation
182
- # self.origY = self.original_y_rotation
183
-
184
- self.update()
185
-
186
- # Handle translation with right mouse button
187
- elif self.translate and event.buttons() == Qt.RightButton:
188
- diff_x = int(position.x() - self.original_x_pos)
189
- diff_y = int(position.y() - self.original_y_pos)
190
-
191
- self.original_x_pos = position.x()
192
- self.original_y_pos = position.y()
193
-
194
- self.model_position.x += self.translation_sensitivity * diff_x
195
- self.model_position.y -= self.translation_sensitivity * diff_y
196
-
197
- # # Sync legacy attributes
198
- # self.origXPos = self.original_x_pos
199
- # self.origYPos = self.original_y_pos
200
- # self.modelPos = self.model_position
201
-
202
- self.update()
203
-
204
- def mousePressEvent(self, event) -> None:
205
- """
206
- Handle mouse button press events to initiate rotation or translation.
207
-
208
- - Left button: Start rotation mode
209
- - Right button: Start translation mode
210
-
211
- Args:
212
- event: The QMouseEvent object
213
- """
214
- position = event.position()
215
-
216
- if event.button() == Qt.LeftButton:
217
- self.original_x_rotation = position.x()
218
- self.original_y_rotation = position.y()
219
- self.rotate = True
220
-
221
- # # Sync legacy attributes
222
- # self.origX = self.original_x_rotation
223
- # self.origY = self.original_y_rotation
224
-
225
- elif event.button() == Qt.RightButton:
226
- self.original_x_pos = position.x()
227
- self.original_y_pos = position.y()
228
- self.translate = True
229
-
230
- # # Sync legacy attributes
231
- # self.origXPos = self.original_x_pos
232
- # self.origYPos = self.original_y_pos
233
-
234
- def mouseReleaseEvent(self, event) -> None:
235
- """
236
- Handle mouse button release events to stop rotation or translation.
237
-
238
- Args:
239
- event: The QMouseEvent object
240
- """
241
- if event.button() == Qt.LeftButton:
242
- self.rotate = False
243
- elif event.button() == Qt.RightButton:
244
- self.translate = False
245
-
246
- def wheelEvent(self, event) -> None:
247
- """
248
- Handle mouse wheel events for zooming.
249
-
250
- Zooming is performed by adjusting the Z coordinate of the model position.
251
-
252
- Args:
253
- event: The QWheelEvent object
254
- """
255
- angle_delta = event.angleDelta()
256
-
257
- # Handle both x and y wheel movement (some mice/trackpads use different axes)
258
- delta = angle_delta.y() if angle_delta.y() != 0 else angle_delta.x()
259
-
260
- if delta > 0:
261
- self.model_position.z += self.zoom_sensitivity
262
- elif delta < 0:
263
- self.model_position.z -= self.zoom_sensitivity
264
-
265
- # # Sync legacy attributes
266
- # self.modelPos = self.model_position
267
-
268
- self.update()
269
-
270
- def get_camera_state(self) -> dict:
271
- """
272
- Get the current camera state for serialization or debugging.
273
-
274
- Returns:
275
- Dictionary containing current camera state
276
- """
277
- return {
278
- "spin_x_face": self.spin_x_face,
279
- "spin_y_face": self.spin_y_face,
280
- "model_position": [
281
- self.model_position.x,
282
- self.model_position.y,
283
- self.model_position.z,
284
- ],
285
- "rotation_sensitivity": self.rotation_sensitivity,
286
- "translation_sensitivity": self.translation_sensitivity,
287
- "zoom_sensitivity": self.zoom_sensitivity,
288
- }
289
-
290
- def set_camera_state(self, state: dict) -> None:
291
- """
292
- Restore camera state from a dictionary.
293
-
294
- Args:
295
- state: Dictionary containing camera state (from get_camera_state())
296
- """
297
- self.spin_x_face = state.get("spin_x_face", 0)
298
- self.spin_y_face = state.get("spin_y_face", 0)
299
-
300
- pos = state.get("model_position", [0, 0, 0])
301
- # Handle cases where pos might have fewer than 3 elements
302
- x = pos[0] if len(pos) > 0 else 0
303
- y = pos[1] if len(pos) > 1 else 0
304
- z = pos[2] if len(pos) > 2 else 0
305
- self.model_position.set(x, y, z)
306
-
307
- self.rotation_sensitivity = state.get(
308
- "rotation_sensitivity", self.DEFAULT_ROTATION_SENSITIVITY
309
- )
310
- self.translation_sensitivity = state.get(
311
- "translation_sensitivity", self.DEFAULT_TRANSLATION_SENSITIVITY
312
- )
313
- self.zoom_sensitivity = state.get(
314
- "zoom_sensitivity", self.DEFAULT_ZOOM_SENSITIVITY
315
- )
316
-
317
- # # Sync legacy attributes
318
- # self.sync_legacy_attributes()
ncca/ngl/quaternion.py DELETED
@@ -1,112 +0,0 @@
1
- """
2
- A simple Quaternion class for use in NCCA Python
3
- Attributes:
4
- s (float): The scalar part of the quaternion.
5
- x (float): The x-coordinate of the vector part of the quaternion.
6
- y (float): The y-coordinate of the vector part of the quaternion.
7
- z (float): The z-coordinate of the vector part of the quaternion.
8
- """
9
-
10
- import math
11
-
12
- from .mat4 import Mat4
13
-
14
-
15
- class Quaternion:
16
- __slots__ = ["s", "x", "y", "z"] # fix the attributes to s x,y,z
17
-
18
- def __init__(self, s: float = 1.0, x: float = 0, y: float = 0, z: float = 0):
19
- """
20
- Initializes a new instance of the Quaternion class.
21
-
22
- Args:
23
- s (float): The scalar part of the quaternion.
24
- x (float): The x-coordinate of the vector part of the quaternion.
25
- y (float): The y-coordinate of the vector part of the quaternion.
26
- z (float): The z-coordinate of the vector part of the quaternion.
27
-
28
- """
29
- self.s = float(s)
30
- self.x = float(x)
31
- self.y = float(y)
32
- self.z = float(z)
33
-
34
- @staticmethod
35
- def from_mat4(mat: "Mat4") -> "Quaternion":
36
- """
37
- Creates a new Quaternion from a Mat4 rotation matrix.
38
-
39
- Args:
40
- mat (Mat4): The rotation matrix to convert.
41
-
42
- Returns:
43
- Quaternion: A new Quaternion representing the rotation matrix.
44
-
45
- """
46
- matrix = mat.get_matrix()
47
- T = 1.0 + matrix[0] + matrix[5] + matrix[10]
48
- if T > 0.00000001: # to avoid large distortions!
49
- scale = math.sqrt(T) * 2.0
50
- x = (matrix[6] - matrix[9]) / scale
51
- y = (matrix[8] - matrix[2]) / scale
52
- z = (matrix[1] - matrix[4]) / scale
53
- s = 0.25 * scale
54
- elif matrix[0] > matrix[5] and matrix[0] > matrix[10]:
55
- scale = math.sqrt(1.0 + matrix[0] - matrix[5] - matrix[10]) * 2.0
56
- x = 0.25 * scale
57
- y = (matrix[4] + matrix[1]) / scale
58
- z = (matrix[2] + matrix[8]) / scale
59
- s = (matrix[6] - matrix[9]) / scale
60
-
61
- elif matrix[5] > matrix[10]:
62
- scale = math.sqrt(1.0 + matrix[5] - matrix[0] - matrix[10]) * 2.0
63
- x = (matrix[4] + matrix[1]) / scale
64
- y = 0.25 * scale
65
- z = (matrix[9] + matrix[6]) / scale
66
- s = (matrix[8] - matrix[2]) / scale
67
-
68
- else:
69
- scale = math.sqrt(1.0 + matrix[10] - matrix[0] - matrix[5]) * 2.0
70
- x = (matrix[8] + matrix[2]) / scale
71
- y = (matrix[9] + matrix[6]) / scale
72
- z = 0.25 * scale
73
- s = (matrix[1] - matrix[4]) / scale
74
-
75
- return Quaternion(s, x, y, z)
76
-
77
- def __add__(self, rhs):
78
- return Quaternion(self.s + rhs.s, self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
79
-
80
- def __iadd__(self, rhs):
81
- self.s += rhs.s
82
- self.x += rhs.x
83
- self.y += rhs.y
84
- self.z += rhs.z
85
- return self
86
-
87
- def __sub__(self, rhs):
88
- return Quaternion(self.s - rhs.s, self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
89
-
90
- def __isub__(self, rhs):
91
- return self.__sub__(rhs)
92
-
93
- def __mul__(self, rhs):
94
- return Quaternion(
95
- self.s * rhs.s - self.x * rhs.x - self.y * rhs.y - self.z * rhs.z,
96
- self.s * rhs.x + self.x * rhs.s + self.y * rhs.z - self.z * rhs.y,
97
- self.s * rhs.y - self.x * rhs.z + self.y * rhs.s + self.z * rhs.x,
98
- self.s * rhs.z + self.x * rhs.y - self.y * rhs.x + self.z * rhs.s,
99
- )
100
-
101
- def __str__(self) -> str:
102
- """
103
- Returns a string representation of the Quaternion.
104
-
105
- Returns:
106
- str: A string representation of the Quaternion.
107
-
108
- """
109
- return f"Quaternion({self.s}, [{self.x}, {self.y}, {self.z}])"
110
-
111
- def __repr__(self) -> str:
112
- return f"Quaternion({self.s}, [{self.x}, {self.y}, {self.z}])"
ncca/ngl/random.py DELETED
@@ -1,167 +0,0 @@
1
- """
2
- A module to replicate the functionality of the NGL C++ Random class.
3
- """
4
-
5
- import random
6
- import time
7
-
8
- from .vec2 import Vec2
9
- from .vec3 import Vec3
10
- from .vec4 import Vec4
11
-
12
-
13
- class Random:
14
- _float_generators = {
15
- "RandomFloat": lambda: random.uniform(-1.0, 1.0),
16
- "RandomPositiveFloat": lambda: random.uniform(0.0, 1.0),
17
- }
18
-
19
- _int_generators = {}
20
-
21
- @staticmethod
22
- def set_seed():
23
- """set the seed using std::time(NULL)"""
24
- random.seed(int(time.time()))
25
-
26
- @staticmethod
27
- def set_seed_value(value: int):
28
- """set the seed using a param value
29
- Args:
30
- value (int): the seed value
31
- """
32
- random.seed(value)
33
-
34
- @staticmethod
35
- def get_float_from_generator_name(name: str) -> float:
36
- """gets a pre-generated float value for a genetator
37
- Args:
38
- name (str): the name of the generator to use for the number
39
- Returns:
40
- a random number created by the generator or 0 if the generator is not found
41
- """
42
- if name in Random._float_generators:
43
- return Random._float_generators[name]()
44
- return 0.0
45
-
46
- @staticmethod
47
- def get_int_from_generator_name(name: str) -> int:
48
- """gets a pre-generated int value for a genetator
49
- Args:
50
- name (str): the name of the generator to use for the number
51
- Returns:
52
- a random number created by the generator or 0 if the generator is not found
53
- """
54
- if name in Random._int_generators:
55
- return Random._int_generators[name]()
56
- return 0
57
-
58
- @staticmethod
59
- def add_int_generator(name: str, generator):
60
- """add a generator to the int generators
61
- Args:
62
- name (str): the name of the generator to use for the number
63
- generator : the generator to add should be a callable function
64
- """
65
- Random._int_generators[name] = generator
66
-
67
- @staticmethod
68
- def add_float_generator(name: str, generator):
69
- """add a generator to the float generators
70
- Args:
71
- name (str): the name of the generator to use for the number
72
- generator : the generator to add should be a callable function
73
- """
74
- Random._float_generators[name] = generator
75
-
76
- @staticmethod
77
- def get_random_vec4() -> Vec4:
78
- """get a random vector with componets ranged from +/- 1"""
79
- gen = Random._float_generators["RandomFloat"]
80
- return Vec4(gen(), gen(), gen(), 0.0)
81
-
82
- @staticmethod
83
- def get_random_colour4() -> Vec4:
84
- """get a random colour with components ranged from 0-1"""
85
- gen = Random._float_generators["RandomPositiveFloat"]
86
- return Vec4(gen(), gen(), gen(), 1.0)
87
-
88
- @staticmethod
89
- def get_random_colour3() -> Vec3:
90
- """get a random colour with components ranged from 0-1"""
91
- gen = Random._float_generators["RandomPositiveFloat"]
92
- return Vec3(gen(), gen(), gen())
93
-
94
- @staticmethod
95
- def get_random_normalized_vec4() -> Vec4:
96
- """get a random vector with componets ranged from +/- 1 and Normalized"""
97
- gen = Random._float_generators["RandomFloat"]
98
- v = Vec4(gen(), gen(), gen(), 0.0)
99
- v.normalize()
100
- return v
101
-
102
- @staticmethod
103
- def get_random_vec3() -> Vec3:
104
- """get a random vector with componets ranged from +/- 1"""
105
- gen = Random._float_generators["RandomFloat"]
106
- return Vec3(gen(), gen(), gen())
107
-
108
- @staticmethod
109
- def get_random_normalized_vec3() -> Vec3:
110
- """get a random vector with componets ranged from +/- 1 and Normalized"""
111
- gen = Random._float_generators["RandomFloat"]
112
- v = Vec3(gen(), gen(), gen())
113
- v.normalize()
114
- return v
115
-
116
- @staticmethod
117
- def get_random_vec2() -> Vec2:
118
- """get a random vector with componets ranged from +/- 1"""
119
- gen = Random._float_generators["RandomFloat"]
120
- return Vec2(gen(), gen())
121
-
122
- @staticmethod
123
- def get_random_normalized_vec2() -> Vec2:
124
- """get a random vector with componets ranged from +/- 1 and Normalized"""
125
- gen = Random._float_generators["RandomFloat"]
126
- v = Vec2(gen(), gen())
127
- v.normalize()
128
- return v
129
-
130
- @staticmethod
131
- def get_random_point(
132
- x_range: float = 1.0, y_range: float = 1.0, z_range: float = 1.0
133
- ) -> Vec3:
134
- """get a random point in 3D space defaults to +/- 1 else user defined range
135
- Args:
136
- x_range (float): the +/-x range
137
- y_range (float): the +/-y range
138
- z_range (float): the +/-z range
139
- Returns:
140
- a random point
141
- """
142
- gen = Random._float_generators["RandomFloat"]
143
- return Vec3(gen() * x_range, gen() * y_range, gen() * z_range)
144
-
145
- @staticmethod
146
- def random_number(mult: float = 1.0) -> float:
147
- """a replacement for the old RandomNumber func
148
- this is basically a convinience function
149
- Args:
150
- mult (float): an optional multiplyer for the output
151
- Returns:
152
- (uniform_random(-1-0-+1) * mult)
153
- """
154
- gen = Random._float_generators["RandomFloat"]
155
- return gen() * mult
156
-
157
- @staticmethod
158
- def random_positive_number(mult: float = 1.0) -> float:
159
- """a replacement for the old ReandomPosNum
160
- this is basically a convinience function
161
- Args:
162
- mult (float): an optional multiplyer for the output
163
- Returns:
164
- (uniform_random(0-1) * mult)
165
- """
166
- gen = Random._float_generators["RandomPositiveFloat"]
167
- return gen() * mult