// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * * * Copyright (c) 2025 Kindred Systems * * * * 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 * * . * * * ***************************************************************************/ #ifndef KCSOLVE_IKCSOLVER_H #define KCSOLVE_IKCSOLVER_H #include #include #include #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 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& /*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& /*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 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