You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement value multiplexer framework for field synchronization
Add variable mapping system for path management
Create UI commands for multiplexer creation
Integrate field change notification system
Diagram Walkthrough
flowchart LR
A["Field Change"] --> B["PdmUiCommandSystemProxy"]
B --> C["ValueMultiplexerCollection"]
C --> D["Connected Fields"]
D --> E["UI Update"]
F["RimProject"] --> G["VariableMapper"]
G --> H["Path Variables"]
The change to suppress command-interface updates when connected fields exist alters undo/redo and notification flow. Verify that connected-field updates still trigger expected UI refreshes and history entries where required, and that mixed scenarios (both selection-related fields and connected fields) behave correctly.
{
if ( otherField != editorField )
{
fieldsToUpdate.push_back( otherField );
}
}
}
std::vector<caf::PdmFieldHandle*> connectedFields;
if ( m_fieldChangedMultiplexer )
{
connectedFields = m_fieldChangedMultiplexer->connectedFields( editorField );
for ( auto connectedField : connectedFields )
{
if ( std::find( fieldsToUpdate.begin(), fieldsToUpdate.end(), connectedField ) == fieldsToUpdate.end() )
{
fieldsToUpdate.push_back( connectedField );
}
}
}
if ( m_commandInterface && !fieldsToUpdate.empty() && connectedFields.empty() )
{
Options building scans all descendants and all fields on every options request. In large projects this can be expensive. Consider caching or filtering (e.g., by class/type) and deferring heavy scans.
QList<caf::PdmOptionItemInfo> RimValueMultiplexer::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &m_source || fieldNeedingOptions == &m_destination )
{
auto root = RimProject::current();
if ( root )
{
auto allObjects = root->descendantsOfType<caf::PdmObjectHandle>();
for ( auto objHandle : allObjects )
{
auto obj = dynamic_cast<caf::PdmObject*>( objHandle );
if ( obj )
{
QString text = obj->uiName() + " - " + obj->classKeyword();
options.push_back( caf::PdmOptionItemInfo( text, obj ) );
}
}
}
}
elseif ( fieldNeedingOptions == &m_sourceFieldName )
{
if ( m_source() )
{
auto allFields = m_source()->fields();
for ( auto field : allFields )
{
if ( auto valueField = dynamic_cast<caf::PdmValueField*>( field ) )
{
options.push_back( caf::PdmOptionItemInfo( valueField->keyword(), valueField->keyword() ) );
}
}
}
}
elseif ( fieldNeedingOptions == &m_destinationFieldName )
{
if ( m_destination() )
{
auto allFields = m_destination()->fields();
for ( auto field : allFields )
{
if ( auto valueField = dynamic_cast<caf::PdmValueField*>( field ) )
{
options.push_back( caf::PdmOptionItemInfo( valueField->keyword(), valueField->keyword() ) );
}
}
}
}
return options;
}
Path variable transfer/distribution performs string token parsing and replacements; edge cases with malformed tokens or unexpected formats may lead to incorrect names or resets. Add safeguards and logging when variables are not found or tokens are partial.
voidRimProject::transferPathsToGlobalPathList()
{
RiaVariableMapper variableMapper( m_globalPathList() );
for ( caf::FilePath* filePath : allFilePaths() )
{
QString path = filePath->path();
if ( !path.isEmpty() )
{
QString pathId = variableMapper.addPathAndGetId( path );
filePath->setPath( pathId );
}
}
for ( auto summaryCase : allSummaryCases() )
{
if ( summaryCase->displayNameType() == RimCaseDisplayNameTools::DisplayName::CUSTOM )
{
// At this point, after the replace of variables into caf::FilePath objects, the variable name is// stored in the summary case object. Read out the variable name and append "_name" for custom// summary variables.
QString variableName = summaryCase->summaryHeaderFilename();
variableName = variableName.remove( RiaVariableMapper::variableToken() );
variableName = RiaVariableMapper::variableToken() + variableName + RiaVariableMapper::postfixName() +
RiaVariableMapper::variableToken();
QString variableValue = summaryCase->displayCaseName();
variableMapper.addVariable( variableName, variableValue );
summaryCase->setCustomCaseName( variableName );
}
}
for ( auto gridCase : allGridCases() )
{
if ( gridCase->displayNameType() == RimCaseDisplayNameTools::DisplayName::CUSTOM )
{
// At this point, after the replace of variables into caf::FilePath objects, the variable name is// stored in the summary case object. Read out the variable name and append "_name" for custom// summary variables.
QString variableName = gridCase->gridFileName();
variableName = variableName.remove( RiaVariableMapper::variableToken() );
variableName = RiaVariableMapper::variableToken() + variableName + RiaVariableMapper::postfixName() +
RiaVariableMapper::variableToken();
QString variableValue = gridCase->caseUserDescription();
variableMapper.addVariable( variableName, variableValue );
gridCase->setCustomCaseName( variableName );
}
}
variableMapper.replaceVariablesInValues();
m_globalPathList = variableMapper.variableTableAsText();
}
//--------------------------------------------------------------------------------------------------/////--------------------------------------------------------------------------------------------------
QString RimProject::updatedFilePathFromPathId( QString filePath, RiaVariableMapper* pathListMapper /*=nullptr*/ ) const
{
std::unique_ptr<RiaVariableMapper> internalMapper;
if ( pathListMapper == nullptr )
{
internalMapper = std::make_unique<RiaVariableMapper>( m_globalPathList );
pathListMapper = internalMapper.get();
}
QString returnValue = filePath;
QString pathIdCandidate = filePath.trimmed();
QStringList pathIdComponents = pathIdCandidate.split( RiaVariableMapper::variableToken() );
if ( pathIdComponents.size() == 3 && pathIdComponents[0].size() == 0 && pathIdComponents[1].size() > 0 && pathIdComponents[2].size() == 0 )
{
bool isFound = false;
QString path = pathListMapper->valueForVariable( pathIdCandidate, &isFound );
if ( isFound )
{
returnValue = path;
}
}
return returnValue;
}
//--------------------------------------------------------------------------------------------------/////--------------------------------------------------------------------------------------------------voidRimProject::distributePathsFromGlobalPathList()
{
RiaVariableMapper pathListMapper( m_globalPathList() );
for ( caf::FilePath* filePath : allFilePaths() )
{
filePath->setPath( updatedFilePathFromPathId( filePath->path(), &pathListMapper ) );
}
for ( auto summaryCase : allSummaryCases() )
{
if ( summaryCase->displayNameType() == RimCaseDisplayNameTools::DisplayName::CUSTOM )
{
auto variableName = summaryCase->displayCaseName();
bool isFound = false;
QString variableValue = pathListMapper.valueForVariable( variableName, &isFound );
if ( isFound )
{
summaryCase->setCustomCaseName( variableValue );
}
elseif ( variableName.contains( RiaVariableMapper::postfixName() + RiaVariableMapper::variableToken() ) )
{
// The variable name is not found in the variable list, but the name indicates a variable. Reset// to full case name.
summaryCase->setDisplayNameOption( RimCaseDisplayNameTools::DisplayName::FULL_CASE_NAME );
}
}
}
for ( auto gridCase : allGridCases() )
{
if ( gridCase->displayNameType() == RimCaseDisplayNameTools::DisplayName::CUSTOM )
{
auto variableName = gridCase->caseUserDescription();
bool isFound = false;
QString variableValue = pathListMapper.valueForVariable( variableName, &isFound );
if ( isFound )
{
gridCase->setCustomCaseName( variableValue );
}
elseif ( variableName.contains( RiaVariableMapper::postfixName() + RiaVariableMapper::variableToken() ) )
{
// The variable name is not found in the variable list, but the name indicates a variable. Reset// to full case name.
gridCase->setDisplayNameType( RimCaseDisplayNameTools::DisplayName::FULL_CASE_NAME );
}
}
}
}
Connected fields are added to fieldsToUpdate but then the command path is skipped when any are present, breaking undo/redo for updates that include multiplexed fields. Allow the command to execute regardless, and ensure non-editable connected fields are filtered out before issuing the command. This preserves undo/redo while supporting multiplexing.
std::vector<caf::PdmFieldHandle*> connectedFields;
if ( m_fieldChangedMultiplexer )
{
connectedFields = m_fieldChangedMultiplexer->connectedFields( editorField );
for ( auto connectedField : connectedFields )
{
- if ( std::find( fieldsToUpdate.begin(), fieldsToUpdate.end(), connectedField ) == fieldsToUpdate.end() )+ if ( connectedField && std::find( fieldsToUpdate.begin(), fieldsToUpdate.end(), connectedField ) == fieldsToUpdate.end() )
{
fieldsToUpdate.push_back( connectedField );
}
}
}
-if ( m_commandInterface && !fieldsToUpdate.empty() && connectedFields.empty() )+// Filter out non-writable fields to avoid issuing commands for read-only targets+fieldsToUpdate.erase(+ std::remove_if(fieldsToUpdate.begin(), fieldsToUpdate.end(),+ [&](caf::PdmFieldHandle* f){ return !m_commandInterface || !m_commandInterface->isFieldWritable(f); }),+ fieldsToUpdate.end());++if ( m_commandInterface && !fieldsToUpdate.empty() )
{
auto firstField = fieldsToUpdate.front();
if ( m_commandInterface->isFieldWritable( firstField ) )
{
...
[To ensure code accuracy, apply this suggestion manually]
Suggestion importance[1-10]: 9
__
Why: The suggestion correctly identifies that the condition && connectedFields.empty() breaks the undo/redo functionality for multiplexed fields and proposes a valid fix, which is a critical correction.
High
Prevent cycles and ensure safe conversion
This can recurse or thrash if destination is also multiplexed back to the source, and it lacks type checking. Add a guard to prevent cycles and only propagate when the value actually changes, converting types safely before setting. This avoids infinite loops and QVariant type mismatches.
void RimValueMultiplexerCollection::notifyFieldChanged( caf::PdmObject* source, const QString& fieldName, QVariant newValue )
{
+ // Simple cycle guard using a static thread_local flag+ static thread_local bool inPropagation = false;+ if ( inPropagation ) return;+ inPropagation = true;+
for ( auto multiplexer : m_valueMultiplexers )
{
if ( multiplexer->source() == source && multiplexer->sourceFieldName() == fieldName )
{
if ( auto destinationField = multiplexer->destinationField() )
{
- auto fh = destinationField->uiCapability();+ QVariant old = destinationField->toQVariant();- auto old = destinationField->toQVariant();+ // Skip if unchanged+ if ( old == newValue )+ continue;- destinationField->setFromQVariant( newValue );+ // Attempt safe conversion to destination type+ QVariant converted = newValue;+ if ( converted.metaType() != old.metaType() )+ {+ converted.convert( old.metaType() );+ }- fh->notifyFieldChanged( old, newValue );+ destinationField->setFromQVariant( converted );++ if ( auto uiCap = destinationField->uiCapability() )+ {+ uiCap->notifyFieldChanged( old, converted );+ }
}
}
}
++ inPropagation = false;
}
Apply / Chat
Suggestion importance[1-10]: 8
__
Why: The suggestion correctly identifies a potential for infinite recursion in notifyFieldChanged and proposes a valid cycle guard, which is a critical improvement for robustness.
Medium
General
Validate selection and field names
Creating a multiplexer with empty field names will silently fail later. Validate that both objects are selected and prompt the user or abort if either field name is empty. At minimum, early-return when objects are null to avoid adding invalid entries.
Why: The suggestion correctly points out that creating a multiplexer with null objects and empty field names is problematic, and the proposed validation improves the robustness of the feature.
Medium
More
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Type
Enhancement
Description
Implement value multiplexer framework for field synchronization
Add variable mapping system for path management
Create UI commands for multiplexer creation
Integrate field change notification system
Diagram Walkthrough
File Walkthrough
11 files
Add command to create value multiplexersRegister multiplexer creation command in context menuAdd multiplexer collection and path mapping functionalityImplement value multiplexer for field synchronizationImplement collection to manage value multiplexersAdd field change multiplexer interface supportHeader for value multiplexer creation commandAdd multiplexer collection and path mapping declarationsHeader for value multiplexer classHeader for multiplexer collection classAdd field change multiplexer interface declaration1 files
Add includes for multiplexer integration4 files
Add variable server for testing frameworkImplement test variable server and client classesAdd variable server member declarationHeader for test variable server classes3 files
Add multiplexer command to build systemAdd multiplexer classes to build systemAdd variable server to test build