1+ """Geological model manager utilities used by the LoopStructural plugin.
2+
3+ This module exposes the `GeologicalModelManager` which wraps a LoopStructural
4+ `GeologicalModel` and provides helpers to ingest GeoDataFrames, update
5+ stratigraphy and faults, evaluate features on point clouds or meshes, and
6+ export results to GeoDataFrames or VTK meshes.
7+
8+ The goal of the manager is to isolate data transformation, sampling and
9+ interaction with the LoopStructural model from the GUI code.
10+ """
11+
112from collections import defaultdict
213from typing import Callable
314
@@ -162,6 +173,27 @@ def update_contact_traces(
162173 unit_name_field = None ,
163174 use_z_coordinate = False ,
164175 ):
176+ """Ingest basal contact traces and populate internal stratigraphy.
177+
178+ Parameters
179+ ----------
180+ basal_contacts : geopandas.GeoDataFrame
181+ GeoDataFrame containing basal contact geometries and attributes.
182+ sampler : callable, optional
183+ Callable used to sample geometries to point rows (default: AllSampler()).
184+ unit_name_field : str or None
185+ Field name in `basal_contacts` giving the stratigraphic unit name. If
186+ None the function returns early.
187+ use_z_coordinate : bool
188+ If True, use Z values from geometries when available; otherwise use
189+ the manager's DEM function.
190+
191+ Notes
192+ -----
193+ This method clears existing stratigraphy and replaces contact entries
194+ keyed by unit name. It does not notify observers; callers should call
195+ `update_model` or trigger observers as required.
196+ """
165197 self .stratigraphy .clear () # Clear existing stratigraphy
166198 unit_points = sampler (basal_contacts , self .dem_function , use_z_coordinate )
167199 if len (unit_points ) == 0 or unit_points .empty :
@@ -345,6 +377,14 @@ def update_model(self):
345377 observer ()
346378
347379 def features (self ):
380+ """Return the list of features currently held by the internal model.
381+
382+ Returns
383+ -------
384+ list
385+ List-like collection of feature objects contained in the wrapped
386+ LoopStructural `GeologicalModel`.
387+ """
348388 return self .model .features
349389
350390 def add_foliation (
@@ -355,6 +395,31 @@ def add_foliation(
355395 sampler = AllSampler (),
356396 use_z_coordinate = False ,
357397 ):
398+ """Create and add a foliation feature from grouped input layers.
399+
400+ Parameters
401+ ----------
402+ name : str
403+ Name for the new foliation feature.
404+ data : dict
405+ Mapping of layer identifiers to dicts describing each layer. Each
406+ layer dict must include a 'type' key (one of 'Orientation',
407+ 'Formline', 'Value', 'Inequality') and the fields required by that
408+ type (e.g. 'strike_field', 'dip_field', 'value_field', ...).
409+ folded_feature_name : str or None
410+ Optional name of a feature to which the foliation should be
411+ associated/converted (currently unused in this helper).
412+ sampler : callable, optional
413+ Callable used to sample provided GeoDataFrames into plain pandas
414+ rows (default: AllSampler()).
415+ use_z_coordinate : bool
416+ Whether to use Z coordinates from input geometries when present.
417+
418+ Raises
419+ ------
420+ ValueError
421+ If a layer uses an unknown 'type' value.
422+ """
358423 # for z
359424 dfs = []
360425 kwargs = {}
@@ -391,7 +456,25 @@ def add_foliation(
391456 # self.model[folded_feature_name] = folded_feature
392457 for observer in self .observers :
393458 observer ()
459+
394460 def add_unconformity (self , foliation_name : str , value : float , type : FeatureType = FeatureType .UNCONFORMITY ):
461+ """Add an unconformity (or onlap unconformity) to a named foliation.
462+
463+ Parameters
464+ ----------
465+ foliation_name : str
466+ Name of an existing foliation feature in the model.
467+ value : float
468+ Value (level) at which the unconformity should be inserted.
469+ type : FeatureType
470+ Type of unconformity (default: FeatureType.UNCONFORMITY). Use
471+ FeatureType.ONLAPUNCONFORMITY for onlap-type behaviour.
472+
473+ Raises
474+ ------
475+ ValueError
476+ If the foliation named by `foliation_name` cannot be found in the model.
477+ """
395478 foliation = self .model .get_feature_by_name (foliation_name )
396479 if foliation is None :
397480 raise ValueError (f"Foliation '{ foliation_name } ' not found in the model." )
@@ -401,6 +484,24 @@ def add_unconformity(self, foliation_name: str, value: float, type: FeatureType
401484 self .model .add_onlap_unconformity (foliation , value )
402485
403486 def add_fold_to_feature (self , feature_name : str , fold_frame_name : str , fold_weights = {}):
487+ """Apply a FoldFrame to an existing feature, producing a folded feature.
488+
489+ Parameters
490+ ----------
491+ feature_name : str
492+ Name of the feature to fold.
493+ fold_frame_name : str
494+ Name of an existing fold frame feature in the model to use for
495+ folding.
496+ fold_weights : dict
497+ Optional weights passed to the fold conversion; currently forwarded
498+ to the converter implementation.
499+
500+ Raises
501+ ------
502+ ValueError
503+ If either the fold frame or the target feature cannot be found.
504+ """
404505
405506 from LoopStructural .modelling .features ._feature_converters import add_fold_to_feature
406507
@@ -416,6 +517,17 @@ def add_fold_to_feature(self, feature_name: str, fold_frame_name: str, fold_weig
416517 self .model [feature_name ] = folded_feature
417518
418519 def convert_feature_to_structural_frame (self , feature_name : str ):
520+ """Convert an interpolated feature into a StructuralFrame.
521+
522+ This helper constructs a StructuralFrameBuilder from the existing
523+ feature's builder and replaces the feature in the model with the new
524+ frame instance.
525+
526+ Parameters
527+ ----------
528+ feature_name : str
529+ Name of the feature to convert.
530+ """
419531 from LoopStructural .modelling .features .builders import StructuralFrameBuilder
420532
421533 builder = self .model .get_feature_by_name (feature_name ).builder
@@ -531,6 +643,23 @@ def export_feature_values_to_geodataframe(
531643 return gdf
532644
533645 def export_feature_values_to_vtk_mesh (self , name , mesh , scalar_type = 'scalar' ):
646+ """Evaluate a feature on a mesh's points and attach the values as a field.
647+
648+ Parameters
649+ ----------
650+ name : str
651+ Feature name to evaluate.
652+ mesh : pyvista.PolyData or similar
653+ Mesh-like object exposing a `points` array and supporting item
654+ assignment for point data (e.g. mesh[name] = values).
655+ scalar_type : str
656+ 'scalar' or 'gradient' to control what is computed and attached.
657+
658+ Returns
659+ -------
660+ mesh
661+ The same mesh instance with added/updated point data named `name`.
662+ """
534663 pts = mesh .points
535664 values = self .evaluate_feature_on_points (name , pts , scalar_type = scalar_type )
536665 mesh [name ] = values
0 commit comments