feat(solver): define IKCSolver C++ API types and interface (#292)

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
This commit is contained in:
forbes
2026-02-19 15:55:57 -06:00
parent ab6d09c138
commit 47e6c14461
6 changed files with 645 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2025 Kindred Systems <development@kindred-systems.com> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef KCSOLVE_IKCSOLVER_H
#define KCSOLVE_IKCSOLVER_H
#include <cstddef>
#include <string>
#include <vector>
#include "Types.h"
namespace KCSolve
{
/// Abstract interface for a pluggable assembly constraint solver.
///
/// Solver backends implement this interface. The Assembly module calls
/// through it via the SolverRegistry. A minimal solver only needs to
/// implement solve(), name(), and supported_joints() — all other methods
/// have default implementations that either delegate to solve() or
/// return sensible defaults.
///
/// Method mapping to current AssemblyObject operations:
///
/// solve() <-> AssemblyObject::solve()
/// pre_drag() <-> AssemblyObject::preDrag()
/// drag_step() <-> AssemblyObject::doDragStep()
/// post_drag() <-> AssemblyObject::postDrag()
/// run_kinematic() <-> AssemblyObject::generateSimulation()
/// num_frames() <-> AssemblyObject::numberOfFrames()
/// update_for_frame() <-> AssemblyObject::updateForFrame()
/// diagnose() <-> AssemblyObject::updateSolveStatus()
class IKCSolver
{
public:
virtual ~IKCSolver() = default;
/// Human-readable solver name (e.g. "OndselSolver (Lagrangian)").
virtual std::string name() const = 0;
/// Return the set of BaseJointKind values this solver supports.
/// The registry uses this for capability-based solver selection.
virtual std::vector<BaseJointKind> supported_joints() const = 0;
// ── Static solve ───────────────────────────────────────────────
/// Solve the assembly for static equilibrium.
/// @param ctx Complete description of parts, constraints, and options.
/// @return Result with updated placements and diagnostics.
virtual SolveResult solve(const SolveContext& ctx) = 0;
/// Incrementally update an already-solved assembly after parameter
/// changes (e.g. joint angle/distance changed during joint creation).
/// Default: delegates to solve().
virtual SolveResult update(const SolveContext& ctx)
{
return solve(ctx);
}
// ── Interactive drag ───────────────────────────────────────────
//
// Three-phase protocol for interactive part dragging:
// 1. pre_drag() — solve initial state, prepare for dragging
// 2. drag_step() — called on each mouse move with updated positions
// 3. post_drag() — finalize and release internal solver state
//
// Solvers can maintain internal state across the drag session for
// better interactive performance. This addresses a known weakness
// in the current direct-OndselSolver integration.
/// Prepare for an interactive drag session.
/// @param ctx Assembly state before dragging begins.
/// @param drag_parts IDs of parts being dragged.
/// @return Initial solve result.
virtual SolveResult pre_drag(const SolveContext& ctx,
const std::vector<std::string>& /*drag_parts*/)
{
return solve(ctx);
}
/// Perform one incremental drag step.
/// @param drag_placements Current placements of the dragged parts
/// (part ID + new transform).
/// @return Updated placements for all affected parts.
virtual SolveResult drag_step(
const std::vector<SolveResult::PartResult>& /*drag_placements*/)
{
return SolveResult {SolveStatus::Success};
}
/// End an interactive drag session and finalize state.
virtual void post_drag()
{
}
// ── Kinematic simulation ───────────────────────────────────────
/// Run a kinematic simulation over the time range in ctx.simulation.
/// After this call, num_frames() returns the frame count and
/// update_for_frame(i) retrieves individual frame placements.
/// Default: delegates to solve() (ignoring simulation params).
virtual SolveResult run_kinematic(const SolveContext& /*ctx*/)
{
return SolveResult {SolveStatus::Failed};
}
/// Number of simulation frames available after run_kinematic().
virtual std::size_t num_frames() const
{
return 0;
}
/// Retrieve part placements for simulation frame at index.
/// @pre index < num_frames()
virtual SolveResult update_for_frame(std::size_t /*index*/)
{
return SolveResult {SolveStatus::Failed};
}
// ── Diagnostics ────────────────────────────────────────────────
/// Analyze the assembly for redundant, conflicting, or malformed
/// constraints. May require a prior solve() call for some solvers.
virtual std::vector<ConstraintDiagnostic> diagnose(const SolveContext& /*ctx*/)
{
return {};
}
// ── Capability queries ─────────────────────────────────────────
/// Whether this solver produces deterministic results given
/// identical input.
virtual bool is_deterministic() const
{
return true;
}
/// Whether this solver handles fixed-joint part bundling internally.
/// When false, the caller bundles parts connected by Fixed joints
/// before building the SolveContext. When true, the solver receives
/// unbundled parts and optimizes internally.
virtual bool supports_bundle_fixed() const
{
return false;
}
protected:
IKCSolver() = default;
// Non-copyable, non-movable (polymorphic base class)
IKCSolver(const IKCSolver&) = delete;
IKCSolver& operator=(const IKCSolver&) = delete;
IKCSolver(IKCSolver&&) = delete;
IKCSolver& operator=(IKCSolver&&) = delete;
};
} // namespace KCSolve
#endif // KCSOLVE_IKCSOLVER_H