0.1.33
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**, exactly as the
520 native ``helios::AxisRotation`` does: the values are passed through unchanged and the
521 plant-architecture library itself builds rotations such as ``0.05 * pi`` and ``2 * pi``.
522 Use ``math.radians()`` to convert from degrees. Used to define the orientation of
523 shoots, stems, and branches during plant construction.
524 """
525 _fields_ = [
526 ('pitch', ctypes.c_float),
527 ('yaw', ctypes.c_float),
528 ('roll', ctypes.c_float)
529 ]
530
531 def __repr__(self) -> str:
532 return f'AxisRotation({self.pitch}, {self.yaw}, {self.roll})'
534 def __str__(self) -> str:
535 return f'AxisRotation({self.pitch}, {self.yaw}, {self.roll})'
537 def __new__(cls, pitch=None, yaw=None, roll=None):
538 """
539 Create AxisRotation instance.
540 Only pass cls to parent __new__ to prevent TypeError on Windows.
541 """
542 return ctypes.Structure.__new__(cls)
543
544 def __init__(self, pitch:float=0, yaw:float=0, roll:float=0):
545 """
546 Initialize AxisRotation fields with validation.
547 Do not call super().__init__() for Windows compatibility.
548
549 Args:
550 pitch: Pitch angle in radians (rotation about transverse axis)
551 yaw: Yaw angle in radians (rotation about vertical axis)
552 roll: Roll angle in radians (rotation about longitudinal axis)
553
554 Raises:
555 ValueError: If any angle value is not finite
556 """
557 # Validate finite numeric inputs
558 if not self._is_finite_numeric(pitch):
559 raise ValueError(f"AxisRotation.pitch must be a finite number, got {type(pitch).__name__}: {pitch}. "
560 f"Rotation angles must be finite (not NaN or infinity).")
561 if not self._is_finite_numeric(yaw):
562 raise ValueError(f"AxisRotation.yaw must be a finite number, got {type(yaw).__name__}: {yaw}. "
563 f"Rotation angles must be finite (not NaN or infinity).")
564 if not self._is_finite_numeric(roll):
565 raise ValueError(f"AxisRotation.roll must be a finite number, got {type(roll).__name__}: {roll}. "
566 f"Rotation angles must be finite (not NaN or infinity).")
568 self.pitch = float(pitch)
569 self.yaw = float(yaw)
570 self.roll = float(roll)
572 def from_list(self, input_list:List[float]):
573 """Initialize from list [pitch, yaw, roll]"""
574 if len(input_list) < 3:
575 raise ValueError("AxisRotation.from_list requires a list with at least 3 elements [pitch, yaw, roll]")
576 self.pitch = input_list[0]
577 self.yaw = input_list[1]
578 self.roll = input_list[2]
579
580 def to_list(self) -> List[float]:
581 """Convert to list [pitch, yaw, roll]"""
582 return [self.pitch, self.yaw, self.roll]
583
584 @staticmethod
585 def _is_finite_numeric(value) -> bool:
586 """Check if value is a finite number (not NaN or inf)."""
587 try:
588 float_value = float(value)
589 return math.isfinite(float_value)
590 except (ValueError, TypeError, OverflowError):
591 return False
592
593
594
595class AdaptiveTileRefinement(ctypes.Structure):
596 """
597 Parameters controlling the adaptive sub-patch refinement of an adaptive tile object.
598
599 Sub-patch edge length grows with distance from ``target``, from ``subpatch_size_min`` at the
600 target itself up to ``subpatch_size_max`` at the point of the tile farthest from it.
601 Refinement is performed by recursive quadtree subdivision, so the achieved sizes are the
602 requested sizes rounded onto a power-of-two ladder; both ends are typically within about 20%
603 of the request. Query what was actually achieved with
604 ``Context.getAdaptiveTileObjectSubpatchSizeRange()``.
605
606 Attributes:
607 target: Point of maximum refinement, in tile-local coordinates relative to the tile center
608 and before rotation is applied (default: (0, 0), the tile center). A target outside the
609 tile is permitted, but the finest requested sub-patch size will then not be reached
610 anywhere and helios issues a warning.
611 subpatch_size_min: Requested edge length of the finest sub-patches, which occur at the
612 target point (default: 0.05, i.e. 5 cm for a scene measured in meters).
613 subpatch_size_max: Requested edge length of the coarsest sub-patches, which occur farthest
614 from the target (default: 1.0). Must be no more than half the smaller tile dimension.
615 transition_exponent: Exponent controlling how rapidly sub-patch size grows with distance
616 from the target (default: 0.35, which suits a typical ground plane). Useful values run
617 from about 0.25 to 1. This does not change the achieved size range, but it is by far
618 the most consequential parameter for the sub-patch count -- on a 50 m tile refined from
619 2 m to 2 cm, 0.25 gives roughly 9 thousand sub-patches and 2 gives roughly 2 million.
620 Use ``Context.predictAdaptiveTileObjectSubpatchCount()`` to check before building.
621
622 Examples:
623 >>> refinement = AdaptiveTileRefinement(target=vec2(0, 0), subpatch_size_min=0.02,
624 ... subpatch_size_max=2.0)
625 >>> refinement.subpatch_size_min
626 0.02
627 """
628 _fields_ = [
629 ('target', vec2),
630 ('subpatch_size_min', ctypes.c_float),
631 ('subpatch_size_max', ctypes.c_float),
632 ('transition_exponent', ctypes.c_float)
633 ]
635 def __repr__(self) -> str:
636 return (f'AdaptiveTileRefinement(target=vec2({self.target.x}, {self.target.y}), '
637 f'subpatch_size_min={self.subpatch_size_min}, '
638 f'subpatch_size_max={self.subpatch_size_max}, '
639 f'transition_exponent={self.transition_exponent})')
640
641 def __str__(self) -> str:
642 return self.__repr__()
643
644 def __new__(cls, target=None, subpatch_size_min=None, subpatch_size_max=None,
645 transition_exponent=None):
646 """Create instance - only pass cls to prevent TypeError on Windows."""
647 return ctypes.Structure.__new__(cls)
648
649 def __init__(self, target: 'vec2' = None, subpatch_size_min: float = 0.05,
650 subpatch_size_max: float = 1.0, transition_exponent: float = 0.35):
651 """
652 Initialize AdaptiveTileRefinement fields with validation.
653 Do not call super().__init__() for Windows compatibility.
654
655 Defaults match helios::AdaptiveTileRefinement.
656
657 Raises:
658 ValueError: If target is not a vec2, if either size is not a positive finite number,
659 if subpatch_size_min exceeds subpatch_size_max, if their ratio exceeds the largest
660 supported value of 16777216, or if transition_exponent is not positive and finite.
661 """
662 if target is None:
663 target = vec2(0.0, 0.0)
664 if not isinstance(target, vec2):
665 raise ValueError(f"AdaptiveTileRefinement.target must be a vec2, "
666 f"got {type(target).__name__}: {target}")
667 if not self._is_finite_numeric(target.x) or not self._is_finite_numeric(target.y):
668 raise ValueError(f"AdaptiveTileRefinement.target must be finite, got {target}")
669
670 if not self._is_finite_numeric(subpatch_size_min) or float(subpatch_size_min) <= 0:
671 raise ValueError(f"AdaptiveTileRefinement.subpatch_size_min must be a positive finite "
672 f"number, got {type(subpatch_size_min).__name__}: {subpatch_size_min}")
673 if not self._is_finite_numeric(subpatch_size_max) or float(subpatch_size_max) <= 0:
674 raise ValueError(f"AdaptiveTileRefinement.subpatch_size_max must be a positive finite "
675 f"number, got {type(subpatch_size_max).__name__}: {subpatch_size_max}")
676 if float(subpatch_size_min) > float(subpatch_size_max):
677 raise ValueError(f"AdaptiveTileRefinement.subpatch_size_min of {subpatch_size_min} is "
678 f"greater than subpatch_size_max of {subpatch_size_max}.")
679 if float(subpatch_size_max) / float(subpatch_size_min) > 16777216.0:
680 raise ValueError(f"AdaptiveTileRefinement size ratio of "
681 f"{float(subpatch_size_max) / float(subpatch_size_min)} exceeds the "
682 f"largest supported ratio of 16777216. Increase subpatch_size_min or "
683 f"decrease subpatch_size_max.")
684 if not self._is_finite_numeric(transition_exponent) or float(transition_exponent) <= 0:
685 raise ValueError(f"AdaptiveTileRefinement.transition_exponent must be a positive "
686 f"finite number, got {type(transition_exponent).__name__}: "
687 f"{transition_exponent}")
688
689 self.target = target
690 self.subpatch_size_min = float(subpatch_size_min)
691 self.subpatch_size_max = float(subpatch_size_max)
692 self.transition_exponent = float(transition_exponent)
693
694 def from_list(self, input_list: List[float]):
695 """Initialize from list [target_x, target_y, subpatch_size_min, subpatch_size_max, transition_exponent]"""
696 if len(input_list) < 5:
697 raise ValueError("AdaptiveTileRefinement.from_list requires a list with at least 5 "
698 "elements [target_x, target_y, subpatch_size_min, subpatch_size_max, "
699 "transition_exponent]")
700 self.target = vec2(float(input_list[0]), float(input_list[1]))
701 self.subpatch_size_min = input_list[2]
702 self.subpatch_size_max = input_list[3]
703 self.transition_exponent = input_list[4]
704
705 def to_list(self) -> List[float]:
706 """
707 Convert to the flat 5-element form used across the C ABI.
708
709 Element order matches the order helios itself uses to serialize the struct to XML:
710 [target_x, target_y, subpatch_size_min, subpatch_size_max, transition_exponent]
711 """
712 return [self.target.x, self.target.y, self.subpatch_size_min,
714
715 @staticmethod
716 def _is_finite_numeric(value) -> bool:
717 """Check if value is a finite number (not NaN or inf)."""
718 try:
719 float_value = float(value)
720 return math.isfinite(float_value)
721 except (ValueError, TypeError, OverflowError):
722 return False
723
725# Factory functions to match C++ API
726def make_int2(x: int, y: int) -> int2:
727 """Make an int2 from two integers"""
728 return int2(x, y)
729
730def make_SphericalCoord(elevation_radians: float, azimuth_radians: float) -> SphericalCoord:
731 """
732 Make a SphericalCoord by specifying elevation and azimuth (C++ API compatibility).
733
734 Args:
735 elevation_radians: Elevation angle in radians
736 azimuth_radians: Azimuthal angle in radians
737
738 Returns:
739 SphericalCoord with radius=1, and automatically computed zenith
740 """
741 return SphericalCoord(radius=1, elevation=elevation_radians, azimuth=azimuth_radians)
742
743def make_int3(x: int, y: int, z: int) -> int3:
744 """Make an int3 from three integers"""
745 return int3(x, y, z)
746
747def make_int4(x: int, y: int, z: int, w: int) -> int4:
748 """Make an int4 from four integers"""
749 return int4(x, y, z, w)
750
751def make_vec2(x: float, y: float) -> vec2:
752 """Make a vec2 from two floats"""
753 return vec2(x, y)
755def make_vec3(x: float, y: float, z: float) -> vec3:
756 """Make a vec3 from three floats"""
757 return vec3(x, y, z)
758
759def make_vec4(x: float, y: float, z: float, w: float) -> vec4:
760 """Make a vec4 from four floats"""
761 return vec4(x, y, z, w)
762
763def make_RGBcolor(r: float, g: float, b: float) -> RGBcolor:
764 """Make an RGBcolor from three floats"""
765 return RGBcolor(r, g, b)
766
767def make_RGBAcolor(r: float, g: float, b: float, a: float) -> RGBAcolor:
768 """Make an RGBAcolor from four floats"""
769 return RGBAcolor(r, g, b, a)
770
771def make_AxisRotation(pitch: float, yaw: float, roll: float) -> AxisRotation:
772 """Make an AxisRotation from three angles in degrees"""
773 return AxisRotation(pitch, yaw, roll)
774
776class Time(ctypes.Structure):
777 """Helios Time structure for representing time values."""
778 _fields_ = [('second', ctypes.c_int32), ('minute', ctypes.c_int32), ('hour', ctypes.c_int32)]
779
780 def __repr__(self) -> str:
781 return f'Time({self.hour:02d}:{self.minute:02d}:{self.second:02d})'
782
783 def __str__(self) -> str:
784 return f'{self.hour:02d}:{self.minute:02d}:{self.second:02d}'
785
786 def __new__(cls, hour=None, minute=None, second=None):
787 """Create instance - only pass cls to prevent TypeError on Windows."""
788 return ctypes.Structure.__new__(cls)
789
790 def __init__(self, hour: int = 0, minute: int = 0, second: int = 0):
791 """
792 Initialize Time fields with validation.
793 Do not call super().__init__() for Windows compatibility.
794
795 Args:
796 hour: Hour (0-23)
797 minute: Minute (0-59)
798 second: Second (0-59)
799 """
800 # Validate inputs
801 if not isinstance(hour, int):
802 raise ValueError(f"Time.hour must be an integer, got {type(hour).__name__}: {hour}")
803 if not isinstance(minute, int):
804 raise ValueError(f"Time.minute must be an integer, got {type(minute).__name__}: {minute}")
805 if not isinstance(second, int):
806 raise ValueError(f"Time.second must be an integer, got {type(second).__name__}: {second}")
807
808 if hour < 0 or hour > 23:
809 raise ValueError(f"Time.hour must be between 0 and 23, got: {hour}")
810 if minute < 0 or minute > 59:
811 raise ValueError(f"Time.minute must be between 0 and 59, got: {minute}")
812 if second < 0 or second > 59:
813 raise ValueError(f"Time.second must be between 0 and 59, got: {second}")
815 # Initialize fields
816 self.hour = hour
817 self.minute = minute
818 self.second = second
819
820 def from_list(self, input_list: List[int]):
821 """Initialize from a list [hour, minute, second]"""
822 if len(input_list) < 3:
823 raise ValueError("Time.from_list requires a list with at least 3 elements [hour, minute, second]")
824 self.hour = input_list[0]
825 self.minute = input_list[1]
826 self.second = input_list[2]
827
828 def to_list(self) -> List[int]:
829 """Convert to list [hour, minute, second]"""
830 return [self.hour, self.minute, self.second]
831
832 def __eq__(self, other) -> bool:
833 """Check equality with another Time object"""
834 if not isinstance(other, Time):
835 return False
836 return (self.hour == other.hour and
837 self.minute == other.minute and
838 self.second == other.second)
839
840 def __ne__(self, other) -> bool:
841 """Check inequality with another Time object"""
842 return not self.__eq__(other)
844
845class Date(ctypes.Structure):
846 """Helios Date structure for representing date values."""
847 _fields_ = [('day', ctypes.c_int32), ('month', ctypes.c_int32), ('year', ctypes.c_int32)]
848
849 def __repr__(self) -> str:
850 return f'Date({self.year}-{self.month:02d}-{self.day:02d})'
851
852 def __str__(self) -> str:
853 return f'{self.year}-{self.month:02d}-{self.day:02d}'
854
855 def __new__(cls, year=None, month=None, day=None):
856 """Create instance - only pass cls to prevent TypeError on Windows."""
857 return ctypes.Structure.__new__(cls)
858
859 def __init__(self, year: int = 2023, month: int = 1, day: int = 1):
860 """
861 Initialize Date fields with validation.
862 Do not call super().__init__() for Windows compatibility.
863
864 Args:
865 year: Year (1900-3000)
866 month: Month (1-12)
867 day: Day (1-31)
868 """
869 # Validate inputs
870 if not isinstance(year, int):
871 raise ValueError(f"Date.year must be an integer, got {type(year).__name__}: {year}")
872 if not isinstance(month, int):
873 raise ValueError(f"Date.month must be an integer, got {type(month).__name__}: {month}")
874 if not isinstance(day, int):
875 raise ValueError(f"Date.day must be an integer, got {type(day).__name__}: {day}")
876
877 if year < 1900 or year > 3000:
878 raise ValueError(f"Date.year must be between 1900 and 3000, got: {year}")
879 if month < 1 or month > 12:
880 raise ValueError(f"Date.month must be between 1 and 12, got: {month}")
881 if day < 1 or day > 31:
882 raise ValueError(f"Date.day must be between 1 and 31, got: {day}")
883
884 # Initialize fields
885 self.year = year
886 self.month = month
887 self.day = day
888
889 def from_list(self, input_list: List[int]):
890 """Initialize from a list [year, month, day]"""
891 if len(input_list) < 3:
892 raise ValueError("Date.from_list requires a list with at least 3 elements [year, month, day]")
893 self.year = input_list[0]
894 self.month = input_list[1]
895 self.day = input_list[2]
896
897 def to_list(self) -> List[int]:
898 """Convert to list [year, month, day]"""
899 return [self.year, self.month, self.day]
900
901 def JulianDay(self) -> int:
902 """Calculate Julian day number for this date."""
903 a = (14 - self.month) // 12
904 y = self.year + 4800 - a
905 m = self.month + 12 * a - 3
906 return self.day + (153 * m + 2) // 5 + 365 * y + y // 4 - y // 100 + y // 400 - 32045
907
908 def incrementDay(self) -> 'Date':
909 """Return a new Date object incremented by one day."""
910 import calendar
911 days_in_month = calendar.monthrange(self.year, self.month)[1]
912
913 new_day = self.day + 1
914 new_month = self.month
915 new_year = self.year
916
917 if new_day > days_in_month:
918 new_day = 1
919 new_month += 1
920 if new_month > 12:
921 new_month = 1
922 new_year += 1
923
924 return Date(new_year, new_month, new_day)
925
926 def isLeapYear(self) -> bool:
927 """Check if this date's year is a leap year."""
928 return (self.year % 4 == 0 and self.year % 100 != 0) or (self.year % 400 == 0)
929
930 def __eq__(self, other) -> bool:
931 """Check equality with another Date object"""
932 if not isinstance(other, Date):
933 return False
934 return (self.year == other.year and
935 self.month == other.month and
936 self.day == other.day)
937
938 def __ne__(self, other) -> bool:
939 """Check inequality with another Date object"""
940 return not self.__eq__(other)
941
942
943def make_Time(hour: int, minute: int, second: int) -> Time:
944 """Make a Time from hour, minute, second"""
945 return Time(hour, minute, second)
946
947def make_Date(year: int, month: int, day: int) -> Date:
948 """Make a Date from year, month, day"""
949 return Date(year, month, day)
950
951
952class Location:
953 """Geographic location for solar position and radiation calculations.
954
955 Mirrors helios::Location: latitude in degrees (+N / -S), longitude in degrees
956 (+W / -E per Helios convention), UTC offset in hours (+moving West), and
957 altitude of the local Cartesian origin in meters above sea level (default 0).
959 Fields are validated against the same ranges as helios::Location::validate():
960
961 | Field | Range | Note |
962 | ----- | ----- | ---- |
963 | latitude | -90 to 90 | |
964 | longitude | -180 to 180 | Helios counts longitude positive moving West |
965 | utc_offset | -14 to 12 | Asymmetric; see below |
966 | altitude | any finite value | No non-arbitrary bound exists for a scene |
967
968 The UTC offset spans -14 to +12 rather than -12 to +12 because Helios counts the
969 offset positive moving West: the real-world span of UTC-12 through UTC+14
970 (Kiribati keeps the latter) inverts to +12 through -14.
971 """
972 __slots__ = ("latitude", "longitude", "utc_offset", "altitude")
973
974 def __init__(self, latitude: float = 38.55, longitude: float = 121.76, utc_offset: float = 8.0, altitude: float = 0.0):
975 if not isinstance(latitude, (int, float)):
976 raise ValueError(f"latitude must be a number, got {type(latitude).__name__}")
977 if not isinstance(longitude, (int, float)):
978 raise ValueError(f"longitude must be a number, got {type(longitude).__name__}")
979 if not isinstance(utc_offset, (int, float)):
980 raise ValueError(f"utc_offset must be a number, got {type(utc_offset).__name__}")
981 if not isinstance(altitude, (int, float)):
982 raise ValueError(f"altitude must be a number, got {type(altitude).__name__}")
983
984 # Mirror helios::Location::validate() (helios-core v1.3.79+), which both native
985 # parameterized constructors call. Checking here rather than only at the ABI
986 # boundary keeps the failure a ValueError raised where the bad value was
987 # written, and makes it identical in mock mode.
988 latitude, longitude = float(latitude), float(longitude)
989 utc_offset, altitude = float(utc_offset), float(altitude)
990
991 if not (-90.0 <= latitude <= 90.0):
992 raise ValueError(
993 f"Latitude of {latitude} degrees is out of range (should be -90 to 90)."
994 )
995 if not (-180.0 <= longitude <= 180.0):
996 raise ValueError(
997 f"Longitude of {longitude} degrees is out of range (should be -180 to "
998 f"180). Note that Helios counts longitude positive in the Western "
999 f"hemisphere."
1000 )
1001 if not (-14.0 <= utc_offset <= 12.0):
1002 raise ValueError(
1003 f"UTC offset of {utc_offset} hours is out of range (should be -14 to "
1004 f"12). Note that Helios counts the UTC offset positive moving West, so "
1005 f"UTC-8 is an offset of +8."
1006 )
1007 if not math.isfinite(altitude):
1008 raise ValueError(
1009 f"Altitude of {altitude} meters is not a finite value."
1010 )
1012 object.__setattr__(self, "latitude", latitude)
1013 object.__setattr__(self, "longitude", longitude)
1014 object.__setattr__(self, "utc_offset", utc_offset)
1015 object.__setattr__(self, "altitude", altitude)
1017 def __setattr__(self, name, value):
1018 # Frozen behavior to match the spirit of an immutable Location.
1019 raise AttributeError(f"Location is immutable; cannot reassign '{name}'")
1021 def __repr__(self) -> str:
1022 return (f"Location(latitude={self.latitude}, longitude={self.longitude}, "
1023 f"utc_offset={self.utc_offset}, altitude={self.altitude})")
1024
1025 def __eq__(self, other) -> bool:
1026 if not isinstance(other, Location):
1027 return False
1028 return (self.latitude == other.latitude and
1029 self.longitude == other.longitude and
1030 self.utc_offset == other.utc_offset and
1031 self.altitude == other.altitude)
1032
1033 def __ne__(self, other) -> bool:
1034 return not self.__eq__(other)
1035
1036 def __hash__(self) -> int:
1037 return hash((self.latitude, self.longitude, self.utc_offset, self.altitude))
1038
1039
1040def make_Location(latitude: float, longitude: float, utc_offset: float, altitude: float = 0.0) -> Location:
1041 """Make a Location from latitude (deg), longitude (deg), UTC offset (hours), and altitude (m)."""
1042 return Location(latitude, longitude, utc_offset, altitude)
1044# 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:666
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:783
List[float] to_list(self)
Convert to the flat 5-element form used across the C ABI.
Definition DataTypes.py:775
__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:710
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:759
__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:725
subpatch_size_min
Requested edge length of the finest sub-patches, which occur at the.
Definition DataTypes.py:754
subpatch_size_max
Requested edge length of the coarsest sub-patches, which occur farthest.
Definition DataTypes.py:755
target
Point of maximum refinement, in tile-local coordinates relative to the tile center.
Definition DataTypes.py:753
transition_exponent
Exponent controlling how rapidly sub-patch size grows with distance.
Definition DataTypes.py:756
Axis rotation structure for specifying shoot orientation in PlantArchitecture.
Definition DataTypes.py:567
__init__(self, float pitch=0, float yaw=0, float roll=0)
Initialize AxisRotation fields with validation.
Definition DataTypes.py:602
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
Definition DataTypes.py:634
List[float] to_list(self)
Convert to list [pitch, yaw, roll].
Definition DataTypes.py:627
__new__(cls, pitch=None, yaw=None, roll=None)
Create AxisRotation instance.
Definition DataTypes.py:587
from_list(self, List[float] input_list)
Initialize from list [pitch, yaw, roll].
Definition DataTypes.py:619
Helios Date structure for representing date values.
Definition DataTypes.py:915
__init__(self, int year=2023, int month=1, int day=1)
Initialize Date fields with validation.
Definition DataTypes.py:940
bool isLeapYear(self)
Check if this date's year is a leap year.
Definition DataTypes.py:999
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:843
__new__(cls, hour=None, minute=None, second=None)
Create only pass cls to prevent TypeError on Windows.
Definition DataTypes.py:856
List[int] to_list(self)
Convert to list [hour, minute, second].
Definition DataTypes.py:898
from_list(self, List[int] input_list)
Initialize from a list [hour, minute, second].
Definition DataTypes.py:890
__init__(self, int hour=0, int minute=0, int second=0)
Initialize Time fields with validation.
Definition DataTypes.py:868
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:834
AxisRotation make_AxisRotation(float pitch, float yaw, float roll)
Make an AxisRotation from three angles in degrees.
Definition DataTypes.py:838
int3 make_int3(int x, int y, int z)
Make an int3 from three integers.
Definition DataTypes.py:810
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:830
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:814
SphericalCoord make_SphericalCoord(float elevation_radians, float azimuth_radians)
Make a SphericalCoord by specifying elevation and azimuth (C++ API compatibility).
Definition DataTypes.py:806
vec2 make_vec2(float x, float y)
Make a vec2 from two floats.
Definition DataTypes.py:818
vec4 make_vec4(float x, float y, float z, float w)
Make a vec4 from four floats.
Definition DataTypes.py:826
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:793
vec3 make_vec3(float x, float y, float z)
Make a vec3 from three floats.
Definition DataTypes.py:822