0.1.8
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 os
10from contextlib import contextmanager
11from enum import Enum
12from pathlib import Path
13from typing import List
14
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
25)
26
27
29 """
30 Check if WeberPennTree plugin is available for use.
31
32 Returns:
33 bool: True if WeberPennTree can be used, False otherwise
34 """
35 try:
36 # Check plugin registry
37 plugin_registry = get_plugin_registry()
38 if not plugin_registry.is_plugin_available('weberpenntree'):
39 return False
40
41 # Check if wrapper functions are available
42 if not wpt_wrapper._WPT_FUNCTIONS_AVAILABLE:
43 return False
44
45 return True
46 except Exception:
47 return False
48
49from .Context import Context
50
51@contextmanager
53 """
54 Context manager that temporarily changes working directory to where WeberPennTree assets are located.
55
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.
59
60 Raises:
61 RuntimeError: If build directory or WeberPennTree assets are not found, indicating a build system error.
62 """
63 # Find the build directory containing WeberPennTree assets
64 # Try asset manager first (works for both development and wheel installations)
65 asset_manager = get_asset_manager()
66 working_dir = asset_manager._get_helios_build_path()
67
68 if working_dir and working_dir.exists():
69 weberpenntree_assets = working_dir / 'plugins' / 'weberpenntree'
70 else:
71 # For wheel installations, check packaged assets
72 current_dir = Path(__file__).parent
73 packaged_build = current_dir / 'assets' / 'build'
74
75 if packaged_build.exists():
76 working_dir = packaged_build
77 weberpenntree_assets = working_dir / 'plugins' / 'weberpenntree'
78 else:
79 # Fallback to development paths
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'
84
85 if not build_lib_dir.exists():
86 raise RuntimeError(
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"
90 )
91
92 if not weberpenntree_assets.exists():
93 raise RuntimeError(
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"
97 )
98
99 xml_file = weberpenntree_assets / 'xml' / 'WeberPennTreeLibrary.xml'
100 if not xml_file.exists():
101 raise RuntimeError(
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"
105 )
106
107 # Change to build directory where assets are located
108 original_cwd = Path.cwd()
109 try:
110 os.chdir(working_dir)
111 yield
112 finally:
113 os.chdir(original_cwd)
114
115class WPTType(Enum):
116 ALMOND = 'Almond'
117 APPLE = 'Apple'
118 AVOCADO = 'Avocado'
119 LEMON = 'Lemon'
120 OLIVE = 'Olive'
121 ORANGE = 'Orange'
122 PEACH = 'Peach'
123 PISTACHIO = 'Pistachio'
124 WALNUT = 'Walnut'
127 WPTType = WPTType # Make WPTType accessible as class attribute
129 def __init__(self, context:Context):
130 self.context = context
131 self._plugin_registry = get_plugin_registry()
133 # Check if weberpenntree functions are available at the wrapper level first
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.")
137
138 # Check if weberpenntree plugin is available in registry
139 if not self._plugin_registry.is_plugin_available('weberpenntree'):
140 print("Warning: WeberPennTree plugin not detected in current build")
141 print("Tree generation functionality may be limited or unavailable")
142
143 # Find build directory for asset loading using asset manager
144 asset_manager = get_asset_manager()
145 build_dir = asset_manager._get_helios_build_path()
146
147 if not build_dir or not build_dir.exists():
148 # In wheel installations, try packaged assets location
149 current_dir = Path(__file__).parent
150 packaged_build = current_dir / 'assets' / 'build'
151
152 if packaged_build.exists() and (packaged_build / 'plugins' / 'weberpenntree').exists():
153 build_dir = packaged_build
154 else:
155 # Fallback to development build directory
156 repo_root = current_dir.parent
157 build_lib_dir = repo_root / 'pyhelios_build' / 'build' / 'lib'
158 build_dir = build_lib_dir.parent
159
160 if not build_dir.exists():
161 raise RuntimeError(
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"
165 )
166
167 # Use the same build_dir for working directory as we use for C++ interface
168 # This ensures consistency between Python working directory and C++ asset paths
169 original_cwd = Path.cwd()
170 try:
171 os.chdir(build_dir)
172 self.wpt = wpt_wrapper.createWeberPennTreeWithBuildPluginRootDirectory(
173 context.getNativePtr(), str(build_dir)
175 finally:
176 os.chdir(original_cwd)
177
178 def __enter__(self):
179 return self
181 def __exit__(self, exc_type, exc_value, traceback):
182 wpt_wrapper.destroyWeberPennTree(self.wpt)
184 def getNativePtr(self):
185 return self.wpt
187
188 def buildTree(self, wpt_type:WPTType, origin:vec3=vec3(0, 0, 0), scale:float=1) -> int:
189 # Validate inputs
190 validate_vec3(origin, "origin", "buildTree")
191 validate_positive_value(scale, "scale", "buildTree")
192
193 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
194 raise RuntimeError(
195 f"WeberPennTree is not properly initialized. "
196 f"This may indicate that the weberpenntree plugin is not available. "
197 f"Check plugin status with context.print_plugin_status()"
198 )
199
200 # Use working directory context manager during tree building to access assets
202 # Use scale-aware function if scale is not 1.0, otherwise use regular function
203 if scale != 1.0:
204 return wpt_wrapper.buildTreeWithScale(self.wpt, wpt_type.value, origin.to_list(), scale)
205 else:
206 return wpt_wrapper.buildTree(self.wpt, wpt_type.value, origin.to_list())
207
208 @validate_tree_uuid_params
209 def getTrunkUUIDs(self, tree_id:int) -> List[int]:
210 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
211 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
212 return wpt_wrapper.getTrunkUUIDs(self.wpt, tree_id)
213
214 @validate_tree_uuid_params
215 def getBranchUUIDs(self, tree_id:int) -> List[int]:
216 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
217 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
218 return wpt_wrapper.getBranchUUIDs(self.wpt, tree_id)
219
220 @validate_tree_uuid_params
221 def getLeafUUIDs(self, tree_id:int) -> List[int]:
222 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
223 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
224 return wpt_wrapper.getLeafUUIDs(self.wpt, tree_id)
225
226 @validate_tree_uuid_params
227 def getAllUUIDs(self, tree_id:int) -> List[int]:
228 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
229 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
230 return wpt_wrapper.getAllUUIDs(self.wpt, tree_id)
231
232 @validate_recursion_params
233 def setBranchRecursionLevel(self, level:int) -> None:
234 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
235 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
236 wpt_wrapper.setBranchRecursionLevel(self.wpt, level)
237
238 @validate_trunk_segment_params
239 def setTrunkSegmentResolution(self, trunk_segs:int) -> None:
240 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
241 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
242 wpt_wrapper.setTrunkSegmentResolution(self.wpt, trunk_segs)
243
244 @validate_branch_segment_params
245 def setBranchSegmentResolution(self, branch_segs:int) -> None:
246 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
247 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
248 wpt_wrapper.setBranchSegmentResolution(self.wpt, branch_segs)
249
250 @validate_leaf_subdivisions_params
251 def setLeafSubdivisions(self, leaf_segs_x:int, leaf_segs_y:int) -> None:
252 if not self.wpt or not isinstance(self.wpt, ctypes._Pointer):
253 raise RuntimeError("WeberPennTree is not properly initialized. Check plugin availability.")
254 wpt_wrapper.setLeafSubdivisions(self.wpt, leaf_segs_x, leaf_segs_y)
255
256
257
258
259
List[int] getAllUUIDs(self, int tree_id)
None setBranchSegmentResolution(self, int branch_segs)
List[int] getLeafUUIDs(self, int tree_id)
None setTrunkSegmentResolution(self, int trunk_segs)
List[int] getBranchUUIDs(self, int tree_id)
int buildTree(self, WPTType wpt_type, vec3 origin=vec3(0, 0, 0), float scale=1)
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)
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.