|
29 | 29 | from pathlib import Path |
30 | 30 | from typing import List, Optional, Tuple, Union |
31 | 31 |
|
| 32 | +from qgis.core import ( |
| 33 | + QgsCategorizedSymbolRenderer, |
| 34 | + QgsProject, |
| 35 | + QgsRasterMarkerSymbolLayer, |
| 36 | + QgsRuleBasedRenderer, |
| 37 | + QgsSingleSymbolRenderer, |
| 38 | + QgsSvgMarkerSymbolLayer, |
| 39 | + QgsVectorLayer, |
| 40 | + QgsWkbTypes, |
| 41 | +) |
32 | 42 | from qgis.PyQt.QtCore import QCoreApplication |
33 | 43 |
|
34 | 44 | from .exceptions import NoProjectFoundError, QFieldSyncError |
@@ -216,3 +226,85 @@ def is_valid_filepath(path: str) -> bool: |
216 | 226 | return False |
217 | 227 |
|
218 | 228 | return True |
| 229 | + |
| 230 | + |
| 231 | +def update_symbol_to_project_assets( |
| 232 | + layer: QgsVectorLayer, new_path: Optional[Path] = None |
| 233 | +) -> None: |
| 234 | + """ |
| 235 | + Updates the paths of symbols to a relative QGIS project |
| 236 | + """ |
| 237 | + |
| 238 | + if ( |
| 239 | + not isinstance(layer, QgsVectorLayer) |
| 240 | + or layer.geometryType() != QgsWkbTypes.PointGeometry |
| 241 | + ): |
| 242 | + return |
| 243 | + |
| 244 | + renderer = layer.renderer() |
| 245 | + |
| 246 | + if not renderer: |
| 247 | + return |
| 248 | + |
| 249 | + if new_path is None: |
| 250 | + project = QgsProject.instance() |
| 251 | + project_home = project.homePath() |
| 252 | + new_path = Path(project_home) |
| 253 | + destination_dir = new_path / "assets" |
| 254 | + destination_dir.mkdir(parents=True, exist_ok=True) |
| 255 | + |
| 256 | + def update_symbol_path_assets(symbol): |
| 257 | + """Updates the path of a symbol layer.""" |
| 258 | + if symbol is None: |
| 259 | + return |
| 260 | + |
| 261 | + for symbol_layer in symbol.symbolLayers(): |
| 262 | + if isinstance( |
| 263 | + symbol_layer, (QgsSvgMarkerSymbolLayer, QgsRasterMarkerSymbolLayer) |
| 264 | + ): |
| 265 | + source_path = Path(symbol_layer.path()) |
| 266 | + |
| 267 | + try: |
| 268 | + source_path.relative_to(new_path) |
| 269 | + continue |
| 270 | + except ValueError: |
| 271 | + # Proceed with copying |
| 272 | + pass |
| 273 | + |
| 274 | + if source_path.exists(): |
| 275 | + destination_path_file = destination_dir / source_path.name |
| 276 | + |
| 277 | + if not destination_path_file.exists(): |
| 278 | + shutil.copy2(source_path, destination_path_file) |
| 279 | + |
| 280 | + symbol_layer.setPath( |
| 281 | + str(destination_path_file.relative_to(new_path)) |
| 282 | + ) |
| 283 | + |
| 284 | + elif hasattr(symbol_layer, "subSymbol"): |
| 285 | + update_symbol_path_assets( |
| 286 | + symbol_layer.subSymbol() |
| 287 | + ) # Recursively for nested symbology |
| 288 | + |
| 289 | + if isinstance(renderer, QgsSingleSymbolRenderer): |
| 290 | + symbol = renderer.symbol() |
| 291 | + if symbol: |
| 292 | + update_symbol_path_assets(symbol) |
| 293 | + |
| 294 | + elif isinstance(renderer, QgsRuleBasedRenderer): |
| 295 | + for rule in renderer.rootRule().children(): |
| 296 | + if rule.symbol(): |
| 297 | + update_symbol_path_assets(rule.symbol()) |
| 298 | + |
| 299 | + elif isinstance(renderer, QgsCategorizedSymbolRenderer): |
| 300 | + categories = renderer.categories() |
| 301 | + if categories: |
| 302 | + for index in range(len(categories)): |
| 303 | + # Get a fresh category object each time, |
| 304 | + # if not the new symbol path is not updated. |
| 305 | + category = renderer.categories()[index] |
| 306 | + symbol = category.symbol().clone() |
| 307 | + update_symbol_path_assets(symbol) |
| 308 | + renderer.updateCategorySymbol(index, symbol) |
| 309 | + |
| 310 | + layer.setRenderer(renderer) |
0 commit comments