#12460 Python: Add triangulated surface created from Python

Add TriangulatedSurface with set_geometry, set_property, get_property
and available_properties, created by
SurfaceCollection.new_triangulated_surface. When the project is saved,
the surface is written as a GOCAD TSurf file next to the project file
and read back from that file when the project is opened.
This commit is contained in:
Stefan Crooijmans authored and Kristian Bendiksen committed 2026-11-09 19:48:06 +01:00
1 parent 70a47d2ac0
commit 31f79f426f
12 files changed
+920 -1

No files matched your search

@@ -12,6 +12,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimFractureSurface.cpp
${CMAKE_CURRENT_LIST_DIR}/RimRegularSurface.cpp
${CMAKE_CURRENT_LIST_DIR}/RimRegularFileSurface.cpp
${CMAKE_CURRENT_LIST_DIR}/RimTriangulatedSurface.cpp
)
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
@@ -0,0 +1,258 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight 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 at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimTriangulatedSurface.h"
#include "RiaLogging.h"
#include "RiaQStringFormatter.h"
#include "RimProject.h"
#include "RimSurfaceCollection.h"
#include "RifSurfaceExporter.h"
#include "RifSurfaceImporter.h"
#include "Surface/RigSurface.h"
#include "Surface/RigTriangleMeshData.h"
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmObjectScriptingCapability.h"
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>
#include <algorithm>
CAF_PDM_SOURCE_INIT( RimTriangulatedSurface, "TriangulatedSurface" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimTriangulatedSurface::RimTriangulatedSurface()
{
CAF_PDM_InitScriptableObject( "Triangulated Surface", ":/ReservoirSurface16x16.png" );
CAF_PDM_InitFieldNoDefault( &m_surfaceFilePath, "SurfaceFilePath", "File" );
m_surfaceFilePath.uiCapability()->setUiReadOnly( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimTriangulatedSurface::~RimTriangulatedSurface()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimTriangulatedSurface::onLoadData()
{
return updateSurfaceData();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimSurface* RimTriangulatedSurface::createCopy()
{
auto newSurface = copyObject<RimTriangulatedSurface>();
if ( m_triangleMeshData ) newSurface->m_triangleMeshData = std::make_unique<RigTriangleMeshData>( *m_triangleMeshData );
if ( !newSurface->onLoadData() )
{
delete newSurface;
return nullptr;
}
return newSurface;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimTriangulatedSurface::setGeometry( const std::vector<cvf::Vec3d>& vertices, const std::vector<unsigned>& triangleIndices )
{
// Properties are per vertex, so a new geometry removes them
m_triangleMeshData = std::make_unique<RigTriangleMeshData>();
m_triangleMeshData->setGeometryData( vertices, triangleIndices );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimTriangulatedSurface::setProperty( const QString& key, const std::vector<float>& values )
{
CAF_ASSERT( values.size() == vertexCount() );
if ( !m_triangleMeshData ) return;
auto names = m_triangleMeshData->propertyNames();
std::vector<std::vector<float>> propertyValues;
for ( const auto& name : names )
{
propertyValues.push_back( m_triangleMeshData->propertyValues( name ) );
}
auto it = std::find( names.begin(), names.end(), key );
if ( it != names.end() )
{
propertyValues[std::distance( names.begin(), it )] = values;
}
else
{
names.push_back( key );
propertyValues.push_back( values );
}
m_triangleMeshData->setPropertyData( names, propertyValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimTriangulatedSurface::vertexCount() const
{
if ( !m_triangleMeshData ) return 0;
return m_triangleMeshData->geometry().first.size();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<float> RimTriangulatedSurface::getProperty( const QString& key ) const
{
if ( !m_triangleMeshData ) return {};
return m_triangleMeshData->propertyValues( key );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<QString> RimTriangulatedSurface::propertyNames() const
{
if ( !m_triangleMeshData ) return {};
return m_triangleMeshData->propertyNames();
}
//--------------------------------------------------------------------------------------------------
/// Write the surface to the project folder before the project file is written
//--------------------------------------------------------------------------------------------------
void RimTriangulatedSurface::onProjectBeingSaved()
{
const QString fileName = fileNameInProjectFolder();
if ( fileName.isEmpty() ) return;
if ( !m_triangleMeshData ) loadDataFromFile();
if ( !m_triangleMeshData || m_triangleMeshData->geometry().first.empty() ) return;
auto [vertices, triangleIndices] = m_triangleMeshData->geometry();
auto names = m_triangleMeshData->propertyNames();
std::vector<std::vector<float>> propertyValues;
for ( const auto& name : names )
{
propertyValues.push_back( m_triangleMeshData->propertyValues( name ) );
}
if ( !RifSurfaceExporter::writeGocadTSurfFile( fileName, m_userDescription(), vertices, triangleIndices, names, propertyValues ) )
{
RiaLogging::error( std::format( "Could not write surface '{}' to {}", m_userDescription(), fileName ) );
return;
}
m_surfaceFilePath = fileName;
}
//--------------------------------------------------------------------------------------------------
/// Returns false for fatal failure
//--------------------------------------------------------------------------------------------------
bool RimTriangulatedSurface::updateSurfaceData()
{
bool result = true;
if ( !m_triangleMeshData )
{
result = loadDataFromFile();
}
if ( !m_triangleMeshData ) return false;
auto [vertices, tringleIndices] = m_triangleMeshData->geometry();
auto surface = new RigSurface;
if ( !vertices.empty() && !tringleIndices.empty() )
{
RimSurface::applyDepthOffsetIfNeeded( &vertices );
surface->setTriangleData( tringleIndices, vertices );
}
for ( const auto& name : m_triangleMeshData->propertyNames() )
{
auto values = m_triangleMeshData->propertyValues( name );
surface->addVertexResult( name, values );
}
setSurfaceData( surface );
return result;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimTriangulatedSurface::clearCachedNativeData()
{
// The geometry only exists in memory until the project is saved
if ( !m_surfaceFilePath().path().isEmpty() ) m_triangleMeshData.reset();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimTriangulatedSurface::loadDataFromFile()
{
QString path = m_surfaceFilePath().path();
if ( path.isEmpty() ) return false;
m_triangleMeshData = std::make_unique<RigTriangleMeshData>();
RifSurfaceImporter::readGocadFile( path, m_triangleMeshData.get() );
return !( m_triangleMeshData->geometry().first.empty() );
}
//--------------------------------------------------------------------------------------------------
/// The surface name, prefixed with the folder name for surfaces in a sub folder
//--------------------------------------------------------------------------------------------------
QString RimTriangulatedSurface::fileNameInProjectFolder() const
{
const QString projectFileName = RimProject::current() ? RimProject::current()->fileName() : QString();
if ( projectFileName.isEmpty() ) return {};
QString name = m_userDescription();
if ( auto folder = firstAncestorOrThisOfType<RimSurfaceCollection>() )
{
if ( folder->firstAncestorOfType<RimSurfaceCollection>() ) name = folder->collectionName() + "_" + name;
}
name.replace( QRegularExpression( "[^A-Za-z0-9._-]" ), "_" );
return QFileInfo( projectFileName ).dir().absoluteFilePath( name + ".ts" );
}
@@ -0,0 +1,63 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight 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 at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimSurface.h"
#include "cafFilePath.h"
#include <memory>
class RigTriangleMeshData;
//==================================================================================================
/// Surface with geometry and properties set from scripting, saved as a GOCAD TSurf file next to the project file
//==================================================================================================
class RimTriangulatedSurface : public RimSurface
{
CAF_PDM_HEADER_INIT;
public:
RimTriangulatedSurface();
~RimTriangulatedSurface() override;
bool onLoadData() override;
RimSurface* createCopy() override;
void setGeometry( const std::vector<cvf::Vec3d>& vertices, const std::vector<unsigned>& triangleIndices );
void setProperty( const QString& key, const std::vector<float>& values );
size_t vertexCount() const;
std::vector<float> getProperty( const QString& key ) const;
std::vector<QString> propertyNames() const;
void onProjectBeingSaved();
private:
bool updateSurfaceData() override;
void clearCachedNativeData() override;
bool loadDataFromFile();
QString fileNameInProjectFolder() const;
private:
caf::PdmField<caf::FilePath> m_surfaceFilePath;
std::unique_ptr<RigTriangleMeshData> m_triangleMeshData;
};