feat(assembly): add diagnostic logging to solver and assembly
C++ (AssemblyObject): - getOrCreateSolver: log which solver backend was loaded - solve: log assembly name, grounded/joint counts, context size, result status with DOF and placement count, per-constraint diagnostics on failure - preDrag/doDragStep/postDrag: log drag part count, per-step validation failures, and summary (total steps / rejected count) - buildSolveContext: log grounded/free part counts, constraint count, limits count, and bundle_fixed flag Python (kindred_solver submodule): - solver.py: log solve entry/exit with timing, system build stats, decomposition decisions, Newton/BFGS fallback events, drag lifecycle - decompose.py: log cluster stats and per-cluster convergence - Init.py: FreeCAD log handler routing Python logging to Console
This commit is contained in:
Submodule mods/solver updated: adaa0f9a69...d20b38e760
@@ -176,6 +176,10 @@ KCSolve::IKCSolver* AssemblyObject::getOrCreateSolver()
|
|||||||
std::string solverName = hGrp->GetASCII("Solver", "");
|
std::string solverName = hGrp->GetASCII("Solver", "");
|
||||||
solver_ = KCSolve::SolverRegistry::instance().get(solverName);
|
solver_ = KCSolve::SolverRegistry::instance().get(solverName);
|
||||||
// get("") returns the registry default (first registered solver)
|
// get("") returns the registry default (first registered solver)
|
||||||
|
if (solver_) {
|
||||||
|
FC_LOG("Assembly : loaded solver '" << solver_->name()
|
||||||
|
<< "' (requested='" << solverName << "')");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return solver_.get();
|
return solver_.get();
|
||||||
}
|
}
|
||||||
@@ -212,14 +216,22 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
|
|||||||
|
|
||||||
auto groundedObjs = getGroundedParts();
|
auto groundedObjs = getGroundedParts();
|
||||||
if (groundedObjs.empty()) {
|
if (groundedObjs.empty()) {
|
||||||
|
FC_LOG("Assembly : solve skipped — no grounded parts");
|
||||||
return -6;
|
return -6;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<App::DocumentObject*> joints = getJoints(updateJCS);
|
std::vector<App::DocumentObject*> joints = getJoints(updateJCS);
|
||||||
removeUnconnectedJoints(joints, groundedObjs);
|
removeUnconnectedJoints(joints, groundedObjs);
|
||||||
|
|
||||||
|
FC_LOG("Assembly : solve on '" << getFullLabel()
|
||||||
|
<< "' — " << groundedObjs.size() << " grounded, "
|
||||||
|
<< joints.size() << " joints");
|
||||||
|
|
||||||
KCSolve::SolveContext ctx = buildSolveContext(joints);
|
KCSolve::SolveContext ctx = buildSolveContext(joints);
|
||||||
|
|
||||||
|
FC_LOG("Assembly : solve context — " << ctx.parts.size() << " parts, "
|
||||||
|
<< ctx.constraints.size() << " constraints");
|
||||||
|
|
||||||
// Always save placements to enable orientation flip detection
|
// Always save placements to enable orientation flip detection
|
||||||
savePlacementsForUndo();
|
savePlacementsForUndo();
|
||||||
|
|
||||||
@@ -241,6 +253,13 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lastResult_.status == KCSolve::SolveStatus::Failed) {
|
if (lastResult_.status == KCSolve::SolveStatus::Failed) {
|
||||||
|
FC_LOG("Assembly : solve failed — status="
|
||||||
|
<< static_cast<int>(lastResult_.status)
|
||||||
|
<< ", " << lastResult_.diagnostics.size() << " diagnostics");
|
||||||
|
for (const auto& d : lastResult_.diagnostics) {
|
||||||
|
Base::Console().warning("Assembly : diagnostic [%s]: %s\n",
|
||||||
|
d.constraint_id.c_str(), d.detail.c_str());
|
||||||
|
}
|
||||||
updateSolveStatus();
|
updateSolveStatus();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -248,6 +267,7 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
|
|||||||
// Validate that the solve didn't cause any parts to flip orientation
|
// Validate that the solve didn't cause any parts to flip orientation
|
||||||
if (!validateNewPlacements()) {
|
if (!validateNewPlacements()) {
|
||||||
// Restore previous placements - the solve found an invalid configuration
|
// Restore previous placements - the solve found an invalid configuration
|
||||||
|
FC_LOG("Assembly : solve rejected — placement validation failed, undoing");
|
||||||
undoSolve();
|
undoSolve();
|
||||||
lastSolverStatus = -2;
|
lastSolverStatus = -2;
|
||||||
updateSolveStatus();
|
updateSolveStatus();
|
||||||
@@ -265,6 +285,9 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
|
|||||||
|
|
||||||
updateSolveStatus();
|
updateSolveStatus();
|
||||||
|
|
||||||
|
FC_LOG("Assembly : solve succeeded — dof=" << lastResult_.dof
|
||||||
|
<< ", " << lastResult_.placements.size() << " placements");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +432,8 @@ size_t Assembly::AssemblyObject::numberOfFrames()
|
|||||||
void AssemblyObject::preDrag(std::vector<App::DocumentObject*> dragParts)
|
void AssemblyObject::preDrag(std::vector<App::DocumentObject*> dragParts)
|
||||||
{
|
{
|
||||||
bundleFixed = true;
|
bundleFixed = true;
|
||||||
|
dragStepCount_ = 0;
|
||||||
|
dragStepRejected_ = 0;
|
||||||
|
|
||||||
auto* solver = getOrCreateSolver();
|
auto* solver = getOrCreateSolver();
|
||||||
if (!solver) {
|
if (!solver) {
|
||||||
@@ -421,6 +446,7 @@ void AssemblyObject::preDrag(std::vector<App::DocumentObject*> dragParts)
|
|||||||
|
|
||||||
auto groundedObjs = getGroundedParts();
|
auto groundedObjs = getGroundedParts();
|
||||||
if (groundedObjs.empty()) {
|
if (groundedObjs.empty()) {
|
||||||
|
FC_LOG("Assembly : preDrag skipped — no grounded parts");
|
||||||
bundleFixed = false;
|
bundleFixed = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -474,6 +500,10 @@ void AssemblyObject::preDrag(std::vector<App::DocumentObject*> dragParts)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FC_LOG("Assembly : preDrag — " << dragPartIds.size() << " drag part(s), "
|
||||||
|
<< joints.size() << " joints, " << ctx.parts.size() << " parts, "
|
||||||
|
<< ctx.constraints.size() << " constraints");
|
||||||
|
|
||||||
savePlacementsForUndo();
|
savePlacementsForUndo();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -482,11 +512,13 @@ void AssemblyObject::preDrag(std::vector<App::DocumentObject*> dragParts)
|
|||||||
}
|
}
|
||||||
catch (...) {
|
catch (...) {
|
||||||
// If pre_drag fails, we still need to be in a valid state
|
// If pre_drag fails, we still need to be in a valid state
|
||||||
|
FC_LOG("Assembly : preDrag — solver pre_drag threw exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyObject::doDragStep()
|
void AssemblyObject::doDragStep()
|
||||||
{
|
{
|
||||||
|
dragStepCount_++;
|
||||||
try {
|
try {
|
||||||
std::vector<KCSolve::SolveResult::PartResult> dragPlacements;
|
std::vector<KCSolve::SolveResult::PartResult> dragPlacements;
|
||||||
|
|
||||||
@@ -506,6 +538,10 @@ void AssemblyObject::doDragStep()
|
|||||||
|
|
||||||
lastResult_ = solver_->drag_step(dragPlacements);
|
lastResult_ = solver_->drag_step(dragPlacements);
|
||||||
|
|
||||||
|
if (lastResult_.status == KCSolve::SolveStatus::Failed) {
|
||||||
|
FC_LOG("Assembly : dragStep #" << dragStepCount_ << " — solver failed");
|
||||||
|
}
|
||||||
|
|
||||||
if (validateNewPlacements()) {
|
if (validateNewPlacements()) {
|
||||||
setNewPlacements();
|
setNewPlacements();
|
||||||
|
|
||||||
@@ -525,9 +561,12 @@ void AssemblyObject::doDragStep()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
dragStepRejected_++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (...) {
|
catch (...) {
|
||||||
// We do nothing if a solve step fails.
|
FC_LOG("Assembly : dragStep #" << dragStepCount_ << " — exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,6 +678,8 @@ bool AssemblyObject::validateNewPlacements()
|
|||||||
|
|
||||||
void AssemblyObject::postDrag()
|
void AssemblyObject::postDrag()
|
||||||
{
|
{
|
||||||
|
FC_LOG("Assembly : postDrag — " << dragStepCount_ << " steps, "
|
||||||
|
<< dragStepRejected_ << " rejected");
|
||||||
if (solver_) {
|
if (solver_) {
|
||||||
solver_->post_drag();
|
solver_->post_drag();
|
||||||
}
|
}
|
||||||
@@ -1350,6 +1391,23 @@ KCSolve::SolveContext AssemblyObject::buildSolveContext(
|
|||||||
ctx.simulation = sp;
|
ctx.simulation = sp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log context summary
|
||||||
|
{
|
||||||
|
int nGrounded = 0, nFree = 0, nLimits = 0;
|
||||||
|
for (const auto& p : ctx.parts) {
|
||||||
|
if (p.grounded) nGrounded++;
|
||||||
|
else nFree++;
|
||||||
|
}
|
||||||
|
for (const auto& c : ctx.constraints) {
|
||||||
|
if (!c.limits.empty()) nLimits++;
|
||||||
|
}
|
||||||
|
FC_LOG("Assembly : buildSolveContext — "
|
||||||
|
<< nGrounded << " grounded + " << nFree << " free parts, "
|
||||||
|
<< ctx.constraints.size() << " constraints"
|
||||||
|
<< (nLimits ? (std::string(", ") + std::to_string(nLimits) + " with limits") : "")
|
||||||
|
<< (ctx.bundle_fixed ? ", bundle_fixed=true" : ""));
|
||||||
|
}
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,10 @@ private:
|
|||||||
|
|
||||||
bool bundleFixed;
|
bool bundleFixed;
|
||||||
|
|
||||||
|
// Drag diagnostic counters (reset in preDrag, reported in postDrag)
|
||||||
|
int dragStepCount_ = 0;
|
||||||
|
int dragStepRejected_ = 0;
|
||||||
|
|
||||||
int lastDoF;
|
int lastDoF;
|
||||||
bool lastHasConflict;
|
bool lastHasConflict;
|
||||||
bool lastHasRedundancies;
|
bool lastHasRedundancies;
|
||||||
|
|||||||
Reference in New Issue
Block a user