/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2012 by Wenzel Jakob and others. Mitsuba is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. Mitsuba 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "rendersettingsdlg.h" #include "ui_rendersettingsdlg.h" #include #include /* ====================== Some helper routines ====================== */ static void setComboBox(QComboBox *box, const std::string &pluginName) { for (int i=0; icount(); ++i) { const QList &data = box->itemData(i).toList(); if (data.at(2) == pluginName.c_str()) { box->setCurrentIndex(i); return; } } SLog(EError, "Unable to find combo box entry named \"%s\"", pluginName.c_str()); } static std::string getPluginName(QComboBox *box) { return box->itemData(box->currentIndex()).toList().at(2).toString().toStdString(); } /* ====================== RenderSettingsDialog impl ====================== */ RenderSettingsDialog::RenderSettingsDialog(QWidget *parent) : QDialog(parent, Qt::Sheet), ui(new Ui::RenderSettingsDialog), m_icNode(NULL), m_aiNode(NULL) { ui->setupUi(this); connect(ui->integratorBox, SIGNAL(highlighted(int)), SLOT(cbHighlighted(int))); connect(ui->integratorBox, SIGNAL(activated(int)), SLOT(update())); connect(ui->samplerBox, SIGNAL(highlighted(int)), SLOT(cbHighlighted(int))); connect(ui->samplerBox, SIGNAL(activated(int)), SLOT(update())); connect(ui->rFilterBox, SIGNAL(highlighted(int)), SLOT(cbHighlighted(int))); connect(ui->rFilterBox, SIGNAL(activated(int)), SLOT(update())); connect(ui->icBox, SIGNAL(pressed()), SLOT(chkBoxPressed())); connect(ui->aiBox, SIGNAL(pressed()), SLOT(chkBoxPressed())); connect(ui->icBox, SIGNAL(toggled(bool)), SLOT(update())); connect(ui->aiBox, SIGNAL(toggled(bool)), SLOT(update())); connect(ui->resolutionBox, SIGNAL(activated(int)), SLOT(refresh())); connect(ui->resolutionBox, SIGNAL(editTextChanged(const QString &)), SLOT(refresh())); QFile file(":/resources/docs.xml"); if (!file.open(QIODevice::ReadOnly) || !m_document.setContent(&file)) SLog(EError, "Unable to read the documentation file!"); file.close(); /* Populate the integrator, rec. filter & sampler combo box widgets */ QDomElement docRoot = m_document.documentElement(); for (QDomElement e = docRoot.firstChildElement("plugin"); !e.isNull(); e = e.nextSiblingElement("plugin")) { QString docString, name = e.attribute("name"); if (!e.firstChildElement("descr").isNull()) { /* Create a HTML-based documentation string */ QDomDocument helpDoc; QDomElement root = helpDoc.createElement("p"); helpDoc.appendChild(root); for (QDomNode child = e.firstChildElement("descr").firstChild(); !child.isNull(); child = child.nextSibling()) root.appendChild(helpDoc.importNode(child, true)); docString = helpDoc.toString(); } if (e.hasAttribute("show") && e.attribute("show") == "true") { QString type = e.attribute("type"), className = e.attribute("className"), readableName = e.attribute("readableName"), name = e.attribute("name"); QList list; list.append(className); list.append(docString); list.append(name); if (type == "integrator") ui->integratorBox->addItem(readableName, list); else if (type == "sampler") ui->samplerBox->addItem(readableName, list); else if (type == "rfilter") ui->rFilterBox->addItem(readableName, list); } if (name == "irrcache") ui->icBox->setProperty("help", docString); else if (name == "adaptive") ui->aiBox->setProperty("help", docString); } m_model = new XMLTreeModel(docRoot, palette(), this); ui->treeView->setModel(m_model); ui->treeView->setAlternatingRowColors(true); ui->treeView->setUniformRowHeights(true); ui->treeView->setColumnWidth(0, 270); ui->treeView->setItemDelegate(new PropertyDelegate(this)); connect(ui->treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection)), SLOT(onTreeSelectionChange(const QItemSelection &, const QItemSelection &))); connect(m_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged())); m_integratorNode = m_model->registerClass("MIPathTracer", "Path tracer"); m_samplerNode = m_model->registerClass("IndependentSampler", "Independent sampler"); m_rFilterNode = m_model->registerClass("BoxFilter", "Box filter"); QRegExp resRegExp("^[1-9]\\d{0,4}x[1-9]\\d{0,4}$"); ui->resolutionBox->setValidator(new QRegExpValidator(resRegExp, this)); QPalette pal = ui->helpViewer->palette(); pal.setColor(QPalette::Text, pal.color(QPalette::Foreground)); pal.setColor(QPalette::Base, pal.color(QPalette::Window)); ui->helpViewer->setPalette(pal); } void RenderSettingsDialog::setDocumentation(const QString &text) { m_currentDocumentation = text; bool hasErrors = false; QString comments; if (m_statusMessages.size() > 0) { comments = QString("
    "); ui->groupBox->setTitle(tr("Issues with the current configuration")); for (int i=0; i%1").arg(message); else comments += QString("
  • %1
  • ").arg(message); hasErrors |= isError; } comments += "
"; } else { ui->groupBox->setTitle(tr("Documentation")); } #if defined(__OSX__) ui->helpViewer->setHtml(comments + "
" + m_currentDocumentation + "
"); #else ui->helpViewer->setHtml(comments + "
" + m_currentDocumentation + "
"); #endif ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!hasErrors); } void RenderSettingsDialog::dataChanged() { QStringList statusMessages = validateConfiguration(); if (statusMessages != m_statusMessages) { m_statusMessages = statusMessages; setDocumentation(m_currentDocumentation); } } void RenderSettingsDialog::cbHighlighted(int index) { QComboBox *comboBox = static_cast(sender()); setDocumentation(comboBox->itemData(index).toList().at(1).toString()); } void RenderSettingsDialog::chkBoxPressed() { QCheckBox *checkBox = static_cast(sender()); setDocumentation(checkBox->property("help").toString()); } void RenderSettingsDialog::onTreeSelectionChange(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList indexList = selected.indexes(); if (indexList.size() > 0) setDocumentation(m_model->data(indexList[0], Qt::ToolTipRole).toString()); } void RenderSettingsDialog::update() { int index = ui->integratorBox->currentIndex(); Properties integratorProps, samplerProps; if (sender() == ui->samplerBox) m_samplerNode->putProperties(samplerProps); if (sender() == ui->integratorBox) m_integratorNode->putProperties(integratorProps); m_integratorNode = m_model->updateClass(m_integratorNode, ui->integratorBox->itemData(index).toList().at(0).toString(), ui->integratorBox->itemText(index)); index = ui->samplerBox->currentIndex(); m_samplerNode = m_model->updateClass(m_samplerNode, ui->samplerBox->itemData(index).toList().at(0).toString(), ui->samplerBox->itemText(index)); index = ui->rFilterBox->currentIndex(); m_rFilterNode = m_model->updateClass(m_rFilterNode, ui->rFilterBox->itemData(index).toList().at(0).toString(), ui->rFilterBox->itemText(index)); if (ui->icBox->isChecked()) { m_icNode = m_model->updateClass(m_icNode, "IrradianceCacheIntegrator", tr("Irradiance Cache")); } else { m_icNode = m_model->updateClass(m_icNode, "", ""); } if (ui->aiBox->isChecked()) { m_aiNode = m_model->updateClass(m_aiNode, "AdaptiveIntegrator", tr("Adaptive Integration")); } else { m_aiNode = m_model->updateClass(m_aiNode, "", ""); } if (sender() == ui->integratorBox) { for (int i=0; ichildCount(); ++i) { TreeItem *treeItem = m_integratorNode->child(i); if (integratorProps.hasProperty(treeItem->getName().toStdString())) m_integratorNode->setProperty(treeItem->getName().toStdString(), integratorProps); } } if (sender() == ui->samplerBox) { for (int i=0; ichildCount(); ++i) { TreeItem *treeItem = m_samplerNode->child(i); if (samplerProps.hasProperty(treeItem->getName().toStdString())) m_samplerNode->setProperty(treeItem->getName().toStdString(), samplerProps); } } ui->treeView->expandAll(); #if 0 /* Make comboboxes etc editable by default .. does not quite work yet */ for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex index = m_model->index(i, 0); for (int j = 1; j < m_model->rowCount(index); ++j) { ui->treeView->openPersistentEditor( m_model->index(j, 1, index)); } } #endif dataChanged(); } bool RenderSettingsDialog::resolutionHasChanged() const { return ui->resolutionBox->currentText() != m_originalResolution; } void RenderSettingsDialog::refresh() { bool valid = true; int pos; QString resolutionString(ui->resolutionBox->currentText()); valid &= ui->resolutionBox->validator()->validate(resolutionString,pos) == QValidator::Acceptable; ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); } void RenderSettingsDialog::load(const SceneContext *ctx) { const Scene *scene = ctx->scene.get(); const Film *film = scene->getFilm(); const Properties &rFilterProps = film->getReconstructionFilter()->getProperties(); const Properties &samplerProps = scene->getSampler()->getProperties(); const Integrator *integrator = scene->getIntegrator(); Properties integratorProps = integrator->getProperties(); if (integratorProps.getPluginName() == "adaptive") { ui->aiBox->setChecked(true); m_model->setProperties(m_aiNode, integratorProps); integrator = integrator->getSubIntegrator(); integratorProps = integrator->getProperties(); } if (integratorProps.getPluginName() == "irrcache") { ui->icBox->setChecked(true); m_model->setProperties(m_icNode, integratorProps); integrator = integrator->getSubIntegrator(); integratorProps = integrator->getProperties(); } ui->resolutionBox->lineEdit()->setText(QString("%1x%2") .arg(film->getCropSize().x).arg(film->getCropSize().y)); m_originalResolution = ui->resolutionBox->lineEdit()->text(); setComboBox(ui->integratorBox, integratorProps.getPluginName()); setComboBox(ui->rFilterBox, rFilterProps.getPluginName()); setComboBox(ui->samplerBox, samplerProps.getPluginName()); update(); m_model->setProperties(m_rFilterNode, rFilterProps); m_model->setProperties(m_samplerNode, samplerProps); m_model->setProperties(m_integratorNode, integratorProps); ui->treeView->expandAll(); } void RenderSettingsDialog::apply(SceneContext *ctx) { Scene *scene = new Scene(ctx->scene); ref oldSensor = scene->getSensor(); Film *oldFilm = oldSensor->getFilm(); Properties filmProps = oldSensor->getFilm()->getProperties(); ref pluginMgr = PluginManager::getInstance(); /* Temporarily set up a new file resolver */ ref thread = Thread::getThread(); ref oldResolver = thread->getFileResolver(); ref newResolver = oldResolver->clone(); newResolver->prependPath(fs::absolute(scene->getSourceFile()).parent_path()); thread->setFileResolver(newResolver); /* Configure the reconstruction filter */ Properties rFilterProps(getPluginName(ui->rFilterBox)); if (m_rFilterNode != NULL) m_rFilterNode->putProperties(rFilterProps); ref rFilter = static_cast (pluginMgr->createObject(MTS_CLASS(ReconstructionFilter), rFilterProps)); rFilter->configure(); /* Configure the sampler */ Properties samplerProps(getPluginName(ui->samplerBox)); if (m_samplerNode != NULL) m_samplerNode->putProperties(samplerProps); ref sampler = static_cast (pluginMgr->createObject(MTS_CLASS(Sampler), samplerProps)); sampler->configure(); /* Configure the integrator */ Properties integratorProps(getPluginName(ui->integratorBox)); if (m_integratorNode != NULL) m_integratorNode->putProperties(integratorProps); ref integrator = static_cast (pluginMgr->createObject(MTS_CLASS(Integrator), integratorProps)); integrator->configure(); if (ui->icBox->isChecked()) { Properties icProps("irrcache"); if (m_icNode != NULL) m_icNode->putProperties(icProps); ref ic = static_cast (pluginMgr->createObject(MTS_CLASS(Integrator), icProps)); ic->addChild(integrator); ic->configure(); integrator = ic; } if (ui->aiBox->isChecked()) { Properties aiProps("adaptive"); if (m_aiNode != NULL) m_aiNode->putProperties(aiProps); ref ai = static_cast (pluginMgr->createObject(MTS_CLASS(Integrator), aiProps)); ai->addChild(integrator); ai->configure(); integrator = ai; } QStringList resolution = ui->resolutionBox->currentText().split('x'); SAssert(resolution.size() == 2); Vector2i cropSize( std::max(1, resolution[0].toInt()), std::max(1, resolution[1].toInt())); /* Configure the film */ Vector2i oldSize = oldFilm->getSize(); Vector2i oldCropSize = oldFilm->getCropSize(); Point2i oldCropOffset = oldFilm->getCropOffset(); Vector2i size(std::ceil((oldSize.x * cropSize.x) / (Float) oldCropSize.x), std::ceil((oldSize.y * cropSize.y) / (Float) oldCropSize.y)); Point2i cropOffset(std::floor((oldCropOffset.x * cropSize.x) / (Float) oldCropSize.x), std::floor((oldCropOffset.y * cropSize.y) / (Float) oldCropSize.y)); filmProps.setInteger("width", size.x, false); filmProps.setInteger("height", size.y, false); if (size.x != cropSize.x || size.y != cropSize.y) { filmProps.setInteger("cropWidth", cropSize.x, false); filmProps.setInteger("cropHeight", cropSize.y, false); filmProps.setInteger("cropOffsetX", cropOffset.x, false); filmProps.setInteger("cropOffsetY", cropOffset.y, false); } else { filmProps.removeProperty("cropWidth"); filmProps.removeProperty("cropHeight"); filmProps.removeProperty("cropOffsetX"); filmProps.removeProperty("cropOffsetY"); } ctx->originalSize = cropSize; ref film = static_cast (pluginMgr->createObject( MTS_CLASS(Film), filmProps)); film->addChild(rFilter); film->configure(); if (cropSize.x != ctx->framebuffer->getWidth() || cropSize.y != ctx->framebuffer->getHeight()) { ctx->framebuffer = new Bitmap(Bitmap::ERGBA, Bitmap::EFloat32, cropSize); ctx->framebuffer->clear(); ctx->mode = EPreview; } /* Configure the sensor */ Properties sensorProps = oldSensor->getProperties(); if (oldSensor->getClass()->derivesFrom(MTS_CLASS(PerspectiveCamera))) { sensorProps.removeProperty("focalLength"); sensorProps.setString("fovAxis", "y", false); sensorProps.setFloat("fov", static_cast(oldSensor.get())->getYFov(), false); } ref newSensor = static_cast (pluginMgr->createObject(MTS_CLASS(Sensor), sensorProps)); newSensor->addChild(sampler); newSensor->addChild(film); newSensor->setMedium(oldSensor->getMedium()); newSensor->configure(); /* Update the scene with the newly constructed elements */ scene->removeSensor(oldSensor); scene->addSensor(newSensor); scene->setSensor(newSensor); scene->setSampler(sampler); scene->setIntegrator(integrator); scene->configure(); ctx->scene = scene; thread->setFileResolver(oldResolver); } RenderSettingsDialog::~RenderSettingsDialog() { delete ui; } void RenderSettingsDialog::changeEvent(QEvent *e) { QDialog::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } } /* ====================== PropertyDelegate impl ====================== */ PropertyDelegate::PropertyDelegate(QObject *parent) : QStyledItemDelegate(parent) { } PropertyDelegate::~PropertyDelegate() { } QString PropertyDelegate::displayText(const QVariant &value, const QLocale &locale) const { if (value.type() == QVariant::Bool) return value.toBool() ? tr("Yes") : tr("No"); return QStyledItemDelegate::displayText(value, locale); } QWidget *PropertyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.data().type() == QVariant::Bool) { QComboBox *cbox = new QComboBox(parent); /* Nicer boolean editor -- by default, Qt creates a True/False combo box */ cbox->addItem(tr("No")); cbox->addItem(tr("Yes")); return cbox; } QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index); #if defined(__OSX__) /* Don't draw focus halos on OSX, they're really distracting */ if (widget != NULL && widget->testAttribute(Qt::WA_MacShowFocusRect)) widget->setAttribute(Qt::WA_MacShowFocusRect, false); if (index.data().type() != QVariant::Bool) { widget->setAttribute(Qt::WA_MacMiniSize, true); widget->setStyleSheet("font-size: 13pt;"); } #endif return widget; } void PropertyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { if (index.data().type() == QVariant::Bool) { QComboBox *cbox = static_cast(editor); cbox->setCurrentIndex(index.data().toBool() ? 1 : 0); return; } QStyledItemDelegate::setEditorData(editor, index); } void PropertyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (index.data().type() == QVariant::Bool) { QComboBox *cbox = static_cast(editor); model->setData(index, QVariant(cbox->currentIndex() == 1), Qt::EditRole); return; } QStyledItemDelegate::setModelData(editor, model, index); } void PropertyDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.data().type() == QVariant::Bool) { editor->setGeometry(option.rect); return; } QStyledItemDelegate::updateEditorGeometry(editor, option, index); } QStringList RenderSettingsDialog::validateConfiguration() const { /* Ad-hoc verification until we have something better (preferably specifiable by the plugins themselves) */ QStringList messages; std::string integratorName = getPluginName(ui->integratorBox); std::string samplerName = getPluginName(ui->samplerBox); Properties integratorProps, samplerProps; m_integratorNode->putProperties(integratorProps); m_samplerNode->putProperties(samplerProps); if (samplerName != "independent") { if (integratorName == "pssmlt" || integratorName == "mlt") messages << "Error: Metropolis Light Transport-type algorithms only work with the independent sampler."; if (ui->aiBox->isChecked()) messages << "Error: Adaptive integration requires the independent sampler."; } if ((samplerName == "ldsampler" || samplerName == "stratified") && integratorName == "ptracer") messages << "Error: the particle tracer does not support the stratified or low-discrepancy samplers!"; if (samplerName == "halton" || samplerName == "hammersley") { if (integratorName == "bdpt") messages << "Error: the Bidirectional Path Tracer should not be used with the Halton/Hammersley samplers!"; else if (integratorName == "erpt") messages << "Error: the Energy Redistribution Path Tracer should not be used with the Halton/Hammersley samplers!"; } if (samplerName == "hammersley") { if (integratorName == "photonmapper") messages << "Error: the Hammersley sampler cannot be used with the photon mapper. Try the Halton sampler instead."; } if (ui->icBox->isChecked()) { if (integratorName != "direct" && integratorName != "path" && integratorName != "volpath" && integratorName != "volpath_simple" && integratorName != "photonmapper") messages << "Error: Irradiance Caching is not compatible with the selected integrator."; } if (ui->aiBox->isChecked()) { if (integratorName != "direct" && integratorName != "path" && integratorName != "volpath" && integratorName != "volpath_simple" && integratorName != "photonmapper") messages << "Error: Adaptive integration is not compatible with the selected integrator."; } if (integratorName == "ppm") { if ((samplerName == "independent" || samplerName == "ldsampler") && samplerProps.hasProperty("sampleCount")) { if (samplerProps.getInteger("sampleCount") > 4) messages << "Warning: are you sure you need more than 4 samples/pixel for progressive photon mapping? This will be slow.."; } else if (samplerName == "stratified" && samplerProps.hasProperty("resolution")) { if (samplerProps.getInteger("resolution") > 2) messages << "Warning: are you sure you need more than 4 samples/pixel for progressive photon mapping? This will be slow.."; } } return messages; }