From c682c5d153b0d2ce84ec76251aeb29c8bd3d3d16 Mon Sep 17 00:00:00 2001 From: forbes Date: Sat, 21 Feb 2026 10:08:10 -0600 Subject: [PATCH] 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 --- mods/solver | 2 +- src/Mod/Assembly/App/AssemblyObject.cpp | 60 ++++++++++++++++++++++++- src/Mod/Assembly/App/AssemblyObject.h | 4 ++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/mods/solver b/mods/solver index adaa0f9a69..d20b38e760 160000 --- a/mods/solver +++ b/mods/solver @@ -1 +1 @@ -Subproject commit adaa0f9a690a311db553158eed82d88f1f911913 +Subproject commit d20b38e76020cb86620c15171a0552dc8495787a diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index 6902acd44b..1614c46299 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -176,6 +176,10 @@ KCSolve::IKCSolver* AssemblyObject::getOrCreateSolver() std::string solverName = hGrp->GetASCII("Solver", ""); solver_ = KCSolve::SolverRegistry::instance().get(solverName); // get("") returns the registry default (first registered solver) + if (solver_) { + FC_LOG("Assembly : loaded solver '" << solver_->name() + << "' (requested='" << solverName << "')"); + } } return solver_.get(); } @@ -212,14 +216,22 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS) auto groundedObjs = getGroundedParts(); if (groundedObjs.empty()) { + FC_LOG("Assembly : solve skipped — no grounded parts"); return -6; } std::vector joints = getJoints(updateJCS); removeUnconnectedJoints(joints, groundedObjs); + FC_LOG("Assembly : solve on '" << getFullLabel() + << "' — " << groundedObjs.size() << " grounded, " + << joints.size() << " 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 savePlacementsForUndo(); @@ -241,6 +253,13 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS) } if (lastResult_.status == KCSolve::SolveStatus::Failed) { + FC_LOG("Assembly : solve failed — status=" + << static_cast(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(); 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 if (!validateNewPlacements()) { // Restore previous placements - the solve found an invalid configuration + FC_LOG("Assembly : solve rejected — placement validation failed, undoing"); undoSolve(); lastSolverStatus = -2; updateSolveStatus(); @@ -265,6 +285,9 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS) updateSolveStatus(); + FC_LOG("Assembly : solve succeeded — dof=" << lastResult_.dof + << ", " << lastResult_.placements.size() << " placements"); + return 0; } @@ -409,6 +432,8 @@ size_t Assembly::AssemblyObject::numberOfFrames() void AssemblyObject::preDrag(std::vector dragParts) { bundleFixed = true; + dragStepCount_ = 0; + dragStepRejected_ = 0; auto* solver = getOrCreateSolver(); if (!solver) { @@ -421,6 +446,7 @@ void AssemblyObject::preDrag(std::vector dragParts) auto groundedObjs = getGroundedParts(); if (groundedObjs.empty()) { + FC_LOG("Assembly : preDrag skipped — no grounded parts"); bundleFixed = false; return; } @@ -474,6 +500,10 @@ void AssemblyObject::preDrag(std::vector dragParts) } } + FC_LOG("Assembly : preDrag — " << dragPartIds.size() << " drag part(s), " + << joints.size() << " joints, " << ctx.parts.size() << " parts, " + << ctx.constraints.size() << " constraints"); + savePlacementsForUndo(); try { @@ -482,11 +512,13 @@ void AssemblyObject::preDrag(std::vector dragParts) } catch (...) { // 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() { + dragStepCount_++; try { std::vector dragPlacements; @@ -506,6 +538,10 @@ void AssemblyObject::doDragStep() lastResult_ = solver_->drag_step(dragPlacements); + if (lastResult_.status == KCSolve::SolveStatus::Failed) { + FC_LOG("Assembly : dragStep #" << dragStepCount_ << " — solver failed"); + } + if (validateNewPlacements()) { setNewPlacements(); @@ -517,9 +553,12 @@ void AssemblyObject::doDragStep() } } } + else { + dragStepRejected_++; + } } catch (...) { - // We do nothing if a solve step fails. + FC_LOG("Assembly : dragStep #" << dragStepCount_ << " — exception"); } } @@ -631,6 +670,8 @@ bool AssemblyObject::validateNewPlacements() void AssemblyObject::postDrag() { + FC_LOG("Assembly : postDrag — " << dragStepCount_ << " steps, " + << dragStepRejected_ << " rejected"); if (solver_) { solver_->post_drag(); } @@ -1342,6 +1383,23 @@ KCSolve::SolveContext AssemblyObject::buildSolveContext( 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; } diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index be241a1a00..ad37602e87 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -280,6 +280,10 @@ private: bool bundleFixed; + // Drag diagnostic counters (reset in preDrag, reported in postDrag) + int dragStepCount_ = 0; + int dragStepRejected_ = 0; + int lastDoF; bool lastHasConflict; bool lastHasRedundancies; -- 2.49.1