feat(assembly): pluggable solver system — Phase 1 (#287) #297

Merged
forbes merged 5 commits from feat/solver-api-types into main 2026-02-19 22:58:35 +00:00
Owner

Summary

Introduces the KCSolve pluggable solver abstraction layer for the Assembly module, decoupling AssemblyObject from direct OndselSolver dependency. This is Phase 1 of the solver plugin system described in #287.

After this PR, OndselSolver is accessed exclusively through the KCSolve::IKCSolver interface and KCSolve::SolverRegistry. No OndselSolver headers are included anywhere in Mod/Assembly/App or Mod/Assembly/Gui.

Commits (5 phases)

Phase Issue Commit Description
1a #292 47e6c14 API types & interfaceTypes.h (Transform, BaseJointKind, SolveContext, SolveResult, etc.), IKCSolver.h (abstract solver interface with solve/drag/simulation/diagnostics), KCSolveGlobal.h (export macro)
1b #293 76b9196 SolverRegistry — Meyer's singleton with thread-safe register/get, default solver, joints_for() capability queries, scan()/scan_default_paths() for dlopen-based plugin discovery
1c #294 32dbe20 OndselAdapter — Concrete IKCSolver wrapping OndselSolver's ASMT API. Translates KCSolve types ↔ ASMTAssembly/ASMTPart/ASMTJoint. All OndselSolver #includes confined to OndselAdapter.cpp
1d #295 5c33aac Refactor AssemblyObject — Rewrites AssemblyObject to use IKCSolver exclusively. Removes 30+ OndselSolver includes, replaces mbdAssembly/objectPartMap with solver_/partIdToObjs_/objToPartId_. New buildSolveContext() method builds solver-agnostic input
1e #296 934cdf5 Regression tests — 18 C++ gtests (SolverRegistry + OndselAdapter) and 6 Python unittests (full-stack solve round-trips)

Architecture

AssemblyObject  ──→  IKCSolver (interface)  ──→  OndselAdapter  ──→  OndselSolver
                          ↑
                    SolverRegistry
                     (singleton)
  • KCSolve shared library (src/Mod/Assembly/Solver/) — standalone, links only FreeCADBase + OndselSolver
  • Assembly module links KCSolve instead of OndselSolver directly
  • Future solver backends (e.g. SolveSpace, server-side) implement IKCSolver and register via SolverRegistry

Key design decisions

  • BaseJointKind enum (24 values) decomposes the existing JointType (13) × DistanceType (35) matrix into explicit primitive constraint types
  • Transform struct uses (w,x,y,z) quaternion convention (mathematical standard); adapter layer handles swap to/from Base::Rotation(x,y,z,w)
  • Fixed-joint bundling handled by registerPart() in AssemblyObject — multiple FreeCAD objects map to a single solver part ID with offsets stored in partIdToObjs_
  • Plugin discovery via dlopen/LoadLibrary scanning directories for kcsolve_create() + kcsolve_api_version() exports (Phase 2+ usage)

Testing

  • 18 C++ gtest cases (KCSolve_tests_run): all pass
    • SolverRegistry: register/get, duplicates, defaults, available, joints_for
    • OndselAdapter: identity, capabilities, Fixed/Revolute solve, no-ground handling, exception safety, drag protocol, redundancy diagnostics
  • 1 existing Assembly gtest (Assembly_tests_run): still passes (no regressions)
  • 6 Python unittest cases (TestSolverIntegration): registered for FreeCAD test runner
    • Fixed/Revolute joint solve, no-ground error code, redundancy detection, ASMT export, deterministic stability

Files changed

  • 13 new files in src/Mod/Assembly/Solver/ and tests/src/Mod/Assembly/Solver/
  • 4 modified filesAssemblyObject.h/.cpp (rewritten), AppAssembly.cpp, Assembly CMakeLists
  • 3 test registration filestests/CMakeLists.txt, TestAssemblyWorkbench.py, Assembly CMakeLists.txt
  • 20 files total, +3770 / −1246 lines

Closes #292, closes #293, closes #294, closes #295, closes #296

## Summary Introduces the **KCSolve** pluggable solver abstraction layer for the Assembly module, decoupling AssemblyObject from direct OndselSolver dependency. This is Phase 1 of the solver plugin system described in #287. After this PR, OndselSolver is accessed exclusively through the `KCSolve::IKCSolver` interface and `KCSolve::SolverRegistry`. No OndselSolver headers are included anywhere in `Mod/Assembly/App` or `Mod/Assembly/Gui`. ## Commits (5 phases) | Phase | Issue | Commit | Description | |-------|-------|--------|-------------| | 1a | #292 | `47e6c14` | **API types & interface** — `Types.h` (Transform, BaseJointKind, SolveContext, SolveResult, etc.), `IKCSolver.h` (abstract solver interface with solve/drag/simulation/diagnostics), `KCSolveGlobal.h` (export macro) | | 1b | #293 | `76b9196` | **SolverRegistry** — Meyer's singleton with thread-safe register/get, default solver, `joints_for()` capability queries, `scan()`/`scan_default_paths()` for dlopen-based plugin discovery | | 1c | #294 | `32dbe20` | **OndselAdapter** — Concrete `IKCSolver` wrapping OndselSolver's ASMT API. Translates KCSolve types ↔ ASMTAssembly/ASMTPart/ASMTJoint. All OndselSolver `#include`s confined to `OndselAdapter.cpp` | | 1d | #295 | `5c33aac` | **Refactor AssemblyObject** — Rewrites AssemblyObject to use `IKCSolver` exclusively. Removes 30+ OndselSolver includes, replaces `mbdAssembly`/`objectPartMap` with `solver_`/`partIdToObjs_`/`objToPartId_`. New `buildSolveContext()` method builds solver-agnostic input | | 1e | #296 | `934cdf5` | **Regression tests** — 18 C++ gtests (SolverRegistry + OndselAdapter) and 6 Python unittests (full-stack solve round-trips) | ## Architecture ``` AssemblyObject ──→ IKCSolver (interface) ──→ OndselAdapter ──→ OndselSolver ↑ SolverRegistry (singleton) ``` - **`KCSolve` shared library** (`src/Mod/Assembly/Solver/`) — standalone, links only `FreeCADBase` + `OndselSolver` - **`Assembly` module** links `KCSolve` instead of `OndselSolver` directly - Future solver backends (e.g. SolveSpace, server-side) implement `IKCSolver` and register via `SolverRegistry` ## Key design decisions - **BaseJointKind enum** (24 values) decomposes the existing JointType (13) × DistanceType (35) matrix into explicit primitive constraint types - **Transform struct** uses `(w,x,y,z)` quaternion convention (mathematical standard); adapter layer handles swap to/from `Base::Rotation(x,y,z,w)` - **Fixed-joint bundling** handled by `registerPart()` in AssemblyObject — multiple FreeCAD objects map to a single solver part ID with offsets stored in `partIdToObjs_` - **Plugin discovery** via `dlopen`/`LoadLibrary` scanning directories for `kcsolve_create()` + `kcsolve_api_version()` exports (Phase 2+ usage) ## Testing - **18 C++ gtest cases** (`KCSolve_tests_run`): all pass - SolverRegistry: register/get, duplicates, defaults, available, joints_for - OndselAdapter: identity, capabilities, Fixed/Revolute solve, no-ground handling, exception safety, drag protocol, redundancy diagnostics - **1 existing Assembly gtest** (`Assembly_tests_run`): still passes (no regressions) - **6 Python unittest cases** (`TestSolverIntegration`): registered for FreeCAD test runner - Fixed/Revolute joint solve, no-ground error code, redundancy detection, ASMT export, deterministic stability ## Files changed - **13 new files** in `src/Mod/Assembly/Solver/` and `tests/src/Mod/Assembly/Solver/` - **4 modified files** — `AssemblyObject.h/.cpp` (rewritten), `AppAssembly.cpp`, Assembly CMakeLists - **3 test registration files** — `tests/CMakeLists.txt`, `TestAssemblyWorkbench.py`, Assembly `CMakeLists.txt` - **20 files total**, +3770 / −1246 lines Closes #292, closes #293, closes #294, closes #295, closes #296
forbes added 5 commits 2026-02-19 22:58:02 +00:00
Add the pluggable solver API as header-only files under
src/Mod/Assembly/Solver/. This is Phase 1a of the pluggable solver
system (INTER_SOLVER.md).

New files:
- Types.h: BaseJointKind enum (24 decomposed constraint types),
  Transform, Part, Constraint, SolveContext, SolveResult, and
  supporting types. Uses standalone types (no FreeCAD dependency)
  for future server worker compatibility.
- IKCSolver.h: Abstract solver interface with solve(), drag protocol
  (pre_drag/drag_step/post_drag), kinematic simulation
  (run_kinematic/num_frames/update_for_frame), and diagnostics.
  Only solve(), name(), and supported_joints() are pure virtual;
  all other methods have default implementations.
- SolverRegistry.h: Thread-safe singleton registry for solver
  backends with factory-based registration and default solver
  selection.
- CMakeLists.txt: INTERFACE library target (header-only for now).

Modified:
- Assembly/CMakeLists.txt: add_subdirectory(Solver)
- Assembly/App/CMakeLists.txt: link KCSolve INTERFACE target
Phase 1b of the pluggable solver system. Converts KCSolve from a
header-only INTERFACE target to a SHARED library and implements
the SolverRegistry with dynamic plugin discovery.

Changes:
- Add KCSolveGlobal.h export macro header (KCSolveExport)
- Move SolverRegistry method bodies from header to SolverRegistry.cpp
- Implement scan() with dlopen/LoadLibrary plugin loading
- Add scan_default_paths() for KCSOLVE_PLUGIN_PATH + system paths
- Plugin entry points: kcsolve_api_version() + kcsolve_create()
- API version checking (major version compatibility)
- Convert CMakeLists.txt from INTERFACE to SHARED library
- Link FreeCADBase (PRIVATE) for Console logging
- Link dl on POSIX for dynamic loading
- Fix -Wmissing-field-initializers warnings in IKCSolver.h defaults

The registry discovers plugins by scanning directories for shared
libraries that export the kcsolve C entry points. Plugins are
validated for API version compatibility before registration.
Manual registration via register_solver() remains available for
built-in solvers (e.g. OndselAdapter in Phase 1c).
Phase 1c of the pluggable solver system. Creates OndselAdapter — the
concrete IKCSolver implementation that wraps OndselSolver's Lagrangian
MBD engine behind the KCSolve abstraction layer.

The adapter translates KCSolve types (SolveContext, BaseJointKind,
Transform) to OndselSolver's ASMT hierarchy (ASMTAssembly, ASMTPart,
ASMTJoint, ASMTMarker) and extracts results back into SolveResult.
All 30+ OndselSolver #includes are confined to OndselAdapter.cpp.

Key implementation details:
- build_assembly(): creates ASMTAssembly from SolveContext
- create_joint(): maps 20 BaseJointKind values to ASMT joint types
  (eliminates the 35-case DistanceType switch — decomposition done upstream)
- Quaternion-to-rotation-matrix conversion for OndselSolver input
- Direct quaternion extraction for output (both use w,x,y,z convention)
- Drag lifecycle: pre_drag/drag_step/post_drag with stateful assembly
- Simulation lifecycle: run_kinematic/num_frames/update_for_frame
- Diagnostic extraction: iterates MBD system constraints for redundancy
- Static register_solver() for SolverRegistry integration
- ExternalSystem back-pointer NOT set — breaks bidirectional coupling

New files:
- Solver/OndselAdapter.h — class declaration with KCSolveExport
- Solver/OndselAdapter.cpp — full implementation (~530 lines)

Modified:
- Solver/CMakeLists.txt — add sources, link OndselSolver (PRIVATE)
Rewire AssemblyObject to call through KCSolve::IKCSolver instead of
directly manipulating OndselSolver ASMT types.

Key changes:
- Remove all 30+ #include <OndselSolver/*> from AssemblyObject.cpp
- Remove MbDPartData, objectPartMap, mbdAssembly members
- Add solver_ (IKCSolver), lastResult_ (SolveResult), partIdToObjs_ maps
- New buildSolveContext() builds SolveContext from FreeCAD document objects
  with JointType/DistanceType -> BaseJointKind decomposition
- New computeMarkerTransform() replaces handleOneSideOfJoint()
- New computeRackPinionMarkers() replaces getRackPinionMarkers()
- Rewrite solve/preDrag/doDragStep/postDrag/generateSimulation to call
  through IKCSolver interface
- Rewrite setNewPlacements/validateNewPlacements to use SolveResult
- Rewrite updateSolveStatus to use ConstraintDiagnostic
- Add export_native() to IKCSolver for ASMT export support
- Register OndselAdapter at Assembly module init in AppAssembly.cpp
- Remove OndselSolver from Assembly_LIBS (linked transitively via KCSolve)

Assembly module now has zero OndselSolver includes. All solver coupling
is confined to KCSolve/OndselAdapter.cpp.
test(assembly): regression tests for KCSolve solver refactor (#296)
All checks were successful
Build and Test / build (pull_request) Successful in 29m11s
934cdf5767
Phase 1e: Add C++ gtest and Python unittest coverage for the pluggable
solver system introduced in Phases 1a-1d.

C++ tests (KCSolve_tests_run):
- SolverRegistryTest (8 tests): register/get, duplicates, defaults,
  available list, joints_for capability queries
- OndselAdapterTest (10 tests): identity checks, supported/unsupported
  joints, Fixed/Revolute solve round-trips, no-grounded-parts handling,
  exception safety, drag protocol (pre_drag/drag_step/post_drag),
  redundant constraint diagnostics

Python tests (TestSolverIntegration):
- Full-stack solve via AssemblyObject → IKCSolver → OndselAdapter
- Fixed joint placement matching, revolute joint success
- No-ground error code (-6), redundancy detection (-2)
- ASMT export produces non-empty file
- Deterministic solve stability (solve twice → same result)
forbes merged commit f20ae3a667 into main 2026-02-19 22:58:35 +00:00
forbes deleted branch feat/solver-api-types 2026-02-19 22:58:36 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/create#297