feat(solver): refactor AssemblyObject to use IKCSolver interface (#295)

Rewire AssemblyObject to call through KCSolve::IKCSolver instead of
directly manipulating OndselSolver ASMT types.

Key changes:
- Remove all 30+ #include <OndselSolver/*> from AssemblyObject.cpp
- Remove MbDPartData, objectPartMap, mbdAssembly members
- Add solver_ (IKCSolver), lastResult_ (SolveResult), partIdToObjs_ maps
- New buildSolveContext() builds SolveContext from FreeCAD document objects
  with JointType/DistanceType -> BaseJointKind decomposition
- New computeMarkerTransform() replaces handleOneSideOfJoint()
- New computeRackPinionMarkers() replaces getRackPinionMarkers()
- Rewrite solve/preDrag/doDragStep/postDrag/generateSimulation to call
  through IKCSolver interface
- Rewrite setNewPlacements/validateNewPlacements to use SolveResult
- Rewrite updateSolveStatus to use ConstraintDiagnostic
- Add export_native() to IKCSolver for ASMT export support
- Register OndselAdapter at Assembly module init in AppAssembly.cpp
- Remove OndselSolver from Assembly_LIBS (linked transitively via KCSolve)

Assembly module now has zero OndselSolver includes. All solver coupling
is confined to KCSolve/OndselAdapter.cpp.
This commit is contained in:
forbes
2026-02-19 16:43:52 -06:00
parent 32dbe20ce0
commit 5c33aacecb
7 changed files with 1092 additions and 1115 deletions

View File

@@ -26,6 +26,8 @@
#include <Base/Interpreter.h>
#include <Base/PyObjectBase.h>
#include <Mod/Assembly/Solver/OndselAdapter.h>
#include "AssemblyObject.h"
#include "AssemblyLink.h"
#include "BomObject.h"
@@ -54,6 +56,10 @@ PyMOD_INIT_FUNC(AssemblyApp)
}
PyObject* mod = Assembly::initModule();
// Register the built-in OndselSolver adapter with the solver registry.
KCSolve::OndselAdapter::register_solver();
Base::Console().log("Loading Assembly module... done\n");

File diff suppressed because it is too large Load Diff

View File

@@ -25,24 +25,21 @@
#ifndef ASSEMBLY_AssemblyObject_H
#define ASSEMBLY_AssemblyObject_H
#include <memory>
#include <boost/signals2.hpp>
#include <Mod/Assembly/AssemblyGlobal.h>
#include <Mod/Assembly/Solver/Types.h>
#include <App/FeaturePython.h>
#include <App/Part.h>
#include <App/PropertyLinks.h>
#include <OndselSolver/enum.h>
namespace MbD
namespace KCSolve
{
class ASMTPart;
class ASMTAssembly;
class ASMTJoint;
class ASMTMarker;
class ASMTPart;
} // namespace MbD
class IKCSolver;
} // namespace KCSolve
namespace App
{
@@ -105,7 +102,6 @@ public:
void exportAsASMT(std::string fileName);
Base::Placement getMbdPlacement(std::shared_ptr<MbD::ASMTPart> mbdPart);
bool validateNewPlacements();
void setNewPlacements();
static void redrawJointPlacements(std::vector<App::DocumentObject*> joints);
@@ -114,42 +110,8 @@ public:
// This makes sure that LinkGroups or sub-assemblies have identity placements.
void ensureIdentityPlacements();
// Ondsel Solver interface
std::shared_ptr<MbD::ASMTAssembly> makeMbdAssembly();
void create_mbdSimulationParameters(App::DocumentObject* sim);
std::shared_ptr<MbD::ASMTPart> makeMbdPart(
std::string& name,
Base::Placement plc = Base::Placement(),
double mass = 1.0
);
std::shared_ptr<MbD::ASMTPart> getMbDPart(App::DocumentObject* obj);
// To help the solver, during dragging, we are bundling parts connected by a fixed joint.
// So several assembly components are bundled in a single ASMTPart.
// So we need to store the plc of each bundled object relative to the bundle origin (first obj
// of objectPartMap).
struct MbDPartData
{
std::shared_ptr<MbD::ASMTPart> part;
Base::Placement offsetPlc; // This is the offset within the bundled parts
};
MbDPartData getMbDData(App::DocumentObject* part);
std::shared_ptr<MbD::ASMTMarker> makeMbdMarker(std::string& name, Base::Placement& plc);
std::vector<std::shared_ptr<MbD::ASMTJoint>> makeMbdJoint(App::DocumentObject* joint);
std::shared_ptr<MbD::ASMTJoint> makeMbdJointOfType(App::DocumentObject* joint, JointType jointType);
std::shared_ptr<MbD::ASMTJoint> makeMbdJointDistance(App::DocumentObject* joint);
std::string handleOneSideOfJoint(
App::DocumentObject* joint,
const char* propRefName,
const char* propPlcName
);
void getRackPinionMarkers(
App::DocumentObject* joint,
std::string& markerNameI,
std::string& markerNameJ
);
int slidingPartIndex(App::DocumentObject* joint);
void jointParts(std::vector<App::DocumentObject*> joints);
JointGroup* getJointGroup() const;
ViewGroup* getExplodedViewGroup() const;
template<typename T>
@@ -169,8 +131,6 @@ public:
const std::vector<App::DocumentObject*>& excludeJoints = {}
);
std::unordered_set<App::DocumentObject*> getGroundedParts();
std::unordered_set<App::DocumentObject*> fixGroundedParts();
void fixGroundedPart(App::DocumentObject* obj, Base::Placement& plc, std::string& jointName);
bool isJointConnectingPartToGround(App::DocumentObject* joint, const char* partPropName);
bool isJointTypeConnecting(App::DocumentObject* joint);
@@ -210,7 +170,7 @@ public:
std::vector<App::DocumentObject*> getMotionsFromSimulation(App::DocumentObject* sim);
bool isMbDJointValid(App::DocumentObject* joint);
bool isJointValid(App::DocumentObject* joint);
bool isEmpty() const;
int numberOfComponents() const;
@@ -259,12 +219,56 @@ public:
fastsignals::signal<void()> signalSolverUpdate;
private:
std::shared_ptr<MbD::ASMTAssembly> mbdAssembly;
// ── Solver integration ─────────────────────────────────────────
KCSolve::IKCSolver* getOrCreateSolver();
KCSolve::SolveContext buildSolveContext(
const std::vector<App::DocumentObject*>& joints,
bool forSimulation = false,
App::DocumentObject* sim = nullptr
);
KCSolve::Transform computeMarkerTransform(
App::DocumentObject* joint,
const char* propRefName,
const char* propPlcName
);
struct RackPinionResult
{
std::string partIdI;
KCSolve::Transform markerI;
std::string partIdJ;
KCSolve::Transform markerJ;
};
RackPinionResult computeRackPinionMarkers(App::DocumentObject* joint);
// ── Part ↔ solver ID mapping ───────────────────────────────────
// Maps a solver part ID to the FreeCAD objects it represents.
// Multiple objects map to one ID when parts are bundled by Fixed joints.
struct PartMapping
{
App::DocumentObject* obj;
Base::Placement offset; // identity for primary, non-identity for bundled
};
std::unordered_map<std::string, std::vector<PartMapping>> partIdToObjs_;
std::unordered_map<App::DocumentObject*, std::string> objToPartId_;
// Register a part (and recursively its fixed-joint bundle when bundleFixed is set).
// Returns the solver part ID.
std::string registerPart(App::DocumentObject* obj);
// ── Solver state ───────────────────────────────────────────────
std::unique_ptr<KCSolve::IKCSolver> solver_;
KCSolve::SolveResult lastResult_;
// ── Existing state (unchanged) ─────────────────────────────────
std::unordered_map<App::DocumentObject*, MbDPartData> objectPartMap;
std::vector<std::pair<App::DocumentObject*, double>> objMasses;
std::vector<App::DocumentObject*> draggedParts;
std::vector<App::DocumentObject*> motions;
std::vector<std::pair<App::DocumentObject*, Base::Placement>> previousPositions;

View File

@@ -5,7 +5,6 @@ set(Assembly_LIBS
PartDesign
Spreadsheet
FreeCADApp
OndselSolver
KCSolve
)

View File

@@ -157,6 +157,12 @@ public:
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

View File

@@ -784,4 +784,13 @@ std::vector<ConstraintDiagnostic> OndselAdapter::diagnose(const SolveContext& ct
return extract_diagnostics();
}
// ── Native export ──────────────────────────────────────────────────
void OndselAdapter::export_native(const std::string& path)
{
if (assembly_) {
assembly_->outputFile(path);
}
}
} // namespace KCSolve

View File

@@ -80,6 +80,7 @@ public:
bool is_deterministic() const override;
bool supports_bundle_fixed() const override;
void export_native(const std::string& path) override;
/// Register OndselAdapter as "ondsel" in the SolverRegistry.
/// Call once at module init time.