0.1.26
Loading...
Searching...
No Matches
datatypes.py
Go to the documentation of this file.
1"""
2Validation for PyHelios DataTypes (vec2, vec3, vec4, colors, etc.)
3
4Provides comprehensive validation for all geometric data types,
5ensuring finite values and appropriate ranges before C++ operations.
6"""
7
8from typing import Any
9from .core import is_finite_numeric
10from .exceptions import ValidationError, create_validation_error
11
12
13def _expected_vector_type(expected_dims: int):
14 """Return the concrete vector class for a dimension count, or None if unknown.
15
16 Imported lazily to avoid a circular import (DataTypes imports validation).
17 """
18 from ..wrappers.DataTypes import vec2, vec3, vec4
19 return {2: vec2, 3: vec3, 4: vec4}.get(expected_dims)
20
21
22def validate_color_component(value: float, component_name: str, param_name: str, function_name: str = None):
23 """Validate a color component is in valid range [0,1]."""
24 if not is_finite_numeric(value):
25 raise create_validation_error(
26 f"Color component {component_name} must be a finite number, got {value} ({type(value).__name__})",
27 param_name=f"{param_name}.{component_name}",
28 function_name=function_name,
29 expected_type="finite number in range [0,1]",
30 actual_value=value,
31 suggestion="Color components must be normalized values between 0 and 1."
32 )
33
34 if not (0.0 <= value <= 1.0):
35 raise create_validation_error(
36 f"Color component {component_name}={value} is outside valid range [0,1]",
37 param_name=f"{param_name}.{component_name}",
38 function_name=function_name,
39 expected_type="number in range [0,1]",
40 actual_value=value,
41 suggestion="Color components must be normalized values between 0 and 1."
42 )
43
44
45def validate_rgb_color(color: Any, param_name: str = "color", function_name: str = None):
46 """
47 Validate RGBcolor has finite values in [0,1] range.
48
49 Args:
50 color: RGBcolor object to validate
51 param_name: Parameter name for error messages
52 function_name: Function name for error messages
53
54 Raises:
55 ValidationError: If color is invalid or components out of range
56 """
57 if color is None:
58 return
59
60 # Check the concrete type first. RGBAcolor also has .r/.g/.b, so an attribute
61 # check alone would accept it here and silently discard the alpha channel.
62 from ..wrappers.DataTypes import RGBcolor
63 if not isinstance(color, RGBcolor):
64 raise create_validation_error(
65 f"Parameter must be an RGBcolor, got {type(color).__name__}",
66 param_name=param_name,
67 function_name=function_name,
68 expected_type="RGBcolor",
69 actual_value=color,
70 suggestion="Use RGBcolor(r, g, b) where r, g, b are values between 0 and 1."
71 )
72
73 # Check if it has the expected attributes
74 if not hasattr(color, 'r') or not hasattr(color, 'g') or not hasattr(color, 'b'):
75 raise create_validation_error(
76 f"Parameter must be an RGBcolor object with .r, .g, .b attributes",
77 param_name=param_name,
78 function_name=function_name,
79 expected_type="RGBcolor",
80 actual_value=color,
81 suggestion="Use RGBcolor(r, g, b) where r, g, b are values between 0 and 1."
82 )
83
84 validate_color_component(color.r, 'r', param_name, function_name)
85 validate_color_component(color.g, 'g', param_name, function_name)
86 validate_color_component(color.b, 'b', param_name, function_name)
87
88
89def validate_rgba_color(color: Any, param_name: str = "color", function_name: str = None):
90 """
91 Validate RGBAcolor has finite values in [0,1] range.
92
93 Args:
94 color: RGBAcolor object to validate
95 param_name: Parameter name for error messages
96 function_name: Function name for error messages
97
98 Raises:
99 ValidationError: If color is invalid or components out of range
100 """
101 if color is None:
102 return
103
104 # Check if it has the expected attributes
105 if not hasattr(color, 'r') or not hasattr(color, 'g') or not hasattr(color, 'b') or not hasattr(color, 'a'):
106 raise create_validation_error(
107 f"Parameter must be an RGBAcolor object with .r, .g, .b, .a attributes",
108 param_name=param_name,
109 function_name=function_name,
110 expected_type="RGBAcolor",
111 actual_value=color,
112 suggestion="Use RGBAcolor(r, g, b, a) where all values are between 0 and 1."
113 )
114
115 validate_color_component(color.r, 'r', param_name, function_name)
116 validate_color_component(color.g, 'g', param_name, function_name)
117 validate_color_component(color.b, 'b', param_name, function_name)
118 validate_color_component(color.a, 'a', param_name, function_name)
119
120
121def validate_vector_finite(vector: Any, param_name: str = "vector", expected_dims: int = 3, function_name: str = None):
122 """
123 Validate vector has finite components.
124
125 Args:
126 vector: Vector object to validate (vec2, vec3, vec4, etc.)
127 param_name: Parameter name for error messages
128 expected_dims: Expected number of dimensions
129 function_name: Function name for error messages
130
131 Raises:
132 ValidationError: If vector is invalid or has non-finite components
133 """
134 if vector is None:
135 return
136
137 # Get the expected attribute names based on dimensions
138 attrs = ['x', 'y', 'z', 'w'][:expected_dims]
139
140 # Check the concrete type first. Attribute checks alone cannot distinguish
141 # these types: a vec4 has .x/.y/.z, so it would satisfy a vec3 check and then
142 # silently reach C++ as a wrong-length buffer (to_list() returns 4 elements).
143 expected_type = _expected_vector_type(expected_dims)
144 if expected_type is not None and not isinstance(vector, expected_type):
145 raise create_validation_error(
146 f"Parameter must be a vec{expected_dims}, got {type(vector).__name__}",
147 param_name=param_name,
148 function_name=function_name,
149 expected_type=f"vec{expected_dims}",
150 actual_value=vector,
151 suggestion=f"Use vec{expected_dims}({', '.join(attrs)}) to construct this parameter."
152 )
153
154 # Check if it has the expected attributes
155 for attr in attrs:
156 if not hasattr(vector, attr):
157 raise create_validation_error(
158 f"Parameter must be a vector with {'.'.join(attrs)} attributes",
159 param_name=param_name,
160 function_name=function_name,
161 expected_type=f"vec{expected_dims}",
162 actual_value=vector,
163 suggestion=f"Use vec{expected_dims}() constructor or provide object with {'.'.join(attrs)} attributes."
164 )
165
166 value = getattr(vector, attr)
167 if not is_finite_numeric(value):
168 raise create_validation_error(
169 f"Vector component {attr} must be a finite number, got {value} ({type(value).__name__})",
170 param_name=f"{param_name}.{attr}",
171 function_name=function_name,
172 expected_type="finite number",
173 actual_value=value,
174 suggestion="Ensure all vector components are finite numbers (not NaN or infinity)."
175 )
176
177
178def validate_vec2(vector: Any, param_name: str = "vector", function_name: str = None):
179 """
180 Validate vec2 has finite x,y components.
181
182 Args:
183 vector: vec2 object to validate
184 param_name: Parameter name for error messages
185 function_name: Function name for error messages
186
187 Returns:
188 The validated vec2 object
189 """
190 validate_vector_finite(vector, param_name, expected_dims=2, function_name=function_name)
191 return vector
192
193
194def validate_vec3(vector: Any, param_name: str = "vector", function_name: str = None):
195 """
196 Validate vec3 has finite x,y,z components.
197
198 Args:
199 vector: vec3 object to validate
200 param_name: Parameter name for error messages
201 function_name: Function name for error messages
202
203 Returns:
204 The validated vec3 object
205 """
206 validate_vector_finite(vector, param_name, expected_dims=3, function_name=function_name)
207 return vector
208
209
210def validate_vec4(vector: Any, param_name: str = "vector", function_name: str = None):
211 """
212 Validate vec4 has finite x,y,z,w components.
213
214 Args:
215 vector: vec4 object to validate
216 param_name: Parameter name for error messages
217 function_name: Function name for error messages
218 """
219 validate_vector_finite(vector, param_name, expected_dims=4, function_name=function_name)
221
222def validate_spherical_coord(coord: Any, param_name: str = "coordinate", function_name: str = None):
223 """
224 Validate SphericalCoord has valid values.
225
226 Args:
227 coord: SphericalCoord object to validate
228 param_name: Parameter name for error messages
229 function_name: Function name for error messages
230
231 Raises:
232 ValidationError: If coordinate has invalid values
233 """
234 if coord is None:
235 return
236
237 if not hasattr(coord, 'radius') or not hasattr(coord, 'elevation') or not hasattr(coord, 'azimuth'):
238 raise create_validation_error(
239 f"Parameter must be a SphericalCoord with .radius, .elevation, .azimuth attributes",
240 param_name=param_name,
241 function_name=function_name,
242 expected_type="SphericalCoord",
243 actual_value=coord,
244 suggestion="Use SphericalCoord(radius, elevation, azimuth) constructor."
245 )
246
247 # Validate radius is positive
248 if not is_finite_numeric(coord.radius) or coord.radius <= 0:
249 raise create_validation_error(
250 f"SphericalCoord radius must be a positive finite number, got {coord.radius}",
251 param_name=f"{param_name}.radius",
252 function_name=function_name,
253 expected_type="positive finite number",
254 actual_value=coord.radius,
255 suggestion="Radius must be greater than 0."
256 )
257
258 # Elevation and azimuth can be any finite values (angles wrap around)
259 if not is_finite_numeric(coord.elevation):
260 raise create_validation_error(
261 f"SphericalCoord elevation must be a finite number, got {coord.elevation} ({type(coord.elevation).__name__})",
262 param_name=f"{param_name}.elevation",
263 function_name=function_name,
264 expected_type="finite number",
265 actual_value=coord.elevation,
266 suggestion="Elevation angle must be a finite number (not NaN or infinity)."
267 )
268
269 if not is_finite_numeric(coord.azimuth):
270 raise create_validation_error(
271 f"SphericalCoord azimuth must be a finite number, got {coord.azimuth} ({type(coord.azimuth).__name__})",
272 param_name=f"{param_name}.azimuth",
273 function_name=function_name,
274 expected_type="finite number",
275 actual_value=coord.azimuth,
276 suggestion="Azimuth angle must be a finite number (not NaN or infinity)."
277 )
278
279
280def validate_integer_vector(vector: Any, param_name: str = "vector", expected_dims: int = 3, function_name: str = None):
281 """
282 Validate integer vector (int2, int3, int4) has valid integer components.
283
284 Args:
285 vector: Integer vector object to validate
286 param_name: Parameter name for error messages
287 expected_dims: Expected number of dimensions
288 function_name: Function name for error messages
289
290 Raises:
291 ValidationError: If vector has invalid or non-integer components
292 """
293 if vector is None:
294 return
295
296 # Get the expected attribute names based on dimensions
297 attrs = ['x', 'y', 'z', 'w'][:expected_dims]
298
299 # Check if it has the expected attributes
300 for attr in attrs:
301 if not hasattr(vector, attr):
302 raise create_validation_error(
303 f"Parameter must be an integer vector with {'.'.join(attrs)} attributes",
304 param_name=param_name,
305 function_name=function_name,
306 expected_type=f"int{expected_dims}",
307 actual_value=vector,
308 suggestion=f"Use int{expected_dims}() constructor or provide object with {'.'.join(attrs)} attributes."
309 )
310
311 value = getattr(vector, attr)
312 if not isinstance(value, int):
313 raise create_validation_error(
314 f"Integer vector component {attr} must be an integer, got {value} ({type(value).__name__})",
315 param_name=f"{param_name}.{attr}",
316 function_name=function_name,
317 expected_type="integer",
318 actual_value=value,
319 suggestion="Ensure all vector components are integers."
320 )
321
322
323def validate_int2(vector: Any, param_name: str = "vector", function_name: str = None):
324 """
325 Validate int2 has valid integer x,y components.
326
327 Args:
328 vector: int2 object to validate
329 param_name: Parameter name for error messages
330 function_name: Function name for error messages
331
332 Returns:
333 The validated vector
334 """
335 validate_integer_vector(vector, param_name, expected_dims=2, function_name=function_name)
336 return vector
337
338
339def validate_int3(vector: Any, param_name: str = "vector", function_name: str = None):
340 """
341 Validate int3 has valid integer x,y,z components.
342
343 Args:
344 vector: int3 object to validate
345 param_name: Parameter name for error messages
346 function_name: Function name for error messages
347 """
348 validate_integer_vector(vector, param_name, expected_dims=3, function_name=function_name)
350
351def validate_int4(vector: Any, param_name: str = "vector", function_name: str = None):
352 """
353 Validate int4 has valid integer x,y,z,w components.
354
355 Args:
356 vector: int4 object to validate
357 param_name: Parameter name for error messages
358 function_name: Function name for error messages
359 """
360 validate_integer_vector(vector, param_name, expected_dims=4, function_name=function_name)
_expected_vector_type(int expected_dims)
Return the concrete vector class for a dimension count, or None if unknown.
Definition datatypes.py:19
validate_vector_finite(Any vector, str param_name="vector", int expected_dims=3, str function_name=None)
Validate vector has finite components.
Definition datatypes.py:135
validate_integer_vector(Any vector, str param_name="vector", int expected_dims=3, str function_name=None)
Validate integer vector (int2, int3, int4) has valid integer components.
Definition datatypes.py:294
validate_vec4(Any vector, str param_name="vector", str function_name=None)
Validate vec4 has finite x,y,z,w components.
Definition datatypes.py:220
validate_vec3(Any vector, str param_name="vector", str function_name=None)
Validate vec3 has finite x,y,z components.
Definition datatypes.py:207
validate_int4(Any vector, str param_name="vector", str function_name=None)
Validate int4 has valid integer x,y,z,w components.
Definition datatypes.py:361
validate_int3(Any vector, str param_name="vector", str function_name=None)
Validate int3 has valid integer x,y,z components.
Definition datatypes.py:349
validate_rgba_color(Any color, str param_name="color", str function_name=None)
Validate RGBAcolor has finite values in [0,1] range.
Definition datatypes.py:102
validate_int2(Any vector, str param_name="vector", str function_name=None)
Validate int2 has valid integer x,y components.
Definition datatypes.py:336
validate_vec2(Any vector, str param_name="vector", str function_name=None)
Validate vec2 has finite x,y components.
Definition datatypes.py:191
validate_rgb_color(Any color, str param_name="color", str function_name=None)
Validate RGBcolor has finite values in [0,1] range.
Definition datatypes.py:58
validate_spherical_coord(Any coord, str param_name="coordinate", str function_name=None)
Validate SphericalCoord has valid values.
Definition datatypes.py:235
validate_color_component(float value, str component_name, str param_name, str function_name=None)
Validate a color component is in valid range [0,1].
Definition datatypes.py:25