Skip to content

Commit fdebe4f

Browse files
Copilotlachlangrose
andcommitted
Add paint stratigraphic order widget and dialog
Co-authored-by: lachlangrose <7371904+lachlangrose@users.noreply.github.com>
1 parent 2446b7a commit fdebe4f

File tree

5 files changed

+471
-20
lines changed

5 files changed

+471
-20
lines changed

loopstructural/gui/map2loop_tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .dialogs import (
88
BasalContactsDialog,
9+
PaintStratigraphicOrderDialog,
910
SamplerDialog,
1011
SorterDialog,
1112
ThicknessCalculatorDialog,
@@ -14,6 +15,7 @@
1415

1516
__all__ = [
1617
'BasalContactsDialog',
18+
'PaintStratigraphicOrderDialog',
1719
'SamplerDialog',
1820
'SorterDialog',
1921
'ThicknessCalculatorDialog',

loopstructural/gui/map2loop_tools/dialogs.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,40 @@ def setup_ui(self):
183183
def _run_and_accept(self):
184184
"""Run the calculator and accept dialog if successful."""
185185
self.widget._run_calculator()
186+
187+
188+
class PaintStratigraphicOrderDialog(QDialog):
189+
"""Dialog for painting stratigraphic order onto geology polygons."""
190+
191+
def __init__(self, parent=None, data_manager=None, debug_manager=None):
192+
"""Initialize the paint stratigraphic order dialog."""
193+
super().__init__(parent)
194+
self.setWindowTitle("Paint Stratigraphic Order")
195+
self.data_manager = data_manager
196+
self.debug_manager = debug_manager
197+
self.setup_ui()
198+
199+
def setup_ui(self):
200+
"""Set up the dialog UI."""
201+
from .paint_stratigraphic_order_widget import PaintStratigraphicOrderWidget
202+
203+
layout = QVBoxLayout(self)
204+
self.widget = PaintStratigraphicOrderWidget(
205+
self,
206+
data_manager=self.data_manager,
207+
debug_manager=self.debug_manager,
208+
)
209+
layout.addWidget(self.widget)
210+
211+
# Replace the run button with dialog buttons
212+
self.widget.runButton.hide()
213+
214+
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
215+
self.button_box.accepted.connect(self._run_and_accept)
216+
self.button_box.rejected.connect(self.reject)
217+
layout.addWidget(self.button_box)
218+
219+
def _run_and_accept(self):
220+
"""Run the painter and accept dialog if successful."""
221+
self.widget._run_painter()
222+
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
"""Widget for painting stratigraphic order onto geology polygons."""
2+
3+
import os
4+
5+
from PyQt5.QtWidgets import QMessageBox, QWidget
6+
from qgis.core import QgsMapLayerProxyModel, QgsProject
7+
from qgis.PyQt import uic
8+
9+
from loopstructural.toolbelt.preferences import PlgOptionsManager
10+
11+
12+
class PaintStratigraphicOrderWidget(QWidget):
13+
"""Widget for painting stratigraphic order or cumulative thickness onto polygons.
14+
15+
This widget provides a GUI interface for the paint stratigraphic order tool,
16+
allowing users to visualize stratigraphic relationships on geology polygons.
17+
"""
18+
19+
def __init__(self, parent=None, data_manager=None, debug_manager=None):
20+
"""Initialize the paint stratigraphic order widget.
21+
22+
Parameters
23+
----------
24+
parent : QWidget, optional
25+
Parent widget.
26+
data_manager : object, optional
27+
Data manager for accessing shared data.
28+
debug_manager : object, optional
29+
Debug manager for logging and debugging.
30+
"""
31+
super().__init__(parent)
32+
self.data_manager = data_manager
33+
self._debug = debug_manager
34+
35+
# Load the UI file
36+
ui_path = os.path.join(os.path.dirname(__file__), "paint_stratigraphic_order_widget.ui")
37+
uic.loadUi(ui_path, self)
38+
39+
# Configure layer filters programmatically
40+
try:
41+
self.geologyLayerComboBox.setFilters(QgsMapLayerProxyModel.PolygonLayer)
42+
self.stratColumnLayerComboBox.setFilters(QgsMapLayerProxyModel.NoGeometry)
43+
except Exception:
44+
# If QGIS isn't available, skip filter setup
45+
pass
46+
47+
# Initialize paint modes
48+
self.paint_modes = ["Stratigraphic Order (0=youngest)", "Cumulative Thickness"]
49+
self.paintModeComboBox.addItems(self.paint_modes)
50+
51+
# Connect signals
52+
self.geologyLayerComboBox.layerChanged.connect(self._on_geology_layer_changed)
53+
self.stratColumnLayerComboBox.layerChanged.connect(self._on_strat_column_layer_changed)
54+
self.runButton.clicked.connect(self._run_painter)
55+
56+
# Set up field combo boxes
57+
self._setup_field_combo_boxes()
58+
59+
def set_debug_manager(self, debug_manager):
60+
"""Attach a debug manager instance."""
61+
self._debug = debug_manager
62+
63+
def _export_layer_for_debug(self, layer, name_prefix: str):
64+
"""Export layer for debugging purposes."""
65+
try:
66+
if getattr(self, '_debug', None) and hasattr(self._debug, 'export_layer'):
67+
exported = self._debug.export_layer(layer, name_prefix)
68+
return exported
69+
except Exception as err:
70+
if getattr(self, '_debug', None):
71+
self._debug.plugin.log(
72+
message=f"[map2loop] Failed to export layer '{name_prefix}': {err}",
73+
log_level=2,
74+
)
75+
return None
76+
77+
def _serialize_layer(self, layer, name_prefix: str):
78+
"""Serialize layer for logging."""
79+
try:
80+
export_path = self._export_layer_for_debug(layer, name_prefix)
81+
return {
82+
"name": layer.name(),
83+
"id": layer.id(),
84+
"provider": layer.providerType() if hasattr(layer, "providerType") else None,
85+
"source": layer.source() if hasattr(layer, "source") else None,
86+
"export_path": export_path,
87+
}
88+
except Exception:
89+
return str(layer)
90+
91+
def _serialize_params_for_logging(self, params, context_label: str):
92+
"""Serialize parameters for logging."""
93+
serialized = {}
94+
for key, value in params.items():
95+
if hasattr(value, "source") or hasattr(value, "id"):
96+
serialized[key] = self._serialize_layer(value, f"{context_label}_{key}")
97+
else:
98+
serialized[key] = value
99+
return serialized
100+
101+
def _log_params(self, context_label: str):
102+
"""Log parameters for debugging."""
103+
if getattr(self, "_debug", None):
104+
try:
105+
self._debug.log_params(
106+
context_label=context_label,
107+
params=self._serialize_params_for_logging(self.get_parameters(), context_label),
108+
)
109+
except Exception:
110+
pass
111+
112+
def _setup_field_combo_boxes(self):
113+
"""Set up field combo boxes based on current layers."""
114+
self._on_geology_layer_changed()
115+
self._on_strat_column_layer_changed()
116+
117+
def _on_geology_layer_changed(self):
118+
"""Update unit name field combo box when geology layer changes."""
119+
geology_layer = self.geologyLayerComboBox.currentLayer()
120+
self.unitNameFieldComboBox.setLayer(geology_layer)
121+
122+
# Try to auto-select common field names
123+
if geology_layer:
124+
field_names = [field.name() for field in geology_layer.fields()]
125+
for common_name in ['UNITNAME', 'unitname', 'unit_name', 'UNIT', 'unit']:
126+
if common_name in field_names:
127+
self.unitNameFieldComboBox.setField(common_name)
128+
break
129+
130+
def _on_strat_column_layer_changed(self):
131+
"""Update stratigraphic column field combo boxes when layer changes."""
132+
strat_layer = self.stratColumnLayerComboBox.currentLayer()
133+
self.stratUnitFieldComboBox.setLayer(strat_layer)
134+
self.stratThicknessFieldComboBox.setLayer(strat_layer)
135+
136+
# Try to auto-select common field names
137+
if strat_layer:
138+
field_names = [field.name() for field in strat_layer.fields()]
139+
140+
# Unit name field
141+
for common_name in ['unit_name', 'name', 'UNITNAME', 'unitname']:
142+
if common_name in field_names:
143+
self.stratUnitFieldComboBox.setField(common_name)
144+
break
145+
146+
# Thickness field
147+
for common_name in ['thickness', 'THICKNESS', 'thick']:
148+
if common_name in field_names:
149+
self.stratThicknessFieldComboBox.setField(common_name)
150+
break
151+
152+
def _run_painter(self):
153+
"""Run the paint stratigraphic order algorithm."""
154+
from qgis import processing
155+
156+
self._log_params("paint_strat_order_widget_run")
157+
158+
# Validate inputs
159+
if not self.geologyLayerComboBox.currentLayer():
160+
QMessageBox.warning(self, "Missing Input", "Please select a geology polygon layer.")
161+
return
162+
163+
if not self.stratColumnLayerComboBox.currentLayer():
164+
QMessageBox.warning(self, "Missing Input", "Please select a stratigraphic column layer.")
165+
return
166+
167+
if not self.unitNameFieldComboBox.currentField():
168+
QMessageBox.warning(self, "Missing Input", "Please select the unit name field.")
169+
return
170+
171+
if not self.stratUnitFieldComboBox.currentField():
172+
QMessageBox.warning(
173+
self, "Missing Input", "Please select the stratigraphic column unit name field."
174+
)
175+
return
176+
177+
# Run the processing algorithm
178+
try:
179+
params = {
180+
'INPUT_POLYGONS': self.geologyLayerComboBox.currentLayer(),
181+
'UNIT_NAME_FIELD': self.unitNameFieldComboBox.currentField(),
182+
'INPUT_STRAT_COLUMN': self.stratColumnLayerComboBox.currentLayer(),
183+
'STRAT_UNIT_NAME_FIELD': self.stratUnitFieldComboBox.currentField(),
184+
'STRAT_THICKNESS_FIELD': self.stratThicknessFieldComboBox.currentField() or '',
185+
'PAINT_MODE': self.paintModeComboBox.currentIndex(),
186+
'OUTPUT': 'TEMPORARY_OUTPUT',
187+
}
188+
189+
if self._debug and self._debug.is_debug():
190+
try:
191+
import json
192+
193+
params_json = json.dumps(
194+
self._serialize_params_for_logging(params, "paint_strat_order"),
195+
indent=2,
196+
).encode("utf-8")
197+
self._debug.save_debug_file("paint_strat_order_params.json", params_json)
198+
except Exception as err:
199+
self._debug.plugin.log(
200+
message=f"[map2loop] Failed to save paint strat order params: {err}",
201+
log_level=2,
202+
)
203+
204+
result = processing.run("plugin_map2loop:paint_stratigraphic_order", params)
205+
206+
if result and 'OUTPUT' in result:
207+
output_layer = result['OUTPUT']
208+
if output_layer:
209+
QgsProject.instance().addMapLayer(output_layer)
210+
211+
field_name = (
212+
'strat_order' if self.paintModeComboBox.currentIndex() == 0
213+
else 'cum_thickness'
214+
)
215+
216+
QMessageBox.information(
217+
self,
218+
"Success",
219+
f"Stratigraphic order painted successfully!\n"
220+
f"Output layer added with '{field_name}' field.",
221+
)
222+
else:
223+
QMessageBox.warning(self, "Warning", "No output layer was generated.")
224+
else:
225+
QMessageBox.warning(self, "Warning", "Algorithm did not produce expected output.")
226+
227+
except Exception as e:
228+
if self._debug:
229+
self._debug.plugin.log(
230+
message=f"[map2loop] Paint stratigraphic order failed: {e}",
231+
log_level=2,
232+
)
233+
if PlgOptionsManager.get_debug_mode():
234+
raise e
235+
QMessageBox.critical(self, "Error", f"An error occurred: {str(e)}")
236+
237+
def get_parameters(self):
238+
"""Get current widget parameters.
239+
240+
Returns
241+
-------
242+
dict
243+
Dictionary of current widget parameters.
244+
"""
245+
return {
246+
'geology_layer': self.geologyLayerComboBox.currentLayer(),
247+
'unit_name_field': self.unitNameFieldComboBox.currentField(),
248+
'strat_column_layer': self.stratColumnLayerComboBox.currentLayer(),
249+
'strat_unit_field': self.stratUnitFieldComboBox.currentField(),
250+
'strat_thickness_field': self.stratThicknessFieldComboBox.currentField(),
251+
'paint_mode': self.paintModeComboBox.currentIndex(),
252+
}
253+
254+
def set_parameters(self, params):
255+
"""Set widget parameters.
256+
257+
Parameters
258+
----------
259+
params : dict
260+
Dictionary of parameters to set.
261+
"""
262+
if 'geology_layer' in params and params['geology_layer']:
263+
self.geologyLayerComboBox.setLayer(params['geology_layer'])
264+
if 'unit_name_field' in params and params['unit_name_field']:
265+
self.unitNameFieldComboBox.setField(params['unit_name_field'])
266+
if 'strat_column_layer' in params and params['strat_column_layer']:
267+
self.stratColumnLayerComboBox.setLayer(params['strat_column_layer'])
268+
if 'strat_unit_field' in params and params['strat_unit_field']:
269+
self.stratUnitFieldComboBox.setField(params['strat_unit_field'])
270+
if 'strat_thickness_field' in params and params['strat_thickness_field']:
271+
self.stratThicknessFieldComboBox.setField(params['strat_thickness_field'])
272+
if 'paint_mode' in params:
273+
self.paintModeComboBox.setCurrentIndex(params['paint_mode'])

0 commit comments

Comments
 (0)