- Replace OndselSolver architecture doc with KCSolve pluggable solver architecture covering IKCSolver interface, SolverRegistry, OndselAdapter, Python bindings, file layout, and testing - Add kcsolve Python API reference with full type documentation, module functions, usage examples, and pybind11 vector-copy caveat - Add INTER_SOLVER.md spec (previously untracked) with Phase 1 and Phase 2 marked as complete - Update SUMMARY.md with new page links
7.0 KiB
KCSolve: Pluggable Solver Architecture
KCSolve is the pluggable assembly constraint solver framework for Kindred Create. It defines an abstract solver interface (IKCSolver) and a runtime registry (SolverRegistry) that lets the Assembly module work with any conforming solver backend. The default backend wraps OndselSolver via OndselAdapter.
- Library:
src/Mod/Assembly/Solver/(buildslibKCSolve.so) - Python module:
src/Mod/Assembly/Solver/bindings/(buildskcsolve.so) - Tests:
tests/src/Mod/Assembly/Solver/(C++),src/Mod/Assembly/AssemblyTests/TestKCSolvePy.py(Python)
Architecture
┌──────────────────────────────────────────────────┐
│ Assembly Module (AssemblyObject.cpp) │
│ Builds SolveContext from FreeCAD document, │
│ calls solver via SolverRegistry │
├──────────────────────────────────────────────────┤
│ SolverRegistry (singleton) │
│ register_solver(), get(), available() │
│ Plugin discovery via scan() / scan_default_paths │
├──────────────┬───────────────────────────────────┤
│ OndselAdapter │ Python solvers │ Future plugins │
│ (C++ built-in)│ (via kcsolve) │ (.so plugins) │
└──────────────┴───────────────────────────────────┘
The Assembly module never references OndselSolver directly. All solver access goes through SolverRegistry::instance().get(), which returns a std::unique_ptr<IKCSolver>.
IKCSolver interface
A solver backend implements IKCSolver (defined in IKCSolver.h). Only three methods are pure virtual; all others have sensible defaults:
| Method | Required | Purpose |
|---|---|---|
name() |
yes | Human-readable solver name |
supported_joints() |
yes | List of BaseJointKind values the solver handles |
solve(ctx) |
yes | Solve for static equilibrium |
update(ctx) |
no | Incremental re-solve after parameter changes |
pre_drag(ctx, parts) |
no | Begin interactive drag session |
drag_step(placements) |
no | One mouse-move during drag |
post_drag() |
no | End drag session |
run_kinematic(ctx) |
no | Run kinematic simulation |
num_frames() |
no | Frame count after simulation |
update_for_frame(i) |
no | Retrieve frame placements |
diagnose(ctx) |
no | Detect redundant/conflicting constraints |
is_deterministic() |
no | Whether output is reproducible (default: true) |
export_native(path) |
no | Write solver-native debug file (e.g. ASMT) |
supports_bundle_fixed() |
no | Whether solver handles Fixed-joint bundling internally |
Core types
All types live in Types.h with no FreeCAD dependencies, making the header standalone for future server/worker use.
Transform -- position [x, y, z] + unit quaternion [w, x, y, z]. Equivalent to Base::Placement but independent. Note the quaternion convention differs from Base::Rotation which uses (x, y, z, w) ordering; the adapter layer handles the swap.
BaseJointKind -- 24 primitive constraint types decomposed from FreeCAD's JointType and DistanceType enums. Covers point constraints (Coincident, PointOnLine, PointInPlane), axis/surface constraints (Concentric, Tangent, Planar), kinematic joints (Fixed, Revolute, Cylindrical, Slider, Ball, Screw, Universal), mechanical elements (Gear, RackPinion), distance variants, and a Custom extension point.
SolveContext -- complete solver input: parts (with placements, mass, grounded flag), constraints (with markers, parameters, limits), optional motion definitions and simulation parameters.
SolveResult -- solver output: status code, updated part placements, DOF count, constraint diagnostics, and simulation frame count.
SolverRegistry
Thread-safe singleton managing solver backends:
auto& reg = SolverRegistry::instance();
// Registration (at module init)
reg.register_solver("ondsel", []() {
return std::make_unique<OndselAdapter>();
});
// Retrieval
auto solver = reg.get(); // default solver
auto solver = reg.get("ondsel"); // by name
// Queries
reg.available(); // ["ondsel", ...]
reg.joints_for("ondsel"); // [Fixed, Revolute, ...]
reg.set_default("ondsel");
Plugin discovery scans directories for shared libraries exporting kcsolve_api_version() and kcsolve_create(). Default paths: KCSOLVE_PLUGIN_PATH env var and <prefix>/lib/kcsolve/.
OndselAdapter
The built-in solver backend wrapping OndselSolver's Lagrangian constraint formulation. Registered as "ondsel" at Assembly module initialization.
Supports all 24 joint types. The adapter translates between SolveContext/SolveResult and OndselSolver's internal ASMTAssembly representation, including:
- Part placement conversion (Transform <-> Base::Placement quaternion ordering)
- Constraint parameter mapping (BaseJointKind -> OndselSolver joint classes)
- Interactive drag protocol (pre_drag/drag_step/post_drag)
- Kinematic simulation (run_kinematic/num_frames/update_for_frame)
- Constraint diagnostics (redundancy detection via MbD system)
Python bindings (kcsolve module)
The kcsolve pybind11 module exposes the full C++ API to Python. See KCSolve Python API for details.
Key capabilities:
- All enums, structs, and classes accessible from Python
- Subclass
IKCSolverin pure Python to create new solver backends - Register Python solvers at runtime via
kcsolve.register_solver() - Query the registry from the FreeCAD console
File layout
src/Mod/Assembly/Solver/
├── Types.h # Enums and structs (no FreeCAD deps)
├── IKCSolver.h # Abstract solver interface
├── SolverRegistry.h/cpp # Singleton registry + plugin loading
├── OndselAdapter.h/cpp # OndselSolver wrapper
├── KCSolveGlobal.h # DLL export macros
├── CMakeLists.txt # Builds libKCSolve.so
└── bindings/
├── PyIKCSolver.h # pybind11 trampoline for Python subclasses
├── kcsolve_py.cpp # Module definition (enums, structs, classes)
└── CMakeLists.txt # Builds kcsolve.so (pybind11 module)
Testing
- 18 C++ tests (
KCSolve_tests_run) covering SolverRegistry (8 tests) and OndselAdapter (10 tests including drag protocol and redundancy diagnosis) - 16 Python tests (
TestKCSolvePy) covering module import, type bindings, registry functions, Python solver subclassing, and full register/load/solve round-trips - 6 Python integration tests (
TestSolverIntegration) testing solver behavior through FreeCAD document objects
Related
- KCSolve Python API Reference
- INTER_SOLVER.md -- full architecture specification