mirror of
https://github.com/OPM/ResInsight.git
synced 2025-02-25 18:55:39 -06:00
Make scriptability a CAF-feature
This commit is contained in:
@@ -95,6 +95,8 @@
|
||||
#include "RiuViewer.h"
|
||||
#include "RiuViewerCommands.h"
|
||||
|
||||
#include "cafPdmCodeGenerator.h"
|
||||
#include "cafPdmDataValueField.h"
|
||||
#include "cafPdmDefaultObjectFactory.h"
|
||||
#include "cafPdmSettings.h"
|
||||
#include "cafPdmUiModelChangeDetector.h"
|
||||
@@ -1700,232 +1702,26 @@ void RiaApplication::resetProject()
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaApplication::generatePythonClasses( const QString& fileName )
|
||||
bool RiaApplication::generateCode( const QString& fileName, QString* errMsg )
|
||||
{
|
||||
auto factory = caf::PdmDefaultObjectFactory::instance();
|
||||
CAF_ASSERT( errMsg );
|
||||
|
||||
QFile pythonFile( fileName );
|
||||
if ( !pythonFile.open( QIODevice::WriteOnly | QIODevice::Text ) )
|
||||
QFile outputFile( fileName );
|
||||
if ( !outputFile.open( QIODevice::WriteOnly | QIODevice::Text ) )
|
||||
{
|
||||
return;
|
||||
*errMsg = QString( "Could not open file %1 for writing" ).arg( fileName );
|
||||
return false;
|
||||
}
|
||||
QTextStream out( &pythonFile );
|
||||
std::vector<QString> classKeywords = factory->classKeywords();
|
||||
QTextStream out( &outputFile );
|
||||
|
||||
std::vector<std::shared_ptr<const caf::PdmObject>> dummyObjects;
|
||||
for ( QString classKeyword : classKeywords )
|
||||
std::string fileExt = QFileInfo( fileName ).suffix().toStdString();
|
||||
|
||||
std::unique_ptr<caf::PdmCodeGenerator> generator( caf::PdmCodeGeneratorFactory::instance()->create( fileExt ) );
|
||||
if ( !generator )
|
||||
{
|
||||
auto objectHandle = factory->create( classKeyword );
|
||||
const caf::PdmObject* object = dynamic_cast<const caf::PdmObject*>( objectHandle );
|
||||
CAF_ASSERT( object );
|
||||
|
||||
std::shared_ptr<const caf::PdmObject> sharedObject( object );
|
||||
if ( RicfObjectCapability::isScriptable( sharedObject.get() ) )
|
||||
{
|
||||
dummyObjects.push_back( sharedObject );
|
||||
}
|
||||
*errMsg = QString( "No code generator matches the provided file extension" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort to make sure super classes get created before sub classes
|
||||
std::sort( dummyObjects.begin(),
|
||||
dummyObjects.end(),
|
||||
[]( std::shared_ptr<const caf::PdmObject> lhs, std::shared_ptr<const caf::PdmObject> rhs ) {
|
||||
if ( lhs->inheritsClassWithKeyword( rhs->classKeyword() ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return lhs->classKeyword() < rhs->classKeyword();
|
||||
} );
|
||||
|
||||
std::map<QString, std::map<QString, std::pair<QString, QString>>> classAttributesGenerated;
|
||||
std::map<QString, std::map<QString, QString>> classMethodsGenerated;
|
||||
std::map<QString, QString> classCommentsGenerated;
|
||||
|
||||
// First generate all attributes and comments to go into each object
|
||||
for ( std::shared_ptr<const caf::PdmObject> object : dummyObjects )
|
||||
{
|
||||
const std::list<QString>& classInheritanceStack = object->classInheritanceStack();
|
||||
|
||||
for ( auto it = classInheritanceStack.begin(); it != classInheritanceStack.end(); ++it )
|
||||
{
|
||||
const QString& classKeyword = *it;
|
||||
QString scriptClassComment = RicfObjectCapability::scriptClassComment( classKeyword );
|
||||
|
||||
std::map<QString, QString> attributesGenerated;
|
||||
|
||||
if ( !scriptClassComment.isEmpty() ) classCommentsGenerated[classKeyword] = scriptClassComment;
|
||||
|
||||
if ( classKeyword == object->classKeyword() )
|
||||
{
|
||||
std::vector<caf::PdmFieldHandle*> fields;
|
||||
object->fields( fields );
|
||||
for ( auto field : fields )
|
||||
{
|
||||
auto pdmValueField = dynamic_cast<const caf::PdmValueField*>( field );
|
||||
if ( pdmValueField )
|
||||
{
|
||||
QString keyword = pdmValueField->keyword();
|
||||
auto ricfHandle = field->template capability<RicfFieldHandle>();
|
||||
if ( ricfHandle != nullptr )
|
||||
{
|
||||
bool shouldBeMethod = false;
|
||||
auto proxyField = dynamic_cast<const caf::PdmProxyFieldHandle*>( field );
|
||||
if ( proxyField && proxyField->isStreamingField() ) shouldBeMethod = true;
|
||||
|
||||
QString snake_field_name = RiaTextStringTools::camelToSnakeCase( ricfHandle->fieldName() );
|
||||
if ( classAttributesGenerated[field->ownerClass()].count( snake_field_name ) ) continue;
|
||||
if ( classMethodsGenerated[field->ownerClass()].count( snake_field_name ) ) continue;
|
||||
|
||||
QString comment;
|
||||
{
|
||||
QStringList commentComponents;
|
||||
commentComponents << pdmValueField->capability<caf::PdmUiFieldHandle>()->uiName();
|
||||
commentComponents << pdmValueField->capability<caf::PdmUiFieldHandle>()->uiWhatsThis();
|
||||
commentComponents.removeAll( QString( "" ) );
|
||||
comment = commentComponents.join( ". " );
|
||||
}
|
||||
|
||||
QVariant valueVariant = pdmValueField->toQVariant();
|
||||
QString dataTypeString = valueVariant.typeName();
|
||||
|
||||
if ( shouldBeMethod )
|
||||
{
|
||||
if ( proxyField->hasGetter() )
|
||||
{
|
||||
QString fullComment =
|
||||
QString( " \"\"\"%1\n Returns:\n %2\n \"\"\"" )
|
||||
.arg( comment )
|
||||
.arg( dataTypeString );
|
||||
|
||||
QString fieldCode = QString( " def %1(self):\n%2\n return "
|
||||
"self._call_get_method(\"%3\")\n" )
|
||||
.arg( snake_field_name )
|
||||
.arg( fullComment )
|
||||
.arg( ricfHandle->fieldName() );
|
||||
classMethodsGenerated[field->ownerClass()][snake_field_name] = fieldCode;
|
||||
}
|
||||
if ( proxyField->hasSetter() )
|
||||
{
|
||||
QString fullComment = QString( " \"\"\"Set %1\n Attributes:\n"
|
||||
" values (%2): data\n \"\"\"" )
|
||||
.arg( comment )
|
||||
.arg( dataTypeString );
|
||||
|
||||
QString fieldCode = QString( " def set_%1(self, values):\n%2\n "
|
||||
"self._call_set_method(\"%3\", values)\n" )
|
||||
.arg( snake_field_name )
|
||||
.arg( fullComment )
|
||||
.arg( ricfHandle->fieldName() );
|
||||
classMethodsGenerated[field->ownerClass()][QString( "set_%1" ).arg( snake_field_name )] =
|
||||
fieldCode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QString fieldCode = QString( " self.%1 = None\n" ).arg( snake_field_name );
|
||||
QString fullComment =
|
||||
QString( "%1 (%2): %3\n" ).arg( snake_field_name ).arg( dataTypeString ).arg( comment );
|
||||
|
||||
classAttributesGenerated[field->ownerClass()][snake_field_name].first = fieldCode;
|
||||
classAttributesGenerated[field->ownerClass()][snake_field_name].second = fullComment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write out classes
|
||||
std::set<QString> classesWritten;
|
||||
for ( std::shared_ptr<const caf::PdmObject> object : dummyObjects )
|
||||
{
|
||||
const std::list<QString>& classInheritanceStack = object->classInheritanceStack();
|
||||
std::list<QString> scriptSuperClassNames;
|
||||
|
||||
for ( auto it = classInheritanceStack.begin(); it != classInheritanceStack.end(); ++it )
|
||||
{
|
||||
const QString& classKeyword = *it;
|
||||
QString scriptClassName = RicfObjectCapability::scriptClassNameFromClassKeyword( classKeyword );
|
||||
if ( scriptClassName.isEmpty() ) scriptClassName = classKeyword;
|
||||
|
||||
if ( !classesWritten.count( scriptClassName ) )
|
||||
{
|
||||
QString classCode;
|
||||
if ( scriptSuperClassNames.empty() )
|
||||
{
|
||||
classCode = QString( "class %1:\n" ).arg( scriptClassName );
|
||||
}
|
||||
else
|
||||
{
|
||||
classCode = QString( "class %1(%2):\n" ).arg( scriptClassName ).arg( scriptSuperClassNames.back() );
|
||||
}
|
||||
if ( !classCommentsGenerated[classKeyword].isEmpty() || !classAttributesGenerated[classKeyword].empty() )
|
||||
{
|
||||
classCode += " \"\"\"\n";
|
||||
if ( !classCommentsGenerated[classKeyword].isEmpty() )
|
||||
{
|
||||
if ( !classCommentsGenerated[classKeyword].isEmpty() )
|
||||
{
|
||||
classCode += QString( " %1\n\n" ).arg( classCommentsGenerated[classKeyword] );
|
||||
}
|
||||
}
|
||||
if ( !classAttributesGenerated[classKeyword].empty() )
|
||||
{
|
||||
classCode += " Attributes\n";
|
||||
for ( auto keyWordValuePair : classAttributesGenerated[classKeyword] )
|
||||
{
|
||||
classCode += " " + keyWordValuePair.second.second;
|
||||
}
|
||||
}
|
||||
classCode += " \"\"\"\n";
|
||||
}
|
||||
classCode +=
|
||||
QString( " __custom_init__ = None #: Assign a custom init routine to be run at __init__\n\n" );
|
||||
|
||||
classCode += QString( " def __init__(self, pb2_object=None, channel=None):\n" );
|
||||
classCode += QString( " self.class_keyword = \"%1\"\n" ).arg( scriptClassName );
|
||||
if ( !scriptSuperClassNames.empty() )
|
||||
{
|
||||
// Own attributes. This initializes a lot of attributes to None.
|
||||
// This means it has to be done before we set any values.
|
||||
for ( auto keyWordValuePair : classAttributesGenerated[classKeyword] )
|
||||
{
|
||||
classCode += keyWordValuePair.second.first;
|
||||
}
|
||||
// Parent constructor
|
||||
classCode +=
|
||||
QString( " %1.__init__(self, pb2_object, channel)\n" ).arg( scriptSuperClassNames.back() );
|
||||
}
|
||||
|
||||
classCode += QString( " if %1.__custom_init__ is not None:\n" ).arg( scriptClassName );
|
||||
classCode += QString( " %1.__custom_init__(self, pb2_object=pb2_object, channel=channel)\n" )
|
||||
.arg( scriptClassName );
|
||||
|
||||
for ( auto keyWordValuePair : classMethodsGenerated[classKeyword] )
|
||||
{
|
||||
classCode += "\n";
|
||||
classCode += keyWordValuePair.second;
|
||||
classCode += "\n";
|
||||
}
|
||||
|
||||
out << classCode << "\n";
|
||||
classesWritten.insert( scriptClassName );
|
||||
}
|
||||
scriptSuperClassNames.push_back( scriptClassName );
|
||||
}
|
||||
}
|
||||
out << "def class_dict():\n";
|
||||
out << " classes = {}\n";
|
||||
for ( QString classKeyword : classesWritten )
|
||||
{
|
||||
out << QString( " classes['%1'] = %1\n" ).arg( classKeyword );
|
||||
}
|
||||
out << " return classes\n\n";
|
||||
|
||||
out << "def class_from_keyword(class_keyword):\n";
|
||||
out << " all_classes = class_dict()\n";
|
||||
out << " if class_keyword in all_classes.keys():\n";
|
||||
out << " return all_classes[class_keyword]\n";
|
||||
out << " return None\n";
|
||||
out << generator->generate( caf::PdmDefaultObjectFactory::instance() );
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ protected:
|
||||
friend class RiaRegressionTestRunner;
|
||||
void resetProject();
|
||||
|
||||
void generatePythonClasses( const QString& outputPath );
|
||||
bool generateCode( const QString& outputPath, QString* errMsg );
|
||||
|
||||
protected:
|
||||
cvf::ref<cvf::Font> m_defaultSceneFont;
|
||||
|
||||
@@ -137,7 +137,12 @@ RiaApplication::ApplicationStatus RiaConsoleApplication::handleArguments( cvf::P
|
||||
CVF_ASSERT( o.valueCount() == 1 );
|
||||
QString outputFile = cvfqt::Utils::toQString( o.value( 0 ) );
|
||||
|
||||
RiaApplication::generatePythonClasses( outputFile );
|
||||
QString errMsg;
|
||||
if ( !RiaApplication::generateCode( outputFile, &errMsg ) )
|
||||
{
|
||||
RiaLogging::error( QString( "Error: %1" ).arg( errMsg ) );
|
||||
return RiaApplication::EXIT_WITH_ERROR;
|
||||
}
|
||||
|
||||
return RiaApplication::EXIT_COMPLETED;
|
||||
}
|
||||
|
||||
@@ -457,7 +457,12 @@ RiaApplication::ApplicationStatus RiaGuiApplication::handleArguments( cvf::Progr
|
||||
CVF_ASSERT( o.valueCount() == 1 );
|
||||
QString outputFile = cvfqt::Utils::toQString( o.value( 0 ) );
|
||||
|
||||
RiaApplication::generatePythonClasses( outputFile );
|
||||
QString errMsg;
|
||||
if ( !RiaApplication::generateCode( outputFile, &errMsg ) )
|
||||
{
|
||||
RiaLogging::error( QString( "Error: %1" ).arg( errMsg ) );
|
||||
return RiaApplication::EXIT_WITH_ERROR;
|
||||
}
|
||||
|
||||
return RiaApplication::EXIT_COMPLETED;
|
||||
}
|
||||
|
||||
@@ -55,17 +55,3 @@ QString RiaTextStringTools::trimAndRemoveDoubleSpaces( const QString& s )
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RiaTextStringTools::camelToSnakeCase( const QString& camelString )
|
||||
{
|
||||
static QRegularExpression re1( "(.)([A-Z][a-z]+)" );
|
||||
static QRegularExpression re2( "([a-z0-9])([A-Z])" );
|
||||
|
||||
QString snake_case = camelString;
|
||||
snake_case.replace( re1, "\\1_\\2" );
|
||||
snake_case.replace( re2, "\\1_\\2" );
|
||||
return snake_case.toLower();
|
||||
}
|
||||
|
||||
@@ -27,5 +27,4 @@ namespace RiaTextStringTools
|
||||
{
|
||||
bool compare( const QString& expected, const QString& actual );
|
||||
QString trimAndRemoveDoubleSpaces( const QString& s );
|
||||
QString camelToSnakeCase( const QString& camelString );
|
||||
} // namespace RiaTextStringTools
|
||||
|
||||
Reference in New Issue
Block a user