# 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/` (builds `libKCSolve.so`) - **Python module:** `src/Mod/Assembly/Solver/bindings/` (builds `kcsolve.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 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: ```cpp auto& reg = SolverRegistry::instance(); // Registration (at module init) reg.register_solver("ondsel", []() { return std::make_unique(); }); // 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 `/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](../reference/kcsolve-python.md) for details. Key capabilities: - All enums, structs, and classes accessible from Python - Subclass `IKCSolver` in 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](../reference/kcsolve-python.md) - [INTER_SOLVER.md](../../INTER_SOLVER.md) -- full architecture specification