================================================================================
  numgeo Material Manager - Source layout and developer guide
================================================================================

Author : Rahul Dogra, Technical University of Darmstadt
Date   : 27-05-2026
Scope  : Tcl/Tk material editor for the numgeo GiD problemtype.
         Originally implemented as a single ~3700-line file by J. Machacek
         (20-05-2026). Split into modules on 27-05-2026 by R. Dogra.


--------------------------------------------------------------------------------
1. WHAT THIS CODE DOES
--------------------------------------------------------------------------------

The Material Manager is the Tk dialog launched from the numgeo menu
("Material manager..."). It lets the user:

  - browse / create / duplicate / delete materials,
  - edit each material across tabs: Phase properties, Mechanical model,
    Permeability, Hydraulic, Rayleigh damping, Stabilization, Others,
  - save the database to numgeo_materials.tcldata inside the project,
  - hand a material name back to CustomLib so it can be assigned to
    geometrical entities.

The full material data (parameters of constitutive models, hydraulic
relations, stabilisation settings, etc.) lives in the manager's own
database. Only the material NAME ever reaches CustomLib. The writer
scripts/write-material.tcl picks up the database at calculation time and
emits material-definitions.inp.


--------------------------------------------------------------------------------
2. FOLDER LAYOUT
--------------------------------------------------------------------------------

numgeo.gid/
  numgeo.tcl                                 - sources material-manager.tcl
                                               (line 268, inside LoadScripts)
  scripts/
    material-manager.tcl                     - entry point and window glue
    material-manager/                        - all split modules
      metadata.tcl                           - static model metadata
      helpers.tcl                            - small utility procs
      persistence.tcl                        - load / save / dict <-> form
      model-editor.tcl                       - generic structured editor
                                               (permeability, hydraulic, relperm)
      mechanical-editor.tcl                  - mechanical-model editor
      traces.tcl                             - variable traces + dynamic
                                               UI refresh procs
      validate.tcl                           - top-level ValidateMaterial
      tabs/
        phase-properties.tcl                 - density + bulk modulus tab
        mechanical.tcl                       - mechanical model tab
        permeability.tcl                     - permeability tab + K-scale
        hydraulic.tcl                        - hydraulic model tab
        rayleigh.tcl                         - Rayleigh damping tab + plot
        stabilization.tcl                    - stabilisation parameters tab
        others.tcl                           - free-form "Others" tab

The split is by responsibility, not by tab. Helpers, metadata and
persistence are in their own files so that all tab files can use them
without duplication.


--------------------------------------------------------------------------------
3. LOAD ORDER
--------------------------------------------------------------------------------

material-manager.tcl sources the modules in this order:

  metadata.tcl                  (creates namespace, fills *_specs arrays)
  helpers.tcl                   (_DictGet, _Warn, TextSet, TextGet, ...)
  persistence.tcl               (Init, Load, Save, MaterialToForm, ReadForm)
  model-editor.tcl              (BuildStructuredModelEditor + friends)
  mechanical-editor.tcl         (UpdateMechanicalModelEditor + friends)
  traces.tcl                    (AddFormTraces, UpdateHeader,
                                 UpdatePhaseVisibility, ...)
  validate.tcl                  (ValidateMaterial)
  tabs/phase-properties.tcl     (BuildPhasePropertiesTab)
  tabs/mechanical.tcl           (BuildMechanicalTab)
  tabs/permeability.tcl         (BuildPermeabilityTab, K-scale procs)
  tabs/hydraulic.tcl            (BuildHydraulicTab)
  tabs/rayleigh.tcl             (BuildRayleighTab, alpha/beta calc, plot)
  tabs/stabilization.tcl        (BuildStabilizationTab, visibility)
  tabs/others.tcl               (BuildOthersTab)

Notes:
  - The order matters for SOURCING (procs called at source-time, e.g.
    metadata.tcl creates the namespace). It does NOT matter for cross-file
    proc calls because Tcl resolves proc names at call time, not at source
    time.
  - The main file's namespace eval block (with all form_* variables) runs
    AFTER the source list. This is intentional: helpers / metadata in the
    sourced files only define procs and fill arrays, they never read the
    form_* variables.


--------------------------------------------------------------------------------
4. FILE-BY-FILE CONTENTS
--------------------------------------------------------------------------------

== material-manager.tcl (entry point) ==

  Source list, namespace declaration with every form_* variable, plus the
  window glue:

    _SetWindowIcon            - per-window favicon for the toplevel
    Open                      - build the manager window, run the modal
                                event loop, return the selected material
    ConfigureSelectMode       - toggle the "Use Selected" button
    Preload                   - pre-build the window during problemtype
                                startup so the menu click feels instant
    BuildNotebook             - just calls Build*Tab procs in order
    Close                     - close window, prompt to save if dirty

  Material tree (left pane) and material-level actions:

    MaterialMechanicalModel   - mechanical-model name for the tree column
    RefreshTree               - rebuild the tree from the materials list
    SelectMaterial            - select a material by name (also fills form)
    TreeSelectionChanged      - <<TreeviewSelect>> binding target
    ShowMaterialByItem        - dispatch helper
    ShowMaterial              - load a material into the form
    ClearForm                 - reset the form to defaults
    InitFormVariables         - one-time initialisation at preload
    ApplyForm                 - validate the form and store it in memory
    SaveAndRefresh            - ApplyForm + Save + RefreshTree
    NewMaterial               - create a new material with a unique name
    DuplicateMaterial         - clone the selected material
    DeleteMaterial            - delete with confirmation
    UseSelected               - close the dialog returning the selected name
    CurrentTreeMaterial       - the name of the currently selected tree row


== material-manager/metadata.tcl ==

  Static lookup tables only. No procs. Editing this file is what you do
  when you add a new constitutive law or hydraulic relation.

    model_types                          - list of mechanical model names
    permeability_types                   - intrinsic permeability models
    hydraulic_types                      - suction-saturation models
    relperm_types                        - relative-permeability models

    mechanical_parameter_specs           - one list per mechanical model:
                                           {key label default kind optionName}
    mechanical_line_layouts              - layout of *Mechanical body lines

    permeability_parameter_specs         - same shape, but four-tuple
                                           (no optionName)
    permeability_line_layouts
    hydraulic_parameter_specs
    hydraulic_line_layouts
    relperm_parameter_specs
    relperm_line_layouts

    default_keywords                     - display-name -> numgeo keyword

  Kinds used in the parameter specs:
    core               written into the body lines following the layout
    header             written into the section header line (e.g. Swr)
    choice             dropdown of allowed values (e.g. Jacobian)
    option             Yes/No checkbox (mechanical only)
    option_value       value attached to an option (mechanical only)
    hca_cycle_time     written under *HCA cycle time (mechanical only)

  Adding a new model:
    1. add display name to *_types list
    2. add parameter spec to *_parameter_specs
    3. add layout to *_line_layouts
    4. add keyword mapping to default_keywords
  Nothing else has to change.


== material-manager/helpers.tcl ==

  Small utilities used everywhere. No Tk widget access (so safe at any
  load order).

    _DictGet                 safe dict read with default
    _Warn                    warning dialog (or stderr if headless)
    _AskYesNo                yes/no confirmation
    _AskYesNoCancel          yes/no/cancel confirmation
    NormalisePhases          coerce phase count to 1..3
    TextSet / TextGet        write / read a Tk text widget (newline-safe)
    PrettyParameterLabel     replace 'phi','alpha' with Greek glyphs
    Pi                       constant
    Log10                    log base 10


== material-manager/persistence.tcl ==

  Material-database I/O and the form <-> dictionary mapping.

    Init                     called once by numgeo::LoadScripts
    ProjectDirectory         resolve the directory for the DB file
    DatabaseFile             full path of numgeo_materials.tcldata
    Load                     read the DB into the in-memory list
    Save                     write atomically (.tmp then rename)
    GetMaterialNames         list of names (used by CustomLib)
    GetMaterialNamesForCustomLib  comma-joined version for CustomLib value
                                  fields
    OpenFromCustomLib        hook used by CustomLib value fields
    FindMaterialIndex        index in the in-memory list by name
    DefaultMaterial          fresh material dict with all defaults
    NormaliseMaterial        upgrade an old dict to the current schema;
                             migrates mu_w/mu_a from the old
                             fluid_properties location to viscosity
    MaterialToForm           copy a material dict into form_* variables
                             and refresh the dependent UI (header, phase
                             visibility, structured editors, Rayleigh)
    ReadForm                 assemble a material dict from the current
                             form state, including the structured params
                             and the bodies generated from them

  Note: Save uses an atomic rename pattern so an interrupted write does
  not corrupt the previous database.


== material-manager/model-editor.tcl ==

  The generic editor shared by the three non-mechanical structured tabs
  (permeability, hydraulic, relperm). They all have the same layout:
  combobox up top, parameter table below, user-defined text fallback at
  the bottom.

  Widget build / refresh:
    BuildStructuredModelEditor       - one-time widget construction
    UpdateStructuredModelEditor      - switch between structured /
                                       user-defined display
    RebuildModelParameterTable       - regenerate the rows of one table
    ModelParameterFrameChanged       - scrollregion update
    ModelParameterCanvasChanged      - canvas-width update
    ReflowViscosityInfo              - re-wrap viscosity hint label

  Metadata lookup:
    ModelSpecs                       - spec list for (section, model)
    ModelLineLayout                  - layout for (section, model)
    ModelParameterUnit               - display unit for one parameter
    KeywordForSectionModel           - default keyword for a model
    UpdateKeywordFromModel           - <<ComboboxSelected>> dispatcher;
                                       also handles the mechanical case

  Form <-> dict:
    DefaultModelParams               - {key -> default} dict
    SetModelParams                   - load values into form array
    ModelParamsFromForm              - read form array back as dict
    ModelBodyFromForm                - format current form as numgeo body
    SetModelParamsFromMaterial       - load from dict + refresh editor
    ParseModelBodyToParams           - parse old saved body back to dict
    FormatModelParameters            - dict -> multi-line body
    ValidateModelParameters          - per-section validation


== material-manager/mechanical-editor.tcl ==

  Mechanical-model editor. Same idea as model-editor.tcl, but extra
  complexity because parameters can be core / option / option_value /
  hca_cycle_time.

  Widget build / refresh:
    UpdateMechanicalModelEditor          - switch structured / user-defined
    RebuildMechanicalParameterTable      - regenerate the table; includes
                                           the "Optional parameters" block
                                           (Yes/No checkbox + value entry)
    MechanicalParameterFrameChanged      - scrollregion update
    MechanicalParameterCanvasChanged     - canvas-width update

  Metadata lookup:
    MechanicalSpecs                      - parameter list for one model
    MechanicalLineLayout                 - body-line layout for one model
    MechanicalParameterUnit              - display unit (heuristic)

  Form <-> dict:
    DefaultMechanicalParams              - {key -> default} dict
    SetMechanicalParams                  - load values into form array
    MechanicalParamsFromForm             - read form array back as dict
    MechanicalBodyFromForm               - format current form as body
    SetMechanicalParamsFromMaterial      - load from dict + refresh
    ParseMechanicalBodyToParams          - parse old body back to dict
                                           (handles *HCA cycle time and
                                           *Optional mechanical parameter
                                           sub-sections)
    FormatMechanicalParameters           - dict -> multi-line body
    ValidateMechanicalParameters         - structural validation


== material-manager/traces.tcl ==

  Variable-trace plumbing and the small refresh procs they fire.

    AddFormTraces                    - install all watches when the
                                       window opens
    RemoveFormTraces                 - tear them down on close
    UpdateHeaderTrace                - adapter for form_name
    UpdateHeaderAndVisibilityTrace   - adapter for form_phases
    KScaleTrace                      - adapter for K-scale inputs
    UpdateHeader                     - rebuild the *Material header
    UpdatePhaseVisibility            - show / hide phase-dependent
                                       widgets and tabs
    SetNotebookTabVisible            - hide / restore a notebook tab
    TabTitle                         - human title used by the above

  Note on a Tcl/Tk quirk we hit during refactoring:
  Array-element write traces on form_permeability_param(p1) do NOT
  fire reliably in every Tcl/Tk build. The permeability k entry widget
  therefore also gets a direct <KeyRelease>/<FocusOut> binding to
  KScaleTrace in RebuildModelParameterTable. Both paths are kept.


== material-manager/validate.tcl ==

  Top-level validator only. The per-section validators live in the
  corresponding editor files.

    ValidateMaterial         - cross-section rules:
                                * name format and non-empty,
                                * phase count 1..3,
                                * density count matches phase count,
                                * mechanical keyword present,
                                * permeability validated at phases >= 2,
                                * hydraulic / relperm at phases == 3,
                                * Rayleigh f1>0, f2>0, f2>f1, zeta>=0
                                  when enabled,
                                * stabilisation enable + dependent
                                  fields consistency.


== material-manager/tabs/phase-properties.tcl ==

  Density rows (per phase) and bulk-modulus rows (Kw, Ka).

    BuildPhasePropertiesTab

  Phase visibility is driven by UpdatePhaseVisibility in traces.tcl.


== material-manager/tabs/mechanical.tcl ==

  Mechanical-tab widget construction only. All editor logic is in
  mechanical-editor.tcl.

    BuildMechanicalTab


== material-manager/tabs/permeability.tcl ==

  Permeability tab + K-scale visualisation.

    BuildPermeabilityTab               - four labelframes, 2x2 grid
    BuildHydraulicConductivityScale    - paint Terzaghi scale
    RecomputeHydraulicConductivity     - position the K marker and the
                                         numeric readout for the current
                                         k, rho_w, mu_w
    _FormatScientific                  - "2.34e-5" formatting

  Formula: K = k * rho_w * g / mu_w with g = 10 m/s^2.
  Unit system: m, s, t, kN, kPa.


== material-manager/tabs/hydraulic.tcl ==

  Hydraulic tab.

    BuildHydraulicTab                  - thin wrapper over the structured
                                         editor; only visible at phases==3


== material-manager/tabs/rayleigh.tcl ==

  Rayleigh damping tab + live alpha/beta computation + damping plot.

    BuildRayleighTab                   - inputs + plot canvas
    RayleighTrace                      - trace adapter
    ComputeRayleighCoefficients        - alpha, beta from f1, f2, zeta
    RayleighDampingRatio               - zeta(f) for the plot
    UpdateRayleighFromForm             - recompute and repaint plot
    UpdateRayleighPlot                 - axes, curve, dots, labels


== material-manager/tabs/stabilization.tcl ==

  Five stabilisation features in one tab.

    BuildStabilizationTab              - the five labelframes
    SetWidgetStateRecursive            - generic helper (currently unused)
    UpdateStabilizationVisibility      - grey / un-grey dependent widgets
                                         based on enable flags;
                                         handles the hourglass scaling-vs-
                                         stiffness sub-mode


== material-manager/tabs/others.tcl ==

  Free-form text block + a notes area.

    BuildOthersTab


--------------------------------------------------------------------------------
5. HOW DATA FLOWS
--------------------------------------------------------------------------------

  Disk (numgeo_materials.tcldata)
      |
      | Load / Save  (persistence.tcl)
      v
  In-memory list `materials` (list of dicts)
      |
      | MaterialToForm / ReadForm  (persistence.tcl)
      v
  Form variables ::numgeo::matmgr::form_*
      |
      | Tk -textvariable / -variable
      v
  Tk widgets in the manager window

Each material dict has these top-level keys:
  name                    string
  phases                  1..3
  densities               list of phase densities (length == phases)
  fluid_properties        dict: kw, ka (bulk moduli)
  viscosity               dict: mu_w, mu_a
  mechanical_model        display name (e.g. "Mohr-Coulomb")
  mechanical_keyword      numgeo keyword (e.g. "Mohr-Coulomb")
  mechanical_params       dict: {key -> value} (p1 -> "30000", p2 -> "0.3", ...)
  mechanical_body         multi-line body string ready for the writer
  permeability_model      display name
  permeability_keyword    numgeo keyword
  permeability_params     dict
  permeability_body       multi-line body string
  hydraulic_model         (same shape)
  hydraulic_keyword
  hydraulic_params
  hydraulic_body
  relperm_model           (same shape)
  relperm_keyword
  relperm_params
  relperm_body
  rayleigh                dict: enabled f1 f2 damping alpha beta
  stabilization           dict: per-feature enable flags + values
  others                  multi-line text appended verbatim by the writer
  notes                   user comment, not written to the input file


--------------------------------------------------------------------------------
6. ADDING THINGS
--------------------------------------------------------------------------------

A. Add a new mechanical constitutive model

   1. Edit material-manager/metadata.tcl:
      - append the display name to `model_types`,
      - add an entry to `mechanical_parameter_specs($model)` listing
        every parameter as {key label default kind optionName},
      - add a layout to `mechanical_line_layouts($model)` describing
        which parameters go on which body line,
      - add a keyword mapping to `default_keywords($model)`.

   2. Restart GiD. Nothing else needs to change.

B. Add a new permeability / hydraulic / relperm model

   Same as (A) but in the matching *_parameter_specs / *_line_layouts
   arrays in metadata.tcl. The structured editor (model-editor.tcl) is
   generic and will build the new parameter table automatically.

C. Add a new tab

   1. Create a new file in material-manager/tabs/ named after the tab.
   2. Add a Build<Name>Tab proc in it.
   3. Edit material-manager.tcl: add a `source ...` line at the bottom
      of the source list AND a call to Build<Name>Tab inside BuildNotebook.
   4. If the new tab has any form variables, declare them inside the
      `namespace eval ::numgeo::matmgr { ... }` block in
      material-manager.tcl with sensible defaults.
   5. If the new tab needs traces, add them to AddFormTraces /
      RemoveFormTraces in traces.tcl.

D. Add a new stabilisation feature

   1. Add a `<feature>_enabled` and any value fields to the
      `form_stabilization` array_set block in material-manager.tcl.
   2. Add the same fields to DefaultMaterial in persistence.tcl.
   3. Add a new labelframe in BuildStabilizationTab
      (tabs/stabilization.tcl) and the matching greyout block in
      UpdateStabilizationVisibility.
   4. Add the persistence path in MaterialToForm / ReadForm
      (persistence.tcl) - just two lines per field.
   5. Add the validation in ValidateMaterial (validate.tcl) if you
      want enabled + empty value to be an error.

E. Migrate an old material database

   NormaliseMaterial in persistence.tcl is the hook. It is called
   every time a material is loaded into the form, so any missing
   key is filled with the default. To add a migration:

     - extend NormaliseMaterial with `if {[dict exists $mat <old-key>]}`,
     - copy/rename the value into the new schema,
     - the result already contains every key from DefaultMaterial.


--------------------------------------------------------------------------------
7. WORTH KNOWING TRAPS
--------------------------------------------------------------------------------

  - Tcl array-element write traces are not reliable across versions for
    triggering on `set arr(key) value`. If you rely on a trace to fire
    when a single element is written, also bind <KeyRelease>/<FocusOut>
    on the widget that writes that element. See KScaleTrace and
    RebuildModelParameterTable for the working pattern.

  - In Tcl, `{}` SUPPRESSES backslash substitution. To produce Unicode
    characters in a literal, use `"..."`, e.g. `"(length\u00b2)"`. The
    metres-squared label looks like `lengthÂ²` if you brace it.

  - `[file dirname [info script]]` returns the directory of the file
    currently being sourced. Combined with `source [file join ... ...]`
    it gives portable paths.

  - The numgeo unit system used by this manager is m, s, t, kN, kPa.
    Density is in t/m^3 (= g/cm^3), pressure in kPa, dynamic viscosity
    in kPa.s, intrinsic permeability k in m^2. The K-scale display
    formula assumes these units; see permeability.tcl header comment.

  - `tk_messageBox` is the simplest sanity check when you cannot figure
    out whether a callback is firing. Add it temporarily, restart GiD,
    remove it once you know.

  - Edit numgeo_materials.tcldata only via the manager. The file is a
    Tcl `set` assignment; a syntax error there silently empties the
    database (Load catches it but won't recover the data).


--------------------------------------------------------------------------------
8. CHANGELOG
--------------------------------------------------------------------------------

  20-05-2026  J. Machacek   Initial version (monolithic ~3700-line file).
  25-05-2026  R. Dogra      Added Rayleigh damping + dynamic viscosity +
                            bulk modulus.
  25-05-2026  R. Dogra      Added Stabilization parameters.
  27-05-2026  R. Dogra      Fixed K-scale bar not updating when k changed
                            (Tcl array-trace + entry-widget binding).
  27-05-2026  R. Dogra      Fixed wrong unit label on intrinsic permeability
                            (length^2 instead of length/time).
  27-05-2026  R. Dogra      Refactor: split the monolithic file into the
                            module layout documented above.


================================================================================
  END OF README
================================================================================
