diff --git a/resources/surface_description.json b/resources/surface_description.json
index ccceaab..785c887 100644
--- a/resources/surface_description.json
+++ b/resources/surface_description.json
@@ -1,206 +1,214 @@
{
- "properties": {
- "none": {
- "cmap": "None",
- "occName": "none",
- "displayName": "None",
- "units": "",
- "needsWavefunction": false,
- "needsIsovalue": false,
- "needsOrbitalSelection": false,
- "description": "
No surface property. The surface has a solid color defined by the 'None' color set in the CrystalExplorer preferences
"
- },
- "di": {
- "cmap": "RedGreenBlue",
- "occName": "di",
- "displayName": "D_i",
- "units": "Å",
- "description": "di
"
- },
- "di_norm": {
- "cmap": "RedGreenBlue",
- "occName": "di_norm",
- "displayName": "D_i (normalized)",
- "units": "",
- "description": "di (norm)
"
- },
- "de": {
- "cmap": "RedGreenBlue",
- "occName": "de",
- "displayName": "D_e",
- "description": "de
"
- },
- "de_norm": {
- "cmap": "RedGreenBlue",
- "occName": "de_norm",
- "displayName": "D_e (normalized)",
- "units": "",
- "description": "de (norm)
"
- },
- "dnorm": {
- "cmap": "RedWhiteBlue",
- "occName": "dnorm",
- "displayName": "D_norm",
- "description": "dnorm
"
- },
- "shape_index": {
- "cmap": "RedGreenBlue",
- "occName": "shape_index",
- "displayName": "Shape Index",
- "description": "Shape Index
"
- },
- "curvedness": {
- "cmap": "RedGreenBlue",
- "occName": "curvedness",
- "displayName": "Curvedness",
- "description": "Curvedness
"
- },
- "promolecule_density": {
- "cmap": "RedWhiteBlue",
- "occName": "promolecule_density",
- "displayName": "Promolecule Density",
- "units": "au",
- "description": "The sum of spherical atom electron densities
"
- },
- "electron_density": {
- "cmap": "RedWhiteBlue",
- "occName": "rho",
- "displayName": "Electron Density",
- "units": "au",
- "needsWavefunction": true,
- "description": "Total electron density calculated from a wavefunction
"
- },
- "deformation_density": {
- "cmap": "RedWhiteBlue",
- "occName": "deformation_density",
- "displayName": "Deformation Density",
- "units": "au",
- "needsWavefunction": true,
- "description": "The difference between the ab-initio Electron density, and the Promolecule (sum of spherical atoms) electron density, calculated from the wavefunction in the previous energy calculation.
"
- },
- "electric_potential": {
- "cmap": "RedWhiteBlue",
- "displayName": "Electrostatic Potential",
- "occName": "esp",
- "units": "au",
- "needsWavefunction": true,
- "description": "The ab-initio electrostatic potential from the electrons and the nuclei, calculated from the wavefunction in the previous energy calculation.
"
- },
- "orbital": {
- "cmap": "RedWhiteBlue",
- "displayName": "Orbital",
- "occName": "mo",
- "needsWavefunction": true,
- "needsOrbital": true,
- "units": "au",
- "description": "The value of the electron density of the chosen molecular orbital in the region of the surface.
"
- },
- "spin_density": {
- "cmap": "RedWhiteBlue",
- "displayName": "Spin Density",
- "occName": "spin_density",
- "units": "au",
- "description": "Difference between the alpha and beta electron densities
"
- },
- "fragment_patch": {
- "cmap": "Qualitative14Dark",
- "displayName": "Fragment Patch",
- "occName": "fragment",
- "description": "The index of the associated neighbouring fragment/molecule
"
- }
- },
-
- "surfaces": {
- "hirshfeld": {
- "displayName": "Hirshfeld",
- "occName": "hirshfeld",
- "defaultIsovalue": 0.5,
- "needsIsovalue": false,
- "description": "The Hirshfeld surface
",
- "requestableProperties": [
- "promolecule_density", "electron_density", "deformation_density",
- "electric_potential", "orbital"
- ]
- },
- "promolecule_density": {
- "displayName": "Promolecule Density",
- "occName": "promolecule",
- "defaultIsovalue": 0.002,
- "needsIsovalue": true,
- "units": "au",
- "description": "The sum of spherical atom electron densities
",
- "requestableProperties": [
- "electron_density", "deformation_density",
- "electric_potential", "orbital"
- ]
- },
- "electron_density": {
- "occName": "rho",
- "displayName": "Electron Density",
- "units": "au",
- "needsWavefunction": true,
- "needsIsovalue": true,
- "defaultIsovalue": 0.002,
- "description": "Total electron density calculated from a wavefunction
",
- "requestableProperties": [
- "promolecule_density", "deformation_density",
- "electric_potential", "orbital"
- ]
- },
- "void": {
- "displayName": "Crystal Void",
- "occName": "void",
- "defaultIsovalue": 0.002,
- "needsIsovalue": true,
- "needsCluster": true,
- "periodic": true,
- "description": "A promolecule density isosurface including all the atoms in the cluster. The surface is capped withing the unit cell, and gives an idea of voids in the crystal. Choose a lower isovalue to investigate channels or pores in the crystal.
"
- },
- "deformation_density": {
- "occName": "deformation_density",
- "displayName": "Deformation Density",
- "units": "au",
- "needsWavefunction": true,
- "needsIsovalue": true,
- "defaultIsovalue": 0.008,
- "description": "The difference between the ab-initio Electron density, and the Promolecule (sum of spherical atoms) electron density, calculated from the wavefunction in the previous energy calculation.
"
- },
- "esp": {
- "displayName": "Electrostatic Potential",
- "occName": "esp",
- "units": "au",
- "needsWavefunction": true,
- "defaultIsovalue": 0.02,
- "needsIsovalue": true,
- "description": "The ab-initio electrostatic potential from the electrons and the nuclei, calculated from the wavefunction in the previous energy calculation.
"
- },
- "orbital": {
- "displayName": "Orbital",
- "occName": "mo",
- "needsWavefunction": true,
- "needsOrbital": true,
- "needsIsovalue": true,
- "defaultIsovalue": 0.002,
- "units": "au",
- "description": "The value of the electron density of the chosen molecular orbital in the region of the surface.
"
- },
- "spin_density": {
- "displayName": "Spin Density",
- "occName": "spin_density",
- "units": "au",
- "needsWavefunction": true,
- "needsOrbital": true,
- "needsIsovalue": true,
- "defaultIsovalue": 0.002,
- "description": "Difference between the alpha and beta electron densities
"
- }
- },
- "resolutionLevels": {
- "Very Low": 1.5,
- "Low": 0.8,
- "Medium": 0.5,
- "High": 0.2,
- "Very High": 0.15,
- "Unreasonably High": 0.05
+ "properties": {
+ "none": {
+ "cmap": "None",
+ "occName": "none",
+ "displayName": "None",
+ "units": "",
+ "needsWavefunction": false,
+ "needsOrbitalSelection": false,
+ "description": "No surface property. The surface has a solid color defined by the 'None' color set in the CrystalExplorer preferences
"
+ },
+ "di": {
+ "cmap": "RedGreenBlue",
+ "occName": "di",
+ "displayName": "D_i",
+ "units": "Å",
+ "description": "di
"
+ },
+ "di_norm": {
+ "cmap": "RedGreenBlue",
+ "occName": "di_norm",
+ "displayName": "D_i (normalized)",
+ "units": "",
+ "description": "di (norm)
"
+ },
+ "de": {
+ "cmap": "RedGreenBlue",
+ "occName": "de",
+ "displayName": "D_e",
+ "description": "de
"
+ },
+ "de_norm": {
+ "cmap": "RedGreenBlue",
+ "occName": "de_norm",
+ "displayName": "D_e (normalized)",
+ "units": "",
+ "description": "de (norm)
"
+ },
+ "dnorm": {
+ "cmap": "RedWhiteBlue",
+ "occName": "dnorm",
+ "displayName": "D_norm",
+ "description": "dnorm
"
+ },
+ "shape_index": {
+ "cmap": "RedGreenBlue",
+ "occName": "shape_index",
+ "displayName": "Shape Index",
+ "description": "Shape Index
"
+ },
+ "curvedness": {
+ "cmap": "RedGreenBlue",
+ "occName": "curvedness",
+ "displayName": "Curvedness",
+ "description": "Curvedness
"
+ },
+ "promolecule_density": {
+ "cmap": "RedWhiteBlue",
+ "occName": "promolecule_density",
+ "displayName": "Promolecule Density",
+ "units": "au",
+ "description": "The sum of spherical atom electron densities
"
+ },
+ "electron_density": {
+ "cmap": "RedWhiteBlue",
+ "occName": "rho",
+ "displayName": "Electron Density",
+ "units": "au",
+ "needsWavefunction": true,
+ "description": "Total electron density calculated from a wavefunction
"
+ },
+ "deformation_density": {
+ "cmap": "RedWhiteBlue",
+ "occName": "deformation_density",
+ "displayName": "Deformation Density",
+ "units": "au",
+ "needsWavefunction": true,
+ "description": "The difference between the ab-initio Electron density, and the Promolecule (sum of spherical atoms) electron density, calculated from the wavefunction in the previous energy calculation.
"
+ },
+ "electric_potential": {
+ "cmap": "RedWhiteBlue",
+ "displayName": "Electrostatic Potential",
+ "occName": "esp",
+ "units": "au",
+ "needsWavefunction": true,
+ "description": "The ab-initio electrostatic potential from the electrons and the nuclei, calculated from the wavefunction in the previous energy calculation.
"
+ },
+ "orbital": {
+ "cmap": "RedWhiteBlue",
+ "displayName": "Orbital",
+ "occName": "mo",
+ "needsWavefunction": true,
+ "needsOrbital": true,
+ "units": "au",
+ "description": "The value of the electron density of the chosen molecular orbital in the region of the surface.
"
+ },
+ "spin_density": {
+ "cmap": "RedWhiteBlue",
+ "displayName": "Spin Density",
+ "occName": "spin_density",
+ "units": "au",
+ "description": "Difference between the alpha and beta electron densities
"
+ },
+ "fragment_patch": {
+ "cmap": "Qualitative14Dark",
+ "displayName": "Fragment Patch",
+ "occName": "fragment",
+ "description": "The index of the associated neighbouring fragment/molecule
"
+ }
+ },
+ "surfaces": {
+ "hirshfeld": {
+ "displayName": "Hirshfeld",
+ "occName": "hirshfeld",
+ "defaultIsovalue": 0.5,
+ "needsIsovalue": false,
+ "description": "The Hirshfeld surface
",
+ "requestableProperties": [
+ "promolecule_density",
+ "electron_density",
+ "deformation_density",
+ "electric_potential",
+ "orbital"
+ ]
+ },
+ "promolecule_density": {
+ "displayName": "Promolecule Density",
+ "occName": "promolecule",
+ "defaultIsovalue": 0.002,
+ "needsIsovalue": true,
+ "units": "au",
+ "description": "The sum of spherical atom electron densities
",
+ "requestableProperties": [
+ "electron_density",
+ "deformation_density",
+ "electric_potential",
+ "orbital"
+ ]
+ },
+ "electron_density": {
+ "occName": "rho",
+ "displayName": "Electron Density",
+ "units": "au",
+ "needsWavefunction": true,
+ "needsIsovalue": true,
+ "defaultIsovalue": 0.002,
+ "description": "Total electron density calculated from a wavefunction
",
+ "requestableProperties": [
+ "promolecule_density",
+ "deformation_density",
+ "electric_potential",
+ "orbital"
+ ]
+ },
+ "void": {
+ "displayName": "Crystal Void",
+ "occName": "void",
+ "defaultIsovalue": 0.002,
+ "needsIsovalue": true,
+ "needsCluster": true,
+ "periodic": true,
+ "description": "A promolecule density isosurface including all the atoms in the cluster. The surface is capped withing the unit cell, and gives an idea of voids in the crystal. Choose a lower isovalue to investigate channels or pores in the crystal.
"
+ },
+ "deformation_density": {
+ "occName": "deformation_density",
+ "displayName": "Deformation Density",
+ "units": "au",
+ "needsWavefunction": true,
+ "needsIsovalue": true,
+ "computeNegativeIsovalue": true,
+ "defaultIsovalue": 0.008,
+ "description": "The difference between the ab-initio Electron density, and the Promolecule (sum of spherical atoms) electron density, calculated from the wavefunction in the previous energy calculation.
"
+ },
+ "esp": {
+ "displayName": "Electrostatic Potential",
+ "occName": "esp",
+ "units": "au",
+ "needsWavefunction": true,
+ "defaultIsovalue": 0.02,
+ "computeNegativeIsovalue": true,
+ "needsIsovalue": true,
+ "description": "The ab-initio electrostatic potential from the electrons and the nuclei, calculated from the wavefunction in the previous energy calculation.
"
+ },
+ "orbital": {
+ "displayName": "Orbital",
+ "occName": "mo",
+ "needsWavefunction": true,
+ "needsOrbital": true,
+ "needsIsovalue": true,
+ "defaultIsovalue": 0.002,
+ "units": "au",
+ "description": "The value of the electron density of the chosen molecular orbital in the region of the surface.
"
+ },
+ "spin_density": {
+ "displayName": "Spin Density",
+ "occName": "spin_density",
+ "units": "au",
+ "needsWavefunction": true,
+ "needsOrbital": true,
+ "needsIsovalue": true,
+ "defaultIsovalue": 0.002,
+ "computeNegativeIsovalue": true,
+ "description": "Difference between the alpha and beta electron densities
"
}
+ },
+ "resolutionLevels": {
+ "Very Low": 1.5,
+ "Low": 0.8,
+ "Medium": 0.5,
+ "High": 0.2,
+ "Very High": 0.15,
+ "Unreasonably High": 0.05
+ }
}
diff --git a/src/core/isosurface_parameters.cpp b/src/core/isosurface_parameters.cpp
index 8e32b54..48adad2 100644
--- a/src/core/isosurface_parameters.cpp
+++ b/src/core/isosurface_parameters.cpp
@@ -10,7 +10,6 @@ void to_json(nlohmann::json &j,
{"occName", f.occName},
{"displayName", f.displayName},
{"units", f.needsWavefunction},
- {"needsIsovalue", f.needsIsovalue},
{"needsOrbital", f.needsOrbital},
{"description", f.description}};
}
@@ -22,19 +21,15 @@ void from_json(const nlohmann::json &j,
j.at("displayName").get_to(f.displayName);
j.at("description").get_to(f.description);
- if(j.contains("units")) {
+ if (j.contains("units")) {
j.at("units").get_to(f.units);
}
- if(j.contains("needsIsovalue")) {
- j.at("needsIsovalue").get_to(f.needsIsovalue);
- }
- if(j.contains("needsOrbital")) {
+ if (j.contains("needsOrbital")) {
j.at("needsOrbital").get_to(f.needsOrbital);
}
- if(j.contains("needsWavefunction")) {
+ if (j.contains("needsWavefunction")) {
j.at("needsWavefunction").get_to(f.needsWavefunction);
}
-
}
void to_json(nlohmann::json &j, const isosurface::SurfaceDescription &f) {
@@ -47,37 +42,41 @@ void to_json(nlohmann::json &j, const isosurface::SurfaceDescription &f) {
{"needsCluster", f.needsCluster},
{"periodic", f.periodic},
{"units", f.units},
- {"requestableProperties", f.requestableProperties}};
+ {"requestableProperties", f.requestableProperties},
+ {"computeNegativeIsovalue", f.computeNegativeIsovalue}};
}
void from_json(const nlohmann::json &j, isosurface::SurfaceDescription &s) {
j.at("displayName").get_to(s.displayName);
j.at("occName").get_to(s.occName);
j.at("description").get_to(s.description);
- if(j.contains("needsIsovalue")) {
+ if (j.contains("needsIsovalue")) {
j.at("needsIsovalue").get_to(s.needsIsovalue);
}
- if(j.contains("defaultIsovalue")) {
+ if (j.contains("defaultIsovalue")) {
j.at("defaultIsovalue").get_to(s.defaultIsovalue);
}
- if(j.contains("needsWavefunction")) {
+ if (j.contains("needsWavefunction")) {
j.at("needsWavefunction").get_to(s.needsWavefunction);
}
- if(j.contains("needsOrbital")) {
+ if (j.contains("needsOrbital")) {
j.at("needsOrbital").get_to(s.needsOrbital);
}
- if(j.contains("needsCluster")) {
+ if (j.contains("needsCluster")) {
j.at("needsCluster").get_to(s.needsCluster);
}
- if(j.contains("periodic")) {
+ if (j.contains("periodic")) {
j.at("periodic").get_to(s.periodic);
}
- if(j.contains("units")) {
+ if (j.contains("units")) {
j.at("units").get_to(s.units);
}
- if(j.contains("requestableProperties")) {
+ if (j.contains("requestableProperties")) {
j.at("requestableProperties").get_to(s.requestableProperties);
}
+ if (j.contains("computeNegativeIsovalue")) {
+ j.at("computeNegativeIsovalue").get_to(s.computeNegativeIsovalue);
+ }
}
namespace isosurface {
@@ -181,8 +180,9 @@ loadSurfaceDescriptions(const nlohmann::json &json) {
SurfaceDescription sd;
from_json(item.value(), sd);
surfaces.insert(s(item.key()), sd);
- } catch (nlohmann::json::exception& e) {
- qWarning() << "Failed to parse surface" << s(item.key()) << ":" << e.what();
+ } catch (nlohmann::json::exception &e) {
+ qWarning() << "Failed to parse surface" << s(item.key()) << ":"
+ << e.what();
}
}
return surfaces;
@@ -192,7 +192,8 @@ QMap loadResolutionLevels(const nlohmann::json &json) {
QMap resolutions;
auto s = [](const std::string &str) { return QString::fromStdString(str); };
- if (!json.contains("resolutionLevels") || !json["resolutionLevels"].is_object()) {
+ if (!json.contains("resolutionLevels") ||
+ !json["resolutionLevels"].is_object()) {
qWarning() << "JSON does not contain a 'resolutions' object";
return resolutions;
}
@@ -201,8 +202,9 @@ QMap loadResolutionLevels(const nlohmann::json &json) {
try {
double value = item.value().get();
resolutions.insert(s(item.key()), value);
- } catch (nlohmann::json::exception& e) {
- qWarning() << "Failed to parse resolution" << s(item.key()) << ":" << e.what();
+ } catch (nlohmann::json::exception &e) {
+ qWarning() << "Failed to parse resolution" << s(item.key()) << ":"
+ << e.what();
}
}
return resolutions;
diff --git a/src/core/isosurface_parameters.h b/src/core/isosurface_parameters.h
index aa10abc..8ececb2 100644
--- a/src/core/isosurface_parameters.h
+++ b/src/core/isosurface_parameters.h
@@ -70,6 +70,7 @@ struct Parameters {
Kind kind;
float isovalue{0.0};
float separation{0.2};
+ bool computeNegativeIsovalue{false};
ChemicalStructure *structure{nullptr};
MolecularWavefunction *wfn{nullptr};
Eigen::Isometry3d wfn_transform{Eigen::Isometry3d::Identity()};
@@ -91,7 +92,6 @@ struct SurfacePropertyDescription {
QString displayName;
QString units;
bool needsWavefunction{false};
- bool needsIsovalue{false};
bool needsOrbital{false};
QString description;
};
@@ -105,6 +105,7 @@ struct SurfaceDescription {
bool needsOrbital{false};
bool needsCluster{false};
bool periodic{false};
+ bool computeNegativeIsovalue{false};
QString units{""};
QString description{"Unknown"};
QStringList requestableProperties{"none"};
diff --git a/src/core/mesh.cpp b/src/core/mesh.cpp
index 3618930..4cc6de2 100644
--- a/src/core/mesh.cpp
+++ b/src/core/mesh.cpp
@@ -1,7 +1,7 @@
#include "mesh.h"
-#include "meshinstance.h"
-#include "json.h"
#include "eigen_json.h"
+#include "json.h"
+#include "meshinstance.h"
#include
#include
#include
@@ -22,7 +22,6 @@ Mesh::Mesh(Eigen::Ref vertices,
m_vertexAreas = computeVertexAreas();
m_vertexMask = Eigen::Matrix(m_vertices.cols());
resetVertexMask(true);
-
}
Mesh::Mesh(Eigen::Ref vertices, QObject *parent)
@@ -340,11 +339,12 @@ Mesh *Mesh::newFromJson(const nlohmann::json &object, QObject *parent) {
// Parse properties
const auto propertiesObj = object["properties"];
- for (const auto &item: propertiesObj.items()) {
+ for (const auto &item : propertiesObj.items()) {
const auto propertyValuesArray = item.value();
ScalarPropertyValues propertyValues(propertyValuesArray.size());
propertyValuesArray.get_to(propertyValues);
- pointCloud->setVertexProperty(QString::fromStdString(item.key()), propertyValues);
+ pointCloud->setVertexProperty(QString::fromStdString(item.key()),
+ propertyValues);
}
return pointCloud;
@@ -374,12 +374,12 @@ bool Mesh::haveChildMatchingTransform(
const Eigen::Isometry3d &transform) const {
// TODO do this in bulk
- auto check = [](occ::Vec3 a, occ::Vec3 b,
- double tolerance = 1e-1) {
- return (a - b).norm() < tolerance;
+ auto check = [](occ::Vec3 a, occ::Vec3 b, double tolerance = 1e-1) {
+ return (a - b).norm() < tolerance;
};
- auto candidateCentroid = transform.rotation() * m_centroid + transform.translation();
+ auto candidateCentroid =
+ transform.rotation() * m_centroid + transform.translation();
for (auto *child : children()) {
auto *instance = qobject_cast(child);
@@ -391,22 +391,99 @@ bool Mesh::haveChildMatchingTransform(
return false;
}
-void Mesh::resetFaceMask(bool value) {
- m_faceMask.array() = value;
-}
+void Mesh::resetFaceMask(bool value) { m_faceMask.array() = value; }
-void Mesh::resetVertexMask(bool value) {
- m_vertexMask.array() = value;
-}
+void Mesh::resetVertexMask(bool value) { m_vertexMask.array() = value; }
ScalarPropertyValues Mesh::computeVertexAreas() const {
- ScalarPropertyValues vertexAreas = ScalarPropertyValues::Zero(m_vertices.cols());
+ ScalarPropertyValues vertexAreas =
+ ScalarPropertyValues::Zero(m_vertices.cols());
for (int i = 0; i < m_faces.cols(); ++i) {
- float v = m_faceAreas(i) / 3.0f;
- vertexAreas(m_faces(0, i)) += v;
- vertexAreas(m_faces(1, i)) += v;
- vertexAreas(m_faces(2, i)) += v;
+ float v = m_faceAreas(i) / 3.0f;
+ vertexAreas(m_faces(0, i)) += v;
+ vertexAreas(m_faces(1, i)) += v;
+ vertexAreas(m_faces(2, i)) += v;
}
return vertexAreas;
}
+
+Mesh *Mesh::combine(const QList &meshes) {
+ if (meshes.isEmpty()) {
+ return nullptr;
+ }
+
+ // Validate meshes are compatible
+ const auto &first = meshes.first();
+ for (const auto *mesh : meshes) {
+ if (!mesh)
+ continue;
+
+ if (mesh->kind() != first->kind()) {
+ qWarning() << "Cannot combine meshes of different kinds";
+ return nullptr;
+ }
+
+ if (mesh->atomsInside() != first->atomsInside() ||
+ mesh->atomsOutside() != first->atomsOutside()) {
+ qWarning() << "Cannot combine meshes with different atom configurations";
+ return nullptr;
+ }
+ }
+
+ int totalVertices = 0;
+ int totalFaces = 0;
+ for (const auto *mesh : meshes) {
+ if (!mesh)
+ continue;
+ totalVertices += mesh->numberOfVertices();
+ totalFaces += mesh->numberOfFaces();
+ }
+
+ // Prepare combined vertex and face matrices
+ VertexList combinedVertices(3, totalVertices);
+ FaceList combinedFaces(3, totalFaces);
+ ScalarPropertyValues isovalues(totalVertices);
+
+ // Copy data with offset tracking
+ int vertexOffset = 0;
+ int faceOffset = 0;
+
+ for (const auto *mesh : meshes) {
+ if (!mesh)
+ continue;
+
+ const int nVerts = mesh->numberOfVertices();
+ const int nFaces = mesh->numberOfFaces();
+
+ // Copy vertices
+ combinedVertices.block(0, vertexOffset, 3, nVerts) = mesh->vertices();
+
+ // Copy faces with offset
+ auto faces = mesh->faces();
+ faces.array() += vertexOffset;
+ combinedFaces.block(0, faceOffset, 3, nFaces) = faces;
+
+ // Set isovalues for this mesh's vertices
+ isovalues.segment(vertexOffset, nVerts).array() =
+ mesh->parameters().isovalue;
+
+ vertexOffset += nVerts;
+ faceOffset += nFaces;
+ }
+
+ // Create new mesh
+ auto *combinedMesh = new Mesh(combinedVertices, combinedFaces);
+
+ // Copy parameters from first mesh
+ combinedMesh->setParameters(first->parameters());
+
+ // Set the isovalue property
+ combinedMesh->setVertexProperty("isovalue", isovalues);
+
+ // Copy description
+ combinedMesh->setDescription(
+ QString("Combined mesh from %1 isosurfaces").arg(meshes.size()));
+
+ return combinedMesh;
+}
diff --git a/src/core/mesh.h b/src/core/mesh.h
index 3a80986..fdd48a8 100644
--- a/src/core/mesh.h
+++ b/src/core/mesh.h
@@ -140,6 +140,8 @@ class Mesh : public QObject {
inline void highlightVertex(int v) { m_vertexHighlights.insert(v); }
[[nodiscard]] const auto &vertexHighlights() const { return m_vertexHighlights; }
+ static Mesh * combine(const QList &meshes);
+
signals:
void visibilityChanged();
void transparencyChanged();
@@ -187,3 +189,4 @@ class Mesh : public QObject {
ScalarPropertyValues m_emptyProperty;
isosurface::Parameters m_params;
};
+
diff --git a/src/core/meshpropertymodel.cpp b/src/core/meshpropertymodel.cpp
index 17fe449..3400c3b 100644
--- a/src/core/meshpropertymodel.cpp
+++ b/src/core/meshpropertymodel.cpp
@@ -1,186 +1,196 @@
#include "meshpropertymodel.h"
-MeshPropertyModel::MeshPropertyModel(QObject* parent)
+MeshPropertyModel::MeshPropertyModel(QObject *parent)
: QAbstractListModel(parent), m_mesh(nullptr) {}
bool MeshPropertyModel::isValid() const {
- if(m_meshInstance || m_mesh) return true;
- return false;
+ if (m_meshInstance || m_mesh)
+ return true;
+ return false;
}
QString MeshPropertyModel::getSelectedProperty() const {
- if(!m_mesh) return "";
- if(m_meshInstance) {
- return m_meshInstance->getSelectedProperty();
- }
- else {
- return m_mesh->getSelectedProperty();
- }
+ if (!m_mesh)
+ return "";
+ if (m_meshInstance) {
+ return m_meshInstance->getSelectedProperty();
+ } else {
+ return m_mesh->getSelectedProperty();
+ }
}
void MeshPropertyModel::setMeshInstance(MeshInstance *meshInstance) {
- if(m_meshInstance == meshInstance) return;
- m_blockedWhileResetting = true;
- beginResetModel();
- m_mesh = meshInstance->mesh();
- m_meshInstance = meshInstance;
- QString prop = meshInstance->getSelectedProperty();
- endResetModel();
- setSelectedProperty(prop);
- m_blockedWhileResetting = false;
-}
-
-void MeshPropertyModel::setMesh(Mesh* mesh) {
- if((m_mesh == mesh) && (!m_meshInstance)) return;
- m_blockedWhileResetting = true;
- beginResetModel();
- m_mesh = mesh;
- m_meshInstance = nullptr; // not associated with a mesh instance
- QString prop = mesh->getSelectedProperty();
- endResetModel();
- setSelectedProperty(prop);
- m_blockedWhileResetting = false;
+ if (m_meshInstance == meshInstance)
+ return;
+ m_blockedWhileResetting = true;
+ beginResetModel();
+ m_mesh = meshInstance->mesh();
+ m_meshInstance = meshInstance;
+ QString prop = meshInstance->getSelectedProperty();
+ endResetModel();
+ setSelectedProperty(prop);
+ m_blockedWhileResetting = false;
+}
+
+void MeshPropertyModel::setMesh(Mesh *mesh) {
+ if ((m_mesh == mesh) && (!m_meshInstance))
+ return;
+ m_blockedWhileResetting = true;
+ beginResetModel();
+ m_mesh = mesh;
+ m_meshInstance = nullptr; // not associated with a mesh instance
+ QString prop = mesh->getSelectedProperty();
+ endResetModel();
+ setSelectedProperty(prop);
+ m_blockedWhileResetting = false;
}
int MeshPropertyModel::rowCount(const QModelIndex &parent) const {
- if (parent.isValid() || !m_mesh) {
- return 0;
- }
- return m_mesh->availableVertexProperties().size(); // Or face properties, depending on what you're listing
+ if (parent.isValid() || !m_mesh) {
+ return 0;
+ }
+ return m_mesh->availableVertexProperties()
+ .size(); // Or face properties, depending on what you're listing
}
double MeshPropertyModel::volume() const {
- if(!m_mesh) return 0.0;
- return m_mesh->volume();
+ if (!m_mesh)
+ return 0.0;
+ return m_mesh->volume();
}
double MeshPropertyModel::area() const {
- if(!m_mesh) return 0.0;
- return m_mesh->surfaceArea();
+ if (!m_mesh)
+ return 0.0;
+ return m_mesh->surfaceArea();
}
double MeshPropertyModel::globularity() const {
- if(!m_mesh) return 0.0;
- return m_mesh->globularity();
+ if (!m_mesh)
+ return 0.0;
+ return m_mesh->globularity();
}
double MeshPropertyModel::asphericity() const {
- if(!m_mesh) return 0.0;
- return m_mesh->asphericity();
+ if (!m_mesh)
+ return 0.0;
+ return m_mesh->asphericity();
}
bool MeshPropertyModel::isFingerprintable() const {
- if(!m_mesh) return false;
- const auto ¶ms = m_mesh->parameters();
- return params.kind == isosurface::Kind::Hirshfeld && params.separation < 0.21;
+ if (!m_mesh)
+ return false;
+ const auto ¶ms = m_mesh->parameters();
+ return params.kind == isosurface::Kind::Hirshfeld && params.separation < 0.21;
}
QVariant MeshPropertyModel::data(const QModelIndex &index, int role) const {
- if (!index.isValid() || !m_mesh) {
- return QVariant();
- }
-
- // Handle custom roles
- switch (role) {
- case VolumeRole:
- return volume();
- case AreaRole:
- return area();
- case GlobularityRole:
- return globularity();
- case AsphericityRole:
- return asphericity();
- case TransparentRole:
- return isTransparent();
- default:
- break;
- }
-
- if (role == Qt::DisplayRole) {
- // Assuming you want to list vertex properties. Adjust accordingly if you're listing something else.
- QStringList properties = m_mesh->availableVertexProperties();
- if (index.row() >= 0 && index.row() < properties.size()) {
- return properties.at(index.row());
- }
+ if (!index.isValid() || !m_mesh) {
+ return QVariant();
+ }
+
+ // Handle custom roles
+ switch (role) {
+ case VolumeRole:
+ return volume();
+ case AreaRole:
+ return area();
+ case GlobularityRole:
+ return globularity();
+ case AsphericityRole:
+ return asphericity();
+ case TransparentRole:
+ return isTransparent();
+ default:
+ break;
+ }
+
+ if (role == Qt::DisplayRole) {
+ // Assuming you want to list vertex properties. Adjust accordingly if you're
+ // listing something else.
+ QStringList properties = m_mesh->availableVertexProperties();
+ if (index.row() >= 0 && index.row() < properties.size()) {
+ return properties.at(index.row());
}
+ }
- return QVariant();
+ return QVariant();
}
-MeshPropertyModel::PropertyStatistics MeshPropertyModel::getSelectedPropertyStatistics() const {
+MeshPropertyModel::PropertyStatistics
+MeshPropertyModel::getSelectedPropertyStatistics() const {
- if(!m_mesh && !m_meshInstance) return {};
+ if (!m_mesh && !m_meshInstance)
+ return {};
- const auto &values = m_mesh->vertexProperty(getSelectedProperty());
- return {
- values.minCoeff(),
- values.maxCoeff(),
- values.mean()
- };
+ const auto &values = m_mesh->vertexProperty(getSelectedProperty());
+ return {values.minCoeff(), values.maxCoeff(), values.mean()};
}
Mesh::ScalarPropertyRange MeshPropertyModel::getSelectedPropertyRange() const {
- if(!m_mesh) return {};
- return m_mesh->vertexPropertyRange(getSelectedProperty());
+ if (!m_mesh)
+ return {};
+ return m_mesh->vertexPropertyRange(getSelectedProperty());
}
-void MeshPropertyModel::setSelectedPropertyRange(Mesh::ScalarPropertyRange range) {
- if(!m_mesh) return;
+void MeshPropertyModel::setSelectedPropertyRange(
+ Mesh::ScalarPropertyRange range) {
+ if (!m_mesh)
+ return;
- QString propertyName;
- if(m_meshInstance) {
- propertyName = m_meshInstance->getSelectedProperty();
- }
- else if (m_mesh) {
- propertyName = m_mesh->getSelectedProperty();
- }
- m_mesh->setVertexPropertyRange(propertyName, range);
- emit propertySelectionChanged(propertyName);
+ QString propertyName;
+ if (m_meshInstance) {
+ propertyName = m_meshInstance->getSelectedProperty();
+ } else if (m_mesh) {
+ propertyName = m_mesh->getSelectedProperty();
+ }
+ m_mesh->setVertexPropertyRange(propertyName, range);
+ emit propertySelectionChanged(propertyName);
}
void MeshPropertyModel::setSelectedProperty(QString propertyName) {
- if (!(m_mesh || m_meshInstance)) return;
- if (m_blockedWhileResetting) {
- emit propertySelectionChanged(propertyName);
- return;
- }
-
- if(m_meshInstance) {
- m_meshInstance->setSelectedProperty(propertyName);
- }
- else {
- m_mesh->setSelectedProperty(propertyName);
- }
+ if (!(m_mesh || m_meshInstance))
+ return;
+ if (m_blockedWhileResetting) {
emit propertySelectionChanged(propertyName);
+ return;
+ }
+
+ if (m_meshInstance) {
+ m_meshInstance->setSelectedProperty(propertyName);
+ } else {
+ m_mesh->setSelectedProperty(propertyName);
+ }
+ emit propertySelectionChanged(propertyName);
}
bool MeshPropertyModel::isTransparent() const {
- if(m_meshInstance) return m_meshInstance->isTransparent();
- if (!m_mesh) return false;
- return m_mesh->isTransparent();
+ if (m_meshInstance)
+ return m_meshInstance->isTransparent();
+ if (!m_mesh)
+ return false;
+ return m_mesh->isTransparent();
}
void MeshPropertyModel::setTransparent(bool transparent) {
- if (!m_mesh) return;
- if(m_meshInstance) {
- m_meshInstance->setTransparent(transparent);
- }
- else {
- m_mesh->setTransparent(transparent);
- }
+ if (!m_mesh)
+ return;
+ if (m_meshInstance) {
+ m_meshInstance->setTransparent(transparent);
+ } else {
+ m_mesh->setTransparent(transparent);
+ }
}
QHash MeshPropertyModel::roleNames() const {
- QHash roles = QAbstractListModel::roleNames();
- roles[VolumeRole] = "volume";
- roles[AreaRole] = "area";
- roles[GlobularityRole] = "globularity";
- roles[AsphericityRole] = "asphericity";
- roles[TransparentRole] = "transparent";
- roles[FingerprintableRole] = "fingerprintable";
- return roles;
+ QHash roles = QAbstractListModel::roleNames();
+ roles[VolumeRole] = "volume";
+ roles[AreaRole] = "area";
+ roles[GlobularityRole] = "globularity";
+ roles[AsphericityRole] = "asphericity";
+ roles[TransparentRole] = "transparent";
+ roles[FingerprintableRole] = "fingerprintable";
+ return roles;
}
-Mesh * MeshPropertyModel::getMesh() {
- return m_mesh;
-}
+Mesh *MeshPropertyModel::getMesh() { return m_mesh; }
diff --git a/src/dialogs/surfacegenerationdialog.cpp b/src/dialogs/surfacegenerationdialog.cpp
index ff14a07..3061797 100644
--- a/src/dialogs/surfacegenerationdialog.cpp
+++ b/src/dialogs/surfacegenerationdialog.cpp
@@ -97,6 +97,7 @@ void SurfaceGenerationDialog::validate() {
isosurface::Parameters parameters;
parameters.isovalue = ui->isovalueLineEdit->text().toFloat();
parameters.kind = currentKind();
+ parameters.computeNegativeIsovalue = shouldAlsoCalculateNegativeIsovalue();
QString prop = currentPropertyName();
if (prop != "none") {
parameters.additionalProperties.append(prop);
@@ -203,7 +204,12 @@ bool SurfaceGenerationDialog::needIsovalueBox() {
const auto ¤tSurface = ui->surfaceComboBox->currentSurfaceDescription();
const auto ¤tSurfaceProperty =
ui->propertyComboBox->currentSurfacePropertyDescription();
- return currentSurface.needsIsovalue || currentSurfaceProperty.needsIsovalue;
+ return currentSurface.needsIsovalue;
+}
+
+bool SurfaceGenerationDialog::shouldAlsoCalculateNegativeIsovalue() const {
+ const auto ¤tSurface = ui->surfaceComboBox->currentSurfaceDescription();
+ return currentSurface.computeNegativeIsovalue;
}
bool SurfaceGenerationDialog::needClusterOptions() {
diff --git a/src/dialogs/surfacegenerationdialog.h b/src/dialogs/surfacegenerationdialog.h
index 1787fba..0d87c16 100644
--- a/src/dialogs/surfacegenerationdialog.h
+++ b/src/dialogs/surfacegenerationdialog.h
@@ -66,6 +66,7 @@ private slots:
bool needOrbitalBox();
bool needWavefunction();
bool mustCalculateWavefunction();
+ bool shouldAlsoCalculateNegativeIsovalue() const;
bool needWavefunctionCalc();
wfn::Parameters getCurrentWavefunctionParameters();
diff --git a/src/exe/occsurfacetask.cpp b/src/exe/occsurfacetask.cpp
index d81cfa0..50cec5c 100644
--- a/src/exe/occsurfacetask.cpp
+++ b/src/exe/occsurfacetask.cpp
@@ -31,44 +31,51 @@ void OccSurfaceTask::start() {
auto name = baseName();
auto input = inputFileName();
auto env = environmentFileName();
- auto outputName = outputFileName();
auto wfn = wavefunctionFilename();
QStringList args{"isosurface", input};
QList reqs{FileDependency(input)};
+ QList outputs;
if (!env.isEmpty()) {
args << env;
reqs << env;
}
-
- args << "-o" << outputName;
+ args << "-o" << outputFileNameTemplate();
args << QString("--kind=%1").arg(kind());
args << QString("--separation=%1").arg(separation());
args << QString("--isovalue=%1").arg(isovalue());
+
+ if (properties().value("computeNegativeIsovalue", false).toBool()) {
+ args << QString("--isovalue=%1").arg(-isovalue());
+ }
+
args << QString("--threads=%1").arg(threads());
if (properties().contains("background_density")) {
args << QString("--background-density=%1")
.arg(properties().value("background_density").toFloat());
}
- if(!wfn.isEmpty()) {
+ if (!wfn.isEmpty()) {
args << "-w" << wfn;
reqs << wfn;
appendWavefunctionTransformArguments(args);
}
- for(const auto &prop: m_parameters.additionalProperties) {
+ for (const auto &prop : m_parameters.additionalProperties) {
args << "--properties=" + prop;
}
qDebug() << "Arguments:" << args;
setArguments(args);
setRequirements(reqs);
- setOutputs({FileDependency(outputName, outputName)});
+ for (const auto &filename : outputFileNames()) {
+ outputs.append(FileDependency{filename, filename});
+ }
+ setOutputs(outputs);
emit progressText("Starting OCC process");
ExternalProgramTask::start();
qDebug() << "Finish occ task start";
@@ -96,8 +103,18 @@ QString OccSurfaceTask::environmentFileName() const {
return properties().value("environmentFile", "").toString();
}
-QString OccSurfaceTask::outputFileName() const {
- return properties().value("outputFile", "surface.ply").toString();
+QString OccSurfaceTask::outputFileNameTemplate() const {
+ return properties()
+ .value("outputFileNameTemplate", "surface{}.ply")
+ .toString();
+}
+
+QStringList OccSurfaceTask::outputFileNames() const {
+
+ if (properties().value("computeNegativeIsovalue", false).toBool()) {
+ return {"surface0.ply", "surface1.ply"};
+ }
+ return {"surface.ply"};
}
QString OccSurfaceTask::wavefunctionFilename() const {
diff --git a/src/exe/occsurfacetask.h b/src/exe/occsurfacetask.h
index 0e2a563..2a6ce47 100644
--- a/src/exe/occsurfacetask.h
+++ b/src/exe/occsurfacetask.h
@@ -18,7 +18,8 @@ class OccSurfaceTask: public ExternalProgramTask {
QString environmentFileName() const;
QString wavefunctionSuffix() const;
QString wavefunctionFilename() const;
- QString outputFileName() const;
+ QString outputFileNameTemplate() const;
+ QStringList outputFileNames() const;
float separation() const;
float isovalue() const;
diff --git a/src/io/io_utilities.cpp b/src/io/io_utilities.cpp
index 7f6e05e..3a5b412 100644
--- a/src/io/io_utilities.cpp
+++ b/src/io/io_utilities.cpp
@@ -46,6 +46,14 @@ bool deleteFile(const QString &filePath) {
return true;
}
+bool deleteFiles(const QStringList &filePaths) {
+ bool success = true;
+ for(const auto &filePath: filePaths) {
+ success &= deleteFile(filePath);
+ }
+ return success;
+}
+
bool copyFile(const QString &sourcePath, const QString &targetPath,
bool overwrite) {
// if they're the same file just do nothing.
diff --git a/src/io/io_utilities.h b/src/io/io_utilities.h
index a08d936..142f6a5 100644
--- a/src/io/io_utilities.h
+++ b/src/io/io_utilities.h
@@ -7,6 +7,7 @@ bool isTextFile(const QString& filePath);
bool writeTextFile(const QString &filename, const QString &text);
bool copyFile(const QString &sourcePath, const QString &targetPath, bool overwrite);
bool deleteFile(const QString &filePath);
+bool deleteFiles(const QStringList &filePaths);
QString changeSuffix(const QString &filePath, const QString &suffix);
QByteArray readFileBytes(const QString& filePath, QIODeviceBase::OpenMode mode = QIODevice::Text);
diff --git a/src/io/load_mesh.cpp b/src/io/load_mesh.cpp
index 9337796..ef80fd5 100644
--- a/src/io/load_mesh.cpp
+++ b/src/io/load_mesh.cpp
@@ -8,4 +8,12 @@ Mesh* loadMesh(const QString& filename) {
return mesh ? mesh.release() : nullptr;
}
+QList loadMeshes(QStringList& filenames) {
+ QList result;
+ for(const auto &filename: filenames) {
+ result.append(loadMesh(filename));
+ }
+ return result;
+}
+
} // namespace io
diff --git a/src/io/load_mesh.h b/src/io/load_mesh.h
index 0588074..f694e84 100644
--- a/src/io/load_mesh.h
+++ b/src/io/load_mesh.h
@@ -1,8 +1,11 @@
#pragma once
#include "mesh.h"
+#include
+#include
namespace io {
Mesh * loadMesh(const QString &filename);
+QList loadMeshes(QStringList& filenames);
}
diff --git a/src/volume/isosurface_calculator.cpp b/src/volume/isosurface_calculator.cpp
index 33f2e56..ac9e0bc 100644
--- a/src/volume/isosurface_calculator.cpp
+++ b/src/volume/isosurface_calculator.cpp
@@ -103,23 +103,26 @@ void IsosurfaceCalculator::start(isosurface::Parameters params) {
m_name = surfaceName();
m_defaultProperty = isosurface::defaultPropertyForKind(params.kind);
- OccSurfaceTask *surface_task = new OccSurfaceTask();
- surface_task->setExecutable(m_occExecutable);
- surface_task->setEnvironment(m_environment);
- surface_task->setSurfaceParameters(params);
- surface_task->setProperty("name", m_name);
- surface_task->setProperty("inputFile", interiorFilename);
- surface_task->setProperty("environmentFile", exteriorFilename);
- surface_task->setProperty("wavefunctionFile", wavefunctionFilename);
- surface_task->setDeleteWorkingFiles(m_deleteWorkingFiles);
+ OccSurfaceTask *surfaceTask = new OccSurfaceTask();
+ surfaceTask->setExecutable(m_occExecutable);
+ surfaceTask->setEnvironment(m_environment);
+ surfaceTask->setSurfaceParameters(params);
+ surfaceTask->setProperty("name", m_name);
+ surfaceTask->setProperty("inputFile", interiorFilename);
+ surfaceTask->setProperty("environmentFile", exteriorFilename);
+ surfaceTask->setProperty("wavefunctionFile", wavefunctionFilename);
+ surfaceTask->setDeleteWorkingFiles(m_deleteWorkingFiles);
qDebug() << "Generating " << isosurface::kindToString(params.kind)
<< "surface with isovalue: " << params.isovalue;
- surface_task->setProperty("isovalue", params.isovalue);
+ surfaceTask->setProperty("isovalue", params.isovalue);
+ if(params.computeNegativeIsovalue) {
+ surfaceTask->setProperty("computeNegativeIsovalue", true);
+ }
- auto taskId = m_taskManager->add(surface_task);
- m_filename = "surface.ply";
+ auto taskId = m_taskManager->add(surfaceTask);
+ m_fileNames = surfaceTask->outputFileNames();
- connect(surface_task, &Task::completed, this,
+ connect(surfaceTask, &Task::completed, this,
&IsosurfaceCalculator::surfaceComplete);
}
@@ -134,19 +137,26 @@ QString IsosurfaceCalculator::surfaceName() {
void IsosurfaceCalculator::surfaceComplete() {
qDebug() << "Task" << m_name << "finished in IsosurfaceCalculator";
- Mesh *mesh = io::loadMesh(m_filename);
+ QList meshes = io::loadMeshes(m_fileNames);
if (m_deleteWorkingFiles) {
- io::deleteFile(m_filename);
+ io::deleteFiles(m_fileNames);
+ }
+ int idx = 0;
+ for(auto * mesh: meshes) {
+ if(!mesh) continue;
+ auto params = m_parameters;
+ if(idx > 0) params.isovalue = - params.isovalue;
+ mesh->setObjectName(m_name);
+ mesh->setParameters(params);
+ mesh->setSelectedProperty(m_defaultProperty);
+ mesh->setAtomsInside(m_atomsInside);
+ mesh->setAtomsOutside(m_atomsOutside);
+ mesh->setParent(m_structure);
+ // create the child instance that will be shown
+ MeshInstance *instance = new MeshInstance(mesh);
+ instance->setObjectName("+ {x,y,z} [0,0,0]");
+ idx++;
}
- mesh->setObjectName(m_name);
- mesh->setParameters(m_parameters);
- mesh->setSelectedProperty(m_defaultProperty);
- mesh->setAtomsInside(m_atomsInside);
- mesh->setAtomsOutside(m_atomsOutside);
- mesh->setParent(m_structure);
- // create the child instance that will be shown
- MeshInstance *instance = new MeshInstance(mesh);
- instance->setObjectName("+ {x,y,z} [0,0,0]");
}
} // namespace volume
diff --git a/src/volume/isosurface_calculator.h b/src/volume/isosurface_calculator.h
index c99368f..15bf3fa 100644
--- a/src/volume/isosurface_calculator.h
+++ b/src/volume/isosurface_calculator.h
@@ -29,7 +29,7 @@ private slots:
QString m_occExecutable{"occ"};
QProcessEnvironment m_environment;
QString m_name;
- QString m_filename;
+ QStringList m_fileNames;
QString m_defaultProperty{"dnorm"};
isosurface::Parameters m_parameters;
std::vector m_atomsInside;