Вернуться наверх
aco.ifmo.ru photonic
вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


Пример 1.
Задание конструктивных параметов и отображение оптической системы

Реализация функций в диалоге (файл lens.cpp)

///////////////////////////////////////////////////////////////////////////////////////////////////
// lens.cpp
// Пример диалогового окна для задания конструктивных параметров и отображения оптической системы.
// 
// Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru
// Университет ИТМО
///////////////////////////////////////////////////////////////////////////////////////////////////

#include "lens.h"
#include <QDialog>
#include <QPainter>
#include <QMessageBox>
#include <QTextDocument>

#include <QUrl>

#define PI 3.14159265358979

#include <fstream>
#include <cmath>
#include <stdexcept>

using namespace std;
//-------------------------------------------------------------------------------------------------
DQtOpticalSystem::DQtOpticalSystem(QWidget* parent, Qt::WindowFlags f)
    : QDialog(parent, f) 
{
    m_ui.setupUi(this);

    // задаем перехват в текущем классе для всех событий от m_qDrawArea (виджет для рисования)
    m_ui.m_qDrawArea->installEventFilter(this);

    // создаем пункт меню для создания новой поверхности в таблице
    m_pqAddSurface = new QAction("Добавить новую поверхность перед выделенной", this );
    m_pqAddSurface->setShortcut(QKeySequence("Ctrl+A"));
    // создаем пункт меню для удаления поверхности в таблице
    m_pqDeleteSurface = new QAction("Удалить выделенную поверхность", this );
    m_pqDeleteSurface->setShortcut(QKeySequence("Ctrl+D"));
    // создаем контекстное меню для таблицы и записываем туда пункты "добавить" и "удалить"
    m_ui.m_qOSParams->setContextMenuPolicy(Qt::ActionsContextMenu);
    m_ui.m_qOSParams->addAction(m_pqAddSurface);
    m_ui.m_qOSParams->addAction(m_pqDeleteSurface);

    // создаем связь между сигналом(событием) от объекта и функцией-обработкой события
    connect(m_ui.m_pqMainTabs, SIGNAL(currentChanged(int )), this, SLOT(OnTabChange()));
    connect(m_pqAddSurface,    SIGNAL(triggered()), this, SLOT(OnAddSurface()));
    connect(m_pqDeleteSurface, SIGNAL(triggered()), this, SLOT(OnDeleteSurface()));
    // задаем ширину колонок у таблицы (добавляем запас на ширину рамки)
    int w=m_ui.m_qOSParams->width()/4.2;
    for(int i=0; i<=3; ++i)
    {
        m_ui.m_qOSParams->setColumnWidth(i, w);
    }

    /////////////////////////////
    // для тестирования - сразу создаем одиночную линзу, 
    // в дальнейшем можно заменить на чтение из файла, передачу данных из других диалогов и т.д.
    m_oSystem.resize(2);
    m_oSystem[0].r=100;
    m_oSystem[0].d=10;
    m_oSystem[0].h=15;
    m_oSystem[0].n=1.5;

    m_oSystem[1].r=-100;
    m_oSystem[1].h=15;
    /////////////////////////////

    // записываем параметры оптической системы в таблицу
    SetOSParam();
} 
//-------------------------------------------------------------------------------------------------
DQtOpticalSystem::~DQtOpticalSystem()
{
} 
//-------------------------------------------------------------------------------------------------
// обработка смены закладки
void 
DQtOpticalSystem::OnTabChange()
{
    // tab = 0 - конструктивные параметры О.С.
    // tab = 1 - рисование О.С.

    // если переходим на любую закладку кроме конструктивных парамеров - записываем данные из таблицы и проверям ошибки
    if(m_ui.m_pqMainTabs->currentIndex()!=0)
    {
        try
        {
            GetOSParams();
        }
        catch(const std::exception& oErr)
        {
            // если возникла ошибка - выдаем сообщение, и возвращаемся на закладку с констр.параметрами
            QMessageBox::critical(this, "Неправильно заданы конструктивные параметры", oErr.what());
            m_ui.m_pqMainTabs->setCurrentIndex(0);
        }
    }
}
//-------------------------------------------------------------------------------------------------
// обработка всех событий на диалоговом окне (перегруженная функция)
// перехватывается ТОЛЬКО событие рисования на закладке с рисованием) 
bool 
DQtOpticalSystem::eventFilter(QObject* pObj_p, QEvent* pEvent_p)
{
    // если объект, генерирующие событие - виджет для рисования
    if(pObj_p == m_ui.m_qDrawArea)
    {
        // если тип события - рисование
        if( pEvent_p->type() == QEvent::Paint )
        {
            try
            {
                // получаем параметры О.С.
                GetOSParams();
                // создаем QPainter, свзяанный с областью рисования
                QPainter p( m_ui.m_qDrawArea );
                // задаем парамеры у QPainter
                p.setRenderHint(QPainter::Antialiasing);
                // рисуем в QPainter
                DrawOs(p);
            }
            // если возникла ошибка - ничего не рисуем, т.к. ошибки проверяются в OnTabChange
            catch(...)
            {}
        }
    }
    // все события передаются дальше
    return QDialog::eventFilter(pObj_p, pEvent_p);
}
//-------------------------------------------------------------------------------------------------
// рисование О.С. в QPainter
void    
DQtOpticalSystem::DrawOs(QPainter& p)
{
    // вычисляем общую высоту и ширину оптической системы
    double dOSHeight=0, dOSWidth=0, dWinHeight=0, dWinWidth=0;
    for(int i=0; i<m_oSystem.size(); ++i)
    {
        dOSWidth+=m_oSystem[i].d;
        dOSHeight=std::max(dOSHeight, m_oSystem[i].h);
    }
    dOSHeight*=2;

    // вычисляем высоту и ширину окна в реальных координатах, с учетом высоты и ширины О.С.  
    // и коэффициента пропорциональности между высотой/шириной окна в px (для правильного масштаба по осям)
    dWinWidth=dOSWidth;
    dWinHeight=dOSHeight;
    double dScale=double(p.viewport().width())/double(p.viewport().height());
    if(dWinWidth/dWinHeight > dScale)
        dWinHeight=dWinWidth/dScale;
    else
        dWinWidth=dWinHeight*dScale;
    double dH=dWinHeight*0.1;
    double dW=dWinWidth*0.1;
    // задаем параметры окна в реальных координатах
    QRect qRegion(-dW, -(dWinHeight/2+dH), dWinWidth+dW*2, dWinHeight+dH*2);
    p.setWindow(qRegion);

    // запоминаем старые QPen и QBrush
    QPen oldPen=p.pen();
    QBrush oldBrush=p.brush();
    // создаем новые QPen и QBrush для линзы и оси
    QPen qLensPen(QColor(102,102,153));
    qLensPen.setCosmetic(true);
    QBrush qLensBrush(QColor(102,209,230));
    QPen qAxisPen(QColor(0,0,0));
    qAxisPen.setStyle(Qt::DashDotLine);
    qAxisPen.setCosmetic(true);
    // устанавливаем для QPainter текущие QPen и QBrush для рисования линз
    p.setPen(qLensPen);
    p.setBrush(qLensBrush);
    // рисование О.С.
    double x=0.;
    for(int i=0; i<m_oSystem.size(); ++i)
    {
        QPainterPath qPath;
        // если следующая среда - не воздух, рисуем линзу
        if(m_oSystem[i].n > 1)
        {
            // создаем контур линзы
            DrawLens(qPath, x, i);
            // отображем контур линзы текущими QPen и QBrush
            p.drawPath(qPath);
        }
        x+=m_oSystem[i].d;
    }
    // рисуем ось
    p.setPen(qAxisPen);
    p.drawLine(QPointF(-dW,0), QPointF(dOSWidth+dW,0));
    // возвращаем старые QPen и QBrush
    p.setPen(oldPen);
    p.setBrush(oldBrush);
}
//-------------------------------------------------------------------------------------------------
// создание замкнутого контура одной линзы
// qPath_p - контур линзы
// dX_p - реальная координата X линзы
// iIndex_p - номер первой поверхности линзы в m_oSystem
void    
DQtOpticalSystem::DrawLens(QPainterPath& qPath_p, double dX_p, int iIndex_p)
{
    // для удобства создаем новы переменные. h1 и р2 будут меняться, поэтому они не ссылки.
    double h1=m_oSystem[iIndex_p].h;
    double& r1=m_oSystem[iIndex_p].r;
    double h2=m_oSystem[iIndex_p+1].h;
    double& r2=m_oSystem[iIndex_p+1].r;
    double& d=m_oSystem[iIndex_p].d;

    // нахидим высоту линзы (max из h1 и h2) и определяем высоты поверхностей в зависимости от знака радиуса
    double h=max(h1,h2);
    if(r1>0)
        h1=h;
    if(r2<0)
        h2=h;

    // вычисляем стрелку прогиба поверхностей
    double dArrow1=fabs(r1) - sqrt(r1*r1 - h1*h1);
    if(r1<0)
        dArrow1=-dArrow1;
    double dArrow2=fabs(r2) - sqrt(r2*r2 - h2*h2);
    if(r2<0)
        dArrow2=-dArrow2;

    // первая точка контура - верхняя точка поверхности
    qPath_p.moveTo(dX_p + dArrow1, -h);

    // если r<0 - рисуем фаску сверху
    if(r1<0 && h1<h2)
        qPath_p.lineTo(dX_p + dArrow1, -h1);
    // рисуем 1ю поверхность (сверху вниз)
    double dAngle=asin(h1/r1)*180/PI; // угол между осью и крайней точкой поверхности
    if(r1>0)
        qPath_p.arcTo(dX_p,     -r1,  r1*2,  r1*2, 180-dAngle, dAngle*2);
    else
        qPath_p.arcTo(dX_p+2*r1, r1, -r1*2, -r1*2, -dAngle,    dAngle*2);
    // параметы функции: лев.точка, верхняя точка, ширина, высота (все для описанного вокруг полной окружности прямоугольника)
    //                   начальный угол, длина дуги в градусах
    // если r<0 - рисуем фаску снизу
    if(r1<0 && h1<h2)
        qPath_p.lineTo(dX_p + dArrow1, h);

    // добавляем нижнюю линию между поверхностями
    qPath_p.lineTo(dX_p + d + dArrow2, h);    
    dX_p+=d;

    // если r>0 - рисуем фаску снизу
    if(r2>0 && h2<h1)
        qPath_p.lineTo(dX_p + dArrow2, h2);
    // рисуем 2ю поверхность (сверху вниз)
    dAngle=asin(h2/r2)*180/PI;
    if(r2>0)
        qPath_p.arcTo(dX_p,     -r2,  r2*2,  r2*2, 180+dAngle, -dAngle*2);
    else
        qPath_p.arcTo(dX_p+2*r2, r2, -r2*2, -r2*2, dAngle,     -dAngle*2);
    // если r>0 - рисуем фаску сверху
    if(r2>0 && h2<h1)
        qPath_p.lineTo(dX_p + dArrow2, -h);

    // замыкаем контур
    qPath_p.closeSubpath();
}
//-------------------------------------------------------------------------------------------------
// записать параметры О.С. из таблицы в m_oSystem
void    
DQtOpticalSystem::GetOSParams()
{
    int iRowCount=m_ui.m_qOSParams->rowCount();
    m_oSystem.resize(iRowCount);
    for(int i=0; i<iRowCount; ++i)
    {
        // берем все данные из таблицы как double
        m_oSystem[i].r=m_ui.m_qOSParams->item(i, 0)->data(0).toDouble();
        m_oSystem[i].d=m_ui.m_qOSParams->item(i, 1)->data(0).toDouble();
        m_oSystem[i].h=m_ui.m_qOSParams->item(i, 2)->data(0).toDouble();
        m_oSystem[i].n=m_ui.m_qOSParams->item(i, 3)->data(0).toDouble();

        // проверяем правильность ввода данных, если что-то неправильно - генерируем исключение
        if(m_oSystem[i].h<0)
            throw std::runtime_error("Световая высота должны быть больше 0");
        if(fabs(m_oSystem[i].h)>=fabs(m_oSystem[i].r))
            throw std::runtime_error("Световая высота должнабыть меньше чем радиус");
        if(m_oSystem[i].n<1)
            throw std::runtime_error("Показатель преломления должен быть больше 1");
    }
}
//-------------------------------------------------------------------------------------------------
// записать параметры О.С. из m_oSystem в таблицу
void 
DQtOpticalSystem::SetOSParam()
{
    // удаляем из таблицы все старые данные
    for(int i=0; i<=m_ui.m_qOSParams->rowCount(); ++i)
    {
        m_ui.m_qOSParams->removeRow(i);
    }
    // добавляем заново все поверхности
    for(int i=0; i<m_oSystem.size(); ++i)
    {
        AddOSParamRow(m_oSystem[i], i);
    }
    // запрещаем сортировку
    m_ui.m_qOSParams->setSortingEnabled(false);
}
//-------------------------------------------------------------------------------------------------
// добавляет одну поверхность в таблицу с конструктивными параметрами
// oSurf_p - параметры поверхности, которая будет добавлена
// iRow_p  - номер, под которым будет добавлена поверхность
void 
DQtOpticalSystem::AddOSParamRow(const OSurface& oSurf_p, int iRow_p)
{
    m_ui.m_qOSParams->insertRow(iRow_p);

    m_ui.m_qOSParams->setItem(iRow_p, 0, CreateTableItem(oSurf_p.r));
    m_ui.m_qOSParams->setItem(iRow_p, 1, CreateTableItem(oSurf_p.d)); 
    m_ui.m_qOSParams->setItem(iRow_p, 2, CreateTableItem(oSurf_p.h));
    m_ui.m_qOSParams->setItem(iRow_p, 3, CreateTableItem(oSurf_p.n));
}
//-------------------------------------------------------------------------------------------------
// создает одну ячейку таблицы с double значением dValue_p
QTableWidgetItem*
DQtOpticalSystem::CreateTableItem(double dValue_p) const
{
    // создаем новую ячейку таблицы
    QTableWidgetItem* qItem = new QTableWidgetItem();
    // устнавливаем параметры: ячейка доступна + ее можно выбрать + ее можно редактировать
    qItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
    // устанавливаем значение в ячейку как double
    qItem->setData(0, QVariant::fromValue(dValue_p));
    return qItem;
} 
//-------------------------------------------------------------------------------------------------
// обработка добавления новой поверхности в таблицу
void 
DQtOpticalSystem::OnAddSurface()
{
    // создаем пустую поверхность и добавляем ее в таблицу под номером выбранной строки 
    // (выбранная строка перейдет на 1 строчку ниже)
    OSurface oSurf;
    AddOSParamRow(oSurf, m_ui.m_qOSParams->currentRow());
}
//-------------------------------------------------------------------------------------------------
// обработка удаления поверхности из таблицы
void 
DQtOpticalSystem::OnDeleteSurface()
{
    // удаляем выбранную строку
    m_ui.m_qOSParams->removeRow(m_ui.m_qOSParams->currentRow());
}
//-------------------------------------------------------------------------------------------------