diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index cc8e6bdf1b..6121457e31 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -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 jm) { + if (!jm) { + return; + } + // Base::Console().warning("jm->name %s\n", jm->name); + bool isJointRedundant = false; + + jm->constraintsDo([&](std::shared_ptr 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 objects = Group.getValues(); - - for (auto* obj : objects) { - if (!obj) { - continue; - } - - if (obj->isLinkGroup()) { - auto* link = static_cast(obj); - count += link->ElementCount.getValue(); - continue; - } - - if (obj->isDerivedFrom(Assembly::AssemblyLink::getClassTypeId())) { - auto* subAssembly = static_cast(obj); - count += subAssembly->numberOfComponents(); - continue; - } - - // Resolve standard App::Links to their target object - if (obj->isDerivedFrom(App::Link::getClassTypeId())) { - obj = static_cast(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 diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index 29ab5d4c58..f1295e4a89 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -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& getLastConflicting() const + inline const std::vector& getLastConflicting() const { - return lastConflicting; + return lastConflictingJoints; } - inline const std::vector& getLastRedundant() const + inline const std::vector& getLastRedundant() const { - return lastRedundant; + return lastRedundantJoints; } - inline const std::vector& getLastPartiallyRedundant() const + inline const std::vector& getLastPartiallyRedundant() const { - return lastPartiallyRedundant; + return lastPartialRedundantJoints; } - inline const std::vector& getLastMalformedConstraints() const + inline const std::vector& getLastMalformed() const { - return lastMalformedConstraints; + return lastMalformedJoints; } fastsignals::signal signalSolverUpdate; @@ -277,10 +278,10 @@ private: bool lastHasMalformedConstraints; int lastSolverStatus; - std::vector lastConflicting; - std::vector lastRedundant; - std::vector lastPartiallyRedundant; - std::vector lastMalformedConstraints; + std::vector lastRedundantJoints; + std::vector lastConflictingJoints; + std::vector lastPartialRedundantJoints; + std::vector lastMalformedJoints; }; } // namespace Assembly diff --git a/src/Mod/Assembly/App/AssemblyObject.pyi b/src/Mod/Assembly/App/AssemblyObject.pyi index 22c4e888a7..95db04dc83 100644 --- a/src/Mod/Assembly/App/AssemblyObject.pyi +++ b/src/Mod/Assembly/App/AssemblyObject.pyi @@ -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 diff --git a/src/Mod/Assembly/App/AssemblyObjectPyImp.cpp b/src/Mod/Assembly/App/AssemblyObjectPyImp.cpp index ca284bdb6f..fa5a28dd3b 100644 --- a/src/Mod/Assembly/App/AssemblyObjectPyImp.cpp +++ b/src/Mod/Assembly/App/AssemblyObjectPyImp.cpp @@ -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, "")) { diff --git a/src/Mod/Assembly/App/AssemblyUtils.cpp b/src/Mod/Assembly/App/AssemblyUtils.cpp index 2848dcc7b6..4fd7356b0e 100644 --- a/src/Mod/Assembly/App/AssemblyUtils.cpp +++ b/src/Mod/Assembly/App/AssemblyUtils.cpp @@ -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& objects, + std::vector& results +) +{ + for (auto* obj : objects) { + if (!obj) { + continue; + } + + if (auto* asmLink = freecad_cast(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(obj); + for (auto* elt : linkGroup->ElementList.getValues()) { + results.push_back(elt); + } + continue; + } + else if (auto* group = freecad_cast(obj)) { + collectComponentsRecursively(group->Group.getValues(), results); + continue; + } + else if (auto* link = freecad_cast(obj)) { + obj = link->getLinkedObject(); + if (obj->isDerivedFrom() + && !obj->isDerivedFrom()) { + results.push_back(link); + } + } + + else if (obj->isDerivedFrom() + && !obj->isDerivedFrom()) { + results.push_back(obj); + } + } +} +} // namespace + +std::vector getAssemblyComponents(const AssemblyObject* assembly) +{ + if (!assembly) { + return {}; + } + + std::vector components; + collectComponentsRecursively(assembly->Group.getValues(), components); + return components; +} } // namespace Assembly diff --git a/src/Mod/Assembly/App/AssemblyUtils.h b/src/Mod/Assembly/App/AssemblyUtils.h index 2e3b2b42ab..52799830cd 100644 --- a/src/Mod/Assembly/App/AssemblyUtils.h +++ b/src/Mod/Assembly/App/AssemblyUtils.h @@ -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 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); diff --git a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp index 4966c27bf6..010095fe39 100644 --- a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp +++ b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp @@ -26,6 +26,7 @@ #include #include +#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. diff --git a/src/Mod/Assembly/Gui/CMakeLists.txt b/src/Mod/Assembly/Gui/CMakeLists.txt index 1fd70c01b9..32101a7855 100644 --- a/src/Mod/Assembly/Gui/CMakeLists.txt +++ b/src/Mod/Assembly/Gui/CMakeLists.txt @@ -41,6 +41,8 @@ SET(AssemblyGui_SRCS_Module AppAssemblyGui.cpp AppAssemblyGuiPy.cpp PreCompiled.h + Commands.cpp + Commands.h TaskAssemblyMessages.cpp TaskAssemblyMessages.h ViewProviderAssembly.cpp diff --git a/src/Mod/Assembly/Gui/Commands.cpp b/src/Mod/Assembly/Gui/Commands.cpp new file mode 100644 index 0000000000..544837fc71 --- /dev/null +++ b/src/Mod/Assembly/Gui/Commands.cpp @@ -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 * + * . * + * * + ***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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(vp)) { + return assemblyVP->getObject(); + } + + return nullptr; +} + +void selectObjects(const std::vector& 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& names) +{ + if (!assembly || names.empty()) { + return; + } + + std::vector 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 objectsToSelect; + std::vector 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()); +} diff --git a/src/Mod/Assembly/Gui/Commands.h b/src/Mod/Assembly/Gui/Commands.h new file mode 100644 index 0000000000..2b146bfc08 --- /dev/null +++ b/src/Mod/Assembly/Gui/Commands.h @@ -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 * + * . * + * * + ***************************************************************************/ + +#ifndef ASSEMBLYGUI_COMMANDS_H +#define ASSEMBLYGUI_COMMANDS_H + +#include + +namespace AssemblyGui +{ + +void CreateAssemblyCommands(); + +} // namespace AssemblyGui + +#endif // ASSEMBLYGUI_COMMANDS_H diff --git a/src/Mod/Assembly/Gui/TaskAssemblyMessages.cpp b/src/Mod/Assembly/Gui/TaskAssemblyMessages.cpp index 7d56b9628f..5047a384b8 100644 --- a/src/Mod/Assembly/Gui/TaskAssemblyMessages.cpp +++ b/src/Mod/Assembly/Gui/TaskAssemblyMessages.cpp @@ -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" diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index ea114b7b9d..6b718e4655 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -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(); @@ -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& ints) +inline QString objListHelper(const AssemblyObject* assembly, const std::vector& names) { + if (!assembly) { + return QString(); + } + App::Document* doc = assembly->getDocument(); + + std::vector 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"), diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.h b/src/Mod/Assembly/Gui/ViewProviderAssembly.h index 189eb6c186..c8e2e29915 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.h +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.h @@ -284,6 +284,8 @@ private: std::set& visited ); + TaskAssemblyMessages* taskSolver; + fastsignals::connection connectSolverUpdate; fastsignals::scoped_connection m_preTransactionConn; };