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
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)
181 def __exit__(self, exc_type, exc_value, traceback):
182 if self.
wpt is not None:
184 wpt_wrapper.destroyWeberPennTree(self.
wpt)
189 """Destructor to ensure C++ resources freed even without 'with' statement."""
190 if hasattr(self,
'wpt')
and self.
wpt is not None:
192 wpt_wrapper.destroyWeberPennTree(self.
wpt)
194 except Exception
as e:
196 warnings.warn(f
"Error in WeberPennTree.__del__: {e}")
202 def buildTree(self, wpt_type, origin:vec3=
vec3(0, 0, 0), scale:float=1) -> int:
204 Build a tree using either a built-in tree type or custom species name.
207 wpt_type: Either WPTType enum for built-in types, or string for custom species loaded via loadXML()
208 origin: Tree origin position (default: vec3(0, 0, 0))
209 scale: Tree scale factor (default: 1.0)
212 Tree ID for querying tree components
215 validate_vec3(origin,
"origin",
"buildTree")
216 validate_positive_value(scale,
"scale",
"buildTree")
218 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
220 f
"WeberPennTree is not properly initialized. "
221 f
"This may indicate that the weberpenntree plugin is not available. "
222 f
"Check plugin status with context.print_plugin_status()"
226 if isinstance(wpt_type, WPTType):
227 tree_name = wpt_type.value
228 elif isinstance(wpt_type, str):
231 raise TypeError(f
"wpt_type must be WPTType enum or string, got {type(wpt_type).__name__}")
237 return wpt_wrapper.buildTreeWithScale(self.
wpt, tree_name, origin.to_list(), scale)
239 return wpt_wrapper.buildTree(self.
wpt, tree_name, origin.to_list())
241 @validate_tree_uuid_params
243 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
244 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
245 return wpt_wrapper.getTrunkUUIDs(self.
wpt, tree_id)
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.")
251 return wpt_wrapper.getBranchUUIDs(self.
wpt, tree_id)
253 @validate_tree_uuid_params
255 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
256 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
257 return wpt_wrapper.getLeafUUIDs(self.
wpt, tree_id)
259 @validate_tree_uuid_params
261 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
262 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
263 return wpt_wrapper.getAllUUIDs(self.
wpt, tree_id)
265 @validate_recursion_params
267 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
268 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
269 wpt_wrapper.setBranchRecursionLevel(self.
wpt, level)
271 @validate_trunk_segment_params
273 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
274 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
275 wpt_wrapper.setTrunkSegmentResolution(self.
wpt, trunk_segs)
277 @validate_branch_segment_params
279 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
280 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
281 wpt_wrapper.setBranchSegmentResolution(self.
wpt, branch_segs)
283 @validate_leaf_subdivisions_params
285 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
286 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
287 wpt_wrapper.setLeafSubdivisions(self.
wpt, leaf_segs_x, leaf_segs_y)
289 @validate_xml_file_params
290 def loadXML(self, filename: str, silent: bool =
False) ->
None:
292 Load custom tree species from XML file.
294 Loads tree species definitions from an XML file into the WeberPennTree library.
295 After loading, trees can be built using buildTree() with the custom species names
296 defined in the XML file.
299 filename: Path to XML file containing tree species definitions.
300 Can be absolute or relative to current working directory.
301 silent: If True, suppress console output during loading. Default False.
304 ValueError: If filename is invalid or empty
305 FileNotFoundError: If XML file does not exist
306 HeliosRuntimeError: If XML file is malformed or cannot be parsed
309 >>> wpt = WeberPennTree(context)
310 >>> wpt.loadXML("my_custom_trees.xml")
311 >>> tree_id = wpt.buildTree("CustomOak") # Use custom species name
314 XML file must follow WeberPennTree XML schema. See WeberPennTreeLibrary.xml
315 in helios-core/plugins/weberpenntree/xml/ for format examples.
317 if not self.
wpt or not isinstance(self.
wpt, ctypes._Pointer):
318 raise RuntimeError(
"WeberPennTree is not properly initialized. Check plugin availability.")
321 xml_path = Path(filename)
322 if not xml_path.is_absolute():
323 xml_path = xml_path.resolve()
327 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)
__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.