diff --git a/loopstructural/gui/modelling/steps/checks.py b/loopstructural/gui/modelling/steps/checks.py index c7ad052..de0a374 100644 --- a/loopstructural/gui/modelling/steps/checks.py +++ b/loopstructural/gui/modelling/steps/checks.py @@ -66,13 +66,18 @@ def check_data(data_manager, model_manager=None) -> StepCheck: def check_stratigraphy(data_manager, model_manager=None) -> StepCheck: """Step 2: the column and the results that come from it.""" problems, todo = [], [] + unit_names = data_manager.get_stratigraphic_unit_names() + if not unit_names: + # The step is optional: a model of faults only has no column + todo.append( + "Add units to the stratigraphic column. This step is optional if you model " + "only faults." + ) + return StepCheck(tuple(problems), tuple(todo)) if data_manager.get_layer_role(layer_roles.GEOLOGY) is None: todo.append("Select the geology layer.") if data_manager.get_layer_role(layer_roles.STRUCTURE) is None: todo.append("Select the structure layer.") - unit_names = data_manager.get_stratigraphic_unit_names() - if not unit_names: - todo.append("Add units to the stratigraphic column.") problems.extend(_out_of_date_messages(data_manager)) contact_units = set(data_manager.get_unique_basal_units()) diff --git a/loopstructural/gui/modelling/steps/export_panel.py b/loopstructural/gui/modelling/steps/export_panel.py index e9f8959..420f47f 100644 --- a/loopstructural/gui/modelling/steps/export_panel.py +++ b/loopstructural/gui/modelling/steps/export_panel.py @@ -169,6 +169,27 @@ def set_model_solved(self, solved): ) for tab_index in range(self.tabs.count()): self.tabs.widget(tab_index).setEnabled(self._model_solved and self._task is None) + # A model of faults only has no units: no stratigraphic surfaces, no block model + has_units = not solved or self._has_stratigraphic_features() + if has_units and not self.surface_strat.isEnabled(): + self.surface_strat.setChecked(True) + self.surface_strat.setEnabled(has_units) + if not has_units: + self.surface_strat.setChecked(False) + self.tabs.setTabEnabled(1, has_units) + if not has_units: + self.hint.setText("The model has faults only, so the block model is not offered.") + + def _has_stratigraphic_features(self): + """Return False if every feature of the model is a fault.""" + manager = self.model_manager + if manager is None: + return True + faults = set(getattr(manager, 'faults', {}) or {}) | set( + getattr(manager, 'parametric_faults', {}) or {} + ) + names = [f.name for f in manager.features() if not f.name.startswith('__')] + return any(name not in faults for name in names) # -- actions ------------------------------------------------------------ diff --git a/loopstructural/main/derived_refresh.py b/loopstructural/main/derived_refresh.py index f8c5386..0ca39ed 100644 --- a/loopstructural/main/derived_refresh.py +++ b/loopstructural/main/derived_refresh.py @@ -31,9 +31,14 @@ def names_to_refresh(data_manager) -> List[str]: because the layer is an input of the user. - Thickness: only when it is out of date. A thickness that was never calculated can be typed, so the build does not calculate it. + + With no units in the column (a model of faults only), nothing comes from + the column, so the list is empty. """ derived = data_manager.derived names = [] + if not data_manager.get_stratigraphic_unit_names(): + return names if ( data_manager.layer_roles.contacts_source == layer_roles.CONTACTS_FROM_GEOLOGY and data_manager.get_stratigraphic_unit_names() diff --git a/loopstructural/main/model_manager.py b/loopstructural/main/model_manager.py index cc59778..35fa5e1 100644 --- a/loopstructural/main/model_manager.py +++ b/loopstructural/main/model_manager.py @@ -1077,6 +1077,10 @@ def update_foliation_features(self): and the basal contacts and structural orientations data. This method will automatically add unconformities based on the stratigraphic column. """ + # A model with faults only has no column, or a column with no units + if self.stratigraphic_column is None or not self.stratigraphic_column.get_groups(): + self._emit('foliation_features_updated') + return stratigraphic_column = {} # Take each unit's training value straight from `get_isovalues()` # (LoopStructural core), which is what labels the extracted @@ -1680,9 +1684,10 @@ def model_state(self) -> str: @property def valid(self): valid = True - if len(self.groups) == 0: + # A model with faults only has no groups and no stratigraphy + if len(self.groups) == 0 and len(self.faults) == 0: valid = False - if len(self.stratigraphy) == 0: + if len(self.stratigraphy) == 0 and len(self.faults) == 0: valid = False if len(self.faults) > 0: for _fault_name, fault_data in self.faults.items(): diff --git a/tests/unit/test_build_plan.py b/tests/unit/test_build_plan.py index 0743959..357611c 100644 --- a/tests/unit/test_build_plan.py +++ b/tests/unit/test_build_plan.py @@ -176,6 +176,13 @@ def test_no_column_calculates_no_contacts(self, dm): dm.units = [] assert names_to_refresh(dm) == [] + def test_no_column_calculates_no_thickness_even_if_out_of_date(self, dm): + dm.derived.record(derived_data.BASAL_CONTACTS) + dm.derived.record(derived_data.THICKNESS) + dm.inputs = {'unit_order': []} + dm.units = [] + assert names_to_refresh(dm) == [] + def test_no_geology_layer_calculates_no_contacts(self, dm): dm.layer_roles.values[layer_roles.GEOLOGY] = None assert names_to_refresh(dm) == [] diff --git a/tests/unit/test_step_checks.py b/tests/unit/test_step_checks.py index f771eb6..eb3c600 100644 --- a/tests/unit/test_step_checks.py +++ b/tests/unit/test_step_checks.py @@ -115,9 +115,16 @@ def map_layers(self, dm, request): def test_an_empty_column_is_not_started(self, dm): check = checks.check_stratigraphy(dm) assert check.status == Status.NOT_STARTED - assert 'Add units to the stratigraphic column.' in check.todo + assert any(t.startswith('Add units to the stratigraphic column.') for t in check.todo) + assert 'optional' in check.todo[0] + + def test_an_empty_column_does_not_ask_for_layers_or_contacts(self, dm): + todo = checks.check_stratigraphy(dm).todo + assert len(todo) == 1 + assert not checks.check_stratigraphy(dm).problems def test_missing_map_layers_are_a_todo(self, dm): + dm.units = ['a'] todo = checks.check_stratigraphy(dm).todo assert 'Select the geology layer.' in todo assert 'Select the structure layer.' in todo