3from typing
import Any, List
4from enum
import IntEnum
8 """Helios primitive type enumeration."""
19class int2(ctypes.Structure):
20 _fields_ = [(
'x', ctypes.c_int32), (
'y', ctypes.c_int32)]
23 return f
'int2({self.x}, {self.y})'
26 return f
'int2({self.x}, {self.y})'
30 if not isinstance(x, int):
31 raise ValueError(f
"int2.x must be an integer, got {type(x).__name__}: {x}")
32 if not isinstance(y, int):
33 raise ValueError(f
"int2.y must be an integer, got {type(y).__name__}: {y}")
39 self.
x = input_list[0]
40 self.
y = input_list[1]
43 return [self.
x, self.
y]
47class int3(ctypes.Structure):
48 _fields_ = [(
'x', ctypes.c_int32), (
'y', ctypes.c_int32), (
'z', ctypes.c_int32)]
51 return f
'int3({self.x}, {self.y}, {self.z})'
54 return f
'int3({self.x}, {self.y}, {self.z})'
58 if not isinstance(x, int):
59 raise ValueError(f
"int3.x must be an integer, got {type(x).__name__}: {x}")
60 if not isinstance(y, int):
61 raise ValueError(f
"int3.y must be an integer, got {type(y).__name__}: {y}")
62 if not isinstance(z, int):
63 raise ValueError(f
"int3.z must be an integer, got {type(z).__name__}: {z}")
69 def from_list(self, input_list:List[int]):
70 self.
x = input_list[0]
71 self.
y = input_list[1]
72 self.
z = input_list[2]
75 return [self.
x, self.
y, self.
z]
79class int4(ctypes.Structure):
80 _fields_ = [(
'x', ctypes.c_int32), (
'y', ctypes.c_int32), (
'z', ctypes.c_int32), (
'w', ctypes.c_int32)]
83 return f
'int4({self.x}, {self.y}, {self.z}, {self.w})'
86 return f
'int4({self.x}, {self.y}, {self.z}, {self.w})'
88 def __init__(self, x:int=0, y:int=0, z:int=0, w:int=0):
90 if not isinstance(x, int):
91 raise ValueError(f
"int4.x must be an integer, got {type(x).__name__}: {x}")
92 if not isinstance(y, int):
93 raise ValueError(f
"int4.y must be an integer, got {type(y).__name__}: {y}")
94 if not isinstance(z, int):
95 raise ValueError(f
"int4.z must be an integer, got {type(z).__name__}: {z}")
96 if not isinstance(w, int):
97 raise ValueError(f
"int4.w must be an integer, got {type(w).__name__}: {w}")
104 def from_list(self, input_list:List[int]):
105 self.
x = input_list[0]
106 self.
y = input_list[1]
107 self.
z = input_list[2]
108 self.
w = input_list[3]
111 return [self.
x, self.
y, self.
z, self.
w]
115class vec2(ctypes.Structure):
116 _fields_ = [(
'x', ctypes.c_float), (
'y', ctypes.c_float)]
119 return f
'vec2({self.x}, {self.y})'
122 return f
'vec2({self.x}, {self.y})'
127 raise ValueError(f
"vec2.x must be a finite number, got {type(x).__name__}: {x}. "
128 f
"Vector components must be finite (not NaN or infinity).")
130 raise ValueError(f
"vec2.y must be a finite number, got {type(y).__name__}: {y}. "
131 f
"Vector components must be finite (not NaN or infinity).")
137 self.
x = input_list[0]
138 self.
y = input_list[1]
140 def to_list(self) -> List[float]:
141 return [self.
x, self.
y]
145 """Check if value is a finite number (not NaN or inf)."""
147 float_value = float(value)
148 return math.isfinite(float_value)
149 except (ValueError, TypeError, OverflowError):
153 _fields_ = [(
'x', ctypes.c_float), (
'y', ctypes.c_float), (
'z', ctypes.c_float)]
156 return f
'vec3({self.x}, {self.y}, {self.z})'
159 return f
'vec3({self.x}, {self.y}, {self.z})'
161 def __init__(self, x:float=0, y:float=0, z:float=0):
164 raise ValueError(f
"vec3.x must be a finite number, got {type(x).__name__}: {x}. "
165 f
"Vector components must be finite (not NaN or infinity).")
167 raise ValueError(f
"vec3.y must be a finite number, got {type(y).__name__}: {y}. "
168 f
"Vector components must be finite (not NaN or infinity).")
170 raise ValueError(f
"vec3.z must be a finite number, got {type(z).__name__}: {z}. "
171 f
"Vector components must be finite (not NaN or infinity).")
177 def from_list(self, input_list:List[float]):
178 self.
x = input_list[0]
179 self.
y = input_list[1]
180 self.
z = input_list[2]
182 def to_list(self) -> List[float]:
183 return [self.
x, self.
y, self.
z]
186 return (self.
x, self.
y, self.
z)
190 """Check if value is a finite number (not NaN or inf)."""
192 float_value = float(value)
193 return math.isfinite(float_value)
194 except (ValueError, TypeError, OverflowError):
198class vec4(ctypes.Structure):
199 _fields_ = [(
'x', ctypes.c_float), (
'y', ctypes.c_float), (
'z', ctypes.c_float), (
'w', ctypes.c_float)]
202 return f
'vec4({self.x}, {self.y}, {self.z}, {self.w})'
205 return f
'vec4({self.x}, {self.y}, {self.z}, {self.w})'
207 def __init__(self, x:float=0, y:float=0, z:float=0, w:float=0):
210 raise ValueError(f
"vec4.x must be a finite number, got {type(x).__name__}: {x}. "
211 f
"Vector components must be finite (not NaN or infinity).")
213 raise ValueError(f
"vec4.y must be a finite number, got {type(y).__name__}: {y}. "
214 f
"Vector components must be finite (not NaN or infinity).")
216 raise ValueError(f
"vec4.z must be a finite number, got {type(z).__name__}: {z}. "
217 f
"Vector components must be finite (not NaN or infinity).")
219 raise ValueError(f
"vec4.w must be a finite number, got {type(w).__name__}: {w}. "
220 f
"Vector components must be finite (not NaN or infinity).")
227 def from_list(self, input_list:List[float]):
228 self.
x = input_list[0]
229 self.
y = input_list[1]
230 self.
z = input_list[2]
231 self.
w = input_list[3]
233 def to_list(self) -> List[float]:
234 return [self.
x, self.
y, self.
z, self.
w]
238 """Check if value is a finite number (not NaN or inf)."""
240 float_value = float(value)
241 return math.isfinite(float_value)
242 except (ValueError, TypeError, OverflowError):
248 _fields_ = [(
'r', ctypes.c_float), (
'g', ctypes.c_float), (
'b', ctypes.c_float)]
251 return f
'RGBcolor({self.r}, {self.g}, {self.b})'
254 return f
'RGBcolor({self.r}, {self.g}, {self.b})'
256 def __init__(self, r:float=0, g:float=0, b:float=0):
258 self._validate_color_component(r,
'r')
259 self._validate_color_component(g,
'g')
260 self._validate_color_component(b,
'b')
266 def from_list(self, input_list:List[float]):
267 self.r = input_list[0]
268 self.g = input_list[1]
269 self.b = input_list[2]
272 return [self.
r, self.
g, self.
b]
276 """Check if value is a finite number (not NaN or inf)."""
278 float_value = float(value)
279 return math.isfinite(float_value)
280 except (ValueError, TypeError, OverflowError):
284 """Validate a color component is finite and in range [0,1]."""
286 raise ValueError(f
"RGBcolor.{component_name} must be a finite number, "
287 f
"got {type(value).__name__}: {value}. "
288 f
"Color components must be finite values between 0 and 1.")
290 if not (0.0 <= value <= 1.0):
291 raise ValueError(f
"RGBcolor.{component_name}={value} is outside valid range [0,1]. "
292 f
"Color components must be normalized values between 0 and 1.")
298 _fields_ = [(
'r', ctypes.c_float), (
'g', ctypes.c_float), (
'b', ctypes.c_float), (
'a', ctypes.c_float)]
301 return f
'RGBAcolor({self.r}, {self.g}, {self.b}, {self.a})'
304 return f
'RGBAcolor({self.r}, {self.g}, {self.b}, {self.a})'
306 def __init__(self, r:float=0, g:float=0, b:float=0, a:float=0):
318 def from_list(self, input_list:List[float]):
319 self.
r = input_list[0]
320 self.
g = input_list[1]
321 self.
b = input_list[2]
322 self.a = input_list[3]
324 def to_list(self) -> List[float]:
325 return [self.
r, self.
g, self.
b, self.a]
329 """Check if value is a finite number (not NaN or inf)."""
331 float_value = float(value)
332 return math.isfinite(float_value)
333 except (ValueError, TypeError, OverflowError):
337 """Validate a color component is finite and in range [0,1]."""
339 raise ValueError(f
"RGBAcolor.{component_name} must be a finite number, "
340 f
"got {type(value).__name__}: {value}. "
341 f
"Color components must be finite values between 0 and 1.")
343 if not (0.0 <= value <= 1.0):
344 raise ValueError(f
"RGBAcolor.{component_name}={value} is outside valid range [0,1]. "
345 f
"Color components must be normalized values between 0 and 1.")
351 (
'radius', ctypes.c_float),
352 (
'elevation', ctypes.c_float),
353 (
'zenith', ctypes.c_float),
354 (
'azimuth', ctypes.c_float)
358 return f
'SphericalCoord({self.radius}, {self.elevation}, {self.zenith}, {self.azimuth})'
361 return f
'SphericalCoord({self.radius}, {self.elevation}, {self.zenith}, {self.azimuth})'
363 def __init__(self, radius:float=1, elevation:float=0, azimuth:float=0):
365 Initialize SphericalCoord matching C++ constructor.
368 radius: Radius (default: 1)
369 elevation: Elevation angle in radians (default: 0)
370 azimuth: Azimuthal angle in radians (default: 0)
372 Note: zenith is automatically computed as (Ï€/2 - elevation) to match C++ behavior
376 raise ValueError(f
"SphericalCoord.radius must be a positive finite number, "
377 f
"got {type(radius).__name__}: {radius}. "
378 f
"Radius must be greater than 0.")
381 raise ValueError(f
"SphericalCoord.elevation must be a finite number, "
382 f
"got {type(elevation).__name__}: {elevation}. "
383 f
"Elevation angle must be finite (not NaN or infinity).")
386 raise ValueError(f
"SphericalCoord.azimuth must be a finite number, "
387 f
"got {type(azimuth).__name__}: {azimuth}. "
388 f
"Azimuth angle must be finite (not NaN or infinity).")
390 self.
radius = float(radius)
392 self.
zenith = 0.5 * math.pi - elevation
395 def from_list(self, input_list:List[float]):
396 self.
radius = input_list[0]
406 """Check if value is a finite number (not NaN or inf)."""
408 float_value = float(value)
409 return math.isfinite(float_value)
410 except (ValueError, TypeError, OverflowError):
417 Axis rotation structure for specifying shoot orientation in PlantArchitecture.
419 Represents rotation using pitch, yaw, and roll angles in degrees.
420 Used to define the orientation of shoots, stems, and branches during plant construction.
423 (
'pitch', ctypes.c_float),
424 (
'yaw', ctypes.c_float),
425 (
'roll', ctypes.c_float)
429 return f
'AxisRotation({self.pitch}, {self.yaw}, {self.roll})'
432 return f
'AxisRotation({self.pitch}, {self.yaw}, {self.roll})'
434 def __init__(self, pitch:float=0, yaw:float=0, roll:float=0):
436 Initialize AxisRotation with pitch, yaw, and roll angles.
439 pitch: Pitch angle in degrees (rotation about transverse axis)
440 yaw: Yaw angle in degrees (rotation about vertical axis)
441 roll: Roll angle in degrees (rotation about longitudinal axis)
444 ValueError: If any angle value is not finite
448 raise ValueError(f
"AxisRotation.pitch must be a finite number, got {type(pitch).__name__}: {pitch}. "
449 f
"Rotation angles must be finite (not NaN or infinity).")
451 raise ValueError(f
"AxisRotation.yaw must be a finite number, got {type(yaw).__name__}: {yaw}. "
452 f
"Rotation angles must be finite (not NaN or infinity).")
454 raise ValueError(f
"AxisRotation.roll must be a finite number, got {type(roll).__name__}: {roll}. "
455 f
"Rotation angles must be finite (not NaN or infinity).")
457 self.pitch = float(pitch)
458 self.yaw = float(yaw)
459 self.roll = float(roll)
461 def from_list(self, input_list:List[float]):
462 """Initialize from list [pitch, yaw, roll]"""
463 if len(input_list) < 3:
464 raise ValueError(
"AxisRotation.from_list requires a list with at least 3 elements [pitch, yaw, roll]")
465 self.
pitch = input_list[0]
466 self.
yaw = input_list[1]
467 self.
roll = input_list[2]
469 def to_list(self) -> List[float]:
470 """Convert to list [pitch, yaw, roll]"""
475 """Check if value is a finite number (not NaN or inf)."""
477 float_value = float(value)
478 return math.isfinite(float_value)
479 except (ValueError, TypeError, OverflowError):
485 """Make an int2 from two integers"""
488def make_SphericalCoord(elevation_radians: float, azimuth_radians: float) -> SphericalCoord:
490 Make a SphericalCoord by specifying elevation and azimuth (C++ API compatibility).
493 elevation_radians: Elevation angle in radians
494 azimuth_radians: Azimuthal angle in radians
497 SphericalCoord with radius=1, and automatically computed zenith
499 return SphericalCoord(radius=1, elevation=elevation_radians, azimuth=azimuth_radians)
501def make_int3(x: int, y: int, z: int) -> int3:
502 """Make an int3 from three integers"""
506 """Make an int4 from four integers"""
507 return int4(x, y, z, w)
509def make_vec2(x: float, y: float) -> vec2:
510 """Make a vec2 from two floats"""
513def make_vec3(x: float, y: float, z: float) -> vec3:
514 """Make a vec3 from three floats"""
517def make_vec4(x: float, y: float, z: float, w: float) -> vec4:
518 """Make a vec4 from four floats"""
519 return vec4(x, y, z, w)
522 """Make an RGBcolor from three floats"""
525def make_RGBAcolor(r: float, g: float, b: float, a: float) -> RGBAcolor:
526 """Make an RGBAcolor from four floats"""
530 """Make an AxisRotation from three angles in degrees"""
534class Time(ctypes.Structure):
535 """Helios Time structure for representing time values."""
536 _fields_ = [(
'second', ctypes.c_int32), (
'minute', ctypes.c_int32), (
'hour', ctypes.c_int32)]
538 def __repr__(self) -> str:
539 return f
'Time({self.hour:02d}:{self.minute:02d}:{self.second:02d})'
541 def __str__(self) -> str:
542 return f
'{self.hour:02d}:{self.minute:02d}:{self.second:02d}'
544 def __init__(self, hour: int = 0, minute: int = 0, second: int = 0):
546 Initialize a Time object.
550 minute: Minute (0-59)
551 second: Second (0-59)
554 if not isinstance(hour, int):
555 raise ValueError(f
"Time.hour must be an integer, got {type(hour).__name__}: {hour}")
556 if not isinstance(minute, int):
557 raise ValueError(f
"Time.minute must be an integer, got {type(minute).__name__}: {minute}")
558 if not isinstance(second, int):
559 raise ValueError(f
"Time.second must be an integer, got {type(second).__name__}: {second}")
561 if hour < 0
or hour > 23:
562 raise ValueError(f
"Time.hour must be between 0 and 23, got: {hour}")
563 if minute < 0
or minute > 59:
564 raise ValueError(f
"Time.minute must be between 0 and 59, got: {minute}")
565 if second < 0
or second > 59:
566 raise ValueError(f
"Time.second must be between 0 and 59, got: {second}")
572 def from_list(self, input_list: List[int]):
573 """Initialize from a list [hour, minute, second]"""
574 if len(input_list) < 3:
575 raise ValueError(
"Time.from_list requires a list with at least 3 elements [hour, minute, second]")
576 self.hour = input_list[0]
577 self.minute = input_list[1]
578 self.second = input_list[2]
580 def to_list(self) -> List[int]:
581 """Convert to list [hour, minute, second]"""
582 return [self.hour, self.minute, self.second]
584 def __eq__(self, other) -> bool:
585 """Check equality with another Time object"""
586 if not isinstance(other, Time):
588 return (self.
hour == other.hour
and
590 self.
second == other.second)
593 """Check inequality with another Time object"""
594 return not self.
__eq__(other)
597class Date(ctypes.Structure):
598 """Helios Date structure for representing date values."""
599 _fields_ = [(
'day', ctypes.c_int32), (
'month', ctypes.c_int32), (
'year', ctypes.c_int32)]
602 return f
'Date({self.year}-{self.month:02d}-{self.day:02d})'
605 return f
'{self.year}-{self.month:02d}-{self.day:02d}'
607 def __init__(self, year: int = 2023, month: int = 1, day: int = 1):
609 Initialize a Date object.
612 year: Year (1900-3000)
617 if not isinstance(year, int):
618 raise ValueError(f
"Date.year must be an integer, got {type(year).__name__}: {year}")
619 if not isinstance(month, int):
620 raise ValueError(f
"Date.month must be an integer, got {type(month).__name__}: {month}")
621 if not isinstance(day, int):
622 raise ValueError(f
"Date.day must be an integer, got {type(day).__name__}: {day}")
624 if year < 1900
or year > 3000:
625 raise ValueError(f
"Date.year must be between 1900 and 3000, got: {year}")
626 if month < 1
or month > 12:
627 raise ValueError(f
"Date.month must be between 1 and 12, got: {month}")
628 if day < 1
or day > 31:
629 raise ValueError(f
"Date.day must be between 1 and 31, got: {day}")
635 def from_list(self, input_list: List[int]):
636 """Initialize from a list [year, month, day]"""
637 if len(input_list) < 3:
638 raise ValueError(
"Date.from_list requires a list with at least 3 elements [year, month, day]")
639 self.year = input_list[0]
640 self.month = input_list[1]
641 self.day = input_list[2]
643 def to_list(self) -> List[int]:
644 """Convert to list [year, month, day]"""
645 return [self.year, self.month, self.day]
647 def __eq__(self, other) -> bool:
648 """Check equality with another Date object"""
649 if not isinstance(other, Date):
651 return (self.
year == other.year
and
652 self.
month == other.month
and
656 """Check inequality with another Date object"""
657 return not self.
__eq__(other)
660def make_Time(hour: int, minute: int, second: int) -> Time:
661 """Make a Time from hour, minute, second"""
662 return Time(hour, minute, second)
664def make_Date(year: int, month: int, day: int) -> Date:
665 """Make a Date from year, month, day"""
666 return Date(year, month, day)
Axis rotation structure for specifying shoot orientation in PlantArchitecture.
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
List[float] to_list(self)
Convert to list [pitch, yaw, roll].
Helios Date structure for representing date values.
bool __ne__(self, other)
Check inequality with another Date object.
bool __eq__(self, other)
Check equality with another Date object.
Helios primitive type enumeration.
_validate_color_component(self, value, component_name)
Validate a color component is finite and in range [0,1].
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
__init__(self, float r=0, float g=0, float b=0, float a=0)
__init__(self, float r=0, float g=0, float b=0)
_validate_color_component(self, value, component_name)
Validate a color component is finite and in range [0,1].
from_list(self, List[float] input_list)
List[float] to_list(self)
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
List[float] to_list(self)
from_list(self, List[float] input_list)
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
__init__(self, float radius=1, float elevation=0, float azimuth=0)
Initialize SphericalCoord matching C++ constructor.
Helios Time structure for representing time values.
List[int] to_list(self)
Convert to list [hour, minute, second].
from_list(self, List[int] input_list)
Initialize from a list [hour, minute, second].
__init__(self, int hour=0, int minute=0, int second=0)
Initialize a Time object.
bool __eq__(self, other)
Check equality with another Time object.
bool __ne__(self, other)
Check inequality with another Time object.
from_list(self, List[int] input_list)
__init__(self, int x=0, int y=0)
__init__(self, int x=0, int y=0, int z=0)
from_list(self, List[int] input_list)
from_list(self, List[int] input_list)
__init__(self, int x=0, int y=0, int z=0, int w=0)
from_list(self, List[float] input_list)
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
List[float] to_list(self)
__init__(self, float x=0, float y=0)
List[float] to_list(self)
__init__(self, float x=0, float y=0, float z=0)
from_list(self, List[float] input_list)
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
List[float] to_list(self)
bool _is_finite_numeric(value)
Check if value is a finite number (not NaN or inf).
__init__(self, float x=0, float y=0, float z=0, float w=0)
from_list(self, List[float] input_list)
RGBAcolor make_RGBAcolor(float r, float g, float b, float a)
Make an RGBAcolor from four floats.
AxisRotation make_AxisRotation(float pitch, float yaw, float roll)
Make an AxisRotation from three angles in degrees.
int3 make_int3(int x, int y, int z)
Make an int3 from three integers.
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.
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.
SphericalCoord make_SphericalCoord(float elevation_radians, float azimuth_radians)
Make a SphericalCoord by specifying elevation and azimuth (C++ API compatibility).
vec2 make_vec2(float x, float y)
Make a vec2 from two floats.
vec4 make_vec4(float x, float y, float z, float w)
Make a vec4 from four floats.
int2 make_int2(int x, int y)
Make an int2 from two integers.
vec3 make_vec3(float x, float y, float z)
Make a vec3 from three floats.