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;