0.1.39
Loading...
Searching...
No Matches
DataTypes.py
Go to the documentation of this file.
1import ctypes
2import math
3from typing import Any, List
4from enum import IntEnum
5
6
7class PrimitiveType(IntEnum):
8 """Helios primitive type enumeration."""
9 Patch = 0
10 Triangle = 1
11 Disk = 2
12 Tile = 3
13 Sphere = 4
14 Tube = 5
15 Box = 6
16 Cone = 7
17 Polymesh = 8
18
19class VertexNormalSource(IntEnum):
20 """Provenance of a polymesh object's per-vertex normals (helios-core 1.3.83)."""
21 NONE = 0
22 AUTHORED = 1
23 COMPUTED = 2
24
25class VertexWeldMode(IntEnum):
26 """Granularity at which coincident vertices are treated as one shared vertex (helios-core 1.3.84).
27
28 Object types with no distinguished axis -- a Polymesh, a Tile -- report the same
29 topology for either mode.
30 """
31 #: Treat every coincident vertex of the object as one shared vertex.
32 WELD_FULL = 0
33 #: Weld only within a cross-section, leaving vertices at the same cross-sectional
34 #: position on different segments distinct. For a Tube or Sphere this preserves
35 #: variation along the axis; identical to WELD_FULL for objects with no such axis.
36 WELD_CROSS_SECTION_ONLY = 1
37
38class int2(ctypes.Structure):
39 _fields_ = [('x', ctypes.c_int32), ('y', ctypes.c_int32)]
40
41 def __repr__(self) -> str:
42 return f'int2({self.x}, {self.y})'
43
44 def __str__(self) -> str:
45 return f'int2({self.x}, {self.y})'
46
47 def __new__(cls, x=None, y=None):
48 """Create instance - only pass cls to prevent TypeError on Windows."""
49 return ctypes.Structure.__new__(cls)
50
51 def __init__(self, x:int=0, y:int=0):
52 # Validate and set fields - do not call super().__init__()
53 if not isinstance(x, int):
54 raise ValueError(f"int2.x must be an integer, got {type(x).__name__}: {x}")
55 if not isinstance(y, int):
56 raise ValueError(f"int2.y must be an integer, got {type(y).__name__}: {y}")
57
58 self.x = x
59 self.y = y
60
61 def from_list(self, input_list:List[int]):
62 self.x = input_list[0]
63 self.y = input_list[1]
65 def to_list(self) -> List[int]:
66 return [self.x, self.y]
67
69
70class int3(ctypes.Structure):
71 _fields_ = [('x', ctypes.c_int32), ('y', ctypes.c_int32), ('z', ctypes.c_int32)]
72
73 def __repr__(self) -> str:
74 return f'int3({self.x}, {self.y}, {self.z})'
75
76 def __str__(self) -> str:
77 return f'int3({self.x}, {self.y}, {self.z})'
78
79 def __new__(cls, x=None, y=None, z=None):
80 """Create instance - only pass cls to prevent TypeError on Windows."""
81 return ctypes.Structure.__new__(cls)
83 def __init__(self, x:int=0, y:int=0, z:int=0):
84 # Validate and set fields - do not call super().__init__()
85 if not isinstance(x, int):
86 raise ValueError(f"int3.x must be an integer, got {type(x).__name__}: {x}")
87 if not isinstance(y, int):
88 raise ValueError(f"int3.y must be an integer, got {type(y).__name__}: {y}")
89 if not isinstance(z, int):
90 raise ValueError(f"int3.z must be an integer, got {type(z).__name__}: {z}")
91
92 self.x = x
93 self.y = y
94 self.z = z
95
96 def from_list(self, input_list:List[int]):
97 self.x = input_list[0]
98 self.y = input_list[1]
99 self.z = input_list[2]
101 def to_list(self) -> List[int]:
102 return [self.x, self.y, self.z]
103
104
105
106class int4(ctypes.Structure):
107 _fields_ = [('x', ctypes.c_int32), ('y', ctypes.c_int32), ('z', ctypes.c_int32), ('w', ctypes.c_int32)]
108
109 def __repr__(self) -> str:
110 return f'int4({self.x}, {self.y}, {self.z}, {self.w})'
111
112 def __str__(self) -> str:
113 return f'int4({self.x}, {self.y}, {self.z}, {self.w})'
114
115 def __new__(cls, x=None, y=None, z=None, w=None):
116 """Create instance - only pass cls to prevent TypeError on Windows."""
117 return ctypes.Structure.__new__(cls)
119 def __init__(self, x:int=0, y:int=0, z:int=0, w:int=0):
120 # Validate and set fields - do not call super().__init__()
121 if not isinstance(x, int):
122 raise ValueError(f"int4.x must be an integer, got {type(x).__name__}: {x}")
123 if not isinstance(y, int):
124 raise ValueError(f"int4.y must be an integer, got {type(y).__name__}: {y}")
125 if not isinstance(z, int):
126 raise ValueError(f"int4.z must be an integer, got {type(z).__name__}: {z}")
127 if not isinstance(w, int):
128 raise ValueError(f"int4.w must be an integer, got {type(w).__name__}: {w}")
129
130 self.x = x
131 self.y = y
132 self.z = z
133 self.w = w
134
135 def from_list(self, input_list:List[int]):
136 self.x = input_list[0]
137 self.y = input_list[1]
138 self.z = input_list[2]
139 self.w = input_list[3]
141 def to_list(self) -> List[int]:
142 return [self.x, self.y, self.z, self.w]
143
145
146class vec2(ctypes.Structure):
147 _fields_ = [('x', ctypes.c_float), ('y', ctypes.c_float)]
148
149 def __repr__(self) -> str:
150 return f'vec2({self.x}, {self.y})'
151
152 def __str__(self) -> str:
153 return f'vec2({self.x}, {self.y})'
154
155 def __new__(cls, x=None, y=None):
156 """Create instance - only pass cls to prevent TypeError on Windows."""
157 return ctypes.Structure.__new__(cls)
158
159 def __init__(self, x:float=0, y:float=0):
160 # Validate and set fields - do not call super().__init__()
161 if not self._is_finite_numeric(x):
162 raise ValueError(f"vec2.x must be a finite number, got {type(x).__name__}: {x}. "
163 f"Vector components must be finite (not NaN or infinity).")
164 if not self._is_finite_numeric(y):
165 raise ValueError(f"vec2.y must be a finite number, got {type(y).__name__}: {y}. "
166 f"Vector components must be finite (not NaN or infinity).")
167
168 self.x = float(x)
169 self.y = float(y)
170
171 def from_list(self, input_list:List[float]):
172 self.x = input_list[0]
173 self.y = input_list[1]
174
175 def to_list(self) -> List[float]:
176 return [self.x, self.y]
177
178 def magnitude(self) -> float:
179 """Return the magnitude (length) of the vector."""
180 import math
181 return math.sqrt(self.x * self.x + self.y * self.y)
182
183 def normalize(self) -> 'vec2':
184 """Return a normalized copy of this vector (unit length)."""
185 mag = self.magnitude()
186 if mag == 0:
187 return vec2(0, 0)
188 return vec2(self.x / mag, self.y / mag)
189
190 @staticmethod
191 def _is_finite_numeric(value) -> bool:
192 """Check if value is a finite number (not NaN or inf)."""
193 try:
194 float_value = float(value)
195 return math.isfinite(float_value)
196 except (ValueError, TypeError, OverflowError):
197 return False
198
199class vec3(ctypes.Structure):
200 _fields_ = [('x', ctypes.c_float), ('y', ctypes.c_float), ('z', ctypes.c_float)]
201
202 def __repr__(self) -> str:
203 return f'vec3({self.x}, {self.y}, {self.z})'
204
205 def __str__(self) -> str:
206 return f'vec3({self.x}, {self.y}, {self.z})'
207
208 def __new__(cls, x=None, y=None, z=None):
209 """Create instance - only pass cls to prevent TypeError on Windows."""
210 return ctypes.Structure.__new__(cls)
211
212 def __init__(self, x:float=0, y:float=0, z:float=0):
213 # Validate and set fields - do not call super().__init__()
214 if not self._is_finite_numeric(x):
215 raise ValueError(f"vec3.x must be a finite number, got {type(x).__name__}: {x}. "
216 f"Vector components must be finite (not NaN or infinity).")
217 if not self._is_finite_numeric(y):
218 raise ValueError(f"vec3.y must be a finite number, got {type(y).__name__}: {y}. "
219 f"Vector components must be finite (not NaN or infinity).")
220 if not self._is_finite_numeric(z):
221 raise ValueError(f"vec3.z must be a finite number, got {type(z).__name__}: {z}. "
222 f"Vector components must be finite (not NaN or infinity).")
223
224 self.x = float(x)
225 self.y = float(y)
226 self.z = float(z)
227
228 def from_list(self, input_list:List[float]):
229 self.x = input_list[0]
230 self.y = input_list[1]
231 self.z = input_list[2]
232
233 def to_list(self) -> List[float]:
234 return [self.x, self.y, self.z]
235
236 def to_tuple(self) -> tuple:
237 return (self.x, self.y, self.z)
238
239 def magnitude(self) -> float:
240 """Return the magnitude (length) of the vector."""
241 import math
242 return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
244 def normalize(self) -> 'vec3':
245 """Return a normalized copy of this vector (unit length)."""
246 mag = self.magnitude()
247 if mag == 0:
248 return vec3(0, 0, 0)
249 return vec3(self.x / mag, self.y / mag, self.z / mag)
251 @staticmethod
252 def _is_finite_numeric(value) -> bool:
253 """Check if value is a finite number (not NaN or inf)."""
254 try:
255 float_value = float(value)
256 return math.isfinite(float_value)
257 except (ValueError, TypeError, OverflowError):
258 return False
259
260
261class vec4(ctypes.Structure):
262 _fields_ = [('x', ctypes.c_float), ('y', ctypes.c_float), ('z', ctypes.c_float), ('w', ctypes.c_float)]
263
264 def __repr__(self) -> str:
265 return f'vec4({self.x}, {self.y}, {self.z}, {self.w})'
266
267 def __str__(self) -> str:
268 return f'vec4({self.x}, {self.y}, {self.z}, {self.w})'
269
270 def __new__(cls, x=None, y=None, z=None, w=None):
271 """Create instance - only pass cls to prevent TypeError on Windows."""
272 return ctypes.Structure.__new__(cls)
273
274 def __init__(self, x:float=0, y:float=0, z:float=0, w:float=0):
275 # Validate and set fields - do not call super().__init__()
276 if not self._is_finite_numeric(x):
277 raise ValueError(f"vec4.x must be a finite number, got {type(x).__name__}: {x}. "
278 f"Vector components must be finite (not NaN or infinity).")
279 if not self._is_finite_numeric(y):
280 raise ValueError(f"vec4.y must be a finite number, got {type(y).__name__}: {y}. "
281 f"Vector components must be finite (not NaN or infinity).")
282 if not self._is_finite_numeric(z):
283 raise ValueError(f"vec4.z must be a finite number, got {type(z).__name__}: {z}. "
284 f"Vector components must be finite (not NaN or infinity).")
285 if not self._is_finite_numeric(w):
286 raise ValueError(f"vec4.w must be a finite number, got {type(w).__name__}: {w}. "
287 f"Vector components must be finite (not NaN or infinity).")
288
289 self.x = float(x)
290 self.y = float(y)
291 self.z = float(z)
292 self.w = float(w)
294 def from_list(self, input_list:List[float]):
295 self.x = input_list[0]
296 self.y = input_list[1]
297 self.z = input_list[2]
298 self.w = input_list[3]
299
300 def to_list(self) -> List[float]:
301 return [self.x, self.y, self.z, self.w]
302
303 @staticmethod
304 def _is_finite_numeric(value) -> bool:
305 """Check if value is a finite number (not NaN or inf)."""
306 try:
307 float_value = float(value)
308 return math.isfinite(float_value)
309 except (ValueError, TypeError, OverflowError):
310 return False
314class RGBcolor(ctypes.Structure):
315 _fields_ = [('r', ctypes.c_float), ('g', ctypes.c_float), ('b', ctypes.c_float)]
317 def __repr__(self) -> str:
318 return f'RGBcolor({self.r}, {self.g}, {self.b})'
319
320 def __str__(self) -> str:
321 return f'RGBcolor({self.r}, {self.g}, {self.b})'
323 def __new__(cls, r=None, g=None, b=None):
324 """Create instance - only pass cls to prevent TypeError on Windows."""
325 return ctypes.Structure.__new__(cls)
326
327 def __init__(self, r:float=0, g:float=0, b:float=0):
328 # Validate and set fields - do not call super().__init__()
329 self._validate_color_component(r, 'r')
330 self._validate_color_component(g, 'g')
331 self._validate_color_component(b, 'b')
332
333 self.r = float(r)
334 self.g = float(g)
335 self.b = float(b)
336
337 def from_list(self, input_list:List[float]):
338 self.r = input_list[0]
339 self.g = input_list[1]
340 self.b = input_list[2]
341
342 def to_list(self) -> List[float]:
343 return [self.r, self.g, self.b]
345 def scale(self, factor: float) -> 'RGBcolor':
346 """Return a scaled copy of this color, clamped to [0, 1]."""
347 return RGBcolor(
348 min(1.0, max(0.0, self.r * factor)),
349 min(1.0, max(0.0, self.g * factor)),
350 min(1.0, max(0.0, self.b * factor))
352
353 @staticmethod
354 def _is_finite_numeric(value) -> bool:
355 """Check if value is a finite number (not NaN or inf)."""
356 try:
357 float_value = float(value)
358 return math.isfinite(float_value)
359 except (ValueError, TypeError, OverflowError):
360 return False
362 @staticmethod
363 def _validate_color_component(value, component_name):
364 """Validate a color component is finite and in range [0,1]."""
365 if not RGBcolor._is_finite_numeric(value):
366 raise ValueError(f"RGBcolor.{component_name} must be a finite number, "
367 f"got {type(value).__name__}: {value}. "
368 f"Color components must be finite values between 0 and 1.")
370 if not (0.0 <= value <= 1.0):
371 raise ValueError(f"RGBcolor.{component_name}={value} is outside valid range [0,1]. "
372 f"Color components must be normalized values between 0 and 1.")
374
375
376
377class RGBAcolor(ctypes.Structure):
378 _fields_ = [('r', ctypes.c_float), ('g', ctypes.c_float), ('b', ctypes.c_float), ('a', ctypes.c_float)]
379
380 def __repr__(self) -> str:
381 return f'RGBAcolor({self.r}, {self.g}, {self.b}, {self.a})'
382
383 def __str__(self) -> str:
384 return f'RGBAcolor({self.r}, {self.g}, {self.b}, {self.a})'
385
386 def __new__(cls, r=None, g=None, b=None, a=None):
387 """Create instance - only pass cls to prevent TypeError on Windows."""
388 return ctypes.Structure.__new__(cls)
389
390 def __init__(self, r:float=0, g:float=0, b:float=0, a:float=0):
391 # Validate and set fields - do not call super().__init__()
392 self._validate_color_component(r, 'r')
393 self._validate_color_component(g, 'g')
394 self._validate_color_component(b, 'b')
396
397 self.r = float(r)
398 self.g = float(g)
399 self.b = float(b)
400 self.a = float(a)
401
402 def from_list(self, input_list:List[float]):
403 self.r = input_list[0]
404 self.g = input_list[1]
405 self.b = input_list[2]
406 self.a = input_list[3]
407
408 def to_list(self) -> List[float]:
409 return [self.r, self.g, self.b, self.a]
410
411 def scale(self, factor: float) -> 'RGBAcolor':
412 """Return a scaled copy of this color, clamped to [0, 1]. Alpha unchanged."""
413 return RGBAcolor(
414 min(1.0, max(0.0, self.r * factor)),
415 min(1.0, max(0.0, self.g * factor)),
416 min(1.0, max(0.0, self.b * factor)),
417 self.a # Alpha not scaled
418 )
419
420 @staticmethod
421 def _is_finite_numeric(value) -> bool:
422 """Check if value is a finite number (not NaN or inf)."""
423 try:
424 float_value = float(value)
425 return math.isfinite(float_value)
426 except (ValueError, TypeError, OverflowError):
427 return False
428
429 @staticmethod
430 def _validate_color_component(value, component_name):
431 """Validate a color component is finite and in range [0,1]."""
432 if not RGBAcolor._is_finite_numeric(value):
433 raise ValueError(f"RGBAcolor.{component_name} must be a finite number, "
434 f"got {type(value).__name__}: {value}. "
435 f"Color components must be finite values between 0 and 1.")
437 if not (0.0 <= value <= 1.0):
438 raise ValueError(f"RGBAcolor.{component_name}={value} is outside valid range [0,1]. "
439 f"Color components must be normalized values between 0 and 1.")
440
441
443class SphericalCoord(ctypes.Structure):
444 _fields_ = [
445 ('radius', ctypes.c_float),
446 ('elevation', ctypes.c_float),
447 ('zenith', ctypes.c_float),
448 ('azimuth', ctypes.c_float)
449 ]
450
451 def __repr__(self) -> str:
452 return f'SphericalCoord({self.radius}, {self.elevation}, {self.zenith}, {self.azimuth})'
453
454 def __str__(self) -> str:
455 return f'SphericalCoord({self.radius}, {self.elevation}, {self.zenith}, {self.azimuth})'
456
457 def __new__(cls, radius=None, elevation=None, azimuth=None):
458 """Create instance - only pass cls to prevent TypeError on Windows."""
459 return ctypes.Structure.__new__(cls)
460
461 def __init__(self, radius:float=1, elevation:float=0, azimuth:float=0):
462 """
463 Initialize SphericalCoord fields with validation.
464 Do not call super().__init__() for Windows compatibility.
465
466 Args:
467 radius: Radius (default: 1)
468 elevation: Elevation angle in radians (default: 0)
469 azimuth: Azimuthal angle in radians (default: 0)
470
471 Note: zenith is automatically computed as (Ï€/2 - elevation) to match C++ behavior
472 """
473 # Validate inputs
474 if not self._is_finite_numeric(radius) or radius <= 0:
475 raise ValueError(f"SphericalCoord.radius must be a positive finite number, "
476 f"got {type(radius).__name__}: {radius}. "
477 f"Radius must be greater than 0.")
478
479 if not self._is_finite_numeric(elevation):
480 raise ValueError(f"SphericalCoord.elevation must be a finite number, "
481 f"got {type(elevation).__name__}: {elevation}. "
482 f"Elevation angle must be finite (not NaN or infinity).")
483
484 if not self._is_finite_numeric(azimuth):
485 raise ValueError(f"SphericalCoord.azimuth must be a finite number, "
486 f"got {type(azimuth).__name__}: {azimuth}. "
487 f"Azimuth angle must be finite (not NaN or infinity).")
488
489 # Initialize fields
490 self.radius = float(radius)
491 self.elevation = float(elevation)
492 self.zenith = 0.5 * math.pi - elevation # zenith = π/2 - elevation (matches C++)
493 self.azimuth = float(azimuth)
494
495 def from_list(self, input_list:List[float]):
496 self.radius = input_list[0]
497 self.elevation = input_list[1]
498 self.zenith = input_list[2]
499 self.azimuth = input_list[3]
500
501 def to_list(self) -> List[float]:
502 return [self.radius, self.elevation, self.zenith, self.azimuth]
503
504 @staticmethod
505 def _is_finite_numeric(value) -> bool:
506 """Check if value is a finite number (not NaN or inf)."""
507 try:
508 float_value = float(value)
509 return math.isfinite(float_value)
510 except (ValueError, TypeError, OverflowError):
511 return False
512
514
515class AxisRotation(ctypes.Structure):
516 """
517 Axis rotation structure for specifying shoot orientation in PlantArchitecture.
518
519 Represents rotation using pitch, yaw, and roll angles in **radians**.
520 Use ``math.radians()`` to convert from degrees. Used to define the orientation of
521 shoots, stems, and branches during plant construction.
522 """
523 _fields_ = [
524 ('pitch', ctypes.c_float),
525 ('yaw', ctypes.c_float),
526 ('roll', ctypes.c_float)
527 ]
528
529 def __repr__(self) -> str:
530 return f'AxisRotation({self.pitch}, {self.yaw}, {self.roll})'
532 def __str__(self) -> str:
533 return f'AxisRotation({self.pitch}, {self.yaw}, {self.roll})'
535 def __new__(cls, pitch=None, yaw=None, roll=None):
536 """
537 Create AxisRotation instance.
538 Only pass cls to parent __new__ to prevent TypeError on Windows.
539 """
540 return ctypes.Structure.__new__(cls)
541
542 def __init__(self, pitch:float=0, yaw:float=0, roll:float=0):
543 """
544 Initialize AxisRotation fields with validation.
545 Do not call super().__init__() for Windows compatibility.
546
547 Args:
548 pitch: Pitch angle in radians (rotation about transverse axis)
549 yaw: Yaw angle in radians (rotation about vertical axis)
550 roll: Roll angle in radians (rotation about longitudinal axis)
551
552 Raises:
553 ValueError: If any angle value is not finite
554 """
555 # Validate finite numeric inputs
556 if not self._is_finite_numeric(pitch):
557 raise ValueError(f"AxisRotation.pitch must be a finite number, got {type(pitch).__name__}: {pitch}. "
558 f"Rotation angles must be finite (not NaN or infinity).")
559 if not self._is_finite_numeric(yaw):
560 raise ValueError(f"AxisRotation.yaw must be a finite number, got {type(yaw).__name__}: {yaw}. "
561 f"Rotation angles must be finite (not NaN or infinity).")
562 if not self._is_finite_numeric(roll):
563 raise ValueError(f"AxisRotation.roll must be a finite number, got {type(roll).__name__}: {roll}. "
564 f"Rotation angles must be finite (not NaN or infinity).")
566 self.pitch = float(pitch)
567 self.yaw = float(yaw)
568 self.roll = float(roll)
570 def from_list(self, input_list:List[float]):
571 """Initialize from list [pitch, yaw, roll]"""
572 if len(input_list) < 3:
573 raise ValueError("AxisRotation.from_list requires a list with at least 3 elements [pitch, yaw, roll]")
574 self.pitch = input_list[0]
575 self.yaw = input_list[1]
576 self.roll = input_list[2]
577
578 def to_list(self) -> List[float]:
579 """Convert to list [pitch, yaw, roll]"""
580 return [self.pitch, self.yaw, self.roll]
581
582 @staticmethod
583 def _is_finite_numeric(value) -> bool:
584 """Check if value is a finite number (not NaN or inf)."""
585 try:
586 float_value = float(value)
587 return math.isfinite(float_value)
588 except (ValueError, TypeError, OverflowError):
589 return False
590
591
592
593class AdaptiveTileRefinement(ctypes.Structure):
594 """
595 Parameters controlling the adaptive sub-patch refinement of an adaptive tile object.
596
597 Sub-patch edge length grows with distance from ``target``, from ``subpatch_size_min`` at the
598 target itself up to ``subpatch_size_max`` at the point of the tile farthest from it.
599 Refinement is performed by recursive quadtree subdivision, so the achieved sizes are the
600 requested sizes rounded onto a power-of-two ladder; both ends are typically within about 20%
601 of the request. Query what was actually achieved with
602 ``Context.getAdaptiveTileObjectSubpatchSizeRange()``.
603
604 Attributes:
605 target: Point of maximum refinement, in tile-local coordinates relative to the tile center
606 and before rotation is applied (default: (0, 0), the tile center). A target outside the
607 tile is permitted, but the finest requested sub-patch size will then not be reached
608 anywhere and helios issues a warning.
609 subpatch_size_min: Requested edge length of the finest sub-patches, which occur at the
610 target point (default: 0.05, i.e. 5 cm for a scene measured in meters).
611 subpatch_size_max: Requested edge length of the coarsest sub-patches, which occur farthest
612 from the target (default: 1.0). Must be no more than half the smaller tile dimension.
613 transition_exponent: Exponent controlling how rapidly sub-patch size grows with distance
614 from the target (default: 0.35, which suits a typical ground plane). Useful values run
615 from about 0.25 to 1. This does not change the achieved size range, but it is by far
616 the most consequential parameter for the sub-patch count -- on a 50 m tile refined from
617 2 m to 2 cm, 0.25 gives roughly 9 thousand sub-patches and 2 gives roughly 2 million.
618 Use ``Context.predictAdaptiveTileObjectSubpatchCount()`` to check before building.
619
620 Examples:
621 >>> refinement = AdaptiveTileRefinement(target=vec2(0, 0), subpatch_size_min=0.02,
622 ... subpatch_size_max=2.0)
623 >>> refinement.subpatch_size_min
624 0.02
625 """
626 _fields_ = [
627 ('target', vec2),
628 ('subpatch_size_min', ctypes.c_float),
629 ('subpatch_size_max', ctypes.c_float),
630 ('transition_exponent', ctypes.c_float)
631 ]
633 def __repr__(self) -> str:
634 return (f'AdaptiveTileRefinement(target=vec2({self.target.x}, {self.target.y}), '
635 f'subpatch_size_min={self.subpatch_size_min}, '
636 f'subpatch_size_max={self.subpatch_size_max}, '
637 f'transition_exponent={self.transition_exponent})')
638
639 def __str__(self) -> str:
640 return self.__repr__()
641
642 def __new__(cls, target=None, subpatch_size_min=None, subpatch_size_max=None,
643 transition_exponent=None):
644 """Create instance - only pass cls to prevent TypeError on Windows."""
645 return ctypes.Structure.__new__(cls)
646
647 def __init__(self, target: 'vec2' = None, subpatch_size_min: float = 0.05,
648 subpatch_size_max: float = 1.0, transition_exponent: float = 0.35):
649 """
650 Initialize AdaptiveTileRefinement fields with validation.
651 Do not call super().__init__() for Windows compatibility.
652
653 Defaults match helios::AdaptiveTileRefinement.
654
655 Raises:
656 ValueError: If target is not a vec2, if either size is not a positive finite number,
657 if subpatch_size_min exceeds subpatch_size_max, if their ratio exceeds the largest
658 supported value of 16777216, or if transition_exponent is not positive and finite.
659 """
660 if target is None:
661 target = vec2(0.0, 0.0)
662 if not isinstance(target, vec2):
663 raise ValueError(f"AdaptiveTileRefinement.target must be a vec2, "
664 f"got {type(target).__name__}: {target}")
665 if not self._is_finite_numeric(target.x) or not self._is_finite_numeric(target.y):
666 raise ValueError(f"AdaptiveTileRefinement.target must be finite, got {target}")
667
668 if not self._is_finite_numeric(subpatch_size_min) or float(subpatch_size_min) <= 0:
669 raise ValueError(f"AdaptiveTileRefinement.subpatch_size_min must be a positive finite "
670 f"number, got {type(subpatch_size_min).__name__}: {subpatch_size_min}")
671 if not self._is_finite_numeric(subpatch_size_max) or float(subpatch_size_max) <= 0:
672 raise ValueError(f"AdaptiveTileRefinement.subpatch_size_max must be a positive finite "
673 f"number, got {type(subpatch_size_max).__name__}: {subpatch_size_max}")
674 if float(subpatch_size_min) > float(subpatch_size_max):
675 raise ValueError(f"AdaptiveTileRefinement.subpatch_size_min of {subpatch_size_min} is "
676 f"greater than subpatch_size_max of {subpatch_size_max}.")
677 if float(subpatch_size_max) / float(subpatch_size_min) > 16777216.0:
678 raise ValueError(f"AdaptiveTileRefinement size ratio of "
679 f"{float(subpatch_size_max) / float(subpatch_size_min)} exceeds the "
680 f"largest supported ratio of 16777216. Increase subpatch_size_min or "
681 f"decrease subpatch_size_max.")
682 if not self._is_finite_numeric(transition_exponent) or float(transition_exponent) <= 0:
683 raise ValueError(f"AdaptiveTileRefinement.transition_exponent must be a positive "
684 f"finite number, got {type(transition_exponent).__name__}: "
685 f"{transition_exponent}")
686
687 self.target = target
688 self.subpatch_size_min = float(subpatch_size_min)
689 self.subpatch_size_max = float(subpatch_size_max)
690 self.transition_exponent = float(transition_exponent)
691
692 def from_list(self, input_list: List[float]):
693 """Initialize from list [target_x, target_y, subpatch_size_min, subpatch_size_max, transition_exponent]"""
694 if len(input_list) < 5:
695 raise ValueError("AdaptiveTileRefinement.from_list requires a list with at least 5 "
696 "elements [target_x, target_y, subpatch_size_min, subpatch_size_max, "
697 "transition_exponent]")
698 self.target = vec2(float(input_list[0]), float(input_list[1]))
699 self.subpatch_size_min = input_list[2]
700 self.subpatch_size_max = input_list[3]
701 self.transition_exponent = input_list[4]
702
703 def to_list(self) -> List[float]:
704 """
705 Convert to the flat 5-element form used across the C ABI.
706
707 Element order matches the order helios itself uses to serialize the struct to XML:
708 [target_x, target_y, subpatch_size_min, subpatch_size_max, transition_exponent]
709 """
710 return [self.target.x, self.target.y, self.subpatch_size_min,
712
713 @staticmethod
714 def _is_finite_numeric(value) -> bool:
715 """Check if value is a finite number (not NaN or inf)."""
716 try:
717 float_value = float(value)
718 return math.isfinite(float_value)
719 except (ValueError, TypeError, OverflowError):
720 return False
721
723# Factory functions to match C++ API
724def make_int2(x: int, y: int) -> int2:
725 """Make an int2 from two integers"""
726 return int2(x, y)
727
728def make_SphericalCoord(elevation_radians: float, azimuth_radians: float) -> SphericalCoord:
729 """
730 Make a SphericalCoord by specifying elevation and azimuth (C++ API compatibility).
731
732 Args:
733 elevation_radians: Elevation angle in radians
734 azimuth_radians: Azimuthal angle in radians
735
736 Returns:
737 SphericalCoord with radius=1, and automatically computed zenith
738 """
739 return SphericalCoord(radius=1, elevation=elevation_radians, azimuth=azimuth_radians)
740
741def make_int3(x: int, y: int, z: int) -> int3:
742 """Make an int3 from three integers"""
743 return int3(x, y, z)
744
745def make_int4(x: int, y: int, z: int, w: int) -> int4:
746 """Make an int4 from four integers"""
747 return int4(x, y, z, w)
748
749def make_vec2(x: float, y: float) -> vec2:
750 """Make a vec2 from two floats"""
751 return vec2(x, y)
753def make_vec3(x: float, y: float, z: float) -> vec3:
754 """Make a vec3 from three floats"""
755 return vec3(x, y, z)
756
757def make_vec4(x: float, y: float, z: float, w: float) -> vec4:
758 """Make a vec4 from four floats"""
759 return vec4(x, y, z, w)
760
761def make_RGBcolor(r: float, g: float, b: float) -> RGBcolor:
762 """Make an RGBcolor from three floats"""
763 return RGBcolor(r, g, b)
764
765def make_RGBAcolor(r: float, g: float, b: float, a: float) -> RGBAcolor:
766 """Make an RGBAcolor from four floats"""
767 return RGBAcolor(r, g, b, a)
768
769def make_AxisRotation(pitch: float, yaw: float, roll: float) -> AxisRotation:
770 """Make an AxisRotation from three angles in radians"""
771 return AxisRotation(pitch, yaw, roll)
772
774class Time(ctypes.Structure):
775 """Helios Time structure for representing time values."""
776 _fields_ = [('second', ctypes.c_int32), ('minute', ctypes.c_int32), ('hour', ctypes.c_int32)]
777
778 def __repr__(self) -> str:
779 return f'Time({self.hour:02d}:{self.minute:02d}:{self.second:02d})'
780
781 def __str__(self) -> str:
782 return f'{self.hour:02d}:{self.minute:02d}:{self.second:02d}'
783
784 def __new__(cls, hour=None, minute=None, second=None):
785 """Create instance - only pass cls to prevent TypeError on Windows."""
786 return ctypes.Structure.__new__(cls)
787
788 def __init__(self, hour: int = 0, minute: int = 0, second: int = 0):
789 """
790 Initialize Time fields with validation.
791 Do not call super().__init__() for Windows compatibility.
792
793 Args:
794 hour: Hour (0-23)
795 minute: Minute (0-59)
796 second: Second (0-59)
797 """
798 # Validate inputs
799 if not isinstance(hour, int):
800 raise ValueError(f"Time.hour must be an integer, got {type(hour).__name__}: {hour}")
801 if not isinstance(minute, int):
802 raise ValueError(f"Time.minute must be an integer, got {type(minute).__name__}: {minute}")
803 if not isinstance(second, int):
804 raise ValueError(f"Time.second must be an integer, got {type(second).__name__}: {second}")
805
806 if hour < 0 or hour > 23:
807 raise ValueError(f"Time.hour must be between 0 and 23, got: {hour}")
808 if minute < 0 or minute > 59:
809 raise ValueError(f"Time.minute must be between 0 and 59, got: {minute}")
810 if second < 0 or second > 59:
811 raise ValueError(f"Time.second must be between 0 and 59, got: {second}")
813 # Initialize fields
814 self.hour = hour
815 self.minute = minute
816 self.second = second
817
818 def from_list(self, input_list: List[int]):
819 """Initialize from a list [hour, minute, second]"""
820 if len(input_list) < 3:
821 raise ValueError("Time.from_list requires a list with at least 3 elements [hour, minute, second]")
822 self.hour = input_list[0]
823 self.minute = input_list[1]
824 self.second = input_list[2]
825
826 def to_list(self) -> List[int]:
827 """Convert to list [hour, minute, second]"""
828 return [self.hour, self.minute, self.second]
829
830 def __eq__(self, other) -> bool:
831 """Check equality with another Time object"""
832 if not isinstance(other, Time):
833 return False
834 return (self.hour == other.hour and
835 self.minute == other.minute and
836 self.second == other.second)
837
838 def __ne__(self, other) -> bool:
839 """Check inequality with another Time object"""
840 return not self.__eq__(other)
842
843class Date(ctypes.Structure):
844 """Helios Date structure for representing date values."""
845 _fields_ = [('day', ctypes.c_int32), ('month', ctypes.c_int32), ('year', ctypes.c_int32)]
846
847 def __repr__(self) -> str:
848 return f'Date({self.year}-{self.month:02d}-{self.day:02d})'
849
850 def __str__(self) -> str:
851 return f'{self.year}-{self.month:02d}-{self.day:02d}'
852
853 def __new__(cls, year=None, month=None, day=None):
854 """Create instance - only pass cls to prevent TypeError on Windows."""
855 return ctypes.Structure.__new__(cls)
856
857 def __init__(self, year: int = 2023, month: int = 1, day: int = 1):
858 """
859 Initialize Date fields with validation.
860 Do not call super().__init__() for Windows compatibility.
861
862 Args:
863 year: Year (1900-3000)
864 month: Month (1-12)
865 day: Day (1-31)
866 """
867 # Validate inputs
868 if not isinstance(year, int):
869 raise ValueError(f"Date.year must be an integer, got {type(year).__name__}: {year}")
870 if not isinstance(month, int):
871 raise ValueError(f"Date.month must be an integer, got {type(month).__name__}: {month}")
872 if not isinstance(day, int):
873 raise ValueError(f"Date.day must be an integer, got {type(day).__name__}: {day}")
874
875 if year < 1900 or year > 3000:
876 raise ValueError(f"Date.year must be between 1900 and 3000, got: {year}")
877 if month < 1 or month > 12:
878 raise ValueError(f"Date.month must be between 1 and 12, got: {month}")
879 if day < 1 or day > 31:
880 raise ValueError(f"Date.day must be between 1 and 31, got: {day}")
881
882 # Initialize fields
883 self.year = year
884 self.month = month
885 self.day = day
886
887 def from_list(self, input_list: List[int]):
888 """Initialize from a list [year, month, day]"""
889 if len(input_list) < 3:
890 raise ValueError("Date.from_list requires a list with at least 3 elements [year, month, day]")
891 self.year = input_list[0]
892 self.month = input_list[1]
893 self.day = input_list[2]
894
895 def to_list(self) -> List[int]:
896 """Convert to list [year, month, day]"""
897 return [self.year, self.month, self.day]
898
899 def JulianDay(self) -> int:
900 """Calculate Julian day number for this date."""
901 a = (14 - self.month) // 12
902 y = self.year + 4800 - a
903 m = self.month + 12 * a - 3
904 return self.day + (153 * m + 2) // 5 + 365 * y + y // 4 - y // 100 + y // 400 - 32045
905
906 def incrementDay(self) -> 'Date':
907 """Return a new Date object incremented by one day."""
908 import calendar
909 days_in_month = calendar.monthrange(self.year, self.month)[1]
910
911 new_day = self.day + 1
912 new_month = self.month
913 new_year = self.year
914
915 if new_day > days_in_month:
916 new_day = 1
917 new_month += 1
918 if new_month > 12:
919 new_month = 1
920 new_year += 1
921
922 return Date(new_year, new_month, new_day)
923
924 def isLeapYear(self) -> bool:
925 """Check if this date's year is a leap year."""
926 return (self.year % 4 == 0 and self.year % 100 != 0) or (self.year % 400 == 0)
927
928 def __eq__(self, other) -> bool:
929 """Check equality with another Date object"""
930 if not isinstance(other, Date):
931 return False
932 return (self.year == other.year and
933 self.month == other.month and
934 self.day == other.day)
935
936 def __ne__(self, other) -> bool:
937 """Check inequality with another Date object"""
938 return not self.__eq__(other)
939
940
941def make_Time(hour: int, minute: int, second: int) -> Time:
942 """Make a Time from hour, minute, second"""
943 return Time(hour, minute, second)
944
945def make_Date(year: int, month: int, day: int) -> Date:
946 """Make a Date from year, month, day"""
947 return Date(year, month, day)
948
949
950class Location:
951 """Geographic location for solar position and radiation calculations.
952
953 Mirrors helios::Location: latitude in degrees (+N / -S), longitude in degrees
954 (+W / -E per Helios convention), UTC offset in hours (+moving West), and
955 altitude of the local Cartesian origin in meters above sea level (default 0).
957 Fields are validated against the same ranges as helios::Location::validate():
958
959 | Field | Range | Note |
960 | ----- | ----- | ---- |
961 | latitude | -90 to 90 | |
962 | longitude | -180 to 180 | Helios counts longitude positive moving West |
963 | utc_offset | -14 to 12 | Asymmetric; see below |
964 | altitude | any finite value | No non-arbitrary bound exists for a scene |
965
966 The UTC offset spans -14 to +12 rather than -12 to +12 because Helios counts the
967 offset positive moving West: the real-world span of UTC-12 through UTC+14
968 (Kiribati keeps the latter) inverts to +12 through -14.
969 """
970 __slots__ = ("latitude", "longitude", "utc_offset", "altitude")
971
972 def __init__(self, latitude: float = 38.55, longitude: float = 121.76, utc_offset: float = 8.0, altitude: float = 0.0):
973 if not isinstance(latitude, (int, float)):
974 raise ValueError(f"latitude must be a number, got {type(latitude).__name__}")
975 if not isinstance(longitude, (int, float)):
976 raise ValueError(f"longitude must be a number, got {type(longitude).__name__}")
977 if not isinstance(utc_offset, (int, float)):
978 raise ValueError(f"utc_offset must be a number, got {type(utc_offset).__name__}")
979 if not isinstance(altitude, (int, float)):
980 raise ValueError(f"altitude must be a number, got {type(altitude).__name__}")
981
982 # Mirror helios::Location::validate() (helios-core v1.3.79+), which both native
983 # parameterized constructors call. Checking here rather than only at the ABI
984 # boundary keeps the failure a ValueError raised where the bad value was
985 # written, and makes it identical in mock mode.
986 latitude, longitude = float(latitude), float(longitude)
987 utc_offset, altitude = float(utc_offset), float(altitude)
988
989 if not (-90.0 <= latitude <= 90.0):
990 raise ValueError(
991 f"Latitude of {latitude} degrees is out of range (should be -90 to 90)."
992 )
993 if not (-180.0 <= longitude <= 180.0):
994 raise ValueError(
995 f"Longitude of {longitude} degrees is out of range (should be -180 to "
996 f"180). Note that Helios counts longitude positive in the Western "
997 f"hemisphere."
998 )
999 if not (-14.0 <= utc_offset <= 12.0):
1000 raise ValueError(
1001 f"UTC offset of {utc_offset} hours is out of range (should be -14 to "
1002 f"12). Note that Helios counts the UTC offset positive moving West, so "
1003 f"UTC-8 is an offset of +8."
1004 )
1005 if not math.isfinite(altitude):
1006 raise ValueError(
1007 f"Altitude of {altitude} meters is not a finite value."
1008 )
1010 object.__setattr__(self, "latitude", latitude)
1011 object.__setattr__(self, "longitude", longitude)
1012 object.__setattr__(self, "utc_offset", utc_offset)
1013 object.__setattr__(self, "altitude", altitude)
1015 def __setattr__(self, name, value):
1016 # Frozen behavior to match the spirit of an immutable Location.
1017 raise AttributeError(f"Location is immutable; cannot reassign '{name}'")
1019 def __repr__(self) -> str:
1020 return (f"Location(latitude={self.latitude}, longitude={self.longitude}, "
1021 f"utc_offset={self.utc_offset}, altitude={self.altitude})")
1022
1023 def __eq__(self, other) -> bool:
1024 if not isinstance(other, Location):
1025 return False
1026 return (self.latitude == other.latitude and
1027 self.longitude == other.longitude and
1028 self.utc_offset == other.utc_offset and
1029 self.altitude == other.altitude)
1030
1031 def __ne__(self, other) -> bool:
1032 return not self.__eq__(other)
1033
1034 def __hash__(self) -> int:
1035 return hash((self.latitude, self.longitude, self.utc_offset, self.altitude))
1036
1037
1038def make_Location(latitude: float, longitude: float, utc_offset: float, altitude: float = 0.0) -> Location:
1039 """Make a Location from latitude (deg), longitude (deg), UTC offset (hours), and altitude (m)."""
1040 return Location(latitude, longitude, utc_offset, altitude)
1042# Removed duplicate make_SphericalCoord function - keeping only the 2-parameter version above
Parameters controlling the adaptive sub-patch refinement of an adaptive tile object.
Definition DataTypes.py:664
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:781
List[float] to_list(self)
Convert to the flat 5-element form used across the C ABI.
Definition DataTypes.py:773
__new__(cls, target=None, subpatch_size_min=None, subpatch_size_max=None, transition_exponent=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:708
from_list(self, List[float] input_list)
Initialize from list [target_x, target_y, subpatch_size_min, subpatch_size_max, transition_exponent].
Definition DataTypes.py:757
__init__(self, 'vec2' target=None, float subpatch_size_min=0.05, float subpatch_size_max=1.0, float transition_exponent=0.35)
Initialize AdaptiveTileRefinement fields with validation.
Definition DataTypes.py:723
subpatch_size_min
Requested edge length of the finest sub-patches, which occur at the.
Definition DataTypes.py:752
subpatch_size_max
Requested edge length of the coarsest sub-patches, which occur farthest.
Definition DataTypes.py:753
target
Point of maximum refinement, in tile-local coordinates relative to the tile center.
Definition DataTypes.py:751
transition_exponent
Exponent controlling how rapidly sub-patch size grows with distance.
Definition DataTypes.py:754
Axis rotation structure for specifying shoot orientation in PlantArchitecture.
Definition DataTypes.py:565
__init__(self, float pitch=0, float yaw=0, float roll=0)
Initialize AxisRotation fields with validation.
Definition DataTypes.py:600
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:632
List[float] to_list(self)
Convert to list [pitch, yaw, roll].
Definition DataTypes.py:625
__new__(cls, pitch=None, yaw=None, roll=None)
Create AxisRotation instance.
Definition DataTypes.py:585
from_list(self, List[float] input_list)
Initialize from list [pitch, yaw, roll].
Definition DataTypes.py:617
Helios Date structure for representing date values.
Definition DataTypes.py:913
__init__(self, int year=2023, int month=1, int day=1)
Initialize Date fields with validation.
Definition DataTypes.py:938
bool isLeapYear(self)
Check if this date's year is a leap year.
Definition DataTypes.py:997
bool __ne__(self, other)
Check inequality with another Date object.
bool __eq__(self, other)
Check equality with another Date object.
Geographic location for solar position and radiation calculations.
Helios primitive type enumeration.
Definition DataTypes.py:8
_validate_color_component(value, component_name)
Validate a color component is finite and in range [0,1].
Definition DataTypes.py:469
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:458
'RGBAcolor' scale(self, float factor)
Return a scaled copy of this color, clamped to [0, 1].
Definition DataTypes.py:446
__init__(self, float r=0, float g=0, float b=0, float a=0)
Definition DataTypes.py:424
__new__(cls, r=None, g=None, b=None, a=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:421
'RGBcolor' scale(self, float factor)
Return a scaled copy of this color, clamped to [0, 1].
Definition DataTypes.py:373
__new__(cls, r=None, g=None, b=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:351
__init__(self, float r=0, float g=0, float b=0)
Definition DataTypes.py:354
from_list(self, List[float] input_list)
Definition DataTypes.py:364
_validate_color_component(value, component_name)
Validate a color component is finite and in range [0,1].
Definition DataTypes.py:395
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:384
from_list(self, List[float] input_list)
Definition DataTypes.py:536
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:549
__init__(self, float radius=1, float elevation=0, float azimuth=0)
Initialize SphericalCoord fields with validation.
Definition DataTypes.py:513
__new__(cls, radius=None, elevation=None, azimuth=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:499
Helios Time structure for representing time values.
Definition DataTypes.py:841
__new__(cls, hour=None, minute=None, second=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:854
List[int] to_list(self)
Convert to list [hour, minute, second].
Definition DataTypes.py:896
from_list(self, List[int] input_list)
Initialize from a list [hour, minute, second].
Definition DataTypes.py:888
__init__(self, int hour=0, int minute=0, int second=0)
Initialize Time fields with validation.
Definition DataTypes.py:866
Provenance of a polymesh object's per-vertex normals (helios-core 1.3.83).
Definition DataTypes.py:20
Granularity at which coincident vertices are treated as one shared vertex (helios-core 1....
Definition DataTypes.py:30
from_list(self, List[int] input_list)
Definition DataTypes.py:64
__init__(self, int x=0, int y=0)
Definition DataTypes.py:54
__new__(cls, x=None, y=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:51
__init__(self, int x=0, int y=0, int z=0)
Definition DataTypes.py:89
from_list(self, List[int] input_list)
Definition DataTypes.py:102
__new__(cls, x=None, y=None, z=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:86
from_list(self, List[int] input_list)
Definition DataTypes.py:144
__init__(self, int x=0, int y=0, int z=0, int w=0)
Definition DataTypes.py:128
__new__(cls, x=None, y=None, z=None, w=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:125
from_list(self, List[float] input_list)
Definition DataTypes.py:183
'vec2' normalize(self)
Return a normalized copy of this vector (unit length).
Definition DataTypes.py:196
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:206
float magnitude(self)
Return the magnitude (length) of the vector.
Definition DataTypes.py:191
__new__(cls, x=None, y=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:168
__init__(self, float x=0, float y=0)
Definition DataTypes.py:171
__init__(self, float x=0, float y=0, float z=0)
Definition DataTypes.py:229
from_list(self, List[float] input_list)
Definition DataTypes.py:245
'vec3' normalize(self)
Return a normalized copy of this vector (unit length).
Definition DataTypes.py:262
__new__(cls, x=None, y=None, z=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:226
float magnitude(self)
Return the magnitude (length) of the vector.
Definition DataTypes.py:257
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:272
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:329
__init__(self, float x=0, float y=0, float z=0, float w=0)
Definition DataTypes.py:296
__new__(cls, x=None, y=None, z=None, w=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:293
from_list(self, List[float] input_list)
Definition DataTypes.py:316
RGBAcolor make_RGBAcolor(float r, float g, float b, float a)
Make an RGBAcolor from four floats.
Definition DataTypes.py:832
AxisRotation make_AxisRotation(float pitch, float yaw, float roll)
Make an AxisRotation from three angles in radians.
Definition DataTypes.py:836
int3 make_int3(int x, int y, int z)
Make an int3 from three integers.
Definition DataTypes.py:808
Time make_Time(int hour, int minute, int second)
Make a Time from hour, minute, second.
RGBcolor make_RGBcolor(float r, float g, float b)
Make an RGBcolor from three floats.
Definition DataTypes.py:828
Date make_Date(int year, int month, int day)
Make a Date from year, month, day.
int4 make_int4(int x, int y, int z, int w)
Make an int4 from four integers.
Definition DataTypes.py:812
SphericalCoord make_SphericalCoord(float elevation_radians, float azimuth_radians)
Make a SphericalCoord by specifying elevation and azimuth (C++ API compatibility).
Definition DataTypes.py:804
vec2 make_vec2(float x, float y)
Make a vec2 from two floats.
Definition DataTypes.py:816
vec4 make_vec4(float x, float y, float z, float w)
Make a vec4 from four floats.
Definition DataTypes.py:824
Location make_Location(float latitude, float longitude, float utc_offset, float altitude=0.0)
Make a Location from latitude (deg), longitude (deg), UTC offset (hours), and altitude (m).
int2 make_int2(int x, int y)
Make an int2 from two integers.
Definition DataTypes.py:791
vec3 make_vec3(float x, float y, float z)
Make a vec3 from three floats.
Definition DataTypes.py:820