diff --git a/docs/development/usability-plan.md b/docs/development/usability-plan.md new file mode 100644 index 00000000..c9854f32 --- /dev/null +++ b/docs/development/usability-plan.md @@ -0,0 +1,370 @@ +# Usability plan: guided modelling workflow + +## Purpose + +This plan makes the plugin easier to use. It changes the plugin from a set of +separate tools into one guided workflow. It also adds a second entry, so that +users can interpolate surfaces directly from their own constraints. + +Do the work in phases. Each phase is a separate pull request. Each phase must +leave the plugin in a usable state. + +## Current problems + +### Entry points + +- The plugin has three separate entry points: the toolbar, the Plugins menu, + and the dock. None of them shows the order of the workflow. +- The toolbar has approximately 10 actions. Many actions have no icon, so the + toolbar shows long text labels. +- `action_fault_topology` is added to the toolbar two times + (`plugin_main.py`, `initGui`). +- `initProcessing()` is called two times in `initGui`. +- The Modelling and Visualisation tabs are at the bottom of the dock + (`TabPosition.South`). Users can easily miss them. + +### Workflow order + +The real dependency order is: + +``` +Convert data -> Define column -> Extract basal contacts -> Calculate thickness + -> Sample -> Area / DEM / layers -> Fault topology -> Fault adjacency + -> Initialise -> Solve -> View in 3D -> Export +``` + +The UI does not follow this order: + +- "Load Data" is the first tab. It asks for a basal contacts layer, but that + layer does not exist until the user defines the column and runs a dialog. +- The map2loop tools are modal dialogs outside the dock. Each tool adds its + output to the project as a new layer. The user must then go back to + "Load Data" and select that layer. +- Each dialog has its own geology, fault and structure layer pickers. The user + selects the same layers many times. +- There are five ways to build the stratigraphic column, in three places: + - the dock buttons "Initialise from map" and "Initialise from Layer Field" + - the Automatic Sorter dialog + - the User-Defined Stratigraphic Column dialog + - Paint Stratigraphic Order +- The Fault Topology Calculator is a dialog, but Fault Adjacency is a tab. +- Save, Load and Reset State are on the "Load Data" tab, but they apply to all + of the plugin. +- Only the Geological Model tab shows a status. The user cannot see which + earlier steps are done. + +### Feedback and consistency + +- Many actions show a modal `QMessageBox` for a successful result. +- Basal Contacts shows "No Contacts Found" and then also "Successfully + extracted" for the same run (`basal_contacts_widget.py`, + `_on_extractor_finished`). +- The Fault Adjacency instruction labels are created, but they are never added + to a layout. Users do not see the colour key. +- The Fault Adjacency tables use red and green only. This is a problem for + colour-blind users. +- `BaseTab(scrollable=True)` puts lists that scroll inside a scroll area. +- Rows use emoji (🗑️) as icons, but toolbars use QGIS theme icons. +- `export_tab.ui`, `topology_tab.ui`, `viewer_tab.ui` and + `geological_history_tab.ui` are not used. + +### Derived data becomes out of date + +- `extract_basal_contacts` gives each contact the name of the younger unit in + the column at the time of the run (see `get_stratigraphic_unit_names` in + `data_manager.py`). Thus the `basal_unit` values record the column order of + that run. +- When the user changes the column order, the model is marked stale + (`GeologicalModelManager._on_stratigraphic_column_changed`). But the next + initialisation reads the old contacts layer. Some contacts then have the + wrong unit name. The plugin gives no warning. +- The calculated thicknesses come from the contacts. They can also become + incorrect, with no warning. +- The `strat_order` and `strat_thickness` fields that are written onto a map + layer also become out of date. + +### Direct interpolation + +- "Add Feature -> Add Foliation" already lets the user add Value, Form Line, + Orientation and Inequality constraints from layers + (`layer_selection_table.py`). But it is at the end of the stratigraphic + workflow, so users do not easily find it. +- "Add Fault" in the Geological Model tab is "not yet implemented". + +## Target design + +### One dock with steps + +``` ++ LoopStructural ---------------------------- [Save][Open][...] + +| (1) Data ok (2) Stratigraphy ! (3) Faults ok (4) Model - (5) View - | ++-------------------------------------------------------------+ +| contents of the current step | ++-------------------------------------------------------------+ +| ! 2 units have no basal contacts. [Back] [Next >] | ++-------------------------------------------------------------+ +``` + +- Each step has a status: done, problem, or not started. +- The footer shows the most important problem and the next action. +- Save, Open, Reset and Settings go in the dock header. + +### Steps + +| Step | Contents | Moves in from | +|---|---|---| +| 1 Data | Bounding box, CRS, DEM. Source layer roles: geology, fault traces, structural points, contacts. Link to "Convert data". | Load Data tab, Data Conversion dialog | +| 2 Stratigraphy | Column list. "Build column" menu. "Derive from map" menu: basal contacts, thickness, sampler. | Stratigraphic Column tab, Sorter, User-Defined Sorter, Paint Order, Basal Contacts, Thickness, Sampler dialogs | +| 3 Faults | Fault list and fault properties. "Calculate topology". Adjacency tables. | Load Data -> Fault Layers, Fault Topology dialog, Fault Adjacency tab | +| 4 Model | Feature list, constraints for each feature, interpolator settings, one primary build button, list of problems. | Geological Model tab | +| 5 View and export | "Open 3D view". Export of surfaces, block model, cross-sections. | Visualisation dock, unused `export_tab.ui` | + +### Two entries, one structure + +At the start, the user selects one of: + +- **Build from a geological map.** All steps are visible. +- **Interpolate surfaces from constraints.** Steps 2 and 3 are hidden. The + checks for unit names, thickness and adjacency do not apply. + +The user can show the hidden steps later. Steps 2 and 3 are generators: they +make features and constraints for step 4. In step 4, generated constraints are +read-only rows ("from stratigraphic column"). The user can add constraints to a +generated feature, or detach it and edit it. + +Thus both entries use the same area setup, the same build path and the same +viewer. A user can mix the two methods in one model, for example a column-based +model with one more surface for a dyke. + +### Derived data + +Some data is calculated from other inputs. The plugin must keep this data +correct when the inputs change. + +**Contacts source.** In step 1, the user selects one of: + +- **Calculate from geology polygons** (default). The plugin extracts the basal + contacts from the geology layer and the stratigraphic column when it needs + them. A layer is added to the project only so that the user can see or + export the contacts. The model does not read that layer. +- **Use a contacts layer.** The user's own layer is an input. The plugin does + not change it. + +The thickness calculator can already make basal contacts from the geology +layer and the column when no contacts layer is selected. Use the same code. + +**Recorded inputs.** For each derived result, keep the inputs of the run: + +| Derived result | Inputs to record | +|---|---| +| Basal contacts | Column unit order, geology layer, unit name field, override and ignored units | +| Calculated thickness | Basal contacts inputs, calculator type, structure layer, cross-section layer | +| `strat_order` / `strat_thickness` fields on a map layer | Column unit order, thickness values | + +When one of these inputs changes, mark the result as out of date. Use a hash +of the inputs, so that a change and its undo (for example, two reorders) do not +mark the result as out of date. + +**When to calculate again.** + +- Do not calculate again after each change in the column list. Extraction is + too slow, and the user often moves many rows. +- In step 2, show the out-of-date results, for example "Basal contacts are out + of date (the column changed)", with an **Update** button. +- In step 4, the build button calculates all out-of-date derived data before it + initialises the model. A build cannot use out-of-date contacts. + +**Thickness values.** Record the source of each unit thickness: "typed" or +"calculated". Do not overwrite a typed value. Mark a calculated value as out +of date with its contacts, and update it at build time. + +### Toolbar and menu + +- Toolbar: LoopStructural (opens the dock), 3D View, Help. +- Plugins menu: the same actions, plus a "Tools" submenu with the map2loop + dialogs for advanced users. +- The Processing algorithms do not change. + +### General rules + +1. Select a layer one time. All tools read the layer roles from the data + manager. +2. Use `iface.messageBar()` for results. Use `QMessageBox` only for errors that + stop an action, and for confirmations before destructive actions. +3. Each step has a check function. The step header shows its result. +4. Use QGIS widgets and theme icons: `QgsColorButton`, `QgsCollapsibleGroupBox`, + `QgsApplication.getThemeIcon`. Do not use emoji as icons. +5. Do not put a scroll area around a widget that scrolls. +6. Derived data records its inputs. The UI shows when it is out of date. A + model build never uses out-of-date derived data. + +## Phases + +### Phase 0: Quick fixes + +No change to the layout. + +- [x] Remove the second `toolbar.addAction(self.action_fault_topology)`. +- [x] Remove the second `initProcessing()` call. +- [x] Add the Fault Adjacency instruction labels to their group layouts. +- [x] Basal Contacts: show one message only. Do not show "Success" after "No + Contacts Found". +- [x] Replace success `QMessageBox.information` calls with message bar + messages. Start with the stratigraphic column, basal contacts, thickness + and save state. +- [x] Ask for confirmation before "Clear Stratigraphic Column". +- [x] Delete the unused `.ui` files, or keep `export_tab.ui` for phase 5. + +Files: `plugin_main.py`, `fault_adjacency_tab.py`, `basal_contacts_widget.py`, +`stratigraphic_column.py`, `model_definition_tab.py`. + +Acceptance: the toolbar shows each action one time. The adjacency legend is +visible. No modal dialog opens for a successful action. + +### Phase 1: Shared layer roles + +- [ ] Add layer roles to the data manager: `geology`, `geology_unit_field`, + `fault_traces`, `structure`, `basal_contacts`, `dem`. Save them with the + application state. +- [ ] Add a signal or observer event when a role changes. +- [ ] Make the "Load Data" widgets write to the roles. +- [ ] Make each map2loop dialog read the roles as its default values. The user + can still change the value in the dialog. +- [ ] When Basal Contacts makes a new layer, set it as the `basal_contacts` + role. +- [ ] The stratigraphic column "geology layer" pickers use the `geology` role. +- [ ] Add the contacts source setting: "Calculate from geology polygons" or + "Use a contacts layer". Save it with the application state. +- [ ] Add a derived-data record to the data manager. For each result (basal + contacts, calculated thickness, styled fields), keep a hash of its inputs + and a status: current or out of date. +- [ ] Compare the hash when the column, a layer role or a tool setting changes. + Send an event when a status changes. +- [ ] Record the source of each unit thickness: "typed" or "calculated". A + thickness that the user types in the column list is "typed". +- [ ] In step 2 (until phase 3, in the Stratigraphic Column tab), show the + out-of-date results and an **Update** button. + +Files: `main/data_manager.py`, +`model_definition/*.py`, `map2loop_tools/*_widget.py`, `stratigraphic_column.py`. + +Acceptance: the user selects the geology layer one time. All dialogs show it. +After Basal Contacts runs, the model uses the new contacts layer without more +selections. After the user changes the column order, the UI shows that the +basal contacts and the calculated thicknesses are out of date. If the user +moves a unit and then moves it back, the contacts stay current. + +Tests: unit tests for the role storage, the save/load of roles, and the change +event. Unit tests for the input hash: a reorder marks the contacts out of date; +a change to a unit colour does not; a reorder and its undo give the same hash. +A typed thickness is not overwritten by a calculated thickness. + +### Phase 2: Stratigraphic column changes + +Depends on: the `feat/highlight-strat-unit` branch (map highlight, thickness +styling) is merged. + +- [ ] Put the geology layer group at the top, with labels. Show a summary of + the unit names that have no match. +- [ ] Toolbar: "+ Unit" and "+ Unconformity" as text buttons. A "Build column" + menu for the import actions. An overflow menu for Reverse and Clear. +- [ ] Add "Youngest" and "Oldest" labels above and below the list. +- [ ] Replace the three apply buttons with one "Style map layer" group: a + "Style by" combo (unit colour, stratigraphic order, thickness), a ramp + combo and one Apply button. +- [ ] Rows: use `QgsColorButton`, a theme delete icon, and no repeated + "Thickness:" label. Give unconformity rows a different style. +- [ ] Show text in the empty list. +- [ ] Remove the outer scroll area from the tab. + +Files: `stratigraphic_column/*.py`, `stratigraphic_column/*.ui`, +`geological_history_tab.py`. + +### Phase 3: Step-based dock + +- [ ] Add a step navigation widget with a status for each step. +- [ ] Add a `check()` function for each step. It returns a status and a list of + problems. +- [ ] Move the existing tabs into the steps, in the order of the target design. +- [ ] Move Save, Open, Reset and Settings into the dock header. +- [ ] Move the dialogs into their steps as buttons or menus. Keep the dialogs. + Do not rewrite them in this phase. +- [ ] Move the Fault Topology Calculator into step 3. +- [ ] Reduce the toolbar to three actions. Put the dialogs in a "Tools" + submenu. +- [ ] Keep the `separate_dock_widgets` setting. + +Files: `loop_widget.py`, `modelling/modelling_widget.py`, `plugin_main.py`, +new `gui/modelling/steps/` module. + +Acceptance: a new user can go from an empty project to a solved model with the +"Next" button only. Each step shows why it is not done. + +### Phase 4: Model step and direct interpolation + +- [ ] Replace "Initialize Model", "Solve Model" and "Update Model Data" with one + primary button. Its text and action come from the model state. +- [ ] Show the problems from all steps before the build. +- [ ] Before the build, calculate all out-of-date derived data again: basal + contacts first, then calculated thicknesses. Run this in a background + task with progress. If the calculation fails, stop the build and show + the error. +- [ ] With "Calculate from geology polygons", give the extracted contacts + directly to the model. Update the project contacts layer for display + only. +- [ ] Add the start choice: "Build from a geological map" or "Interpolate + surfaces from constraints". Save the choice with the state. +- [ ] Constraint list for each feature: source layer, constraint type, field + mapping, weight, Z source (layer Z, DEM or constant). +- [ ] Add the constraint types: value, interface, gradient/normal, tangent, + inequality, pairwise inequality. +- [ ] Show generated constraints as read-only rows. Add "Detach" to make a + generated feature editable. +- [ ] Build and preview one feature: an isoline on the map canvas, or a surface + in the 3D view. +- [ ] Implement "Add Fault" in the model step. + +Files: `geological_model_tab/*.py`, `layer_selection_table.py`, +`feature_details_panel/*.py`, `main/model_manager.py`. + +Acceptance: a user can make a model from constraint layers only, without a +stratigraphic column. A user can add a direct-constraint feature to a +column-based model. After the user changes the column order and builds the +model, the model uses basal contacts and thicknesses that match the new order. + +### Phase 5: View and export + +- [ ] Show "Open 3D view" as the next action after a successful build. +- [ ] Add export of surfaces, block model and cross-sections to step 5. + +## Risks + +- **Large UI change.** Users of the current version must learn the new layout. + Update `docs/usage` in the same pull request as each phase. +- **Saved state files.** Phases 1 and 4 add new data to the state. Load old + state files without errors, and use default values for new keys. +- **Dialogs and Processing algorithms share code.** Phase 1 must not change the + Processing algorithm parameters. +- **Two dock modes.** The `separate_dock_widgets` setting must work after + phase 3. +- **Build time.** Extraction and thickness calculation at build time make the + build slower. Calculate only the results that are out of date. Show the + progress of each part. +- **User edits to a derived layer.** A user can edit the contacts layer that + the plugin made. With "Calculate from geology polygons", the next update + overwrites these edits. Show a warning before the overwrite, and offer to + change the contacts source to "Use a contacts layer". + +## Open questions + +1. Must the steps be strict (the user cannot go to step 4 before step 2 is + done), or only a guide? Recommendation: only a guide. Show problems, but do + not lock steps. +2. Is the Data Conversion dialog part of step 1, or a separate tool? +3. In direct mode, which constraint types does each interpolator (FDI, PLI, + surfe) support? The UI must hide types that the selected interpolator does + not support. +4. Is there a minimum QGIS version for the new widgets? +5. Must the Processing algorithms also use the derived-data record, or only + the dock? Recommendation: only the dock. Processing runs are single runs + with explicit inputs. diff --git a/docs/index.md b/docs/index.md index 6c8ea8a5..ca83f886 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,4 +34,5 @@ development/translation development/packaging development/testing development/history +development/usability-plan ``` diff --git a/loopstructural/gui/map2loop_tools/basal_contacts_widget.py b/loopstructural/gui/map2loop_tools/basal_contacts_widget.py index b7d3400c..91a4fbb1 100644 --- a/loopstructural/gui/map2loop_tools/basal_contacts_widget.py +++ b/loopstructural/gui/map2loop_tools/basal_contacts_widget.py @@ -12,6 +12,7 @@ from ...main.vectorLayerWrapper import addGeoDataFrameToproject from ..background_task import finish_background_task, start_background_task from ..compatibility import configure_layer_combo +from ..messages import push_success, push_warning class BasalContactsWidget(QWidget): @@ -268,16 +269,13 @@ def _on_extractor_finished(self, payload): basal_layer, 'basal_unit' ) else: - QMessageBox.information( - self, - "No Contacts Found", - "No contacts were found with the given parameters.", + contact_type = None + if contact_type is None: + push_warning( + "No Contacts Found", "No contacts were found with the given parameters." ) - QMessageBox.information( - self, - "Success", - f"Successfully extracted {contact_type}!", - ) + else: + push_success("Basal Contacts", f"Successfully extracted {contact_type}.") if self._debug and self._debug.is_debug(): try: self._debug.save_debug_file( diff --git a/loopstructural/gui/map2loop_tools/thickness_calculator_widget.py b/loopstructural/gui/map2loop_tools/thickness_calculator_widget.py index 16f2c42a..b77013f5 100644 --- a/loopstructural/gui/map2loop_tools/thickness_calculator_widget.py +++ b/loopstructural/gui/map2loop_tools/thickness_calculator_widget.py @@ -10,6 +10,7 @@ from loopstructural.gui.background_task import finish_background_task, start_background_task from loopstructural.gui.compatibility import configure_layer_combo +from loopstructural.gui.messages import push_success from loopstructural.toolbelt.preferences import PlgOptionsManager from ...main.helpers import ColumnMatcher, get_layer_names @@ -468,9 +469,8 @@ def _on_calculator_finished(self, result): addGeoDataFrameToproject(lines, "Thickness Lines") if location_tracking is not None: addGeoDataFrameToproject(location_tracking, "Thickness Locations") - QMessageBox.information( - self, - "Success", + push_success( + "Thickness Calculator", "Thickness calculation completed successfully and added to project.", ) self.task_succeeded.emit() @@ -478,15 +478,14 @@ def _on_calculator_finished(self, result): if hasattr(result, 'geometry'): addGeoDataFrameToproject(result, "Thickness Results") - QMessageBox.information( - self, - "Success", + push_success( + "Thickness Calculator", "Thickness calculation completed successfully and added to project.", ) self.task_succeeded.emit() return - QMessageBox.information(self, "Success", f"Thickness calculation completed: {result}") + push_success("Thickness Calculator", f"Thickness calculation completed: {result}") self.task_succeeded.emit() def _on_calculator_error(self, traceback_text): diff --git a/loopstructural/gui/messages.py b/loopstructural/gui/messages.py new file mode 100644 index 00000000..96ada241 --- /dev/null +++ b/loopstructural/gui/messages.py @@ -0,0 +1,33 @@ +"""Show the result of an action without a modal dialog.""" + +from qgis.core import Qgis, QgsMessageLog +from qgis.utils import iface + + +def push_success(title: str, text: str, duration: int = 5): + """Show a success message in the message bar of QGIS. + + Use a `QMessageBox` only for an error that stops an action, and for the + confirmation before a destructive action. + """ + if iface is not None: + iface.messageBar().pushMessage(title, text, level=Qgis.MessageLevel.Success, duration=duration) + else: + # No main window, for example in the tests + QgsMessageLog.logMessage(f"{title}: {text}", "LoopStructural", Qgis.MessageLevel.Success) + + +def push_info(title: str, text: str, duration: int = 5): + """Show an information message in the message bar of QGIS.""" + if iface is not None: + iface.messageBar().pushMessage(title, text, level=Qgis.MessageLevel.Info, duration=duration) + else: + QgsMessageLog.logMessage(f"{title}: {text}", "LoopStructural", Qgis.MessageLevel.Info) + + +def push_warning(title: str, text: str, duration: int = 10): + """Show a warning in the message bar of QGIS, for a result that is not an error.""" + if iface is not None: + iface.messageBar().pushMessage(title, text, level=Qgis.MessageLevel.Warning, duration=duration) + else: + QgsMessageLog.logMessage(f"{title}: {text}", "LoopStructural", Qgis.MessageLevel.Warning) diff --git a/loopstructural/gui/modelling/fault_adjacency_tab.py b/loopstructural/gui/modelling/fault_adjacency_tab.py index 4908154f..cb2c94b8 100644 --- a/loopstructural/gui/modelling/fault_adjacency_tab.py +++ b/loopstructural/gui/modelling/fault_adjacency_tab.py @@ -38,6 +38,7 @@ def init_ui(self): ) self.fault_fault_instructions_label = QLabel(self.fault_fault_instructions) self.fault_fault_instructions_label.setWordWrap(True) + self.fault_table_layout.addWidget(self.fault_fault_instructions_label) self.update_fault_adjacency_table() self.layout().addWidget(self.fault_table_group) @@ -51,6 +52,7 @@ def init_ui(self): ) self.strat_fault_instructions_label = QLabel(self.strat_fault_instructions) self.strat_fault_instructions_label.setWordWrap(True) + self.stratigraphic_table_layout.addWidget(self.strat_fault_instructions_label) self.update_stratigraphic_units_table() self.layout().addWidget(self.stratigraphic_table_group) diff --git a/loopstructural/gui/modelling/geological_history_tab.ui b/loopstructural/gui/modelling/geological_history_tab.ui deleted file mode 100644 index 6a576424..00000000 --- a/loopstructural/gui/modelling/geological_history_tab.ui +++ /dev/null @@ -1,371 +0,0 @@ - - - Form - - - - 0 - 0 - 622 - 785 - - - - Form - - - - - 20 - 10 - 591 - 701 - - - - - - - - - Stratigraphic Column - - - - - 10 - 30 - 561 - 281 - - - - - QLayout::SetMinimumSize - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - <html><head/><body><p>Save the stratigraphic order and thickness as new columns in the contacts basal contacts layer. Note this will overwrite any existing data in the &quot;LS_order' and 'LS_thickness' columns</p></body></html> - - - Add Unit - - - - - - - - - - - - - - - - - Fault Properties - - - - - 10 - 20 - 679 - 540 - - - - - - - - - - - - Active - - - - - - - false - - - - - - - - - Orientation - - - - - - Displacement - - - - - - - false - - - -10000000000.000000000000000 - - - 10000000000.000000000000000 - - - - - - - Dip - - - - - - - false - - - 0.000000000000000 - - - 90.000000000000000 - - - - - - - Pitch - - - - - - - false - - - 0.000000000000000 - - - 180.000000000000000 - - - - - - - - - - Position - - - - - - - - x - - - - - - - false - - - -100000000000.000000000000000 - - - 100000000000.000000000000000 - - - - - - - false - - - -100000000000.000000000000000 - - - 100000000000.000000000000000 - - - - - - - y - - - - - - - z - - - - - - - false - - - -100000000000.000000000000000 - - - 0.000000000000000 - - - - - - - - - false - - - Select on Map - - - - - - - - - - Ellipsoid Size - - - - - - Major Axis - - - - - - - false - - - 10000000.000000000000000 - - - - - - - Minor Axis - - - - - - - false - - - 10000000.000000000000000 - - - - - - - Intermediate Axis - - - - - - - false - - - 1000000.000000000000000 - - - - - - - - - - Add Elipse to Map - - - - - - - - - - - - - - - QgsDoubleSpinBox - QDoubleSpinBox -
qgsdoublespinbox.h
-
-
- - -
diff --git a/loopstructural/gui/modelling/model_definition/model_definition_tab.py b/loopstructural/gui/modelling/model_definition/model_definition_tab.py index c7a115da..a6e969fe 100644 --- a/loopstructural/gui/modelling/model_definition/model_definition_tab.py +++ b/loopstructural/gui/modelling/model_definition/model_definition_tab.py @@ -8,6 +8,7 @@ QWidget, ) +from loopstructural.gui.messages import push_success from loopstructural.gui.modelling.base_tab import BaseTab from .bounding_box import BoundingBoxWidget @@ -87,9 +88,7 @@ def on_save_state_clicked(self): self, "Save Application State", f"Failed to save application state:\n{err}" ) else: - QMessageBox.information( - self, "Save Application State", f"Application state saved to:\n{filepath}" - ) + push_success("Save Application State", f"Application state saved to: {filepath}") def on_load_state_clicked(self): """Prompt for a state file and, after confirmation, load it.""" diff --git a/loopstructural/gui/modelling/stratigraphic_column/stratigraphic_column.py b/loopstructural/gui/modelling/stratigraphic_column/stratigraphic_column.py index 86cbd7d1..48faf685 100644 --- a/loopstructural/gui/modelling/stratigraphic_column/stratigraphic_column.py +++ b/loopstructural/gui/modelling/stratigraphic_column/stratigraphic_column.py @@ -19,6 +19,7 @@ from loopstructural.__about__ import DIR_PLUGIN_ROOT from loopstructural.gui.compatibility import configure_layer_combo +from loopstructural.gui.messages import push_success from loopstructural.gui.modelling.stratigraphic_column.unconformity import UnconformityWidget from loopstructural.main.helpers import ColumnMatcher, get_layer_names @@ -217,7 +218,18 @@ def _build_tool_button(self, icon: QIcon, tooltip: str) -> QToolButton: return button def clearColumn(self): - """Clear the stratigraphic column.""" + """Clear the stratigraphic column, after the user confirms.""" + if self.unitList.count() > 0: + reply = QMessageBox.question( + self, + "Clear Stratigraphic Column", + "This removes all units and unconformities from the stratigraphic column. " + "This cannot be undone.\n\nContinue?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if reply != QMessageBox.StandardButton.Yes: + return # Use the data manager's clear method to ensure callback is triggered # This will notify all listening widgets (including this one and others) if self.data_manager: @@ -470,8 +482,7 @@ def apply_colours_to_layer(self): return applied = self.data_manager.apply_stratigraphic_colours_to_layer(layer, field_name) if applied: - QMessageBox.information( - self, + push_success( "Apply Colours to Map Layer", f"Applied stratigraphic column colours to layer '{layer.name()}'.", ) @@ -501,8 +512,7 @@ def apply_age_to_layer(self): layer, field_name, ramp_name=ramp_name ) if applied: - QMessageBox.information( - self, + push_success( "Apply Stratigraphic Age to Map Layer", f"Applied stratigraphic age and graduated styling to layer '{layer.name()}'.", ) diff --git a/loopstructural/gui/modelling/topology_tab.ui b/loopstructural/gui/modelling/topology_tab.ui deleted file mode 100644 index e3162d4d..00000000 --- a/loopstructural/gui/modelling/topology_tab.ui +++ /dev/null @@ -1,69 +0,0 @@ - - - Form - - - - 0 - 0 - 607 - 713 - - - - Form - - - - - 0 - 0 - 591 - 701 - - - - - - - - - Fault-Fault - - - - - 0 - 20 - 571 - 271 - - - - - - - - - - - Fault-Stratigraphy - - - - - 0 - 20 - 571 - 311 - - - - - - - - - - - diff --git a/loopstructural/gui/modelling/viewer_tab.ui b/loopstructural/gui/modelling/viewer_tab.ui deleted file mode 100644 index ec3088d4..00000000 --- a/loopstructural/gui/modelling/viewer_tab.ui +++ /dev/null @@ -1,69 +0,0 @@ - - - Form - - - - 0 - 0 - 123 - 207 - - - - Form - - - - - 0 - 10 - 591 - 511 - - - - - - - - - - QLayout::SetMinimumSize - - - - - Add Data - - - - - - - Add Block Model - - - - - - - Add Surfaces - - - - - - - Clear - - - - - - - - - - - diff --git a/loopstructural/plugin_main.py b/loopstructural/plugin_main.py index 8204b6dd..9f3c48b3 100644 --- a/loopstructural/plugin_main.py +++ b/loopstructural/plugin_main.py @@ -236,7 +236,6 @@ def initGui(self): self.toolbar.addAction(self.action_basal_contacts) self.toolbar.addAction(self.action_thickness) self.toolbar.addAction(self.action_paint_strat_order) - self.toolbar.addAction(self.action_fault_topology) self.iface.addPluginToMenu(__title__, self.action_sampler) self.iface.addPluginToMenu(__title__, self.action_sorter) @@ -246,8 +245,6 @@ def initGui(self): self.iface.addPluginToMenu(__title__, self.action_paint_strat_order) self.iface.addPluginToMenu(__title__, self.action_fault_topology) - self.initProcessing() - # -- Help menu # documentation