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

View File

@@ -26,6 +26,7 @@
#include <Base/Interpreter.h>
#include <Base/PyObjectBase.h>
#include "Commands.h"
#include "ViewProviderAssembly.h"
#include "ViewProviderAssemblyLink.h"
#include "ViewProviderBom.h"
@@ -34,7 +35,6 @@
#include "ViewProviderViewGroup.h"
#include "ViewProviderSimulationGroup.h"
namespace AssemblyGui
{
extern PyObject* initModule();
@@ -55,6 +55,7 @@ PyMOD_INIT_FUNC(AssemblyGui)
PyObject* mod = AssemblyGui::initModule();
Base::Console().log("Loading AssemblyGui module... done\n");
AssemblyGui::CreateAssemblyCommands();
// NOTE: To finish the initialization of our own type objects we must
// call PyType_Ready, otherwise we run into a segmentation fault, later on.

View File

@@ -41,6 +41,8 @@ SET(AssemblyGui_SRCS_Module
AppAssemblyGui.cpp
AppAssemblyGuiPy.cpp
PreCompiled.h
Commands.cpp
Commands.h
TaskAssemblyMessages.cpp
TaskAssemblyMessages.h
ViewProviderAssembly.cpp

View File

@@ -0,0 +1,247 @@
// SPDX - License - Identifier: LGPL - 2.1 - or -later
/****************************************************************************
* *
* Copyright (c) 2025 Pierre-Louis Boyer *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <vector>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <Gui/Application.h>
#include <Gui/CommandT.h>
#include <Gui/Document.h>
#include <Gui/Selection/Selection.h>
#include <Mod/Assembly/App/AssemblyObject.h>
#include <Mod/Assembly/App/AssemblyUtils.h>
#include "Commands.h"
#include "ViewProviderAssembly.h"
using namespace Assembly;
using namespace AssemblyGui;
// Helper function to get the active AssemblyObject in edit mode
static AssemblyObject* getActiveAssembly()
{
Gui::Document* doc = Gui::Application::Instance->activeDocument();
if (!doc) {
return nullptr;
}
auto* vp = doc->getInEdit();
if (auto* assemblyVP = freecad_cast<ViewProviderAssembly*>(vp)) {
return assemblyVP->getObject<AssemblyObject>();
}
return nullptr;
}
void selectObjects(const std::vector<App::DocumentObject*>& objectsToSelect)
{
if (objectsToSelect.empty()) {
return;
}
Gui::Selection().clearSelection();
for (App::DocumentObject* obj : objectsToSelect) {
Gui::Selection().addSelection(obj->getDocument()->getName(), obj->getNameInDocument());
}
}
void selectObjectsByName(AssemblyObject* assembly, const std::vector<std::string>& names)
{
if (!assembly || names.empty()) {
return;
}
std::vector<App::DocumentObject*> objectsToSelect;
App::Document* doc = assembly->getDocument();
for (const auto& name : names) {
if (auto* obj = doc->getObject(name.c_str())) {
objectsToSelect.push_back(obj);
}
}
selectObjects(objectsToSelect);
}
// ================================================================================
// Select Conflicting Constraints
// ================================================================================
DEF_STD_CMD_A(CmdAssemblySelectConflictingConstraints)
CmdAssemblySelectConflictingConstraints::CmdAssemblySelectConflictingConstraints()
: Command("Assembly_SelectConflictingConstraints")
{
sGroup = QT_TR_NOOP("Assembly");
sMenuText = QT_TR_NOOP("Select conflicting constraints");
sToolTipText = QT_TR_NOOP("Selects conflicting joints in the active assembly");
sWhatsThis = "Assembly_SelectConflictingConstraints";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdAssemblySelectConflictingConstraints::activated(int iMsg)
{
Q_UNUSED(iMsg);
AssemblyObject* assembly = getActiveAssembly();
if (!assembly) {
return;
}
// NOTE: The solver currently reports conflicting constraints as redundant.
// This uses the redundant list until the solver provides a separate conflicting list.
selectObjectsByName(assembly, assembly->getLastRedundant());
}
bool CmdAssemblySelectConflictingConstraints::isActive()
{
return getActiveAssembly() != nullptr;
}
// ================================================================================
// Select Redundant Constraints
// ================================================================================
DEF_STD_CMD_A(CmdAssemblySelectRedundantConstraints)
CmdAssemblySelectRedundantConstraints::CmdAssemblySelectRedundantConstraints()
: Command("Assembly_SelectRedundantConstraints")
{
sGroup = QT_TR_NOOP("Assembly");
sMenuText = QT_TR_NOOP("Select redundant constraints");
sToolTipText = QT_TR_NOOP("Selects redundant joints in the active assembly");
sWhatsThis = "Assembly_SelectRedundantConstraints";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdAssemblySelectRedundantConstraints::activated(int iMsg)
{
Q_UNUSED(iMsg);
AssemblyObject* assembly = getActiveAssembly();
if (!assembly) {
return;
}
selectObjectsByName(assembly, assembly->getLastRedundant());
}
bool CmdAssemblySelectRedundantConstraints::isActive()
{
return getActiveAssembly() != nullptr;
}
// ================================================================================
// Select Malformed Constraints
// ================================================================================
DEF_STD_CMD_A(CmdAssemblySelectMalformedConstraints)
CmdAssemblySelectMalformedConstraints::CmdAssemblySelectMalformedConstraints()
: Command("Assembly_SelectMalformedConstraints")
{
sGroup = QT_TR_NOOP("Assembly");
sMenuText = QT_TR_NOOP("Select malformed constraints");
sToolTipText = QT_TR_NOOP("Selects malformed joints in the active assembly");
sWhatsThis = "Assembly_SelectMalformedConstraints";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdAssemblySelectMalformedConstraints::activated(int iMsg)
{
Q_UNUSED(iMsg);
AssemblyObject* assembly = getActiveAssembly();
if (!assembly) {
return;
}
selectObjectsByName(assembly, assembly->getLastMalformed());
}
bool CmdAssemblySelectMalformedConstraints::isActive()
{
return getActiveAssembly() != nullptr;
}
// ================================================================================
// Select Components with Degrees of Freedom
// ================================================================================
DEF_STD_CMD_A(CmdAssemblySelectComponentsWithDoFs)
CmdAssemblySelectComponentsWithDoFs::CmdAssemblySelectComponentsWithDoFs()
: Command("Assembly_SelectComponentsWithDoFs")
{
sGroup = QT_TR_NOOP("Assembly");
sMenuText = QT_TR_NOOP("Select components with DoFs");
sToolTipText = QT_TR_NOOP("Selects unconstrained components in the active assembly");
sWhatsThis = "Assembly_SelectComponentsWithDoFs";
sStatusTip = sToolTipText;
eType = ForEdit;
}
void CmdAssemblySelectComponentsWithDoFs::activated(int iMsg)
{
Q_UNUSED(iMsg);
AssemblyObject* assembly = getActiveAssembly();
if (!assembly) {
return;
}
std::vector<App::DocumentObject*> objectsToSelect;
std::vector<App::DocumentObject*> allParts = getAssemblyComponents(assembly);
// Iterate through all collected parts and check their connectivity
for (App::DocumentObject* part : allParts) {
if (!assembly->isPartConnected(part)) {
objectsToSelect.push_back(part);
}
}
selectObjects(objectsToSelect);
}
bool CmdAssemblySelectComponentsWithDoFs::isActive()
{
return getActiveAssembly() != nullptr;
}
// ================================================================================
// Command Creation
// ================================================================================
void AssemblyGui::CreateAssemblyCommands()
{
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
rcCmdMgr.addCommand(new CmdAssemblySelectConflictingConstraints());
rcCmdMgr.addCommand(new CmdAssemblySelectRedundantConstraints());
rcCmdMgr.addCommand(new CmdAssemblySelectMalformedConstraints());
rcCmdMgr.addCommand(new CmdAssemblySelectComponentsWithDoFs());
}

View File

@@ -0,0 +1,36 @@
// SPDX - License - Identifier: LGPL - 2.1 - or -later
/****************************************************************************
* *
* Copyright (c) 2025 Pierre-Louis Boyer *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef ASSEMBLYGUI_COMMANDS_H
#define ASSEMBLYGUI_COMMANDS_H
#include <Mod/Assembly/App/AssemblyObject.h>
namespace AssemblyGui
{
void CreateAssemblyCommands();
} // namespace AssemblyGui
#endif // ASSEMBLYGUI_COMMANDS_H

View File

@@ -59,35 +59,40 @@ void TaskAssemblyMessages::updateToolTip(const QString& link)
setLinkTooltip(tr("Click to select these redundant joints."));
}
else if (link == QStringLiteral("#dofs")) {
setLinkTooltip(
tr("The assembly has unconstrained components giving rise to those "
"Degrees Of Freedom. Click to select these unconstrained components.")
);
setLinkTooltip(tr(
"The assembly has unconstrained components giving rise to those "
"Degrees Of Freedom.\nClick to select these unconstrained components.\nNote: Currently "
"this selects only unconnected parts, not constrained parts that still have free "
"DoF."
));
}
else if (link == QStringLiteral("#malformed")) {
setLinkTooltip(tr("Click to select these malformed joints."));
}
}
void TaskAssemblyMessages::onLabelStatusLinkClicked(const QString& /*str*/)
void TaskAssemblyMessages::onLabelStatusLinkClicked(const QString& str)
{
// The commands are not implemented yet since App is not reporting yet the solver's status
/* if (str == QStringLiteral("#conflicting")) {
if (str == QStringLiteral("#conflicting")) {
Gui::Application::Instance->commandManager().runCommandByName(
"Assembly_SelectConflictingConstraints");
"Assembly_SelectConflictingConstraints"
);
}
else if (str == QStringLiteral("#redundant")) {
Gui::Application::Instance->commandManager().runCommandByName(
"Assembly_SelectRedundantConstraints");
"Assembly_SelectRedundantConstraints"
);
}
else if (str == QStringLiteral("#dofs")) {
Gui::Application::Instance->commandManager().runCommandByName(
"Assembly_SelectComponentsWithDoFs");
"Assembly_SelectComponentsWithDoFs"
);
}
else if (str == QStringLiteral("#malformed")) {
Gui::Application::Instance->commandManager().runCommandByName(
"Assembly_SelectMalformedConstraints");
}*/
"Assembly_SelectMalformedConstraints"
);
}
}
#include "moc_TaskAssemblyMessages.cpp"

View File

@@ -26,6 +26,7 @@
#include <QMessageBox>
#include <QTimer>
#include <QMenu>
#include <QString>
#include <vector>
#include <sstream>
#include <iostream>
@@ -295,8 +296,8 @@ bool ViewProviderAssembly::setEdit(int mode)
Gui::TaskView::TaskView* taskView = Gui::Control().taskPanel();
if (taskView) {
// Waiting for the solver to support reporting information.
// taskSolver = new TaskAssemblyMessages(this);
// taskView->addContextualPanel(taskSolver);
taskSolver = new TaskAssemblyMessages(this);
taskView->addContextualPanel(taskSolver);
}
auto* assembly = getObject<AssemblyObject>();
@@ -304,6 +305,8 @@ bool ViewProviderAssembly::setEdit(int mode)
UpdateSolverInformation();
});
assembly->solve();
return true;
}
return ViewProviderPart::setEdit(mode);
@@ -337,7 +340,7 @@ void ViewProviderAssembly::unsetEdit(int mode)
Gui::TaskView::TaskView* taskView = Gui::Control().taskPanel();
if (taskView) {
// Waiting for the solver to support reporting information.
// taskView->removeContextualPanel(taskSolver);
taskView->removeContextualPanel(taskSolver);
}
connectSolverUpdate.disconnect();
@@ -1577,26 +1580,40 @@ Base::Vector3d ViewProviderAssembly::getCenterOfBoundingBox(const std::vector<Mo
return center;
}
inline QString intListHelper(const std::vector<int>& ints)
inline QString objListHelper(const AssemblyObject* assembly, const std::vector<std::string>& names)
{
if (!assembly) {
return QString();
}
App::Document* doc = assembly->getDocument();
std::vector<App::DocumentObject*> joints;
for (const auto& name : names) {
if (auto* obj = doc->getObject(name.c_str())) {
joints.push_back(obj);
}
}
QString results;
if (ints.size() < 8) { // The 8 is a bit heuristic... more than that and we shift formats
for (const auto i : ints) {
if (results.isEmpty()) {
results.append(QStringLiteral("%1").arg(i));
}
else {
results.append(QStringLiteral(", %1").arg(i));
if (joints.size() < 3) { // The 3 is a bit heuristic... more than that and we shift formats
for (const auto joint : joints) {
if (!results.isEmpty()) {
results.append(QStringLiteral(", "));
}
results.append(
QStringLiteral("%1").arg(QString::fromLatin1(joint->Label.getStrValue().c_str()))
);
}
}
else {
const int numToShow = 3;
int more = ints.size() - numToShow;
const int numToShow = 2;
int more = joints.size() - numToShow;
for (int i = 0; i < numToShow; ++i) {
results.append(QStringLiteral("%1, ").arg(ints[i]));
results.append(QStringLiteral("%1, ").arg(
QString::fromLatin1(joints[i]->Label.getStrValue().c_str())
));
}
results.append(ViewProviderAssembly::tr("ViewProviderAssembly", "and %1 more").arg(more));
results.append(ViewProviderAssembly::tr("and %1 more").arg(more));
}
return results;
}
@@ -1615,12 +1632,19 @@ void ViewProviderAssembly::UpdateSolverInformation()
if (assembly->isEmpty()) {
signalSetUp(QStringLiteral("empty"), tr("Empty Assembly"), QString(), QString());
}
else if (dofs < 0 || hasConflicts) { // over-constrained
else if (dofs < 0 || /*hasConflicts*/ hasRedundancies) { // over-constrained
// Currently the solver does not distinguish between conflicts and redundancies.
/*signalSetUp(QStringLiteral("conflicting_constraints"),
tr("Over-constrained:") + QLatin1String(" "),
QStringLiteral("#conflicting"),
QStringLiteral("(%1)").arg(objListHelper(assembly,
assembly->getLastConflicting())));*/
// So for now we report like follows:
signalSetUp(
QStringLiteral("conflicting_constraints"),
tr("Over-constrained:") + QLatin1String(" "),
QStringLiteral("#conflicting"),
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastConflicting()))
QStringLiteral("(%1)").arg(objListHelper(assembly, assembly->getLastRedundant()))
);
}
else if (hasMalformed) { // malformed joints
@@ -1628,25 +1652,25 @@ void ViewProviderAssembly::UpdateSolverInformation()
QStringLiteral("malformed_constraints"),
tr("Malformed joints:") + QLatin1String(" "),
QStringLiteral("#malformed"),
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastMalformedConstraints()))
QStringLiteral("(%1)").arg(objListHelper(assembly, assembly->getLastMalformed()))
);
}
else if (hasRedundancies) {
signalSetUp(
QStringLiteral("redundant_constraints"),
tr("Redundant joints:") + QLatin1String(" "),
QStringLiteral("#redundant"),
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastRedundant()))
);
// Currently the solver does not distinguish between conflicts and redundancies.
/* else if (hasRedundancies) {
signalSetUp(QStringLiteral("redundant_constraints"),
tr("Redundant joints:") + QLatin1String(" "),
QStringLiteral("#redundant"),
QStringLiteral("(%1)").arg(objListHelper(assembly,
assembly->getLastRedundant())));
}
else if (hasPartiallyRedundant) {
signalSetUp(
QStringLiteral("partially_redundant_constraints"),
tr("Partially redundant:") + QLatin1String(" "),
QStringLiteral("#partiallyredundant"),
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastPartiallyRedundant()))
);
}
QStringLiteral("(%1)").arg(objListHelper(assembly,
assembly->getLastPartiallyRedundant())));
}*/
else if (assembly->getLastSolverStatus() != 0) {
signalSetUp(
QStringLiteral("solver_failed"),

View File

@@ -284,6 +284,8 @@ private:
std::set<App::DocumentObject*>& visited
);
TaskAssemblyMessages* taskSolver;
fastsignals::connection connectSolverUpdate;
fastsignals::scoped_connection m_preTransactionConn;
};