mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
Workflows: Show ResInsight object icons on graph ports
Ports holding a ResInsight object (instance, case, view, well path, surface) show the project tree icon of that object in front of the type. The rips class is taken from the schema's Python bases, directly or via ObjectModel[Class].
This commit is contained in:
1 parent
a47a871fea
commit
d994e2c95e
7 files changed
+172
-47
No files matched your search
@@ -218,6 +218,7 @@ QJsonArray inputPortsJson( const RimWorkflowDefinition& definit
|
||||
wired = wired || edge.input == port.name;
|
||||
result.append( QJsonObject{ { "name", port.name },
|
||||
{ "type", port.typeName },
|
||||
{ "icon", port.iconResource },
|
||||
{ "description", port.description },
|
||||
{ "required", port.required },
|
||||
{ "configurable", port.configurable },
|
||||
@@ -236,8 +237,11 @@ QJsonArray outputPortsJson( const std::vector<RimWorkflowPort>& ports, const std
|
||||
bool wired = false;
|
||||
for ( const auto& edge : outgoing )
|
||||
wired = wired || edge.output == port.name;
|
||||
result.append(
|
||||
QJsonObject{ { "name", port.name }, { "type", port.typeName }, { "description", port.description }, { "wired", wired } } );
|
||||
result.append( QJsonObject{ { "name", port.name },
|
||||
{ "type", port.typeName },
|
||||
{ "icon", port.iconResource },
|
||||
{ "description", port.description },
|
||||
{ "wired", wired } } );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -288,6 +292,8 @@ QJsonObject graphTask( const RimWorkflowDefinition& definition,
|
||||
{ "output_ports", outputPortsJson( outputPorts, definition.outgoingEdges( node.name ) ) },
|
||||
{ "input_types", RimWorkflowPortCompatibility::portTypes( inputPorts ) },
|
||||
{ "output_types", RimWorkflowPortCompatibility::portTypes( outputPorts ) },
|
||||
{ "input_icons", RimWorkflowPortCompatibility::portIcons( inputPorts ) },
|
||||
{ "output_icons", RimWorkflowPortCompatibility::portIcons( outputPorts ) },
|
||||
{ "is_result", node.name == resultTask },
|
||||
{ "issues", taskIssues } };
|
||||
}
|
||||
|
||||
@@ -120,7 +120,9 @@ QJsonObject taskFromDescribe( const QJsonObject& task )
|
||||
{ "outputs", outputs },
|
||||
{ "config_fields", configFields },
|
||||
{ "input_types", RimWorkflowPortCompatibility::portTypes( RimWorkflowPortCompatibility::inputPorts( task ) ) },
|
||||
{ "output_types", RimWorkflowPortCompatibility::portTypes( RimWorkflowPortCompatibility::outputPorts( task ) ) } };
|
||||
{ "output_types", RimWorkflowPortCompatibility::portTypes( RimWorkflowPortCompatibility::outputPorts( task ) ) },
|
||||
{ "input_icons", RimWorkflowPortCompatibility::portIcons( RimWorkflowPortCompatibility::inputPorts( task ) ) },
|
||||
{ "output_icons", RimWorkflowPortCompatibility::portIcons( RimWorkflowPortCompatibility::outputPorts( task ) ) } };
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QMap>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
|
||||
@@ -155,12 +156,13 @@ std::vector<RimWorkflowPort> RimWorkflowPortCompatibility::inputPorts( const QJs
|
||||
const QJsonObject inputSchema = taskType.value( "input_schema" ).toObject();
|
||||
|
||||
std::vector<RimWorkflowPort> ports;
|
||||
ports.push_back( { .name = "",
|
||||
.schema = inputSchema,
|
||||
.rootSchema = inputSchema,
|
||||
.description = inputSchema.value( "description" ).toString(),
|
||||
.typeName = typeName( inputSchema, inputSchema ),
|
||||
.required = true } );
|
||||
ports.push_back( { .name = "",
|
||||
.schema = inputSchema,
|
||||
.rootSchema = inputSchema,
|
||||
.description = inputSchema.value( "description" ).toString(),
|
||||
.typeName = typeName( inputSchema, inputSchema ),
|
||||
.iconResource = iconResource( inputSchema, inputSchema ),
|
||||
.required = true } );
|
||||
|
||||
const QStringList required = RimWorkflowSchemaTools::requiredFields( inputSchema );
|
||||
const QJsonObject properties = inputSchema.value( "properties" ).toObject();
|
||||
@@ -173,6 +175,7 @@ std::vector<RimWorkflowPort> RimWorkflowPortCompatibility::inputPorts( const QJs
|
||||
.rootSchema = inputSchema,
|
||||
.description = property.value( "description" ).toString(),
|
||||
.typeName = typeName( property, inputSchema ),
|
||||
.iconResource = iconResource( property, inputSchema ),
|
||||
.required = required.contains( name ),
|
||||
.configurable = RimWorkflowSchemaTools::isConfigurable( property, inputSchema ) } );
|
||||
}
|
||||
@@ -187,22 +190,24 @@ std::vector<RimWorkflowPort> RimWorkflowPortCompatibility::outputPorts( const QJ
|
||||
const QJsonObject outputSchema = taskType.value( "output_schema" ).toObject();
|
||||
|
||||
std::vector<RimWorkflowPort> ports;
|
||||
ports.push_back( { .name = "",
|
||||
.schema = outputSchema,
|
||||
.rootSchema = outputSchema,
|
||||
.description = outputSchema.value( "description" ).toString(),
|
||||
.typeName = typeName( outputSchema, outputSchema ) } );
|
||||
ports.push_back( { .name = "",
|
||||
.schema = outputSchema,
|
||||
.rootSchema = outputSchema,
|
||||
.description = outputSchema.value( "description" ).toString(),
|
||||
.typeName = typeName( outputSchema, outputSchema ),
|
||||
.iconResource = iconResource( outputSchema, outputSchema ) } );
|
||||
|
||||
const QJsonObject properties = outputSchema.value( "properties" ).toObject();
|
||||
for ( const QString& name : RimWorkflowSchemaTools::propertyNames( outputSchema ) )
|
||||
{
|
||||
const QJsonObject property = properties.value( name ).toObject();
|
||||
if ( isHiddenObjectValue( name, property, outputSchema ) ) continue;
|
||||
ports.push_back( { .name = name,
|
||||
.schema = property,
|
||||
.rootSchema = outputSchema,
|
||||
.description = property.value( "description" ).toString(),
|
||||
.typeName = typeName( property, outputSchema ) } );
|
||||
ports.push_back( { .name = name,
|
||||
.schema = property,
|
||||
.rootSchema = outputSchema,
|
||||
.description = property.value( "description" ).toString(),
|
||||
.typeName = typeName( property, outputSchema ),
|
||||
.iconResource = iconResource( property, outputSchema ) } );
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
@@ -346,6 +351,63 @@ QString RimWorkflowPortCompatibility::typeName( const QJsonObject& schema, const
|
||||
return pythonNames.value( type, type );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Icon of the ResInsight object a schema holds, or an empty string. The rips class is found in the
|
||||
/// Python class bases: directly for opaque rips objects, or as `ObjectModel[Class]` for wrappers.
|
||||
/// The icons are the ones the matching ResInsight project classes use.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimWorkflowPortCompatibility::iconResource( const QJsonObject& schema, const QJsonObject& rootSchema )
|
||||
{
|
||||
const QJsonObject resolved = RimWorkflowSchemaTools::resolveReferences( schema, rootSchema );
|
||||
for ( const char* unionKey : { "anyOf", "oneOf" } )
|
||||
{
|
||||
for ( const QJsonValue& option : resolved.value( unionKey ).toArray() )
|
||||
{
|
||||
const QString icon = iconResource( option.toObject(), rootSchema );
|
||||
if ( !icon.isEmpty() ) return icon;
|
||||
}
|
||||
}
|
||||
if ( resolved.value( "type" ).toString() == "array" ) return iconResource( resolved.value( "items" ).toObject(), rootSchema );
|
||||
|
||||
static const QMap<QString, QString> icons = { { "Instance", ":/AppLogo48x48.png" },
|
||||
{ "Case", ":/Case48x48.png" },
|
||||
{ "Reservoir", ":/Case48x48.png" },
|
||||
{ "EclipseCase", ":/Case48x48.png" },
|
||||
{ "View", ":/3DView16x16.png" },
|
||||
{ "EclipseView", ":/3DView16x16.png" },
|
||||
{ "WellPath", ":/Well.svg" },
|
||||
{ "Surface", ":/ReservoirSurface16x16.png" } };
|
||||
|
||||
QJsonArray bases = resolved.value( "x-ri-python-bases" ).toArray();
|
||||
if ( bases.isEmpty() && resolved.contains( "x-ri-python-type" ) ) bases.append( resolved.value( "x-ri-python-type" ) );
|
||||
|
||||
static const QRegularExpression objectModel( R"(ObjectModel\[(\w+)\]$)" );
|
||||
for ( const QJsonValue& value : bases )
|
||||
{
|
||||
const QString base = value.toString();
|
||||
QString className;
|
||||
if ( const auto match = objectModel.match( base ); match.hasMatch() )
|
||||
className = match.captured( 1 );
|
||||
else if ( base.startsWith( "rips." ) )
|
||||
className = base.section( '.', -1 );
|
||||
if ( icons.contains( className ) ) return icons.value( className );
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Port name -> icon resource, for the ports that hold a ResInsight object
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QJsonObject RimWorkflowPortCompatibility::portIcons( const std::vector<RimWorkflowPort>& ports )
|
||||
{
|
||||
QJsonObject icons;
|
||||
for ( const auto& port : ports )
|
||||
{
|
||||
if ( !port.iconResource.isEmpty() ) icons[port.name] = port.iconResource;
|
||||
}
|
||||
return icons;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Port name -> type name, with an empty name for the whole model. Used to label ports in the graph.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -37,6 +37,7 @@ struct RimWorkflowPort
|
||||
QJsonObject rootSchema{};
|
||||
QString description{};
|
||||
QString typeName{};
|
||||
QString iconResource{};
|
||||
bool required = true;
|
||||
bool configurable = false;
|
||||
|
||||
@@ -58,7 +59,9 @@ bool isCompatible( const QJsonObject& produced, const QJsonObject& producedRoot,
|
||||
bool isCompatible( const RimWorkflowPort& produced, const RimWorkflowPort& expected );
|
||||
bool isAny( const QJsonObject& schema, const QJsonObject& rootSchema );
|
||||
QString typeName( const QJsonObject& schema, const QJsonObject& rootSchema );
|
||||
QString iconResource( const QJsonObject& schema, const QJsonObject& rootSchema );
|
||||
QJsonObject portTypes( const std::vector<RimWorkflowPort>& ports );
|
||||
QJsonObject portIcons( const std::vector<RimWorkflowPort>& ports );
|
||||
|
||||
QStringList coveredByWholeOutput( const QJsonObject& upstreamTaskType, const QJsonObject& downstreamTaskType );
|
||||
bool wouldCreateCycle( const RimWorkflowDefinition& definition, const QString& from, const QString& to );
|
||||
|
||||
@@ -163,6 +163,25 @@ TEST( RimWorkflowDefinition, graphFromDefinition )
|
||||
EXPECT_EQ( "RipsInstance", exportTask.value( "input_types" ).toObject().value( "resinsight" ).toString() );
|
||||
EXPECT_EQ( "str", exportTask.value( "output_types" ).toObject().value( "export_file" ).toString() );
|
||||
|
||||
// Icons of ResInsight objects, found through `ObjectModel[Class]` and rips bases
|
||||
const QJsonObject exportIcons = exportTask.value( "input_icons" ).toObject();
|
||||
EXPECT_EQ( ":/AppLogo48x48.png", exportIcons.value( "resinsight" ).toString() );
|
||||
EXPECT_FALSE( exportTask.value( "output_icons" ).toObject().contains( "export_file" ) );
|
||||
EXPECT_EQ( ":/Case48x48.png", taskByName( graph, "select_eclipse_case" ).value( "output_icons" ).toObject().value( "" ).toString() );
|
||||
EXPECT_EQ( ":/Well.svg", taskByName( graph, "select_well_path_1" ).value( "output_icons" ).toObject().value( "" ).toString() );
|
||||
EXPECT_EQ( ":/Case48x48.png",
|
||||
RimWorkflowPortCompatibility::iconResource( QJsonObject{ { "x-ri-python-type", "rips.generated.generated_classes.Reservoir" } },
|
||||
{} ) );
|
||||
EXPECT_EQ( ":/3DView16x16.png",
|
||||
RimWorkflowPortCompatibility::iconResource( QJsonObject{ { "anyOf",
|
||||
QJsonArray{ QJsonObject{ { "type", "null" } },
|
||||
QJsonObject{ { "x-ri-python-bases",
|
||||
QJsonArray{ "pkg.GridView",
|
||||
"taskmaestro.object_model."
|
||||
"ObjectModel["
|
||||
"View]" } } } } } },
|
||||
{} ) );
|
||||
|
||||
// The connect task's opaque `value` is not a port of its own
|
||||
const QJsonObject connect = taskByName( graph, "connect_to_resinsight" );
|
||||
EXPECT_TRUE( connect.value( "outputs" ).toArray().isEmpty() );
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
#include <QFontMetrics>
|
||||
#include <QGraphicsEllipseItem>
|
||||
#include <QGraphicsPathItem>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QGraphicsPolygonItem>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsTextItem>
|
||||
#include <QIcon>
|
||||
#include <QJsonValue>
|
||||
#include <QKeyEvent>
|
||||
#include <QMimeData>
|
||||
@@ -54,6 +56,7 @@ constexpr qreal configGap = 110.0;
|
||||
constexpr qreal rowGap = 55.0;
|
||||
constexpr qreal portRadius = 5.0;
|
||||
constexpr qreal portHitDist = 14.0;
|
||||
constexpr int iconSize = 12;
|
||||
|
||||
const QColor taskBorder( 60, 85, 115 );
|
||||
const QColor taskBackground( 226, 238, 250 );
|
||||
@@ -123,6 +126,7 @@ struct PortInfo
|
||||
QString label;
|
||||
QString toolTip;
|
||||
QString typeName{};
|
||||
QString iconResource{};
|
||||
Style style = Style::Normal;
|
||||
};
|
||||
|
||||
@@ -373,14 +377,15 @@ void GraphNode::addPorts( const std::vector<PortInfo>& ports, bool output )
|
||||
const QString toolTip = info.toolTip.isEmpty() ? info.label : info.toolTip;
|
||||
const qreal textWidth = m_isConfig ? m_width - 24 : m_width / 2 - 18;
|
||||
const bool showType = !info.typeName.isEmpty();
|
||||
auto addText = [&]( const QString& content, const QFont& font, const QColor& color, qreal top )
|
||||
auto addText = [&]( const QString& content, const QFont& font, const QColor& color, qreal top, qreal indent = 0.0 )
|
||||
{
|
||||
auto* text = new QGraphicsTextItem( QFontMetrics( font ).elidedText( content, Qt::ElideRight, textWidth ), this );
|
||||
auto* text = new QGraphicsTextItem( QFontMetrics( font ).elidedText( content, Qt::ElideRight, textWidth - indent ), this );
|
||||
text->setFont( font );
|
||||
text->setToolTip( toolTip );
|
||||
text->setDefaultTextColor( color );
|
||||
text->setPos( output ? m_width - text->boundingRect().width() - 12 : 10, top );
|
||||
text->setPos( output ? m_width - text->boundingRect().width() - 12 : 10 + indent, top );
|
||||
text->setAcceptedMouseButtons( Qt::NoButton );
|
||||
return text;
|
||||
};
|
||||
|
||||
QFont labelFont;
|
||||
@@ -394,7 +399,21 @@ void GraphNode::addPorts( const std::vector<PortInfo>& ports, bool output )
|
||||
QFont typeFont;
|
||||
typeFont.setPointSize( 7 );
|
||||
typeFont.setItalic( true );
|
||||
addText( info.typeName, typeFont, QColor( 110, 120, 135 ), y - 3 );
|
||||
const QIcon icon( info.iconResource );
|
||||
const bool showIcon = !info.iconResource.isEmpty() && !icon.isNull();
|
||||
auto* typeText = addText( info.typeName, typeFont, QColor( 110, 120, 135 ), y - 3, showIcon ? iconSize + 2 : 0.0 );
|
||||
|
||||
// The icon of the ResInsight object goes in front of the type
|
||||
if ( showIcon )
|
||||
{
|
||||
const QRectF textRect = typeText->mapRectToParent( typeText->boundingRect() );
|
||||
const qreal iconX = output ? textRect.left() - iconSize - 1 : 10 + 3;
|
||||
auto* pixmap = new QGraphicsPixmapItem( icon.pixmap( iconSize, iconSize ), this );
|
||||
pixmap->setPos( iconX, textRect.center().y() - iconSize / 2.0 );
|
||||
pixmap->setTransformationMode( Qt::SmoothTransformation );
|
||||
pixmap->setToolTip( toolTip );
|
||||
pixmap->setAcceptedMouseButtons( Qt::NoButton );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -705,9 +724,10 @@ std::pair<std::vector<PortInfo>, std::vector<PortInfo>> editModePorts( const QJs
|
||||
}
|
||||
|
||||
PortInfo info;
|
||||
info.key = keyForField( field );
|
||||
info.label = field.isEmpty() ? "input" : field;
|
||||
info.typeName = port.value( "type" ).toString();
|
||||
info.key = keyForField( field );
|
||||
info.label = field.isEmpty() ? "input" : field;
|
||||
info.typeName = port.value( "type" ).toString();
|
||||
info.iconResource = port.value( "icon" ).toString();
|
||||
QStringList toolTip{ QString( "%1: %2" ).arg( info.label, port.value( "type" ).toString( "any" ) ) };
|
||||
if ( !port.value( "description" ).toString().isEmpty() ) toolTip << port.value( "description" ).toString();
|
||||
toolTip << ( required ? "Required" : "Optional" );
|
||||
@@ -723,10 +743,11 @@ std::pair<std::vector<PortInfo>, std::vector<PortInfo>> editModePorts( const QJs
|
||||
if ( edge.value( "to" ).toString() == name && edge.value( "input" ).toString().isEmpty() && !hasWholeInput )
|
||||
{
|
||||
inputs.insert( inputs.begin(),
|
||||
PortInfo{ .key = "model",
|
||||
.label = "input",
|
||||
.toolTip = {},
|
||||
.typeName = task.value( "input_types" ).toObject().value( "" ).toString() } );
|
||||
PortInfo{ .key = "model",
|
||||
.label = "input",
|
||||
.toolTip = {},
|
||||
.typeName = task.value( "input_types" ).toObject().value( "" ).toString(),
|
||||
.iconResource = task.value( "input_icons" ).toObject().value( "" ).toString() } );
|
||||
hasWholeInput = true;
|
||||
}
|
||||
}
|
||||
@@ -737,36 +758,39 @@ std::pair<std::vector<PortInfo>, std::vector<PortInfo>> editModePorts( const QJs
|
||||
const QJsonObject port = value.toObject();
|
||||
const QString field = port.value( "name" ).toString();
|
||||
PortInfo info;
|
||||
info.key = keyForField( field );
|
||||
info.label = field.isEmpty() ? "output" : field;
|
||||
info.typeName = port.value( "type" ).toString();
|
||||
info.key = keyForField( field );
|
||||
info.label = field.isEmpty() ? "output" : field;
|
||||
info.typeName = port.value( "type" ).toString();
|
||||
info.iconResource = port.value( "icon" ).toString();
|
||||
QStringList toolTip{ QString( "%1: %2" ).arg( info.label, port.value( "type" ).toString( "any" ) ) };
|
||||
if ( !port.value( "description" ).toString().isEmpty() ) toolTip << port.value( "description" ).toString();
|
||||
info.toolTip = toolTip.join( "\n" );
|
||||
outputs.push_back( info );
|
||||
}
|
||||
if ( outputs.empty() )
|
||||
outputs.push_back( PortInfo{ .key = "model",
|
||||
.label = "output",
|
||||
.toolTip = {},
|
||||
.typeName = task.value( "output_types" ).toObject().value( "" ).toString() } );
|
||||
outputs.push_back( PortInfo{ .key = "model",
|
||||
.label = "output",
|
||||
.toolTip = {},
|
||||
.typeName = task.value( "output_types" ).toObject().value( "" ).toString(),
|
||||
.iconResource = task.value( "output_icons" ).toObject().value( "" ).toString() } );
|
||||
return { inputs, outputs };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<PortInfo> portInfos( const QStringList& keys, bool output, const QJsonObject& types )
|
||||
std::vector<PortInfo> portInfos( const QStringList& keys, bool output, const QJsonObject& types, const QJsonObject& icons )
|
||||
{
|
||||
std::vector<PortInfo> result;
|
||||
for ( const QString& key : keys )
|
||||
{
|
||||
const QString typeName = types.value( fieldForKey( key ) ).toString();
|
||||
const QString label = key == "model" ? ( output ? "output" : "input" ) : fieldForKey( key );
|
||||
result.push_back( PortInfo{ .key = key,
|
||||
.label = label,
|
||||
.toolTip = typeName.isEmpty() ? label : QString( "%1: %2" ).arg( label, typeName ),
|
||||
.typeName = typeName } );
|
||||
result.push_back( PortInfo{ .key = key,
|
||||
.label = label,
|
||||
.toolTip = typeName.isEmpty() ? label : QString( "%1: %2" ).arg( label, typeName ),
|
||||
.typeName = typeName,
|
||||
.iconResource = icons.value( fieldForKey( key ) ).toString() } );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -963,8 +987,8 @@ void RiuWorkflowGraphView::showGraph( const QJsonObject& graph, const QString& e
|
||||
out.removeDuplicates();
|
||||
in.sort();
|
||||
out.sort();
|
||||
inputs = portInfos( in, false, data.value( "input_types" ).toObject() );
|
||||
outputs = portInfos( out, true, data.value( "output_types" ).toObject() );
|
||||
inputs = portInfos( in, false, data.value( "input_types" ).toObject(), data.value( "input_icons" ).toObject() );
|
||||
outputs = portInfos( out, true, data.value( "output_types" ).toObject(), data.value( "output_icons" ).toObject() );
|
||||
}
|
||||
|
||||
auto* node = new GraphNode( name, inputs, outputs );
|
||||
@@ -984,8 +1008,13 @@ void RiuWorkflowGraphView::showGraph( const QJsonObject& graph, const QString& e
|
||||
|
||||
if ( !configFields.value( name ).isEmpty() )
|
||||
{
|
||||
auto* configNode =
|
||||
new GraphNode( name, {}, portInfos( configFields.value( name ), true, data.value( "input_types" ).toObject() ), true );
|
||||
auto* configNode = new GraphNode( name,
|
||||
{},
|
||||
portInfos( configFields.value( name ),
|
||||
true,
|
||||
data.value( "input_types" ).toObject(),
|
||||
data.value( "input_icons" ).toObject() ),
|
||||
true );
|
||||
m_scene->addItem( configNode );
|
||||
m_configNodes.insert( name, configNode );
|
||||
}
|
||||
|
||||
@@ -156,7 +156,11 @@ in a central dock tab:
|
||||
|
||||
Each port shows its name with its type below it in smaller text (from the
|
||||
task schemas, as `input_types`/`output_types` in the graph JSON). The
|
||||
tooltip also shows the description.
|
||||
tooltip also shows the description. Ports that hold a ResInsight object
|
||||
(instance, case, view, well path, surface) show that object's project tree
|
||||
icon in front of the type (`input_icons`/`output_icons`). The rips class is
|
||||
found in the `x-ri-python-bases`, either directly or as `ObjectModel[Class]`
|
||||
(`RimWorkflowPortCompatibility::iconResource`).
|
||||
|
||||
`RiuWorkflowGraphLayout` computes the layout: topological ranks with a Kahn
|
||||
sort and longest-path ranks, then one barycenter pass to order nodes. It
|
||||
|
||||
Reference in new issue
Block a user