fluxrender 0.1.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.
@@ -0,0 +1,13 @@
1
+ from .core import Scene, CoordinateSystem
2
+ from .ui import Button, UIStyle, Grid, Axis
3
+ from .entities import VectorField, ParticleSystem
4
+ from .regions import CircularRegion, CursorRegion
5
+ from .math_engine import VectorMathEngine
6
+ from .probes import DataProbe
7
+
8
+
9
+ from .constants import Align, Property, ArrowStyle, FieldMode, ScaleType
10
+ from .colors import ColorMapper
11
+
12
+
13
+
@@ -0,0 +1,307 @@
1
+ import numpy as np
2
+ from typing import Sequence, Tuple
3
+
4
+ from .constants import ScaleType
5
+ from .validators import _fatal_error
6
+
7
+
8
+
9
+ def hsl_to_rgba(h: float, s: float, l: float, a: float = 1.0):
10
+ """
11
+ Converts HSL color space to RGBA color space.
12
+
13
+ Args:
14
+ h (float): Hue component [0, 1].
15
+ s (float): Saturation component [0, 1].
16
+ l (float): Lightness component [0, 1].
17
+ a (float): Alpha component [0, 1].
18
+
19
+ Returns:
20
+ tuple: A tuple representing the RGBA color (r, g, b, a).
21
+ """
22
+
23
+ c = (1 - abs(2 * l - 1)) * s
24
+ x = c * (1 - abs((h * 6) % 2 - 1))
25
+ m = l - c / 2
26
+
27
+ r1, g1, b1 = 0, 0, 0
28
+ if 0 <= h < 1/6:
29
+ r1, g1, b1 = c, x, 0
30
+ elif 1/6 <= h < 1/3:
31
+ r1, g1, b1 = x, c, 0
32
+ elif 1/3 <= h < 1/2:
33
+ r1, g1, b1 = 0, c, x
34
+ elif 1/2 <= h < 2/3:
35
+ r1, g1, b1 = 0, x, c
36
+ elif 2/3 <= h < 5/6:
37
+ r1, g1, b1 = x, 0, c
38
+ elif 5/6 <= h < 1:
39
+ r1, g1, b1 = c, 0, x
40
+
41
+ r = r1 + m
42
+ g = g1 + m
43
+ b = b1 + m
44
+
45
+ return (r, g, b, a)
46
+
47
+ def hsl_to_rgb_vectorized(h: np.ndarray, s: np.ndarray, l: np.ndarray, a: np.ndarray = 1.0):
48
+ """
49
+ Converts HSL color space to RGBA color space for NumPy arrays.
50
+
51
+ Args:
52
+ h (np.ndarray): Hue component array [0, 1].
53
+ s (np.ndarray): Saturation component array [0, 1].
54
+ l (np.ndarray): Lightness component array [0, 1].
55
+ a (np.ndarray): Alpha component array [0, 1].
56
+ """
57
+
58
+ c = (1 - np.abs(2 * l - 1)) * s
59
+ h_prime = h / 60.0
60
+ x = c * (1 - np.abs(h_prime % 2 - 1))
61
+ m = l - c / 2
62
+
63
+ r = np.zeros_like(h)
64
+ g = np.zeros_like(h)
65
+ b = np.zeros_like(h)
66
+
67
+ # Warunki wektorowe (zamiast if/elif)
68
+ # 0 <= H' < 1
69
+ mask = (h_prime >= 0) & (h_prime < 1)
70
+ r[mask], g[mask], b[mask] = c[mask], x[mask], 0
71
+
72
+ # 1 <= H' < 2
73
+ mask = (h_prime >= 1) & (h_prime < 2)
74
+ r[mask], g[mask], b[mask] = x[mask], c[mask], 0
75
+
76
+ # 2 <= H' < 3
77
+ mask = (h_prime >= 2) & (h_prime < 3)
78
+ r[mask], g[mask], b[mask] = 0, c[mask], x[mask]
79
+
80
+ # 3 <= H' < 4
81
+ mask = (h_prime >= 3) & (h_prime < 4)
82
+ r[mask], g[mask], b[mask] = 0, x[mask], c[mask]
83
+
84
+ # 4 <= H' < 5
85
+ mask = (h_prime >= 4) & (h_prime < 5)
86
+ r[mask], g[mask], b[mask] = x[mask], 0, c[mask]
87
+
88
+ # 5 <= H' < 6
89
+ mask = (h_prime >= 5) & (h_prime < 6)
90
+ r[mask], g[mask], b[mask] = c[mask], 0, x[mask]
91
+
92
+ return r + m, g + m, b + m
93
+
94
+ def parse_color(color_input: Sequence[float]) -> Tuple[float, float, float, float]:
95
+ """
96
+ Validates and standardizes color inputs to a guaranteed RGBA tuple.
97
+ Ensures all elements are numbers within the [0.0, 1.0] range.
98
+ Assumes an alpha of 1.0 if only RGB is provided.
99
+ """
100
+
101
+ try:
102
+ length = len(color_input)
103
+ except TypeError:
104
+ _fatal_error(f"Color input must be a sequence (e.g., tuple, list). Got {type(color_input).__name__}.", "TypeError")
105
+
106
+ if length not in (3, 4):
107
+ _fatal_error(f"Color must be defined as RGB (3 values) or RGBA (4 values). Got {length} elements.", "ValueError")
108
+
109
+ try:
110
+ floats = [float(c) for c in color_input]
111
+ except (ValueError, TypeError):
112
+ _fatal_error(f"All color components must be numbers. Got: {color_input}", "TypeError")
113
+
114
+ if any(c < 0 or c > 1 for c in color_input):
115
+ _fatal_error(f"Color components must be in the range [0, 1]. Got {color_input}.", "ValueError")
116
+
117
+ if any(c < 0.0 or c > 1.0 for c in floats):
118
+ _fatal_error(f"Color components must be in the range [0.0, 1.0]. Got {color_input}.", "ValueError")
119
+
120
+ if length == 3:
121
+ return (floats[0], floats[1], floats[2], 1.0) # Assume alpha = 1.0 if not provided
122
+
123
+ return (floats[0], floats[1], floats[2], floats[3])
124
+
125
+ class ColorSequence:
126
+ def __set_name__(self, owner, name):
127
+ self.private_name = '_' + name
128
+
129
+ def __get__(self, obj, objtype=None):
130
+ return getattr(obj, self.private_name)
131
+
132
+ def __set__(self, obj, value):
133
+ safe_color = parse_color(value)
134
+ setattr(obj, self.private_name, safe_color)
135
+
136
+ if hasattr(obj, '_flag_for_update'):
137
+ obj._flag_for_update(self.private_name[1:])
138
+
139
+
140
+
141
+ class ColorMapper:
142
+ """A dynamic color interpolation engine that translates scalar fields into vibrant visual gradients.
143
+
144
+ The ColorMapper acts as the visual translator for the mathematical engine. It takes raw
145
+ scalar values (such as velocity magnitude, divergence, or custom topological metrics)
146
+ and smoothly maps them to physical RGBA colors.
147
+
148
+ Crucially, all color interpolation is performed natively within the HSL (Hue, Saturation,
149
+ Lightness) color space rather than RGB. This architectural choice guarantees perceptually
150
+ uniform, vivid transitions and completely eliminates the "muddy" or desaturated mid-tones
151
+ commonly seen in standard linear color blending. It features highly flexible normalization,
152
+ allowing bounds to be strictly locked at initialization or dynamically injected frame-by-frame
153
+ by the rendering entities (like VectorField or ParticleSystem).
154
+ """
155
+
156
+ def __init__(self,
157
+ min_hue: int = 240, max_hue: int = 0,
158
+ min_saturation: float = 0.4, max_saturation: float = 1.0,
159
+ min_lightness: float = 0.3, max_lightness: float = 0.65,
160
+ min_alpha: float = 0.8, max_alpha: float = 1.0,
161
+ scale_type: ScaleType = ScaleType.LINEAR,
162
+ scale_function = None,
163
+ min_value: float = None, max_value: float = None
164
+ ):
165
+ """
166
+ Args:
167
+ min_hue (int): Minimum hue in degrees (0-360). Default is 240 (blue).
168
+ max_hue (int): Maximum hue in degrees (0-360). Default is 0 (red).
169
+ min_saturation (float): Minimum saturation (0-1). Default is 0.4.
170
+ max_saturation (float): Maximum saturation (0-1). Default is 1.0.
171
+ min_lightness (float): Minimum lightness (0-1). Default is 0.3.
172
+ max_lightness (float): Maximum lightness (0-1). Default is 0.65.
173
+ min_alpha (float): Minimum alpha (0-1). Default is 0.8.
174
+ max_alpha (float): Maximum alpha (0-1). Default is 1.0.
175
+ scale_type (ScaleType): The type of scaling to apply to the input values. Default is ScaleType.LINEAR.
176
+ scale_function (Callable, optional): A custom single-argument function f(t). Input t is normalized in [0.0, 1.0], and the output must also be within [0.0, 1.0]. Used only if `scale_type` is `ScaleType.CUSTOM`.
177
+ min_value (float, optional): Hardcoded minimum value for normalization. If None, the rendering entity will dynamically calculate and inject the minimum value of the current frame.
178
+ max_value (float, optional): Hardcoded maximum value for normalization. If None, the rendering entity will dynamically calculate and inject the maximum value.
179
+
180
+ Example:
181
+ Creating a highly saturated thermal gradient (Blue to Red) with a custom logarithmic-like curve:
182
+ ```python
183
+ import FluxRender as fr
184
+
185
+ # [Initialize scene and coordinate system here]
186
+
187
+ # This mapper transitions from green to orange, but uses a square-root curve
188
+ mapper = fr.ColorMapper(
189
+ min_hue=150,
190
+ max_hue=20,
191
+ min_saturation=0.8,
192
+ max_saturation=1,
193
+ min_lightness=0.1,
194
+ max_lightness=0.6,
195
+ min_alpha=1,
196
+ scale_type = fr.ScaleType.CUSTOM,
197
+ scale_function = lambda x: x ** 0.5,
198
+ )
199
+
200
+ # Apply the mapper directly to a ParticleSystem
201
+ particles = fr.ParticleSystem(
202
+ vec_function = lambda x, y: (np.sin(x), np.cos(x) * np.sin(y)),
203
+ color_mapper = mapper,
204
+ )
205
+
206
+ scene.add(particles)
207
+ ```
208
+ """
209
+
210
+ self.min_hue = min_hue
211
+ self.max_hue = max_hue
212
+ self.min_saturation = min_saturation
213
+ self.max_saturation = max_saturation
214
+ self.min_lightness = min_lightness
215
+ self.max_lightness = max_lightness
216
+ self.min_alpha = min_alpha
217
+ self.max_alpha = max_alpha
218
+
219
+ self.min_value = min_value
220
+ self.max_value = max_value
221
+
222
+ self.scale_type = scale_type
223
+ self.scale_function = scale_function
224
+
225
+
226
+ def calc_color(self, value: float, min_value: float = None, max_value: float = None):
227
+ """
228
+ Maps a scalar value to an RGBA color using HSL color space.
229
+
230
+ Args:
231
+ value (float): The scalar value to map.
232
+ min_value (float): The minimum value of the range (if self.min_value is not None, it will be used).
233
+ max_value (float): The maximum value of the range (if self.max_value is not None, it will be used).
234
+
235
+ Returns:
236
+ color (tuple): A tuple representing the RGBA color (r, g, b, a).
237
+ """
238
+
239
+ # Use instance min_value and max_value if not provided
240
+ if self.min_value is not None and self.max_value is not None:
241
+ min_value = self.min_value
242
+ max_value = self.max_value
243
+
244
+ # Normalize value to [0, 1]
245
+ t = (value - min_value) / (max_value - min_value)
246
+ t = np.clip(t, 0.0, 1.0)
247
+
248
+ # Interpolate HSL components
249
+ if self.scale_type == ScaleType.LOGARITHMIC:
250
+ t = np.log(1 + 9 * t) / np.log(10)
251
+ elif self.scale_type == ScaleType.EXPONENTIAL:
252
+ t = (10 ** t - 1) / 9
253
+ elif self.scale_type == ScaleType.CUSTOM and self.scale_function is not None:
254
+ t = self.scale_function(t)
255
+
256
+ hue = self.min_hue + t * (self.max_hue - self.min_hue)
257
+ saturation = self.min_saturation + t * (self.max_saturation - self.min_saturation)
258
+ lightness = self.min_lightness + t * (self.max_lightness - self.min_lightness)
259
+ alpha = self.min_alpha + t * (self.max_alpha - self.min_alpha)
260
+
261
+
262
+ return hsl_to_rgba(hue / 360.0, saturation, lightness, alpha)
263
+
264
+ def map_array(self, values: np.ndarray, min_value: float = None, max_value: float = None):
265
+ """
266
+ Maps a NumPy array of scalar values to an array of RGBA colors.
267
+
268
+ Args:
269
+ values (np.ndarray): A NumPy array of scalar values.
270
+ min_value (float): The minimum value of the range.
271
+ max_value (float): The maximum value of the range.
272
+
273
+ Returns:
274
+ colors (np.ndarray): A NumPy array of RGBA colors corresponding to the input values.
275
+ """
276
+
277
+ diff = max_value - min_value
278
+ if diff == 0: diff = 1.0 # Avoid division by zero when all values are the same
279
+
280
+ with np.errstate(divide='ignore', over='ignore', invalid='ignore'):
281
+ # Normalize value to [0, 1]
282
+ t = (values - min_value) / diff
283
+ t = np.clip(t, 0.0, 1.0)
284
+
285
+ if self.scale_type == ScaleType.LOGARITHMIC:
286
+ t = np.log10(1 + 9 * t)
287
+ elif self.scale_type == ScaleType.EXPONENTIAL:
288
+ t = (np.power(10.0, t) - 1) / 9.0
289
+ elif self.scale_type == ScaleType.CUSTOM and self.scale_function is not None:
290
+ try:
291
+ # The function can take list/array inputs and return list/array outputs (vectorized)
292
+ t = self.scale_function(t)
293
+ except Exception:
294
+ # The function is a regular Python function (for each point individually)
295
+ f = np.vectorize(self.scale_function)
296
+ t = f(t)
297
+
298
+ hue = self.min_hue + t * (self.max_hue - self.min_hue)
299
+ saturation = self.min_saturation + t * (self.max_saturation - self.min_saturation)
300
+ lightness = self.min_lightness + t * (self.max_lightness - self.min_lightness)
301
+ alpha = self.min_alpha + t * (self.max_alpha - self.min_alpha)
302
+
303
+ r, g, b = hsl_to_rgb_vectorized(hue, saturation, lightness)
304
+
305
+ return np.column_stack((r, g, b, alpha)).astype(np.float32)
306
+
307
+
@@ -0,0 +1,105 @@
1
+ from enum import Enum, IntEnum, auto
2
+
3
+ class Align(Enum):
4
+ """Specifies the anchoring or alignment behavior for UI elements and spatial bounding boxes.
5
+
6
+ Attributes:
7
+ LEFT_TOP: Anchored to the top-left corner.
8
+ RIGHT_TOP: Anchored to the top-right corner.
9
+ LEFT_BOTTOM: Anchored to the bottom-left corner.
10
+ RIGHT_BOTTOM: Anchored to the bottom-right corner.
11
+ CENTER: Centered both horizontally and vertically.
12
+ """
13
+
14
+ LEFT_TOP = auto()
15
+ RIGHT_TOP = auto()
16
+ LEFT_BOTTOM = auto()
17
+ RIGHT_BOTTOM = auto()
18
+ CENTER = auto()
19
+
20
+ class Property(Enum):
21
+ """Defines the mathematical or physical property to be evaluated and visualized.
22
+
23
+ Used primarily for coloring mappings, filtering, or data probe measurements
24
+ within vector fields and particle systems.
25
+
26
+ Attributes:
27
+ VELOCITY: The magnitude (length) of the vector.
28
+ ANGLE: The angle between the field vector and the base vector (usually equal to [1, 0] by default).
29
+ DIVERGENCE: The rate at which the field acts as a source or sink.
30
+ CURL: The macroscopic rotation or vorticity of the field.
31
+ COMPONENT_X: The horizontal component of the vector.
32
+ COMPONENT_Y: The vertical component of the vector.
33
+ JACOBIAN: The determinant of the Jacobian matrix.
34
+ OKUBO_WEISS: The Okubo-Weiss parameter used for vortex identification.
35
+ CONVECTIVE_ACCELERATION: The convective term of the material derivative.
36
+ CUSTOM: A user-defined scalar function evaluated by the math engine.
37
+ """
38
+
39
+ VELOCITY = auto()
40
+ ANGLE = auto()
41
+ DIVERGENCE = auto()
42
+ CURL = auto()
43
+ COMPONENT_X = auto()
44
+ COMPONENT_Y = auto()
45
+ JACOBIAN = auto()
46
+ OKUBO_WEISS = auto()
47
+ CONVECTIVE_ACCELERATION = auto()
48
+ CUSTOM = auto()
49
+
50
+
51
+
52
+ class ArrowStyle(IntEnum):
53
+ """Determines the visual shape and rendering style of arrowheads in vector fields.
54
+
55
+ Attributes:
56
+ OPEN: Simple, unclosed line segments forming a 'V' shape.
57
+ TRIANGLE_TIGHT: A filled triangle with a narrow base.
58
+ TRIANGLE_WIDE: A filled triangle with a broad base.
59
+ HARPOON: A half-arrowhead with only one side drawn.
60
+ CURVED: Arrowhead wings with a slight inner curve.
61
+ BLOCK: A solid, rectangular block style.
62
+ """
63
+ OPEN = 0
64
+ TRIANGLE_TIGHT = 1
65
+ TRIANGLE_WIDE = 2
66
+ HARPOON = 3
67
+ CURVED = 4
68
+ BLOCK = 5
69
+
70
+
71
+ class ScaleType(Enum):
72
+ """Dictates the mathematical scaling function applied during data mapping.
73
+
74
+ Controls how scalar values are normalized before being mapped to a color gradient
75
+ or determining physical attributes like particles speed.
76
+
77
+ Attributes:
78
+ LINEAR: Direct proportional scaling.
79
+ LOGARITHMIC: Base-10 logarithmic scaling, useful for wide-ranging data.
80
+ EXPONENTIAL: Exponential scaling, highlighting peak values.
81
+ CUSTOM: A user-provided mapping function.
82
+ """
83
+ LINEAR = auto()
84
+ LOGARITHMIC = auto()
85
+ EXPONENTIAL = auto()
86
+ CUSTOM = auto()
87
+
88
+
89
+ class FieldMode(IntEnum):
90
+ """Controls how a vector field scales and positions itself relative to the viewport.
91
+
92
+ Attributes:
93
+ SCREEN_FIXED: Anchored strictly to UI pixel coordinates. Ignores camera movement.
94
+ WORLD_FIXED: Anchored to the mathematical world space. Arrow density is static.
95
+ ZOOM_ADAPTIVE: Arrows change length dynamically based on the camera's zoom level.
96
+ WORLD_DENSITY_ADAPTIVE: Arrow density changes based on the mathematical region size.
97
+ ZOOM_DENSITY_ADAPTIVE: Arrow density automatically recalculates based on camera zoom.
98
+ """
99
+ SCREEN_FIXED = 0
100
+ WORLD_FIXED = 1
101
+ ZOOM_ADAPTIVE = 2
102
+ WORLD_DENSITY_ADAPTIVE = 3
103
+ ZOOM_DENSITY_ADAPTIVE = 4
104
+
105
+