10from contextlib
import contextmanager
12from pathlib
import Path
13from typing
import List
15from .wrappers
import UWeberPennTreeWrapper
as wpt_wrapper
16from .wrappers.DataTypes
import vec3
17from .plugins.registry
import get_plugin_registry, graceful_plugin_fallback
18from .validation.plugins
import validate_wpt_parameters
19from .validation.datatypes
import validate_vec3
20from .validation.core
import validate_positive_value
21from .assets
import get_asset_manager
22from .validation.plugin_decorators
import (
23 validate_tree_uuid_params, validate_recursion_params, validate_trunk_segment_params,
24 validate_branch_segment_params, validate_leaf_subdivisions_params, validate_xml_file_params
30 Check if WeberPennTree plugin is available for use.
33 bool: True if WeberPennTree can be used, False otherwise
37 plugin_registry = get_plugin_registry()
38 if not plugin_registry.is_plugin_available(
'weberpenntree'):
42 if not wpt_wrapper._WPT_FUNCTIONS_AVAILABLE:
49from .Context
import Context, check_context_alive
54 Context manager that temporarily changes working directory to where WeberPennTree assets are located.
56 WeberPennTree C++ code uses hardcoded relative paths like "plugins/weberpenntree/xml/WeberPennTreeLibrary.xml"
57 expecting assets relative to working directory. This manager temporarily changes to the build directory
58 where assets are actually located.
61 RuntimeError: If build directory or WeberPennTree assets are not found, indicating a build system error.
65 asset_manager = get_asset_manager()
66 working_dir = asset_manager._get_helios_build_path()
68 if working_dir
and working_dir.exists():
69 weberpenntree_assets = working_dir /
'plugins' /
'weberpenntree'
72 current_dir = Path(__file__).parent
73 packaged_build = current_dir /
'assets' /
'build'
75 if packaged_build.exists():
76 working_dir = packaged_build
77 weberpenntree_assets = working_dir /
'plugins' /
'weberpenntree'
80 repo_root = current_dir.parent
81 build_lib_dir = repo_root /
'pyhelios_build' /
'build' /
'lib'
82 working_dir = build_lib_dir.parent
83 weberpenntree_assets = working_dir /
'plugins' /
'weberpenntree'
85 if not build_lib_dir.exists():
87 f
"PyHelios build directory not found: {build_lib_dir}. "
88 f
"WeberPennTree requires native libraries to be built. "
89 f
"Run: python build_scripts/build_helios.py --plugins weberpenntree"
92 if not weberpenntree_assets.exists():
94 f
"WeberPennTree assets not found: {weberpenntree_assets}. "
95 f
"Build system failed to copy WeberPennTree assets. "
96 f
"Run: python build_scripts/build_helios.py --clean --plugins weberpenntree"
99 xml_file = weberpenntree_assets /
'xml' /
'WeberPennTreeLibrary.xml'
100 if not xml_file.exists():
102 f
"WeberPennTree XML library not found: {xml_file}. "
103 f
"Critical WeberPennTree asset missing from build. "
104 f
"Run: python build_scripts/build_helios.py --clean --plugins weberpenntree"
108 original_cwd = Path.cwd()
110 os.chdir(working_dir)
113 os.chdir(original_cwd)
123 PISTACHIO =
'Pistachio'
134 from .wrappers.UWeberPennTreeWrapper
import _WPT_FUNCTIONS_AVAILABLE
135 if not _WPT_FUNCTIONS_AVAILABLE:
136 raise NotImplementedError(
"WeberPennTree functions not available in current Helios library.")
140 print(
"Warning: WeberPennTree plugin not detected in current build")
141 print(
"Tree generation functionality may be limited or unavailable")
144 asset_manager = get_asset_manager()
145 build_dir = asset_manager._get_helios_build_path()
147 if not build_dir
or not build_dir.exists():
149 current_dir = Path(__file__).parent
150 packaged_build = current_dir /
'assets' /
'build'
152 if packaged_build.exists()
and (packaged_build /
'plugins' /
'weberpenntree').exists():
153 build_dir = packaged_build
156 repo_root = current_dir.parent
157 build_lib_dir = repo_root /
'pyhelios_build' /
'build' /
'lib'
158 build_dir = build_lib_dir.parent
160 if not build_dir.exists():
162 f
"PyHelios build directory not found: {build_dir}. "
163 f
"WeberPennTree requires native libraries to be built. "
164 f
"Run: python build_scripts/build_helios.py --plugins weberpenntree"
169 original_cwd = Path.cwd()
172 self.
wpt = wpt_wrapper.createWeberPennTreeWithBuildPluginRootDirectory(
173 context.getNativePtr(), str(build_dir)
176 os.chdir(original_cwd)
179 """Raise if the owning Context has been destroyed (see Context.check_context_alive)."""
180 check_context_alive(getattr(self,
"context",
None),
"WeberPennTree")
185 def __exit__(self, exc_type, exc_value, traceback):
188 wpt_wrapper.destroyWeberPennTree(self.
wpt)
193 """Destructor to ensure C++ resources freed even without 'with' statement."""
194 if hasattr(self,
'wpt')
and self.
wpt is not None:
196 wpt_wrapper.destroyWeberPennTree(self.
wpt)
198 except Exception
as e:
200 warnings.warn(f
"Error in WeberPennTree.__del__: {e}")
206 def buildTree(self, wpt_type, origin:vec3=
vec3(0, 0, 0), scale:float=1) -> int:
208 Build a tree using either a built-in tree type or custom species name.
211 wpt_type: Either WPTType enum for built-in types, or string for custom species loaded via loadXML()
212 origin: Tree origin position (default: vec3(0, 0, 0))
213 scale: Tree scale factor (default: 1.0)
216 Tree ID for querying tree components
219 validate_vec3(origin,
"origin",
"buildTree")
220 validate_positive_value(scale,
"scale",
"buildTree")
222 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
224 f
"WeberPennTree is not properly initialized. "
225 f
"This may indicate that the weberpenntree plugin is not available. "
226 f
"Check plugin status with context.print_plugin_status()"
230 if isinstance(wpt_type, WPTType):
231 tree_name = wpt_type.value
232 elif isinstance(wpt_type, str):
235 raise TypeError(f
"wpt_type must be WPTType enum or string, got {type(wpt_type).__name__}")
242 return wpt_wrapper.buildTreeWithScale(self.
wpt, tree_name, origin.to_list(), scale)
245 return wpt_wrapper.buildTree(self.
wpt, tree_name, origin.to_list())
247 @validate_tree_uuid_params
249 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
250 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
252 return wpt_wrapper.getTrunkUUIDs(self.
wpt, tree_id)
254 @validate_tree_uuid_params
256 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
257 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
259 return wpt_wrapper.getBranchUUIDs(self.
wpt, tree_id)
261 @validate_tree_uuid_params
263 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
264 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
266 return wpt_wrapper.getLeafUUIDs(self.
wpt, tree_id)
268 @validate_tree_uuid_params
270 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
271 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
273 return wpt_wrapper.getAllUUIDs(self.
wpt, tree_id)
275 @validate_recursion_params
277 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
278 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
280 wpt_wrapper.setBranchRecursionLevel(self.
wpt, level)
282 @validate_trunk_segment_params
284 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
285 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
287 wpt_wrapper.setTrunkSegmentResolution(self.
wpt, trunk_segs)
289 @validate_branch_segment_params
291 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
292 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
294 wpt_wrapper.setBranchSegmentResolution(self.
wpt, branch_segs)
296 @validate_leaf_subdivisions_params
298 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
299 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
301 wpt_wrapper.setLeafSubdivisions(self.
wpt, leaf_segs_x, leaf_segs_y)
303 @validate_xml_file_params
304 def loadXML(self, filename: str, silent: bool =
False) ->
None:
306 Load custom tree species from XML file.
308 Loads tree species definitions from an XML file into the WeberPennTree library.
309 After loading, trees can be built using buildTree() with the custom species names
310 defined in the XML file.
313 filename: Path to XML file containing tree species definitions.
314 Can be absolute or relative to current working directory.
315 silent: If True, suppress console output during loading. Default False.
318 ValueError: If filename is invalid or empty
319 FileNotFoundError: If XML file does not exist
320 HeliosRuntimeError: If XML file is malformed or cannot be parsed
323 >>> wpt = WeberPennTree(context)
324 >>> wpt.loadXML("my_custom_trees.xml")
325 >>> tree_id = wpt.buildTree("CustomOak") # Use custom species name
328 XML file must follow WeberPennTree XML schema. See WeberPennTreeLibrary.xml
329 in helios-core/plugins/weberpenntree/xml/ for format examples.
331 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
332 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
335 xml_path = Path(filename)
336 if not xml_path.is_absolute():
337 xml_path = xml_path.resolve()
342 wpt_wrapper.loadXML(self.
wpt, str(xml_path), silent)
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).
__init__(self, Context context)
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)
__del__(self)
Destructor to ensure C++ resources freed even without 'with' statement.
None setBranchRecursionLevel(self, int level)
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.