All checks were successful
Build and Test / build (pull_request) Successful in 29m19s
Add the kcsolve pybind11 module exposing the KCSolve C++ API to Python: - PyIKCSolver trampoline enabling Python IKCSolver subclasses - Bindings for all 5 enums, 10 structs, IKCSolver, and OndselAdapter - Module functions wrapping SolverRegistry (available, load, joints_for, set_default, get_default, register_solver) - PySolverHolder class forwarding virtual calls with GIL acquisition - register_solver() for runtime Python solver registration IKCSolver constructor moved from protected to public for pybind11 trampoline access (class remains abstract via 3 pure virtuals). Includes 16 Python tests covering module import, type bindings, enum values, registry functions, Python solver subclassing, and full register/load/solve round-trip. Closes #288
190 lines
8.0 KiB
C++
190 lines
8.0 KiB
C++
// 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, {}, -1, {}, 0};
|
|
}
|
|
|
|
/// 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, {}, -1, {}, 0};
|
|
}
|
|
|
|
/// 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, {}, -1, {}, 0};
|
|
}
|
|
|
|
// ── 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;
|
|
}
|
|
|
|
/// Export solver-native debug/diagnostic file (e.g. ASMT for OndselSolver).
|
|
/// Default: no-op. Requires a prior solve() or run_kinematic() call.
|
|
virtual void export_native(const std::string& /*path*/)
|
|
{
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
// Public default constructor for pybind11 trampoline support.
|
|
// The class remains abstract (3 pure virtuals prevent direct instantiation).
|
|
IKCSolver() = default;
|
|
|
|
private:
|
|
// 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
|