Skip to content

Commit 42f175d

Browse files
fix: block model from being built with default bounding box.
* Initial plan * Prevent model init with unset bounding box and persist flag Co-authored-by: lachlangrose <7371904+lachlangrose@users.noreply.github.com> * fix: block spin boxes to prevent defaults triggering update --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lachlangrose <7371904+lachlangrose@users.noreply.github.com> Co-authored-by: Lachlan Grose <lachlan.grose@monash.edu>
1 parent 76aef57 commit 42f175d

3 files changed

Lines changed: 78 additions & 10 deletions

File tree

loopstructural/gui/modelling/geological_model_tab/geological_model_tab.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ def initialize_model(self):
142142
# Run update_model in a background thread to avoid blocking the UI.
143143
if not self.model_manager:
144144
return
145+
if self.data_manager is not None:
146+
if not self.data_manager.is_bounding_box_set():
147+
QMessageBox.critical(
148+
self,
149+
"Bounding box required",
150+
"Please set the bounding box before initializing the model.",
151+
)
152+
return
145153

146154
# create progress dialog (indeterminate)
147155
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)

loopstructural/gui/modelling/model_definition/bounding_box.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def __init__(self, parent=None, data_manager=None):
2222
self.useCurrentViewExtentButton.clicked.connect(self.useCurrentViewExtent)
2323
self.selectFromCurrentLayerButton.clicked.connect(self.selectFromCurrentLayer)
2424
self.data_manager.set_bounding_box_update_callback(self.set_bounding_box)
25+
self._update_bounding_box_styles()
2526

2627
def set_bounding_box(self, bounding_box):
2728
"""Populate UI controls with values from a BoundingBox object.
@@ -31,12 +32,37 @@ def set_bounding_box(self, bounding_box):
3132
bounding_box : object
3233
BoundingBox-like object with `origin` and `maximum` sequences of length 3.
3334
"""
34-
self.originXSpinBox.setValue(bounding_box.origin[0])
35-
self.maxXSpinBox.setValue(bounding_box.maximum[0])
36-
self.originYSpinBox.setValue(bounding_box.origin[1])
37-
self.maxYSpinBox.setValue(bounding_box.maximum[1])
38-
self.originZSpinBox.setValue(bounding_box.origin[2])
39-
self.maxZSpinBox.setValue(bounding_box.maximum[2])
35+
# Block spinbox signals to avoid emitting valueChanged while setting values
36+
spinboxes = (
37+
self.originXSpinBox,
38+
self.maxXSpinBox,
39+
self.originYSpinBox,
40+
self.maxYSpinBox,
41+
self.originZSpinBox,
42+
self.maxZSpinBox,
43+
)
44+
for sb in spinboxes:
45+
try:
46+
sb.blockSignals(True)
47+
except Exception:
48+
pass
49+
50+
try:
51+
self.originXSpinBox.setValue(bounding_box.origin[0])
52+
self.maxXSpinBox.setValue(bounding_box.maximum[0])
53+
self.originYSpinBox.setValue(bounding_box.origin[1])
54+
self.maxYSpinBox.setValue(bounding_box.maximum[1])
55+
self.originZSpinBox.setValue(bounding_box.origin[2])
56+
self.maxZSpinBox.setValue(bounding_box.maximum[2])
57+
finally:
58+
# Ensure signals are unblocked even if setting values raises
59+
for sb in spinboxes:
60+
try:
61+
sb.blockSignals(False)
62+
except Exception:
63+
pass
64+
65+
self._update_bounding_box_styles()
4066

4167
def useCurrentViewExtent(self):
4268
"""Set bounding box values from the current map canvas view extent."""
@@ -70,3 +96,27 @@ def selectFromCurrentLayer(self):
7096

7197
def onChangeExtent(self, value):
7298
self.data_manager.set_bounding_box(**value)
99+
try:
100+
self._update_bounding_box_styles()
101+
except Exception:
102+
pass
103+
104+
def _update_bounding_box_styles(self):
105+
"""Highlight spin boxes if bounding box has not been set."""
106+
if not hasattr(self, 'data_manager'):
107+
return
108+
try:
109+
is_set = self.data_manager.is_bounding_box_set()
110+
except Exception:
111+
is_set = False
112+
red_style = "border: 1px solid red;"
113+
clear_style = ""
114+
for sb in (
115+
self.originXSpinBox,
116+
self.originYSpinBox,
117+
self.originZSpinBox,
118+
self.maxXSpinBox,
119+
self.maxYSpinBox,
120+
self.maxZSpinBox,
121+
):
122+
sb.setStyleSheet(clear_style if is_set else red_style)

loopstructural/main/data_manager.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(self, *, project=None, mapCanvas=None, logger=None):
4545
default_bounding_box['zmax'],
4646
],
4747
)
48+
self._bounding_box_set = False
4849

4950
self._basal_contacts = None
5051
self._fault_traces = None
@@ -99,7 +100,9 @@ def set_model_manager(self, model_manager):
99100
self._model_manager.set_fault_topology(self._fault_topology)
100101
self._model_manager.update_bounding_box(self._bounding_box)
101102

102-
def set_bounding_box(self, xmin=None, xmax=None, ymin=None, ymax=None, zmin=None, zmax=None):
103+
def set_bounding_box(
104+
self, xmin=None, xmax=None, ymin=None, ymax=None, zmin=None, zmax=None, *, mark_set=True
105+
):
103106
"""Set the bounding box for the model."""
104107
origin = self._bounding_box.origin
105108
maximum = self._bounding_box.maximum
@@ -118,8 +121,8 @@ def set_bounding_box(self, xmin=None, xmax=None, ymin=None, ymax=None, zmin=None
118121
maximum[2] = zmax
119122
self._bounding_box.origin = origin
120123
self._bounding_box.maximum = maximum
121-
self._bounding_box.origin = origin
122-
self._bounding_box.maximum = maximum
124+
if mark_set:
125+
self._bounding_box_set = True
123126
self._model_manager.update_bounding_box(self._bounding_box)
124127
if self.bounding_box_callback:
125128
self.bounding_box_callback(self._bounding_box)
@@ -128,6 +131,10 @@ def set_bounding_box_update_callback(self, callback):
128131
self.bounding_box_callback = callback
129132
self.bounding_box_callback(self._bounding_box)
130133

134+
def is_bounding_box_set(self):
135+
"""Return True if the bounding box has been explicitly set by the user."""
136+
return bool(self._bounding_box_set)
137+
131138
def set_fault_trace_layer_callback(self, callback):
132139
"""Set the callback for when the fault trace layer is updated."""
133140
self.fault_traces_callback = callback
@@ -457,6 +464,7 @@ def to_dict(self):
457464

458465
return {
459466
'bounding_box': self._bounding_box.to_dict(),
467+
'bounding_box_set': self._bounding_box_set,
460468
'basal_contacts': basal_contacts,
461469
'fault_traces': fault_traces,
462470
'structural_orientations': structural_orientations,
@@ -478,6 +486,7 @@ def from_dict(self, data):
478486
ymax=data['bounding_box']['maximum'][1],
479487
zmin=data['bounding_box']['origin'][2],
480488
zmax=data['bounding_box']['maximum'][2],
489+
mark_set=data.get('bounding_box_set', True),
481490
)
482491
if 'dem_layer' in data and data['dem_layer'] is not None:
483492
dem_layer = QgsProject.instance().mapLayersByName(data['dem_layer'])
@@ -512,9 +521,10 @@ def update_from_dict(self, data):
512521
ymax=data['bounding_box']['maximum'][1],
513522
zmin=data['bounding_box']['origin'][2],
514523
zmax=data['bounding_box']['maximum'][2],
524+
mark_set=data.get('bounding_box_set', True),
515525
)
516526
else:
517-
self.set_bounding_box(**default_bounding_box)
527+
self.set_bounding_box(**default_bounding_box, mark_set=False)
518528
if 'dem_layer' in data and data['dem_layer'] is not None:
519529
dem_layer = QgsProject.instance().mapLayersByName(data['dem_layer'])
520530
if dem_layer:

0 commit comments

Comments
 (0)