Gui: allow to change orientation of image plane, implement two-side rendering

This commit is contained in:
wmayer
2023-03-20 12:02:37 +01:00
parent 4d6b2c4a14
commit 9b38980b7a
8 changed files with 599 additions and 41 deletions

View File

@@ -345,6 +345,7 @@ SET(Gui_UIC_SRCS
Placement.ui
TextureMapping.ui
TaskView/TaskAppearance.ui
TaskView/TaskOrientation.ui
TaskView/TaskSelectLinkProperty.ui
TaskElementColors.ui
DlgObjectSelection.ui
@@ -726,6 +727,9 @@ SET(Task_View_SRCS
TaskView/TaskAppearance.cpp
TaskView/TaskAppearance.h
TaskView/TaskAppearance.ui
TaskView/TaskOrientation.cpp
TaskView/TaskOrientation.h
TaskView/TaskOrientation.ui
TaskView/TaskSelectLinkProperty.cpp
TaskView/TaskSelectLinkProperty.h
TaskView/TaskSelectLinkProperty.ui
@@ -794,6 +798,7 @@ SOURCE_GROUP("Widget\\QSintActionPanel\\Mocs" FILES ${qsint_MOC_SRCS})
# The 3d view
SET(View3D_CPP_SRCS
Camera.cpp
Flag.cpp
GLBuffer.cpp
GLPainter.cpp
@@ -824,6 +829,7 @@ SET(View3D_CPP_SRCS
)
SET(View3D_SRCS
${View3D_CPP_SRCS}
Camera.h
Flag.h
GLBuffer.h
GLPainter.h
@@ -1233,6 +1239,7 @@ if(MSVC)
propertyeditor/PropertyItemDelegate.cpp
propertyeditor/PropertyModel.cpp
TaskView/TaskAppearance.cpp
TaskView/TaskOrientation.cpp
TaskView/TaskSelectLinkProperty.cpp
TaskView/TaskEditControl.cpp
TaskView/TaskView.cpp

View File

@@ -24,6 +24,7 @@
#include "PreCompiled.h"
#include "Camera.h"
#include "Utilities.h"
using namespace Gui;
@@ -92,51 +93,109 @@ vz.z=0
https://de.wikipedia.org/wiki/Arkussinus_und_Arkuskosinus
*/
SbRotation Camera::top()
{
return SbRotation(0, 0, 0, 1);
}
SbRotation Camera::bottom()
{
return SbRotation(1, 0, 0, 0);
}
SbRotation Camera::front()
{
auto root = (float)(sqrt(2.0)/2.0);
return SbRotation(root, 0, 0, root);
}
SbRotation Camera::rear()
{
auto root = (float)(sqrt(2.0)/2.0);
return SbRotation(0, root, root, 0);
}
SbRotation Camera::right()
{
return SbRotation(0.5, 0.5, 0.5, 0.5);
}
SbRotation Camera::left()
{
return SbRotation(-0.5, 0.5, 0.5, -0.5);
}
SbRotation Camera::isometric()
{
//from math import sqrt, degrees, asin
//p1=App.Rotation(App.Vector(1,0,0),45)
//p2=App.Rotation(App.Vector(0,0,1),-45)
//p3=p2.multiply(p1)
//return SbRotation(0.353553f, -0.146447f, -0.353553f, 0.853553f);
//from math import sqrt, degrees, asin
//p1=App.Rotation(App.Vector(1,0,0),90)
//p2=App.Rotation(App.Vector(0,0,1),135)
//p3=App.Rotation(App.Vector(-1,1,0),degrees(asin(-sqrt(1.0/3.0))))
//p4=p3.multiply(p2).multiply(p1)
//return SbRotation(0.17592, 0.424708, 0.820473, 0.339851);
//from math import sqrt, degrees, asin
//p1=App.Rotation(App.Vector(1,0,0),90)
//p2=App.Rotation(App.Vector(0,0,1),45)
//#p3=App.Rotation(App.Vector(1,1,0),45)
//p3=App.Rotation(App.Vector(1,1,0),degrees(asin(-sqrt(1.0/3.0))))
//p4=p3.multiply(p2).multiply(p1)
return SbRotation(0.424708f, 0.17592f, 0.339851f, 0.820473f);
}
SbRotation Camera::dimetric()
{
return SbRotation(0.567952f, 0.103751f, 0.146726f, 0.803205f);
}
SbRotation Camera::trimetric()
{
return SbRotation(0.446015f, 0.119509f, 0.229575f, 0.856787f);
}
SbRotation Camera::rotation(Camera::Orientation view)
{
switch (view) {
case Top:
return SbRotation(0, 0, 0, 1);
return top();
case Bottom:
return SbRotation(1, 0, 0, 0);
case Front: {
auto root = (float)(sqrt(2.0)/2.0);
return SbRotation(root, 0, 0, root);
}
case Rear: {
auto root = (float)(sqrt(2.0)/2.0);
return SbRotation(0, root, root, 0);
}
return bottom();
case Front:
return front();
case Rear:
return rear();
case Right:
return SbRotation(0.5, 0.5, 0.5, 0.5);
return right();
case Left:
return SbRotation(-0.5, 0.5, 0.5, -0.5);
return left();
case Isometric:
//from math import sqrt, degrees, asin
//p1=App.Rotation(App.Vector(1,0,0),45)
//p2=App.Rotation(App.Vector(0,0,1),-45)
//p3=p2.multiply(p1)
//return SbRotation(0.353553f, -0.146447f, -0.353553f, 0.853553f);
//from math import sqrt, degrees, asin
//p1=App.Rotation(App.Vector(1,0,0),90)
//p2=App.Rotation(App.Vector(0,0,1),135)
//p3=App.Rotation(App.Vector(-1,1,0),degrees(asin(-sqrt(1.0/3.0))))
//p4=p3.multiply(p2).multiply(p1)
//return SbRotation(0.17592, 0.424708, 0.820473, 0.339851);
//from math import sqrt, degrees, asin
//p1=App.Rotation(App.Vector(1,0,0),90)
//p2=App.Rotation(App.Vector(0,0,1),45)
//#p3=App.Rotation(App.Vector(1,1,0),45)
//p3=App.Rotation(App.Vector(1,1,0),degrees(asin(-sqrt(1.0/3.0))))
//p4=p3.multiply(p2).multiply(p1)
return SbRotation(0.424708f, 0.17592f, 0.339851f, 0.820473f);
return isometric();
case Dimetric:
return SbRotation(0.567952f, 0.103751f, 0.146726f, 0.803205f);
return dimetric();
case Trimetric:
return SbRotation(0.446015f, 0.119509f, 0.229575f, 0.856787f);
return trimetric();
default:
return SbRotation(0, 0, 0, 1);
return top();
}
}
Base::Rotation Camera::convert(Camera::Orientation view)
{
return convert(Camera::rotation(view));
}
Base::Rotation Camera::convert(const SbRotation& rot)
{
return Base::convertTo<Base::Rotation>(rot);
}
SbRotation Camera::convert(const Base::Rotation& rot)
{
return Base::convertTo<SbRotation>(rot);
}

View File

@@ -45,7 +45,20 @@ public:
Trimetric,
};
static SbRotation top();
static SbRotation bottom();
static SbRotation front();
static SbRotation rear();
static SbRotation right();
static SbRotation left();
static SbRotation isometric();
static SbRotation dimetric();
static SbRotation trimetric();
static SbRotation rotation(Orientation view);
static Base::Rotation convert(Orientation view);
static Base::Rotation convert(const SbRotation&);
static SbRotation convert(const Base::Rotation&);
};
}

View File

@@ -0,0 +1,227 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2023 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QDialog>
# include <map>
#endif
#include <Base/Tools.h>
#include <App/Document.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Camera.h>
#include <Gui/TaskView/TaskView.h>
#include "TaskOrientation.h"
#include "ui_TaskOrientation.h"
using namespace Gui;
TaskOrientation::TaskOrientation(App::GeoFeature* obj, QWidget* parent)
: QWidget(parent)
, ui(new Ui_TaskOrientation)
, feature(obj)
{
ui->setupUi(this);
connect(ui->Reverse_checkBox, &QCheckBox::clicked, this, &TaskOrientation::onPreview);
connect(ui->XY_radioButton , &QRadioButton::clicked, this, &TaskOrientation::onPreview);
connect(ui->XZ_radioButton , &QRadioButton::clicked, this, &TaskOrientation::onPreview);
connect(ui->YZ_radioButton , &QRadioButton::clicked, this, &TaskOrientation::onPreview);
connect(ui->Offset_doubleSpinBox, qOverload<double>(&QuantitySpinBox::valueChanged),
this, &TaskOrientation::onPreview);
}
TaskOrientation::~TaskOrientation()
{
}
void TaskOrientation::open()
{
if (!feature.expired()) {
App::Document* doc = feature->getDocument();
doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Change orientation"));
restore(feature->Placement.getValue());
}
}
void TaskOrientation::accept()
{
if (!feature.expired()) {
App::Document* doc = feature->getDocument();
doc->commitTransaction();
}
}
void TaskOrientation::reject()
{
if (!feature.expired()) {
App::Document* doc = feature->getDocument();
doc->abortTransaction();
}
}
void TaskOrientation::onPreview()
{
updateIcon();
updatePlacement();
}
void TaskOrientation::restore(const Base::Placement& plm)
{
auto isReversed = [](Camera::Orientation type) {
return (type == Camera::Bottom) ||
(type == Camera::Rear) ||
(type == Camera::Left);
};
std::map<Camera::Orientation, Base::Rotation> rotations {
{Camera::Top, Camera::convert(Camera::Top)},
{Camera::Bottom, Camera::convert(Camera::Bottom)},
{Camera::Front, Camera::convert(Camera::Front)},
{Camera::Rear, Camera::convert(Camera::Rear)},
{Camera::Right, Camera::convert(Camera::Right)},
{Camera::Left, Camera::convert(Camera::Left)}
};
Base::Rotation rot = plm.getRotation();
Base::Vector3d pos = plm.getPosition();
double prec = 1.0e-5;
for (const auto& it : rotations) {
if (rot.isSame(it.second, prec)) {
if (it.first == Camera::Top ||
it.first == Camera::Bottom) {
ui->XY_radioButton->setChecked(true);
ui->Offset_doubleSpinBox->setValue(pos.z);
}
else if (it.first == Camera::Front ||
it.first == Camera::Rear) {
ui->XZ_radioButton->setChecked(true);
ui->Offset_doubleSpinBox->setValue(pos.y);
}
else if (it.first == Camera::Right ||
it.first == Camera::Left) {
ui->YZ_radioButton->setChecked(true);
ui->Offset_doubleSpinBox->setValue(pos.x);
}
if (isReversed(it.first)) {
ui->Reverse_checkBox->setChecked(true);
}
}
}
onPreview();
}
void TaskOrientation::updatePlacement()
{
Base::Placement Pos;
double offset = ui->Offset_doubleSpinBox->value().getValue();
bool reverse = ui->Reverse_checkBox->isChecked();
if (ui->XY_radioButton->isChecked()) {
if (!reverse) {
Pos = Base::Placement(Base::Vector3d(0, 0, offset),
Camera::convert(Camera::Top));
}
else {
Pos = Base::Placement(Base::Vector3d(0, 0, offset),
Camera::convert(Camera::Bottom));
}
}
else if (ui->XZ_radioButton->isChecked()) {
if (!reverse) {
Pos = Base::Placement(Base::Vector3d(0, offset, 0),
Camera::convert(Camera::Front));
}
else {
Pos = Base::Placement(Base::Vector3d(0, offset, 0),
Camera::convert(Camera::Rear));
}
}
else if (ui->YZ_radioButton->isChecked()) {
if (!reverse) {
Pos = Base::Placement(Base::Vector3d(offset, 0, 0),
Camera::convert(Camera::Right));
}
else {
Pos = Base::Placement(Base::Vector3d(offset, 0, 0),
Camera::convert(Camera::Left));
}
}
if (!feature.expired()) {
feature->Placement.setValue(Pos);
}
}
void TaskOrientation::updateIcon()
{
std::string icon;
bool reverse = ui->Reverse_checkBox->isChecked();
if (ui->XY_radioButton->isChecked()) {
icon = reverse ? "view-bottom" : "view-top";
}
else if (ui->XZ_radioButton->isChecked()) {
icon = reverse ? "view-rear" : "view-front";
}
else if (ui->YZ_radioButton->isChecked()) {
icon = reverse ? "view-left" : "view-right";
}
ui->previewLabel->setPixmap(
Gui::BitmapFactory().pixmapFromSvg(icon.c_str(),
ui->previewLabel->size()));
}
// ----------------------------------------------------------------------------
TaskOrientationDialog::TaskOrientationDialog(App::GeoFeature* obj)
{
widget = new TaskOrientation(obj);
Gui::TaskView::TaskBox* taskbox = new Gui::TaskView::TaskBox(
QPixmap(), widget->windowTitle(), true, nullptr);
taskbox->groupLayout()->addWidget(widget);
Content.push_back(taskbox);
}
void TaskOrientationDialog::open()
{
widget->open();
}
bool TaskOrientationDialog::accept()
{
widget->accept();
return true;
}
bool TaskOrientationDialog::reject()
{
widget->reject();
return true;
}
#include "moc_TaskOrientation.cpp"

View File

@@ -0,0 +1,80 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2023 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#ifndef GUI_TASKORIENTATION_H
#define GUI_TASKORIENTATION_H
#include <Gui/TaskView/TaskDialog.h>
#include <App/DocumentObserver.h>
#include <App/GeoFeature.h>
#include <memory>
namespace Gui {
class Ui_TaskOrientation;
class TaskOrientation : public QWidget
{
Q_OBJECT
public:
explicit TaskOrientation(App::GeoFeature* obj, QWidget* parent = nullptr);
~TaskOrientation() override;
void open();
void accept();
void reject();
private:
void restore(const Base::Placement&);
void onPreview();
void updateIcon();
void updatePlacement();
private:
std::unique_ptr<Ui_TaskOrientation> ui;
App::WeakPtrT<App::GeoFeature> feature;
};
class TaskOrientationDialog : public Gui::TaskView::TaskDialog
{
Q_OBJECT
public:
explicit TaskOrientationDialog(App::GeoFeature* obj);
public:
void open() override;
bool accept() override;
bool reject() override;
QDialogButtonBox::StandardButtons getStandardButtons() const override {
return QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
}
private:
TaskOrientation* widget;
};
}
#endif // GUI_TASKORIENTATION_H

View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::TaskOrientation</class>
<widget class="QWidget" name="Gui::TaskOrientation">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>194</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Choose orientation</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Planes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="XY_radioButton">
<property name="text">
<string>XY-Plane</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="XZ_radioButton">
<property name="text">
<string>XZ-Plane</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="YZ_radioButton">
<property name="text">
<string>YZ-Plane</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="Reverse_checkBox">
<property name="text">
<string>Reverse direction</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="previewLabel">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="text">
<string notr="true">Preview</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Offset:</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::QuantitySpinBox" name="Offset_doubleSpinBox" native="true">
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<property name="minimum" stdset="0">
<double>-999999999.000000000000000</double>
</property>
<property name="maximum" stdset="0">
<double>999999999.000000000000000</double>
</property>
<property name="singleStep" stdset="0">
<double>1.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -23,20 +23,25 @@
#include "PreCompiled.h"
#ifndef _PreComp_
# include <sstream>
# include <QAction>
# include <QFileInfo>
# include <QImage>
# include <QMenu>
# include <QString>
# include <Inventor/nodes/SoCoordinate3.h>
# include <Inventor/nodes/SoFaceSet.h>
# include <Inventor/nodes/SoMaterial.h>
# include <Inventor/nodes/SoSeparator.h>
# include <Inventor/nodes/SoShapeHints.h>
# include <Inventor/nodes/SoTexture2.h>
# include <Inventor/nodes/SoTextureCoordinate2.h>
#endif
#include <App/Document.h>
#include <Gui/ActionFunction.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Control.h>
#include <Gui/TaskView/TaskOrientation.h>
#include <App/ImagePlane.h>
#include "ViewProviderImagePlane.h"
@@ -46,15 +51,24 @@ using namespace Gui;
PROPERTY_SOURCE(Gui::ViewProviderImagePlane, Gui::ViewProviderGeometryObject)
const char* ViewProviderImagePlane::LightingEnums[]= {"One side", "Two side", nullptr};
ViewProviderImagePlane::ViewProviderImagePlane()
{
ADD_PROPERTY_TYPE(Lighting,(1L), "Object Style", App::Prop_None, "Set object lighting.");
Lighting.setEnums(LightingEnums);
texture = new SoTexture2;
texture->ref();
pcCoords = new SoCoordinate3();
pcCoords->ref();
shapeHints = new SoShapeHints;
shapeHints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
shapeHints->vertexOrdering = SoShapeHints::COUNTERCLOCKWISE;
shapeHints->ref();
sPixmap = "image-plane";
}
@@ -62,6 +76,7 @@ ViewProviderImagePlane::~ViewProviderImagePlane()
{
pcCoords->unref();
texture->unref();
shapeHints->unref();
}
void ViewProviderImagePlane::attach(App::DocumentObject *pcObj)
@@ -85,6 +100,7 @@ void ViewProviderImagePlane::attach(App::DocumentObject *pcObj)
texture->model = SoTexture2::MODULATE;
planesep->addChild(texture);
planesep->addChild(shapeHints);
planesep->addChild(pcShapeMaterial);
// plane
@@ -113,6 +129,42 @@ std::vector<std::string> ViewProviderImagePlane::getDisplayModes() const
return StrList;
}
void ViewProviderImagePlane::onChanged(const App::Property* prop)
{
if (prop == &Lighting) {
if (Lighting.getValue() == 0)
shapeHints->vertexOrdering = SoShapeHints::UNKNOWN_ORDERING;
else
shapeHints->vertexOrdering = SoShapeHints::COUNTERCLOCKWISE;
}
ViewProviderGeometryObject::onChanged(prop);
}
void ViewProviderImagePlane::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
{
ViewProviderGeometryObject::setupContextMenu(menu, receiver, member);
Gui::ActionFunction* func = new Gui::ActionFunction(menu);
QAction* orient = menu->addAction(QObject::tr("Change orientation..."));
func->trigger(orient, std::bind(&ViewProviderImagePlane::changeOrientation, this));
QAction* scale = menu->addAction(QObject::tr("Scale image..."));
scale->setIcon(QIcon(QLatin1String("images:image-scaling.svg")));
func->trigger(scale, std::bind(&ViewProviderImagePlane::scaleImage, this));
}
void ViewProviderImagePlane::changeOrientation()
{
Gui::Control().showDialog(new TaskOrientationDialog(
dynamic_cast<App::GeoFeature*>(getObject())
));
}
void ViewProviderImagePlane::scaleImage()
{
}
bool ViewProviderImagePlane::loadSvg(const char* filename, float x, float y, QImage& img)
{
QFileInfo fi(QString::fromUtf8(filename));

View File

@@ -28,6 +28,7 @@
class SoCoordinate3;
class SoDrawStyle;
class SoShapeHints;
class SoTexture2;
class QImage;
@@ -39,24 +40,29 @@ class GuiExport ViewProviderImagePlane : public Gui::ViewProviderGeometryObject
PROPERTY_HEADER_WITH_OVERRIDE(Gui::ViewProviderImagePlane);
public:
/// constructor.
ViewProviderImagePlane();
/// destructor.
~ViewProviderImagePlane() override;
App::PropertyEnumeration Lighting;
void attach(App::DocumentObject *pcObject) override;
void setDisplayMode(const char* ModeName) override;
std::vector<std::string> getDisplayModes() const override;
void updateData(const App::Property*) override;
void setupContextMenu(QMenu*, QObject*, const char*) override;
void onChanged(const App::Property* prop) override;
private:
bool loadSvg(const char*, float x, float y, QImage& img);
void changeOrientation();
void scaleImage();
protected:
private:
SoCoordinate3 * pcCoords;
SoTexture2 * texture;
};
SoShapeHints * shapeHints;
static const char * LightingEnums[];
};
} //namespace Gui