Files
create/docs/src/architecture/ondsel-solver.md
forbes 406e120180
All checks were successful
Build and Test / build (pull_request) Successful in 28m58s
docs: KCSolve architecture and Python API reference
- 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
2026-02-19 18:59:05 -06:00

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/ (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>.

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 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