Skip to content

[Bindings] Refactor Sofa.Component bindings layout - #653

Open
fredroy wants to merge 4 commits into
sofa-framework:masterfrom
fredroy:component_bindings_layout
Open

fredroy wants to merge 4 commits into
sofa-framework:masterfrom
fredroy:component_bindings_layout

Conversation

@fredroy

@fredroy fredroy commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

(no AI flag is present but obviously Claude did the heavy lifting here)

Lots of files are modified but mostly because of renaming and/or compat layer.

The refactoring aimed to replicate the same file structure as in the SOFA.Component directory. (Sofa.FEM and Sofa.GL as well)
It involved also some stubgen updates because of... stuff (did not really understand but it seems because of naming and bugs or whatever)

This is supposed not to break any existing scripts using SofaPytthon3.

For completeness, it would break in those cases:

  • Code that compares module or type names as strings.
  • Installing over an old install without cleaning it.
  • Code that imported the generated stub files at runtime
  • C++ code (plugins that include the old headers or link to per-module targets)

Tested locally on macOS 27 (clang), ubuntu 26.04 (gcc and clang) and windows (MSVC26)

Claude summary:

The bindings of SOFA components (bindings/Modules, bindings/SofaExporter) are
moved to bindings/Component, mirroring SOFA's Sofa/Component layout. Each SOFA
module Sofa.Component.<Area>.<Sub> gets a python module of the same name:

  Sofa.SofaBaseTopology     -> Sofa.Component.Topology.Container.Grid
  Sofa.SofaDeformable       -> Sofa.Component.SolidMechanics.Spring
                               Sofa.Component.SolidMechanics.FEM.Elastic
  Sofa.SofaLinearSolver     -> Sofa.Component.LinearSolver.Iterative
  Sofa.SofaLinearSystem     -> Sofa.Component.LinearSystem
  Sofa.SofaConstraintSolver -> Sofa.Component.Constraint.Lagrangian.Solver
  SofaExporter              -> Sofa.Component.IO.Mesh

Sofa.FEM and Sofa.GL are not components: their bindings (previously
Sofa.SofaFEM and Sofa.SofaGL) move next to Sofa.Core in bindings/Sofa.

- A binding is only built when the SOFA module it binds is available.
- The old python modules are kept as deprecated aliases re-exporting the new
  ones, and Bindings.Modules as a cmake alias of Bindings.Component. Outdated
  compiled modules are removed from the build tree so they do not shadow them.
- SP3_add_python_module: compute the macOS rpath to site-packages from the
  module depth, and set INSTALL_NAME_DIR on the module target.
- Stub generation: for SOFA modules having a compiled binding, the stubs of
  the components without binding are merged in a .pyi next to the binding
  instead of a package that would shadow it.
- Tests, examples and docs use the new module names. The matrix access test
  scenes now load the plugins they use instead of relying on test order.

And Claude summary for the stubgen:

Component bindings are now compiled modules (e.g. `Sofa/Component/SolidMechanics/Spring.<ext>`), so the stub packages written by `sofaStubgen.py` (`.../Spring/__init__.py`) would have shadowed them.

- **Bound SOFA modules get a `.pyi` next to the binding:** the pybind11/mypy stub of the binding, plus placeholder classes for that module's components that have no binding. Classes that are already bound are skipped. Modules without bindings keep the previous package layout.
- `generate_module_stubs()` now handles single-module output, and a warning is printed when an existing directory would shadow a binding.
- **Generated stubs were mostly invalid on master (309 of 362 did not parse), now fixed:**
  - constructor arguments are now keyword-only and comma-separated;
  - keyword, duplicate and invalid data field names are filtered out;
  - docstrings are escaped;
  - `TypeHints.SofaArray` was replaced by `SofaArray`, which is the name actually imported.
- **Found by the full pybind11-stubgen run:**
  - `LinearSpring`'s `restLength` now defaults to `0` (as in C++);
  - `from __future__` imports are moved to the top of generated `.pyi` files.
- **Behavior change:** for the 7 bound modules, placeholders of unbound components exist only for type checkers and can no longer be imported at runtime.
- **Tested:** all 369 generated stubs compile and execute

The bindings of SOFA components (bindings/Modules, bindings/SofaExporter) are
moved to bindings/Component, mirroring SOFA's Sofa/Component layout. Each SOFA
module Sofa.Component.<Area>.<Sub> gets a python module of the same name:

  Sofa.SofaBaseTopology     -> Sofa.Component.Topology.Container.Grid
  Sofa.SofaDeformable       -> Sofa.Component.SolidMechanics.Spring
                               Sofa.Component.SolidMechanics.FEM.Elastic
  Sofa.SofaLinearSolver     -> Sofa.Component.LinearSolver.Iterative
  Sofa.SofaLinearSystem     -> Sofa.Component.LinearSystem
  Sofa.SofaConstraintSolver -> Sofa.Component.Constraint.Lagrangian.Solver
  SofaExporter              -> Sofa.Component.IO.Mesh

Sofa.FEM and Sofa.GL are not components: their bindings (previously
Sofa.SofaFEM and Sofa.SofaGL) move next to Sofa.Core in bindings/Sofa.

- A binding is only built when the SOFA module it binds is available.
- The old python modules are kept as deprecated aliases re-exporting the new
  ones, and Bindings.Modules as a cmake alias of Bindings.Component. Outdated
  compiled modules are removed from the build tree so they do not shadow them.
- SP3_add_python_module: compute the macOS rpath to site-packages from the
  module depth, and set INSTALL_NAME_DIR on the module target.
- Stub generation: for SOFA modules having a compiled binding, the stubs of
  the components without binding are merged in a .pyi next to the binding
  instead of a package that would shadow it.
- Tests, examples and docs use the new module names. The matrix access test
  scenes now load the plugins they use instead of relying on test order.
Most of the stubs generated by sofaStubgen.py for Sofa.Component did not
parse (309 of 362), and many of the others failed when imported:

- the constructor arguments without default value followed the optional
  'template' argument, and the required and optional arguments were joined
  without a comma: the arguments are now keyword-only, separated by commas.
- data fields named after a python keyword other than the few listed (eg.
  'global'), duplicated or not valid identifiers were not filtered out, and
  the keyword ones were written as invalid class attributes: they are now
  written as comments.
- descriptions ending with a quote closed the docstring early, and
  backslashes produced invalid escape sequences: they are now escaped.
- array types were annotated 'TypeHints.SofaArray' while only 'SofaArray' is
  imported, raising a NameError when importing the stub.

All the 369 generated stubs now compile and import.
restLength had no default while following arguments that have one, which
made the generated stub invalid python ("parameter without a default
follows parameter with a default"). Use 0, the default of the C++
constructor.
pybind11-stubgen can write imports such as 'import Sofa.Core' before
'from __future__ import annotations', which makes the .pyi files invalid
python. After stub generation, move the __future__ imports right after the
module docstring.
@fredroy
fredroy requested review from bakpaul and hugtalbot November 11, 2026 01:15
@fredroy fredroy added pr: status to review pr: clean-fix pr: new feature pr: highlighted in next release Highlight this contribution in the notes of the upcoming release topic for next dev-meeting Worth discussion at dev meeting labels Nov 11, 2026
@damienmarchal

Copy link
Copy Markdown
Contributor

Hi @fredroy

Thanks for this work. Looks ok, just one open questions. Shouldn't we move the binding into a dedicated subdirectory of each Sofa.Component.XXX instead of centralizing all that into SofaPython3.

If I remember correctly the choice of centralizing everything was a consequence SofaPython3 was a plugin that may be compiled after the compilation of each Sofa.Component's target. If we want SofaPython3 to be detected... it has to be injected at the beginning of the build process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: clean-fix pr: highlighted in next release Highlight this contribution in the notes of the upcoming release pr: new feature pr: status to review topic for next dev-meeting Worth discussion at dev meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants