22
33import numpy as np
44from PyQt5 .QtWidgets import QWidget
5+ from qgis .core import QgsProject
56from qgis .PyQt import uic
67
78from loopstructural .main .data_manager import default_bounding_box
@@ -13,7 +14,7 @@ def __init__(self, parent=None, data_manager=None):
1314 super ().__init__ (parent )
1415 ui_path = os .path .join (os .path .dirname (__file__ ), "bounding_box.ui" )
1516 uic .loadUi (ui_path , self )
16-
17+
1718 # Connect bounding box spinbox signals
1819 self .originXSpinBox .valueChanged .connect (lambda x : self .onChangeExtent ({'xmin' : x }))
1920 self .maxXSpinBox .valueChanged .connect (lambda x : self .onChangeExtent ({'xmax' : x }))
@@ -23,20 +24,28 @@ def __init__(self, parent=None, data_manager=None):
2324 self .maxZSpinBox .valueChanged .connect (lambda z : self .onChangeExtent ({'zmax' : z }))
2425 self .useCurrentViewExtentButton .clicked .connect (self .useCurrentViewExtent )
2526 self .selectFromCurrentLayerButton .clicked .connect (self .selectFromCurrentLayer )
26-
27+
2728 # Connect CRS control signals
2829 self .useProjectCrsRadioButton .toggled .connect (self .onCrsSourceChanged )
2930 self .useCustomCrsRadioButton .toggled .connect (self .onCrsSourceChanged )
3031 self .crsSelector .crsChanged .connect (self .onCrsChanged )
31-
32+
3233 # Set up callbacks
3334 self .data_manager .set_bounding_box_update_callback (self .set_bounding_box )
3435 self .data_manager .set_model_crs_callback (self .update_crs_ui )
35-
36+
3637 # Initialize CRS UI
3738 self .initialize_crs_ui ()
3839 self ._update_bounding_box_styles ()
3940
41+ # Connect to project CRS changes so the widget updates when the project's CRS changes
42+ try :
43+ project = getattr (self .data_manager , 'project' , None ) or QgsProject .instance ()
44+ project .crsChanged .connect (self ._onProjectCrsChanged )
45+ except Exception :
46+ # If the signal isn't available or connection fails, ignore to keep widget functional
47+ pass
48+
4049 def initialize_crs_ui (self ):
4150 """Initialize CRS controls with current settings."""
4251 # Set initial CRS selector value
@@ -46,28 +55,28 @@ def initialize_crs_ui(self):
4655 else :
4756 # Default to project CRS
4857 self .crsSelector .setCrs (self .data_manager .project .crs ())
49-
58+
5059 # Set radio button based on use_project_crs setting
5160 if self .data_manager ._use_project_crs :
5261 self .useProjectCrsRadioButton .setChecked (True )
5362 else :
5463 self .useCustomCrsRadioButton .setChecked (True )
55-
64+
5665 self .validate_crs ()
5766
5867 def onCrsSourceChanged (self ):
5968 """Handle change in CRS source (project vs custom)."""
6069 use_project_crs = self .useProjectCrsRadioButton .isChecked ()
6170 self .crsSelector .setEnabled (not use_project_crs )
62-
71+
6372 if use_project_crs :
6473 # Use project CRS
6574 success , msg = self .data_manager .set_model_crs (None , use_project_crs = True )
6675 else :
6776 # Use custom CRS
6877 crs = self .crsSelector .crs ()
6978 success , msg = self .data_manager .set_model_crs (crs , use_project_crs = False )
70-
79+
7180 self .validate_crs ()
7281
7382 def onCrsChanged (self ):
@@ -79,7 +88,7 @@ def onCrsChanged(self):
7988
8089 def update_crs_ui (self , crs , use_project_crs ):
8190 """Update UI when model CRS changes externally.
82-
91+
8392 Parameters
8493 ----------
8594 crs : QgsCoordinateReferenceSystem or None
@@ -91,45 +100,68 @@ def update_crs_ui(self, crs, use_project_crs):
91100 self .useProjectCrsRadioButton .blockSignals (True )
92101 self .useCustomCrsRadioButton .blockSignals (True )
93102 self .crsSelector .blockSignals (True )
94-
103+
95104 try :
96105 if use_project_crs :
97106 self .useProjectCrsRadioButton .setChecked (True )
98107 self .crsSelector .setEnabled (False )
108+ self .crsSelector .setCrs (crs )
109+
99110 else :
100111 self .useCustomCrsRadioButton .setChecked (True )
101112 self .crsSelector .setEnabled (True )
102113 if crs is not None and crs .isValid ():
103114 self .crsSelector .setCrs (crs )
104-
115+
105116 self .validate_crs ()
106117 finally :
107118 # Unblock signals
108119 self .useProjectCrsRadioButton .blockSignals (False )
109120 self .useCustomCrsRadioButton .blockSignals (False )
110121 self .crsSelector .blockSignals (False )
111122
123+ def _onProjectCrsChanged (self , crs = None ):
124+ """Handle project CRS changes and update UI when the widget is using the project CRS.
125+
126+ Accept an optional `crs` argument because different QGIS versions may emit the
127+ new CRS or emit no arguments when the project's CRS changes.
128+ """
129+ # If the signal didn't provide a CRS, try to obtain it from the project's current CRS
130+ if crs is None :
131+ try :
132+ project = getattr (self .data_manager , 'project' , None ) or QgsProject .instance ()
133+ crs = project .crs ()
134+ except Exception :
135+ crs = None
136+
137+ # Only update the UI if the model is configured to use the project CRS
138+ try :
139+ if getattr (self .data_manager , '_use_project_crs' , False ):
140+ # Update the UI to reflect the new project CRS
141+ self .update_crs_ui (crs , use_project_crs = True )
142+ except Exception :
143+ pass
144+
112145 def validate_crs (self ):
113146 """Validate the selected CRS and update warning label."""
114147 crs = self .data_manager .get_model_crs ()
115-
148+
116149 if crs is None or not crs .isValid ():
117150 self .crsWarningLabel .setText ("⚠ Invalid CRS selected. Model cannot be initialized." )
118151 return False
119-
152+
120153 if crs .isGeographic ():
121154 # Safely get CRS description
122155 try :
123156 crs_desc = crs .description () or crs .authid () or "Unknown"
124157 except Exception :
125158 crs_desc = crs .authid () if hasattr (crs , 'authid' ) else "Unknown"
126-
159+
127160 self .crsWarningLabel .setText (
128- f"⚠ CRS must be projected (in meters), not geographic.\n "
129- f"Selected: { crs_desc } "
161+ f"⚠ CRS must be projected (in meters), not geographic.\n " f"Selected: { crs_desc } "
130162 )
131163 return False
132-
164+
133165 # CRS is valid and projected
134166 self .crsWarningLabel .setText ("" )
135167 return True
0 commit comments