================================================================================
  numgeo Truss/Beam Material Manager - Source layout and developer guide
================================================================================

Author : Rahul Dogra, Technical University of Darmstadt
Date   : 28-07-2026
Scope  : Tcl/Tk material editor for one-dimensional structural materials
         (truss and beam elements) of the numgeo GiD problemtype.
         Originally implemented as a single ~1270-line file by J. Machacek
         (20-05-2026). Split into modules on 28-07-2026 by R. Dogra,
         following the layout of the continuum material manager.


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

The Truss/Beam Material Manager is the Tk dialog launched from the numgeo
menu. It lets the user:

  - browse / create / duplicate / delete truss and beam materials,
  - edit each material across two tabs: Density and Truss/Beam properties,
  - fill the section properties from a rolled-steel profile catalogue,
  - save the database to numgeo_truss_beam_materials.tcldata inside the
    project,
  - hand a material name back to CustomLib so it can be assigned to
    geometrical entities.

Only the material NAME ever reaches CustomLib. The complete definitions are
written by WriteMaterials (write.tcl) into material-definitions.inp at
calculation time, called from scripts/write-material.tcl.

The manager mirrors the continuum material manager
(scripts/material-manager.tcl and scripts/material-manager/) on purpose:
same window layout, same database pattern, same module split. Anything
learnt about one applies to the other.


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

numgeo.gid/
  numgeo.tcl                                 - sources truss-beam-material-manager.tcl
                                               (inside LoadScripts, unchanged)
  scripts/
    truss-beam-material-manager.tcl          - entry point and window glue
    truss-beam-material-manager/             - all split modules
      README.txt                             - this file
      metadata.tcl                           - parameter specs, profile catalogue
      helpers.tcl                            - small utility procs
      database.tcl                           - load / save / dict <-> form
      profiles.tcl                           - section-profile presets
      section-editor.tcl                     - parameter table of the section tab
      traces.tcl                             - variable traces + refresh procs
      validate.tcl                           - top-level ValidateMaterial
      write.tcl                              - numgeo input syntax writer
      tabs/
        density.tcl                          - density tab
        section-properties.tcl               - truss/beam properties tab

The split is by responsibility, not by tab. Metadata, helpers and the
database live in their own files so that the tab files and the editor can
use them without duplication.

The entry point keeps its old path and its old name, so no other file in
the problemtype has to change: numgeo.tcl still sources exactly the same
script, and every public proc keeps its fully qualified name.


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

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

  metadata.tcl                  (creates the namespace, fills the spec tables)
  helpers.tcl                   (PropertySpecs, _DictGet, _Warn, ...)
  database.tcl                  (Init, Load, Save, MaterialToForm, ReadForm)
  profiles.tcl                  (ProfileNames, ProfileValues, ApplySelectedProfile)
  section-editor.tcl            (UpdatePropertyTab, BuildParameterTable, ...)
  traces.tcl                    (AddFormTraces, UpdateHeader, ...)
  validate.tcl                  (ValidateMaterial)
  write.tcl                     (WriteMaterials, _WriteLine)
  tabs/density.tcl              (BuildDensityTab)
  tabs/section-properties.tcl   (BuildPropertyTab)

Notes:
  - The order matters for SOURCING (metadata.tcl creates the namespace that
    the other files extend). It does NOT matter for cross-file proc calls,
    because Tcl resolves proc names at call time, not at source time.
  - The entry file's namespace eval block (with all form_* variables) runs
    AFTER the source list. This is intentional: the sourced files only
    define procs and fill static tables, they never read the form_*
    variables at source time.
  - Nothing in the module files touches Tk at source time, so the whole set
    can be sourced in a plain tclsh for a syntax check.


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

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

  Source list, namespace declaration with every runtime and form variable,
  plus the window glue:

    Preload                   - pre-build the window during problemtype
                                startup so the menu click feels instant
    Open                      - build the manager window, run the modal
                                event loop, return the selected material
    _SetWindowIcon            - per-window favicon for the toplevel
    ConfigureTreeStyle        - selected-row colours of the material tree
    ConfigureSelectMode       - toggle the "Use selected material" button
    BuildNotebook             - just calls the Build*Tab procs in order

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

    RefreshTree               - rebuild the tree from the materials list
    SortedMaterialRows        - name / type / profile row per material
    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 ("Apply & Save" button)
    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
    Close                     - close window, prompt to save if dirty
    CurrentTreeMaterial       - name of the currently selected tree row


== truss-beam-material-manager/metadata.tcl ==

  Static lookup tables only. No procs. Editing this file is what you do when
  you add a section parameter or a section profile.

    truss_specs        - {key label unit help} per truss parameter
    beam_specs         - {key label unit help} per beam parameter
    profile_library    - IPE catalogue, in native catalogue units:
                         A_mm2, Iy_1e6, Iz_1e6, J_1e3


== truss-beam-material-manager/helpers.tcl ==

    PropertySpecs      - beam_specs for "Beam", truss_specs otherwise
    _DictGet           - safe dict read with a default
    _ArrayGet          - safe array read with a default, trimmed
    _Warn              - warning dialog, or stderr when headless
    _AskYesNo          - yes/no dialog
    _AskYesNoCancel    - yes/no/cancel dialog

  The dialog helpers degrade gracefully when Tk is unavailable, so the
  writer path works in batch mode.


== truss-beam-material-manager/database.tcl ==

    Init                         - bootstrap for the active problemtype
    ProjectDirectory             - directory used for the database file
    DatabaseFile                 - numgeo_truss_beam_materials.tcldata
    Load                         - read the database into memory
    Save                         - atomic write back to disk (via .tmp)
    GetMaterialNames             - sorted names
    GetMaterialNamesForCustomLib - comma-separated names for CustomLib
    OpenFromCustomLib            - CustomLib edit hook (xml/solid-section.xml)
    FindMaterialIndex            - locate a material by name
    DefaultMaterial              - fresh material with all defaults
    NormaliseMaterial            - upgrade an old dict to the current schema
    NormaliseParameterDict       - drop unknown keys, add missing ones
    MaterialToForm               - material dict -> form variables
    ReadForm                     - form variables -> material dict

  Material dictionary schema:

    name          unique material name, no commas or line breaks
    type          Truss | Beam
    section_unit  m | mm
    density       density value as entered
    profile       Custom, or a key of profile_library
    truss_params  dict with keys A, E
    beam_params   dict with keys A, E, G, Iyy, Izz, J, kappaY, kappaZ

  Both parameter dicts are always stored, so switching a material between
  Truss and Beam and back does not lose the other type's values.


== truss-beam-material-manager/profiles.tcl ==

    ProfileNames         - Custom plus every catalogue entry
    ProfileValues        - catalogue values converted to the active unit
    ApplySelectedProfile - copy those values into param_var

  Presets fill geometry only (A, Iyy, Izz, J, kappaY, kappaZ). E and G are
  material properties and are never overwritten.


== truss-beam-material-manager/section-editor.tcl ==

    UpdatePropertyTab       - retitle the tab, rebuild its table
    BuildParameterTable     - one Parameter/Value/Unit row per spec entry
    ParameterInnerChanged   - scrollregion update on content change
    ParameterCanvasChanged  - width re-fit on canvas resize
    DisplayUnitForProperty  - unit label for a key in the active unit

  The table is rebuilt from scratch on every element-type and section-unit
  change. It lives in a canvas so the eight beam rows stay reachable in a
  small window.


== truss-beam-material-manager/traces.tcl ==

    AddFormTraces / RemoveFormTraces
    UpdateHeaderTrace        - adapter for form_name
    TypeChangedTrace         - adapter for form_type
    UnitChangedTrace         - adapter for form_unit
    SectionUnitButtonChanged - explicit radiobutton command
    ProfileChangedTrace      - adapter for form_profile
    UpdateHeader             - header label above the form

  Every adapter returns early while loading_form is set, so loading a
  material never triggers a profile recalculation.


== truss-beam-material-manager/validate.tcl ==

    ValidateMaterial   - first error string, or "" when valid

  Only the parameter block of the ACTIVE element type is validated, which is
  what makes switching the type back and forth non-destructive.


== truss-beam-material-manager/write.tcl ==

    WriteMaterials     - write every material in numgeo input syntax
    _WriteLine         - one line via GiD_WriteCalculationFile, or stdout

  Emitted per material:

    ** Material = <name>
    *Material, name=<name>, phases=1
    *Density
    <density>
    *Truss properties            or   *Beam properties
    A, E                              A, E, G, Iyy, Izz, J, kappaY, kappaZ


== truss-beam-material-manager/tabs/density.tcl ==

    BuildDensityTab    - the single-row Density tab


== truss-beam-material-manager/tabs/section-properties.tcl ==

    BuildPropertyTab   - profile combobox, preset hint and the empty
                         container frame that UpdatePropertyTab fills


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

Disk -> form

  Load                 reads numgeo_truss_beam_materials.tcldata into the
                       materials list (a list of dicts)
  RefreshTree          shows name / type / profile per material
  ShowMaterial         -> MaterialToForm, which raises loading_form,
                       assigns the form_* variables and param_var, lowers
                       loading_form again, then refreshes the tab

Form -> disk

  ApplyForm            -> ReadForm assembles a dict from the form
                       -> ValidateMaterial checks it
                       -> the dict replaces the selected entry, or is
                          appended when the name is new
                       -> dirty = 1, RefreshTree, SelectMaterial
  SaveAndRefresh       ApplyForm, then Save (atomic .tmp + rename)

Form -> input file

  WriteMaterials       reloads from disk, normalises, validates, then emits
                       the material blocks. Unsaved editor state is
                       deliberately NOT written.

Editing side effects

  form_type change     -> TypeChangedTrace -> UpdatePropertyTab (new rows)
                          -> ApplySelectedProfile
  form_unit change     -> UnitChangedTrace -> new unit labels
                          -> ApplySelectedProfile recomputes the geometry
  form_profile change  -> ProfileChangedTrace -> ApplySelectedProfile


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

Add a section parameter (e.g. a warping constant for beams)

  1. add {key label unit help} to beam_specs in metadata.tcl,
  2. add the key to the beam_params dict in DefaultMaterial (database.tcl),
  3. add a unit rule to DisplayUnitForProperty (section-editor.tcl) if the
     label has to change with the m / mm switch,
  4. add the key to the *Beam properties line in WriteMaterials (write.tcl),
     in the column order numgeo expects.

  Steps 1 and 2 are enough for the table, the validator and the form
  round-trip. Step 4 is what actually reaches the solver.

Add a section profile

  1. add one entry to profile_library in metadata.tcl, in catalogue units.
     Nothing else changes: ProfileNames, the combobox and the unit
     conversion pick it up automatically.

Add a whole profile family (HEA, HEB, UPE, ...)

  Either add the entries to profile_library with a distinct name prefix, or
  introduce a second array plus a family combobox. The unit conversion in
  ProfileValues assumes the catalogue units documented in metadata.tcl; a
  family stored in different units needs its own conversion branch.

Add a tab

  1. create truss-beam-material-manager/tabs/<name>.tcl with a
     Build<Name>Tab proc,
  2. add it to the source list in the entry file,
  3. call it from BuildNotebook.


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

- loading_form is the guard against feedback loops. Any new trace adapter
  must check it first, otherwise loading a stored material will overwrite
  its values with profile defaults.

- param_var is ONE array shared by the truss and the beam layout. Keys with
  the same name (A, E) are shared on purpose; ReadForm splits the array into
  truss_params and beam_params by walking the specs, so a key that exists in
  only one spec never leaks into the other block.

- Section units are a DISPLAY choice that is stored with the material. The
  values are written to the input file exactly as entered, in the unit the
  user picked. Switching m / mm does not convert values the user typed; it
  only relabels the table and recomputes profile-derived geometry.

- SelectMaterial with a name that no longer exists falls back to the first
  row, and to ClearForm when the tree is empty. Deleting therefore always
  leaves the form in a defined state.

- ApplyForm resolves the target row by the SELECTED name, not by the name in
  the entry field, which is what makes renaming work. The extra
  same_name_idx check rejects a rename onto an existing material.

- Save writes to <file>.tmp and renames, so an interrupted write cannot
  corrupt an existing database.

- The tree is rebuilt on every Apply. SelectMaterial is called immediately
  afterwards to restore the edited row; do not add a second RefreshTree
  after Save or the visible selection disappears.

- WriteMaterials calls Load first. A material that is only in the editor,
  not on disk, will not be written. This is intentional but surprises users
  who forget to press Apply & Save.


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

20-05-2026  J. Machacek  Initial truss/beam material manager (single file).
23-07-2026  J. Machacek  Persistent selected-row highlighting, selection-
                         preserving saves.
28-07-2026  R. Dogra     Apply and Save combined into one "Apply & Save"
                         button.
28-07-2026  R. Dogra     Split into the modular file structure described
                         above. No behavioural change: same procs, same
                         bodies, same public API.

================================================================================
