Compare commits

...

1 Commits

Author SHA1 Message Date
forbes
c225ba7da2 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
2026-02-21 10:08:10 -06:00
3 changed files with 64 additions and 2 deletions

View File

@@ -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;
} }

View File

@@ -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;