mitsuba/src/qtgui/save.cpp

437 lines
14 KiB
C++

/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2011 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 <http://www.gnu.org/licenses/>.
*/
#include "save.h"
#include <QtXml>
static QDomElement findUniqueChild(QDomElement element, const char *tagName) {
QDomElement result;
QDomNode n = element.firstChild();
bool found = false;
while (!n.isNull()) {
QDomElement e = n.toElement();
if (!e.isNull()) {
if (e.tagName() == tagName) {
if (found)
SLog(EError, "Unexpectedly found multiple tags named \"%s\"", tagName);
found = true;
result = e;
}
}
n = n.nextSibling();
}
return result;
}
static QDomElement findProperty(QDomElement element, const QString &propertyName) {
QDomElement result;
QDomNode n = element.firstChild();
bool found = false;
while (!n.isNull()) {
QDomElement e = n.toElement();
if (!e.isNull()) {
if (e.attribute("name") == propertyName) {
if (found)
SLog(EError, "Unexpectedly found multiple properties named \"%s\"", qPrintable(propertyName));
found = true;
result = e;
}
}
n = n.nextSibling();
}
return result;
}
static void removeChildren(QDomElement element) {
while (!element.lastChild().isNull())
element.removeChild(element.lastChild());
}
static void setProperties(QDomDocument &doc, QDomElement &element,
const Properties &props) {
element.setAttribute("type", props.getPluginName().c_str());
std::vector<std::string> propertyNames;
props.putPropertyNames(propertyNames);
for (std::vector<std::string>::const_iterator it = propertyNames.begin();
it != propertyNames.end(); ++it) {
QDomElement property;
switch (props.getType(*it)) {
case Properties::EBoolean:
property = doc.createElement("boolean");
property.setAttribute("value", props.getBoolean(*it) ? "true" : "false");
break;
case Properties::EInteger:
property = doc.createElement("integer");
property.setAttribute("value", QString::number(props.getInteger(*it)));
break;
case Properties::EFloat:
property = doc.createElement("float");
property.setAttribute("value", QString::number(props.getFloat(*it)));
break;
case Properties::EString:
property = doc.createElement("string");
property.setAttribute("value", props.getString(*it).c_str());
break;
default:
SLog(EError, "setProperties(): \"%s\": Unable to handle elements of type %i",
(*it).c_str(), props.getType(*it));
}
property.setAttribute("name", (*it).c_str());
element.appendChild(property);
}
}
void saveScene(QWidget *parent, SceneContext *ctx, const QString &targetFile) {
QDomDocument doc("MitsubaScene");
QFile file(ctx->fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(parent, parent->tr("Unable to save"),
parent->tr("Unable to save changes: could not open the original scene file <b>%1</b>. "
"Was this file moved or deleted in the meantime?").arg(ctx->fileName),
QMessageBox::Ok);
return;
}
if (!doc.setContent(&file)) {
QMessageBox::critical(parent, parent->tr("Unable to save"),
parent->tr("Unable to save changes: could not parse the original scene file <b>%1</b>. "
"Was this file modified in the meantime?").arg(ctx->fileName),
QMessageBox::Ok);
file.close();
return;
}
file.close();
QDomElement root = doc.documentElement();
// ====================================================================
// Serialize the camera configuration
// ====================================================================
QDomElement camera = findUniqueChild(root, "camera");
if (camera.isNull()) {
camera = doc.createElement("camera");
camera.setAttribute("type", "perspective");
root.insertAfter(camera, QDomNode());
}
const PerspectiveCamera *sceneCamera = static_cast<const PerspectiveCamera *>(ctx->scene->getCamera());
QDomElement fovProperty = findProperty(camera, "fov");
if (fovProperty.isNull()) {
fovProperty = doc.createElement("float");
fovProperty.setAttribute("name", "fov");
camera.insertBefore(fovProperty, QDomNode());
}
fovProperty.setAttribute("value", QString::number(sceneCamera->getFov()));
QDomElement focusDepthProperty = findProperty(camera, "focusDepth");
if (focusDepthProperty.isNull()) {
focusDepthProperty = doc.createElement("float");
focusDepthProperty.setAttribute("name", "focusDepth");
camera.insertBefore(focusDepthProperty, QDomNode());
}
focusDepthProperty.setAttribute("value", QString::number(sceneCamera->getFocusDepth()));
QDomElement cameraTransform = findUniqueChild(camera, "transform");
if (cameraTransform.isNull()) {
cameraTransform = doc.createElement("transform");
cameraTransform.setAttribute("name", "toWorld");
camera.insertBefore(cameraTransform, QDomNode());
}
removeChildren(cameraTransform);
QDomElement lookAt = doc.createElement("lookAt");
cameraTransform.insertAfter(lookAt, QDomNode());
Vector direction = sceneCamera->getInverseViewTransform()(Vector(0,0,1)),
u = ctx->up;
Point t, p = sceneCamera->getInverseViewTransform()(Point(0,0,0));
if (sceneCamera->getViewTransform().det3x3() > 0) {
QDomElement scale = doc.createElement("scale");
scale.setAttribute("x", "-1");
cameraTransform.insertBefore(scale, lookAt);
}
t = p + direction;
lookAt.setAttribute("ox", QString::number(p.x));
lookAt.setAttribute("oy", QString::number(p.y));
lookAt.setAttribute("oz", QString::number(p.z));
lookAt.setAttribute("tx", QString::number(t.x));
lookAt.setAttribute("ty", QString::number(t.y));
lookAt.setAttribute("tz", QString::number(t.z));
lookAt.setAttribute("ux", QString::number(u.x));
lookAt.setAttribute("uy", QString::number(u.y));
lookAt.setAttribute("uz", QString::number(u.z));
// ====================================================================
// Serialize the sampler configuration
// ====================================================================
QDomElement newSampler, sampler = findUniqueChild(camera, "sampler");
if (sampler.isNull()) {
newSampler = doc.createElement("sampler");
camera.appendChild(newSampler);
} else {
newSampler = doc.createElement("sampler");
camera.insertAfter(newSampler, sampler);
camera.removeChild(sampler);
}
setProperties(doc, newSampler, ctx->scene->getSampler()->getProperties());
// ====================================================================
// Serialize the film configuration
// ====================================================================
QDomElement film = findUniqueChild(camera, "film");
if (film.isNull()) {
film = doc.createElement("film");
film.setAttribute("type", "exrfilm");
camera.insertAfter(film, QDomNode());
}
QDomElement widthProperty = findProperty(film, "width");
QDomElement heightProperty = findProperty(film, "height");
if (widthProperty.isNull()) {
widthProperty = doc.createElement("integer");
widthProperty.setAttribute("name", "width");
film.insertBefore(widthProperty, QDomNode());
}
if (heightProperty.isNull()) {
heightProperty = doc.createElement("integer");
heightProperty.setAttribute("name", "height");
film.insertBefore(heightProperty, QDomNode());
}
if (film.attribute("type") == "pngfilm") {
/* Set tonemapping attributes */
QDomElement method = findProperty(film, "toneMappingMethod");
QDomElement reinhardBurn = findProperty(film, "reinhardBurn");
QDomElement reinhardKey = findProperty(film, "reinhardKey");
QDomElement exposure = findProperty(film, "exposure");
QDomElement gamma = findProperty(film, "gamma");
if (method.isNull()) {
method = doc.createElement("string");
method.setAttribute("name", "toneMappingMethod");
film.insertBefore(method, QDomNode());
}
method.setAttribute("value", ctx->toneMappingMethod == EReinhard ? "reinhard" : "gamma");
if (gamma.isNull()) {
gamma = doc.createElement("float");
gamma.setAttribute("name", "gamma");
film.insertBefore(gamma, QDomNode());
}
gamma.setAttribute("value", QString::number(ctx->srgb ? (Float) -1 : ctx->gamma));
if (ctx->toneMappingMethod == EGamma) {
if (exposure.isNull()) {
exposure = doc.createElement("float");
exposure.setAttribute("name", "exposure");
film.insertBefore(exposure, QDomNode());
}
exposure.setAttribute("value", QString::number(ctx->exposure));
if (!reinhardKey.isNull())
film.removeChild(reinhardKey);
if (!reinhardBurn.isNull())
film.removeChild(reinhardBurn);
} else {
if (reinhardKey.isNull()) {
reinhardKey = doc.createElement("float");
reinhardKey.setAttribute("name", "reinhardKey");
film.insertBefore(reinhardKey, QDomNode());
}
if (reinhardBurn.isNull()) {
reinhardBurn = doc.createElement("float");
reinhardBurn.setAttribute("name", "reinhardBurn");
film.insertBefore(reinhardBurn, QDomNode());
}
reinhardKey.setAttribute("value", QString::number(ctx->reinhardKey));
reinhardBurn.setAttribute("value", QString::number(ctx->reinhardBurn));
if (!exposure.isNull())
film.removeChild(exposure);
}
}
Vector2i filmSize = sceneCamera->getFilm()->getSize();
widthProperty.setAttribute("value", QString::number(filmSize.x));
heightProperty.setAttribute("value", QString::number(filmSize.y));
// ====================================================================
// Serialize the reconstruction filter configuration
// ====================================================================
QDomElement newRFilter, rfilter = findUniqueChild(film, "rfilter");
if (rfilter.isNull()) {
newRFilter = doc.createElement("rfilter");
film.appendChild(rfilter);
} else {
newRFilter = doc.createElement("rfilter");
film.insertAfter(newRFilter, rfilter);
film.removeChild(rfilter);
}
setProperties(doc, newRFilter, ctx->scene->getCamera()->
getFilm()->getReconstructionFilter()->getProperties());
// ====================================================================
// Serialize the integrator configuration
// ====================================================================
QDomElement oldIntegratorNode = findUniqueChild(root, "integrator");
QDomElement newIntegratorNode = doc.createElement("integrator");
if (oldIntegratorNode.isNull()) {
film.appendChild(oldIntegratorNode);
} else {
film.insertAfter(newIntegratorNode, oldIntegratorNode);
film.removeChild(oldIntegratorNode);
}
const Integrator *integrator = ctx->scene->getIntegrator();
setProperties(doc, newIntegratorNode, integrator->getProperties());
QDomElement currentIntegratorNode = newIntegratorNode;
while (integrator->getSubIntegrator() != NULL) {
integrator = integrator->getSubIntegrator();
QDomElement childIntegratorNode = doc.createElement("integrator");
setProperties(doc, childIntegratorNode, integrator->getProperties());
currentIntegratorNode.appendChild(childIntegratorNode);
currentIntegratorNode = childIntegratorNode;
}
root.insertAfter(newIntegratorNode, oldIntegratorNode);
root.removeChild(oldIntegratorNode);
file.setFileName(targetFile);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
QMessageBox::critical(parent, parent->tr("Unable to save"),
parent->tr("Unable to save changes: could not open the destination file <b>%1</b>.").arg(targetFile),
QMessageBox::Ok);
return;
}
/* Clean up the XML output generated by Qt so that it
is more suitable for human consumption ..
Beware: the code below is tailored to Qt's
output and won't work on arbitrary XML files */
QString textContent = doc.toString();
QTextStream input(&textContent);
QTextStream output(&file);
QRegExp
filenameRegExp("filename=\"[^\"]*\""),
nameRegExp("name=\"[^\"]*\""),
tagRegExp("^\\s*<([a-zA-Z]+) "),
leadingSpaces("^ *"),
closeTag("^\\s*</");
bool inComment = false, hasContents = false;
while (!input.atEnd()) {
QString line = input.readLine();
bool startComment = line.contains("<!--");
bool endComment = line.contains("-->");
if (startComment)
inComment = true;
if (endComment)
inComment = false;
if (inComment || endComment) {
if (startComment) {
/* Turn leading spaces into tabs */
if (leadingSpaces.indexIn(line) == 0)
line = QString('\t').repeated(leadingSpaces.matchedLength()) +
line.mid(leadingSpaces.matchedLength());
}
output << line << endl;
continue;
}
/* Make sure that the name attribute is always the first one */
int tagMatch = tagRegExp.indexIn(line),
tagLength = tagRegExp.matchedLength();
int nameMatch = nameRegExp.indexIn(line),
filenameMatch = filenameRegExp.indexIn(line),
nameLength = nameRegExp.matchedLength();
if (tagMatch != -1 && nameMatch != -1 && filenameMatch == -1) {
line = line.left(tagLength) + line.mid(nameMatch, nameLength) + " "
+ line.mid(tagMatch+tagLength, nameMatch-(tagMatch+tagLength))
+ line.mid(nameMatch+nameLength);
}
/* Add an extra newline if this is an object tag, and if there
have been siblings before it */
if (tagMatch != -1) {
const QString &el = tagRegExp.cap(1);
bool isObject = true;
isObject &= (el != "string");
isObject &= (el != "integer");
isObject &= (el != "float");
isObject &= (el != "boolean");
isObject &= (el != "vector");
isObject &= (el != "point");
isObject &= (el != "transform");
isObject &= (el != "spectrum");
isObject &= (el != "rgb");
isObject &= (el != "scale");
isObject &= (el != "translate");
isObject &= (el != "rotate");
isObject &= (el != "lookAt");
isObject &= (el != "matrix");
if (isObject && hasContents) {
output << endl;
hasContents = false;
}
if (!isObject)
hasContents = true;
}
/* Turn leading spaces into tabs */
if (leadingSpaces.indexIn(line) == 0)
line = QString('\t').repeated(leadingSpaces.matchedLength()) +
line.mid(leadingSpaces.matchedLength());
/* Remove ugly spaces */
if (line.endsWith(" />")) {
line = line.left(line.size()-4) + QString("/>");
hasContents = true;
} else if (line.endsWith(" />")) {
line = line.left(line.size()-3) + QString("/>");
hasContents = true;
} else if (line.endsWith(" >")) {
line = line.left(line.size()-2) + QString(">");
}
if (closeTag.indexIn(line) == 0)
hasContents = true;
output << line << endl;
}
file.close();
}