Assembly: Solver message taskbox. UI setup, App not implemented yet. (#23420)
* Assembly: Solver message taskbox. UI setup, App not implemented yet. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update TaskAssemblyMessages.cpp * Update ViewProviderAssembly.cpp --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -541,7 +541,7 @@ AssemblyObject* AssemblyLink::getParentAssembly() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AssemblyLink::isRigid()
|
||||
bool AssemblyLink::isRigid() const
|
||||
{
|
||||
auto* prop = dynamic_cast<App::PropertyBool*>(getPropertyByName("Rigid"));
|
||||
if (!prop) {
|
||||
@@ -559,3 +559,18 @@ std::vector<App::DocumentObject*> AssemblyLink::getJoints()
|
||||
}
|
||||
return jointGroup->getJoints();
|
||||
}
|
||||
|
||||
bool AssemblyLink::allowDuplicateLabel() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int AssemblyLink::numberOfComponents() const
|
||||
{
|
||||
return isRigid() ? 1 : getLinkedAssembly()->numberOfComponents();
|
||||
}
|
||||
|
||||
bool AssemblyLink::isEmpty() const
|
||||
{
|
||||
return numberOfComponents() == 0;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
// This function returns the linked object, either an AssemblyObject or an AssemblyLink
|
||||
App::DocumentObject* getLinkedObject2(bool recurse = true) const;
|
||||
|
||||
bool isRigid();
|
||||
bool isRigid() const;
|
||||
|
||||
/**
|
||||
* Update all of the components and joints from the Assembly
|
||||
@@ -82,6 +82,11 @@ public:
|
||||
JointGroup* ensureJointGroup();
|
||||
std::vector<App::DocumentObject*> getJoints();
|
||||
|
||||
bool allowDuplicateLabel() const override;
|
||||
|
||||
bool isEmpty() const;
|
||||
int numberOfComponents() const;
|
||||
|
||||
App::PropertyXLink LinkedObject;
|
||||
App::PropertyBool Rigid;
|
||||
|
||||
|
||||
@@ -102,8 +102,17 @@ PROPERTY_SOURCE(Assembly::AssemblyObject, App::Part)
|
||||
AssemblyObject::AssemblyObject()
|
||||
: mbdAssembly(std::make_shared<ASMTAssembly>())
|
||||
, bundleFixed(false)
|
||||
, lastDoF(0)
|
||||
, lastHasConflict(false)
|
||||
, lastHasRedundancies(false)
|
||||
, lastHasPartialRedundancies(false)
|
||||
, lastHasMalformedConstraints(false)
|
||||
, lastSolverStatus(0)
|
||||
{
|
||||
mbdAssembly->externalSystem->freecadAssemblyObject = this;
|
||||
|
||||
lastDoF = numberOfComponents() * 6;
|
||||
signalSolverUpdate();
|
||||
}
|
||||
|
||||
AssemblyObject::~AssemblyObject() = default;
|
||||
@@ -131,6 +140,8 @@ App::DocumentObjectExecReturn* AssemblyObject::execute()
|
||||
|
||||
int AssemblyObject::solve(bool enableRedo, bool updateJCS)
|
||||
{
|
||||
lastDoF = numberOfComponents() * 6;
|
||||
|
||||
ensureIdentityPlacements();
|
||||
|
||||
mbdAssembly = makeMbdAssembly();
|
||||
@@ -169,6 +180,8 @@ int AssemblyObject::solve(bool enableRedo, bool updateJCS)
|
||||
|
||||
redrawJointPlacements(joints);
|
||||
|
||||
signalSolverUpdate();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1972,3 +1985,52 @@ 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;
|
||||
}
|
||||
|
||||
bool AssemblyObject::isEmpty() const
|
||||
{
|
||||
return numberOfComponents() == 0;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#ifndef ASSEMBLY_AssemblyObject_H
|
||||
#define ASSEMBLY_AssemblyObject_H
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
#include <Mod/Assembly/AssemblyGlobal.h>
|
||||
|
||||
@@ -193,6 +194,51 @@ public:
|
||||
|
||||
bool isMbDJointValid(App::DocumentObject* joint);
|
||||
|
||||
bool isEmpty() const;
|
||||
int numberOfComponents() const;
|
||||
|
||||
inline int getLastDoF() const
|
||||
{
|
||||
return lastDoF;
|
||||
}
|
||||
inline bool getLastHasConflicts() const
|
||||
{
|
||||
return lastHasConflict;
|
||||
}
|
||||
inline bool getLastHasRedundancies() const
|
||||
{
|
||||
return lastHasRedundancies;
|
||||
}
|
||||
inline bool getLastHasPartialRedundancies() const
|
||||
{
|
||||
return lastHasPartialRedundancies;
|
||||
}
|
||||
inline bool getLastHasMalformedConstraints() const
|
||||
{
|
||||
return lastHasMalformedConstraints;
|
||||
}
|
||||
inline int getLastSolverStatus() const
|
||||
{
|
||||
return lastSolverStatus;
|
||||
}
|
||||
inline const std::vector<int>& getLastConflicting() const
|
||||
{
|
||||
return lastConflicting;
|
||||
}
|
||||
inline const std::vector<int>& getLastRedundant() const
|
||||
{
|
||||
return lastRedundant;
|
||||
}
|
||||
inline const std::vector<int>& getLastPartiallyRedundant() const
|
||||
{
|
||||
return lastPartiallyRedundant;
|
||||
}
|
||||
inline const std::vector<int>& getLastMalformedConstraints() const
|
||||
{
|
||||
return lastMalformedConstraints;
|
||||
}
|
||||
boost::signals2::signal<void()> signalSolverUpdate;
|
||||
|
||||
private:
|
||||
std::shared_ptr<MbD::ASMTAssembly> mbdAssembly;
|
||||
|
||||
@@ -204,6 +250,18 @@ private:
|
||||
std::vector<std::pair<App::DocumentObject*, Base::Placement>> previousPositions;
|
||||
|
||||
bool bundleFixed;
|
||||
|
||||
int lastDoF;
|
||||
bool lastHasConflict;
|
||||
bool lastHasRedundancies;
|
||||
bool lastHasPartialRedundancies;
|
||||
bool lastHasMalformedConstraints;
|
||||
int lastSolverStatus;
|
||||
|
||||
std::vector<int> lastConflicting;
|
||||
std::vector<int> lastRedundant;
|
||||
std::vector<int> lastPartiallyRedundant;
|
||||
std::vector<int> lastMalformedConstraints;
|
||||
};
|
||||
|
||||
} // namespace Assembly
|
||||
|
||||
@@ -45,6 +45,8 @@ SET(AssemblyGui_SRCS_Module
|
||||
AppAssemblyGuiPy.cpp
|
||||
PreCompiled.cpp
|
||||
PreCompiled.h
|
||||
TaskAssemblyMessages.cpp
|
||||
TaskAssemblyMessages.h
|
||||
ViewProviderAssembly.cpp
|
||||
ViewProviderAssembly.h
|
||||
ViewProviderAssemblyLink.cpp
|
||||
|
||||
93
src/Mod/Assembly/Gui/TaskAssemblyMessages.cpp
Normal file
93
src/Mod/Assembly/Gui/TaskAssemblyMessages.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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 "PreCompiled.h"
|
||||
#ifndef _PreComp_
|
||||
#endif
|
||||
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Gui/Command.h>
|
||||
// #include <Mod/Assembly/App/AssemblyObject.h>
|
||||
|
||||
#include "TaskAssemblyMessages.h"
|
||||
#include "ViewProviderAssembly.h"
|
||||
|
||||
using namespace AssemblyGui;
|
||||
using namespace Gui::TaskView;
|
||||
namespace sp = std::placeholders;
|
||||
|
||||
TaskAssemblyMessages::TaskAssemblyMessages(ViewProviderAssembly* vp)
|
||||
: TaskSolverMessages(Gui::BitmapFactory().pixmap("Geoassembly"), tr("Solver messages"))
|
||||
, vp(vp)
|
||||
{
|
||||
// NOLINTBEGIN
|
||||
connectionSetUp = vp->signalSetUp.connect(
|
||||
std::bind(&TaskAssemblyMessages::slotSetUp, this, sp::_1, sp::_2, sp::_3, sp::_4));
|
||||
// NOLINTEND
|
||||
}
|
||||
|
||||
TaskAssemblyMessages::~TaskAssemblyMessages()
|
||||
{
|
||||
connectionSetUp.disconnect();
|
||||
}
|
||||
|
||||
void TaskAssemblyMessages::updateToolTip(const QString& link)
|
||||
{
|
||||
if (link == QStringLiteral("#conflicting")) {
|
||||
setLinkTooltip(tr("Click to select these conflicting joints."));
|
||||
}
|
||||
else if (link == QStringLiteral("#redundant")) {
|
||||
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."));
|
||||
}
|
||||
else if (link == QStringLiteral("#malformed")) {
|
||||
setLinkTooltip(tr("Click to select these malformed joints."));
|
||||
}
|
||||
}
|
||||
|
||||
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")) {
|
||||
Gui::Application::Instance->commandManager().runCommandByName(
|
||||
"Assembly_SelectConflictingConstraints");
|
||||
}
|
||||
else if (str == QStringLiteral("#redundant")) {
|
||||
Gui::Application::Instance->commandManager().runCommandByName(
|
||||
"Assembly_SelectRedundantConstraints");
|
||||
}
|
||||
else if (str == QStringLiteral("#dofs")) {
|
||||
Gui::Application::Instance->commandManager().runCommandByName(
|
||||
"Assembly_SelectComponentsWithDoFs");
|
||||
}
|
||||
else if (str == QStringLiteral("#malformed")) {
|
||||
Gui::Application::Instance->commandManager().runCommandByName(
|
||||
"Assembly_SelectMalformedConstraints");
|
||||
}*/
|
||||
}
|
||||
|
||||
#include "moc_TaskAssemblyMessages.cpp"
|
||||
54
src/Mod/Assembly/Gui/TaskAssemblyMessages.h
Normal file
54
src/Mod/Assembly/Gui/TaskAssemblyMessages.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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 GUI_TASKVIEW_TaskAssemblyMessages_H
|
||||
#define GUI_TASKVIEW_TaskAssemblyMessages_H
|
||||
|
||||
#include <Gui/TaskView/TaskSolverMessages.h>
|
||||
|
||||
|
||||
namespace AssemblyGui
|
||||
{
|
||||
|
||||
class ViewProviderAssembly;
|
||||
|
||||
class TaskAssemblyMessages: public Gui::TaskSolverMessages
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TaskAssemblyMessages(ViewProviderAssembly* vp);
|
||||
~TaskAssemblyMessages() override;
|
||||
|
||||
private:
|
||||
void onLabelStatusLinkClicked(const QString&) override;
|
||||
|
||||
void updateToolTip(const QString& link);
|
||||
|
||||
protected:
|
||||
ViewProviderAssembly* vp;
|
||||
};
|
||||
|
||||
} // namespace AssemblyGui
|
||||
|
||||
#endif // GUI_TASKVIEW_TaskAssemblyMessages_H
|
||||
@@ -69,6 +69,8 @@
|
||||
#include <Mod/Assembly/App/BomGroup.h>
|
||||
#include <Mod/PartDesign/App/Body.h>
|
||||
|
||||
#include "TaskAssemblyMessages.h"
|
||||
|
||||
#include "ViewProviderAssembly.h"
|
||||
#include "ViewProviderAssemblyPy.h"
|
||||
|
||||
@@ -277,6 +279,17 @@ bool ViewProviderAssembly::setEdit(int mode)
|
||||
|
||||
attachSelection();
|
||||
|
||||
Gui::TaskView::TaskView* taskView = Gui::Control().taskPanel();
|
||||
if (taskView) {
|
||||
// Waiting for the solver to support reporting information.
|
||||
// taskSolver = new TaskAssemblyMessages(this);
|
||||
// taskView->addContextualPanel(taskSolver);
|
||||
}
|
||||
|
||||
auto* assembly = getObject<AssemblyObject>();
|
||||
connectSolverUpdate = assembly->signalSolverUpdate.connect(
|
||||
boost::bind(&ViewProviderAssembly::UpdateSolverInformation, this));
|
||||
|
||||
return true;
|
||||
}
|
||||
return ViewProviderPart::setEdit(mode);
|
||||
@@ -304,6 +317,15 @@ void ViewProviderAssembly::unsetEdit(int mode)
|
||||
"Gui.getDocument(appDoc).ActiveView.setActiveObject('%s', None)",
|
||||
this->getObject()->getDocument()->getName(),
|
||||
PARTKEY);
|
||||
|
||||
Gui::TaskView::TaskView* taskView = Gui::Control().taskPanel();
|
||||
if (taskView) {
|
||||
// Waiting for the solver to support reporting information.
|
||||
// taskView->removeContextualPanel(taskSolver);
|
||||
}
|
||||
|
||||
connectSolverUpdate.disconnect();
|
||||
|
||||
return;
|
||||
}
|
||||
ViewProviderPart::unsetEdit(mode);
|
||||
@@ -1271,3 +1293,87 @@ ViewProviderAssembly::getCenterOfBoundingBox(const std::vector<MovingObject>& mo
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
inline QString intListHelper(const std::vector<int>& ints)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const int numToShow = 3;
|
||||
int more = ints.size() - numToShow;
|
||||
for (int i = 0; i < numToShow; ++i) {
|
||||
results.append(QStringLiteral("%1, ").arg(ints[i]));
|
||||
}
|
||||
results.append(ViewProviderAssembly::tr("ViewProviderAssembly", "and %1 more").arg(more));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::UpdateSolverInformation()
|
||||
{
|
||||
// Updates Solver Information with the Last solver execution at AssemblyObject level
|
||||
auto* assembly = getObject<AssemblyObject>();
|
||||
|
||||
int dofs = assembly->getLastDoF();
|
||||
bool hasConflicts = assembly->getLastHasConflicts();
|
||||
bool hasRedundancies = assembly->getLastHasRedundancies();
|
||||
bool hasPartiallyRedundant = assembly->getLastHasPartialRedundancies();
|
||||
bool hasMalformed = assembly->getLastHasMalformedConstraints();
|
||||
|
||||
if (assembly->isEmpty()) {
|
||||
signalSetUp(QStringLiteral("empty"), tr("Empty Assembly"), QString(), QString());
|
||||
}
|
||||
else if (dofs < 0 || hasConflicts) { // over-constrained
|
||||
signalSetUp(QStringLiteral("conflicting_constraints"),
|
||||
tr("Over-constrained:") + QLatin1String(" "),
|
||||
QStringLiteral("#conflicting"),
|
||||
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastConflicting())));
|
||||
}
|
||||
else if (hasMalformed) { // malformed joints
|
||||
signalSetUp(
|
||||
QStringLiteral("malformed_constraints"),
|
||||
tr("Malformed joints:") + QLatin1String(" "),
|
||||
QStringLiteral("#malformed"),
|
||||
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastMalformedConstraints())));
|
||||
}
|
||||
else if (hasRedundancies) {
|
||||
signalSetUp(QStringLiteral("redundant_constraints"),
|
||||
tr("Redundant joints:") + QLatin1String(" "),
|
||||
QStringLiteral("#redundant"),
|
||||
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastRedundant())));
|
||||
}
|
||||
else if (hasPartiallyRedundant) {
|
||||
signalSetUp(
|
||||
QStringLiteral("partially_redundant_constraints"),
|
||||
tr("Partially redundant:") + QLatin1String(" "),
|
||||
QStringLiteral("#partiallyredundant"),
|
||||
QStringLiteral("(%1)").arg(intListHelper(assembly->getLastPartiallyRedundant())));
|
||||
}
|
||||
else if (assembly->getLastSolverStatus() != 0) {
|
||||
signalSetUp(QStringLiteral("solver_failed"),
|
||||
tr("Solver failed to converge"),
|
||||
QStringLiteral(""),
|
||||
QStringLiteral(""));
|
||||
}
|
||||
else if (dofs > 0) {
|
||||
signalSetUp(QStringLiteral("under_constrained"),
|
||||
tr("Under-constrained:") + QLatin1String(" "),
|
||||
QStringLiteral("#dofs"),
|
||||
tr("%n Degrees of Freedom", "", dofs));
|
||||
}
|
||||
else {
|
||||
signalSetUp(QStringLiteral("fully_constrained"),
|
||||
tr("Fully constrained"),
|
||||
QString(),
|
||||
QString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssembly_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
#include <Mod/Assembly/AssemblyGlobal.h>
|
||||
|
||||
@@ -44,6 +45,7 @@ class View3DInventorViewer;
|
||||
|
||||
namespace AssemblyGui
|
||||
{
|
||||
class TaskAssemblyMessages;
|
||||
|
||||
struct MovingObject
|
||||
{
|
||||
@@ -200,6 +202,8 @@ public:
|
||||
|
||||
static Base::Vector3d getCenterOfBoundingBox(const std::vector<MovingObject>& movingObjs);
|
||||
|
||||
void UpdateSolverInformation();
|
||||
|
||||
DragMode dragMode;
|
||||
bool canStartDragging;
|
||||
bool partMoving;
|
||||
@@ -229,6 +233,10 @@ public:
|
||||
SoFieldSensor* translationSensor = nullptr;
|
||||
SoFieldSensor* rotationSensor = nullptr;
|
||||
|
||||
boost::signals2::signal<
|
||||
void(const QString& state, const QString& msg, const QString& url, const QString& linkText)>
|
||||
signalSetUp;
|
||||
|
||||
private:
|
||||
bool tryMouseMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer);
|
||||
void tryInitMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer);
|
||||
@@ -237,6 +245,9 @@ private:
|
||||
const std::string& subNamePrefix,
|
||||
App::DocumentObject* currentObject,
|
||||
bool onlySolids);
|
||||
|
||||
TaskAssemblyMessages* taskSolver;
|
||||
boost::signals2::connection connectSolverUpdate;
|
||||
};
|
||||
|
||||
} // namespace AssemblyGui
|
||||
|
||||
Reference in New Issue
Block a user