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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, "")) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -41,6 +41,8 @@ SET(AssemblyGui_SRCS_Module
|
||||
AppAssemblyGui.cpp
|
||||
AppAssemblyGuiPy.cpp
|
||||
PreCompiled.h
|
||||
Commands.cpp
|
||||
Commands.h
|
||||
TaskAssemblyMessages.cpp
|
||||
TaskAssemblyMessages.h
|
||||
ViewProviderAssembly.cpp
|
||||
|
||||
247
src/Mod/Assembly/Gui/Commands.cpp
Normal file
247
src/Mod/Assembly/Gui/Commands.cpp
Normal 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());
|
||||
}
|
||||
36
src/Mod/Assembly/Gui/Commands.h
Normal file
36
src/Mod/Assembly/Gui/Commands.h
Normal 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -284,6 +284,8 @@ private:
|
||||
std::set<App::DocumentObject*>& visited
|
||||
);
|
||||
|
||||
TaskAssemblyMessages* taskSolver;
|
||||
|
||||
fastsignals::connection connectSolverUpdate;
|
||||
fastsignals::scoped_connection m_preTransactionConn;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user