cherry-pick #1: initial Kindred branding + assembly joint fix

Cherry-picked 316d4f4b52 with conflict resolution:
- CMakeLists.txt: merged Kindred version vars with upstream 1.2.0-dev base
- src/Main/*.cpp: applied Kindred branding (banner, copyright, license)
- Resolved add/add conflicts for files already copied in Phase 1
- Includes assembly joint flip overconstrain fix
This commit is contained in:
forbes
2026-02-13 14:05:31 -06:00
parent 87a0af0b0f
commit 9be9f9420a
36 changed files with 3384 additions and 68 deletions

View File

@@ -165,9 +165,8 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
jointParts(joints);
if (enableRedo) {
savePlacementsForUndo();
}
// Always save placements to enable orientation flip detection
savePlacementsForUndo();
try {
mbdAssembly->runPreDrag();
@@ -186,8 +185,22 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
return -1;
}
// Validate that the solve didn't cause any parts to flip orientation
if (!validateNewPlacements()) {
// Restore previous placements - the solve found an invalid configuration
undoSolve();
lastSolverStatus = -2;
updateSolveStatus();
return -2;
}
setNewPlacements();
// Clear undo history if the caller didn't want redo capability
if (!enableRedo) {
clearUndo();
}
redrawJointPlacements(joints);
updateSolveStatus();
@@ -480,8 +493,56 @@ bool AssemblyObject::validateNewPlacements()
}
}
// TODO: We could do further tests
// For example check if the joints connectors are correctly aligned.
// Check if any part has flipped orientation (rotation > 90 degrees from original)
// This prevents joints from "breaking" when the solver finds an alternate configuration
for (const auto& savedPair : previousPositions) {
App::DocumentObject* obj = savedPair.first;
if (!obj) {
continue;
}
auto it = objectPartMap.find(obj);
if (it == objectPartMap.end()) {
continue;
}
std::shared_ptr<MbD::ASMTPart> mbdPart = it->second.part;
if (!mbdPart) {
continue;
}
Base::Placement newPlacement = getMbdPlacement(mbdPart);
if (!it->second.offsetPlc.isIdentity()) {
newPlacement = newPlacement * it->second.offsetPlc;
}
const Base::Placement& oldPlc = savedPair.second;
// Calculate the rotation difference between old and new orientations
Base::Rotation oldRot = oldPlc.getRotation();
Base::Rotation newRot = newPlacement.getRotation();
// Get the relative rotation: how much did the part rotate?
Base::Rotation relativeRot = newRot * oldRot.inverse();
// Get the angle of this rotation
Base::Vector3d axis;
double angle;
relativeRot.getRawValue(axis, angle);
// If the part rotated more than 90 degrees, consider it a flip
// Use 91 degrees to allow for small numerical errors
constexpr double maxAngle = 91.0 * M_PI / 180.0;
if (std::abs(angle) > maxAngle) {
Base::Console().warning(
"Assembly : Ignoring bad solve, part (%s) flipped orientation (%.1f degrees).\n",
obj->getFullLabel(),
std::abs(angle) * 180.0 / M_PI
);
return false;
}
}
return true;
}