Fix #113014: Improved sorting to keep node group outputs above inputs

The position validation when manipulating node group items now includes
outputs..inputs order in addition to sockets..panels order. The method
for finding a valid position has been simplified, it's just a single
iteration of insertion sort.

Versioning has been added to ensure files from 4.0 alpha with
potentially unsorted sockets get re-sorted. This uses `std::stable_sort`
so that sockets keep their relative order apart from the input/output
grouping.

Pull Request: https://projects.blender.org/blender/blender/pulls/113060
This commit is contained in:
Lukas Tönne 2023-10-03 12:18:36 +02:00
parent 19ac7c0f87
commit 71732a9600
3 changed files with 79 additions and 27 deletions

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 32
#define BLENDER_FILE_SUBVERSION 33
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -780,40 +780,47 @@ int bNodeTreeInterfacePanel::find_valid_insert_position_for_item(
NODE_INTERFACE_PANEL_ALLOW_SOCKETS_AFTER_PANELS);
const blender::Span<const bNodeTreeInterfaceItem *> items = this->items();
int pos = initial_pos;
if (sockets_above_panels) {
if (item.item_type == NODE_INTERFACE_PANEL) {
/* Find the closest valid position from the end, only panels at or after #position. */
for (int test_pos = items.size() - 1; test_pos >= initial_pos; test_pos--) {
if (test_pos < 0) {
/* Initial position is out of range but valid. */
break;
}
if (items[test_pos]->item_type != NODE_INTERFACE_PANEL) {
/* Found valid position, insert after the last socket item. */
pos = test_pos + 1;
break;
}
/* True if item a should be above item b. */
auto item_compare = [sockets_above_panels](const bNodeTreeInterfaceItem &a,
const bNodeTreeInterfaceItem &b) -> bool {
if (a.item_type != b.item_type) {
/* Keep sockets above panels. */
if (sockets_above_panels) {
return a.item_type == NODE_INTERFACE_SOCKET;
}
}
else {
/* Find the closest valid position from the start, no panels at or after #position. */
for (int test_pos = 0; test_pos <= initial_pos; test_pos++) {
if (test_pos >= items.size()) {
/* Initial position is out of range but valid. */
break;
}
if (items[test_pos]->item_type == NODE_INTERFACE_PANEL) {
/* Found valid position, inserting moves the first panel. */
pos = test_pos;
break;
/* Keep outputs above inputs. */
if (a.item_type == NODE_INTERFACE_SOCKET) {
const bNodeTreeInterfaceSocket &sa = reinterpret_cast<const bNodeTreeInterfaceSocket &>(a);
const bNodeTreeInterfaceSocket &sb = reinterpret_cast<const bNodeTreeInterfaceSocket &>(b);
const bool is_output_a = sa.flag & NODE_INTERFACE_SOCKET_OUTPUT;
const bool is_output_b = sb.flag & NODE_INTERFACE_SOCKET_OUTPUT;
if (is_output_a != is_output_b) {
return is_output_a;
}
}
}
return false;
};
if (items.is_empty()) {
return initial_pos;
}
return pos;
/* Insertion sort for a single item.
* items.size() is a valid position for appending. */
int test_pos = clamp_i(initial_pos, 0, items.size());
/* Move upward until valid position found. */
while (test_pos > 0 && item_compare(item, *items[test_pos - 1])) {
--test_pos;
}
/* Move downward until valid position found.
* Result can be out of range, this is valid, items get appended. */
while (test_pos < items.size() && item_compare(*items[test_pos], item)) {
++test_pos;
}
return test_pos;
}
void bNodeTreeInterfacePanel::add_item(bNodeTreeInterfaceItem &item)

View File

@ -8,6 +8,7 @@
#define DNA_DEPRECATED_ALLOW
#include <algorithm>
#include <cmath>
#include "CLG_log.h"
@ -984,6 +985,42 @@ static void version_node_group_split_socket(bNodeTreeInterface &tree_interface,
csocket->flag &= ~NODE_INTERFACE_SOCKET_OUTPUT;
}
static void versioning_node_group_sort_sockets_recursive(bNodeTreeInterfacePanel &panel)
{
/* True if item a should be above item b. */
auto item_compare = [](const bNodeTreeInterfaceItem *a,
const bNodeTreeInterfaceItem *b) -> bool {
if (a->item_type != b->item_type) {
/* Keep sockets above panels. */
return a->item_type == NODE_INTERFACE_SOCKET;
}
else {
/* Keep outputs above inputs. */
if (a->item_type == NODE_INTERFACE_SOCKET) {
const bNodeTreeInterfaceSocket *sa = reinterpret_cast<const bNodeTreeInterfaceSocket *>(a);
const bNodeTreeInterfaceSocket *sb = reinterpret_cast<const bNodeTreeInterfaceSocket *>(b);
const bool is_output_a = sa->flag & NODE_INTERFACE_SOCKET_OUTPUT;
const bool is_output_b = sb->flag & NODE_INTERFACE_SOCKET_OUTPUT;
if (is_output_a != is_output_b) {
return is_output_a;
}
}
}
return false;
};
/* Sort panel content. */
std::stable_sort(panel.items().begin(), panel.items().end(), item_compare);
/* Sort any child panels too. */
for (bNodeTreeInterfaceItem *item : panel.items()) {
if (item->item_type == NODE_INTERFACE_PANEL) {
versioning_node_group_sort_sockets_recursive(
*reinterpret_cast<bNodeTreeInterfacePanel *>(item));
}
}
}
static void enable_geometry_nodes_is_modifier(Main &bmain)
{
/* Any node group with a first socket geometry output can potentially be a modifier. Previously
@ -1640,6 +1677,14 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 33)) {
/* Fix node group socket order by sorting outputs and inputs. */
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
versioning_node_group_sort_sockets_recursive(ntree->tree_interface.root_panel);
}
}
/**
* Versioning code until next subversion bump goes here.
*