Architecture¶
This page describes Elementa's internal design for contributors and developers who want to extend the application.
Package Layout¶
elementa/
├── __init__.py # version, author, license
├── __main__.py # CLI entry point: `elementa` / `python -m elementa`
├── assets/ # PNG icons and ICO files
├── cad/
│ └── cad.py # ElementaCAD: thin wrapper around gmsh OCC kernel
├── core/
│ ├── cad_builder.py # CADBuilder: drives ElementaCAD from ProjectState
│ ├── evaluator.py # ParameterEvaluator: resolves symbolic parameters
│ ├── exceptions.py # Custom exception hierarchy
│ ├── geometry_registry.py # ShapeDef registry mapping kind→build method
│ ├── logger.py # Logging factory (Python logging)
│ ├── material_library.py # Static material property database
│ └── project_state.py # ProjectState (QObject): central data + serialisation
├── physics/
│ ├── base.py # PhysicsState dataclass
│ ├── registry.py # PhysicsDescriptor + PHYSICS_REGISTRY + helpers
│ ├── electrostatics.py
│ └── heat_transfer.py
└── ui/
├── expr.py # safe_eval: sandboxed expression evaluator
├── graphics_canvas.py
├── icon_manager.py
├── main_window.py # ElementaMainWindow + SolverThread
├── model_builder.py
├── new_project_wizard.py
├── plot_window.py
├── project_manager.py
├── property_panel.py
├── ribbon_toolbar.py
├── welcome_window.py
└── panels/
├── mesh_panel.py
├── parameters_panel.py
├── physics_panel.py
└── study_panel.py
Data Flow¶
User Action
│
▼
UI Panel / Ribbon
│ updates
▼
ProjectState (QObject)
│ emits signals (state_changed, geometry_changed, …)
▼
Main Window / Canvas
│ reacts to signals
│
├── CADBuilder.build_model(project)
│ │ ParameterEvaluator.resolve_parameters()
│ │ SHAPE_REGISTRY[kind].build(cad, name, params)
│ └─► ElementaCAD (gmsh API)
│
└── SolverThread (QThread)
│ PhysicsDescriptor.assemble_and_solve(PhysicsState)
│ scikit-fem assembly + direct solve
└─► ProjectState.set_results(basis, results)
│ emits results_changed
▼
Plot Window / Probes
ProjectState¶
ProjectState is the single source of truth for all model data. It is a QObject and emits Qt signals when data changes, allowing all UI panels to stay in sync without direct coupling.
Key signals:
| Signal | Emitted when |
|---|---|
state_changed |
Any general model update |
geometry_changed |
Geometry items or boolean ops modified |
mesh_changed |
Mesh generated or loaded |
results_changed |
Solver finished or results loaded |
selection_changed |
User selects boundaries/domains on canvas |
Physics Plugin System¶
All physics modules register themselves into PHYSICS_REGISTRY (a dict mapping name → descriptor class). The main window queries the registry to populate the physics selector, build the physics panel, and dispatch to the correct assemble_and_solve method.
PHYSICS_REGISTRY = {
"Electrostatics": ElectrostaticsDescriptor,
"Heat Transfer": HeatTransferDescriptor,
...
}
Adding a new physics module requires zero changes to existing code — only an import in physics/__init__.py.
Solver Thread¶
The solver runs in SolverThread (QThread) to keep the GUI responsive. It emits:
progress(int)— 0–100 for the progress barstatus(str)— human-readable stage descriptionfinished_solve(PhysicsState)— on successerror(str)— on failure
The main window connects these signals and updates ProjectState on the main thread.
Safe Expression Evaluator¶
elementa.ui.expr.safe_eval evaluates mathematical expressions from user input in a sandboxed environment. It uses Python's ast module to white-list only:
- Numeric constants
- Arithmetic operators
- Unary operators
ast.Namenodes that are math functions or known parameter names- Calls to
mathmodule functions
This prevents arbitrary code execution while supporting the full range of scientific expressions users need.