FreeCAD / src /Gui /Selection /Selection.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2011 Jürgen Riegel <juergen.riegel@web.de> *
* Copyright (c) 2011 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include <array>
#include <set>
#include <boost/algorithm/string/predicate.hpp>
#include <QApplication>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObjectPy.h>
#include <App/GeoFeature.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Interpreter.h>
#include <Base/Tools.h>
#include <Base/PyWrapParseTupleAndKeywords.h>
#include <Base/UnitsApi.h>
#include "Selection.h"
#include "SelectionObject.h"
#include "Application.h"
#include "Document.h"
#include "Macro.h"
#include "MainWindow.h"
#include "MDIView.h"
#include "SelectionFilter.h"
#include "SelectionFilterPy.h"
#include "SelectionObserverPython.h"
#include "Tree.h"
#include "ViewProvider.h"
#include "ViewProviderDocumentObject.h"
FC_LOG_LEVEL_INIT("Selection", false, true, true)
using namespace Gui;
using namespace std;
namespace sp = std::placeholders;
SelectionGateFilterExternal::SelectionGateFilterExternal(const char* docName, const char* objName)
{
if (docName) {
DocName = docName;
if (objName) {
ObjName = objName;
}
}
}
bool SelectionGateFilterExternal::allow(App::Document* doc, App::DocumentObject* obj, const char*)
{
if (!doc || !obj) {
return true;
}
if (!DocName.empty() && doc->getName() != DocName) {
notAllowedReason = "Cannot select external object";
}
else if (!ObjName.empty() && ObjName == obj->getNameInDocument()) {
notAllowedReason = "Cannot select self";
}
else {
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////
SelectionObserver::SelectionObserver(bool attach, ResolveMode resolve)
: resolve(resolve)
, blockedSelection(false)
{
if (attach) {
attachSelection();
}
}
SelectionObserver::SelectionObserver(const ViewProviderDocumentObject* vp, bool attach, ResolveMode resolve)
: resolve(resolve)
, blockedSelection(false)
{
if (vp && vp->getObject() && vp->getObject()->getDocument()) {
filterDocName = vp->getObject()->getDocument()->getName();
filterObjName = vp->getObject()->getNameInDocument();
}
if (attach) {
attachSelection();
}
}
SelectionObserver::~SelectionObserver()
{
detachSelection();
}
bool SelectionObserver::blockSelection(bool block)
{
bool ok = blockedSelection;
blockedSelection = block;
return ok;
}
bool SelectionObserver::isSelectionBlocked() const
{
return blockedSelection;
}
bool SelectionObserver::isSelectionAttached() const
{
return connectSelection.connected();
}
void SelectionObserver::attachSelection()
{
if (!connectSelection.connected()) {
bool newStyle = (resolve >= ResolveMode::NewStyleElement);
bool oldStyle = (resolve == ResolveMode::OldStyleElement);
auto& signal = newStyle ? Selection().signalSelectionChanged3
: oldStyle ? Selection().signalSelectionChanged2
: Selection().signalSelectionChanged;
// NOLINTBEGIN
connectSelection = signal.connect(
std::bind(&SelectionObserver::_onSelectionChanged, this, sp::_1)
);
// NOLINTEND
if (!filterDocName.empty()) {
Selection().addSelectionGate(
new SelectionGateFilterExternal(filterDocName.c_str(), filterObjName.c_str())
);
}
}
}
void SelectionObserver::_onSelectionChanged(const SelectionChanges& msg)
{
try {
if (blockedSelection) {
return;
}
onSelectionChanged(msg);
}
catch (Base::Exception& e) {
e.reportException();
FC_ERR("Unhandled Base::Exception caught in selection observer: ");
}
catch (std::exception& e) {
FC_ERR("Unhandled std::exception caught in selection observer: " << e.what());
}
catch (...) {
FC_ERR("Unhandled unknown exception caught in selection observer");
}
}
void SelectionObserver::detachSelection()
{
if (connectSelection.connected()) {
connectSelection.disconnect();
if (!filterDocName.empty()) {
Selection().rmvSelectionGate();
}
}
}
// -------------------------------------------
bool SelectionSingleton::hasSelection() const
{
return !_SelList.empty();
}
bool SelectionSingleton::hasPreselection() const
{
return !CurrentPreselection.Object.getObjectName().empty();
}
std::vector<SelectionSingleton::SelObj> SelectionSingleton::getCompleteSelection(ResolveMode resolve) const
{
return getSelection("*", resolve);
}
std::vector<SelectionSingleton::SelObj> SelectionSingleton::getSelection(
const char* pDocName,
ResolveMode resolve,
bool single
) const
{
std::vector<SelObj> temp;
if (single) {
temp.reserve(1);
}
SelObj tempSelObj;
App::Document* pcDoc = nullptr;
if (!pDocName || strcmp(pDocName, "*") != 0) {
pcDoc = getDocument(pDocName);
if (!pcDoc) {
return temp;
}
}
std::map<App::DocumentObject*, std::set<std::string>> objMap;
for (auto& sel : _SelList) {
if (!sel.pDoc) {
continue;
}
const char* subelement = nullptr;
auto obj = getObjectOfType(sel, App::DocumentObject::getClassTypeId(), resolve, &subelement);
if (!obj || (pcDoc && sel.pObject->getDocument() != pcDoc)) {
continue;
}
// In case we are resolving objects, make sure no duplicates
if (resolve != ResolveMode::NoResolve
&& !objMap[obj].insert(std::string(subelement ? subelement : "")).second) {
continue;
}
if (single && !temp.empty()) {
temp.clear();
break;
}
tempSelObj.DocName = obj->getDocument()->getName();
tempSelObj.FeatName = obj->getNameInDocument();
tempSelObj.SubName = subelement;
tempSelObj.TypeName = obj->getTypeId().getName();
tempSelObj.pObject = obj;
tempSelObj.pResolvedObject = sel.pResolvedObject;
tempSelObj.pDoc = obj->getDocument();
tempSelObj.x = sel.x;
tempSelObj.y = sel.y;
tempSelObj.z = sel.z;
temp.push_back(tempSelObj);
}
return temp;
}
bool SelectionSingleton::hasSelection(const char* doc, ResolveMode resolve) const
{
App::Document* pcDoc = nullptr;
if (!doc || strcmp(doc, "*") != 0) {
pcDoc = getDocument(doc);
if (!pcDoc) {
return false;
}
}
for (auto& sel : _SelList) {
if (!sel.pDoc) {
continue;
}
auto obj = getObjectOfType(sel, App::DocumentObject::getClassTypeId(), resolve);
if (obj && (!pcDoc || sel.pObject->getDocument() == pcDoc)) {
return true;
}
}
return false;
}
bool SelectionSingleton::hasSubSelection(const char* doc, bool subElement) const
{
App::Document* pcDoc = nullptr;
if (!doc || strcmp(doc, "*") != 0) {
pcDoc = getDocument(doc);
if (!pcDoc) {
return false;
}
}
for (auto& sel : _SelList) {
if (pcDoc && pcDoc != sel.pDoc) {
continue;
}
if (sel.SubName.empty()) {
continue;
}
if (subElement && sel.SubName.back() != '.') {
return true;
}
if (sel.pObject != sel.pResolvedObject) {
return true;
}
}
return false;
}
std::vector<SelectionSingleton::SelObj> SelectionSingleton::getPickedList(const char* pDocName) const
{
std::vector<SelObj> temp;
SelObj tempSelObj;
App::Document* pcDoc = nullptr;
if (!pDocName || strcmp(pDocName, "*") != 0) {
pcDoc = getDocument(pDocName);
if (!pcDoc) {
return temp;
}
}
for (std::list<_SelObj>::const_iterator It = _PickedList.begin(); It != _PickedList.end(); ++It) {
if (!pcDoc || It->pDoc == pcDoc) {
tempSelObj.DocName = It->DocName.c_str();
tempSelObj.FeatName = It->FeatName.c_str();
tempSelObj.SubName = It->SubName.c_str();
tempSelObj.TypeName = It->TypeName.c_str();
tempSelObj.pObject = It->pObject;
tempSelObj.pResolvedObject = It->pResolvedObject;
tempSelObj.pDoc = It->pDoc;
tempSelObj.x = It->x;
tempSelObj.y = It->y;
tempSelObj.z = It->z;
temp.push_back(tempSelObj);
}
}
return temp;
}
std::vector<Gui::SelectionObject> SelectionSingleton::getSelectionIn(
App::DocumentObject* container,
Base::Type typeId,
bool single
) const
{
if (!container) {
return getSelectionEx(nullptr, typeId, ResolveMode::NoResolve, single);
}
std::vector<SelectionObject> sels
= getSelectionEx(nullptr, App::DocumentObject::getClassTypeId(), ResolveMode::NoResolve, single);
std::vector<SelectionObject> ret;
std::map<App::DocumentObject*, size_t> SortMap;
for (auto& sel : sels) {
auto* rootObj = sel.getObject();
App::Document* doc = rootObj->getDocument();
std::vector<std::string> subs = sel.getSubNames();
bool objPassed = false;
for (size_t i = 0; i < subs.size(); ++i) {
auto& sub = subs[i];
App::DocumentObject* newRootObj = nullptr;
std::string newSub = "";
std::vector<std::string> names = Base::Tools::splitSubName(sub);
if (container == rootObj) {
objPassed = true;
}
if (rootObj->isLink()) {
// Update doc in case its an external link.
doc = rootObj->getLinkedObject()->getDocument();
}
for (auto& name : names) {
App::DocumentObject* obj = doc->getObject(name.c_str());
if (!obj) { // We reached the element name (for example 'edge1')
newSub += name;
break;
}
if (objPassed) {
if (!newRootObj) {
// We are the first object after the container is passed.
newRootObj = obj;
}
else {
newSub += name + ".";
}
}
if (obj == container) {
objPassed = true;
}
if (obj->isLink()) {
// Update doc in case its an external link.
doc = obj->getLinkedObject()->getDocument();
}
}
if (newRootObj) {
// Make sure selected object is of correct type
auto* lastObj = newRootObj->resolve(newSub.c_str());
if (!lastObj || !lastObj->isDerivedFrom(typeId)) {
continue;
}
auto it = SortMap.find(newRootObj);
if (it != SortMap.end()) {
// only add sub-element
if (newSub != "") {
ret[it->second].SubNames.emplace_back(newSub);
ret[it->second].SelPoses.emplace_back(sel.SelPoses[i]);
}
}
else {
if (single && !ret.empty()) {
ret.clear();
break;
}
// create a new entry
ret.emplace_back(newRootObj);
if (newSub != "") {
ret.back().SubNames.emplace_back(newSub);
ret.back().SelPoses.emplace_back(sel.SelPoses[i]);
}
SortMap.insert(std::make_pair(newRootObj, ret.size() - 1));
}
}
}
}
return ret;
}
std::vector<SelectionObject> SelectionSingleton::getSelectionEx(
const char* pDocName,
Base::Type typeId,
ResolveMode resolve,
bool single
) const
{
return getObjectList(pDocName, typeId, _SelList, resolve, single);
}
std::vector<SelectionObject> SelectionSingleton::getPickedListEx(
const char* pDocName,
Base::Type typeId
) const
{
return getObjectList(pDocName, typeId, _PickedList, ResolveMode::NoResolve);
}
std::vector<SelectionObject> SelectionSingleton::getObjectList(
const char* pDocName,
Base::Type typeId,
std::list<_SelObj>& objList,
ResolveMode resolve,
bool single
) const
{
std::vector<SelectionObject> temp;
if (single) {
temp.reserve(1);
}
std::map<App::DocumentObject*, size_t> SortMap;
// check the type
if (typeId.isBad()) {
return temp;
}
App::Document* pcDoc = nullptr;
if (!pDocName || strcmp(pDocName, "*") != 0) {
pcDoc = getDocument(pDocName);
if (!pcDoc) {
return temp;
}
}
for (auto& sel : objList) {
if (!sel.pDoc) {
continue;
}
const char* subelement = nullptr;
auto obj = getObjectOfType(sel, typeId, resolve, &subelement);
if (!obj || (pcDoc && sel.pObject->getDocument() != pcDoc)) {
continue;
}
auto it = SortMap.find(obj);
if (it != SortMap.end()) {
// only add sub-element
if (subelement && *subelement) {
if (resolve != ResolveMode::NoResolve
&& !temp[it->second]._SubNameSet.insert(subelement).second) {
continue;
}
temp[it->second].SubNames.emplace_back(subelement);
temp[it->second].SelPoses.emplace_back(sel.x, sel.y, sel.z);
}
}
else {
if (single && !temp.empty()) {
temp.clear();
break;
}
// create a new entry
temp.emplace_back(obj);
if (subelement && *subelement) {
temp.back().SubNames.emplace_back(subelement);
temp.back().SelPoses.emplace_back(sel.x, sel.y, sel.z);
if (resolve != ResolveMode::NoResolve) {
temp.back()._SubNameSet.insert(subelement);
}
}
SortMap.insert(std::make_pair(obj, temp.size() - 1));
}
}
return temp;
}
bool SelectionSingleton::needPickedList() const
{
return _needPickedList;
}
void SelectionSingleton::enablePickedList(bool enable)
{
if (enable != _needPickedList) {
_needPickedList = enable;
_PickedList.clear();
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
}
static void notifyDocumentObjectViewProvider(const SelectionChanges& changes)
{
const auto* doc = App::GetApplication().getDocument(changes.pDocName);
if (!doc) {
return;
}
const auto* obj = doc->getObject(changes.pObjectName);
if (!obj) {
return;
}
auto* vp = Application::Instance->getViewProvider(obj);
if (!vp) {
return;
}
vp->onSelectionChanged(changes);
}
void SelectionSingleton::notify(SelectionChanges&& Chng)
{
if (Notifying) {
NotificationQueue.push_back(std::move(Chng));
return;
}
Base::FlagToggler<bool> flag(Notifying);
NotificationQueue.push_back(std::move(Chng));
while (!NotificationQueue.empty()) {
const auto& msg = NotificationQueue.front();
bool notify = false;
switch (msg.Type) {
case SelectionChanges::AddSelection:
notify = isSelected(msg.pDocName, msg.pObjectName, msg.pSubName, ResolveMode::NoResolve);
break;
case SelectionChanges::RmvSelection:
notify = !isSelected(msg.pDocName, msg.pObjectName, msg.pSubName, ResolveMode::NoResolve);
break;
case SelectionChanges::SetPreselect:
notify = CurrentPreselection.Type == SelectionChanges::SetPreselect
&& CurrentPreselection.Object == msg.Object;
break;
case SelectionChanges::RmvPreselect:
notify = CurrentPreselection.Type == SelectionChanges::ClrSelection;
break;
default:
notify = true;
}
if (notify) {
// Notify the view provider of the object.
notifyDocumentObjectViewProvider(msg);
Notify(msg);
try {
signalSelectionChanged(msg);
}
catch (const boost::exception&) {
// reported by code analyzers
Base::Console().warning("notify: Unexpected boost exception\n");
}
}
NotificationQueue.pop_front();
}
}
bool SelectionSingleton::hasPickedList() const
{
return !_PickedList.empty();
}
int SelectionSingleton::getAsPropertyLinkSubList(App::PropertyLinkSubList& prop) const
{
std::vector<Gui::SelectionObject> sel = this->getSelectionEx();
std::vector<App::DocumentObject*> objs;
objs.reserve(sel.size() * 2);
std::vector<std::string> subs;
subs.reserve(sel.size() * 2);
for (auto& selitem : sel) {
App::DocumentObject* obj = selitem.getObject();
const std::vector<std::string>& subnames = selitem.getSubNames();
// whole object is selected
if (subnames.empty()) {
objs.push_back(obj);
subs.emplace_back();
}
else {
for (const auto& subname : subnames) {
objs.push_back(obj);
subs.push_back(subname);
}
}
}
assert(objs.size() == subs.size());
prop.setValues(objs, subs);
return objs.size();
}
App::DocumentObject* SelectionSingleton::getObjectOfType(
_SelObj& sel,
Base::Type typeId,
ResolveMode resolve,
const char** subelement
)
{
auto obj = sel.pObject;
if (!obj || !obj->isAttachedToDocument()) {
return nullptr;
}
const char* subname = sel.SubName.c_str();
if (resolve != ResolveMode::NoResolve) {
obj = sel.pResolvedObject;
if (resolve == ResolveMode::NewStyleElement && !sel.elementName.newName.empty()) {
subname = sel.elementName.newName.c_str();
}
else {
subname = sel.elementName.oldName.c_str();
}
}
if (!obj) {
return nullptr;
}
if (!obj->isDerivedFrom(typeId)
&& (resolve != ResolveMode::FollowLink || !obj->getLinkedObject(true)->isDerivedFrom(typeId))) {
return nullptr;
}
if (subelement) {
*subelement = subname;
}
return obj;
}
vector<App::DocumentObject*> SelectionSingleton::getObjectsOfType(
const Base::Type& typeId,
const char* pDocName,
ResolveMode resolve
) const
{
std::vector<App::DocumentObject*> temp;
App::Document* pcDoc = nullptr;
if (!pDocName || strcmp(pDocName, "*") != 0) {
pcDoc = getDocument(pDocName);
if (!pcDoc) {
return temp;
}
}
std::set<App::DocumentObject*> objs;
for (auto& sel : _SelList) {
if (pcDoc && pcDoc != sel.pDoc) {
continue;
}
App::DocumentObject* pObject = getObjectOfType(sel, typeId, resolve);
if (pObject) {
auto ret = objs.insert(pObject);
if (ret.second) {
temp.push_back(pObject);
}
}
}
return temp;
}
std::vector<App::DocumentObject*> SelectionSingleton::getObjectsOfType(
const char* typeName,
const char* pDocName,
ResolveMode resolve
) const
{
Base::Type typeId = Base::Type::fromName(typeName);
if (typeId.isBad()) {
return {};
}
return getObjectsOfType(typeId, pDocName, resolve);
}
unsigned int SelectionSingleton::countObjectsOfType(
const Base::Type& typeId,
const char* pDocName,
ResolveMode resolve
) const
{
App::Document* pcDoc = nullptr;
if (!pDocName || strcmp(pDocName, "*") != 0) {
pcDoc = getDocument(pDocName);
if (!pcDoc) {
return 0;
}
}
return std::count_if(_SelList.begin(), _SelList.end(), [&](auto& sel) {
return (!pcDoc || pcDoc == sel.pDoc) && getObjectOfType(sel, typeId, resolve);
});
}
unsigned int SelectionSingleton::countObjectsOfType(
const char* typeName,
const char* pDocName,
ResolveMode resolve
) const
{
Base::Type typeId = Base::Type::fromName(typeName);
if (typeId.isBad()) {
return 0;
}
return countObjectsOfType(typeId, pDocName, resolve);
}
void SelectionSingleton::slotSelectionChanged(const SelectionChanges& msg)
{
if (msg.Type == SelectionChanges::SetPreselectSignal || msg.Type == SelectionChanges::ShowSelection
|| msg.Type == SelectionChanges::HideSelection) {
return;
}
if (!msg.Object.getSubName().empty()) {
auto pParent = msg.Object.getObject();
if (!pParent) {
return;
}
App::ElementNamePair elementName;
auto& newElementName = elementName.newName;
auto& oldElementName = elementName.oldName;
auto pObject = App::GeoFeature::resolveElement(pParent, msg.pSubName, elementName);
if (!pObject) {
return;
}
SelectionChanges msg2(
msg.Type,
pObject->getDocument()->getName(),
pObject->getNameInDocument(),
!newElementName.empty() ? newElementName.c_str() : oldElementName.c_str(),
pObject->getTypeId().getName(),
msg.x,
msg.y,
msg.z
);
try {
msg2.pOriginalMsg = &msg;
signalSelectionChanged3(msg2);
msg2.Object.setSubName(oldElementName.c_str());
msg2.pSubName = msg2.Object.getSubName().c_str();
signalSelectionChanged2(msg2);
}
catch (const boost::exception&) {
// reported by code analyzers
Base::Console().warning("slotSelectionChanged: Unexpected boost exception\n");
}
}
else {
try {
signalSelectionChanged3(msg);
signalSelectionChanged2(msg);
}
catch (const boost::exception&) {
// reported by code analyzers
Base::Console().warning("slotSelectionChanged: Unexpected boost exception\n");
}
}
}
int SelectionSingleton::setPreselect(
const char* pDocName,
const char* pObjectName,
const char* pSubName,
float x,
float y,
float z,
SelectionChanges::MsgSource signal
)
{
if (!pDocName || !pObjectName) {
rmvPreselect();
return 0;
}
if (!pSubName) {
pSubName = "";
}
if (DocName == pDocName && FeatName == pObjectName && SubName == pSubName) {
return -1;
}
rmvPreselect();
if (ActiveGate && signal != SelectionChanges::MsgSource::Internal) {
App::Document* pDoc = getDocument(pDocName);
if (!pDoc || !pObjectName) {
return 0;
}
App::ElementNamePair elementName;
auto pObject = pDoc->getObject(pObjectName);
if (!pObject) {
return 0;
}
const char* subelement = pSubName;
if (gateResolve != ResolveMode::NoResolve) {
auto& newElementName = elementName.newName;
auto& oldElementName = elementName.oldName;
pObject = App::GeoFeature::resolveElement(pObject, pSubName, elementName);
if (!pObject) {
return 0;
}
if (gateResolve > ResolveMode::OldStyleElement) {
subelement = !newElementName.empty() ? newElementName.c_str()
: oldElementName.c_str();
}
else {
subelement = oldElementName.c_str();
}
}
if (!ActiveGate->allow(pObject->getDocument(), pObject, subelement)) {
QString msg;
if (ActiveGate->notAllowedReason.length() > 0) {
msg = QObject::tr(ActiveGate->notAllowedReason.c_str());
}
else {
msg = QCoreApplication::translate("SelectionFilter", "Not allowed:");
}
msg.append(QStringLiteral(" %1.%2.%3 ")
.arg(
QString::fromLatin1(pDocName),
QString::fromLatin1(pObjectName),
QString::fromLatin1(pSubName)
));
if (getMainWindow()) {
getMainWindow()->showMessage(msg);
Gui::MDIView* mdi = Gui::Application::Instance->activeDocument()->getActiveView();
mdi->setOverrideCursor(QCursor(Qt::ForbiddenCursor));
}
return 0;
}
Gui::MDIView* mdi = Gui::Application::Instance->activeDocument()->getActiveView();
mdi->restoreOverrideCursor();
}
DocName = pDocName;
FeatName = pObjectName;
SubName = pSubName;
hx = x;
hy = y;
hz = z;
// set up the change object
SelectionChanges Chng(
signal == SelectionChanges::MsgSource::Internal ? SelectionChanges::SetPreselectSignal
: SelectionChanges::SetPreselect,
DocName,
FeatName,
SubName,
std::string(),
x,
y,
z,
signal
);
if (Chng.Type == SelectionChanges::SetPreselect) {
CurrentPreselection = Chng;
FC_TRACE("preselect " << DocName << '#' << FeatName << '.' << SubName);
}
else {
FC_TRACE("preselect signal " << DocName << '#' << FeatName << '.' << SubName);
}
notify(Chng);
if (signal == SelectionChanges::MsgSource::Internal && !DocName.empty()) {
FC_TRACE("preselect " << DocName << '#' << FeatName << '.' << SubName);
Chng.Type = SelectionChanges::SetPreselect;
CurrentPreselection = Chng;
notify(std::move(Chng));
}
// It is possible the preselect is removed during notification
return DocName.empty() ? 0 : 1;
}
namespace Gui
{
std::array<std::pair<double, std::string>, 3> schemaTranslatePoint(
double x,
double y,
double z,
double precision
)
{
Base::Quantity mmx(Base::Quantity::MilliMetre);
mmx.setValue(fabs(x) > precision ? x : 0.0);
Base::Quantity mmy(Base::Quantity::MilliMetre);
mmy.setValue(fabs(y) > precision ? y : 0.0);
Base::Quantity mmz(Base::Quantity::MilliMetre);
mmz.setValue(fabs(z) > precision ? z : 0.0);
double xfactor, yfactor, zfactor;
std::string xunit, yunit, zunit;
Base::UnitsApi::schemaTranslate(mmx, xfactor, xunit);
Base::UnitsApi::schemaTranslate(mmy, yfactor, yunit);
Base::UnitsApi::schemaTranslate(mmz, zfactor, zunit);
double xuser = fabs(x) > precision ? x / xfactor : 0.0;
double yuser = fabs(y) > precision ? y / yfactor : 0.0;
double zuser = fabs(z) > precision ? z / zfactor : 0.0;
std::array<std::pair<double, std::string>, 3> ret
= {std::make_pair(xuser, xunit), std::make_pair(yuser, yunit), std::make_pair(zuser, zunit)};
return ret;
}
QString getPreselectionInfo(
const char* documentName,
const char* objectName,
const char* subElementName,
float x,
float y,
float z,
double precision
)
{
auto pts = schemaTranslatePoint(x, y, z, precision);
int numberDecimals = std::min(6, static_cast<int>(Base::UnitsApi::getDecimals()));
QString message = QStringLiteral("Preselected: %1.%2.%3 (%4 %5, %6 %7, %8 %9)")
.arg(QString::fromUtf8(documentName))
.arg(QString::fromUtf8(objectName))
.arg(QString::fromUtf8(subElementName))
.arg(QString::number(pts[0].first, 'f', numberDecimals))
.arg(QString::fromStdString(pts[0].second))
.arg(QString::number(pts[1].first, 'f', numberDecimals))
.arg(QString::fromStdString(pts[1].second))
.arg(QString::number(pts[2].first, 'f', numberDecimals))
.arg(QString::fromStdString(pts[2].second));
return message;
}
void printPreselectionInfo(
const char* documentName,
const char* objectName,
const char* subElementName,
float x,
float y,
float z,
double precision
)
{
if (getMainWindow()) {
QString message
= getPreselectionInfo(documentName, objectName, subElementName, x, y, z, precision);
getMainWindow()->showMessage(message);
}
}
} // namespace Gui
void SelectionSingleton::setPreselectCoord(float x, float y, float z)
{
// if nothing is in preselect ignore
if (CurrentPreselection.Object.getObjectName().empty()) {
return;
}
CurrentPreselection.x = x;
CurrentPreselection.y = y;
CurrentPreselection.z = z;
printPreselectionInfo(
CurrentPreselection.pDocName,
CurrentPreselection.pObjectName,
CurrentPreselection.pSubName,
x,
y,
z,
0.0
);
}
void SelectionSingleton::rmvPreselect(bool signal)
{
if (DocName.empty()) {
return;
}
if (signal) {
SelectionChanges Chng(SelectionChanges::RmvPreselectSignal, DocName, FeatName, SubName);
notify(std::move(Chng));
return;
}
SelectionChanges Chng(SelectionChanges::RmvPreselect, DocName, FeatName, SubName);
// reset the current preselection
CurrentPreselection = SelectionChanges();
DocName = "";
FeatName = "";
SubName = "";
hx = 0;
hy = 0;
hz = 0;
if (ActiveGate && getMainWindow()) {
Gui::MDIView* mdi = Gui::Application::Instance->activeDocument()->getActiveView();
mdi->restoreOverrideCursor();
}
FC_TRACE("rmv preselect");
// notify observing objects
notify(std::move(Chng));
}
const SelectionChanges& SelectionSingleton::getPreselection() const
{
return CurrentPreselection;
}
// add a SelectionGate to control what is selectable
void SelectionSingleton::addSelectionGate(Gui::SelectionGate* gate, ResolveMode resolve)
{
if (ActiveGate) {
rmvSelectionGate();
}
ActiveGate = gate;
gateResolve = resolve;
}
// remove the active SelectionGate
void SelectionSingleton::rmvSelectionGate()
{
if (ActiveGate) {
delete ActiveGate;
ActiveGate = nullptr;
Gui::Document* doc = Gui::Application::Instance->activeDocument();
if (doc) {
// if a document is about to be closed it has no MDI view any more
Gui::MDIView* mdi = doc->getActiveView();
if (mdi) {
mdi->restoreOverrideCursor();
}
}
}
}
App::Document* SelectionSingleton::getDocument(const char* pDocName) const
{
if (!Base::Tools::isNullOrEmpty(pDocName)) {
return App::GetApplication().getDocument(pDocName);
}
else {
return App::GetApplication().getActiveDocument();
}
}
int SelectionSingleton::disableCommandLog()
{
if (!logDisabled) {
logHasSelection = hasSelection();
}
return ++logDisabled;
}
int SelectionSingleton::enableCommandLog(bool silent)
{
--logDisabled;
if (!logDisabled && !silent) {
auto manager = Application::Instance->macroManager();
if (!hasSelection()) {
if (logHasSelection) {
manager->addLine(MacroManager::Cmt, "Gui.Selection.clearSelection()");
}
}
else {
for (auto& sel : _SelList) {
sel.log();
}
}
}
return logDisabled;
}
void SelectionSingleton::_SelObj::log(bool remove, bool clearPreselect)
{
if (logged && !remove) {
return;
}
logged = true;
std::ostringstream ss;
ss << "Gui.Selection." << (remove ? "removeSelection" : "addSelection") << "('" << DocName
<< "','" << FeatName << "'";
if (!SubName.empty()) {
if (!elementName.oldName.empty() && !elementName.newName.empty()) {
ss << ",'" << SubName.substr(0, SubName.size() - elementName.newName.size())
<< elementName.oldName << "'";
}
else {
ss << ",'" << SubName << "'";
}
}
if (!remove && (x || y || z || !clearPreselect)) {
if (SubName.empty()) {
ss << ",''";
}
ss << ',' << x << ',' << y << ',' << z;
if (!clearPreselect) {
ss << ",False";
}
}
ss << ')';
Application::Instance->macroManager()->addLine(MacroManager::Cmt, ss.str().c_str());
}
bool SelectionSingleton::addSelection(
const char* pDocName,
const char* pObjectName,
const char* pSubName,
float x,
float y,
float z,
const std::vector<SelObj>* pickedList,
bool clearPreselect
)
{
if (pickedList) {
_PickedList.clear();
for (const auto& sel : *pickedList) {
_PickedList.emplace_back();
auto& s = _PickedList.back();
s.DocName = sel.DocName;
s.FeatName = sel.FeatName;
s.SubName = sel.SubName;
s.TypeName = sel.TypeName;
s.pObject = sel.pObject;
s.pDoc = sel.pDoc;
s.x = sel.x;
s.y = sel.y;
s.z = sel.z;
}
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
_SelObj temp;
int ret = checkSelection(pDocName, pObjectName, pSubName, ResolveMode::NoResolve, temp);
if (ret != 0) {
return false;
}
temp.x = x;
temp.y = y;
temp.z = z;
// check for a Selection Gate
if (ActiveGate) {
const char* subelement = nullptr;
auto pObject
= getObjectOfType(temp, App::DocumentObject::getClassTypeId(), gateResolve, &subelement);
if (!ActiveGate->allow(pObject ? pObject->getDocument() : temp.pDoc, pObject, subelement)) {
if (getMainWindow()) {
QString msg;
if (ActiveGate->notAllowedReason.length() > 0) {
msg = QObject::tr(ActiveGate->notAllowedReason.c_str());
}
else {
msg = QCoreApplication::translate(
"SelectionFilter",
"Selection not allowed by filter"
);
}
getMainWindow()->showMessage(msg);
Gui::MDIView* mdi = Gui::Application::Instance->activeDocument()->getActiveView();
mdi->setOverrideCursor(Qt::ForbiddenCursor);
}
ActiveGate->notAllowedReason.clear();
QApplication::beep();
return false;
}
}
if (!logDisabled) {
temp.log(false, clearPreselect);
}
_SelList.push_back(temp);
_SelStackForward.clear();
if (clearPreselect) {
rmvPreselect();
}
SelectionChanges Chng(
SelectionChanges::AddSelection,
temp.DocName,
temp.FeatName,
temp.SubName,
temp.TypeName,
x,
y,
z
);
FC_LOG(
"Add Selection " << Chng.pDocName << '#' << Chng.pObjectName << '.' << Chng.pSubName << " ("
<< x << ", " << y << ", " << z << ')'
);
notify(std::move(Chng));
getMainWindow()->updateActions();
rmvPreselect(true);
// There is a possibility that some observer removes or clears selection
// inside signal handler, hence the check here
return isSelected(temp.DocName.c_str(), temp.FeatName.c_str(), temp.SubName.c_str());
}
void SelectionSingleton::selStackPush(bool clearForward, bool overwrite)
{
static int stackSize;
if (!stackSize) {
stackSize = App::GetApplication()
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/View")
->GetInt("SelectionStackSize", 100);
}
if (clearForward) {
_SelStackForward.clear();
}
if (_SelList.empty()) {
return;
}
if ((int)_SelStackBack.size() >= stackSize) {
_SelStackBack.pop_front();
}
SelStackItem item;
for (auto& sel : _SelList) {
item.emplace(sel.DocName.c_str(), sel.FeatName.c_str(), sel.SubName.c_str());
}
if (!_SelStackBack.empty() && _SelStackBack.back() == item) {
return;
}
if (!overwrite || _SelStackBack.empty()) {
_SelStackBack.emplace_back();
}
_SelStackBack.back() = std::move(item);
}
void SelectionSingleton::selStackGoBack(int count)
{
if ((int)_SelStackBack.size() < count) {
count = _SelStackBack.size();
}
if (count <= 0) {
return;
}
if (!_SelList.empty()) {
selStackPush(false, true);
clearCompleteSelection();
}
else {
--count;
}
for (int i = 0; i < count; ++i) {
_SelStackForward.push_front(std::move(_SelStackBack.back()));
_SelStackBack.pop_back();
}
std::deque<SelStackItem> tmpStack;
_SelStackForward.swap(tmpStack);
while (!_SelStackBack.empty()) {
bool found = false;
for (auto& sobjT : _SelStackBack.back()) {
if (sobjT.getSubObject()) {
addSelection(
sobjT.getDocumentName().c_str(),
sobjT.getObjectName().c_str(),
sobjT.getSubName().c_str()
);
found = true;
}
}
if (found) {
break;
}
tmpStack.push_front(std::move(_SelStackBack.back()));
_SelStackBack.pop_back();
}
_SelStackForward = std::move(tmpStack);
getMainWindow()->updateActions();
}
void SelectionSingleton::selStackGoForward(int count)
{
if ((int)_SelStackForward.size() < count) {
count = _SelStackForward.size();
}
if (count <= 0) {
return;
}
if (!_SelList.empty()) {
selStackPush(false, true);
clearCompleteSelection();
}
for (int i = 0; i < count; ++i) {
_SelStackBack.push_back(_SelStackForward.front());
_SelStackForward.pop_front();
}
std::deque<SelStackItem> tmpStack;
_SelStackForward.swap(tmpStack);
while (true) {
bool found = false;
for (auto& sobjT : _SelStackBack.back()) {
if (sobjT.getSubObject()) {
addSelection(
sobjT.getDocumentName().c_str(),
sobjT.getObjectName().c_str(),
sobjT.getSubName().c_str()
);
found = true;
}
}
if (found || tmpStack.empty()) {
break;
}
_SelStackBack.push_back(tmpStack.front());
tmpStack.pop_front();
}
_SelStackForward = std::move(tmpStack);
getMainWindow()->updateActions();
}
std::vector<SelectionObject> SelectionSingleton::selStackGet(
const char* pDocName,
ResolveMode resolve,
int index
) const
{
const SelStackItem* item = nullptr;
if (index >= 0) {
if (index >= (int)_SelStackBack.size()) {
return {};
}
item = &_SelStackBack[_SelStackBack.size() - 1 - index];
}
else {
index = -index - 1;
if (index >= (int)_SelStackForward.size()) {
return {};
}
item = &_SelStackBack[_SelStackForward.size() - 1 - index];
}
std::list<_SelObj> selList;
for (auto& sobjT : *item) {
_SelObj sel;
if (checkSelection(
sobjT.getDocumentName().c_str(),
sobjT.getObjectName().c_str(),
sobjT.getSubName().c_str(),
ResolveMode::NoResolve,
sel,
&selList
)
== 0) {
selList.push_back(sel);
}
}
return getObjectList(pDocName, App::DocumentObject::getClassTypeId(), selList, resolve);
}
bool SelectionSingleton::addSelections(
const char* pDocName,
const char* pObjectName,
const std::vector<std::string>& pSubNames
)
{
if (!_PickedList.empty()) {
_PickedList.clear();
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
bool update = false;
for (const auto& pSubName : pSubNames) {
_SelObj temp;
int ret = checkSelection(pDocName, pObjectName, pSubName.c_str(), ResolveMode::NoResolve, temp);
if (ret != 0) {
continue;
}
temp.x = 0;
temp.y = 0;
temp.z = 0;
_SelList.push_back(temp);
_SelStackForward.clear();
SelectionChanges Chng(
SelectionChanges::AddSelection,
temp.DocName,
temp.FeatName,
temp.SubName,
temp.TypeName
);
FC_LOG("Add Selection " << Chng.pDocName << '#' << Chng.pObjectName << '.' << Chng.pSubName);
notify(std::move(Chng));
update = true;
}
if (update) {
getMainWindow()->updateActions();
}
return true;
}
bool SelectionSingleton::updateSelection(
bool show,
const char* pDocName,
const char* pObjectName,
const char* pSubName
)
{
if (!pDocName || !pObjectName) {
return false;
}
if (!pSubName) {
pSubName = "";
}
if (DocName == pDocName && FeatName == pObjectName && SubName == pSubName) {
if (show) {
FC_TRACE("preselect signal");
notify(SelectionChanges(SelectionChanges::SetPreselectSignal, DocName, FeatName, SubName));
}
else {
rmvPreselect();
}
}
auto pDoc = getDocument(pDocName);
if (!pDoc) {
return false;
}
auto pObject = pDoc->getObject(pObjectName);
if (!pObject) {
return false;
}
if (!isSelected(pObject, pSubName, ResolveMode::NoResolve)) {
return false;
}
SelectionChanges Chng(
show ? SelectionChanges::ShowSelection : SelectionChanges::HideSelection,
pDocName,
pObjectName,
pSubName,
pObject->getTypeId().getName()
);
FC_LOG("Update Selection " << Chng.pDocName << '#' << Chng.pObjectName << '.' << Chng.pSubName);
notify(std::move(Chng));
return true;
}
bool SelectionSingleton::addSelection(const SelectionObject& obj, bool clearPreselect)
{
const std::vector<std::string>& subNames = obj.getSubNames();
const std::vector<Base::Vector3d> points = obj.getPickedPoints();
if (!subNames.empty() && subNames.size() == points.size()) {
bool ok = true;
for (std::size_t i = 0; i < subNames.size(); i++) {
const std::string& name = subNames[i];
const Base::Vector3d& pnt = points[i];
ok &= addSelection(
obj.getDocName(),
obj.getFeatName(),
name.c_str(),
static_cast<float>(pnt.x),
static_cast<float>(pnt.y),
static_cast<float>(pnt.z),
nullptr,
clearPreselect
);
}
return ok;
}
else if (!subNames.empty()) {
bool ok = true;
for (const std::string& name : subNames) {
ok &= addSelection(obj.getDocName(), obj.getFeatName(), name.c_str());
}
return ok;
}
else {
return addSelection(obj.getDocName(), obj.getFeatName());
}
}
void SelectionSingleton::rmvSelection(
const char* pDocName,
const char* pObjectName,
const char* pSubName,
const std::vector<SelObj>* pickedList
)
{
if (pickedList) {
_PickedList.clear();
for (const auto& sel : *pickedList) {
_PickedList.emplace_back();
auto& s = _PickedList.back();
s.DocName = sel.DocName;
s.FeatName = sel.FeatName;
s.SubName = sel.SubName;
s.TypeName = sel.TypeName;
s.pObject = sel.pObject;
s.pDoc = sel.pDoc;
s.x = sel.x;
s.y = sel.y;
s.z = sel.z;
}
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
if (!pDocName) {
return;
}
_SelObj temp;
int ret = checkSelection(pDocName, pObjectName, pSubName, ResolveMode::NoResolve, temp);
if (ret < 0) {
return;
}
std::vector<SelectionChanges> changes;
for (auto It = _SelList.begin(), ItNext = It; It != _SelList.end(); It = ItNext) {
++ItNext;
if (It->DocName != temp.DocName || It->FeatName != temp.FeatName) {
continue;
}
// if no subname is specified, remove all subobjects of the matching object
if (!temp.SubName.empty()) {
// otherwise, match subojects with common prefix, separated by '.'
if (!boost::starts_with(It->SubName, temp.SubName)
|| (It->SubName.length() != temp.SubName.length()
&& It->SubName[temp.SubName.length() - 1] != '.')) {
continue;
}
}
It->log(true);
changes.emplace_back(
SelectionChanges::RmvSelection,
It->DocName,
It->FeatName,
It->SubName,
It->TypeName
);
// destroy the _SelObj item
_SelList.erase(It);
}
// NOTE: It can happen that there are nested calls of rmvSelection()
// so that it's not safe to invoke the notifications inside the loop
// as this can invalidate the iterators and thus leads to undefined
// behaviour.
// So, the notification is done after the loop, see also #0003469
if (!changes.empty()) {
for (auto& Chng : changes) {
FC_LOG(
"Rmv Selection " << Chng.pDocName << '#' << Chng.pObjectName << '.' << Chng.pSubName
);
notify(std::move(Chng));
}
getMainWindow()->updateActions();
}
}
struct SelInfo
{
std::string DocName;
std::string FeatName;
std::string SubName;
SelInfo(const std::string& docName, const std::string& featName, const std::string& subName)
: DocName(docName)
, FeatName(featName)
, SubName(subName)
{}
};
void SelectionSingleton::setVisible(VisibleState vis)
{
std::set<std::pair<App::DocumentObject*, App::DocumentObject*>> filter;
int visible;
switch (vis) {
case VisShow:
visible = 1;
break;
case VisToggle:
visible = -1;
break;
default:
visible = 0;
}
// Copy the selection in case it changes during this function
std::vector<SelInfo> sels;
sels.reserve(_SelList.size());
for (auto& sel : _SelList) {
if (sel.DocName.empty() || sel.FeatName.empty() || !sel.pObject) {
continue;
}
sels.emplace_back(sel.DocName, sel.FeatName, sel.SubName);
}
for (auto& sel : sels) {
App::Document* doc = App::GetApplication().getDocument(sel.DocName.c_str());
if (!doc) {
continue;
}
App::DocumentObject* obj = doc->getObject(sel.FeatName.c_str());
if (!obj) {
continue;
}
// get parent object
App::DocumentObject* parent = nullptr;
std::string elementName;
obj = obj->resolve(sel.SubName.c_str(), &parent, &elementName);
if (!obj || !obj->isAttachedToDocument() || (parent && !parent->isAttachedToDocument())) {
continue;
}
// try call parent object's setElementVisible
if (parent) {
// prevent setting the same object visibility more than once
if (!filter.insert(std::make_pair(obj, parent)).second) {
continue;
}
int visElement = parent->isElementVisible(elementName.c_str());
if (visElement >= 0) {
if (visElement > 0) {
visElement = 1;
}
if (visible >= 0) {
if (visElement == visible) {
continue;
}
visElement = visible;
}
else {
visElement = !visElement;
}
if (!visElement) {
updateSelection(
false,
sel.DocName.c_str(),
sel.FeatName.c_str(),
sel.SubName.c_str()
);
}
parent->setElementVisible(elementName.c_str(), visElement ? true : false);
if (visElement) {
updateSelection(true, sel.DocName.c_str(), sel.FeatName.c_str(), sel.SubName.c_str());
}
continue;
}
// Fall back to direct object visibility setting
}
if (!filter.insert(std::make_pair(obj, static_cast<App::DocumentObject*>(nullptr))).second) {
continue;
}
auto vp = Application::Instance->getViewProvider(obj);
if (vp) {
bool visObject;
if (visible >= 0) {
visObject = visible ? true : false;
}
else {
visObject = !vp->isShow();
}
if (visObject) {
vp->show();
updateSelection(
visObject,
sel.DocName.c_str(),
sel.FeatName.c_str(),
sel.SubName.c_str()
);
}
else {
updateSelection(
visObject,
sel.DocName.c_str(),
sel.FeatName.c_str(),
sel.SubName.c_str()
);
vp->hide();
}
}
}
}
void SelectionSingleton::setSelection(const char* pDocName, const std::vector<App::DocumentObject*>& sel)
{
App::Document* pcDoc;
pcDoc = getDocument(pDocName);
if (!pcDoc) {
return;
}
if (!_PickedList.empty()) {
_PickedList.clear();
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
bool touched = false;
for (auto obj : sel) {
if (!obj || !obj->isAttachedToDocument()) {
continue;
}
_SelObj temp;
int ret
= checkSelection(pDocName, obj->getNameInDocument(), nullptr, ResolveMode::NoResolve, temp);
if (ret != 0) {
continue;
}
touched = true;
_SelList.push_back(temp);
}
if (touched) {
_SelStackForward.clear();
notify(SelectionChanges(SelectionChanges::SetSelection, pDocName));
getMainWindow()->updateActions();
}
}
void SelectionSingleton::clearSelection(const char* pDocName, bool clearPreSelect)
{
// Because the introduction of external editing, it is best to make
// clearSelection(0) behave as clearCompleteSelection(), which is the same
// behavior of python Selection.clearSelection(None)
if (!pDocName || !pDocName[0] || strcmp(pDocName, "*") == 0) {
clearCompleteSelection(clearPreSelect);
return;
}
if (!_PickedList.empty()) {
_PickedList.clear();
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
App::Document* pDoc;
pDoc = getDocument(pDocName);
if (pDoc) {
std::string docName = pDocName;
if (clearPreSelect && DocName == docName) {
rmvPreselect();
}
bool touched = false;
for (auto it = _SelList.begin(); it != _SelList.end();) {
if (it->DocName == docName) {
touched = true;
it = _SelList.erase(it);
}
else {
++it;
}
}
if (!touched) {
return;
}
if (!logDisabled) {
std::ostringstream ss;
ss << "Gui.Selection.clearSelection('" << docName << "'";
if (!clearPreSelect) {
ss << ", False";
}
ss << ')';
Application::Instance->macroManager()->addLine(MacroManager::Cmt, ss.str().c_str());
}
notify(SelectionChanges(SelectionChanges::ClrSelection, docName.c_str()));
getMainWindow()->updateActions();
}
}
void SelectionSingleton::clearCompleteSelection(bool clearPreSelect)
{
if (!_PickedList.empty()) {
_PickedList.clear();
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
if (clearPreSelect) {
rmvPreselect();
}
if (_SelList.empty()) {
return;
}
if (!logDisabled) {
Application::Instance->macroManager()->addLine(
MacroManager::Cmt,
clearPreSelect ? "Gui.Selection.clearSelection()" : "Gui.Selection.clearSelection(False)"
);
}
// Send the clear selection notification to all view providers associated with the
// objects being deselected.
std::set<ViewProvider*> viewProviders;
for (_SelObj& sel : _SelList) {
if (auto vp = Application::Instance->getViewProvider(sel.pObject)) {
viewProviders.insert(vp);
}
}
for (auto& vp : viewProviders) {
SelectionChanges Chng(SelectionChanges::ClrSelection);
vp->onSelectionChanged(Chng);
}
_SelList.clear();
SelectionChanges Chng(SelectionChanges::ClrSelection);
FC_LOG("Clear selection");
notify(std::move(Chng));
getMainWindow()->updateActions();
}
bool SelectionSingleton::isSelected(
const char* pDocName,
const char* pObjectName,
const char* pSubName,
ResolveMode resolve
) const
{
_SelObj sel;
return checkSelection(pDocName, pObjectName, pSubName, resolve, sel, &_SelList) > 0;
}
bool SelectionSingleton::isSelected(
App::DocumentObject* pObject,
const char* pSubName,
ResolveMode resolve
) const
{
if (!pObject || !pObject->isAttachedToDocument() || !pObject->getDocument()) {
return false;
}
_SelObj sel;
return checkSelection(
pObject->getDocument()->getName(),
pObject->getNameInDocument(),
pSubName,
resolve,
sel,
&_SelList
)
> 0;
}
int SelectionSingleton::checkSelection(
const char* pDocName,
const char* pObjectName,
const char* pSubName,
ResolveMode resolve,
_SelObj& sel,
const std::list<_SelObj>* selList
) const
{
sel.pDoc = getDocument(pDocName);
if (!sel.pDoc) {
if (!selList) {
FC_ERR("Cannot find document");
}
return -1;
}
pDocName = sel.pDoc->getName();
sel.DocName = pDocName;
if (pObjectName) {
sel.pObject = sel.pDoc->getObject(pObjectName);
}
else {
sel.pObject = nullptr;
}
if (!sel.pObject) {
if (!selList) {
FC_ERR("Object not found");
}
return -1;
}
if (sel.pObject->testStatus(App::ObjectStatus::Remove)) {
return -1;
}
if (pSubName) {
sel.SubName = pSubName;
}
if (resolve == ResolveMode::NoResolve) {
TreeWidget::checkTopParent(sel.pObject, sel.SubName);
}
pSubName = !sel.SubName.empty() ? sel.SubName.c_str() : nullptr;
sel.FeatName = sel.pObject->getNameInDocument();
sel.TypeName = sel.pObject->getTypeId().getName();
const char* element = nullptr;
sel.pResolvedObject = App::GeoFeature::resolveElement(
sel.pObject,
pSubName,
sel.elementName,
false,
App::GeoFeature::Normal,
nullptr,
&element
);
if (!sel.pResolvedObject) {
if (!selList) {
FC_ERR(
"Sub-object " << sel.DocName << '#' << sel.FeatName << '.' << sel.SubName << " not found"
);
}
return -1;
}
if (sel.pResolvedObject->testStatus(App::ObjectStatus::Remove)) {
return -1;
}
std::string subname;
std::string prefix;
if (pSubName && element) {
prefix = std::string(pSubName, element - pSubName);
if (!sel.elementName.newName.empty()) {
// make sure the selected sub name is a new style if available
subname = prefix + sel.elementName.newName;
pSubName = subname.c_str();
sel.SubName = subname;
}
}
if (!selList) {
selList = &_SelList;
}
if (!pSubName) {
pSubName = "";
}
for (auto& s : *selList) {
if (s.DocName == pDocName && s.FeatName == sel.FeatName) {
if (s.SubName == pSubName) {
return 1;
}
if (resolve > ResolveMode::OldStyleElement && boost::starts_with(s.SubName, prefix)) {
return 1;
}
}
}
if (resolve == ResolveMode::OldStyleElement) {
for (auto& s : *selList) {
if (s.pResolvedObject != sel.pResolvedObject) {
continue;
}
if (!pSubName[0]) {
return 1;
}
if (!s.elementName.newName.empty()) {
if (s.elementName.newName == sel.elementName.newName) {
return 1;
}
}
else if (s.SubName == sel.elementName.oldName) {
return 1;
}
}
}
return 0;
}
const char* SelectionSingleton::getSelectedElement(App::DocumentObject* obj, const char* pSubName) const
{
if (!obj) {
return nullptr;
}
for (list<_SelObj>::const_iterator It = _SelList.begin(); It != _SelList.end(); ++It) {
if (It->pObject == obj) {
auto len = It->SubName.length();
if (!len) {
return "";
}
if (pSubName && strncmp(pSubName, It->SubName.c_str(), It->SubName.length()) == 0) {
if (pSubName[len] == 0 || pSubName[len - 1] == '.') {
return It->SubName.c_str();
}
}
}
}
return nullptr;
}
void SelectionSingleton::slotDeletedObject(const App::DocumentObject& Obj)
{
if (!Obj.isAttachedToDocument()) {
return;
}
// For safety reason, don't bother checking
rmvPreselect();
// Remove also from the selection, if selected
// We don't walk down the hierarchy for each selection, so there may be stray selection
std::vector<SelectionChanges> changes;
for (auto it = _SelList.begin(), itNext = it; it != _SelList.end(); it = itNext) {
++itNext;
if (it->pResolvedObject == &Obj || it->pObject == &Obj) {
changes.emplace_back(
SelectionChanges::RmvSelection,
it->DocName,
it->FeatName,
it->SubName,
it->TypeName
);
_SelList.erase(it);
}
}
if (!changes.empty()) {
for (auto& Chng : changes) {
FC_LOG(
"Rmv Selection " << Chng.pDocName << '#' << Chng.pObjectName << '.' << Chng.pSubName
);
notify(std::move(Chng));
}
getMainWindow()->updateActions();
}
if (!_PickedList.empty()) {
bool changed = false;
for (auto it = _PickedList.begin(), itNext = it; it != _PickedList.end(); it = itNext) {
++itNext;
auto& sel = *it;
if (sel.DocName == Obj.getDocument()->getName()
&& sel.FeatName == Obj.getNameInDocument()) {
changed = true;
_PickedList.erase(it);
}
}
if (changed) {
notify(SelectionChanges(SelectionChanges::PickedListChanged));
}
}
}
void SelectionSingleton::setSelectionStyle(SelectionStyle selStyle)
{
selectionStyle = selStyle;
}
SelectionSingleton::SelectionStyle SelectionSingleton::getSelectionStyle()
{
return selectionStyle;
}
//**************************************************************************
// Construction/Destruction
/**
* A constructor.
* A more elaborate description of the constructor.
*/
SelectionSingleton::SelectionSingleton()
: CurrentPreselection(SelectionChanges::ClrSelection)
, selectionStyle(SelectionStyle::NormalSelection)
{
hx = 0;
hy = 0;
hz = 0;
ActiveGate = nullptr;
gateResolve = ResolveMode::OldStyleElement;
// NOLINTBEGIN
App::GetApplication().signalDeletedObject.connect(
std::bind(&Gui::SelectionSingleton::slotDeletedObject, this, sp::_1)
);
signalSelectionChanged.connect(
std::bind(&Gui::SelectionSingleton::slotSelectionChanged, this, sp::_1)
);
// NOLINTEND
}
/**
* A destructor.
* A more elaborate description of the destructor.
*/
SelectionSingleton::~SelectionSingleton() = default;
SelectionSingleton* SelectionSingleton::_pcSingleton = nullptr;
SelectionSingleton& SelectionSingleton::instance()
{
if (!_pcSingleton) {
_pcSingleton = new SelectionSingleton;
}
return *_pcSingleton;
}
void SelectionSingleton::destruct()
{
if (_pcSingleton) {
delete _pcSingleton;
}
_pcSingleton = nullptr;
}
//**************************************************************************
// Python stuff
// SelectionSingleton Methods // Methods structure
PyMethodDef SelectionSingleton::Methods[] = {
{"addSelection",
(PyCFunction)SelectionSingleton::sAddSelection,
METH_VARARGS,
"addSelection(docName, objName, subName, x=0, y=0, z=0, clear=True) -> None\n"
"addSelection(obj, subName, x=0, y=0, z=0, clear=True) -> None\n"
"addSelection(obj, subNames, clear=True) -> None\n"
"\n"
"Add an object to the selection.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"objName : str\n Name of the `App.DocumentObject` to add.\n"
"obj : App.DocumentObject\n Object to add.\n"
"subName : str\n Subelement name.\n"
"x : float\n Coordinate `x` of the point to pick.\n"
"y : float\n Coordinate `y` of the point to pick.\n"
"z : float\n Coordinate `z` of the point to pick.\n"
"subNames : list of str\n List of subelement names.\n"
"clear : bool\n Clear preselection."},
{"updateSelection",
(PyCFunction)SelectionSingleton::sUpdateSelection,
METH_VARARGS,
"updateSelection(show, obj, subName) -> None\n"
"\n"
"Update an object in the selection.\n"
"\n"
"show : bool\n Show or hide the selection.\n"
"obj : App.DocumentObject\n Object to update.\n"
"subName : str\n Name of the subelement to update."},
{"removeSelection",
(PyCFunction)SelectionSingleton::sRemoveSelection,
METH_VARARGS,
"removeSelection(obj, subName) -> None\n"
"removeSelection(docName, objName, subName) -> None\n"
"\n"
"Remove an object from the selection.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"objName : str\n Name of the `App.DocumentObject` to remove.\n"
"obj : App.DocumentObject\n Object to remove.\n"
"subName : str\n Name of the subelement to remove."},
{"clearSelection",
(PyCFunction)SelectionSingleton::sClearSelection,
METH_VARARGS,
"clearSelection(docName, clearPreSelect=True) -> None\n"
"clearSelection(clearPreSelect=True) -> None\n"
"\n"
"Clear the selection in the given document. If no document is\n"
"given the complete selection is cleared.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"clearPreSelect : bool\n Clear preselection."},
{"isSelected",
(PyCFunction)SelectionSingleton::sIsSelected,
METH_VARARGS,
"isSelected(obj, subName, resolve=ResolveMode.OldStyleElement) -> bool\n"
"\n"
"Check if a given object is selected.\n"
"\n"
"obj : App.DocumentObject\n Object to check.\n"
"subName : str\n Name of the subelement.\n"
"resolve : int\n Resolve subelement reference."},
{"setPreselection",
reinterpret_cast<PyCFunction>(reinterpret_cast<void (*)()>(SelectionSingleton::sSetPreselection)),
METH_VARARGS | METH_KEYWORDS,
"setPreselection(obj, subName, x=0, y=0, z=0, type=1) -> None\n"
"\n"
"Set preselected object.\n"
"\n"
"obj : App.DocumentObject\n Object to preselect.\n"
"subName : str\n Subelement name.\n"
"x : float\n Coordinate `x` of the point to preselect.\n"
"y : float\n Coordinate `y` of the point to preselect.\n"
"z : float\n Coordinate `z` of the point to preselect.\n"
"type : int"},
{"getPreselection",
(PyCFunction)SelectionSingleton::sGetPreselection,
METH_VARARGS,
"getPreselection() -> Gui.SelectionObject\n"
"\n"
"Get preselected object."},
{"clearPreselection",
(PyCFunction)SelectionSingleton::sRemPreselection,
METH_VARARGS,
"clearPreselection() -> None\n"
"\n"
"Clear the preselection."},
{"countObjectsOfType",
(PyCFunction)SelectionSingleton::sCountObjectsOfType,
METH_VARARGS,
"countObjectsOfType(type, docName, resolve=ResolveMode.OldStyleElement) -> int\n"
"\n"
"Get the number of selected objects. If no document name is given the\n"
"active document is used and '*' means all documents.\n"
"\n"
"type : str\n Object type id name.\n"
"docName : str\n Name of the `App.Document`.\n"
"resolve : int"},
{"getSelection",
(PyCFunction)SelectionSingleton::sGetSelection,
METH_VARARGS,
"getSelection(docName, resolve=ResolveMode.OldStyleElement, single=False) -> list\n"
"\n"
"Return a list of selected objects. If no document name is given\n"
"the active document is used and '*' means all documents.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"resolve : int\n Resolve the subname references.\n"
" 0: do not resolve, 1: resolve, 2: resolve with element map.\n"
"single : bool\n Only return if there is only one selection."},
{"getPickedList",
(PyCFunction)SelectionSingleton::sGetPickedList,
1,
"getPickedList(docName) -> list of Gui.SelectionObject\n"
"\n"
"Return a list of SelectionObjects generated by the last mouse click.\n"
"If no document name is given the active document is used and '*'\n"
"means all documents.\n"
"\n"
"docName : str\n Name of the `App.Document`."},
{"enablePickedList",
(PyCFunction)SelectionSingleton::sEnablePickedList,
METH_VARARGS,
"enablePickedList(enable=True) -> None\n"
"\n"
"Enable/disable pick list.\n"
"\n"
"enable : bool"},
{"getCompleteSelection",
(PyCFunction)SelectionSingleton::sGetCompleteSelection,
METH_VARARGS,
"getCompleteSelection(resolve=ResolveMode.OldStyleElement) -> list\n"
"\n"
"Return a list of selected objects across all documents.\n"
"\n"
"resolve : int"},
{"getSelectionEx",
(PyCFunction)SelectionSingleton::sGetSelectionEx,
METH_VARARGS,
"getSelectionEx(docName, resolve=ResolveMode.OldStyleElement, single=False) -> list of "
"Gui.SelectionObject\n"
"\n"
"Return a list of SelectionObjects. If no document name is given the\n"
"active document is used and '*' means all documents.\n"
"The SelectionObjects contain a variety of information about the selection,\n"
"e.g. subelement names.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"resolve : int\n Resolve the subname references.\n"
" 0: do not resolve, 1: resolve, 2: resolve with element map.\n"
"single : bool\n Only return if there is only one selection."},
{"getSelectionObject",
(PyCFunction)SelectionSingleton::sGetSelectionObject,
METH_VARARGS,
"getSelectionObject(docName, objName, subName, point) -> Gui.SelectionObject\n"
"\n"
"Return a SelectionObject.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"objName : str\n Name of the `App.DocumentObject` to select.\n"
"subName : str\n Subelement name.\n"
"point : tuple\n Coordinates of the point to pick."},
{"setSelectionStyle",
(PyCFunction)SelectionSingleton::sSetSelectionStyle,
METH_VARARGS,
"setSelectionStyle(selectionStyle) -> None\n"
"\n"
"Change the selection style. 0 for normal selection, 1 for greedy selection\n"
"\n"
"selectionStyle : int"},
{"addObserver",
(PyCFunction)SelectionSingleton::sAddSelObserver,
METH_VARARGS,
"addObserver(object, resolve=ResolveMode.OldStyleElement) -> None\n"
"\n"
"Install an observer.\n"
"\n"
"object : object\n Python object instance.\n"
"resolve : int"},
{"removeObserver",
(PyCFunction)SelectionSingleton::sRemSelObserver,
METH_VARARGS,
"removeObserver(object) -> None\n"
"\n"
"Uninstall an observer.\n"
"\n"
"object : object\n Python object instance."},
{"addSelectionGate",
(PyCFunction)SelectionSingleton::sAddSelectionGate,
METH_VARARGS,
"addSelectionGate(filter, resolve=ResolveMode.OldStyleElement) -> None\n"
"\n"
"Activate the selection gate.\n"
"The selection gate will prohibit all selections that do not match\n"
"the given selection criteria.\n"
"\n"
"filter : str, SelectionFilter, object\n"
"resolve : int\n"
"\n"
"Examples strings are:\n"
"Gui.Selection.addSelectionGate('SELECT Part::Feature SUBELEMENT Edge')\n"
"Gui.Selection.addSelectionGate('SELECT Robot::RobotObject')\n"
"\n"
"An instance of SelectionFilter can also be set:\n"
"filter = Gui.Selection.Filter('SELECT Part::Feature SUBELEMENT Edge')\n"
"Gui.Selection.addSelectionGate(filter)\n"
"\n"
"The most flexible approach is to write a selection gate class that\n"
"implements the method 'allow':\n"
"class Gate:\n"
" def allow(self,doc,obj,sub):\n"
" return (sub[0:4] == 'Face')\n"
"Gui.Selection.addSelectionGate(Gate())"},
{"removeSelectionGate",
(PyCFunction)SelectionSingleton::sRemoveSelectionGate,
METH_VARARGS,
"removeSelectionGate() -> None\n"
"\n"
"Remove the active selection gate."},
{"setVisible",
(PyCFunction)SelectionSingleton::sSetVisible,
METH_VARARGS,
"setVisible(visible=None) -> None\n"
"\n"
"Set visibility of all selection items.\n"
"\n"
"visible : bool, None\n If None, then toggle visibility."},
{"pushSelStack",
(PyCFunction)SelectionSingleton::sPushSelStack,
METH_VARARGS,
"pushSelStack(clearForward=True, overwrite=False) -> None\n"
"\n"
"Push current selection to stack.\n"
"\n"
"clearForward : bool\n Clear the forward selection stack.\n"
"overwrite : bool\n Overwrite the top back selection stack with current selection."},
{"hasSelection",
(PyCFunction)SelectionSingleton::sHasSelection,
METH_VARARGS,
"hasSelection(docName, resolve=ResolveMode.NoResolve) -> bool\n"
"\n"
"Check if there is any selection. If no document name is given,\n"
"checks selections in all documents.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"resolve : int"},
{"hasSubSelection",
(PyCFunction)SelectionSingleton::sHasSubSelection,
METH_VARARGS,
"hasSubSelection(docName, subElement=False) -> bool\n"
"\n"
"Check if there is any selection with subname. If no document name\n"
"is given the active document is used and '*' means all documents.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"subElement : bool"},
{"getSelectionFromStack",
(PyCFunction)SelectionSingleton::sGetSelectionFromStack,
METH_VARARGS,
"getSelectionFromStack(docName, resolve=ResolveMode.OldStyleElement, index=0) -> list of "
"Gui.SelectionObject\n"
"\n"
"Return SelectionObjects from selection stack. If no document name is given\n"
"the active document is used and '*' means all documents.\n"
"\n"
"docName : str\n Name of the `App.Document`.\n"
"resolve : int\n Resolve the subname references.\n"
" 0: do not resolve, 1: resolve, 2: resolve with element map.\n"
"index : int\n Select stack index.\n"
" 0: last pushed selection, > 0: trace back, < 0: trace forward."},
{nullptr, nullptr, 0, nullptr} /* Sentinel */
};
PyObject* SelectionSingleton::sAddSelection(PyObject* /*self*/, PyObject* args)
{
SelectionLogDisabler disabler(true);
PyObject* clearPreselect = Py_True;
char* objname;
char* docname;
char* subname = nullptr;
float x = 0, y = 0, z = 0;
if (PyArg_ParseTuple(args, "ss|sfffO!", &docname, &objname, &subname, &x, &y, &z, &PyBool_Type, &clearPreselect)) {
Selection()
.addSelection(docname, objname, subname, x, y, z, nullptr, Base::asBoolean(clearPreselect));
Py_Return;
}
PyErr_Clear();
PyObject* object;
subname = nullptr;
x = 0, y = 0, z = 0;
if (PyArg_ParseTuple(
args,
"O!|sfffO!",
&(App::DocumentObjectPy::Type),
&object,
&subname,
&x,
&y,
&z,
&PyBool_Type,
&clearPreselect
)) {
auto docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->isAttachedToDocument()) {
PyErr_SetString(Base::PyExc_FC_GeneralError, "Cannot check invalid object");
return nullptr;
}
Selection().addSelection(
docObj->getDocument()->getName(),
docObj->getNameInDocument(),
subname,
x,
y,
z,
nullptr,
Base::asBoolean(clearPreselect)
);
Py_Return;
}
PyErr_Clear();
PyObject* sequence;
if (PyArg_ParseTuple(
args,
"O!O|O!",
&(App::DocumentObjectPy::Type),
&object,
&sequence,
&PyBool_Type,
&clearPreselect
)) {
auto docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->isAttachedToDocument()) {
PyErr_SetString(Base::PyExc_FC_GeneralError, "Cannot check invalid object");
return nullptr;
}
try {
if (PyTuple_Check(sequence) || PyList_Check(sequence)) {
Py::Sequence list(sequence);
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
std::string subname = static_cast<std::string>(Py::String(*it));
Selection().addSelection(
docObj->getDocument()->getName(),
docObj->getNameInDocument(),
subname.c_str(),
0,
0,
0,
nullptr,
Base::asBoolean(clearPreselect)
);
}
Py_Return;
}
}
catch (const Py::Exception&) {
// do nothing here
}
}
PyErr_SetString(PyExc_ValueError, "type must be 'DocumentObject[,subname[,x,y,z]]' or 'DocumentObject, list or tuple of subnames'");
return nullptr;
}
PyObject* SelectionSingleton::sUpdateSelection(PyObject* /*self*/, PyObject* args)
{
PyObject* show;
PyObject* object;
char* subname = nullptr;
if (!PyArg_ParseTuple(
args,
"O!O!|s",
&PyBool_Type,
&show,
&(App::DocumentObjectPy::Type),
&object,
&subname
)) {
return nullptr;
}
auto docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->isAttachedToDocument()) {
PyErr_SetString(Base::PyExc_FC_GeneralError, "Cannot check invalid object");
return nullptr;
}
Selection().updateSelection(
Base::asBoolean(show),
docObj->getDocument()->getName(),
docObj->getNameInDocument(),
subname
);
Py_Return;
}
PyObject* SelectionSingleton::sRemoveSelection(PyObject* /*self*/, PyObject* args)
{
SelectionLogDisabler disabler(true);
char *docname, *objname;
char* subname = nullptr;
if (PyArg_ParseTuple(args, "ss|s", &docname, &objname, &subname)) {
Selection().rmvSelection(docname, objname, subname);
Py_Return;
}
PyErr_Clear();
PyObject* object;
subname = nullptr;
if (!PyArg_ParseTuple(args, "O!|s", &(App::DocumentObjectPy::Type), &object, &subname)) {
return nullptr;
}
auto docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->isAttachedToDocument()) {
PyErr_SetString(Base::PyExc_FC_GeneralError, "Cannot check invalid object");
return nullptr;
}
Selection().rmvSelection(docObj->getDocument()->getName(), docObj->getNameInDocument(), subname);
Py_Return;
}
PyObject* SelectionSingleton::sClearSelection(PyObject* /*self*/, PyObject* args)
{
SelectionLogDisabler disabler(true);
PyObject* clearPreSelect = Py_True;
char* documentName = nullptr;
if (!PyArg_ParseTuple(args, "|O!", &PyBool_Type, &clearPreSelect)) {
PyErr_Clear();
if (!PyArg_ParseTuple(args, "|sO!", &documentName, &PyBool_Type, &clearPreSelect)) {
return nullptr;
}
}
Selection().clearSelection(documentName, Base::asBoolean(clearPreSelect));
Py_Return;
}
namespace
{
ResolveMode toEnum(int value)
{
switch (value) {
case 0:
return ResolveMode::NoResolve;
case 1:
return ResolveMode::OldStyleElement;
case 2:
return ResolveMode::NewStyleElement;
case 3:
return ResolveMode::FollowLink;
default:
throw Base::ValueError("Wrong enum value");
}
}
} // namespace
PyObject* SelectionSingleton::sIsSelected(PyObject* /*self*/, PyObject* args)
{
PyObject* object;
char* subname = nullptr;
int resolve = 1;
if (!PyArg_ParseTuple(args, "O!|si", &(App::DocumentObjectPy::Type), &object, &subname, &resolve)) {
return nullptr;
}
try {
auto docObj = static_cast<App::DocumentObjectPy*>(object);
bool ok = Selection().isSelected(docObj->getDocumentObjectPtr(), subname, toEnum(resolve));
return Py_BuildValue("O", (ok ? Py_True : Py_False));
}
catch (const Base::Exception& e) {
e.setPyException();
return nullptr;
}
}
PyObject* SelectionSingleton::sCountObjectsOfType(PyObject* /*self*/, PyObject* args)
{
char* objecttype;
char* document = nullptr;
int resolve = 1;
if (!PyArg_ParseTuple(args, "s|si", &objecttype, &document, &resolve)) {
return nullptr;
}
try {
unsigned int count = Selection().countObjectsOfType(objecttype, document, toEnum(resolve));
return PyLong_FromLong(count);
}
catch (const Base::Exception& e) {
e.setPyException();
return nullptr;
}
}
PyObject* SelectionSingleton::sGetSelection(PyObject* /*self*/, PyObject* args)
{
char* documentName = nullptr;
int resolve = 1;
PyObject* single = Py_False;
if (!PyArg_ParseTuple(args, "|siO!", &documentName, &resolve, &PyBool_Type, &single)) {
return nullptr;
}
try {
std::vector<SelectionSingleton::SelObj> sel;
sel = Selection().getSelection(documentName, toEnum(resolve), Base::asBoolean(single));
std::set<App::DocumentObject*> noduplicates;
std::vector<App::DocumentObject*> selectedObjects; // keep the order of selection
Py::List list;
for (const auto& it : sel) {
if (noduplicates.insert(it.pObject).second) {
selectedObjects.push_back(it.pObject);
}
}
for (const auto& selectedObject : selectedObjects) {
list.append(Py::asObject(selectedObject->getPyObject()));
}
return Py::new_reference_to(list);
}
catch (const Base::Exception& e) {
e.setPyException();
return nullptr;
}
catch (Py::Exception&) {
return nullptr;
}
}
PyObject* SelectionSingleton::sEnablePickedList(PyObject* /*self*/, PyObject* args)
{
PyObject* enable = Py_True;
if (!PyArg_ParseTuple(args, "|O!", &PyBool_Type, &enable)) {
return nullptr;
}
Selection().enablePickedList(Base::asBoolean(enable));
Py_Return;
}
PyObject* SelectionSingleton::sSetPreselection(PyObject* /*self*/, PyObject* args, PyObject* kwd)
{
PyObject* object;
const char* subname = nullptr;
float x = 0, y = 0, z = 0;
int type = 1;
static const std::array<const char*, 7> kwlist {"obj", "subname", "x", "y", "z", "tp", nullptr};
if (Base::Wrapped_ParseTupleAndKeywords(
args,
kwd,
"O!|sfffi",
kwlist,
&(App::DocumentObjectPy::Type),
&object,
&subname,
&x,
&y,
&z,
&type
)) {
auto docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->isAttachedToDocument()) {
PyErr_SetString(Base::PyExc_FC_GeneralError, "Cannot check invalid object");
return nullptr;
}
Selection().setPreselect(
docObj->getDocument()->getName(),
docObj->getNameInDocument(),
subname,
x,
y,
z,
static_cast<SelectionChanges::MsgSource>(type)
);
Py_Return;
}
PyErr_SetString(PyExc_ValueError, "type must be 'DocumentObject[,subname[,x,y,z]]'");
return nullptr;
}
PyObject* SelectionSingleton::sGetPreselection(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
const SelectionChanges& sel = Selection().getPreselection();
SelectionObject obj(sel);
return obj.getPyObject();
}
PyObject* SelectionSingleton::sRemPreselection(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
Selection().rmvPreselect();
Py_Return;
}
PyObject* SelectionSingleton::sGetCompleteSelection(PyObject* /*self*/, PyObject* args)
{
int resolve = 1;
if (!PyArg_ParseTuple(args, "|i", &resolve)) {
return nullptr;
}
try {
std::vector<SelectionSingleton::SelObj> sel;
sel = Selection().getCompleteSelection(toEnum(resolve));
Py::List list;
for (const auto& it : sel) {
SelectionObject obj(SelectionChanges(
SelectionChanges::AddSelection,
it.DocName,
it.FeatName,
it.SubName,
it.TypeName,
it.x,
it.y,
it.z
));
list.append(Py::asObject(obj.getPyObject()));
}
return Py::new_reference_to(list);
}
catch (const Base::Exception& e) {
e.setPyException();
return nullptr;
}
catch (Py::Exception&) {
return nullptr;
}
}
PyObject* SelectionSingleton::sGetSelectionEx(PyObject* /*self*/, PyObject* args)
{
char* documentName = nullptr;
int resolve = 1;
PyObject* single = Py_False;
if (!PyArg_ParseTuple(args, "|siO!", &documentName, &resolve, &PyBool_Type, &single)) {
return nullptr;
}
try {
std::vector<SelectionObject> sel;
sel = Selection().getSelectionEx(
documentName,
App::DocumentObject::getClassTypeId(),
toEnum(resolve),
Base::asBoolean(single)
);
Py::List list;
for (auto& it : sel) {
list.append(Py::asObject(it.getPyObject()));
}
return Py::new_reference_to(list);
}
catch (const Base::Exception& e) {
e.setPyException();
return nullptr;
}
catch (Py::Exception&) {
return nullptr;
}
}
PyObject* SelectionSingleton::sGetPickedList(PyObject* /*self*/, PyObject* args)
{
char* documentName = nullptr;
if (!PyArg_ParseTuple(args, "|s", &documentName)) {
return nullptr;
}
std::vector<SelectionObject> sel;
sel = Selection().getPickedListEx(documentName);
try {
Py::List list;
for (auto& it : sel) {
list.append(Py::asObject(it.getPyObject()));
}
return Py::new_reference_to(list);
}
catch (Py::Exception&) {
return nullptr;
}
}
PyObject* SelectionSingleton::sGetSelectionObject(PyObject* /*self*/, PyObject* args)
{
char *docName, *objName, *subName;
PyObject* tuple = nullptr;
if (!PyArg_ParseTuple(args, "sss|O!", &docName, &objName, &subName, &PyTuple_Type, &tuple)) {
return nullptr;
}
try {
SelectionObject selObj;
selObj.DocName = docName;
selObj.FeatName = objName;
std::string sub = subName;
if (!sub.empty()) {
selObj.SubNames.push_back(sub);
if (tuple) {
Py::Tuple t(tuple);
double x = (double)Py::Float(t.getItem(0));
double y = (double)Py::Float(t.getItem(1));
double z = (double)Py::Float(t.getItem(2));
selObj.SelPoses.emplace_back(x, y, z);
}
}
return selObj.getPyObject();
}
catch (const Py::Exception&) {
return nullptr;
}
catch (const Base::Exception& e) {
e.setPyException();
return nullptr;
}
}
PyObject* SelectionSingleton::sSetSelectionStyle(PyObject* /*self*/, PyObject* args)
{
int selStyle = 0;
if (!PyArg_ParseTuple(args, "i", &selStyle)) {
return nullptr;
}
PY_TRY
{
Selection().setSelectionStyle(
selStyle == 0 ? SelectionStyle::NormalSelection : SelectionStyle::GreedySelection
);
Py_Return;
}
PY_CATCH;
}
PyObject* SelectionSingleton::sAddSelObserver(PyObject* /*self*/, PyObject* args)
{
PyObject* o;
int resolve = 1;
if (!PyArg_ParseTuple(args, "O|i", &o, &resolve)) {
return nullptr;
}
PY_TRY
{
SelectionObserverPython::addObserver(Py::Object(o), toEnum(resolve));
Py_Return;
}
PY_CATCH;
}
PyObject* SelectionSingleton::sRemSelObserver(PyObject* /*self*/, PyObject* args)
{
PyObject* o;
if (!PyArg_ParseTuple(args, "O", &o)) {
return nullptr;
}
PY_TRY
{
SelectionObserverPython::removeObserver(Py::Object(o));
Py_Return;
}
PY_CATCH;
}
PyObject* SelectionSingleton::sAddSelectionGate(PyObject* /*self*/, PyObject* args)
{
char* filter;
int resolve = 1;
if (PyArg_ParseTuple(args, "s|i", &filter, &resolve)) {
PY_TRY
{
Selection().addSelectionGate(new SelectionFilterGate(filter), toEnum(resolve));
Py_Return;
}
PY_CATCH;
}
PyErr_Clear();
PyObject* filterPy;
if (PyArg_ParseTuple(args, "O!|i", SelectionFilterPy::type_object(), &filterPy, resolve)) {
PY_TRY
{
Selection().addSelectionGate(
new SelectionFilterGatePython(SelectionFilterPy::cast(filterPy)),
toEnum(resolve)
);
Py_Return;
}
PY_CATCH;
}
PyErr_Clear();
PyObject* gate;
if (PyArg_ParseTuple(args, "O|i", &gate, &resolve)) {
PY_TRY
{
Selection().addSelectionGate(
new SelectionGatePython(Py::Object(gate, false)),
toEnum(resolve)
);
Py_Return;
}
PY_CATCH;
}
PyErr_SetString(PyExc_ValueError, "Argument is neither string nor SelectionFiler nor SelectionGate");
return nullptr;
}
PyObject* SelectionSingleton::sRemoveSelectionGate(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
PY_TRY
{
Selection().rmvSelectionGate();
Py_Return;
}
PY_CATCH;
}
PyObject* SelectionSingleton::sSetVisible(PyObject* /*self*/, PyObject* args)
{
PyObject* visible = Py_None;
if (!PyArg_ParseTuple(args, "|O", &visible)) {
return nullptr;
}
PY_TRY
{
VisibleState vis = VisToggle;
Base::PyTypeCheck(&visible, &PyBool_Type);
if (visible) {
vis = PyObject_IsTrue(visible) ? VisShow : VisHide;
}
Selection().setVisible(vis);
Py_Return;
}
PY_CATCH;
}
PyObject* SelectionSingleton::sPushSelStack(PyObject* /*self*/, PyObject* args)
{
PyObject* clear = Py_True;
PyObject* overwrite = Py_False;
if (!PyArg_ParseTuple(args, "|O!O!", &PyBool_Type, &clear, &PyBool_Type, &overwrite)) {
return nullptr;
}
Selection().selStackPush(Base::asBoolean(clear), Base::asBoolean(overwrite));
Py_Return;
}
PyObject* SelectionSingleton::sHasSelection(PyObject* /*self*/, PyObject* args)
{
const char* doc = nullptr;
int resolve = 0;
if (!PyArg_ParseTuple(args, "|si", &doc, &resolve)) {
return nullptr;
}
PY_TRY
{
bool ret;
if (doc || resolve > 0) {
ret = Selection().hasSelection(doc, toEnum(resolve));
}
else {
ret = Selection().hasSelection();
}
return Py::new_reference_to(Py::Boolean(ret));
}
PY_CATCH;
}
PyObject* SelectionSingleton::sHasSubSelection(PyObject* /*self*/, PyObject* args)
{
const char* doc = nullptr;
PyObject* subElement = Py_False;
if (!PyArg_ParseTuple(args, "|sO!", &doc, &PyBool_Type, &subElement)) {
return nullptr;
}
PY_TRY
{
return Py::new_reference_to(
Py::Boolean(Selection().hasSubSelection(doc, Base::asBoolean(subElement)))
);
}
PY_CATCH;
}
PyObject* SelectionSingleton::sGetSelectionFromStack(PyObject* /*self*/, PyObject* args)
{
char* documentName = nullptr;
int resolve = 1;
int index = 0;
if (!PyArg_ParseTuple(args, "|sii", &documentName, &resolve, &index)) {
return nullptr;
}
PY_TRY
{
Py::List list;
for (auto& sel : Selection().selStackGet(documentName, toEnum(resolve), index)) {
list.append(Py::asObject(sel.getPyObject()));
}
return Py::new_reference_to(list);
}
PY_CATCH;
}
bool SelectionSingleton::isClarifySelectionActive()
{
return clarifySelectionActive;
}
void SelectionSingleton::setClarifySelectionActive(bool active)
{
clarifySelectionActive = active;
}