0.1.39
Loading...
Searching...
No Matches
WeberPennTree.py
Go to the documentation of this file.
1# import ctypes
2# from pyhelios import Context
3# from wrappers import UWeberPennTreeWrapper as wpt_wrapper
4# from enum import Enum
5# from typing import List
6# from wrappers import Vec3
7
8import ctypes
9import math
10import os
11from contextlib import contextmanager
12from dataclasses import dataclass, field, fields as dataclass_fields
13from enum import Enum
14from pathlib import Path
15from typing import List, Union
16
17from .wrappers import UWeberPennTreeWrapper as wpt_wrapper
18from .wrappers.DataTypes import vec3, RGBcolor
19from .plugins.registry import get_plugin_registry, graceful_plugin_fallback
20from .validation.plugins import validate_wpt_parameters
21from .validation.datatypes import validate_vec3
22from .validation.core import validate_positive_value
23from .assets import get_asset_manager
24from .validation.plugin_decorators import (
25 validate_tree_uuid_params, validate_recursion_params, validate_trunk_segment_params,
26 validate_branch_segment_params, validate_leaf_subdivisions_params, validate_xml_file_params
27)
28
29
31 """
32 Check if WeberPennTree plugin is available for use.
33
34 Returns:
35 bool: True if WeberPennTree can be used, False otherwise
36 """
37 try:
38 # Check plugin registry
39 plugin_registry = get_plugin_registry()
40 if not plugin_registry.is_plugin_available('weberpenntree'):
41 return False
42
43 # Check if wrapper functions are available
44 if not wpt_wrapper._WPT_FUNCTIONS_AVAILABLE:
45 return False
46
47 return True
48 except Exception:
49 return False
50
51from .Context import Context, check_context_alive
52
53@contextmanager
55 """
56 Context manager that temporarily changes working directory to where WeberPennTree assets are located.
57
58 WeberPennTree C++ code uses hardcoded relative paths like "plugins/weberpenntree/xml/WeberPennTreeLibrary.xml"
59 expecting assets relative to working directory. This manager temporarily changes to the build directory
60 where assets are actually located.
61
62 Raises:
63 RuntimeError: If build directory or WeberPennTree assets are not found, indicating a build system error.
64 """
65 # Find the build directory containing WeberPennTree assets
66 # Try asset manager first (works for both development and wheel installations)
67 asset_manager = get_asset_manager()
68 working_dir = asset_manager._get_helios_build_path()
69
70 if working_dir and working_dir.exists():
71 weberpenntree_assets = working_dir / 'plugins' / 'weberpenntree'
72 else:
73 # For wheel installations, check packaged assets
74 current_dir = Path(__file__).parent
75 packaged_build = current_dir / 'assets' / 'build'
76
77 if packaged_build.exists():
78 working_dir = packaged_build
79 weberpenntree_assets = working_dir / 'plugins' / 'weberpenntree'
80 else:
81 # Fallback to development paths
82 repo_root = current_dir.parent
83 build_lib_dir = repo_root / 'pyhelios_build' / 'build' / 'lib'
84 working_dir = build_lib_dir.parent
85 weberpenntree_assets = working_dir / 'plugins' / 'weberpenntree'
86
87 if not build_lib_dir.exists():
88 raise RuntimeError(
89 f"PyHelios build directory not found: {build_lib_dir}. "
90 f"WeberPennTree requires native libraries to be built. "
91 f"Run: python build_scripts/build_helios.py --plugins weberpenntree"
92 )
93
94 if not weberpenntree_assets.exists():
95 raise RuntimeError(
96 f"WeberPennTree assets not found: {weberpenntree_assets}. "
97 f"Build system failed to copy WeberPennTree assets. "
98 f"Run: python build_scripts/build_helios.py --clean --plugins weberpenntree"
99 )
100
101 xml_file = weberpenntree_assets / 'xml' / 'WeberPennTreeLibrary.xml'
102 if not xml_file.exists():
103 raise RuntimeError(
104 f"WeberPennTree XML library not found: {xml_file}. "
105 f"Critical WeberPennTree asset missing from build. "
106 f"Run: python build_scripts/build_helios.py --clean --plugins weberpenntree"
107 )
108
109 # Change to build directory where assets are located
110 original_cwd = Path.cwd()
111 try:
112 os.chdir(working_dir)
113 yield
114 finally:
115 os.chdir(original_cwd)
116
117class WPTType(Enum):
118 ALMOND = 'Almond'
119 APPLE = 'Apple'
120 AVOCADO = 'Avocado'
121 LEMON = 'Lemon'
122 OLIVE = 'Olive'
123 ORANGE = 'Orange'
124 PEACH = 'Peach'
125 PISTACHIO = 'Pistachio'
126 WALNUT = 'Walnut'
128_INT32_MIN, _INT32_MAX = -2**31, 2**31 - 1
129_UINT32_MAX = 2**32 - 1
130_MAX_LEVELS = 3
131
132# Per-level arrays: indexed by recursion level, so each needs at least Levels+1 entries.
133_PER_LEVEL_FLOAT_FIELDS = (
134 'nSegSplits', 'nSplitAngle', 'nSplitAngleV', 'nCurve', 'nCurveV', 'nCurveBack', 'nLength', 'nLengthV',
135 'nTaper', 'nDownAngle', 'nDownAngleV', 'nRotate', 'nRotateV',
136)
137_PER_LEVEL_INT_FIELDS = ('nCurveRes', 'nBranches')
138_SCALAR_FLOAT_FIELDS = (
139 'BaseSize', 'BaseSizeV', 'BaseSplitSize', 'BaseSplitSizeV', 'Scale', 'ScaleV', 'ZScale', 'ZScaleV',
140 'Ratio', 'RatioPower', 'LobeDepth', 'Flare', 'LeafScale', 'LeafScaleX',
141)
142_SCALAR_INT_FIELDS = ('Shape', 'Lobes', 'Levels', 'Leaves', 'LeafShape')
143_SCALAR_UINT_FIELDS = ('BaseSplits', 'BaseSplitsV')
144_STRING_FIELDS = ('label', 'LeafFile', 'WoodFile')
145
146
147@dataclass(eq=False)
149 """
150 Architectural parameters of a Weber-Penn tree, mirroring the C++ ``WeberPennTreeParameters`` struct.
152 Field names and meanings are identical to the C++ struct and to the tags of the XML tree library
153 (see the "Creating Custom XML Files" section of the plugin documentation). Per-level arrays
154 (``nSegSplits`` ... ``nBranches``) are indexed by recursion level (0 = trunk) and must hold at least
155 ``Levels + 1`` entries. ``leafAngleCDF`` is the cumulative leaf inclination distribution computed from
156 the XML ``LeafAngleDist`` tag (empty means uniform). Float values are stored as 32-bit floats natively.
157
158 The usual way to obtain one is :meth:`WeberPennTree.getTreeParameters`, modify it, and pass it to
159 :meth:`WeberPennTree.setTreeParameters`.
160 """
161 label: str = ''
162 Shape: int = 0
163 BaseSize: float = 0.0
164 BaseSizeV: float = 0.0
165 BaseSplits: int = 0
166 BaseSplitsV: int = 0
167 BaseSplitSize: float = 0.0
168 BaseSplitSizeV: float = 0.0
169 Scale: float = 0.0
170 ScaleV: float = 0.0
171 ZScale: float = 0.0
172 ZScaleV: float = 0.0
173 Ratio: float = 0.0
174 RatioPower: float = 0.0
175 Lobes: int = 0
176 LobeDepth: float = 0.0
177 Flare: float = 0.0
178 Levels: int = 0
179 nSegSplits: List[float] = field(default_factory=list)
180 nSplitAngle: List[float] = field(default_factory=list)
181 nSplitAngleV: List[float] = field(default_factory=list)
182 nCurveRes: List[int] = field(default_factory=list)
183 nCurve: List[float] = field(default_factory=list)
184 nCurveV: List[float] = field(default_factory=list)
185 nCurveBack: List[float] = field(default_factory=list)
186 nLength: List[float] = field(default_factory=list)
187 nLengthV: List[float] = field(default_factory=list)
188 nTaper: List[float] = field(default_factory=list)
189 nDownAngle: List[float] = field(default_factory=list)
190 nDownAngleV: List[float] = field(default_factory=list)
191 nRotate: List[float] = field(default_factory=list)
192 nRotateV: List[float] = field(default_factory=list)
193 nBranches: List[int] = field(default_factory=list)
194 LeafFile: str = ''
195 Leaves: int = 0
196 LeafShape: int = 0
197 LeafScale: float = 0.0
198 LeafScaleX: float = 0.0
199 WoodColor: RGBcolor = field(default_factory=lambda: RGBcolor(0.4, 0.2, 0.05))
200 WoodFile: str = 'none'
201 leafAngleCDF: List[float] = field(default_factory=list)
203 def __eq__(self, other):
204 if not isinstance(other, WeberPennTreeParameters):
205 return NotImplemented
206 for f in dataclass_fields(self):
207 a, b = getattr(self, f.name), getattr(other, f.name)
208 if f.name == 'WoodColor':
209 if not (isinstance(a, RGBcolor) and isinstance(b, RGBcolor)) or a.to_list() != b.to_list():
210 return False
211 elif isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
212 if list(a) != list(b):
213 return False
214 elif a != b:
215 return False
216 return True
218 def _to_wrapper_fields(self) -> dict:
219 result = {f.name: getattr(self, f.name) for f in dataclass_fields(self)}
220 result['WoodColor'] = self.WoodColor.to_list()
221 return result
223 @classmethod
224 def _from_wrapper_fields(cls, raw: dict) -> 'WeberPennTreeParameters':
225 values = dict(raw)
226 wood = values['WoodColor']
227 color = RGBcolor()
228 color.r, color.g, color.b = wood[0], wood[1], wood[2]
229 values['WoodColor'] = color
230 return cls(**values)
231
232 def validate(self) -> None:
233 """
234 Check field types, ranges and per-level array lengths.
235
236 Raises:
237 ValueError: If any field has the wrong type, is out of range, or a per-level array is too short.
238 """
239 def is_int(v):
240 return isinstance(v, int) and not isinstance(v, bool)
241
242 def is_real(v):
243 return isinstance(v, (int, float)) and not isinstance(v, bool) and math.isfinite(v)
245 for name in _STRING_FIELDS:
246 v = getattr(self, name)
247 if not isinstance(v, str):
248 raise ValueError(f"WeberPennTreeParameters.{name} must be a str, got {type(v).__name__}")
249 if '\x00' in v:
250 raise ValueError(f"WeberPennTreeParameters.{name} must not contain NUL characters")
251 for name in _SCALAR_FLOAT_FIELDS:
252 v = getattr(self, name)
253 if not is_real(v):
254 raise ValueError(f"WeberPennTreeParameters.{name} must be a finite number, got {type(v).__name__}: {v!r}")
255 for name in _SCALAR_INT_FIELDS:
256 v = getattr(self, name)
257 if not is_int(v) or not (_INT32_MIN <= v <= _INT32_MAX):
258 raise ValueError(f"WeberPennTreeParameters.{name} must be a 32-bit int, got {type(v).__name__}: {v!r}")
259 for name in _SCALAR_UINT_FIELDS:
260 v = getattr(self, name)
261 if not is_int(v) or not (0 <= v <= _UINT32_MAX):
262 raise ValueError(f"WeberPennTreeParameters.{name} must be a non-negative 32-bit int, got {type(v).__name__}: {v!r}")
263 if not isinstance(self.WoodColor, RGBcolor):
264 raise ValueError(f"WeberPennTreeParameters.WoodColor must be an RGBcolor, got {type(self.WoodColor).__name__}")
265 for name in _PER_LEVEL_FLOAT_FIELDS + ('leafAngleCDF',):
266 v = getattr(self, name)
267 if not isinstance(v, (list, tuple)) or not all(is_real(x) for x in v):
268 raise ValueError(f"WeberPennTreeParameters.{name} must be a list of finite numbers, got {v!r}")
269 for name in _PER_LEVEL_INT_FIELDS:
270 v = getattr(self, name)
271 if not isinstance(v, (list, tuple)) or not all(is_int(x) and _INT32_MIN <= x <= _INT32_MAX for x in v):
272 raise ValueError(f"WeberPennTreeParameters.{name} must be a list of 32-bit ints, got {v!r}")
273
274 if not (1 <= self.Levels <= _MAX_LEVELS):
275 raise ValueError(
276 f"WeberPennTreeParameters.Levels is {self.Levels}, but it must be between 1 and {_MAX_LEVELS} "
277 f"(level 0 is the trunk and leaves are placed at level Levels)."
278 )
279 required = self.Levels + 1
280 for name in _PER_LEVEL_FLOAT_FIELDS + _PER_LEVEL_INT_FIELDS:
281 n = len(getattr(self, name))
282 if n < required:
283 raise ValueError(
284 f"WeberPennTreeParameters.{name} has {n} entries, but Levels={self.Levels} requires at least {required}."
285 )
286
287
288class WeberPennTree:
289 WPTType = WPTType # Make WPTType accessible as class attribute
290
291 def __init__(self, context:Context):
292 self.context = context
293 self._plugin_registry = get_plugin_registry()
294
295 # Check if weberpenntree functions are available at the wrapper level first
296 from .wrappers.UWeberPennTreeWrapper import _WPT_FUNCTIONS_AVAILABLE
297 if not _WPT_FUNCTIONS_AVAILABLE:
298 raise NotImplementedError("WeberPennTree functions not available in current Helios library.")
299
300 # Check if weberpenntree plugin is available in registry
301 if not self._plugin_registry.is_plugin_available('weberpenntree'):
302 print("Warning: WeberPennTree plugin not detected in current build")
303 print("Tree generation functionality may be limited or unavailable")
304
305 # Find build directory for asset loading using asset manager
306 asset_manager = get_asset_manager()
307 build_dir = asset_manager._get_helios_build_path()
308
309 if not build_dir or not build_dir.exists():
310 # In wheel installations, try packaged assets location
311 current_dir = Path(__file__).parent
312 packaged_build = current_dir / 'assets' / 'build'
313
314 if packaged_build.exists() and (packaged_build / 'plugins' / 'weberpenntree').exists():
315 build_dir = packaged_build
316 else:
317 # Fallback to development build directory
318 repo_root = current_dir.parent
319 build_lib_dir = repo_root / 'pyhelios_build' / 'build' / 'lib'
320 build_dir = build_lib_dir.parent
321
322 if not build_dir.exists():
323 raise RuntimeError(
324 f"PyHelios build directory not found: {build_dir}. "
325 f"WeberPennTree requires native libraries to be built. "
326 f"Run: python build_scripts/build_helios.py --plugins weberpenntree"
327 )
328
329 # Use the same build_dir for working directory as we use for C++ interface
330 # This ensures consistency between Python working directory and C++ asset paths
331 original_cwd = Path.cwd()
332 try:
333 os.chdir(build_dir)
334 self.wpt = wpt_wrapper.createWeberPennTreeWithBuildPluginRootDirectory(
335 context.getNativePtr(), str(build_dir)
336 )
337 finally:
338 os.chdir(original_cwd)
339
340 def _check_context_alive(self):
341 """Raise if the owning Context has been destroyed (see Context.check_context_alive)."""
342 check_context_alive(getattr(self, "context", None), "WeberPennTree")
343
344 def __enter__(self):
345 return self
346
347 def __exit__(self, exc_type, exc_value, traceback):
348 if self.wpt is not None:
349 try:
350 wpt_wrapper.destroyWeberPennTree(self.wpt)
351 finally:
352 self.wpt = None # Prevent double deletion
353
354 def __del__(self):
355 """Destructor to ensure C++ resources freed even without 'with' statement."""
356 if hasattr(self, 'wpt') and self.wpt is not None:
357 try:
358 wpt_wrapper.destroyWeberPennTree(self.wpt)
359 self.wpt = None
360 except Exception as e:
361 import warnings
362 warnings.warn(f"Error in WeberPennTree.__del__: {e}")
363
364 def getNativePtr(self):
365 return self.wpt
366
367
368 def buildTree(self, wpt_type, origin:vec3=vec3(0, 0, 0), scale:float=1) -> int:
369 """
370 Build a tree using either a built-in tree type or custom species name.
371
372 Args:
373 wpt_type: Either WPTType enum for built-in types, or string for custom species loaded via loadXML()
374 origin: Tree origin position (default: vec3(0, 0, 0))
375 scale: Tree scale factor (default: 1.0)
376
377 Returns:
378 Tree ID for querying tree components
379 """
380 # Validate inputs
381 validate_vec3(origin, "origin", "buildTree")
382 validate_positive_value(scale, "scale", "buildTree")
384 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
385 raise RuntimeError(
386 f"WeberPennTree is not properly initialized. "
387 f"This may indicate that the weberpenntree plugin is not available. "
388 f"Check plugin status with context.print_plugin_status()"
389 )
390
391 # Convert wpt_type to string (handle both WPTType enum and string)
392 if isinstance(wpt_type, WPTType):
393 tree_name = wpt_type.value
394 elif isinstance(wpt_type, str):
395 tree_name = wpt_type
396 else:
397 raise TypeError(f"wpt_type must be WPTType enum or string, got {type(wpt_type).__name__}")
398
399 # Use working directory context manager during tree building to access assets
401 # Use scale-aware function if scale is not 1.0, otherwise use regular function
402 if scale != 1.0:
404 return wpt_wrapper.buildTreeWithScale(self.wpt, tree_name, origin.to_list(), scale)
405 else:
407 return wpt_wrapper.buildTree(self.wpt, tree_name, origin.to_list())
408
409 @validate_tree_uuid_params
410 def getTrunkUUIDs(self, tree_id:int) -> List[int]:
411 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
412 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
414 return wpt_wrapper.getTrunkUUIDs(self.wpt, tree_id)
415
416 @validate_tree_uuid_params
417 def getBranchUUIDs(self, tree_id:int) -> List[int]:
418 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
419 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
421 return wpt_wrapper.getBranchUUIDs(self.wpt, tree_id)
422
423 @validate_tree_uuid_params
424 def getLeafUUIDs(self, tree_id:int) -> List[int]:
425 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
426 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
428 return wpt_wrapper.getLeafUUIDs(self.wpt, tree_id)
429
430 @validate_tree_uuid_params
431 def getAllUUIDs(self, tree_id:int) -> List[int]:
432 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
433 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
435 return wpt_wrapper.getAllUUIDs(self.wpt, tree_id)
436
437 @validate_recursion_params
438 def setBranchRecursionLevel(self, level:int) -> None:
439 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
440 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
442 wpt_wrapper.setBranchRecursionLevel(self.wpt, level)
443
444 @validate_trunk_segment_params
445 def setTrunkSegmentResolution(self, trunk_segs:int) -> None:
446 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
447 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
449 wpt_wrapper.setTrunkSegmentResolution(self.wpt, trunk_segs)
450
451 @validate_branch_segment_params
452 def setBranchSegmentResolution(self, branch_segs:int) -> None:
453 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
454 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
456 wpt_wrapper.setBranchSegmentResolution(self.wpt, branch_segs)
457
458 @validate_leaf_subdivisions_params
459 def setLeafSubdivisions(self, leaf_segs_x:int, leaf_segs_y:int) -> None:
460 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
461 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
463 wpt_wrapper.setLeafSubdivisions(self.wpt, leaf_segs_x, leaf_segs_y)
464
465 @validate_xml_file_params
466 def loadXML(self, filename: str, silent: bool = False) -> None:
467 """
468 Load custom tree species from XML file.
469
470 Loads tree species definitions from an XML file into the WeberPennTree library.
471 After loading, trees can be built using buildTree() with the custom species names
472 defined in the XML file.
474 Args:
475 filename: Path to XML file containing tree species definitions.
476 Can be absolute or relative to current working directory.
477 silent: If True, suppress console output during loading. Default False.
478
479 Raises:
480 ValueError: If filename is invalid or empty
481 FileNotFoundError: If XML file does not exist
482 HeliosRuntimeError: If XML file is malformed or cannot be parsed
483
484 Example:
485 >>> wpt = WeberPennTree(context)
486 >>> wpt.loadXML("my_custom_trees.xml")
487 >>> tree_id = wpt.buildTree("CustomOak") # Use custom species name
488
489 Note:
490 XML file must follow WeberPennTree XML schema. See "Creating Custom XML Files" in the
491 WeberPennTree plugin documentation for the format.
492 """
493 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
494 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
495
496 # Convert relative path to absolute BEFORE changing working directory
497 xml_path = Path(filename)
498 if not xml_path.is_absolute():
499 xml_path = xml_path.resolve()
500
501 # Use working directory context manager for C++ asset access
504 wpt_wrapper.loadXML(self.wpt, str(xml_path), silent)
505
506 @staticmethod
507 def _tree_name(tree_name: Union[WPTType, str], method: str) -> str:
508 if isinstance(tree_name, WPTType):
509 return tree_name.value
510 if isinstance(tree_name, str):
511 if not tree_name.strip():
512 raise ValueError(f"{method}: tree_name must be a non-empty string")
513 if '\x00' in tree_name:
514 raise ValueError(f"{method}: tree_name must not contain NUL characters")
515 return tree_name
516 raise ValueError(f"{method}: tree_name must be a WPTType or str, got {type(tree_name).__name__}")
517
518 def getTreeParameters(self, tree_name: Union[WPTType, str]) -> WeberPennTreeParameters:
519 """
520 Get the architectural parameters of a tree in the library.
521
522 Args:
523 tree_name: WPTType for a built-in species, or the name of a species loaded with loadXML()
524 or defined with setTreeParameters().
525
526 Returns:
527 WeberPennTreeParameters with every field of the C++ struct.
528
529 Raises:
530 ValueError: If tree_name is not a WPTType or non-empty string.
531 HeliosRuntimeError: If no tree with that name exists in the library.
532 """
533 name = self._tree_name(tree_name, "getTreeParameters")
534 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
535 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
537 return WeberPennTreeParameters._from_wrapper_fields(wpt_wrapper.getTreeParameters(self.wpt, name))
538
539 def setTreeParameters(self, tree_name: Union[WPTType, str], parameters: WeberPennTreeParameters) -> None:
540 """
541 Set the architectural parameters of a tree in the library.
542
543 If ``tree_name`` is not yet in the library, a new species with that name is added, and
544 ``buildTree(tree_name)`` will then build it. Parameters are validated in Python (types, Levels
545 range, per-level array lengths) and again natively (e.g. Scale > 0, nCurveRes >= 1,
546 0 <= BaseSize < 1); an invalid set leaves the library unchanged.
547
548 Args:
549 tree_name: WPTType or species name.
550 parameters: WeberPennTreeParameters, typically obtained from getTreeParameters() and modified.
551
552 Raises:
553 ValueError: If tree_name or parameters are invalid.
554 HeliosRuntimeError: If the native validation rejects the parameters.
555 """
556 name = self._tree_name(tree_name, "setTreeParameters")
557 if not isinstance(parameters, WeberPennTreeParameters):
558 raise ValueError(
559 f"setTreeParameters: parameters must be a WeberPennTreeParameters, got {type(parameters).__name__}"
561 parameters.validate()
562 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
563 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
565 wpt_wrapper.setTreeParameters(self.wpt, name, parameters._to_wrapper_fields())
566
567 def seedRandomGenerator(self, seed: int) -> None:
568 """
569 Seed the WeberPennTree random number generator to make tree geometry reproducible.
570
571 WeberPennTree has its own generator, independent of Context.seedRandomGenerator(); seeding
572 the Context does not make trees repeatable. The generator advances with each buildTree() call,
573 so re-seed before each build that should be reproduced.
574
575 Args:
576 seed: Non-negative integer below 2**32.
577
578 Raises:
579 ValueError: If seed is not an int in [0, 2**32).
580 """
581 if not isinstance(seed, int) or isinstance(seed, bool) or not (0 <= seed <= _UINT32_MAX):
582 raise ValueError(f"seedRandomGenerator: seed must be an int in [0, 2**32), got {seed!r}")
583 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
584 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
586 wpt_wrapper.seedRandomGenerator(self.wpt, seed)
Architectural parameters of a Weber-Penn tree, mirroring the C++ WeberPennTreeParameters struct.
'WeberPennTreeParameters' _from_wrapper_fields(cls, dict raw)
None validate(self)
Check field types, ranges and per-level array lengths.
List[int] getAllUUIDs(self, int tree_id)
None setBranchSegmentResolution(self, int branch_segs)
None loadXML(self, str filename, bool silent=False)
Load custom tree species from XML file.
List[int] getLeafUUIDs(self, int tree_id)
None setTrunkSegmentResolution(self, int trunk_segs)
List[int] getBranchUUIDs(self, int tree_id)
_check_context_alive(self)
Raise if the owning Context has been destroyed (see Context.check_context_alive).
str _tree_name(Union[WPTType, str] tree_name, str method)
None seedRandomGenerator(self, int seed)
Seed the WeberPennTree random number generator to make tree geometry reproducible.
int buildTree(self, wpt_type, vec3 origin=vec3(0, 0, 0), float scale=1)
Build a tree using either a built-in tree type or custom species name.
List[int] getTrunkUUIDs(self, int tree_id)
None setLeafSubdivisions(self, int leaf_segs_x, int leaf_segs_y)
__exit__(self, exc_type, exc_value, traceback)
WeberPennTreeParameters getTreeParameters(self, Union[WPTType, str] tree_name)
Get the architectural parameters of a tree in the library.
__del__(self)
Destructor to ensure C++ resources freed even without 'with' statement.
None setBranchRecursionLevel(self, int level)
None setTreeParameters(self, Union[WPTType, str] tree_name, WeberPennTreeParameters parameters)
Set the architectural parameters of a tree in the library.
is_weberpenntree_available()
Check if WeberPennTree plugin is available for use.
_weberpenntree_working_directory()
Context manager that temporarily changes working directory to where WeberPennTree assets are located.