Merge pull request 'feat(assembly): add diagnostic logging to solver and assembly' (#313) from feat/solver-diagnostic-logging into main
Some checks failed
Build and Test / build (push) Has been cancelled

Reviewed-on: #313
This commit was merged in pull request #313.
This commit is contained in:
2026-02-21 16:11:34 +00: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", "");
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<App::DocumentObject*> 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<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();
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;
}
@@ -415,6 +438,8 @@ size_t Assembly::AssemblyObject::numberOfFrames()
void AssemblyObject::preDrag(std::vector<App::DocumentObject*> dragParts)
{
bundleFixed = true;
dragStepCount_ = 0;
dragStepRejected_ = 0;
auto* solver = getOrCreateSolver();
if (!solver) {
@@ -427,6 +452,7 @@ void AssemblyObject::preDrag(std::vector<App::DocumentObject*> dragParts)
auto groundedObjs = getGroundedParts();
if (groundedObjs.empty()) {
FC_LOG("Assembly : preDrag skipped — no grounded parts");
bundleFixed = false;
return;
}
@@ -480,6 +506,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();
try {
@@ -488,11 +518,13 @@ void AssemblyObject::preDrag(std::vector<App::DocumentObject*> 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<KCSolve::SolveResult::PartResult> dragPlacements;
@@ -512,6 +544,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();
@@ -531,9 +567,12 @@ void AssemblyObject::doDragStep()
}
}
}
else {
dragStepRejected_++;
}
}
catch (...) {
// We do nothing if a solve step fails.
FC_LOG("Assembly : dragStep #" << dragStepCount_ << " — exception");
}
}
@@ -645,6 +684,8 @@ bool AssemblyObject::validateNewPlacements()
void AssemblyObject::postDrag()
{
FC_LOG("Assembly : postDrag — " << dragStepCount_ << " steps, "
<< dragStepRejected_ << " rejected");
if (solver_) {
solver_->post_drag();
}
@@ -1356,6 +1397,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;
}

View File

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