Assembly: Solver messages (#24623)

* Assembly: Solver messages

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update ViewProviderAssembly.cpp

* Update src/Mod/Assembly/App/AssemblyUtils.cpp

Co-authored-by: Kacper Donat <kadet1090@gmail.com>

* Update src/Mod/Assembly/App/AssemblyUtils.cpp

Co-authored-by: Kacper Donat <kadet1090@gmail.com>

* Update src/Mod/Assembly/App/AssemblyUtils.cpp

Co-authored-by: Kacper Donat <kadet1090@gmail.com>

* Update src/Mod/Assembly/Gui/Commands.cpp

Co-authored-by: Kacper Donat <kadet1090@gmail.com>

* Update ViewProviderAssembly.cpp

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update src/Mod/Assembly/Gui/TaskAssemblyMessages.cpp

Co-authored-by: Kacper Donat <kadet1090@gmail.com>

* Update AssemblyObject.h

* Update AssemblyObject.cpp

* Update Commands.cpp

* Update ViewProviderAssembly.cpp

* Update AssemblyObject.cpp

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Thank you

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Kacper Donat <kadet1090@gmail.com>
This commit is contained in:
PaddleStroke
2026-01-22 15:21:13 +01:00
committed by GitHub
parent 54d235f8a5
commit bb6832897a
13 changed files with 552 additions and 101 deletions

View File

@@ -137,10 +137,16 @@ App::DocumentObjectExecReturn* AssemblyObject::execute()
return ret;
}
void AssemblyObject::onChanged(const App::Property* prop)
{
if (prop == &Group) {
updateSolveStatus();
}
App::Part::onChanged(prop);
}
int AssemblyObject::solve(bool enableRedo, bool updateJCS)
{
lastDoF = numberOfComponents() * 6;
ensureIdentityPlacements();
mbdAssembly = makeMbdAssembly();
@@ -165,13 +171,18 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
try {
mbdAssembly->runPreDrag();
lastSolverStatus = 0;
}
catch (const std::exception& e) {
FC_ERR("Solve failed: " << e.what());
lastSolverStatus = -1;
updateSolveStatus();
return -1;
}
catch (...) {
FC_ERR("Solve failed: unhandled exception");
lastSolverStatus = -1;
updateSolveStatus();
return -1;
}
@@ -179,11 +190,87 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
redrawJointPlacements(joints);
signalSolverUpdate();
updateSolveStatus();
return 0;
}
void AssemblyObject::updateSolveStatus()
{
lastRedundantJoints.clear();
lastHasRedundancies = false;
//+1 because there's a grounded joint to origin
lastDoF = (1 + numberOfComponents()) * 6;
if (!mbdAssembly || !mbdAssembly->mbdSystem) {
solve();
}
if (!mbdAssembly || !mbdAssembly->mbdSystem) {
return;
}
// Helper lambda to clean up the joint name from the solver
auto cleanJointName = [](const std::string& rawName) -> std::string {
// rawName is like : /OndselAssembly/ground_moves#Joint001
size_t hashPos = rawName.find_last_of('#');
if (hashPos != std::string::npos) {
// Return the substring after the '#'
return rawName.substr(hashPos + 1);
}
return rawName;
};
// Iterate through all joints and motions in the MBD system
mbdAssembly->mbdSystem->jointsMotionsDo([&](std::shared_ptr<MbD::Joint> jm) {
if (!jm) {
return;
}
// Base::Console().warning("jm->name %s\n", jm->name);
bool isJointRedundant = false;
jm->constraintsDo([&](std::shared_ptr<MbD::Constraint> con) {
if (!con) {
return;
}
std::string spec = con->constraintSpec();
// A constraint is redundant if its spec starts with "Redundant"
if (spec.rfind("Redundant", 0) == 0) {
isJointRedundant = true;
}
// Base::Console().warning(" - %s\n", spec);
--lastDoF;
});
const std::string fullName = cleanJointName(jm->name);
App::DocumentObject* docObj = getDocument()->getObject(fullName.c_str());
// We only care about objects that are actual joints in the FreeCAD document.
// This effectively filters out the grounding joints, which are named after parts.
if (!docObj || !docObj->getPropertyByName("Reference1")) {
return;
}
if (isJointRedundant) {
// Check if this joint is already in the list to avoid duplicates
std::string objName = docObj->getNameInDocument();
if (std::find(lastRedundantJoints.begin(), lastRedundantJoints.end(), objName)
== lastRedundantJoints.end()) {
lastRedundantJoints.push_back(objName);
}
}
});
// Update the summary boolean flag
if (!lastRedundantJoints.empty()) {
lastHasRedundancies = true;
}
signalSolverUpdate();
}
int AssemblyObject::generateSimulation(App::DocumentObject* sim)
{
mbdAssembly = makeMbdAssembly();
@@ -2018,46 +2105,7 @@ void AssemblyObject::ensureIdentityPlacements()
int AssemblyObject::numberOfComponents() const
{
int count = 0;
const std::vector<App::DocumentObject*> objects = Group.getValues();
for (auto* obj : objects) {
if (!obj) {
continue;
}
if (obj->isLinkGroup()) {
auto* link = static_cast<const App::Link*>(obj);
count += link->ElementCount.getValue();
continue;
}
if (obj->isDerivedFrom(Assembly::AssemblyLink::getClassTypeId())) {
auto* subAssembly = static_cast<const AssemblyLink*>(obj);
count += subAssembly->numberOfComponents();
continue;
}
// Resolve standard App::Links to their target object
if (obj->isDerivedFrom(App::Link::getClassTypeId())) {
obj = static_cast<const App::Link*>(obj)->getLinkedObject();
if (!obj) {
continue;
}
}
if (!obj->isDerivedFrom(App::GeoFeature::getClassTypeId())) {
continue;
}
if (obj->isDerivedFrom(App::LocalCoordinateSystem::getClassTypeId())) {
continue;
}
count++;
}
return count;
return getAssemblyComponents(this).size();
}
bool AssemblyObject::isEmpty() const

View File

@@ -88,7 +88,7 @@ public:
}
App::DocumentObjectExecReturn* execute() override;
void onChanged(const App::Property* prop) override;
/* Solve the assembly. It will update first the joints, solve, update placements of the parts
and redraw the joints Args : enableRedo : This store initial positions to enable undo while
being in an active transaction (joint creation).*/
@@ -216,6 +216,7 @@ public:
bool isEmpty() const;
int numberOfComponents() const;
void updateSolveStatus();
inline int getLastDoF() const
{
return lastDoF;
@@ -240,21 +241,21 @@ public:
{
return lastSolverStatus;
}
inline const std::vector<int>& getLastConflicting() const
inline const std::vector<std::string>& getLastConflicting() const
{
return lastConflicting;
return lastConflictingJoints;
}
inline const std::vector<int>& getLastRedundant() const
inline const std::vector<std::string>& getLastRedundant() const
{
return lastRedundant;
return lastRedundantJoints;
}
inline const std::vector<int>& getLastPartiallyRedundant() const
inline const std::vector<std::string>& getLastPartiallyRedundant() const
{
return lastPartiallyRedundant;
return lastPartialRedundantJoints;
}
inline const std::vector<int>& getLastMalformedConstraints() const
inline const std::vector<std::string>& getLastMalformed() const
{
return lastMalformedConstraints;
return lastMalformedJoints;
}
fastsignals::signal<void()> signalSolverUpdate;
@@ -277,10 +278,10 @@ private:
bool lastHasMalformedConstraints;
int lastSolverStatus;
std::vector<int> lastConflicting;
std::vector<int> lastRedundant;
std::vector<int> lastPartiallyRedundant;
std::vector<int> lastMalformedConstraints;
std::vector<std::string> lastRedundantJoints;
std::vector<std::string> lastConflictingJoints;
std::vector<std::string> lastPartialRedundantJoints;
std::vector<std::string> lastMalformedJoints;
};
} // namespace Assembly

View File

@@ -79,10 +79,21 @@ class AssemblyObject(Part):
...
@constmethod
def undoSolve(self) -> None:
"""
Undo the last solve of the assembly and return part placements to their initial position.
"""
def updateSolveStatus(self) -> Any:
"""updateSolveStatus()
Args: None
Returns: None"""
...
@constmethod
def undoSolve(self) -> Any:
"""Undo the last solve of the assembly and return part placements to their initial position.
undoSolve()
Returns: None"""
...
@constmethod

View File

@@ -112,6 +112,16 @@ PyObject* AssemblyObjectPy::numberOfFrames(PyObject* args) const
return Py_BuildValue("k", ret);
}
PyObject* AssemblyObjectPy::updateSolveStatus(PyObject* args) const
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
this->getAssemblyObjectPtr()->updateSolveStatus();
Py_Return;
}
PyObject* AssemblyObjectPy::undoSolve(PyObject* args) const
{
if (!PyArg_ParseTuple(args, "")) {

View File

@@ -756,5 +756,67 @@ void syncPlacements(App::DocumentObject* src, App::DocumentObject* to)
}
}
}
namespace
{
// Helper function to perform the recursive traversal. Kept in an anonymous
// namespace as it's an implementation detail of getAssemblyComponents.
void collectComponentsRecursively(
const std::vector<App::DocumentObject*>& objects,
std::vector<App::DocumentObject*>& results
)
{
for (auto* obj : objects) {
if (!obj) {
continue;
}
if (auto* asmLink = freecad_cast<Assembly::AssemblyLink*>(obj)) {
// If the sub-assembly is rigid, treat it as a single movable part.
// If it's flexible, we need to check its individual components.
if (asmLink->isRigid()) {
results.push_back(asmLink);
}
else {
collectComponentsRecursively(asmLink->Group.getValues(), results);
}
continue;
}
else if (obj->isLinkGroup()) {
auto* linkGroup = static_cast<App::Link*>(obj);
for (auto* elt : linkGroup->ElementList.getValues()) {
results.push_back(elt);
}
continue;
}
else if (auto* group = freecad_cast<App::DocumentObjectGroup*>(obj)) {
collectComponentsRecursively(group->Group.getValues(), results);
continue;
}
else if (auto* link = freecad_cast<App::Link*>(obj)) {
obj = link->getLinkedObject();
if (obj->isDerivedFrom<App::GeoFeature>()
&& !obj->isDerivedFrom<App::LocalCoordinateSystem>()) {
results.push_back(link);
}
}
else if (obj->isDerivedFrom<App::GeoFeature>()
&& !obj->isDerivedFrom<App::LocalCoordinateSystem>()) {
results.push_back(obj);
}
}
}
} // namespace
std::vector<App::DocumentObject*> getAssemblyComponents(const AssemblyObject* assembly)
{
if (!assembly) {
return {};
}
std::vector<App::DocumentObject*> components;
collectComponentsRecursively(assembly->Group.getValues(), components);
return components;
}
} // namespace Assembly

View File

@@ -150,6 +150,8 @@ AssemblyExport double getEdgeRadius(const App::DocumentObject* obj, const std::s
AssemblyExport DistanceType getDistanceType(App::DocumentObject* joint);
AssemblyExport JointGroup* getJointGroup(const App::Part* part);
AssemblyExport std::vector<App::DocumentObject*> getAssemblyComponents(const AssemblyObject* assembly);
// getters to get from properties
AssemblyExport void setJointActivated(const App::DocumentObject* joint, bool val);
AssemblyExport bool getJointActivated(const App::DocumentObject* joint);