"Professional CMake" book suggest the following: "Targets should build successfully with or without compiler support for precompiled headers. It should be considered an optimization, not a requirement. In particular, do not explicitly include a precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically generated precompile header on the compiler command line instead. This is more portable across the major compilers and is likely to be easier to maintain. It will also avoid warnings being generated from certain code checking tools like iwyu (include what you use)." Therefore, removed the "#include <PreCompiled.h>" from sources, also there is no need for the "#ifdef _PreComp_" anymore
852 lines
30 KiB
C++
852 lines
30 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2016 WandererFan <wandererfan@gmail.com> *
|
|
* Copyright (c) 2024 Benjamin Bræstrup Sayoc <benj5378@outlook.com> *
|
|
* *
|
|
* 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 <sstream>
|
|
|
|
#include <QBitmap>
|
|
#include <QColor>
|
|
#include <QComboBox>
|
|
#include <QMessageBox>
|
|
#include <QPalette>
|
|
#include <QPixmap>
|
|
#include <QPointF>
|
|
#include <QRectF>
|
|
#include <QString>
|
|
|
|
# include <BRepAdaptor_Surface.hxx>
|
|
# include <BRepLProp_SLProps.hxx>
|
|
# include <gp_Dir.hxx>
|
|
|
|
#include <App/Application.h>
|
|
#include <App/Document.h>
|
|
#include <App/DocumentObject.h>
|
|
#include <App/PropertyPythonObject.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Exception.h>
|
|
#include <Base/Parameter.h>
|
|
#include <Base/Tools.h>
|
|
#include <Base/Tools2D.h>
|
|
#include <Base/Type.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/Document.h>
|
|
#include <Gui/MainWindow.h>
|
|
#include <Gui/MDIView.h>
|
|
#include <Gui/Selection/Selection.h>
|
|
#include <Gui/View3DInventor.h>
|
|
#include <Gui/View3DInventorViewer.h>
|
|
#include <Gui/PrefWidgets.h>
|
|
#include <Inventor/SbVec3f.h>
|
|
|
|
#include <Mod/TechDraw/App/ArrowPropEnum.h>
|
|
#include <Mod/TechDraw/App/BalloonPropEnum.h>
|
|
#include <Mod/TechDraw/App/LineNameEnum.h>
|
|
#include <Mod/TechDraw/App/MattingPropEnum.h>
|
|
#include <Mod/TechDraw/App/DrawPage.h>
|
|
#include <Mod/TechDraw/App/DrawUtil.h>
|
|
#include <Mod/TechDraw/App/DrawViewPart.h>
|
|
#include <Mod/TechDraw/App/LineGenerator.h>
|
|
#include <Mod/TechDraw/App/LineGroup.h>
|
|
#include <Mod/TechDraw/App/Preferences.h>
|
|
|
|
#include "DlgPageChooser.h"
|
|
#include "DrawGuiUtil.h"
|
|
#include "MDIViewPage.h"
|
|
#include "QGIEdge.h"
|
|
#include "QGIVertex.h"
|
|
#include "QGIViewPart.h"
|
|
#include "QGSPage.h"
|
|
#include "ViewProviderPage.h"
|
|
#include "Rez.h"
|
|
|
|
using namespace TechDrawGui;
|
|
using namespace TechDraw;
|
|
using DU = DrawUtil;
|
|
|
|
void DrawGuiUtil::loadArrowBox(QComboBox* qcb)
|
|
{
|
|
qcb->clear();
|
|
|
|
auto curStyleSheet =
|
|
App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow")
|
|
->GetASCII("StyleSheet", "None");
|
|
|
|
int i = 0;
|
|
for (; i < ArrowPropEnum::ArrowCount; i++) {
|
|
qcb->addItem(
|
|
QCoreApplication::translate("ArrowPropEnum", ArrowPropEnum::ArrowTypeEnums[i]));
|
|
QIcon itemIcon(QString::fromUtf8(ArrowPropEnum::ArrowTypeIcons[i].c_str()));
|
|
if (isStyleSheetDark(curStyleSheet)) {
|
|
QColor textColor = Preferences::lightTextColor().asValue<QColor>();
|
|
QSize iconSize(48, 48);
|
|
QIcon itemUpdatedIcon(maskBlackPixels(itemIcon, iconSize, textColor));
|
|
qcb->setItemIcon(i, itemUpdatedIcon);
|
|
}
|
|
else {
|
|
qcb->setItemIcon(i, itemIcon);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawGuiUtil::loadBalloonShapeBox(QComboBox* qballooncb)
|
|
{
|
|
qballooncb->clear();
|
|
|
|
auto curStyleSheet =
|
|
App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow")
|
|
->GetASCII("StyleSheet", "None");
|
|
|
|
int i = 0;
|
|
for (; i < BalloonPropEnum::BalloonCount; i++) {
|
|
qballooncb->addItem(
|
|
QCoreApplication::translate("BalloonPropEnum", BalloonPropEnum::BalloonTypeEnums[i]));
|
|
QIcon itemIcon(QString::fromUtf8(BalloonPropEnum::BalloonTypeIcons[i].c_str()));
|
|
if (isStyleSheetDark(curStyleSheet)) {
|
|
QColor textColor = Preferences::lightTextColor().asValue<QColor>();
|
|
QSize iconSize(48, 48);
|
|
QIcon itemUpdatedIcon(maskBlackPixels(itemIcon, iconSize, textColor));
|
|
qballooncb->setItemIcon(i, itemUpdatedIcon);
|
|
}
|
|
else {
|
|
qballooncb->setItemIcon(i, itemIcon);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawGuiUtil::loadMattingStyleBox(QComboBox* qmattingcb)
|
|
{
|
|
qmattingcb->clear();
|
|
auto curStyleSheet =
|
|
App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow")
|
|
->GetASCII("StyleSheet", "None");
|
|
|
|
int i = 0;
|
|
for (; i < MattingPropEnum::MattingCount; i++) {
|
|
qmattingcb->addItem(
|
|
QCoreApplication::translate("MattingPropEnum", MattingPropEnum::MattingTypeEnums[i]));
|
|
QIcon itemIcon(QString::fromUtf8(MattingPropEnum::MattingTypeIcons[i].c_str()));
|
|
if (isStyleSheetDark(curStyleSheet)) {
|
|
QColor textColor = Preferences::lightTextColor().asValue<QColor>();
|
|
QSize iconSize(48, 48);
|
|
QIcon itemUpdatedIcon(maskBlackPixels(itemIcon, iconSize, textColor));
|
|
qmattingcb->setItemIcon(i, itemUpdatedIcon);
|
|
}
|
|
else {
|
|
qmattingcb->setItemIcon(i, itemIcon);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawGuiUtil::loadLineStandardsChoices(QComboBox* combo)
|
|
{
|
|
combo->clear();
|
|
std::vector<std::string> choices = LineGenerator::getAvailableLineStandards();
|
|
for (auto& entry : choices) {
|
|
QString qentry = QString::fromStdString(entry);
|
|
combo->addItem(qentry);
|
|
}
|
|
}
|
|
|
|
void DrawGuiUtil::loadLineStyleChoices(QComboBox* combo, LineGenerator* generator)
|
|
{
|
|
combo->clear();
|
|
std::vector<std::string> choices;
|
|
if (generator) {
|
|
choices = generator->getLoadedDescriptions();
|
|
}
|
|
else {
|
|
choices = LineGenerator::getLineDescriptions();
|
|
}
|
|
|
|
auto translationContext = LineName::currentTranslationContext();
|
|
int itemNumber {0};
|
|
for (auto& entry : choices) {
|
|
QString qentry = QCoreApplication::translate(translationContext.c_str(), entry.c_str());
|
|
combo->addItem(qentry);
|
|
if (generator) {
|
|
combo->setItemIcon(itemNumber, iconForLine(itemNumber + 1, generator));
|
|
}
|
|
itemNumber++;
|
|
}
|
|
}
|
|
|
|
void DrawGuiUtil::loadLineGroupChoices(QComboBox* combo)
|
|
{
|
|
combo->clear();
|
|
std::string lgFileName = Preferences::lineGroupFile();
|
|
std::string lgRecord = LineGroup::getGroupNamesFromFile(lgFileName);
|
|
// split collected groups
|
|
std::stringstream ss(lgRecord);
|
|
std::vector<QString> lgNames;
|
|
while (std::getline(ss, lgRecord, ',')) {
|
|
lgNames.push_back(QString::fromStdString(lgRecord));
|
|
}
|
|
// fill the combobox with the found names
|
|
for (auto& name : lgNames) {
|
|
combo->addItem(name);
|
|
}
|
|
}
|
|
|
|
|
|
//! make an icon that shows a sample of lineNumber in the current line standard
|
|
QIcon DrawGuiUtil::iconForLine(size_t lineNumber,
|
|
TechDraw::LineGenerator* generator)
|
|
{
|
|
// Base::Console().message("DGU::iconForLine(lineNumber: %d)\n", lineNumber);
|
|
constexpr int iconSize {64};
|
|
constexpr int borderSize {4};
|
|
constexpr double iconLineWeight {1.0};
|
|
size_t lineCount {4};
|
|
double maxLineLength = iconSize - borderSize * 2.0;
|
|
QBitmap bitmap {iconSize, iconSize};
|
|
bitmap.clear();
|
|
|
|
QPainter painter(&bitmap);
|
|
QPen linePen = generator->getLinePen(lineNumber, iconLineWeight);
|
|
linePen.setDashOffset(0.0);
|
|
linePen.setCapStyle(Qt::FlatCap);
|
|
linePen.setColor(Qt::color1);
|
|
|
|
QSize lineIconSize(iconSize, iconSize);
|
|
|
|
auto curStyleSheet =
|
|
App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow")
|
|
->GetASCII("StyleSheet", "None");
|
|
QColor textColor{Qt::black};
|
|
if (isStyleSheetDark(curStyleSheet)) {
|
|
textColor = Preferences::lightTextColor().asValue<QColor>();
|
|
}
|
|
|
|
// handle simple case of continuous line
|
|
if (linePen.style() == Qt::SolidLine) {
|
|
linePen.setWidthF(iconLineWeight * lineCount);
|
|
painter.setPen(linePen);
|
|
painter.drawLine(borderSize, iconSize / 2, iconSize - borderSize, iconSize / 2);
|
|
if (isStyleSheetDark(curStyleSheet)) {
|
|
QIcon lineItemIcon(bitmap);
|
|
return QIcon(maskBlackPixels(lineItemIcon, lineIconSize, textColor));
|
|
}
|
|
else {
|
|
return QIcon(bitmap);
|
|
}
|
|
}
|
|
|
|
// dashed line
|
|
linePen.setWidthF(iconLineWeight);
|
|
painter.setPen(linePen);
|
|
double yHeight = (iconSize / 2) - (lineCount * iconLineWeight);
|
|
size_t iLine = 0;
|
|
// draw multiple lines to stretch the line vertically without horizontal
|
|
// distortion
|
|
for (; iLine < lineCount; iLine++) {
|
|
painter.drawLine(borderSize, yHeight, maxLineLength, yHeight);
|
|
yHeight += iconLineWeight;
|
|
}
|
|
if (isStyleSheetDark(curStyleSheet)) {
|
|
QIcon lineItemIcon(bitmap);
|
|
return QIcon(maskBlackPixels(lineItemIcon, lineIconSize, textColor));
|
|
}
|
|
else {
|
|
return QIcon(bitmap);
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// validate helper routines
|
|
//===========================================================================
|
|
|
|
// find a page in Selection, Document or CurrentWindow.
|
|
TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd, bool findAny)
|
|
{
|
|
std::vector<std::string> names;
|
|
std::vector<std::string> labels;
|
|
auto docs = App::GetApplication().getDocuments();
|
|
|
|
if (findAny) {
|
|
// find a page in any open document
|
|
std::vector<App::DocumentObject*> foundPageObjects;
|
|
// no page found in the usual places, but we have been asked to search all
|
|
// open documents for a page.
|
|
auto docsAll = App::GetApplication().getDocuments();
|
|
for (auto& doc : docsAll) {
|
|
auto docPages = doc->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
|
|
if (docPages.empty()) {
|
|
// this open document has no TD pages
|
|
continue;
|
|
}
|
|
foundPageObjects.insert(foundPageObjects.end(), docPages.begin(), docPages.end());
|
|
}
|
|
if (foundPageObjects.empty()) {
|
|
QMessageBox::warning(Gui::getMainWindow(),
|
|
QObject::tr("No page found"),
|
|
QObject::tr("No Drawing Pages available."));
|
|
return nullptr;
|
|
}
|
|
|
|
if (foundPageObjects.size() > 1) {
|
|
// multiple pages available, ask for help
|
|
for (auto obj : foundPageObjects) {
|
|
std::string name = obj->getNameInDocument();
|
|
names.push_back(name);
|
|
std::string label = obj->Label.getValue();
|
|
labels.push_back(label);
|
|
}
|
|
DlgPageChooser dlg(labels, names, Gui::getMainWindow());
|
|
if (dlg.exec() == QDialog::Accepted) {
|
|
std::string selName = dlg.getSelection();
|
|
if (selName.empty()) {
|
|
showNoPageMessage();
|
|
return nullptr;
|
|
}
|
|
App::Document* doc = cmd->getDocument();
|
|
return static_cast<TechDraw::DrawPage*>(doc->getObject(selName.c_str()));
|
|
}
|
|
}
|
|
else {
|
|
// only 1 page found
|
|
return static_cast<TechDraw::DrawPage*>(foundPageObjects.front());
|
|
}
|
|
}
|
|
|
|
// check Selection for a page
|
|
std::vector<App::DocumentObject*> selPages =
|
|
Gui::Command::getSelection().getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
|
|
if (selPages.empty()) {
|
|
// no page in selection, try this document
|
|
auto docPages = cmd->getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
|
|
if (docPages.empty()) {
|
|
// we are only to look in this document, and there is no page in this document
|
|
showNoPageMessage();
|
|
return nullptr;
|
|
}
|
|
|
|
if (docPages.size() > 1) {
|
|
// multiple pages in document, use active page if there is one
|
|
auto* w = Gui::getMainWindow();
|
|
auto* mv = w->activeWindow();
|
|
auto* mvp = qobject_cast<MDIViewPage*>(mv);
|
|
if (mvp) {
|
|
QGSPage* qp = mvp->getViewProviderPage()->getQGSPage();
|
|
return qp->getDrawPage();
|
|
}
|
|
|
|
// none of pages in document is active, ask for help
|
|
for (auto obj : docPages) {
|
|
std::string name = obj->getNameInDocument();
|
|
names.push_back(name);
|
|
std::string label = obj->Label.getValue();
|
|
labels.push_back(label);
|
|
}
|
|
DlgPageChooser dlg(labels, names, Gui::getMainWindow());
|
|
if (dlg.exec() == QDialog::Accepted) {
|
|
std::string selName = dlg.getSelection();
|
|
if (selName.empty()) {
|
|
showNoPageMessage();
|
|
return nullptr;
|
|
}
|
|
App::Document* doc = cmd->getDocument();
|
|
return static_cast<TechDraw::DrawPage*>(doc->getObject(selName.c_str()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// only 1 page in document - use it
|
|
return static_cast<TechDraw::DrawPage*>(docPages.front());
|
|
}
|
|
|
|
if (selPages.size() > 1) {
|
|
// multiple pages in selection
|
|
for (auto obj : selPages) {
|
|
std::string name = obj->getNameInDocument();
|
|
names.push_back(name);
|
|
std::string label = obj->Label.getValue();
|
|
labels.push_back(label);
|
|
}
|
|
DlgPageChooser dlg(labels, names, Gui::getMainWindow());
|
|
if (dlg.exec() == QDialog::Accepted) {
|
|
std::string selName = dlg.getSelection();
|
|
if (selName.empty()) {
|
|
showNoPageMessage();
|
|
return nullptr;
|
|
}
|
|
App::Document* doc = cmd->getDocument();
|
|
return static_cast<TechDraw::DrawPage*>(doc->getObject(selName.c_str()));
|
|
}
|
|
}
|
|
else {
|
|
// exactly 1 page in selection, use it
|
|
return static_cast<TechDraw::DrawPage*>(selPages.front());
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void DrawGuiUtil::showNoPageMessage()
|
|
{
|
|
QMessageBox::warning(Gui::getMainWindow(),
|
|
QObject::tr("No page selected"),
|
|
QObject::tr("This function needs a page."));
|
|
}
|
|
|
|
bool DrawGuiUtil::isDraftObject(App::DocumentObject* obj)
|
|
{
|
|
bool result = false;
|
|
App::PropertyPythonObject* proxy =
|
|
dynamic_cast<App::PropertyPythonObject*>(obj->getPropertyByName("Proxy"));
|
|
|
|
if (proxy) {
|
|
// if no proxy, can not be Draft obj
|
|
// if has proxy, might be Draft obj
|
|
std::stringstream ss;
|
|
Py::Object proxyObj = proxy->getValue();
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (proxyObj.hasAttr("__module__")) {
|
|
Py::String mod(proxyObj.getAttr("__module__"));
|
|
ss << (std::string)mod;
|
|
if (ss.str().find("Draft") != std::string::npos) {
|
|
result = true;
|
|
}
|
|
else if (ss.str().find("draft") != std::string::npos) {
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e; // extract the Python error text
|
|
e.reportException();
|
|
result = false;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DrawGuiUtil::isArchObject(App::DocumentObject* obj)
|
|
{
|
|
bool result = false;
|
|
App::PropertyPythonObject* proxy =
|
|
dynamic_cast<App::PropertyPythonObject*>(obj->getPropertyByName("Proxy"));
|
|
|
|
if (proxy) {
|
|
// if no proxy, can not be Arch obj
|
|
// if has proxy, might be Arch obj
|
|
Py::Object proxyObj = proxy->getValue();
|
|
std::stringstream ss;
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (proxyObj.hasAttr("__module__")) {
|
|
Py::String mod(proxyObj.getAttr("__module__"));
|
|
ss << (std::string)mod;
|
|
// does this have to be an ArchSection, or can it be any Arch object?
|
|
if (ss.str().find("Arch") != std::string::npos) {
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e; // extract the Python error text
|
|
e.reportException();
|
|
result = false;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DrawGuiUtil::isArchSection(App::DocumentObject* obj)
|
|
{
|
|
bool result = false;
|
|
App::PropertyPythonObject* proxy =
|
|
dynamic_cast<App::PropertyPythonObject*>(obj->getPropertyByName("Proxy"));
|
|
|
|
if (proxy) {
|
|
// if no proxy, can not be Arch obj
|
|
// if has proxy, might be Arch obj
|
|
Py::Object proxyObj = proxy->getValue();
|
|
std::stringstream ss;
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (proxyObj.hasAttr("__module__")) {
|
|
Py::String mod(proxyObj.getAttr("__module__"));
|
|
ss << (std::string)mod;
|
|
// does this have to be an ArchSection, or can it be other Arch objects?
|
|
if (ss.str().find("ArchSectionPlane") != std::string::npos) {
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e; // extract the Python error text
|
|
e.reportException();
|
|
result = false;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DrawGuiUtil::needPage(Gui::Command* cmd, bool findAny)
|
|
{
|
|
if (findAny) {
|
|
// look for any page in any open document
|
|
auto docsAll = App::GetApplication().getDocuments();
|
|
for (auto& doc : docsAll) {
|
|
auto docPages = doc->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
|
|
if (docPages.empty()) {
|
|
// this open document has no TD pages
|
|
continue;
|
|
}
|
|
else {
|
|
// found at least 1 page
|
|
return true;
|
|
}
|
|
}
|
|
// did not find any pages
|
|
return false;
|
|
}
|
|
|
|
// need a Document and a Page
|
|
if (cmd->hasActiveDocument()) {
|
|
auto drawPageType(TechDraw::DrawPage::getClassTypeId());
|
|
auto selPages = cmd->getDocument()->getObjectsOfType(drawPageType);
|
|
return !selPages.empty();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DrawGuiUtil::needView(Gui::Command* cmd, bool partOnly)
|
|
{
|
|
bool haveView = false;
|
|
if (cmd->hasActiveDocument()) {
|
|
if (partOnly) {
|
|
auto drawPartType(TechDraw::DrawViewPart::getClassTypeId());
|
|
auto selParts = cmd->getDocument()->getObjectsOfType(drawPartType);
|
|
if (!selParts.empty()) {
|
|
haveView = true;
|
|
}
|
|
}
|
|
else {
|
|
auto drawViewType(TechDraw::DrawView::getClassTypeId());
|
|
auto selParts = cmd->getDocument()->getObjectsOfType(drawViewType);
|
|
if (!selParts.empty()) {
|
|
haveView = true;
|
|
}
|
|
}
|
|
}
|
|
return haveView;
|
|
}
|
|
|
|
void DrawGuiUtil::dumpRectF(const char* text, const QRectF& r)
|
|
{
|
|
Base::Console().message("DUMP - dumpRectF - %s\n", text);
|
|
double left = r.left();
|
|
double right = r.right();
|
|
double top = r.top();
|
|
double bottom = r.bottom();
|
|
Base::Console().message("Extents: L: %.3f, R: %.3f, T: %.3f, B: %.3f\n",
|
|
left,
|
|
right,
|
|
top,
|
|
bottom);
|
|
Base::Console().message("Size: W: %.3f H: %.3f\n", r.width(), r.height());
|
|
Base::Console().message("Centre: (%.3f, %.3f)\n", r.center().x(), r.center().y());
|
|
}
|
|
|
|
void DrawGuiUtil::dumpPointF(const char* text, const QPointF& p)
|
|
{
|
|
Base::Console().message("DUMP - dumpPointF - %s\n", text);
|
|
Base::Console().message("Point: (%.3f, %.3f)\n", p.x(), p.y());
|
|
}
|
|
|
|
std::pair<Base::Vector3d, Base::Vector3d> DrawGuiUtil::get3DDirAndRot()
|
|
{
|
|
std::pair<Base::Vector3d, Base::Vector3d> result;
|
|
Base::Vector3d viewDir(0.0, -1.0, 0.0); // default to front
|
|
Base::Vector3d viewUp(0.0, 0.0, 1.0); // default to top
|
|
Base::Vector3d viewRight(1.0, 0.0, 0.0); // default to right
|
|
std::list<Gui::MDIView*> mdis = Gui::Application::Instance->activeDocument()->getMDIViews();
|
|
Gui::View3DInventor* view;
|
|
Gui::View3DInventorViewer* viewer = nullptr;
|
|
for (auto& m : mdis) { // find the 3D viewer
|
|
view = dynamic_cast<Gui::View3DInventor*>(m);
|
|
if (view) {
|
|
viewer = view->getViewer();
|
|
break;
|
|
}
|
|
}
|
|
if (!viewer) {
|
|
return std::make_pair(viewDir, viewRight);
|
|
}
|
|
|
|
// Coin is giving us a values like 0.000000134439 instead of 0.000000000000.
|
|
// This small difference caused circles to be projected as ellipses among other
|
|
// problems.
|
|
// Since SbVec3f is single precision floating point, it is only good to 6-9
|
|
// significant decimal digits, and the rest of TechDraw works with doubles
|
|
// that are good to 15-18 significant decimal digits.
|
|
// But. When a float is promoted to double the value is supposed to be unchanged!
|
|
// So where do the garbage digits come from???
|
|
// In any case, if we restrict directions to 6 digits, we avoid the problem.
|
|
int digits(6);
|
|
SbVec3f dvec = viewer->getViewDirection();
|
|
double dvecX = roundToDigits(dvec[0], digits);
|
|
double dvecY = roundToDigits(dvec[1], digits);
|
|
double dvecZ = roundToDigits(dvec[2], digits);
|
|
viewDir = Base::Vector3d(dvecX, dvecY, dvecZ);
|
|
viewDir = viewDir * (-1.0); // Inventor dir is opposite TD projection dir
|
|
|
|
SbVec3f upvec = viewer->getUpDirection();
|
|
double upvecX = roundToDigits(upvec[0], digits);
|
|
double upvecY = roundToDigits(upvec[1], digits);
|
|
double upvecZ = roundToDigits(upvec[2], digits);
|
|
viewUp = Base::Vector3d(upvecX, upvecY, upvecZ);
|
|
|
|
Base::Vector3d right = viewUp.Cross(viewDir);
|
|
|
|
result = std::make_pair(viewDir, right);
|
|
return result;
|
|
}
|
|
|
|
std::pair<Base::Vector3d, Base::Vector3d> DrawGuiUtil::getProjDirFromFace(App::DocumentObject* obj,
|
|
std::string faceName)
|
|
{
|
|
std::pair<Base::Vector3d, Base::Vector3d> d3Dirs = get3DDirAndRot();
|
|
std::pair<Base::Vector3d, Base::Vector3d> dirs;
|
|
dirs.first = Base::Vector3d(0.0, 0.0, 1.0); // set a default
|
|
dirs.second = Base::Vector3d(1.0, 0.0, 0.0);
|
|
Base::Vector3d projDir, rotVec;
|
|
projDir = d3Dirs.first;
|
|
rotVec = d3Dirs.second;
|
|
|
|
auto ts = Part::Feature::getShape(obj,
|
|
Part::ShapeOption::NeedSubElement
|
|
| Part::ShapeOption::ResolveLink
|
|
| Part::ShapeOption::Transform,
|
|
faceName.c_str());
|
|
|
|
if (ts.IsNull() || ts.ShapeType() != TopAbs_FACE) {
|
|
Base::Console().warning("getProjDirFromFace(%s) is not a Face\n", faceName.c_str());
|
|
return dirs;
|
|
}
|
|
|
|
const TopoDS_Face& face = TopoDS::Face(ts);
|
|
TopAbs_Orientation orient = face.Orientation();
|
|
BRepAdaptor_Surface adapt(face);
|
|
|
|
double u1 = adapt.FirstUParameter();
|
|
double u2 = adapt.LastUParameter();
|
|
double v1 = adapt.FirstVParameter();
|
|
double v2 = adapt.LastVParameter();
|
|
double uMid = (u1 + u2) / 2.0;
|
|
double vMid = (v1 + v2) / 2.0;
|
|
|
|
BRepLProp_SLProps props(adapt, uMid, vMid, 2, Precision::Confusion());
|
|
if (props.IsNormalDefined()) {
|
|
gp_Dir vec = props.Normal();
|
|
projDir = Base::Vector3d(vec.X(), vec.Y(), vec.Z());
|
|
if (orient != TopAbs_FORWARD) {
|
|
projDir = projDir * (-1.0);
|
|
}
|
|
}
|
|
|
|
return std::make_pair(projDir, rotVec);
|
|
}
|
|
|
|
// converts original value to one with only digits significant figures
|
|
double DrawGuiUtil::roundToDigits(double original, int digits)
|
|
{
|
|
double factor = std::pow(10.0, digits);
|
|
double temp = original * factor;
|
|
double rounded = std::round(temp);
|
|
temp = rounded / factor;
|
|
return temp;
|
|
}
|
|
|
|
// Returns true if the item or any of its descendants is selected
|
|
bool DrawGuiUtil::isSelectedInTree(QGraphicsItem* item)
|
|
{
|
|
if (item) {
|
|
if (item->isSelected()) {
|
|
return true;
|
|
}
|
|
|
|
for (QGraphicsItem* child : item->childItems()) {
|
|
if (isSelectedInTree(child)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Selects or deselects the item and all its descendants
|
|
void DrawGuiUtil::setSelectedTree(QGraphicsItem* item, bool selected)
|
|
{
|
|
if (item) {
|
|
item->setSelected(selected);
|
|
|
|
for (QGraphicsItem* child : item->childItems()) {
|
|
setSelectedTree(child, selected);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//! convert point from scene coords to mm and conventional Y axis (page coords).
|
|
Base::Vector3d DrawGuiUtil::fromSceneCoords(const Base::Vector3d& sceneCoord, bool invert)
|
|
{
|
|
Base::Vector3d result;
|
|
if (invert) {
|
|
result = Rez::appX(DU::invertY(sceneCoord));
|
|
} else {
|
|
result = Rez::appX(sceneCoord);
|
|
}
|
|
return Rez::appX(DU::invertY(sceneCoord));
|
|
}
|
|
|
|
//! convert point from printed page coords to scene units (Rez(mm) and inverted Y axis (scene coords)
|
|
Base::Vector3d DrawGuiUtil::toSceneCoords(const Base::Vector3d& pageCoord, bool invert)
|
|
{
|
|
Base::Vector3d result;
|
|
if (invert) {
|
|
result = Rez::guiX(DU::invertY(pageCoord));
|
|
} else {
|
|
result = Rez::guiX(pageCoord);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//! convert unscaled, unrotated point to scaled, rotated view coordinates
|
|
Base::Vector3d DrawGuiUtil::toGuiPoint(DrawView* obj, const Base::Vector3d& toConvert)
|
|
{
|
|
Base::Vector3d result{toConvert};
|
|
auto rotDegrees = obj->Rotation.getValue();
|
|
if (rotDegrees != 0.0) {
|
|
result.RotateZ(Base::toRadians(rotDegrees));
|
|
}
|
|
result *= obj->getScale();
|
|
result = DU::invertY(result);
|
|
result = Rez::guiX(result);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//! true if targetObj is in the selection list
|
|
bool DrawGuiUtil::findObjectInSelection(const std::vector<Gui::SelectionObject>& selection,
|
|
const App::DocumentObject& targetObject)
|
|
{
|
|
for (auto& selObj : selection) {
|
|
if (&targetObject == selObj.getObject()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::string> DrawGuiUtil::getSubsForSelectedObject(const std::vector<Gui::SelectionObject>& selection,
|
|
App::DocumentObject* selectedObj)
|
|
{
|
|
for (auto& selObj : selection) {
|
|
if (selectedObj == selObj.getObject()) {
|
|
return selObj.getSubNames();
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
bool DrawGuiUtil::isStyleSheetDark(std::string curStyleSheet)
|
|
{
|
|
if (curStyleSheet.find("dark") != std::string::npos ||
|
|
curStyleSheet.find("Dark") != std::string::npos) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
QIcon DrawGuiUtil::maskBlackPixels(QIcon itemIcon, QSize iconSize, QColor textColor)
|
|
{
|
|
QPixmap originalPix = itemIcon.pixmap(iconSize, QIcon::Mode::Normal, QIcon::State::On);
|
|
QPixmap filler(iconSize);
|
|
filler.fill(QColor(textColor));
|
|
filler.setMask(originalPix.createMaskFromColor(Qt::black, Qt::MaskOutColor));
|
|
return filler;
|
|
}
|
|
|
|
void DrawGuiUtil::rotateToAlign(const QGIEdge* edge, const Base::Vector2d& direction)
|
|
{
|
|
QGIViewPart* view = static_cast<QGIViewPart*>(edge->parentItem());
|
|
DrawViewPart* dvp = static_cast<DrawViewPart*>(view->getViewObject());
|
|
BaseGeomPtr bg = dvp->getEdgeGeometry().at(edge->getProjIndex());
|
|
std::vector<Base::Vector3d> endPoints = bg->findEndPoints();
|
|
Base::Vector3d oldDirection3d = endPoints.at(0) - endPoints.at(1);
|
|
Base::Vector2d oldDirection2d(oldDirection3d.x, oldDirection3d.y);
|
|
rotateToAlign(dvp, oldDirection2d, direction);
|
|
}
|
|
|
|
//! The view of p1 and p2 will be rotated to make p1 and p2 aligned with direction (for instance horizontalle aligned)
|
|
void DrawGuiUtil::rotateToAlign(const QGIVertex* p1, const QGIVertex* p2, const Base::Vector2d& direction)
|
|
{
|
|
QGIViewPart* view = static_cast<QGIViewPart*>(p1->parentItem());
|
|
if(view != static_cast<QGIViewPart*>(p2->parentItem())) {
|
|
Base::Console().error("Vertexes have to be from the same view!");
|
|
}
|
|
|
|
Base::Vector2d oldDirection = p2->vector2dBetweenPoints(p1);
|
|
DrawViewPart* dvp = static_cast<DrawViewPart*>(view->getViewObject());
|
|
rotateToAlign(dvp, oldDirection, direction);
|
|
}
|
|
|
|
void DrawGuiUtil::rotateToAlign(DrawViewPart* view, const Base::Vector2d& oldDirection, const Base::Vector2d& newDirection)
|
|
{
|
|
// If pointing counterclockwise, we need to rotate clockwise
|
|
// If pointing clockwise, we need to rotate counter clockwise
|
|
int cw = 1;
|
|
if(newDirection.Angle() > oldDirection.Angle()) {
|
|
cw = -1;
|
|
}
|
|
|
|
double toRotate = newDirection.GetAngle(oldDirection);
|
|
// Radians to degrees
|
|
toRotate = Base::toDegrees(toRotate);
|
|
|
|
// Rotate least amount possible
|
|
if(toRotate > 90) {
|
|
// Instead of rotating 145 degrees to match direction
|
|
// we only rotate -35 degrees
|
|
toRotate = toRotate - 180;
|
|
}
|
|
else if(toRotate < -90) {
|
|
toRotate = toRotate + 180;
|
|
}
|
|
|
|
double oldRotation = view->Rotation.getValue();
|
|
view->Rotation.setValue(oldRotation + toRotate * cw);
|
|
}
|