Merge pull request #22029 from tetektoza/realthunder_multiselect_without_pie
Gui: Add a context menu to select obstructed items (from RT's fork Pick Geometry)
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
# include <Inventor/events/SoMouseButtonEvent.h>
|
||||
# include <Inventor/nodes/SoOrthographicCamera.h>
|
||||
# include <Inventor/nodes/SoPerspectiveCamera.h>
|
||||
# include <Inventor/SoPickedPoint.h>
|
||||
# include <QApplication>
|
||||
# include <QDialog>
|
||||
# include <QDomDocument>
|
||||
@@ -47,6 +48,7 @@
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/DocumentObjectGroup.h>
|
||||
#include <App/DocumentObserver.h>
|
||||
#include <App/GeoFeature.h>
|
||||
#include <App/GeoFeatureGroupExtension.h>
|
||||
#include <App/Part.h>
|
||||
@@ -73,6 +75,7 @@
|
||||
#include "OverlayManager.h"
|
||||
#include "SceneInspector.h"
|
||||
#include "Selection.h"
|
||||
#include "Selection/SelectionView.h"
|
||||
#include "SelectionObject.h"
|
||||
#include "SoFCOffscreenRenderer.h"
|
||||
#include "TextureMapping.h"
|
||||
@@ -3953,6 +3956,141 @@ bool StdCmdAlignToSelection::isActive()
|
||||
return getGuiApplication()->sendHasMsgToActiveView("AlignToSelection");
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_ClarifySelection
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_A(StdCmdClarifySelection)
|
||||
|
||||
StdCmdClarifySelection::StdCmdClarifySelection()
|
||||
: Command("Std_ClarifySelection")
|
||||
{
|
||||
sGroup = "View";
|
||||
sMenuText = QT_TR_NOOP("Clarify Selection");
|
||||
sToolTipText = QT_TR_NOOP("Displays a context menu at the mouse cursor to select overlapping "
|
||||
"or obstructed geometry in the 3D view.\n");
|
||||
sWhatsThis = "Std_ClarifySelection";
|
||||
sStatusTip = sToolTipText;
|
||||
sAccel = "G, G";
|
||||
eType = NoTransaction | AlterSelection;
|
||||
}
|
||||
|
||||
void StdCmdClarifySelection::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
|
||||
// Get the active view
|
||||
auto view3d = freecad_cast<View3DInventor*>(Application::Instance->activeView());
|
||||
if (!view3d) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto viewer = view3d->getViewer();
|
||||
if (!viewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
QWidget* widget = viewer->getGLWidget();
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we have a stored right-click position (context menu) or should use current cursor position (keyboard shortcut)
|
||||
SbVec2s point;
|
||||
auto& storedPosition = viewer->navigationStyle()->getRightClickPosition();
|
||||
if (storedPosition.has_value()) {
|
||||
point = storedPosition.value();
|
||||
} else {
|
||||
QPoint pos = QCursor::pos();
|
||||
QPoint local = widget->mapFromGlobal(pos);
|
||||
point = SbVec2s(static_cast<short>(local.x()),
|
||||
static_cast<short>(widget->height() - local.y() - 1));
|
||||
}
|
||||
|
||||
// Use ray picking to get all objects under cursor
|
||||
SoRayPickAction pickAction(viewer->getSoRenderManager()->getViewportRegion());
|
||||
pickAction.setPoint(point);
|
||||
|
||||
constexpr double defaultMultiplier = 5.0F;
|
||||
double clarifyRadiusMultiplier = App::GetApplication()
|
||||
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/View")
|
||||
->GetFloat("ClarifySelectionRadiusMultiplier", defaultMultiplier);
|
||||
|
||||
pickAction.setRadius(viewer->getPickRadius() * clarifyRadiusMultiplier);
|
||||
pickAction.setPickAll(static_cast<SbBool>(true)); // Get all objects under cursor
|
||||
pickAction.apply(viewer->getSoRenderManager()->getSceneGraph());
|
||||
|
||||
const SoPickedPointList& pplist = pickAction.getPickedPointList();
|
||||
if (pplist.getLength() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert picked points to PickData list
|
||||
std::vector<PickData> selections;
|
||||
|
||||
for (int i = 0; i < pplist.getLength(); ++i) {
|
||||
SoPickedPoint* pp = pplist[i];
|
||||
if (!pp || !pp->getPath()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ViewProvider* vp = viewer->getViewProviderByPath(pp->getPath());
|
||||
if (!vp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cast to ViewProviderDocumentObject to get the object
|
||||
auto vpDoc = freecad_cast<Gui::ViewProviderDocumentObject*>(vp);
|
||||
if (!vpDoc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
App::DocumentObject* obj = vpDoc->getObject();
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get element information - handle sub-objects like Assembly parts
|
||||
std::string elementName = vp->getElement(pp->getDetail());
|
||||
std::string subName;
|
||||
|
||||
// Try to get more detailed sub-object information
|
||||
bool hasSubObject = false;
|
||||
if (vp->getElementPicked(pp, subName)) {
|
||||
hasSubObject = true;
|
||||
}
|
||||
|
||||
// Create PickData with selection information
|
||||
PickData pickData {.obj = obj,
|
||||
.element = elementName,
|
||||
.docName = obj->getDocument()->getName(),
|
||||
.objName = obj->getNameInDocument(),
|
||||
.subName = hasSubObject ? subName : elementName};
|
||||
|
||||
selections.push_back(pickData);
|
||||
}
|
||||
|
||||
if (selections.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPoint globalPos;
|
||||
if (storedPosition.has_value()) {
|
||||
globalPos = widget->mapToGlobal(QPoint(point[0], widget->height() - point[1] - 1));
|
||||
} else {
|
||||
globalPos = QCursor::pos();
|
||||
}
|
||||
|
||||
// Use SelectionMenu to display and handle the pick menu
|
||||
SelectionMenu contextMenu(widget);
|
||||
contextMenu.doPick(selections, globalPos);
|
||||
}
|
||||
|
||||
bool StdCmdClarifySelection::isActive()
|
||||
{
|
||||
return qobject_cast<View3DInventor*>(getMainWindow()->activeWindow()) != nullptr;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Instantiation
|
||||
//===========================================================================
|
||||
@@ -3985,6 +4123,7 @@ void CreateViewStdCommands()
|
||||
rcCmdMgr.addCommand(new StdRecallWorkingView());
|
||||
rcCmdMgr.addCommand(new StdCmdViewGroup());
|
||||
rcCmdMgr.addCommand(new StdCmdAlignToSelection());
|
||||
rcCmdMgr.addCommand(new StdCmdClarifySelection());
|
||||
|
||||
rcCmdMgr.addCommand(new StdCmdViewExample1());
|
||||
rcCmdMgr.addCommand(new StdCmdViewExample2());
|
||||
|
||||
@@ -29,17 +29,22 @@
|
||||
#include <GL/gl.h>
|
||||
#endif
|
||||
#include <Inventor/elements/SoCacheElement.h>
|
||||
#include <algorithm>
|
||||
#endif
|
||||
|
||||
#include "So3DAnnotation.h"
|
||||
#include <Gui/Selection/Selection.h>
|
||||
|
||||
using namespace Gui;
|
||||
|
||||
SO_ELEMENT_SOURCE(SoDelayedAnnotationsElement);
|
||||
|
||||
bool SoDelayedAnnotationsElement::isProcessingDelayedPaths = false;
|
||||
|
||||
void SoDelayedAnnotationsElement::init(SoState* state)
|
||||
{
|
||||
SoElement::init(state);
|
||||
paths.clear();
|
||||
}
|
||||
|
||||
void SoDelayedAnnotationsElement::initClass()
|
||||
@@ -49,21 +54,64 @@ void SoDelayedAnnotationsElement::initClass()
|
||||
SO_ENABLE(SoGLRenderAction, SoDelayedAnnotationsElement);
|
||||
}
|
||||
|
||||
void SoDelayedAnnotationsElement::addDelayedPath(SoState* state, SoPath* path)
|
||||
void SoDelayedAnnotationsElement::addDelayedPath(SoState* state, SoPath* path, int priority)
|
||||
{
|
||||
auto elt = static_cast<SoDelayedAnnotationsElement*>(state->getElementNoPush(classStackIndex));
|
||||
|
||||
elt->paths.append(path);
|
||||
|
||||
// add to unified storage with specified priority (default = 0)
|
||||
elt->paths.emplace_back(path, priority);
|
||||
}
|
||||
|
||||
SoPathList SoDelayedAnnotationsElement::getDelayedPaths(SoState* state)
|
||||
{
|
||||
auto elt = static_cast<SoDelayedAnnotationsElement*>(state->getElementNoPush(classStackIndex));
|
||||
auto copy = elt->paths;
|
||||
|
||||
if (elt->paths.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// sort by priority (lower numbers render first)
|
||||
std::stable_sort(elt->paths.begin(), elt->paths.end(),
|
||||
[](const PriorityPath& a, const PriorityPath& b) {
|
||||
return a.priority < b.priority;
|
||||
});
|
||||
|
||||
SoPathList sortedPaths;
|
||||
for (const auto& priorityPath : elt->paths) {
|
||||
sortedPaths.append(priorityPath.path);
|
||||
}
|
||||
|
||||
// Clear storage
|
||||
elt->paths.clear();
|
||||
|
||||
return sortedPaths;
|
||||
}
|
||||
|
||||
elt->paths.truncate(0);
|
||||
|
||||
return copy;
|
||||
void SoDelayedAnnotationsElement::processDelayedPathsWithPriority(SoState* state, SoGLRenderAction* action)
|
||||
{
|
||||
auto elt = static_cast<SoDelayedAnnotationsElement*>(state->getElementNoPush(classStackIndex));
|
||||
|
||||
if (elt->paths.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stable_sort(elt->paths.begin(), elt->paths.end(),
|
||||
[](const PriorityPath& a, const PriorityPath& b) {
|
||||
return a.priority < b.priority;
|
||||
});
|
||||
|
||||
isProcessingDelayedPaths = true;
|
||||
|
||||
for (const auto& priorityPath : elt->paths) {
|
||||
SoPathList singlePath;
|
||||
singlePath.append(priorityPath.path);
|
||||
|
||||
action->apply(singlePath, TRUE);
|
||||
}
|
||||
|
||||
isProcessingDelayedPaths = false;
|
||||
|
||||
elt->paths.clear();
|
||||
}
|
||||
|
||||
SO_NODE_SOURCE(So3DAnnotation);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <Inventor/elements/SoElement.h>
|
||||
#include <Inventor/elements/SoSubElement.h>
|
||||
#include <FCGlobal.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
@@ -43,6 +44,15 @@ protected:
|
||||
SoDelayedAnnotationsElement& operator=(const SoDelayedAnnotationsElement& other) = default;
|
||||
SoDelayedAnnotationsElement& operator=(SoDelayedAnnotationsElement&& other) noexcept = default;
|
||||
|
||||
// internal structure to hold path with it's rendering
|
||||
// priority (lower renders first)
|
||||
struct PriorityPath {
|
||||
SoPath* path;
|
||||
int priority;
|
||||
|
||||
PriorityPath(SoPath* p, int pr = 0) : path(p), priority(pr) {}
|
||||
};
|
||||
|
||||
public:
|
||||
SoDelayedAnnotationsElement(const SoDelayedAnnotationsElement& other) = delete;
|
||||
SoDelayedAnnotationsElement(SoDelayedAnnotationsElement&& other) noexcept = delete;
|
||||
@@ -51,8 +61,13 @@ public:
|
||||
|
||||
static void initClass();
|
||||
|
||||
static void addDelayedPath(SoState* state, SoPath* path);
|
||||
static void addDelayedPath(SoState* state, SoPath* path, int priority = 0);
|
||||
|
||||
static SoPathList getDelayedPaths(SoState* state);
|
||||
|
||||
static void processDelayedPathsWithPriority(SoState* state, SoGLRenderAction* action);
|
||||
|
||||
static bool isProcessingDelayedPaths;
|
||||
|
||||
SbBool matches([[maybe_unused]] const SoElement* element) const override
|
||||
{
|
||||
@@ -64,7 +79,8 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SoPathList paths;
|
||||
private:
|
||||
std::vector<PriorityPath> paths;
|
||||
};
|
||||
|
||||
/*! @brief 3D Annotation Node - Annotation with depth buffer
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
#include "Navigation/NavigationStyle.h"
|
||||
#include "Navigation/NavigationStylePy.h"
|
||||
#include "Application.h"
|
||||
#include "Command.h"
|
||||
#include "Action.h"
|
||||
#include "Inventor/SoMouseWheelEvent.h"
|
||||
#include "MenuManager.h"
|
||||
#include "MouseSelection.h"
|
||||
@@ -1022,6 +1024,11 @@ SbVec3f NavigationStyle::getRotationCenter(SbBool& found) const
|
||||
return this->rotationCenter;
|
||||
}
|
||||
|
||||
std::optional<SbVec2s>& NavigationStyle::getRightClickPosition()
|
||||
{
|
||||
return rightClickPosition;
|
||||
}
|
||||
|
||||
void NavigationStyle::setRotationCenter(const SbVec3f& cnt)
|
||||
{
|
||||
this->rotationCenter = cnt;
|
||||
@@ -1932,9 +1939,39 @@ SbBool NavigationStyle::isPopupMenuEnabled() const
|
||||
return this->menuenabled;
|
||||
}
|
||||
|
||||
bool NavigationStyle::isNavigationStyleAction(QAction* action, QActionGroup* navMenuGroup) const
|
||||
{
|
||||
return action && navMenuGroup->actions().indexOf(action) >= 0 && action->isChecked();
|
||||
}
|
||||
|
||||
QWidget* NavigationStyle::findView3DInventorWidget() const
|
||||
{
|
||||
QWidget* widget = viewer->getWidget();
|
||||
while (widget && !widget->inherits("Gui::View3DInventor")) {
|
||||
widget = widget->parentWidget();
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
void NavigationStyle::applyNavigationStyleChange(QAction* selectedAction)
|
||||
{
|
||||
QByteArray navigationStyleTypeName = selectedAction->data().toByteArray();
|
||||
QWidget* view3DWidget = findView3DInventorWidget();
|
||||
|
||||
if (view3DWidget) {
|
||||
Base::Type newNavigationStyle = Base::Type::fromName(navigationStyleTypeName.constData());
|
||||
if (newNavigationStyle != this->getTypeId()) {
|
||||
QEvent* navigationChangeEvent = new NavigationStyleEvent(newNavigationStyle);
|
||||
QApplication::postEvent(view3DWidget, navigationChangeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationStyle::openPopupMenu(const SbVec2s& position)
|
||||
{
|
||||
Q_UNUSED(position);
|
||||
// store the right-click position for potential use by Clarify Selection
|
||||
rightClickPosition = position;
|
||||
|
||||
// ask workbenches and view provider, ...
|
||||
MenuItem view;
|
||||
Gui::Application::Instance->setupContextMenu("View", &view);
|
||||
@@ -1953,6 +1990,7 @@ void NavigationStyle::openPopupMenu(const SbVec2s& position)
|
||||
QAction *item = navMenuGroup->addAction(name);
|
||||
navMenu->addAction(item);
|
||||
item->setCheckable(true);
|
||||
item->setData(QByteArray(style.first.getName()));
|
||||
|
||||
if (const Base::Type item_style = style.first; item_style != this->getTypeId()) {
|
||||
auto triggeredFun = [this, item_style](){
|
||||
@@ -1970,7 +2008,58 @@ void NavigationStyle::openPopupMenu(const SbVec2s& position)
|
||||
item->setChecked(true);
|
||||
}
|
||||
|
||||
contextMenu->popup(QCursor::pos());
|
||||
// Add Clarify Selection option if there are objects under cursor
|
||||
bool separator = false;
|
||||
auto posAction = !contextMenu->actions().empty() ? contextMenu->actions().front() : nullptr;
|
||||
|
||||
// Get picked objects at position
|
||||
SoRayPickAction rp(viewer->getSoRenderManager()->getViewportRegion());
|
||||
rp.setPoint(position);
|
||||
rp.setRadius(viewer->getPickRadius());
|
||||
rp.setPickAll(true);
|
||||
rp.apply(viewer->getSoRenderManager()->getSceneGraph());
|
||||
|
||||
const SoPickedPointList& pplist = rp.getPickedPointList();
|
||||
QAction *pickAction = nullptr;
|
||||
|
||||
if (pplist.getLength() > 0) {
|
||||
separator = true;
|
||||
if (auto cmd =
|
||||
Application::Instance->commandManager().getCommandByName("Std_ClarifySelection")) {
|
||||
pickAction = new QAction(cmd->getAction()->text(), contextMenu);
|
||||
pickAction->setShortcut(cmd->getAction()->shortcut());
|
||||
} else {
|
||||
pickAction = new QAction(QObject::tr("Clarify Selection"), contextMenu);
|
||||
}
|
||||
if (posAction) {
|
||||
contextMenu->insertAction(posAction, pickAction);
|
||||
contextMenu->insertSeparator(posAction);
|
||||
} else {
|
||||
contextMenu->addAction(pickAction);
|
||||
}
|
||||
}
|
||||
|
||||
if (separator && posAction)
|
||||
contextMenu->insertSeparator(posAction);
|
||||
|
||||
QAction* selectedAction = contextMenu->exec(QCursor::pos());
|
||||
|
||||
// handle navigation style change if user selected a navigation style option
|
||||
if (selectedAction && isNavigationStyleAction(selectedAction, navMenuGroup)) {
|
||||
applyNavigationStyleChange(selectedAction);
|
||||
rightClickPosition.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (pickAction && selectedAction == pickAction) {
|
||||
// Execute the Clarify Selection command at this position
|
||||
auto cmd = Application::Instance->commandManager().getCommandByName("Std_ClarifySelection");
|
||||
if (cmd && cmd->isActive()) {
|
||||
cmd->invoke(0); // required placeholder value - we don't use group command
|
||||
}
|
||||
}
|
||||
|
||||
rightClickPosition.reset();
|
||||
}
|
||||
|
||||
PyObject* NavigationStyle::getPyObject()
|
||||
|
||||
@@ -36,11 +36,13 @@
|
||||
#include <Inventor/events/SoEvents.h>
|
||||
|
||||
#include <QEvent>
|
||||
#include <QAction>
|
||||
#include <Base/BaseClass.h>
|
||||
#include <Base/SmartPtrPy.h>
|
||||
#include <Gui/Namespace.h>
|
||||
#include <FCGlobal.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
// forward declarations
|
||||
class SoEvent;
|
||||
@@ -195,6 +197,8 @@ public:
|
||||
|
||||
SbVec3f getRotationCenter(SbBool&) const;
|
||||
|
||||
std::optional<SbVec2s>& getRightClickPosition();
|
||||
|
||||
PyObject *getPyObject() override;
|
||||
|
||||
protected:
|
||||
@@ -239,6 +243,13 @@ protected:
|
||||
void syncWithEvent(const SoEvent * const ev);
|
||||
virtual void openPopupMenu(const SbVec2s& position);
|
||||
|
||||
private:
|
||||
bool isNavigationStyleAction(QAction* action, QActionGroup* navMenuGroup) const;
|
||||
QWidget* findView3DInventorWidget() const;
|
||||
void applyNavigationStyleChange(QAction* selectedAction);
|
||||
|
||||
protected:
|
||||
|
||||
void clearLog();
|
||||
void addToLog(const SbVec2s pos, const SbTime time);
|
||||
|
||||
@@ -290,6 +301,10 @@ protected:
|
||||
|
||||
Py::SmartPtr pythonObject;
|
||||
|
||||
// store the position where right-click occurred just before
|
||||
// the menu popped up
|
||||
std::optional<SbVec2s> rightClickPosition;
|
||||
|
||||
private:
|
||||
friend class NavigationAnimator;
|
||||
|
||||
|
||||
@@ -2604,3 +2604,11 @@ PyObject *SelectionSingleton::sGetSelectionFromStack(PyObject * /*self*/, PyObje
|
||||
}
|
||||
PY_CATCH;
|
||||
}
|
||||
|
||||
bool SelectionSingleton::isClarifySelectionActive() {
|
||||
return clarifySelectionActive;
|
||||
}
|
||||
|
||||
void SelectionSingleton::setClarifySelectionActive(bool active) {
|
||||
clarifySelectionActive = active;
|
||||
}
|
||||
|
||||
@@ -409,6 +409,9 @@ public:
|
||||
*/
|
||||
void setVisible(VisibleState visible);
|
||||
|
||||
bool isClarifySelectionActive();
|
||||
void setClarifySelectionActive(bool active);
|
||||
|
||||
/// signal on new object
|
||||
boost::signals2::signal<void (const SelectionChanges& msg)> signalSelectionChanged;
|
||||
|
||||
@@ -708,6 +711,7 @@ protected:
|
||||
|
||||
int logDisabled = 0;
|
||||
bool logHasSelection = false;
|
||||
bool clarifySelectionActive = false;
|
||||
|
||||
SelectionStyle selectionStyle;
|
||||
};
|
||||
|
||||
@@ -31,17 +31,22 @@
|
||||
#include <QTextStream>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <set>
|
||||
#endif
|
||||
|
||||
#include <App/ComplexGeoData.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/ElementNamingUtils.h>
|
||||
#include <App/GeoFeature.h>
|
||||
#include <App/IndexedName.h>
|
||||
#include <Base/Console.h>
|
||||
|
||||
#include "SelectionView.h"
|
||||
#include "Application.h"
|
||||
#include "BitmapFactory.h"
|
||||
#include "Command.h"
|
||||
#include "Document.h"
|
||||
#include "ViewProvider.h"
|
||||
|
||||
|
||||
FC_LOG_LEVEL_INIT("Selection", true, true, true)
|
||||
@@ -704,4 +709,433 @@ void SelectionView::onEnablePickList()
|
||||
|
||||
/// @endcond
|
||||
|
||||
// SelectionMenu implementation
|
||||
SelectionMenu::SelectionMenu(QWidget *parent)
|
||||
: QMenu(parent)
|
||||
{
|
||||
connect(this, &QMenu::hovered, this, &SelectionMenu::onHover);
|
||||
}
|
||||
|
||||
struct ElementInfo {
|
||||
QMenu *menu = nullptr;
|
||||
QIcon icon;
|
||||
std::vector<int> indices;
|
||||
};
|
||||
|
||||
struct SubMenuInfo {
|
||||
QMenu *menu = nullptr;
|
||||
// Map from sub-object label to map from object path to element info
|
||||
std::map<std::string, std::map<std::string, ElementInfo>> items;
|
||||
};
|
||||
|
||||
PickData SelectionMenu::doPick(const std::vector<PickData> &sels, const QPoint& pos)
|
||||
{
|
||||
clear();
|
||||
Gui::Selection().setClarifySelectionActive(true);
|
||||
|
||||
currentSelections = sels;
|
||||
|
||||
std::map<std::string, SubMenuInfo> menus;
|
||||
processSelections(currentSelections, menus);
|
||||
buildMenuStructure(menus, currentSelections);
|
||||
|
||||
QAction* picked = this->exec(pos);
|
||||
return onPicked(picked, currentSelections);
|
||||
}
|
||||
|
||||
void SelectionMenu::processSelections(std::vector<PickData> &selections, std::map<std::string, SubMenuInfo> &menus)
|
||||
{
|
||||
std::map<App::DocumentObject*, QIcon> icons;
|
||||
std::set<std::string> createdElementTypes;
|
||||
std::set<std::string> processedItems;
|
||||
|
||||
for (int i = 0; i < (int)selections.size(); ++i) {
|
||||
const auto &sel = selections[i];
|
||||
|
||||
App::DocumentObject* sobj = getSubObject(sel);
|
||||
std::string elementType = extractElementType(sel);
|
||||
std::string objKey = createObjectKey(sel);
|
||||
std::string itemId = elementType + "|" + std::string(sobj->Label.getValue()) + "|" + sel.subName;
|
||||
|
||||
if (processedItems.find(itemId) != processedItems.end()) {
|
||||
continue;
|
||||
}
|
||||
processedItems.insert(itemId);
|
||||
|
||||
QIcon icon = getOrCreateIcon(sobj, icons);
|
||||
|
||||
auto &elementInfo = menus[elementType].items[sobj->Label.getValue()][objKey];
|
||||
elementInfo.icon = icon;
|
||||
elementInfo.indices.push_back(i);
|
||||
|
||||
addGeoFeatureTypes(sobj, menus, createdElementTypes);
|
||||
addWholeObjectSelection(sel, sobj, selections, menus, icon);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectionMenu::buildMenuStructure(std::map<std::string, SubMenuInfo> &menus, const std::vector<PickData> &selections)
|
||||
{
|
||||
std::vector<std::string> preferredOrder = {"Object", "Solid", "Face", "Edge", "Vertex", "Wire", "Shell", "Compound", "CompSolid"};
|
||||
std::vector<std::map<std::string, SubMenuInfo>::iterator> menuArray;
|
||||
menuArray.reserve(menus.size());
|
||||
|
||||
for (const auto& category : preferredOrder) {
|
||||
if (auto it = menus.find(category); it != menus.end()) {
|
||||
menuArray.push_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = menus.begin(); it != menus.end(); ++it) {
|
||||
if (std::find(preferredOrder.begin(), preferredOrder.end(), it->first) == preferredOrder.end()) {
|
||||
menuArray.push_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto elementTypeIterator : menuArray) {
|
||||
auto &elementTypeEntry = *elementTypeIterator;
|
||||
auto &subMenuInfo = elementTypeEntry.second;
|
||||
const std::string &elementType = elementTypeEntry.first;
|
||||
|
||||
if (subMenuInfo.items.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
subMenuInfo.menu = addMenu(QString::fromUtf8(elementType.c_str()));
|
||||
|
||||
// for "Object" type, and "Other", always use flat menu (no submenus for individual objects)
|
||||
bool groupMenu = (elementType != "Object" && elementType != "Other") && shouldGroupMenu(subMenuInfo);
|
||||
|
||||
for (auto &objectLabelEntry : subMenuInfo.items) {
|
||||
const std::string &objectLabel = objectLabelEntry.first;
|
||||
|
||||
for (auto &objectPathEntry : objectLabelEntry.second) {
|
||||
auto &elementInfo = objectPathEntry.second;
|
||||
|
||||
if (!groupMenu) {
|
||||
createFlatMenu(elementInfo, subMenuInfo.menu, objectLabel, elementType, selections);
|
||||
} else {
|
||||
createGroupedMenu(elementInfo, subMenuInfo.menu, objectLabel, elementType, selections);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PickData SelectionMenu::onPicked(QAction *picked, const std::vector<PickData> &sels)
|
||||
{
|
||||
// Clear the ClarifySelection active flag when menu is done
|
||||
Gui::Selection().setClarifySelectionActive(false);
|
||||
|
||||
Gui::Selection().rmvPreselect();
|
||||
if (!picked)
|
||||
return PickData{};
|
||||
|
||||
int index = picked->data().toInt();
|
||||
if (index >= 0 && index < (int)sels.size()) {
|
||||
const auto &sel = sels[index];
|
||||
if (sel.obj) {
|
||||
Gui::Selection().addSelection(sel.docName.c_str(),
|
||||
sel.objName.c_str(),
|
||||
sel.subName.c_str());
|
||||
}
|
||||
return sel;
|
||||
}
|
||||
return PickData{};
|
||||
}
|
||||
|
||||
void SelectionMenu::onHover(QAction *action)
|
||||
{
|
||||
if (!action || currentSelections.empty())
|
||||
return;
|
||||
|
||||
// Clear previous preselection
|
||||
Gui::Selection().rmvPreselect();
|
||||
|
||||
// Get the selection index from the action data
|
||||
bool ok;
|
||||
int index = action->data().toInt(&ok);
|
||||
if (!ok || index < 0 || index >= (int)currentSelections.size())
|
||||
return;
|
||||
|
||||
const auto &sel = currentSelections[index];
|
||||
if (!sel.obj)
|
||||
return;
|
||||
|
||||
// set preselection for both sub-objects and whole objects
|
||||
Gui::Selection().setPreselect(sel.docName.c_str(),
|
||||
sel.objName.c_str(),
|
||||
!sel.subName.empty() ? sel.subName.c_str() : "",
|
||||
0, 0, 0,
|
||||
SelectionChanges::MsgSource::TreeView);
|
||||
}
|
||||
|
||||
bool SelectionMenu::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
return QMenu::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void SelectionMenu::leaveEvent(QEvent *e)
|
||||
{
|
||||
Gui::Selection().rmvPreselect();
|
||||
QMenu::leaveEvent(e);
|
||||
}
|
||||
|
||||
App::DocumentObject* SelectionMenu::getSubObject(const PickData &sel)
|
||||
{
|
||||
App::DocumentObject* sobj = sel.obj;
|
||||
if (!sel.subName.empty()) {
|
||||
sobj = sel.obj->getSubObject(sel.subName.c_str());
|
||||
if (!sobj) {
|
||||
sobj = sel.obj;
|
||||
}
|
||||
}
|
||||
return sobj;
|
||||
}
|
||||
|
||||
std::string SelectionMenu::extractElementType(const PickData &sel)
|
||||
{
|
||||
std::string actualElement;
|
||||
|
||||
if (!sel.element.empty()) {
|
||||
actualElement = sel.element;
|
||||
} else if (!sel.subName.empty()) {
|
||||
const char *elementName = Data::findElementName(sel.subName.c_str());
|
||||
if (elementName && elementName[0]) {
|
||||
actualElement = elementName;
|
||||
} else {
|
||||
// for link objects like "Bucket.Edge222", extract "Edge222"
|
||||
std::string subName = sel.subName;
|
||||
std::size_t lastDot = subName.find_last_of('.');
|
||||
if (lastDot != std::string::npos && lastDot + 1 < subName.length()) {
|
||||
actualElement = subName.substr(lastDot + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!actualElement.empty()) {
|
||||
std::size_t pos = actualElement.find_first_of("0123456789");
|
||||
if (pos != std::string::npos) {
|
||||
return actualElement.substr(0, pos);
|
||||
}
|
||||
return actualElement;
|
||||
}
|
||||
|
||||
return "Other";
|
||||
}
|
||||
|
||||
std::string SelectionMenu::createObjectKey(const PickData &sel)
|
||||
{
|
||||
std::string objKey = std::string(sel.objName);
|
||||
if (!sel.subName.empty()) {
|
||||
std::string subNameNoElement = sel.subName;
|
||||
const char *elementName = Data::findElementName(sel.subName.c_str());
|
||||
if (elementName && elementName[0]) {
|
||||
std::string elementStr = elementName;
|
||||
std::size_t elementPos = subNameNoElement.rfind(elementStr);
|
||||
if (elementPos != std::string::npos) {
|
||||
subNameNoElement = subNameNoElement.substr(0, elementPos);
|
||||
}
|
||||
}
|
||||
objKey += "." + subNameNoElement;
|
||||
}
|
||||
return objKey;
|
||||
}
|
||||
|
||||
QIcon SelectionMenu::getOrCreateIcon(App::DocumentObject* sobj, std::map<App::DocumentObject*, QIcon> &icons)
|
||||
{
|
||||
auto &icon = icons[sobj];
|
||||
if (icon.isNull()) {
|
||||
auto vp = Application::Instance->getViewProvider(sobj);
|
||||
if (vp)
|
||||
icon = vp->getIcon();
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
void SelectionMenu::addGeoFeatureTypes(App::DocumentObject* sobj, std::map<std::string, SubMenuInfo> &menus, std::set<std::string> &createdTypes)
|
||||
{
|
||||
auto geoFeature = freecad_cast<App::GeoFeature*>(sobj->getLinkedObject(true));
|
||||
if (geoFeature) {
|
||||
std::vector<const char*> types = geoFeature->getElementTypes(true);
|
||||
for (const char* type : types) {
|
||||
if (type && type[0] && createdTypes.find(type) == createdTypes.end()) {
|
||||
menus[type];
|
||||
createdTypes.insert(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SelectionMenu::addWholeObjectSelection(const PickData &sel, App::DocumentObject* sobj,
|
||||
std::vector<PickData> &selections, std::map<std::string, SubMenuInfo> &menus, const QIcon &icon)
|
||||
{
|
||||
if (sel.subName.empty()) return;
|
||||
|
||||
std::string actualElement = extractElementType(sel) != "Other" ? sel.element : "";
|
||||
if (actualElement.empty() && !sel.subName.empty()) {
|
||||
const char *elementName = Data::findElementName(sel.subName.c_str());
|
||||
if (elementName) actualElement = elementName;
|
||||
}
|
||||
if (actualElement.empty()) return;
|
||||
|
||||
bool shouldAdd = false;
|
||||
if (sobj) {
|
||||
if (sobj != sel.obj) {
|
||||
// sub-objects
|
||||
std::string typeName = sobj->getTypeId().getName();
|
||||
if (typeName == "App::Part" || typeName == "PartDesign::Body") {
|
||||
shouldAdd = true;
|
||||
} else {
|
||||
auto geoFeature = freecad_cast<App::GeoFeature*>(sobj->getLinkedObject(true));
|
||||
if (geoFeature) {
|
||||
std::vector<const char*> types = geoFeature->getElementTypes(true);
|
||||
if (types.size() > 1) {
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// top-level objects (sobj == sel.obj)
|
||||
// check if subName is just an element name
|
||||
if (sel.subName.find('.') == std::string::npos) {
|
||||
auto geoFeature = freecad_cast<App::GeoFeature*>(sobj->getLinkedObject(true));
|
||||
if (geoFeature) {
|
||||
std::vector<const char*> types = geoFeature->getElementTypes(true);
|
||||
if (!types.empty()) {
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAdd) {
|
||||
std::string wholeObjKey;
|
||||
std::string wholeObjSubName;
|
||||
|
||||
if (sobj != sel.obj) {
|
||||
// sub-objects
|
||||
std::string subNameStr = sel.subName;
|
||||
std::size_t lastDot = subNameStr.find_last_of('.');
|
||||
if (lastDot != std::string::npos && lastDot > 0) {
|
||||
std::size_t prevDot = subNameStr.find_last_of('.', lastDot - 1);
|
||||
std::string subObjName;
|
||||
if (prevDot != std::string::npos) {
|
||||
subObjName = subNameStr.substr(prevDot + 1, lastDot - prevDot - 1);
|
||||
} else {
|
||||
subObjName = subNameStr.substr(0, lastDot);
|
||||
}
|
||||
|
||||
if (!subObjName.empty()) {
|
||||
wholeObjKey = std::string(sel.objName) + "." + subObjName + ".";
|
||||
wholeObjSubName = subObjName + ".";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// top-level objects (sobj == sel.obj)
|
||||
wholeObjKey = std::string(sel.objName) + ".";
|
||||
wholeObjSubName = ""; // empty subName for top-level whole object
|
||||
}
|
||||
|
||||
if (!wholeObjKey.empty()) {
|
||||
auto &objItems = menus["Object"].items[sobj->Label.getValue()];
|
||||
if (objItems.find(wholeObjKey) == objItems.end()) {
|
||||
PickData wholeObjSel = sel;
|
||||
wholeObjSel.subName = wholeObjSubName;
|
||||
wholeObjSel.element = "";
|
||||
|
||||
selections.push_back(wholeObjSel);
|
||||
|
||||
auto &wholeObjInfo = objItems[wholeObjKey];
|
||||
wholeObjInfo.icon = icon;
|
||||
wholeObjInfo.indices.push_back(selections.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SelectionMenu::shouldGroupMenu(const SubMenuInfo &info)
|
||||
{
|
||||
constexpr std::size_t MAX_MENU_ITEMS_BEFORE_GROUPING = 20;
|
||||
if (info.items.size() > MAX_MENU_ITEMS_BEFORE_GROUPING) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t objCount = 0;
|
||||
std::size_t count = 0;
|
||||
constexpr std::size_t MAX_SELECTION_COUNT_BEFORE_GROUPING = 5;
|
||||
for (auto &objectLabelEntry : info.items) {
|
||||
objCount += objectLabelEntry.second.size();
|
||||
for (auto &objectPathEntry : objectLabelEntry.second)
|
||||
count += objectPathEntry.second.indices.size();
|
||||
if (count > MAX_SELECTION_COUNT_BEFORE_GROUPING && objCount > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SelectionMenu::createFlatMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label,
|
||||
const std::string &elementType, const std::vector<PickData> &selections)
|
||||
{
|
||||
for (int idx : elementInfo.indices) {
|
||||
const auto &sel = selections[idx];
|
||||
QString text = QString::fromUtf8(label.c_str());
|
||||
if (!sel.element.empty()) {
|
||||
text += QStringLiteral(" (%1)").arg(QString::fromUtf8(sel.element.c_str()));
|
||||
} else if (!sel.subName.empty() && elementType != "Object" && elementType != "Other") {
|
||||
// For link objects, extract element name from subName
|
||||
// For "Bucket.Face74", we want to show "Bucket001 (Face74)"
|
||||
std::string subName = sel.subName;
|
||||
std::size_t lastDot = subName.find_last_of('.');
|
||||
if (lastDot != std::string::npos && lastDot + 1 < subName.length()) {
|
||||
QString elementName = QString::fromUtf8(subName.substr(lastDot + 1).c_str());
|
||||
text += QStringLiteral(" (%1)").arg(elementName);
|
||||
}
|
||||
}
|
||||
|
||||
QAction *action = parentMenu->addAction(elementInfo.icon, text);
|
||||
action->setData(idx);
|
||||
connect(action, &QAction::hovered, this, [this, action]() {
|
||||
onHover(action);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void SelectionMenu::createGroupedMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label,
|
||||
const std::string &elementType, const std::vector<PickData> &selections)
|
||||
{
|
||||
if (!elementInfo.menu) {
|
||||
elementInfo.menu = parentMenu->addMenu(elementInfo.icon, QString::fromUtf8(label.c_str()));
|
||||
}
|
||||
|
||||
for (int idx : elementInfo.indices) {
|
||||
const auto &sel = selections[idx];
|
||||
QString text;
|
||||
if (!sel.element.empty()) {
|
||||
text = QString::fromUtf8(sel.element.c_str());
|
||||
} else if (elementType == "Object" && !sel.subName.empty() && sel.subName.back() == '.') {
|
||||
text = tr("Whole Object");
|
||||
} else if (!sel.subName.empty()) {
|
||||
// extract just the element name from subName for link objects
|
||||
// for "Bucket.Edge222", we want just "Edge222"
|
||||
std::string subName = sel.subName;
|
||||
std::size_t lastDot = subName.find_last_of('.');
|
||||
if (lastDot != std::string::npos && lastDot + 1 < subName.length()) {
|
||||
text = QString::fromUtf8(subName.substr(lastDot + 1).c_str());
|
||||
} else {
|
||||
text = QString::fromUtf8(sel.subName.c_str());
|
||||
}
|
||||
} else {
|
||||
text = QString::fromUtf8(sel.subName.c_str());
|
||||
}
|
||||
|
||||
QAction *action = elementInfo.menu->addAction(text);
|
||||
action->setData(idx);
|
||||
connect(action, &QAction::hovered, this, [this, action]() {
|
||||
onHover(action);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_SelectionView.cpp"
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
|
||||
#include "DockWindow.h"
|
||||
#include "Selection.h"
|
||||
#include <QMenu>
|
||||
#include <QPointer>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
class QListWidget;
|
||||
@@ -36,6 +42,9 @@ namespace App {
|
||||
class DocumentObject;
|
||||
}
|
||||
|
||||
struct ElementInfo;
|
||||
struct SubMenuInfo;
|
||||
|
||||
namespace Gui {
|
||||
namespace DockWnd {
|
||||
|
||||
@@ -112,6 +121,62 @@ private:
|
||||
};
|
||||
|
||||
} // namespace DockWnd
|
||||
|
||||
// Simple selection data structure
|
||||
struct PickData {
|
||||
App::DocumentObject* obj;
|
||||
std::string element;
|
||||
std::string docName;
|
||||
std::string objName;
|
||||
std::string subName;
|
||||
};
|
||||
|
||||
// Add SelectionMenu class outside the DockWnd namespace
|
||||
class GuiExport SelectionMenu : public QMenu {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SelectionMenu(QWidget *parent=nullptr);
|
||||
|
||||
/** Populate and show the menu for picking geometry elements.
|
||||
*
|
||||
* @param sels: a list of geometry element references
|
||||
* @param pos: optional position to show the menu (defaults to current cursor position)
|
||||
* @return Return the picked geometry reference
|
||||
*
|
||||
* The menu will be divided into submenus that are grouped by element type.
|
||||
*/
|
||||
PickData doPick(const std::vector<PickData> &sels, const QPoint& pos = QCursor::pos());
|
||||
|
||||
public Q_SLOTS:
|
||||
void onHover(QAction *);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *, QEvent *) override;
|
||||
void leaveEvent(QEvent *e) override;
|
||||
PickData onPicked(QAction *, const std::vector<PickData> &sels);
|
||||
|
||||
private:
|
||||
void processSelections(std::vector<PickData> &selections, std::map<std::string, SubMenuInfo> &menus);
|
||||
void buildMenuStructure(std::map<std::string, SubMenuInfo> &menus, const std::vector<PickData> &selections);
|
||||
|
||||
App::DocumentObject* getSubObject(const PickData &sel);
|
||||
std::string extractElementType(const PickData &sel);
|
||||
std::string createObjectKey(const PickData &sel);
|
||||
QIcon getOrCreateIcon(App::DocumentObject* sobj, std::map<App::DocumentObject*, QIcon> &icons);
|
||||
void addGeoFeatureTypes(App::DocumentObject* sobj, std::map<std::string, SubMenuInfo> &menus, std::set<std::string> &createdTypes);
|
||||
void addWholeObjectSelection(const PickData &sel, App::DocumentObject* sobj, std::vector<PickData> &selections,
|
||||
std::map<std::string, SubMenuInfo> &menus, const QIcon &icon);
|
||||
bool shouldGroupMenu(const SubMenuInfo &info);
|
||||
void createFlatMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label,
|
||||
const std::string &elementType, const std::vector<PickData> &selections);
|
||||
void createGroupedMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label,
|
||||
const std::string &elementType, const std::vector<PickData> &selections);
|
||||
|
||||
QPointer<QMenu> activeMenu;
|
||||
QPointer<QAction> activeAction;
|
||||
std::vector<PickData> currentSelections;
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
#endif // GUI_DOCKWND_SELECTIONVIEW_H
|
||||
|
||||
@@ -367,20 +367,51 @@ void SoFCUnifiedSelection::doAction(SoAction *action)
|
||||
App::Document* doc = App::GetApplication().getDocument(preselectAction->SelChange.pDocName);
|
||||
App::DocumentObject* obj = doc->getObject(preselectAction->SelChange.pObjectName);
|
||||
ViewProvider*vp = Application::Instance->getViewProvider(obj);
|
||||
SoDetail* detail = vp->getDetail(preselectAction->SelChange.pSubName);
|
||||
|
||||
// use getDetailPath() like selection does, instead of just getDetail()
|
||||
SoDetail* detail = nullptr;
|
||||
detailPath->truncate(0);
|
||||
auto subName = preselectAction->SelChange.pSubName;
|
||||
|
||||
SoFullPath* pathToHighlight = nullptr;
|
||||
if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()) &&
|
||||
(useNewSelection.getValue() || vp->useNewSelectionModel()) && vp->isSelectable()) {
|
||||
|
||||
// get proper detail path for sub-objects (like Assembly parts)
|
||||
if (!subName || !subName[0] || vp->getDetailPath(subName, detailPath, true, detail)) {
|
||||
if (detailPath->getLength()) {
|
||||
pathToHighlight = detailPath;
|
||||
} else {
|
||||
// fallback to ViewProvider root if no specific path
|
||||
pathToHighlight = static_cast<SoFullPath*>(new SoPath(2));
|
||||
pathToHighlight->ref();
|
||||
pathToHighlight->append(vp->getRoot());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
detail = vp->getDetail(subName);
|
||||
pathToHighlight = static_cast<SoFullPath*>(new SoPath(2));
|
||||
pathToHighlight->ref();
|
||||
pathToHighlight->append(vp->getRoot());
|
||||
}
|
||||
|
||||
SoHighlightElementAction highlightAction;
|
||||
highlightAction.setHighlighted(true);
|
||||
highlightAction.setColor(this->colorHighlight.getValue());
|
||||
highlightAction.setElement(detail);
|
||||
highlightAction.apply(vp->getRoot());
|
||||
if (pathToHighlight) {
|
||||
SoHighlightElementAction highlightAction;
|
||||
highlightAction.setHighlighted(true);
|
||||
highlightAction.setColor(this->colorHighlight.getValue());
|
||||
highlightAction.setElement(detail);
|
||||
highlightAction.apply(pathToHighlight);
|
||||
|
||||
currentHighlightPath = static_cast<SoFullPath*>(pathToHighlight->copy());
|
||||
currentHighlightPath->ref();
|
||||
|
||||
// clean up temporary path if we created one
|
||||
if (pathToHighlight != detailPath) {
|
||||
pathToHighlight->unref();
|
||||
}
|
||||
}
|
||||
|
||||
delete detail;
|
||||
|
||||
SoSearchAction sa;
|
||||
sa.setNode(vp->getRoot());
|
||||
sa.apply(vp->getRoot());
|
||||
currentHighlightPath = static_cast<SoFullPath*>(sa.getPath()->copy());
|
||||
currentHighlightPath->ref();
|
||||
}
|
||||
|
||||
if (useNewSelection.getValue())
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
|
||||
#include "View3DInventorViewer.h"
|
||||
#include "Application.h"
|
||||
#include "Command.h"
|
||||
#include "Document.h"
|
||||
#include "GLPainter.h"
|
||||
#include "Inventor/SoAxisCrossKit.h"
|
||||
@@ -231,9 +232,26 @@ while the progress bar is running.
|
||||
class Gui::ViewerEventFilter : public QObject
|
||||
{
|
||||
public:
|
||||
ViewerEventFilter() = default;
|
||||
ViewerEventFilter() : longPressTimer(new QTimer(this)) {
|
||||
longPressTimer->setSingleShot(true);
|
||||
connect(longPressTimer, &QTimer::timeout, [this]() {
|
||||
if (currentViewer) {
|
||||
triggerClarifySelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
~ViewerEventFilter() override = default;
|
||||
|
||||
private:
|
||||
void triggerClarifySelection() {
|
||||
Gui::Command::runCommand(Gui::Command::Gui, "Gui.runCommand('Std_ClarifySelection')");
|
||||
}
|
||||
|
||||
QTimer* longPressTimer;
|
||||
QPoint pressPosition;
|
||||
View3DInventorViewer* currentViewer = nullptr;
|
||||
|
||||
public:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override {
|
||||
// Bug #0000607: Some mice also support horizontal scrolling which however might
|
||||
// lead to some unwanted zooming when pressing the MMB for panning.
|
||||
@@ -276,6 +294,37 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
currentViewer = static_cast<View3DInventorViewer*>(obj);
|
||||
pressPosition = mouseEvent->pos();
|
||||
|
||||
int longPressTimeout = App::GetApplication()
|
||||
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/View")
|
||||
->GetInt("LongPressTimeout", 1000);
|
||||
longPressTimer->setInterval(longPressTimeout);
|
||||
longPressTimer->start();
|
||||
}
|
||||
}
|
||||
else if (event->type() == QEvent::MouseButtonRelease) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
longPressTimer->stop();
|
||||
currentViewer = nullptr;
|
||||
}
|
||||
}
|
||||
else if (event->type() == QEvent::MouseMove) {
|
||||
if (longPressTimer->isActive()) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
// cancel long press if mouse moved too far (more than 5 pixels)
|
||||
if ((mouseEvent->pos() - pressPosition).manhattanLength() > 5) {
|
||||
longPressTimer->stop();
|
||||
currentViewer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -840,11 +889,10 @@ void View3DInventorViewer::onSelectionChanged(const SelectionChanges & reason)
|
||||
}
|
||||
|
||||
if(Reason.Type == SelectionChanges::RmvPreselect ||
|
||||
Reason.Type == SelectionChanges::RmvPreselectSignal)
|
||||
Reason.Type == SelectionChanges::RmvPreselectSignal ||
|
||||
Reason.Type == SelectionChanges::SetPreselect)
|
||||
{
|
||||
//Hint: do not create a tmp. instance of SelectionChanges
|
||||
SelectionChanges selChanges(SelectionChanges::RmvPreselect);
|
||||
SoFCPreselectionAction preselectionAction(selChanges);
|
||||
SoFCPreselectionAction preselectionAction(Reason);
|
||||
preselectionAction.apply(pcViewProviderRoot);
|
||||
} else {
|
||||
SoFCSelectionAction selectionAction(Reason);
|
||||
@@ -2474,7 +2522,15 @@ void View3DInventorViewer::renderScene()
|
||||
|
||||
So3DAnnotation::render = true;
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
glra->apply(SoDelayedAnnotationsElement::getDelayedPaths(state));
|
||||
|
||||
// process delayed paths with priority support
|
||||
if (Gui::Selection().isClarifySelectionActive()) {
|
||||
Gui::SoDelayedAnnotationsElement::processDelayedPathsWithPriority(state, glra);
|
||||
} else {
|
||||
// standard processing for normal delayed annotations
|
||||
glra->apply(Gui::SoDelayedAnnotationsElement::getDelayedPaths(state));
|
||||
}
|
||||
|
||||
So3DAnnotation::render = false;
|
||||
}
|
||||
catch (const Base::MemoryException&) {
|
||||
|
||||
@@ -723,6 +723,7 @@ MenuItem* StdWorkbench::setupMenuBar() const
|
||||
<< "Separator";
|
||||
#endif
|
||||
*tool << "Std_Measure"
|
||||
<< "Std_ClarifySelection"
|
||||
<< "Std_QuickMeasure"
|
||||
<< "Std_UnitsCalculator"
|
||||
<< "Separator"
|
||||
|
||||
@@ -77,7 +77,7 @@ class ExportImportTest(unittest.TestCase):
|
||||
sa.apply(feature.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
bind = paths.get(2).getTail()
|
||||
bind = paths.get(1).getTail()
|
||||
self.assertEqual(bind.value.getValue(), bind.PER_PART)
|
||||
|
||||
sa = coin.SoSearchAction()
|
||||
@@ -87,5 +87,5 @@ class ExportImportTest(unittest.TestCase):
|
||||
sa.apply(feature.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
mat = paths.get(2).getTail()
|
||||
mat = paths.get(1).getTail()
|
||||
self.assertEqual(mat.diffuseColor.getNum(), 6)
|
||||
|
||||
@@ -44,10 +44,18 @@
|
||||
# include <Inventor/elements/SoLineWidthElement.h>
|
||||
# include <Inventor/errors/SoDebugError.h>
|
||||
# include <Inventor/misc/SoState.h>
|
||||
# include <Inventor/nodes/SoGroup.h>
|
||||
# include <Inventor/actions/SoSearchAction.h>
|
||||
#endif
|
||||
|
||||
#include <Gui/Selection/SoFCUnifiedSelection.h>
|
||||
#include <Gui/Selection/Selection.h>
|
||||
#include <Base/Console.h>
|
||||
#include "SoBrepEdgeSet.h"
|
||||
#include "SoBrepFaceSet.h"
|
||||
#include "ViewProviderExt.h"
|
||||
|
||||
#include <Gui/Inventor/So3DAnnotation.h>
|
||||
|
||||
|
||||
using namespace PartGui;
|
||||
@@ -79,6 +87,26 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction *action)
|
||||
SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext<SelContext>(this,selContext,ctx2);
|
||||
if(ctx2 && ctx2->selectionIndex.empty())
|
||||
return;
|
||||
|
||||
|
||||
bool hasContextHighlight = ctx && !ctx->hl.empty();
|
||||
bool hasFaceHighlight = viewProvider && viewProvider->isFaceHighlightActive();
|
||||
bool hasAnyHighlight = hasContextHighlight || hasFaceHighlight;
|
||||
|
||||
if (Gui::Selection().isClarifySelectionActive()
|
||||
&& !Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths
|
||||
&& hasAnyHighlight) {
|
||||
// if we are using clarifyselection - add this to delayed paths with priority
|
||||
// as we want to get this rendered on top of everything
|
||||
if (viewProvider) {
|
||||
viewProvider->setFaceHighlightActive(true);
|
||||
}
|
||||
Gui::SoDelayedAnnotationsElement::addDelayedPath(action->getState(),
|
||||
action->getCurPath()->copy(),
|
||||
200);
|
||||
return;
|
||||
}
|
||||
|
||||
if(selContext2->checkGlobal(ctx)) {
|
||||
if(selContext2->isSelectAll()) {
|
||||
selContext2->sl.clear();
|
||||
@@ -132,9 +160,22 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction *action)
|
||||
}
|
||||
if(ctx2 && !ctx2->selectionIndex.empty())
|
||||
renderSelection(action,ctx2,false);
|
||||
else
|
||||
else if (Gui::Selection().isClarifySelectionActive()
|
||||
&& !Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths && hasAnyHighlight) {
|
||||
state->push();
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDepthMask(false);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
inherited::GLRender(action);
|
||||
|
||||
state->pop();
|
||||
}
|
||||
else {
|
||||
inherited::GLRender(action);
|
||||
}
|
||||
|
||||
// Workaround for #0000433
|
||||
//#if !defined(FC_OS_WIN32)
|
||||
if(!action->isRenderingDelayedPaths())
|
||||
|
||||
@@ -36,6 +36,8 @@ class SoTextureCoordinateBundle;
|
||||
|
||||
namespace PartGui {
|
||||
|
||||
class ViewProviderPartExt;
|
||||
|
||||
class PartGuiExport SoBrepEdgeSet : public SoIndexedLineSet {
|
||||
using inherited = SoIndexedLineSet;
|
||||
|
||||
@@ -44,6 +46,8 @@ class PartGuiExport SoBrepEdgeSet : public SoIndexedLineSet {
|
||||
public:
|
||||
static void initClass();
|
||||
SoBrepEdgeSet();
|
||||
|
||||
void setViewProvider(ViewProviderPartExt* vp) { viewProvider = vp; }
|
||||
|
||||
protected:
|
||||
~SoBrepEdgeSet() override = default;
|
||||
@@ -68,11 +72,15 @@ private:
|
||||
void renderSelection(SoGLRenderAction *action, SelContextPtr, bool push=true);
|
||||
bool validIndexes(const SoCoordinateElement*, const std::vector<int32_t>&) const;
|
||||
|
||||
|
||||
private:
|
||||
SelContextPtr selContext;
|
||||
SelContextPtr selContext2;
|
||||
Gui::SoFCSelectionCounter selCounter;
|
||||
uint32_t packedColor{0};
|
||||
|
||||
// backreference to viewprovider that owns this node
|
||||
ViewProviderPartExt* viewProvider = nullptr;
|
||||
};
|
||||
|
||||
} // namespace PartGui
|
||||
|
||||
@@ -73,8 +73,12 @@
|
||||
#include <Gui/SoFCInteractiveElement.h>
|
||||
#include <Gui/Selection/SoFCSelectionAction.h>
|
||||
#include <Gui/Selection/SoFCUnifiedSelection.h>
|
||||
#include <Gui/Inventor/So3DAnnotation.h>
|
||||
|
||||
#include "SoBrepFaceSet.h"
|
||||
#include "ViewProviderExt.h"
|
||||
#include "SoBrepEdgeSet.h"
|
||||
|
||||
|
||||
|
||||
using namespace PartGui;
|
||||
@@ -188,6 +192,9 @@ void SoBrepFaceSet::doAction(SoAction* action)
|
||||
ctx->highlightIndex = -1;
|
||||
touch();
|
||||
}
|
||||
if (viewProvider) {
|
||||
viewProvider->setFaceHighlightActive(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -204,6 +211,9 @@ void SoBrepFaceSet::doAction(SoAction* action)
|
||||
ctx->highlightIndex = -1;
|
||||
touch();
|
||||
}
|
||||
if (viewProvider) {
|
||||
viewProvider->setFaceHighlightActive(false);
|
||||
}
|
||||
}else {
|
||||
int index = static_cast<const SoFaceDetail*>(detail)->getPartIndex();
|
||||
SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action,this,selContext);
|
||||
@@ -521,6 +531,41 @@ void SoBrepFaceSet::GLRender(SoGLRenderAction *action)
|
||||
|
||||
auto state = action->getState();
|
||||
selCounter.checkRenderCache(state);
|
||||
|
||||
bool hasContextHighlight = ctx && ctx->isHighlighted() && !ctx->isHighlightAll()
|
||||
&& ctx->highlightIndex >= 0 && ctx->highlightIndex < partIndex.getNum();
|
||||
|
||||
// for the tool add this node to delayed paths as we want to render it on top of the scene
|
||||
if (Gui::Selection().isClarifySelectionActive() && hasContextHighlight) {
|
||||
|
||||
if (!Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths) {
|
||||
if (viewProvider) {
|
||||
viewProvider->setFaceHighlightActive(true);
|
||||
}
|
||||
|
||||
const SoPath* currentPath = action->getCurPath();
|
||||
Gui::SoDelayedAnnotationsElement::addDelayedPath(action->getState(),
|
||||
currentPath->copy(),
|
||||
100);
|
||||
return;
|
||||
} else {
|
||||
// during priority delayed paths processing:
|
||||
// render base faces normally first, then render highlight on top
|
||||
|
||||
inherited::GLRender(action);
|
||||
|
||||
state->push();
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDepthMask(false);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
renderHighlight(action, ctx);
|
||||
|
||||
state->pop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// override material binding to PER_PART_INDEX to achieve
|
||||
// preselection/selection with transparency
|
||||
@@ -730,7 +775,7 @@ bool SoBrepFaceSet::overrideMaterialBinding(SoGLRenderAction *action, SelContext
|
||||
singleColor = ctx?-1:1;
|
||||
}
|
||||
|
||||
bool partialRender = ctx2 && !ctx2->isSelectAll();
|
||||
bool partialRender = (ctx2 && !ctx2->isSelectAll());
|
||||
|
||||
if(singleColor>0 && !partialRender) {
|
||||
//optimization for single color non-partial rendering
|
||||
@@ -772,7 +817,8 @@ bool SoBrepFaceSet::overrideMaterialBinding(SoGLRenderAction *action, SelContext
|
||||
packedColors.push_back(ctx->highlightColor.getPackedValue(trans0));
|
||||
matIndex[ctx->highlightIndex] = packedColors.size()-1;
|
||||
}
|
||||
}else{
|
||||
}
|
||||
else{
|
||||
if(partialRender) {
|
||||
packedColors.push_back(SbColor(1.0,1.0,1.0).getPackedValue(1.0));
|
||||
matIndex.resize(partIndex.getNum(),0);
|
||||
@@ -848,7 +894,7 @@ bool SoBrepFaceSet::overrideMaterialBinding(SoGLRenderAction *action, SelContext
|
||||
SoLazyElement::setPacked(state, this, packedColors.size(), packedColors.data(), hasTransparency);
|
||||
SoTextureEnabledElement::set(state,this,false);
|
||||
|
||||
if(hasTransparency && action->isRenderingDelayedPaths()) {
|
||||
if (hasTransparency && action->isRenderingDelayedPaths()) {
|
||||
// rendering delayed paths means we are doing annotation (e.g.
|
||||
// always on top rendering). To render transparency correctly in
|
||||
// this case, we shall use openGL transparency blend. Override
|
||||
|
||||
@@ -38,6 +38,8 @@ class SoTextureCoordinateBundle;
|
||||
|
||||
namespace PartGui {
|
||||
|
||||
class ViewProviderPartExt;
|
||||
|
||||
/**
|
||||
* First some words to the history and the reason why we have this class:
|
||||
* In older FreeCAD versions we had an own Inventor node for each sub-element of a shape with its own highlight node.
|
||||
@@ -79,6 +81,8 @@ class PartGuiExport SoBrepFaceSet : public SoIndexedFaceSet {
|
||||
public:
|
||||
static void initClass();
|
||||
SoBrepFaceSet();
|
||||
|
||||
void setViewProvider(ViewProviderPartExt* vp) { viewProvider = vp; }
|
||||
|
||||
SoMFInt32 partIndex;
|
||||
|
||||
@@ -154,6 +158,9 @@ private:
|
||||
// Define some VBO pointer for the current mesh
|
||||
class VBO;
|
||||
std::unique_ptr<VBO> pimpl;
|
||||
|
||||
// backreference to viewprovider that owns this node
|
||||
ViewProviderPartExt* viewProvider = nullptr;
|
||||
};
|
||||
|
||||
} // namespace PartGui
|
||||
|
||||
@@ -44,7 +44,9 @@
|
||||
#endif
|
||||
|
||||
#include <Gui/Selection/SoFCUnifiedSelection.h>
|
||||
#include <Gui/Inventor/So3DAnnotation.h>
|
||||
|
||||
#include "ViewProviderExt.h"
|
||||
#include "SoBrepPointSet.h"
|
||||
|
||||
|
||||
@@ -81,6 +83,22 @@ void SoBrepPointSet::GLRender(SoGLRenderAction *action)
|
||||
return;
|
||||
if(selContext2->checkGlobal(ctx))
|
||||
ctx = selContext2;
|
||||
|
||||
|
||||
bool hasContextHighlight =
|
||||
ctx && ctx->isHighlighted() && !ctx->isHighlightAll() && ctx->highlightIndex >= 0;
|
||||
// for clarifyselection, add this node to delayed path if it is highlighted and render it on
|
||||
// top of everything else (highest priority)
|
||||
if (Gui::Selection().isClarifySelectionActive() && hasContextHighlight
|
||||
&& !Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths) {
|
||||
if (viewProvider) {
|
||||
viewProvider->setFaceHighlightActive(true);
|
||||
}
|
||||
Gui::SoDelayedAnnotationsElement::addDelayedPath(action->getState(),
|
||||
action->getCurPath()->copy(),
|
||||
300);
|
||||
return;
|
||||
}
|
||||
|
||||
if(ctx && ctx->highlightIndex == std::numeric_limits<int>::max()) {
|
||||
if(ctx->selectionIndex.empty() || ctx->isSelectAll()) {
|
||||
@@ -121,8 +139,15 @@ void SoBrepPointSet::GLRender(SoGLRenderAction *action)
|
||||
}
|
||||
if(ctx2 && !ctx2->selectionIndex.empty())
|
||||
renderSelection(action,ctx2,false);
|
||||
else
|
||||
else if (Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths) {
|
||||
glPushAttrib(GL_DEPTH_BUFFER_BIT);
|
||||
glDepthFunc(GL_ALWAYS);
|
||||
inherited::GLRender(action);
|
||||
glPopAttrib();
|
||||
}
|
||||
else {
|
||||
inherited::GLRender(action);
|
||||
}
|
||||
|
||||
// Workaround for #0000433
|
||||
//#if !defined(FC_OS_WIN32)
|
||||
|
||||
@@ -36,6 +36,8 @@ class SoTextureCoordinateBundle;
|
||||
|
||||
namespace PartGui {
|
||||
|
||||
class ViewProviderPartExt;
|
||||
|
||||
class PartGuiExport SoBrepPointSet : public SoPointSet {
|
||||
using inherited = SoPointSet;
|
||||
|
||||
@@ -44,6 +46,8 @@ class PartGuiExport SoBrepPointSet : public SoPointSet {
|
||||
public:
|
||||
static void initClass();
|
||||
SoBrepPointSet();
|
||||
|
||||
void setViewProvider(ViewProviderPartExt* vp) { viewProvider = vp; }
|
||||
|
||||
protected:
|
||||
~SoBrepPointSet() override = default;
|
||||
@@ -64,6 +68,9 @@ private:
|
||||
SelContextPtr selContext2;
|
||||
Gui::SoFCSelectionCounter selCounter;
|
||||
uint32_t packedColor{0};
|
||||
|
||||
// backreference to viewprovider that owns this node
|
||||
ViewProviderPartExt* viewProvider = nullptr;
|
||||
};
|
||||
|
||||
} // namespace PartGui
|
||||
|
||||
@@ -191,6 +191,7 @@ ViewProviderPartExt::ViewProviderPartExt()
|
||||
coords = new SoCoordinate3();
|
||||
coords->ref();
|
||||
faceset = new SoBrepFaceSet();
|
||||
faceset->setViewProvider(this);
|
||||
faceset->ref();
|
||||
norm = new SoNormal;
|
||||
norm->ref();
|
||||
@@ -198,8 +199,10 @@ ViewProviderPartExt::ViewProviderPartExt()
|
||||
normb->value = SoNormalBinding::PER_VERTEX_INDEXED;
|
||||
normb->ref();
|
||||
lineset = new SoBrepEdgeSet();
|
||||
lineset->setViewProvider(this);
|
||||
lineset->ref();
|
||||
nodeset = new SoBrepPointSet();
|
||||
nodeset->setViewProvider(this);
|
||||
nodeset->ref();
|
||||
|
||||
pcFaceBind = new SoMaterialBinding();
|
||||
@@ -447,9 +450,9 @@ void ViewProviderPartExt::attach(App::DocumentObject *pcFeat)
|
||||
|
||||
// normal viewing with edges and points
|
||||
pcNormalRoot->addChild(pcPointsRoot);
|
||||
pcNormalRoot->addChild(wireframe);
|
||||
pcNormalRoot->addChild(offset);
|
||||
pcNormalRoot->addChild(pcFlatRoot);
|
||||
pcNormalRoot->addChild(wireframe);
|
||||
|
||||
// just faces with no edges or points
|
||||
pcFlatRoot->addChild(pShapeHints);
|
||||
|
||||
@@ -156,6 +156,9 @@ public:
|
||||
|
||||
bool allowOverride(const App::DocumentObject &) const override;
|
||||
|
||||
void setFaceHighlightActive(bool active) { faceHighlightActive = active; }
|
||||
bool isFaceHighlightActive() const { return faceHighlightActive; }
|
||||
|
||||
/** @name Edit methods */
|
||||
//@{
|
||||
void setupContextMenu(QMenu*, QObject*, const char*) override;
|
||||
@@ -213,6 +216,7 @@ protected:
|
||||
|
||||
bool VisualTouched;
|
||||
bool NormalsFromUV;
|
||||
bool faceHighlightActive = false;
|
||||
|
||||
private:
|
||||
Gui::ViewProviderFaceTexture texture;
|
||||
|
||||
@@ -49,7 +49,7 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(box.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
mat = paths.get(2).getTail()
|
||||
mat = paths.get(1).getTail()
|
||||
self.assertEqual(mat.diffuseColor.getNum(), 6)
|
||||
|
||||
def testBoxAndLink(self):
|
||||
@@ -83,7 +83,7 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(box.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
mat = paths.get(2).getTail()
|
||||
mat = paths.get(1).getTail()
|
||||
self.assertEqual(mat.diffuseColor.getNum(), 6)
|
||||
|
||||
def testTransparency(self):
|
||||
@@ -110,7 +110,7 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(box.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
bind = paths.get(2).getTail()
|
||||
bind = paths.get(1).getTail()
|
||||
self.assertEqual(bind.value.getValue(), bind.PER_PART)
|
||||
|
||||
sa = coin.SoSearchAction()
|
||||
@@ -120,7 +120,7 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(box.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
mat = paths.get(2).getTail()
|
||||
mat = paths.get(1).getTail()
|
||||
self.assertEqual(mat.diffuseColor.getNum(), 6)
|
||||
|
||||
def testMultiFuse(self):
|
||||
@@ -146,7 +146,7 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(fuse.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
bind = paths.get(2).getTail()
|
||||
bind = paths.get(1).getTail()
|
||||
self.assertEqual(bind.value.getValue(), bind.PER_PART)
|
||||
|
||||
sa = coin.SoSearchAction()
|
||||
@@ -156,7 +156,7 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(fuse.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
mat = paths.get(2).getTail()
|
||||
mat = paths.get(1).getTail()
|
||||
self.assertEqual(mat.diffuseColor.getNum(), 11)
|
||||
|
||||
self.assertEqual(len(fuse.Shape.Faces), 11)
|
||||
@@ -195,7 +195,7 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(fuse.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
bind = paths.get(2).getTail()
|
||||
bind = paths.get(1).getTail()
|
||||
self.assertEqual(bind.value.getValue(), bind.PER_PART)
|
||||
|
||||
sa = coin.SoSearchAction()
|
||||
@@ -205,5 +205,5 @@ class ColorPerFaceTest(unittest.TestCase):
|
||||
sa.apply(fuse.ViewObject.RootNode)
|
||||
paths = sa.getPaths()
|
||||
|
||||
mat = paths.get(2).getTail()
|
||||
mat = paths.get(1).getTail()
|
||||
self.assertEqual(mat.diffuseColor.getNum(), 11)
|
||||
|
||||
Reference in New Issue
Block a user