UI: Improvements to Confirmation of Apply Modifier #117156
|
@ -164,8 +164,6 @@ def draw_kmi(display_keymaps, kc, km, kmi, layout, level):
|
|||
if km.is_modal:
|
||||
sub.prop(kmi, "propvalue", text="")
|
||||
else:
|
||||
# One day...
|
||||
# sub.prop_search(kmi, "idname", bpy.context.window_manager, "operators_all", text="")
|
||||
sub.prop(kmi, "idname", text="")
|
||||
|
||||
if map_type not in {'TEXTINPUT', 'TIMER'}:
|
||||
|
|
|
@ -1246,6 +1246,14 @@ def km_outliner(params):
|
|||
{"properties": [("extend_range", True), ("deselect_all", not params.legacy)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True, "shift": True},
|
||||
{"properties": [("extend", True), ("extend_range", True), ("deselect_all", not params.legacy)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'},
|
||||
{"properties": [("recurse", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True},
|
||||
{"properties": [("recurse", True), ("extend", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "shift": True},
|
||||
{"properties": [("recurse", True), ("extend_range", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True, "shift": True},
|
||||
{"properties": [("recurse", True), ("extend", True), ("extend_range", True), ("deselect_all", True)]}),
|
||||
("outliner.select_box", {"type": 'B', "value": 'PRESS'}, None),
|
||||
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": [("tweak", True)]}),
|
||||
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
|
||||
|
|
|
@ -486,6 +486,14 @@ def km_outliner(params):
|
|||
{"properties": [("extend", False), ("extend_range", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True, "shift": True},
|
||||
{"properties": [("extend", True), ("extend_range", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'},
|
||||
{"properties": [("recurse", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True},
|
||||
{"properties": [("recurse", True), ("extend", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "shift": True},
|
||||
{"properties": [("recurse", True), ("extend_range", True), ("deselect_all", True)]}),
|
||||
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True, "shift": True},
|
||||
{"properties": [("recurse", True), ("extend", True), ("extend_range", True), ("deselect_all", True)]}),
|
||||
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": [("tweak", True)]}),
|
||||
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
|
||||
{"properties": [("tweak", True), ("mode", 'ADD')]}),
|
||||
|
|
|
@ -237,35 +237,8 @@ class TOPBAR_MT_file_cleanup(Menu):
|
|||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
|
||||
props = layout.operator("outliner.orphans_purge", text="Unused Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = False
|
||||
props = layout.operator("outliner.orphans_purge", text="Recursive Unused Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = True
|
||||
|
||||
layout.separator()
|
||||
props = layout.operator("outliner.orphans_purge", text="Unused Linked Data-Blocks")
|
||||
props.do_local_ids = False
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = False
|
||||
props = layout.operator("outliner.orphans_purge", text="Recursive Unused Linked Data-Blocks")
|
||||
props.do_local_ids = False
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = True
|
||||
|
||||
layout.separator()
|
||||
props = layout.operator("outliner.orphans_purge", text="Unused Local Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = False
|
||||
props.do_recursive = False
|
||||
props = layout.operator("outliner.orphans_purge", text="Recursive Unused Local Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = False
|
||||
props.do_recursive = True
|
||||
layout.operator("outliner.orphans_cleanup")
|
||||
layout.operator("outliner.orphans_manage")
|
||||
|
||||
|
||||
class TOPBAR_MT_file(Menu):
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace blender::ed::curves {
|
|||
|
||||
/**
|
||||
* Merges copy intervals at curve endings to minimize number of copy operations.
|
||||
* For example above intervals [0, 3, 4, 4, 4] became [0, 4, 4].
|
||||
* For example given in function 'extrude_curves' intervals [0, 3, 4, 4, 4] became [0, 4, 4].
|
||||
* Leading to only two copy operations.
|
||||
*/
|
||||
static Span<int> compress_intervals(const Span<IndexRange> curve_interval_ranges,
|
||||
|
@ -263,7 +263,8 @@ static void extrude_curves(Curves &curves_id)
|
|||
Array<IndexRange> curve_interval_ranges(curves_num);
|
||||
|
||||
/* Per curve boolean indicating if first interval in a curve is selected.
|
||||
* Other can be calculated as in a curve two adjacent intervals can have same selection state. */
|
||||
* Other can be calculated as in a curve two adjacent intervals can not have same selection
|
||||
* state. */
|
||||
Array<bool> is_first_selected(curves_num);
|
||||
|
||||
calc_curves_extrusion(extruded_points,
|
||||
|
@ -276,7 +277,11 @@ static void extrude_curves(Curves &curves_id)
|
|||
new_curves.resize(new_offsets.last(), new_curves.curves_num());
|
||||
|
||||
const bke::AttributeAccessor src_attributes = curves.attributes();
|
||||
const GVArraySpan src_selection = *src_attributes.lookup(".selection", bke::AttrDomain::Point);
|
||||
GVArray src_selection_array = *src_attributes.lookup(".selection", bke::AttrDomain::Point);
|
||||
if (!src_selection_array) {
|
||||
src_selection_array = VArray<bool>::ForSingle(true, curves.points_num());
|
||||
}
|
||||
const GVArraySpan src_selection = src_selection_array;
|
||||
const CPPType &src_selection_type = src_selection.type();
|
||||
bke::GSpanAttributeWriter dst_selection = ensure_selection_attribute(
|
||||
new_curves,
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include <cstddef> /* `offsetof()` */
|
||||
#include <cstring>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
@ -466,7 +468,7 @@ void ui_block_bounds_calc(uiBlock *block)
|
|||
|
||||
/* hardcoded exception... but that one is annoying with larger safety */
|
||||
uiBut *bt = static_cast<uiBut *>(block->buttons.first);
|
||||
const int xof = ((bt && STRPREFIX(bt->str, "ERROR")) ? 10 : 40) * UI_SCALE_FAC;
|
||||
const int xof = ((bt && STRPREFIX(bt->str.c_str(), "ERROR")) ? 10 : 40) * UI_SCALE_FAC;
|
||||
|
||||
block->safety.xmin = block->rect.xmin - xof;
|
||||
block->safety.ymin = block->rect.ymin - xof;
|
||||
|
@ -918,22 +920,7 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
|
|||
|
||||
/* move/copy string from the new button to the old */
|
||||
/* needed for alt+mouse wheel over enums */
|
||||
if (but->str != but->strdata) {
|
||||
if (oldbut->str != oldbut->strdata) {
|
||||
std::swap(but->str, oldbut->str);
|
||||
}
|
||||
else {
|
||||
oldbut->str = but->str;
|
||||
but->str = but->strdata;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (oldbut->str != oldbut->strdata) {
|
||||
MEM_freeN(oldbut->str);
|
||||
oldbut->str = oldbut->strdata;
|
||||
}
|
||||
STRNCPY(oldbut->strdata, but->strdata);
|
||||
}
|
||||
std::swap(but->str, oldbut->str);
|
||||
|
||||
if (but->dragpoin) {
|
||||
std::swap(but->dragpoin, oldbut->dragpoin);
|
||||
|
@ -1161,11 +1148,11 @@ static void ui_menu_block_set_keyaccels(uiBlock *block)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (but->str == nullptr || but->str[0] == '\0') {
|
||||
if (but->str.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *str_pt = but->str;
|
||||
const char *str_pt = but->str.c_str();
|
||||
uchar menu_key;
|
||||
do {
|
||||
menu_key = tolower(*str_pt);
|
||||
|
@ -1214,9 +1201,9 @@ static void ui_menu_block_set_keyaccels(uiBlock *block)
|
|||
void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_strip)
|
||||
{
|
||||
if (do_strip && (but->flag & UI_BUT_HAS_SEP_CHAR)) {
|
||||
char *cpoin = strrchr(but->str, UI_SEP_CHAR);
|
||||
if (cpoin) {
|
||||
*cpoin = '\0';
|
||||
const size_t sep_index = but->str.find_first_of(UI_SEP_CHAR);
|
||||
if (sep_index != std::string::npos) {
|
||||
but->str = but->str.substr(0, sep_index);
|
||||
}
|
||||
but->flag &= ~UI_BUT_HAS_SEP_CHAR;
|
||||
}
|
||||
|
@ -1226,16 +1213,7 @@ void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_str
|
|||
return;
|
||||
}
|
||||
|
||||
char *butstr_orig;
|
||||
if (but->str != but->strdata) {
|
||||
butstr_orig = but->str; /* free after using as source buffer */
|
||||
}
|
||||
else {
|
||||
butstr_orig = BLI_strdup(but->str);
|
||||
}
|
||||
SNPRINTF(but->strdata, "%s" UI_SEP_CHAR_S "%s", butstr_orig, shortcut_str);
|
||||
MEM_freeN(butstr_orig);
|
||||
but->str = but->strdata;
|
||||
but->str = fmt::format("{}" UI_SEP_CHAR_S "{}", but->str, shortcut_str);
|
||||
but->flag |= UI_BUT_HAS_SEP_CHAR;
|
||||
ui_but_update(but);
|
||||
}
|
||||
|
@ -3138,27 +3116,7 @@ bool ui_but_string_eval_number(bContext *C, const uiBut *but, const char *str, d
|
|||
static void ui_but_string_set_internal(uiBut *but, const char *str, size_t str_len)
|
||||
{
|
||||
BLI_assert(str_len == strlen(str));
|
||||
BLI_assert(but->str == nullptr);
|
||||
str_len += 1;
|
||||
|
||||
if (str_len > UI_MAX_NAME_STR) {
|
||||
but->str = static_cast<char *>(MEM_mallocN(str_len, "ui_def_but str"));
|
||||
}
|
||||
else {
|
||||
but->str = but->strdata;
|
||||
}
|
||||
memcpy(but->str, str, str_len);
|
||||
}
|
||||
|
||||
static void ui_but_string_free_internal(uiBut *but)
|
||||
{
|
||||
if (but->str) {
|
||||
if (but->str != but->strdata) {
|
||||
MEM_freeN(but->str);
|
||||
}
|
||||
/* must call 'ui_but_string_set_internal' after */
|
||||
but->str = nullptr;
|
||||
}
|
||||
but->str = std::string(str, str_len);
|
||||
}
|
||||
|
||||
bool ui_but_string_set(bContext *C, uiBut *but, const char *str)
|
||||
|
@ -3509,9 +3467,6 @@ static void ui_but_free(const bContext *C, uiBut *but)
|
|||
}
|
||||
}
|
||||
}
|
||||
if (but->str && but->str != but->strdata) {
|
||||
MEM_freeN(but->str);
|
||||
}
|
||||
|
||||
if ((but->type == UI_BTYPE_IMAGE) && but->poin) {
|
||||
IMB_freeImBuf((ImBuf *)but->poin);
|
||||
|
@ -3736,7 +3691,7 @@ void UI_block_set_search_only(uiBlock *block, bool search_only)
|
|||
static void ui_but_build_drawstr_float(uiBut *but, double value)
|
||||
{
|
||||
size_t slen = 0;
|
||||
STR_CONCAT(but->drawstr, slen, but->str);
|
||||
STR_CONCAT(but->drawstr, slen, but->str.c_str());
|
||||
|
||||
PropertySubType subtype = PROP_NONE;
|
||||
if (but->rnaprop) {
|
||||
|
@ -3784,7 +3739,7 @@ static void ui_but_build_drawstr_float(uiBut *but, double value)
|
|||
static void ui_but_build_drawstr_int(uiBut *but, int value)
|
||||
{
|
||||
size_t slen = 0;
|
||||
STR_CONCAT(but->drawstr, slen, but->str);
|
||||
STR_CONCAT(but->drawstr, slen, but->str.c_str());
|
||||
|
||||
PropertySubType subtype = PROP_NONE;
|
||||
if (but->rnaprop) {
|
||||
|
@ -3878,13 +3833,13 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
|
|||
&item))
|
||||
{
|
||||
const size_t slen = strlen(item.name);
|
||||
ui_but_string_free_internal(but);
|
||||
but->str.clear();
|
||||
ui_but_string_set_internal(but, item.name, slen);
|
||||
but->icon = item.icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
STRNCPY(but->drawstr, but->str);
|
||||
STRNCPY(but->drawstr, but->str.c_str());
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -3906,10 +3861,10 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
|
|||
if (ui_but_is_float(but)) {
|
||||
UI_GET_BUT_VALUE_INIT(but, value);
|
||||
const int prec = ui_but_calc_float_precision(but, value);
|
||||
SNPRINTF(but->drawstr, "%s%.*f", but->str, prec, value);
|
||||
SNPRINTF(but->drawstr, "%s%.*f", but->str.c_str(), prec, value);
|
||||
}
|
||||
else {
|
||||
STRNCPY(but->drawstr, but->str);
|
||||
STRNCPY(but->drawstr, but->str.c_str());
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -3920,7 +3875,7 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
|
|||
char str[UI_MAX_DRAW_STR];
|
||||
|
||||
ui_but_string_get(but, str, UI_MAX_DRAW_STR);
|
||||
SNPRINTF(but->drawstr, "%s%s", but->str, str);
|
||||
SNPRINTF(but->drawstr, "%s%s", but->str.c_str(), str);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -3933,7 +3888,7 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
|
|||
UI_GET_BUT_VALUE_INIT(but, value);
|
||||
str = WM_key_event_string(short(value), false);
|
||||
}
|
||||
SNPRINTF(but->drawstr, "%s%s", but->str, str);
|
||||
SNPRINTF(but->drawstr, "%s%s", but->str.c_str(), str);
|
||||
break;
|
||||
}
|
||||
case UI_BTYPE_HOTKEY_EVENT:
|
||||
|
@ -3954,7 +3909,7 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
|
|||
}
|
||||
}
|
||||
else {
|
||||
STRNCPY_UTF8(but->drawstr, but->str);
|
||||
STRNCPY_UTF8(but->drawstr, but->str.c_str());
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -3963,7 +3918,7 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
|
|||
case UI_BTYPE_HSVCIRCLE:
|
||||
break;
|
||||
default:
|
||||
STRNCPY(but->drawstr, but->str);
|
||||
STRNCPY(but->drawstr, but->str.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -4082,7 +4037,6 @@ uiBut *ui_but_change_type(uiBut *but, eButType new_type)
|
|||
|
||||
const uiBut *old_but_ptr = but;
|
||||
/* Button may have pointer to a member within itself, this will have to be updated. */
|
||||
const bool has_str_ptr_to_self = but->str == but->strdata;
|
||||
const bool has_poin_ptr_to_self = but->poin == (char *)but;
|
||||
|
||||
/* Copy construct button with the new type. */
|
||||
|
@ -4090,9 +4044,6 @@ uiBut *ui_but_change_type(uiBut *but, eButType new_type)
|
|||
*but = *old_but_ptr;
|
||||
/* We didn't mean to override this :) */
|
||||
but->type = new_type;
|
||||
if (has_str_ptr_to_self) {
|
||||
but->str = but->strdata;
|
||||
}
|
||||
if (has_poin_ptr_to_self) {
|
||||
but->poin = (char *)but;
|
||||
}
|
||||
|
@ -4201,18 +4152,16 @@ static uiBut *ui_def_but(uiBlock *block,
|
|||
but->pos = -1; /* cursor invisible */
|
||||
|
||||
if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* add a space to name */
|
||||
/* slen remains unchanged from previous assignment, ensure this stays true */
|
||||
if (slen > 0 && slen < UI_MAX_NAME_STR - 2) {
|
||||
if (but->str[slen - 1] != ' ') {
|
||||
but->str[slen] = ' ';
|
||||
but->str[slen + 1] = 0;
|
||||
if (but->str[but->str.size() - 1] != ' ') {
|
||||
but->str += ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (block->flag & UI_BLOCK_RADIAL) {
|
||||
but->drawflag |= UI_BUT_TEXT_LEFT;
|
||||
if (but->str && but->str[0]) {
|
||||
if (!but->str.empty()) {
|
||||
but->drawflag |= UI_BUT_ICON_LEFT;
|
||||
}
|
||||
}
|
||||
|
@ -4296,7 +4245,7 @@ void ui_def_but_icon(uiBut *but, const int icon, const int flag)
|
|||
but->icon = icon;
|
||||
but->flag |= flag;
|
||||
|
||||
if (but->str && but->str[0]) {
|
||||
if (!but->str.empty()) {
|
||||
but->drawflag |= UI_BUT_ICON_LEFT;
|
||||
}
|
||||
}
|
||||
|
@ -6628,28 +6577,24 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
va_start(args, but);
|
||||
while ((si = (uiStringInfo *)va_arg(args, void *))) {
|
||||
uiStringInfoType type = si->type;
|
||||
char *tmp = nullptr;
|
||||
std::optional<std::string> tmp;
|
||||
|
||||
if (type == BUT_GET_TIP_LABEL) {
|
||||
if (but->tip_label_func) {
|
||||
const std::string tooltip_label = but->tip_label_func(but);
|
||||
tmp = BLI_strdupn(tooltip_label.c_str(), tooltip_label.size());
|
||||
tmp = but->tip_label_func(but);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == BUT_GET_LABEL) {
|
||||
if (but->str && but->str[0]) {
|
||||
const char *str_sep;
|
||||
size_t str_len;
|
||||
|
||||
if ((but->flag & UI_BUT_HAS_SEP_CHAR) && (str_sep = strrchr(but->str, UI_SEP_CHAR))) {
|
||||
str_len = (str_sep - but->str);
|
||||
if (!but->str.empty()) {
|
||||
size_t str_len = but->str.size();
|
||||
if (but->flag & UI_BUT_HAS_SEP_CHAR) {
|
||||
const size_t sep_index = but->str.find_first_of(UI_SEP_CHAR);
|
||||
if (sep_index != std::string::npos) {
|
||||
str_len = sep_index;
|
||||
}
|
||||
}
|
||||
else {
|
||||
str_len = strlen(but->str);
|
||||
}
|
||||
|
||||
tmp = BLI_strdupn(but->str, str_len);
|
||||
tmp = but->str.substr(0, str_len);
|
||||
}
|
||||
else {
|
||||
type = BUT_GET_RNA_LABEL; /* Fail-safe solution... */
|
||||
|
@ -6660,7 +6605,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
tmp = but->tip_func(C, but->tip_arg, but->tip);
|
||||
}
|
||||
else if (but->tip && but->tip[0]) {
|
||||
tmp = BLI_strdup(but->tip);
|
||||
tmp = but->tip;
|
||||
}
|
||||
else {
|
||||
type = BUT_GET_RNA_TIP; /* Fail-safe solution... */
|
||||
|
@ -6669,49 +6614,49 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
|
||||
if (type == BUT_GET_RNAPROP_IDENTIFIER) {
|
||||
if (but->rnaprop) {
|
||||
tmp = BLI_strdup(RNA_property_identifier(but->rnaprop));
|
||||
tmp = RNA_property_identifier(but->rnaprop);
|
||||
}
|
||||
}
|
||||
else if (type == BUT_GET_RNASTRUCT_IDENTIFIER) {
|
||||
if (but->rnaprop && but->rnapoin.data) {
|
||||
tmp = BLI_strdup(RNA_struct_identifier(but->rnapoin.type));
|
||||
tmp = RNA_struct_identifier(but->rnapoin.type);
|
||||
}
|
||||
else if (but->optype) {
|
||||
tmp = BLI_strdup(but->optype->idname);
|
||||
tmp = but->optype->idname;
|
||||
}
|
||||
else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) {
|
||||
MenuType *mt = UI_but_menutype_get(but);
|
||||
if (mt) {
|
||||
tmp = BLI_strdup(mt->idname);
|
||||
tmp = mt->idname;
|
||||
}
|
||||
}
|
||||
else if (but->type == UI_BTYPE_POPOVER) {
|
||||
PanelType *pt = UI_but_paneltype_get(but);
|
||||
if (pt) {
|
||||
tmp = BLI_strdup(pt->idname);
|
||||
tmp = pt->idname;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ELEM(type, BUT_GET_RNA_LABEL, BUT_GET_RNA_TIP)) {
|
||||
if (but->rnaprop) {
|
||||
if (type == BUT_GET_RNA_LABEL) {
|
||||
tmp = BLI_strdup(RNA_property_ui_name(but->rnaprop));
|
||||
tmp = RNA_property_ui_name(but->rnaprop);
|
||||
}
|
||||
else {
|
||||
const char *t = RNA_property_ui_description(but->rnaprop);
|
||||
if (t && t[0]) {
|
||||
tmp = BLI_strdup(t);
|
||||
tmp = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (but->optype) {
|
||||
if (type == BUT_GET_RNA_LABEL) {
|
||||
tmp = BLI_strdup(WM_operatortype_name(but->optype, opptr).c_str());
|
||||
tmp = WM_operatortype_name(but->optype, opptr).c_str();
|
||||
}
|
||||
else {
|
||||
const bContextStore *previous_ctx = CTX_store_get(C);
|
||||
CTX_store_set(C, but->context);
|
||||
tmp = BLI_strdup(WM_operatortype_description(C, but->optype, opptr).c_str());
|
||||
tmp = WM_operatortype_description(C, but->optype, opptr).c_str();
|
||||
CTX_store_set(C, previous_ctx);
|
||||
}
|
||||
}
|
||||
|
@ -6720,37 +6665,37 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
MenuType *mt = UI_but_menutype_get(but);
|
||||
if (mt) {
|
||||
if (type == BUT_GET_RNA_LABEL) {
|
||||
tmp = BLI_strdup(CTX_TIP_(mt->translation_context, mt->label));
|
||||
tmp = CTX_TIP_(mt->translation_context, mt->label);
|
||||
}
|
||||
else {
|
||||
/* Not all menus are from Python. */
|
||||
if (mt->rna_ext.srna) {
|
||||
const char *t = RNA_struct_ui_description(mt->rna_ext.srna);
|
||||
if (t && t[0]) {
|
||||
tmp = BLI_strdup(t);
|
||||
tmp = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tmp == nullptr) {
|
||||
if (!tmp) {
|
||||
wmOperatorType *ot = UI_but_operatortype_get_from_enum_menu(but, nullptr);
|
||||
if (ot) {
|
||||
if (type == BUT_GET_RNA_LABEL) {
|
||||
tmp = BLI_strdup(WM_operatortype_name(ot, nullptr).c_str());
|
||||
tmp = WM_operatortype_name(ot, nullptr).c_str();
|
||||
}
|
||||
else {
|
||||
tmp = BLI_strdup(WM_operatortype_description(C, ot, nullptr).c_str());
|
||||
tmp = WM_operatortype_description(C, ot, nullptr).c_str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tmp == nullptr) {
|
||||
if (!tmp) {
|
||||
PanelType *pt = UI_but_paneltype_get(but);
|
||||
if (pt) {
|
||||
if (type == BUT_GET_RNA_LABEL) {
|
||||
tmp = BLI_strdup(CTX_TIP_(pt->translation_context, pt->label));
|
||||
tmp = CTX_TIP_(pt->translation_context, pt->label);
|
||||
}
|
||||
else {
|
||||
/* Not all panels are from Python. */
|
||||
|
@ -6779,7 +6724,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
if (BLT_is_default_context(_tmp)) {
|
||||
_tmp = BLT_I18NCONTEXT_DEFAULT_BPYRNA;
|
||||
}
|
||||
tmp = BLI_strdup(_tmp);
|
||||
tmp = _tmp;
|
||||
}
|
||||
else if (ELEM(type, BUT_GET_RNAENUM_IDENTIFIER, BUT_GET_RNAENUM_LABEL, BUT_GET_RNAENUM_TIP)) {
|
||||
PointerRNA *ptr = nullptr;
|
||||
|
@ -6828,13 +6773,13 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
}
|
||||
if (item && item->identifier) {
|
||||
if (type == BUT_GET_RNAENUM_IDENTIFIER) {
|
||||
tmp = BLI_strdup(item->identifier);
|
||||
tmp = item->identifier;
|
||||
}
|
||||
else if (type == BUT_GET_RNAENUM_LABEL) {
|
||||
tmp = BLI_strdup(item->name);
|
||||
tmp = item->name;
|
||||
}
|
||||
else if (item->description && item->description[0]) {
|
||||
tmp = BLI_strdup(item->description);
|
||||
tmp = item->description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6846,7 +6791,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
if (!(ui_block_is_menu(but->block) && !ui_block_is_pie_menu(but->block))) {
|
||||
char buf[128];
|
||||
if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) {
|
||||
tmp = BLI_strdup(buf);
|
||||
tmp = buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6854,12 +6799,12 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
|
|||
if (!(ui_block_is_menu(but->block) && !ui_block_is_pie_menu(but->block))) {
|
||||
char buf[128];
|
||||
if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) {
|
||||
tmp = BLI_strdup(buf);
|
||||
tmp = buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
si->strinfo = tmp;
|
||||
si->strinfo = tmp ? BLI_strdupn(tmp->c_str(), tmp->size()) : nullptr;
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
|
@ -6878,19 +6823,19 @@ void UI_but_extra_icon_string_info_get(bContext *C, uiButExtraOpIcon *extra_icon
|
|||
|
||||
va_start(args, extra_icon);
|
||||
while ((si = (uiStringInfo *)va_arg(args, void *))) {
|
||||
char *tmp = nullptr;
|
||||
std::string tmp;
|
||||
|
||||
switch (si->type) {
|
||||
case BUT_GET_LABEL:
|
||||
tmp = BLI_strdup(WM_operatortype_name(optype, opptr).c_str());
|
||||
tmp = WM_operatortype_name(optype, opptr);
|
||||
break;
|
||||
case BUT_GET_TIP:
|
||||
tmp = BLI_strdup(WM_operatortype_description(C, optype, opptr).c_str());
|
||||
tmp = WM_operatortype_description(C, optype, opptr);
|
||||
break;
|
||||
case BUT_GET_OP_KEYMAP: {
|
||||
char buf[128];
|
||||
if (ui_but_extra_icon_event_operator_string(C, extra_icon, buf, sizeof(buf))) {
|
||||
tmp = BLI_strdup(buf);
|
||||
tmp = buf;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -6900,7 +6845,7 @@ void UI_but_extra_icon_string_info_get(bContext *C, uiButExtraOpIcon *extra_icon
|
|||
break;
|
||||
}
|
||||
|
||||
si->strinfo = tmp;
|
||||
si->strinfo = BLI_strdupn(tmp.c_str(), tmp.size());
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
|
|
@ -178,8 +178,8 @@ struct uiBut {
|
|||
short bit = 0, bitnr = 0, retval = 0, strwidth = 0, alignnr = 0;
|
||||
short ofs = 0, pos = 0, selsta = 0, selend = 0;
|
||||
|
||||
char *str = nullptr;
|
||||
char strdata[UI_MAX_NAME_STR] = "";
|
||||
std::string str;
|
||||
|
||||
char drawstr[UI_MAX_DRAW_STR] = "";
|
||||
|
||||
char *placeholder = nullptr;
|
||||
|
|
|
@ -2536,7 +2536,7 @@ void uiItemFullR(uiLayout *layout,
|
|||
|
||||
/* ensure text isn't added to icon_only buttons */
|
||||
if (but && icon_only) {
|
||||
BLI_assert(but->str[0] == '\0');
|
||||
BLI_assert(but->str.empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2863,7 +2863,7 @@ uiBut *ui_but_add_search(uiBut *but,
|
|||
if (RNA_property_type(prop) == PROP_ENUM) {
|
||||
/* XXX, this will have a menu string,
|
||||
* but in this case we just want the text */
|
||||
but->str[0] = 0;
|
||||
but->str.clear();
|
||||
}
|
||||
|
||||
UI_but_func_search_set_results_are_suggestions(but, results_are_suggestions);
|
||||
|
@ -3602,7 +3602,7 @@ static int menu_item_enum_opname_menu_active(bContext *C, uiBut *but, MenuItemLe
|
|||
WM_operator_properties_sanitize(&ptr, false);
|
||||
PropertyRNA *prop = RNA_struct_find_property(&ptr, lvl->propname);
|
||||
RNA_property_enum_items_gettexted(C, &ptr, prop, &item_array, &totitem, &free);
|
||||
int active = RNA_enum_from_name(item_array, but->str);
|
||||
int active = RNA_enum_from_name(item_array, but->str.c_str());
|
||||
if (free) {
|
||||
MEM_freeN((void *)item_array);
|
||||
}
|
||||
|
@ -5405,7 +5405,7 @@ static bool block_search_panel_label_matches(const uiBlock *block, const char *s
|
|||
static bool button_matches_search_filter(uiBut *but, const char *search_filter)
|
||||
{
|
||||
/* Do the shorter checks first for better performance in case there is a match. */
|
||||
if (BLI_strcasestr(but->str, search_filter)) {
|
||||
if (BLI_strcasestr(but->str.c_str(), search_filter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -5891,7 +5891,7 @@ void ui_layout_add_but(uiLayout *layout, uiBut *but)
|
|||
ui_item_size((uiItem *)bitem, &w, &h);
|
||||
/* XXX uiBut hasn't scaled yet
|
||||
* we can flag the button as not expandable, depending on its size */
|
||||
if (w <= 2 * UI_UNIT_X && (!but->str || but->str[0] == '\0')) {
|
||||
if (w <= 2 * UI_UNIT_X && but->str.empty()) {
|
||||
bitem->item.flag |= UI_ITEM_FIXED_SIZE;
|
||||
}
|
||||
|
||||
|
@ -6166,7 +6166,7 @@ static bool ui_layout_has_panel_label(const uiLayout *layout, const PanelType *p
|
|||
if (subitem->type == ITEM_BUTTON) {
|
||||
uiButtonItem *bitem = (uiButtonItem *)subitem;
|
||||
if (!(bitem->but->flag & UI_HIDDEN) &&
|
||||
STREQ(bitem->but->str, CTX_IFACE_(pt->translation_context, pt->label)))
|
||||
STREQ(bitem->but->str.c_str(), CTX_IFACE_(pt->translation_context, pt->label)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -199,7 +199,7 @@ static void ui_update_color_picker_buts_rgb(uiBut *from_but,
|
|||
* push, so disable it on RNA buttons in the color picker block */
|
||||
UI_but_flag_disable(bt, UI_BUT_UNDO);
|
||||
}
|
||||
else if (STREQ(bt->str, "Hex:")) {
|
||||
else if (bt->str == "Hex:") {
|
||||
float rgb_hex[3];
|
||||
uchar rgb_hex_uchar[3];
|
||||
char col[16];
|
||||
|
|
|
@ -180,7 +180,7 @@ uiPieMenu *UI_pie_menu_begin(bContext *C, const char *title, int icon, const wmE
|
|||
}
|
||||
/* do not align left */
|
||||
but->drawflag &= ~UI_BUT_TEXT_LEFT;
|
||||
pie->block_radial->pie_data.title = but->str;
|
||||
pie->block_radial->pie_data.title = but->str.c_str();
|
||||
pie->block_radial->pie_data.icon = icon;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ int ui_but_menu_step(uiBut *but, int direction)
|
|||
direction);
|
||||
}
|
||||
|
||||
printf("%s: cannot cycle button '%s'\n", __func__, but->str);
|
||||
printf("%s: cannot cycle button '%s'\n", __func__, but->str.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ static uiBut *ui_popup_menu_memory__internal(uiBlock *block, uiBut *but)
|
|||
|
||||
if (but) {
|
||||
/* set */
|
||||
mem[hash_mod] = ui_popup_string_hash(but->str, but->flag & UI_BUT_HAS_SEP_CHAR);
|
||||
mem[hash_mod] = ui_popup_string_hash(but->str.c_str(), but->flag & UI_BUT_HAS_SEP_CHAR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,8 @@ static uiBut *ui_popup_menu_memory__internal(uiBlock *block, uiBut *but)
|
|||
if (ELEM(but_iter->type, UI_BTYPE_LABEL, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) {
|
||||
continue;
|
||||
}
|
||||
if (mem[hash_mod] == ui_popup_string_hash(but_iter->str, but_iter->flag & UI_BUT_HAS_SEP_CHAR))
|
||||
if (mem[hash_mod] ==
|
||||
ui_popup_string_hash(but_iter->str.c_str(), but_iter->flag & UI_BUT_HAS_SEP_CHAR))
|
||||
{
|
||||
return but_iter;
|
||||
}
|
||||
|
|
|
@ -298,7 +298,6 @@ static bool menu_items_to_ui_button(MenuSearch_Item *item, uiBut *but)
|
|||
}
|
||||
|
||||
but->icon = item->icon;
|
||||
but->str = but->strdata;
|
||||
}
|
||||
|
||||
return changed;
|
||||
|
|
|
@ -1342,7 +1342,7 @@ static void widget_draw_icon(
|
|||
if (but->drawflag & UI_BUT_ICON_LEFT) {
|
||||
/* special case - icon_only pie buttons */
|
||||
if (ui_block_is_pie_menu(but->block) && !ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_POPOVER) &&
|
||||
but->str && but->str[0] == '\0')
|
||||
but->str.empty())
|
||||
{
|
||||
xs = rect->xmin + 2.0f * ofs;
|
||||
}
|
||||
|
@ -4960,7 +4960,8 @@ void ui_draw_but(const bContext *C, ARegion *region, uiStyle *style, uiBut *but,
|
|||
|
||||
/* We could use a flag for this, but for now just check size,
|
||||
* add up/down arrows if there is room. */
|
||||
if ((!but->str[0] && but->icon && (BLI_rcti_size_x(rect) < BLI_rcti_size_y(rect) + 2)) ||
|
||||
if ((but->str.empty() && but->icon &&
|
||||
(BLI_rcti_size_x(rect) < BLI_rcti_size_y(rect) + 2)) ||
|
||||
/* disable for brushes also */
|
||||
(but->flag & UI_BUT_ICON_PREVIEW))
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@ set(INC
|
|||
../../makesrna
|
||||
../../sequencer
|
||||
../../windowmanager
|
||||
../../../../extern/fmtlib/include
|
||||
|
||||
# RNA_prototypes.h
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
|
@ -137,6 +138,7 @@ set(LIB
|
|||
bf_editor_undo
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
extern_fmtlib
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -65,6 +65,10 @@
|
|||
#include "tree/tree_element_rna.hh"
|
||||
#include "tree/tree_iterator.hh"
|
||||
|
||||
#include "wm_window.hh"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
using namespace blender::ed::outliner;
|
||||
|
||||
namespace blender::ed::outliner {
|
||||
|
@ -413,7 +417,7 @@ static int outliner_item_rename_invoke(bContext *C, wmOperator *op, const wmEven
|
|||
TreeElement *te = use_active ? outliner_item_rename_find_active(space_outliner, op->reports) :
|
||||
outliner_item_rename_find_hovered(space_outliner, region, event);
|
||||
if (!te) {
|
||||
return OPERATOR_CANCELLED;
|
||||
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
|
||||
}
|
||||
|
||||
/* Force element into view. */
|
||||
|
@ -2140,7 +2144,7 @@ static int outliner_orphans_purge_invoke(bContext *C, wmOperator *op, const wmEv
|
|||
RNA_int_set(op->ptr, "num_deleted", num_tagged[INDEX_ID_NULL]);
|
||||
|
||||
if (num_tagged[INDEX_ID_NULL] == 0) {
|
||||
BKE_report(op->reports, RPT_INFO, "No orphaned data-blocks to purge");
|
||||
BKE_report(op->reports, RPT_INFO, "No unused data-blocks to purge");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
|
@ -2188,7 +2192,7 @@ static int outliner_orphans_purge_exec(bContext *C, wmOperator *op)
|
|||
bmain, LIB_TAG_DOIT, do_local_ids, do_linked_ids, do_recursive_cleanup, num_tagged);
|
||||
|
||||
if (num_tagged[INDEX_ID_NULL] == 0) {
|
||||
BKE_report(op->reports, RPT_INFO, "No orphaned data-blocks to purge");
|
||||
BKE_report(op->reports, RPT_INFO, "No unused data-blocks to purge");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
}
|
||||
|
@ -2219,7 +2223,7 @@ void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
|
|||
/* identifiers */
|
||||
ot->idname = "OUTLINER_OT_orphans_purge";
|
||||
ot->name = "Purge All";
|
||||
ot->description = "Clear all orphaned data-blocks without any users from the file";
|
||||
ot->description = "Remove all unused data-blocks without any users from the file";
|
||||
|
||||
/* callbacks */
|
||||
ot->invoke = outliner_orphans_purge_invoke;
|
||||
|
@ -2248,10 +2252,267 @@ void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
|
|||
"do_recursive",
|
||||
false,
|
||||
"Recursive Delete",
|
||||
"Recursively check for indirectly unused data-blocks, ensuring that no orphaned "
|
||||
"Recursively check for indirectly unused data-blocks, ensuring that no unused "
|
||||
"data-blocks remain after execution");
|
||||
}
|
||||
|
||||
static void wm_block_orphans_cancel(bContext *C, void *arg_block, void * /*arg_data*/)
|
||||
{
|
||||
UI_popup_block_close(C, CTX_wm_window(C), (uiBlock *)arg_block);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_cancel_button(uiBlock *block)
|
||||
{
|
||||
uiBut *but = uiDefIconTextBut(
|
||||
block, UI_BTYPE_BUT, 0, 0, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
|
||||
UI_but_func_set(but, wm_block_orphans_cancel, block, nullptr);
|
||||
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
|
||||
UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
|
||||
}
|
||||
|
||||
struct OrphansPurgeData {
|
||||
wmOperator *op = nullptr;
|
||||
char local = true;
|
||||
char linked = false;
|
||||
char recursive = false;
|
||||
};
|
||||
|
||||
static void wm_block_orphans_purge(bContext *C, void *arg_block, void *arg_data)
|
||||
{
|
||||
OrphansPurgeData *purge_data = (OrphansPurgeData *)arg_data;
|
||||
wmOperator *op = purge_data->op;
|
||||
Main *bmain = CTX_data_main(C);
|
||||
int num_tagged[INDEX_ID_MAX] = {0};
|
||||
|
||||
/* Tag all IDs to delete. */
|
||||
BKE_lib_query_unused_ids_tag(bmain,
|
||||
LIB_TAG_DOIT,
|
||||
purge_data->local,
|
||||
purge_data->linked,
|
||||
purge_data->recursive,
|
||||
num_tagged);
|
||||
|
||||
if (num_tagged[INDEX_ID_NULL] == 0) {
|
||||
BKE_report(op->reports, RPT_INFO, "No unused data to remove");
|
||||
}
|
||||
else {
|
||||
BKE_id_multi_tagged_delete(bmain);
|
||||
BKE_reportf(op->reports, RPT_INFO, "Deleted %d data block(s)", num_tagged[INDEX_ID_NULL]);
|
||||
DEG_relations_tag_update(bmain);
|
||||
WM_event_add_notifier(C, NC_ID | NA_REMOVED, nullptr);
|
||||
/* Force full redraw of the UI. */
|
||||
WM_main_add_notifier(NC_WINDOW, nullptr);
|
||||
}
|
||||
|
||||
UI_popup_block_close(C, CTX_wm_window(C), (uiBlock *)arg_block);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_purge_button(uiBlock *block, OrphansPurgeData *purge_data)
|
||||
{
|
||||
uiBut *but = uiDefIconTextBut(
|
||||
block, UI_BTYPE_BUT, 0, 0, IFACE_("Delete"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
|
||||
UI_but_func_set(but, wm_block_orphans_purge, block, purge_data);
|
||||
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
|
||||
}
|
||||
|
||||
static std::string orphan_desc(const bContext *C, const bool local, bool linked, bool recursive)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
int num_tagged[INDEX_ID_MAX] = {0};
|
||||
std::string desc;
|
||||
|
||||
BKE_lib_query_unused_ids_tag(bmain, LIB_TAG_DOIT, local, linked, recursive, num_tagged);
|
||||
|
||||
bool is_first = true;
|
||||
for (int i = 0; i < INDEX_ID_MAX - 2; i++) {
|
||||
if (num_tagged[i] != 0) {
|
||||
desc += fmt::format(
|
||||
"{}{} {}",
|
||||
(is_first) ? "" : ", ",
|
||||
num_tagged[i],
|
||||
(num_tagged[i] > 1) ?
|
||||
TIP_(BKE_idtype_idcode_to_name_plural(BKE_idtype_idcode_from_index(i))) :
|
||||
TIP_(BKE_idtype_idcode_to_name(BKE_idtype_idcode_from_index(i))));
|
||||
is_first = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (desc.empty()) {
|
||||
desc = "Nothing";
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
static uiBlock *wm_block_create_orphans_cleanup(bContext *C, ARegion *region, void *arg)
|
||||
{
|
||||
OrphansPurgeData *purge_data = static_cast<OrphansPurgeData *>(arg);
|
||||
uiBlock *block = UI_block_begin(C, region, "orphans_remove_popup", UI_EMBOSS);
|
||||
UI_block_flag_enable(
|
||||
block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
|
||||
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
|
||||
|
||||
uiLayout *layout = uiItemsAlertBox(block, 40, ALERT_ICON_WARNING);
|
||||
|
||||
/* Title. */
|
||||
uiItemL_ex(layout, TIP_("Purge Unused Data From This File"), 0, true, false);
|
||||
|
||||
uiItemS(layout);
|
||||
|
||||
std::string desc = "Local data: " + orphan_desc(C, true, false, false);
|
||||
|
||||
uiDefButBitC(block,
|
||||
UI_BTYPE_CHECKBOX,
|
||||
1,
|
||||
0,
|
||||
desc.c_str(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_UNIT_Y,
|
||||
&purge_data->local,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Delete unused local data");
|
||||
|
||||
desc = "Linked data: " + orphan_desc(C, false, true, false);
|
||||
|
||||
uiDefButBitC(block,
|
||||
UI_BTYPE_CHECKBOX,
|
||||
1,
|
||||
0,
|
||||
desc.c_str(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_UNIT_Y,
|
||||
&purge_data->linked,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Delete unused linked data");
|
||||
|
||||
uiDefButBitC(
|
||||
block,
|
||||
UI_BTYPE_CHECKBOX,
|
||||
1,
|
||||
0,
|
||||
"Include indirect data",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_UNIT_Y,
|
||||
&purge_data->recursive,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Recursively check for indirectly unused data, ensuring that no unused data remains");
|
||||
|
||||
uiItemS_ex(layout, 2.0f);
|
||||
|
||||
/* Buttons. */
|
||||
#ifdef _WIN32
|
||||
const bool windows_layout = true;
|
||||
#else
|
||||
const bool windows_layout = false;
|
||||
#endif
|
||||
|
||||
uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
|
||||
uiLayoutSetScaleY(split, 1.2f);
|
||||
|
||||
if (windows_layout) {
|
||||
/* Windows standard layout. */
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_purge_button(block, purge_data);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_cancel_button(block);
|
||||
}
|
||||
else {
|
||||
/* Non-Windows layout (macOS and Linux). */
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_cancel_button(block);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_purge_button(block, purge_data);
|
||||
}
|
||||
|
||||
UI_block_bounds_set_centered(block, 14 * UI_SCALE_FAC);
|
||||
return block;
|
||||
}
|
||||
|
||||
static int outliner_orphans_cleanup_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
OrphansPurgeData *purge_data = MEM_new<OrphansPurgeData>(__func__);
|
||||
purge_data->op = op;
|
||||
UI_popup_block_invoke(C, wm_block_create_orphans_cleanup, purge_data, MEM_freeN);
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_orphans_cleanup(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->idname = "OUTLINER_OT_orphans_cleanup";
|
||||
ot->name = "Purge Unused Data...";
|
||||
ot->description = "Remove unused data from this file";
|
||||
|
||||
/* callbacks */
|
||||
ot->exec = outliner_orphans_cleanup_exec;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
static int outliner_orphans_manage_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
int width = 800;
|
||||
int height = 500;
|
||||
if (wm_get_screensize(&width, &height)) {
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
}
|
||||
|
||||
const rcti window_rect = {
|
||||
/*xmin*/ 0,
|
||||
/*xmax*/ width,
|
||||
/*ymin*/ 0,
|
||||
/*ymax*/ height,
|
||||
};
|
||||
|
||||
if (WM_window_open(C,
|
||||
IFACE_("Manage Unused Data"),
|
||||
&window_rect,
|
||||
SPACE_OUTLINER,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
WIN_ALIGN_PARENT_CENTER,
|
||||
nullptr,
|
||||
nullptr) != nullptr)
|
||||
{
|
||||
SpaceOutliner *soutline = (SpaceOutliner *)CTX_wm_area(C)->spacedata.first;
|
||||
soutline->outlinevis = SO_ID_ORPHANS;
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_orphans_manage(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->idname = "OUTLINER_OT_orphans_manage";
|
||||
ot->name = "Manage Unused Data...";
|
||||
ot->description = "Open a window to manage unused data";
|
||||
|
||||
/* callbacks */
|
||||
ot->exec = outliner_orphans_manage_exec;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::ed::outliner
|
||||
|
|
|
@ -511,7 +511,9 @@ void OUTLINER_OT_keyingset_remove_selected(wmOperatorType *ot);
|
|||
void OUTLINER_OT_drivers_add_selected(wmOperatorType *ot);
|
||||
void OUTLINER_OT_drivers_delete_selected(wmOperatorType *ot);
|
||||
|
||||
void OUTLINER_OT_orphans_cleanup(wmOperatorType *ot);
|
||||
void OUTLINER_OT_orphans_purge(wmOperatorType *ot);
|
||||
void OUTLINER_OT_orphans_manage(wmOperatorType *ot);
|
||||
|
||||
/* `outliner_query.cc` */
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ void outliner_operatortypes()
|
|||
WM_operatortype_append(OUTLINER_OT_drivers_delete_selected);
|
||||
|
||||
WM_operatortype_append(OUTLINER_OT_orphans_purge);
|
||||
WM_operatortype_append(OUTLINER_OT_orphans_cleanup);
|
||||
WM_operatortype_append(OUTLINER_OT_orphans_manage);
|
||||
|
||||
WM_operatortype_append(OUTLINER_OT_parent_drop);
|
||||
WM_operatortype_append(OUTLINER_OT_parent_clear);
|
||||
|
|
|
@ -1569,30 +1569,84 @@ void outliner_item_select(bContext *C,
|
|||
}
|
||||
}
|
||||
|
||||
static bool can_select_recursive(TreeElement *te, Collection *in_collection)
|
||||
{
|
||||
if (te->store_elem->type == TSE_LAYER_COLLECTION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (te->store_elem->type == TSE_SOME_ID && te->idcode == ID_OB) {
|
||||
/* Only actually select the object if
|
||||
* 1. We are not restricted to any collection, or
|
||||
* 2. The object is in fact in the given collection. */
|
||||
if (!in_collection || BKE_collection_has_object_recursive(
|
||||
in_collection, reinterpret_cast<Object *>(te->store_elem->id)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void do_outliner_select_recursive(ListBase *lb, bool selecting, Collection *in_collection)
|
||||
{
|
||||
LISTBASE_FOREACH (TreeElement *, te, lb) {
|
||||
TreeStoreElem *tselem = TREESTORE(te);
|
||||
/* Recursive selection only on collections or objects. */
|
||||
if (can_select_recursive(te, in_collection)) {
|
||||
tselem->flag = selecting ? (tselem->flag | TSE_SELECTED) : (tselem->flag & ~TSE_SELECTED);
|
||||
if (tselem->type == TSE_LAYER_COLLECTION) {
|
||||
/* Restrict sub-tree selections to this collection. This prevents undesirable behavior in
|
||||
* the edge-case where there is an object which is part of this collection, but which has
|
||||
* children that are part of another collection. */
|
||||
do_outliner_select_recursive(
|
||||
&te->subtree, selecting, static_cast<LayerCollection *>(te->directdata)->collection);
|
||||
}
|
||||
else {
|
||||
do_outliner_select_recursive(&te->subtree, selecting, in_collection);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tselem->flag &= ~TSE_SELECTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool do_outliner_range_select_recursive(ListBase *lb,
|
||||
TreeElement *active,
|
||||
TreeElement *cursor,
|
||||
bool selecting)
|
||||
bool selecting,
|
||||
const bool recurse,
|
||||
Collection *in_collection)
|
||||
{
|
||||
LISTBASE_FOREACH (TreeElement *, te, lb) {
|
||||
TreeStoreElem *tselem = TREESTORE(te);
|
||||
|
||||
if (selecting) {
|
||||
tselem->flag |= TSE_SELECTED;
|
||||
}
|
||||
bool can_select = !recurse || can_select_recursive(te, in_collection);
|
||||
|
||||
/* Remember if we are selecting before we potentially change the selecting state. */
|
||||
bool selecting_before = selecting;
|
||||
|
||||
/* Set state for selection */
|
||||
if (ELEM(te, active, cursor)) {
|
||||
selecting = !selecting;
|
||||
}
|
||||
|
||||
if (selecting) {
|
||||
if (can_select && (selecting_before || selecting)) {
|
||||
tselem->flag |= TSE_SELECTED;
|
||||
}
|
||||
|
||||
/* Don't look inside closed elements */
|
||||
if (!(tselem->flag & TSE_CLOSED)) {
|
||||
selecting = do_outliner_range_select_recursive(&te->subtree, active, cursor, selecting);
|
||||
/* Don't look inside closed elements, unless we're forcing the recursion all the way down. */
|
||||
if (!(tselem->flag & TSE_CLOSED) || recurse) {
|
||||
/* If this tree element is a collection, then it sets
|
||||
* the precedent for inclusion of its subobjects. */
|
||||
Collection *child_collection = in_collection;
|
||||
if (tselem->type == TSE_LAYER_COLLECTION) {
|
||||
child_collection = static_cast<LayerCollection *>(te->directdata)->collection;
|
||||
}
|
||||
selecting = do_outliner_range_select_recursive(
|
||||
&te->subtree, active, cursor, selecting, recurse, child_collection);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1603,7 +1657,9 @@ static bool do_outliner_range_select_recursive(ListBase *lb,
|
|||
static void do_outliner_range_select(bContext *C,
|
||||
SpaceOutliner *space_outliner,
|
||||
TreeElement *cursor,
|
||||
const bool extend)
|
||||
const bool extend,
|
||||
const bool recurse,
|
||||
Collection *in_collection)
|
||||
{
|
||||
TreeElement *active = outliner_find_element_with_flag(&space_outliner->tree, TSE_ACTIVE);
|
||||
|
||||
|
@ -1632,7 +1688,8 @@ static void do_outliner_range_select(bContext *C,
|
|||
return;
|
||||
}
|
||||
|
||||
do_outliner_range_select_recursive(&space_outliner->tree, active, cursor, false);
|
||||
do_outliner_range_select_recursive(
|
||||
&space_outliner->tree, active, cursor, false, recurse, in_collection);
|
||||
}
|
||||
|
||||
static bool outliner_is_co_within_restrict_columns(const SpaceOutliner *space_outliner,
|
||||
|
@ -1673,7 +1730,8 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
|
|||
const int mval[2],
|
||||
const bool extend,
|
||||
const bool use_range,
|
||||
const bool deselect_all)
|
||||
const bool deselect_all,
|
||||
const bool recurse)
|
||||
{
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
|
||||
|
@ -1716,21 +1774,73 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
|
|||
|
||||
TreeStoreElem *activate_tselem = TREESTORE(activate_te);
|
||||
|
||||
/* If we're recursing, we need to know the collection of the selected item in order
|
||||
* to prevent selecting across collection boundaries. (Object hierarchies might cross
|
||||
* collection boundaries, i.e., children may be in different collections from their
|
||||
* parents.) */
|
||||
Collection *parent_collection = nullptr;
|
||||
if (recurse) {
|
||||
if (activate_tselem->type == TSE_LAYER_COLLECTION) {
|
||||
parent_collection = static_cast<LayerCollection *>(activate_te->directdata)->collection;
|
||||
}
|
||||
else if (activate_tselem->type == TSE_SOME_ID && activate_te->idcode == ID_OB) {
|
||||
parent_collection = BKE_collection_object_find(
|
||||
CTX_data_main(C),
|
||||
CTX_data_scene(C),
|
||||
nullptr,
|
||||
reinterpret_cast<Object *>(activate_tselem->id));
|
||||
}
|
||||
}
|
||||
|
||||
/* If we're not recursing (not double clicking), and we are extending or range selecting by
|
||||
* holding CTRL or SHIFT, ignore events when the cursor is over the icon. This disambiguates
|
||||
* the case where we are recursing *and* holding CTRL or SHIFT in order to extend or range
|
||||
* select recursively. */
|
||||
if (!recurse && (extend || use_range) &&
|
||||
outliner_item_is_co_over_icon(activate_te, view_mval[0]))
|
||||
{
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (use_range) {
|
||||
do_outliner_range_select(C, space_outliner, activate_te, extend);
|
||||
do_outliner_range_select(C, space_outliner, activate_te, extend, recurse, parent_collection);
|
||||
if (recurse) {
|
||||
do_outliner_select_recursive(&activate_te->subtree, true, parent_collection);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const bool is_over_name_icons = outliner_item_is_co_over_name_icons(activate_te,
|
||||
view_mval[0]);
|
||||
/* Always select unless already active and selected */
|
||||
const bool select = !extend || !(activate_tselem->flag & TSE_ACTIVE &&
|
||||
activate_tselem->flag & TSE_SELECTED);
|
||||
/* Always select unless already active and selected. */
|
||||
bool select = !extend || !(activate_tselem->flag & TSE_ACTIVE) ||
|
||||
!(activate_tselem->flag & TSE_SELECTED);
|
||||
|
||||
/* If we're CTRL+double-clicking and the element is aleady
|
||||
* selected, skip the activation and go straight to deselection. */
|
||||
if (extend && recurse && activate_tselem->flag & TSE_SELECTED) {
|
||||
select = false;
|
||||
}
|
||||
|
||||
const short select_flag = OL_ITEM_ACTIVATE | (select ? OL_ITEM_SELECT : OL_ITEM_DESELECT) |
|
||||
(is_over_name_icons ? OL_ITEM_SELECT_DATA : 0) |
|
||||
(extend ? OL_ITEM_EXTEND : 0);
|
||||
|
||||
outliner_item_select(C, space_outliner, activate_te, select_flag);
|
||||
/* The recurse flag is set when the user double-clicks
|
||||
* to select everything in a collection or hierarchy. */
|
||||
if (recurse) {
|
||||
if (outliner_item_is_co_over_icon(activate_te, view_mval[0])) {
|
||||
/* Select or deselect object hierarchy recursively. */
|
||||
outliner_item_select(C, space_outliner, activate_te, select_flag);
|
||||
do_outliner_select_recursive(&activate_te->subtree, select, parent_collection);
|
||||
}
|
||||
else {
|
||||
/* Double-clicked, but it wasn't on the icon. */
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
outliner_item_select(C, space_outliner, activate_te, select_flag);
|
||||
}
|
||||
|
||||
/* Only switch properties editor tabs when icons are selected. */
|
||||
if (is_over_icon) {
|
||||
|
@ -1765,10 +1875,11 @@ static int outliner_item_activate_invoke(bContext *C, wmOperator *op, const wmEv
|
|||
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
||||
const bool use_range = RNA_boolean_get(op->ptr, "extend_range");
|
||||
const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
|
||||
const bool recurse = RNA_boolean_get(op->ptr, "recurse");
|
||||
|
||||
int mval[2];
|
||||
WM_event_drag_start_mval(event, region, mval);
|
||||
return outliner_item_do_activate_from_cursor(C, mval, extend, use_range, deselect_all);
|
||||
return outliner_item_do_activate_from_cursor(C, mval, extend, use_range, deselect_all, recurse);
|
||||
}
|
||||
|
||||
void OUTLINER_OT_item_activate(wmOperatorType *ot)
|
||||
|
@ -1796,6 +1907,10 @@ void OUTLINER_OT_item_activate(wmOperatorType *ot)
|
|||
"Deselect On Nothing",
|
||||
"Deselect all when nothing under the cursor");
|
||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||
|
||||
prop = RNA_def_boolean(
|
||||
ot->srna, "recurse", false, "Recurse", "Select objects recursively from active element");
|
||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -340,7 +340,7 @@ void VIEW3D_OT_render_border(wmOperatorType *ot)
|
|||
ot->modal = WM_gesture_box_modal;
|
||||
ot->cancel = WM_gesture_box_cancel;
|
||||
|
||||
ot->poll = ED_operator_view3d_active;
|
||||
ot->poll = ED_operator_region_view3d_active;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
#include "BLI_math_color_blend.h"
|
||||
|
@ -22,73 +21,39 @@
|
|||
|
||||
namespace blender::imbuf::transform {
|
||||
|
||||
struct TransformUserData {
|
||||
/** \brief Source image buffer to read from. */
|
||||
struct TransformContext {
|
||||
const ImBuf *src;
|
||||
/** \brief Destination image buffer to write to. */
|
||||
ImBuf *dst;
|
||||
/** \brief UV coordinates at the origin (0,0) in source image space. */
|
||||
eIMBTransformMode mode;
|
||||
|
||||
/* UV coordinates at the destination origin (0,0) in source image space. */
|
||||
float2 start_uv;
|
||||
|
||||
/**
|
||||
* \brief delta UV coordinates along the source image buffer, when moving a single pixel in the X
|
||||
* axis of the dst image buffer.
|
||||
*/
|
||||
/* Source UV step delta, when moving along one destination pixel in X axis. */
|
||||
float2 add_x;
|
||||
|
||||
/**
|
||||
* \brief delta UV coordinate along the source image buffer, when moving a single pixel in the Y
|
||||
* axes of the dst image buffer.
|
||||
*/
|
||||
/* Source UV step delta, when moving along one destination pixel in Y axis. */
|
||||
float2 add_y;
|
||||
|
||||
struct {
|
||||
/**
|
||||
* Contains per sub-sample a delta to be added to the uv of the source image buffer.
|
||||
*/
|
||||
Vector<float2, 9> delta_uvs;
|
||||
} subsampling;
|
||||
/* Per-subsample source image delta UVs. */
|
||||
Vector<float2, 9> subsampling_deltas;
|
||||
|
||||
struct {
|
||||
IndexRange x_range;
|
||||
IndexRange y_range;
|
||||
} destination_region;
|
||||
IndexRange dst_region_x_range;
|
||||
IndexRange dst_region_y_range;
|
||||
|
||||
/**
|
||||
* \brief Cropping region in source image pixel space.
|
||||
*/
|
||||
/* Cropping region in source image pixel space. */
|
||||
rctf src_crop;
|
||||
|
||||
/**
|
||||
* \brief Initialize the start_uv, add_x and add_y fields based on the given transform matrix.
|
||||
*/
|
||||
void init(const float4x4 &transform_matrix,
|
||||
const int num_subsamples,
|
||||
const bool do_crop_destination_region)
|
||||
void init(const float4x4 &transform_matrix, const int num_subsamples, const bool has_source_crop)
|
||||
{
|
||||
init_start_uv(transform_matrix);
|
||||
init_add_x(transform_matrix);
|
||||
init_add_y(transform_matrix);
|
||||
start_uv = transform_matrix.location().xy();
|
||||
add_x = transform_matrix.x_axis().xy();
|
||||
add_y = transform_matrix.y_axis().xy();
|
||||
init_subsampling(num_subsamples);
|
||||
init_destination_region(transform_matrix, do_crop_destination_region);
|
||||
init_destination_region(transform_matrix, has_source_crop);
|
||||
}
|
||||
|
||||
private:
|
||||
void init_start_uv(const float4x4 &transform_matrix)
|
||||
{
|
||||
start_uv = transform_matrix.location().xy();
|
||||
}
|
||||
|
||||
void init_add_x(const float4x4 &transform_matrix)
|
||||
{
|
||||
add_x = transform_matrix.x_axis().xy();
|
||||
}
|
||||
|
||||
void init_add_y(const float4x4 &transform_matrix)
|
||||
{
|
||||
add_y = transform_matrix.y_axis().xy();
|
||||
}
|
||||
|
||||
void init_subsampling(const int num_subsamples)
|
||||
{
|
||||
float2 subsample_add_x = add_x / num_subsamples;
|
||||
|
@ -101,17 +66,16 @@ struct TransformUserData {
|
|||
float2 delta_uv = offset_x + offset_y;
|
||||
delta_uv += x * subsample_add_x;
|
||||
delta_uv += y * subsample_add_y;
|
||||
subsampling.delta_uvs.append(delta_uv);
|
||||
subsampling_deltas.append(delta_uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_destination_region(const float4x4 &transform_matrix,
|
||||
const bool do_crop_destination_region)
|
||||
void init_destination_region(const float4x4 &transform_matrix, const bool has_source_crop)
|
||||
{
|
||||
if (!do_crop_destination_region) {
|
||||
destination_region.x_range = IndexRange(dst->x);
|
||||
destination_region.y_range = IndexRange(dst->y);
|
||||
if (!has_source_crop) {
|
||||
dst_region_x_range = IndexRange(dst->x);
|
||||
dst_region_y_range = IndexRange(dst->y);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -136,99 +100,28 @@ struct TransformUserData {
|
|||
rcti dest_rect;
|
||||
BLI_rcti_init(&dest_rect, 0, dst->x, 0, dst->y);
|
||||
BLI_rcti_isect(&rect, &dest_rect, &rect);
|
||||
destination_region.x_range = IndexRange(rect.xmin, BLI_rcti_size_x(&rect));
|
||||
destination_region.y_range = IndexRange(rect.ymin, BLI_rcti_size_y(&rect));
|
||||
dst_region_x_range = IndexRange(rect.xmin, BLI_rcti_size_x(&rect));
|
||||
dst_region_y_range = IndexRange(rect.ymin, BLI_rcti_size_y(&rect));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Crop uv-coordinates that are outside the user data src_crop rect.
|
||||
*/
|
||||
struct CropSource {
|
||||
/**
|
||||
* \brief Should the source pixel at the given uv coordinate be discarded.
|
||||
*
|
||||
* Uses user_data.src_crop to determine if the uv coordinate should be skipped.
|
||||
*/
|
||||
static bool should_discard(const TransformUserData &user_data, const float2 &uv)
|
||||
{
|
||||
return uv.x < user_data.src_crop.xmin || uv.x >= user_data.src_crop.xmax ||
|
||||
uv.y < user_data.src_crop.ymin || uv.y >= user_data.src_crop.ymax;
|
||||
}
|
||||
};
|
||||
/* Crop uv-coordinates that are outside the user data src_crop rect. */
|
||||
static bool should_discard(const TransformContext &ctx, const float2 &uv)
|
||||
{
|
||||
return uv.x < ctx.src_crop.xmin || uv.x >= ctx.src_crop.xmax || uv.y < ctx.src_crop.ymin ||
|
||||
uv.y >= ctx.src_crop.ymax;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Discard that does not discard anything.
|
||||
*/
|
||||
struct NoDiscard {
|
||||
/**
|
||||
* \brief Should the source pixel at the given uv coordinate be discarded.
|
||||
*
|
||||
* Will never discard any pixels.
|
||||
*/
|
||||
static bool should_discard(const TransformUserData & /*user_data*/, const float2 & /*uv*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
template<typename T> static T *init_pixel_pointer(const ImBuf *image, int x, int y);
|
||||
template<> uchar *init_pixel_pointer(const ImBuf *image, int x, int y)
|
||||
{
|
||||
return image->byte_buffer.data + (size_t(y) * image->x + x) * image->channels;
|
||||
}
|
||||
template<> float *init_pixel_pointer(const ImBuf *image, int x, int y)
|
||||
{
|
||||
return image->float_buffer.data + (size_t(y) * image->x + x) * image->channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Pointer to a pixel to write to in serial.
|
||||
*/
|
||||
template<
|
||||
/**
|
||||
* \brief Kind of buffer.
|
||||
* Possible options: float, uchar.
|
||||
*/
|
||||
typename StorageType = float,
|
||||
|
||||
/**
|
||||
* \brief Number of channels of a single pixel.
|
||||
*/
|
||||
int NumChannels = 4>
|
||||
class PixelPointer {
|
||||
public:
|
||||
static const int ChannelLen = NumChannels;
|
||||
|
||||
private:
|
||||
StorageType *pointer;
|
||||
|
||||
public:
|
||||
void init_pixel_pointer(const ImBuf *image_buffer, int2 start_coordinate)
|
||||
{
|
||||
const size_t offset = (start_coordinate.y * size_t(image_buffer->x) + start_coordinate.x) *
|
||||
NumChannels;
|
||||
|
||||
if constexpr (std::is_same_v<StorageType, float>) {
|
||||
pointer = image_buffer->float_buffer.data + offset;
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, uchar>) {
|
||||
pointer = const_cast<uchar *>(
|
||||
static_cast<const uchar *>(static_cast<const void *>(image_buffer->byte_buffer.data)) +
|
||||
offset);
|
||||
}
|
||||
else {
|
||||
pointer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get pointer to the current pixel to write to.
|
||||
*/
|
||||
StorageType *get_pointer()
|
||||
{
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void increase_pixel_pointer()
|
||||
{
|
||||
pointer += NumChannels;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Repeats UV coordinate.
|
||||
*/
|
||||
static float wrap_uv(float value, int size)
|
||||
{
|
||||
int x = int(floorf(value));
|
||||
|
@ -241,416 +134,246 @@ static float wrap_uv(float value, int size)
|
|||
return x;
|
||||
}
|
||||
|
||||
/* TODO: should we use math_vectors for this. */
|
||||
template<typename StorageType, int NumChannels>
|
||||
class Pixel : public std::array<StorageType, NumChannels> {
|
||||
public:
|
||||
void clear()
|
||||
{
|
||||
for (int channel_index : IndexRange(NumChannels)) {
|
||||
(*this)[channel_index] = 0;
|
||||
template<typename T, int NumChannels>
|
||||
static void add_subsample(const T *src, T *dst, int sample_number)
|
||||
{
|
||||
BLI_STATIC_ASSERT((is_same_any_v<T, uchar, float>), "Only uchar and float channels supported.");
|
||||
|
||||
float factor = 1.0 / (sample_number + 1);
|
||||
if constexpr (std::is_same_v<T, uchar>) {
|
||||
BLI_STATIC_ASSERT(NumChannels == 4, "Pixels using uchar requires to have 4 channels.");
|
||||
blend_color_interpolate_byte(dst, dst, src, factor);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float> && NumChannels == 4) {
|
||||
blend_color_interpolate_float(dst, dst, src, factor);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float>) {
|
||||
for (int i : IndexRange(NumChannels)) {
|
||||
dst[i] = dst[i] * (1.0f - factor) + src[i] * factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_subsample(const Pixel<StorageType, NumChannels> other, int sample_number)
|
||||
{
|
||||
BLI_STATIC_ASSERT((std::is_same_v<StorageType, uchar>) || (std::is_same_v<StorageType, float>),
|
||||
"Only uchar and float channels supported.");
|
||||
template<int NumChannels>
|
||||
static void sample_nearest_float(const ImBuf *source, float u, float v, float *r_sample)
|
||||
{
|
||||
int x1 = int(u);
|
||||
int y1 = int(v);
|
||||
|
||||
float factor = 1.0 / (sample_number + 1);
|
||||
if constexpr (std::is_same_v<StorageType, uchar>) {
|
||||
BLI_STATIC_ASSERT(NumChannels == 4, "Pixels using uchar requires to have 4 channels.");
|
||||
blend_color_interpolate_byte(this->data(), this->data(), other.data(), factor);
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && NumChannels == 4) {
|
||||
blend_color_interpolate_float(this->data(), this->data(), other.data(), factor);
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float>) {
|
||||
for (int channel_index : IndexRange(NumChannels)) {
|
||||
(*this)[channel_index] = (*this)[channel_index] * (1.0 - factor) +
|
||||
other[channel_index] * factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Read a sample from an image buffer.
|
||||
*
|
||||
* A sampler can read from an image buffer.
|
||||
*/
|
||||
template<
|
||||
/** \brief Interpolation mode to use when sampling. */
|
||||
eIMBInterpolationFilterMode Filter,
|
||||
|
||||
/** \brief storage type of a single pixel channel (uchar or float). */
|
||||
typename StorageType,
|
||||
/**
|
||||
* \brief number of channels if the image to read.
|
||||
*
|
||||
* Must match the actual channels of the image buffer that is sampled.
|
||||
*/
|
||||
int NumChannels,
|
||||
/**
|
||||
* \brief Should UVs wrap
|
||||
*/
|
||||
bool UVWrapping>
|
||||
class Sampler {
|
||||
public:
|
||||
using ChannelType = StorageType;
|
||||
static const int ChannelLen = NumChannels;
|
||||
using SampleType = Pixel<StorageType, NumChannels>;
|
||||
|
||||
void sample(const ImBuf *source, const float2 &uv, SampleType &r_sample)
|
||||
{
|
||||
float u = uv.x;
|
||||
float v = uv.y;
|
||||
if constexpr (UVWrapping) {
|
||||
u = wrap_uv(u, source->x);
|
||||
v = wrap_uv(v, source->y);
|
||||
}
|
||||
/* BLI_bilinear_interpolation functions use `floor(uv)` and `floor(uv)+1`
|
||||
* texels. For proper mapping between pixel and texel spaces, need to
|
||||
* subtract 0.5. Same for bicubic. */
|
||||
if constexpr (Filter == IMB_FILTER_BILINEAR || Filter == IMB_FILTER_BICUBIC) {
|
||||
u -= 0.5f;
|
||||
v -= 0.5f;
|
||||
}
|
||||
if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, float> &&
|
||||
NumChannels == 4)
|
||||
{
|
||||
bilinear_interpolation_color_fl(source, r_sample.data(), u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<StorageType, uchar> &&
|
||||
NumChannels == 4)
|
||||
{
|
||||
nearest_interpolation_color_char(source, r_sample.data(), nullptr, u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, uchar> &&
|
||||
NumChannels == 4)
|
||||
{
|
||||
bilinear_interpolation_color_char(source, r_sample.data(), u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, float>) {
|
||||
if constexpr (UVWrapping) {
|
||||
BLI_bilinear_interpolation_wrap_fl(source->float_buffer.data,
|
||||
r_sample.data(),
|
||||
source->x,
|
||||
source->y,
|
||||
NumChannels,
|
||||
UNPACK2(uv),
|
||||
true,
|
||||
true);
|
||||
}
|
||||
else {
|
||||
BLI_bilinear_interpolation_fl(
|
||||
source->float_buffer.data, r_sample.data(), source->x, source->y, NumChannels, u, v);
|
||||
}
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<StorageType, float>) {
|
||||
sample_nearest_float(source, u, v, r_sample);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<StorageType, float>) {
|
||||
BLI_bicubic_interpolation_fl(
|
||||
source->float_buffer.data, r_sample.data(), source->x, source->y, NumChannels, u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<StorageType, uchar> &&
|
||||
NumChannels == 4)
|
||||
{
|
||||
BLI_bicubic_interpolation_char(
|
||||
source->byte_buffer.data, r_sample.data(), source->x, source->y, u, v);
|
||||
}
|
||||
else {
|
||||
/* Unsupported sampler. */
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void sample_nearest_float(const ImBuf *source,
|
||||
const float u,
|
||||
const float v,
|
||||
SampleType &r_sample)
|
||||
{
|
||||
BLI_STATIC_ASSERT(std::is_same_v<StorageType, float>);
|
||||
|
||||
/* ImBuf in must have a valid rect or rect_float, assume this is already checked */
|
||||
int x1 = int(u);
|
||||
int y1 = int(v);
|
||||
|
||||
/* Break when sample outside image is requested. */
|
||||
if (x1 < 0 || x1 >= source->x || y1 < 0 || y1 >= source->y) {
|
||||
for (int i = 0; i < NumChannels; i++) {
|
||||
r_sample[i] = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t offset = (size_t(source->x) * y1 + x1) * NumChannels;
|
||||
const float *dataF = source->float_buffer.data + offset;
|
||||
/* Break when sample outside image is requested. */
|
||||
if (x1 < 0 || x1 >= source->x || y1 < 0 || y1 >= source->y) {
|
||||
for (int i = 0; i < NumChannels; i++) {
|
||||
r_sample[i] = dataF[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Change the number of channels and store it.
|
||||
*
|
||||
* Template class to convert and store a sample in a PixelPointer.
|
||||
* It supports:
|
||||
* - 4 channel uchar -> 4 channel uchar.
|
||||
* - 4 channel float -> 4 channel float.
|
||||
* - 3 channel float -> 4 channel float.
|
||||
* - 2 channel float -> 4 channel float.
|
||||
* - 1 channel float -> 4 channel float.
|
||||
*/
|
||||
template<typename StorageType, int SourceNumChannels, int DestinationNumChannels>
|
||||
class ChannelConverter {
|
||||
public:
|
||||
using SampleType = Pixel<StorageType, SourceNumChannels>;
|
||||
using PixelType = PixelPointer<StorageType, DestinationNumChannels>;
|
||||
|
||||
/**
|
||||
* \brief Convert the number of channels of the given sample to match the pixel pointer and
|
||||
* store it at the location the pixel_pointer points at.
|
||||
*/
|
||||
void convert_and_store(const SampleType &sample, PixelType &pixel_pointer)
|
||||
{
|
||||
if constexpr (std::is_same_v<StorageType, uchar>) {
|
||||
BLI_STATIC_ASSERT(SourceNumChannels == 4, "Unsigned chars always have 4 channels.");
|
||||
BLI_STATIC_ASSERT(DestinationNumChannels == 4, "Unsigned chars always have 4 channels.");
|
||||
|
||||
copy_v4_v4_uchar(pixel_pointer.get_pointer(), sample.data());
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 4 &&
|
||||
DestinationNumChannels == 4)
|
||||
{
|
||||
copy_v4_v4(pixel_pointer.get_pointer(), sample.data());
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 3 &&
|
||||
DestinationNumChannels == 4)
|
||||
{
|
||||
copy_v4_fl4(pixel_pointer.get_pointer(), sample[0], sample[1], sample[2], 1.0f);
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 2 &&
|
||||
DestinationNumChannels == 4)
|
||||
{
|
||||
copy_v4_fl4(pixel_pointer.get_pointer(), sample[0], sample[1], 0.0f, 1.0f);
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 1 &&
|
||||
DestinationNumChannels == 4)
|
||||
{
|
||||
copy_v4_fl4(pixel_pointer.get_pointer(), sample[0], sample[0], sample[0], 1.0f);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
r_sample[i] = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void mix_and_store(const SampleType &sample, PixelType &pixel_pointer, const float mix_factor)
|
||||
{
|
||||
if constexpr (std::is_same_v<StorageType, uchar>) {
|
||||
BLI_STATIC_ASSERT(SourceNumChannels == 4, "Unsigned chars always have 4 channels.");
|
||||
BLI_STATIC_ASSERT(DestinationNumChannels == 4, "Unsigned chars always have 4 channels.");
|
||||
blend_color_interpolate_byte(
|
||||
pixel_pointer.get_pointer(), pixel_pointer.get_pointer(), sample.data(), mix_factor);
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 4 &&
|
||||
DestinationNumChannels == 4)
|
||||
{
|
||||
blend_color_interpolate_float(
|
||||
pixel_pointer.get_pointer(), pixel_pointer.get_pointer(), sample.data(), mix_factor);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
size_t offset = (size_t(source->x) * y1 + x1) * NumChannels;
|
||||
const float *dataF = source->float_buffer.data + offset;
|
||||
for (int i = 0; i < NumChannels; i++) {
|
||||
r_sample[i] = dataF[i];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Processor for a scanline.
|
||||
*/
|
||||
template<
|
||||
/**
|
||||
* \brief Discard functor that implements `should_discard`.
|
||||
*/
|
||||
typename Discard,
|
||||
|
||||
/**
|
||||
* \brief Color interpolation function to read from the source buffer.
|
||||
*/
|
||||
typename Sampler,
|
||||
|
||||
/**
|
||||
* \brief Kernel to store to the destination buffer.
|
||||
* Should be an PixelPointer
|
||||
*/
|
||||
typename OutputPixelPointer>
|
||||
class ScanlineProcessor {
|
||||
Discard discarder;
|
||||
OutputPixelPointer output;
|
||||
Sampler sampler;
|
||||
|
||||
/**
|
||||
* \brief Channels sizzling logic to convert between the input image buffer and the output
|
||||
* image buffer.
|
||||
*/
|
||||
ChannelConverter<typename Sampler::ChannelType,
|
||||
Sampler::ChannelLen,
|
||||
OutputPixelPointer::ChannelLen>
|
||||
channel_converter;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Inner loop of the transformations, processing a full scanline.
|
||||
*/
|
||||
void process(const TransformUserData *user_data, int scanline)
|
||||
{
|
||||
if (user_data->subsampling.delta_uvs.size() > 1) {
|
||||
process_with_subsampling(user_data, scanline);
|
||||
}
|
||||
else {
|
||||
process_one_sample_per_pixel(user_data, scanline);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void process_one_sample_per_pixel(const TransformUserData *user_data, int scanline)
|
||||
{
|
||||
/* Note: sample at pixel center for proper filtering. */
|
||||
float pixel_x = 0.5f;
|
||||
float pixel_y = scanline + 0.5f;
|
||||
float2 uv0 = user_data->start_uv + user_data->add_x * pixel_x + user_data->add_y * pixel_y;
|
||||
|
||||
output.init_pixel_pointer(user_data->dst,
|
||||
int2(user_data->destination_region.x_range.first(), scanline));
|
||||
for (int xi : user_data->destination_region.x_range) {
|
||||
float2 uv = uv0 + xi * user_data->add_x;
|
||||
if (!discarder.should_discard(*user_data, uv)) {
|
||||
typename Sampler::SampleType sample;
|
||||
sampler.sample(user_data->src, uv, sample);
|
||||
channel_converter.convert_and_store(sample, output);
|
||||
}
|
||||
output.increase_pixel_pointer();
|
||||
}
|
||||
}
|
||||
|
||||
void process_with_subsampling(const TransformUserData *user_data, int scanline)
|
||||
{
|
||||
/* Note: sample at pixel center for proper filtering. */
|
||||
float pixel_x = 0.5f;
|
||||
float pixel_y = scanline + 0.5f;
|
||||
float2 uv0 = user_data->start_uv + user_data->add_x * pixel_x + user_data->add_y * pixel_y;
|
||||
|
||||
output.init_pixel_pointer(user_data->dst,
|
||||
int2(user_data->destination_region.x_range.first(), scanline));
|
||||
for (int xi : user_data->destination_region.x_range) {
|
||||
float2 uv = uv0 + xi * user_data->add_x;
|
||||
typename Sampler::SampleType sample;
|
||||
sample.clear();
|
||||
int num_subsamples_added = 0;
|
||||
|
||||
for (const float2 &delta_uv : user_data->subsampling.delta_uvs) {
|
||||
const float2 subsample_uv = uv + delta_uv;
|
||||
if (!discarder.should_discard(*user_data, subsample_uv)) {
|
||||
typename Sampler::SampleType sub_sample;
|
||||
sampler.sample(user_data->src, subsample_uv, sub_sample);
|
||||
sample.add_subsample(sub_sample, num_subsamples_added);
|
||||
num_subsamples_added += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_subsamples_added != 0) {
|
||||
const float mix_weight = float(num_subsamples_added) /
|
||||
user_data->subsampling.delta_uvs.size();
|
||||
channel_converter.mix_and_store(sample, output, mix_weight);
|
||||
}
|
||||
output.increase_pixel_pointer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief callback function for threaded transformation.
|
||||
*/
|
||||
template<typename Processor> void transform_scanline_function(void *custom_data, int scanline)
|
||||
{
|
||||
const TransformUserData *user_data = static_cast<const TransformUserData *>(custom_data);
|
||||
Processor processor;
|
||||
processor.process(user_data, scanline);
|
||||
}
|
||||
|
||||
/* Read a pixel from an image buffer, with filtering/wrapping parameters. */
|
||||
template<eIMBInterpolationFilterMode Filter, typename T, int NumChannels, bool WrapUV>
|
||||
static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
|
||||
{
|
||||
if constexpr (WrapUV) {
|
||||
u = wrap_uv(u, source->x);
|
||||
v = wrap_uv(v, source->y);
|
||||
}
|
||||
/* BLI_bilinear_interpolation functions use `floor(uv)` and `floor(uv)+1`
|
||||
* texels. For proper mapping between pixel and texel spaces, need to
|
||||
* subtract 0.5. Same for bicubic. */
|
||||
if constexpr (Filter == IMB_FILTER_BILINEAR || Filter == IMB_FILTER_BICUBIC) {
|
||||
u -= 0.5f;
|
||||
v -= 0.5f;
|
||||
}
|
||||
if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float> && NumChannels == 4) {
|
||||
bilinear_interpolation_color_fl(source, r_sample, u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, uchar> && NumChannels == 4)
|
||||
{
|
||||
nearest_interpolation_color_char(source, r_sample, nullptr, u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, uchar> && NumChannels == 4)
|
||||
{
|
||||
bilinear_interpolation_color_char(source, r_sample, u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float>) {
|
||||
if constexpr (WrapUV) {
|
||||
BLI_bilinear_interpolation_wrap_fl(source->float_buffer.data,
|
||||
r_sample,
|
||||
source->x,
|
||||
source->y,
|
||||
NumChannels,
|
||||
u,
|
||||
v,
|
||||
true,
|
||||
true);
|
||||
}
|
||||
else {
|
||||
BLI_bilinear_interpolation_fl(
|
||||
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
|
||||
}
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, float>) {
|
||||
sample_nearest_float<NumChannels>(source, u, v, r_sample);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, float>) {
|
||||
BLI_bicubic_interpolation_fl(
|
||||
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, uchar> && NumChannels == 4)
|
||||
{
|
||||
BLI_bicubic_interpolation_char(source->byte_buffer.data, r_sample, source->x, source->y, u, v);
|
||||
}
|
||||
else {
|
||||
/* Unsupported sampler. */
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, int SrcChannels> static void store_sample(const T *sample, T *dst)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, uchar>) {
|
||||
BLI_STATIC_ASSERT(SrcChannels == 4, "Unsigned chars always have 4 channels.");
|
||||
copy_v4_v4_uchar(dst, sample);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float> && SrcChannels == 4) {
|
||||
copy_v4_v4(dst, sample);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float> && SrcChannels == 3) {
|
||||
copy_v4_fl4(dst, sample[0], sample[1], sample[2], 1.0f);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float> && SrcChannels == 2) {
|
||||
copy_v4_fl4(dst, sample[0], sample[1], 0.0f, 1.0f);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float> && SrcChannels == 1) {
|
||||
/* Note: single channel sample is stored as grayscale. */
|
||||
copy_v4_fl4(dst, sample[0], sample[0], sample[0], 1.0f);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, int SrcChannels>
|
||||
static void mix_and_store_sample(const T *sample, T *dst, const float mix_factor)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, uchar>) {
|
||||
BLI_STATIC_ASSERT(SrcChannels == 4, "Unsigned chars always have 4 channels.");
|
||||
blend_color_interpolate_byte(dst, dst, sample, mix_factor);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float> && SrcChannels == 4) {
|
||||
blend_color_interpolate_float(dst, dst, sample, mix_factor);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/* Process a block of destination image scanlines. */
|
||||
template<eIMBInterpolationFilterMode Filter,
|
||||
typename StorageType,
|
||||
int SourceNumChannels,
|
||||
int DestinationNumChannels>
|
||||
ScanlineThreadFunc get_scanline_function(const eIMBTransformMode mode)
|
||||
|
||||
typename T,
|
||||
int SrcChannels,
|
||||
bool CropSource,
|
||||
bool WrapUV>
|
||||
static void process_scanlines(const TransformContext &ctx, IndexRange y_range)
|
||||
{
|
||||
switch (mode) {
|
||||
case IMB_TRANSFORM_MODE_REGULAR:
|
||||
return transform_scanline_function<
|
||||
ScanlineProcessor<NoDiscard,
|
||||
Sampler<Filter, StorageType, SourceNumChannels, false>,
|
||||
PixelPointer<StorageType, DestinationNumChannels>>>;
|
||||
case IMB_TRANSFORM_MODE_CROP_SRC:
|
||||
return transform_scanline_function<
|
||||
ScanlineProcessor<CropSource,
|
||||
Sampler<Filter, StorageType, SourceNumChannels, false>,
|
||||
PixelPointer<StorageType, DestinationNumChannels>>>;
|
||||
case IMB_TRANSFORM_MODE_WRAP_REPEAT:
|
||||
return transform_scanline_function<
|
||||
ScanlineProcessor<NoDiscard,
|
||||
Sampler<Filter, StorageType, SourceNumChannels, true>,
|
||||
PixelPointer<StorageType, DestinationNumChannels>>>;
|
||||
}
|
||||
/* Note: sample at pixel center for proper filtering. */
|
||||
float2 uv_start = ctx.start_uv + ctx.add_x * 0.5f + ctx.add_y * 0.5f;
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
if (ctx.subsampling_deltas.size() > 1) {
|
||||
/* Multiple samples per pixel. */
|
||||
for (int yi : y_range) {
|
||||
T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
|
||||
float2 uv_row = uv_start + yi * ctx.add_y;
|
||||
for (int xi : ctx.dst_region_x_range) {
|
||||
float2 uv = uv_row + xi * ctx.add_x;
|
||||
T sample[4] = {};
|
||||
int num_subsamples_added = 0;
|
||||
|
||||
template<eIMBInterpolationFilterMode Filter>
|
||||
ScanlineThreadFunc get_scanline_function(const TransformUserData *user_data,
|
||||
const eIMBTransformMode mode)
|
||||
{
|
||||
const ImBuf *src = user_data->src;
|
||||
const ImBuf *dst = user_data->dst;
|
||||
for (const float2 &delta_uv : ctx.subsampling_deltas) {
|
||||
const float2 sub_uv = uv + delta_uv;
|
||||
if (!CropSource || !should_discard(ctx, sub_uv)) {
|
||||
T sub_sample[4];
|
||||
sample_image<Filter, T, SrcChannels, WrapUV>(ctx.src, sub_uv.x, sub_uv.y, sub_sample);
|
||||
add_subsample<T, SrcChannels>(sub_sample, sample, num_subsamples_added);
|
||||
num_subsamples_added += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (src->channels == 4 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 4, 4>(mode);
|
||||
}
|
||||
if (src->channels == 3 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 3, 4>(mode);
|
||||
}
|
||||
if (src->channels == 2 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 2, 4>(mode);
|
||||
}
|
||||
if (src->channels == 1 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 1, 4>(mode);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<eIMBInterpolationFilterMode Filter>
|
||||
static void transform_threaded(TransformUserData *user_data, const eIMBTransformMode mode)
|
||||
{
|
||||
ScanlineThreadFunc scanline_func = nullptr;
|
||||
|
||||
if (user_data->dst->float_buffer.data && user_data->src->float_buffer.data) {
|
||||
scanline_func = get_scanline_function<Filter>(user_data, mode);
|
||||
}
|
||||
else if (user_data->dst->byte_buffer.data && user_data->src->byte_buffer.data) {
|
||||
/* Number of channels is always 4 when using uchar buffers (sRGB + straight alpha). */
|
||||
scanline_func = get_scanline_function<Filter, uchar, 4, 4>(mode);
|
||||
}
|
||||
|
||||
if (scanline_func != nullptr) {
|
||||
threading::parallel_for(user_data->destination_region.y_range, 8, [&](IndexRange range) {
|
||||
for (int scanline : range) {
|
||||
scanline_func(user_data, scanline);
|
||||
if (num_subsamples_added != 0) {
|
||||
const float mix_weight = float(num_subsamples_added) / ctx.subsampling_deltas.size();
|
||||
mix_and_store_sample<T, SrcChannels>(sample, output, mix_weight);
|
||||
}
|
||||
output += 4;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* One sample per pixel. */
|
||||
for (int yi : y_range) {
|
||||
T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
|
||||
float2 uv_row = uv_start + yi * ctx.add_y;
|
||||
for (int xi : ctx.dst_region_x_range) {
|
||||
float2 uv = uv_row + xi * ctx.add_x;
|
||||
if (!CropSource || !should_discard(ctx, uv)) {
|
||||
T sample[4];
|
||||
sample_image<Filter, T, SrcChannels, WrapUV>(ctx.src, uv.x, uv.y, sample);
|
||||
store_sample<T, SrcChannels>(sample, output);
|
||||
}
|
||||
output += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<eIMBInterpolationFilterMode Filter, typename T, int SrcChannels>
|
||||
static void transform_scanlines(const TransformContext &ctx, IndexRange y_range)
|
||||
{
|
||||
switch (ctx.mode) {
|
||||
case IMB_TRANSFORM_MODE_REGULAR:
|
||||
process_scanlines<Filter, T, SrcChannels, false, false>(ctx, y_range);
|
||||
break;
|
||||
case IMB_TRANSFORM_MODE_CROP_SRC:
|
||||
process_scanlines<Filter, T, SrcChannels, true, false>(ctx, y_range);
|
||||
break;
|
||||
case IMB_TRANSFORM_MODE_WRAP_REPEAT:
|
||||
process_scanlines<Filter, T, SrcChannels, false, true>(ctx, y_range);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<eIMBInterpolationFilterMode Filter>
|
||||
static void transform_scanlines_filter(const TransformContext &ctx, IndexRange y_range)
|
||||
{
|
||||
int channels = ctx.src->channels;
|
||||
if (ctx.dst->float_buffer.data && ctx.src->float_buffer.data) {
|
||||
/* Float images. */
|
||||
if (channels == 4) {
|
||||
transform_scanlines<Filter, float, 4>(ctx, y_range);
|
||||
}
|
||||
else if (channels == 3) {
|
||||
transform_scanlines<Filter, float, 3>(ctx, y_range);
|
||||
}
|
||||
else if (channels == 2) {
|
||||
transform_scanlines<Filter, float, 2>(ctx, y_range);
|
||||
}
|
||||
else if (channels == 1) {
|
||||
transform_scanlines<Filter, float, 1>(ctx, y_range);
|
||||
}
|
||||
}
|
||||
else if (ctx.dst->byte_buffer.data && ctx.src->byte_buffer.data) {
|
||||
/* Byte images. */
|
||||
if (channels == 4) {
|
||||
transform_scanlines<Filter, uchar, 4>(ctx, y_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -659,6 +382,7 @@ static void transform_threaded(TransformUserData *user_data, const eIMBTransform
|
|||
extern "C" {
|
||||
|
||||
using namespace blender::imbuf::transform;
|
||||
using namespace blender;
|
||||
|
||||
void IMB_transform(const ImBuf *src,
|
||||
ImBuf *dst,
|
||||
|
@ -671,25 +395,28 @@ void IMB_transform(const ImBuf *src,
|
|||
BLI_assert_msg(mode != IMB_TRANSFORM_MODE_CROP_SRC || src_crop != nullptr,
|
||||
"No source crop rect given, but crop source is requested. Or source crop rect "
|
||||
"was given, but crop source was not requested.");
|
||||
BLI_assert_msg(dst->channels == 4, "Destination image must have 4 channels.");
|
||||
|
||||
TransformUserData user_data;
|
||||
user_data.src = src;
|
||||
user_data.dst = dst;
|
||||
if (mode == IMB_TRANSFORM_MODE_CROP_SRC) {
|
||||
user_data.src_crop = *src_crop;
|
||||
TransformContext ctx;
|
||||
ctx.src = src;
|
||||
ctx.dst = dst;
|
||||
ctx.mode = mode;
|
||||
bool crop = mode == IMB_TRANSFORM_MODE_CROP_SRC;
|
||||
if (crop) {
|
||||
ctx.src_crop = *src_crop;
|
||||
}
|
||||
user_data.init(blender::float4x4(transform_matrix),
|
||||
num_subsamples,
|
||||
ELEM(mode, IMB_TRANSFORM_MODE_CROP_SRC));
|
||||
ctx.init(blender::float4x4(transform_matrix), num_subsamples, crop);
|
||||
|
||||
if (filter == IMB_FILTER_NEAREST) {
|
||||
transform_threaded<IMB_FILTER_NEAREST>(&user_data, mode);
|
||||
}
|
||||
else if (filter == IMB_FILTER_BILINEAR) {
|
||||
transform_threaded<IMB_FILTER_BILINEAR>(&user_data, mode);
|
||||
}
|
||||
else if (filter == IMB_FILTER_BICUBIC) {
|
||||
transform_threaded<IMB_FILTER_BICUBIC>(&user_data, mode);
|
||||
}
|
||||
threading::parallel_for(ctx.dst_region_y_range, 8, [&](IndexRange y_range) {
|
||||
if (filter == IMB_FILTER_NEAREST) {
|
||||
transform_scanlines_filter<IMB_FILTER_NEAREST>(ctx, y_range);
|
||||
}
|
||||
else if (filter == IMB_FILTER_BILINEAR) {
|
||||
transform_scanlines_filter<IMB_FILTER_BILINEAR>(ctx, y_range);
|
||||
}
|
||||
else if (filter == IMB_FILTER_BICUBIC) {
|
||||
transform_scanlines_filter<IMB_FILTER_BICUBIC>(ctx, y_range);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -265,6 +265,14 @@ add_blender_test(
|
|||
--run-all-tests
|
||||
)
|
||||
|
||||
add_blender_test(
|
||||
curves_extrude
|
||||
${TEST_SRC_DIR}/modeling/curves_extrude.blend
|
||||
--python ${TEST_PYTHON_DIR}/curves_extrude.py
|
||||
--
|
||||
--run-all-tests
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# MODIFIERS TESTS
|
||||
add_blender_test(
|
||||
|
@ -816,6 +824,7 @@ set(geo_node_tests
|
|||
mesh
|
||||
mesh/extrude
|
||||
mesh/split_edges
|
||||
mesh/triangulate
|
||||
points
|
||||
texture
|
||||
utilities
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
# SPDX-FileCopyrightText: 2020-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import bpy
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
||||
from modules.mesh_test import RunTest
|
||||
|
||||
|
||||
class CurvesTest(ABC):
|
||||
|
||||
def __init__(self, test_object_name, exp_object_name, test_name=None):
|
||||
self.test_object_name = test_object_name
|
||||
self.exp_object_name = exp_object_name
|
||||
self.test_object = bpy.data.objects[self.test_object_name]
|
||||
self.expected_object = bpy.data.objects[self.exp_object_name]
|
||||
self.verbose = os.getenv("BLENDER_VERBOSE") is not None
|
||||
|
||||
if test_name:
|
||||
self.test_name = test_name
|
||||
else:
|
||||
filepath = bpy.data.filepath
|
||||
self.test_name = bpy.path.display_name_from_filepath(filepath)
|
||||
self._failed_tests_list = []
|
||||
|
||||
def create_evaluated_object(self):
|
||||
"""
|
||||
Creates an evaluated object.
|
||||
"""
|
||||
bpy.context.view_layer.objects.active = self.test_object
|
||||
|
||||
# Duplicate test object.
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = self.test_object
|
||||
|
||||
self.test_object.select_set(True)
|
||||
bpy.ops.object.duplicate()
|
||||
self.evaluated_object = bpy.context.active_object
|
||||
self.evaluated_object.name = "evaluated_object"
|
||||
|
||||
@staticmethod
|
||||
def _print_result(result):
|
||||
"""
|
||||
Prints the comparison, selection and validation result.
|
||||
"""
|
||||
print("Results:")
|
||||
for key in result:
|
||||
print("{} : {}".format(key, result[key][1]))
|
||||
print()
|
||||
|
||||
def run_test(self):
|
||||
"""
|
||||
Runs a single test, runs it again if test file is updated.
|
||||
"""
|
||||
print("\nSTART {} test.".format(self.test_name))
|
||||
|
||||
self.create_evaluated_object()
|
||||
self.apply_operations()
|
||||
|
||||
result = self.compare_objects(self.evaluated_object, self.expected_object)
|
||||
|
||||
# Initializing with True to get correct resultant of result_code booleans.
|
||||
success = True
|
||||
inside_loop_flag = False
|
||||
for key in result:
|
||||
inside_loop_flag = True
|
||||
success = success and result[key][0]
|
||||
|
||||
# Check "success" is actually evaluated and is not the default True value.
|
||||
if not inside_loop_flag:
|
||||
success = False
|
||||
|
||||
if success:
|
||||
self.print_passed_test_result(result)
|
||||
# Clean up.
|
||||
if self.verbose:
|
||||
print("Cleaning up...")
|
||||
# Delete evaluated_test_object.
|
||||
bpy.ops.object.delete()
|
||||
return True
|
||||
|
||||
else:
|
||||
self.print_failed_test_result(result)
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def apply_operations(self, evaluated_test_object_name):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def compare_curves(evaluated_curves, expected_curves):
|
||||
if len(evaluated_curves.attributes.items()) != len(expected_curves.attributes.items()):
|
||||
print("Attribute count doesn't match")
|
||||
|
||||
for a_idx, attribute in evaluated_curves.attributes.items():
|
||||
expected_attribute = expected_curves.attributes[a_idx]
|
||||
|
||||
if len(attribute.data.items()) != len(expected_attribute.data.items()):
|
||||
print("Attribute data length doesn't match")
|
||||
|
||||
value_attr_name = ('vector' if attribute.data_type == 'FLOAT_VECTOR'
|
||||
or attribute.data_type == 'FLOAT2' else
|
||||
'color' if attribute.data_type == 'FLOAT_COLOR' else 'value')
|
||||
|
||||
for v_idx, attribute_value in attribute.data.items():
|
||||
if getattr(
|
||||
attribute_value,
|
||||
value_attr_name) != getattr(
|
||||
expected_attribute.data[v_idx],
|
||||
value_attr_name):
|
||||
print("Attribute '{}' values do not match".format(attribute.name))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def compare_objects(self, evaluated_object, expected_object):
|
||||
result_codes = {}
|
||||
|
||||
equal = self.compare_curves(evaluated_object.data, expected_object.data)
|
||||
|
||||
result_codes['Curves Comparison'] = (equal, evaluated_object.data)
|
||||
return result_codes
|
||||
|
||||
def print_failed_test_result(self, result):
|
||||
"""
|
||||
Print results for failed test.
|
||||
"""
|
||||
print("FAILED {} test with the following: ".format(self.test_name))
|
||||
|
||||
def print_passed_test_result(self, result):
|
||||
"""
|
||||
Print results for passing test.
|
||||
"""
|
||||
print("PASSED {} test successfully.".format(self.test_name))
|
||||
|
||||
|
||||
class CurvesOpTest(CurvesTest):
|
||||
|
||||
def __init__(self, test_name, test_object_name, exp_object_name, operators_stack):
|
||||
super().__init__(test_object_name, exp_object_name, test_name)
|
||||
self.operators_stack = operators_stack
|
||||
|
||||
def apply_operations(self):
|
||||
for operator_name in self.operators_stack:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
curves_operator = getattr(bpy.ops.curves, operator_name)
|
||||
|
||||
try:
|
||||
retval = curves_operator()
|
||||
except AttributeError:
|
||||
raise AttributeError("bpy.ops.curves has no attribute {}".format(operator_name))
|
||||
except TypeError as ex:
|
||||
raise TypeError("Incorrect operator parameters {!r} raised {!r}".format([], ex))
|
||||
|
||||
if retval != {'FINISHED'}:
|
||||
raise RuntimeError("Unexpected operator return value: {}".format(operator_name))
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
def main():
|
||||
tests = [
|
||||
CurvesOpTest("Extrude 1 Point Curve", "a_test1PointCurve", "a_test1PointCurveExpected", ['extrude']),
|
||||
CurvesOpTest("Extrude Middle Points", "b_testMiddlePoints", "b_testMiddlePointsExpected", ['extrude']),
|
||||
CurvesOpTest("Extrude End Points", "c_testEndPoints", "c_testEndPointsExpected", ['extrude']),
|
||||
CurvesOpTest("Extrude Neighbors In Separate Curves", "d_testNeighborsInCurves", "d_testNeighborsInCurvesExpected", ['extrude']),
|
||||
CurvesOpTest("Extrude Edge Curves", "e_testEdgeCurves", "e_testEdgeCurvesExpected", ['extrude']),
|
||||
CurvesOpTest("Extrude Middle Curve", "f_testMiddleCurve", "f_testMiddleCurveExpected", ['extrude']),
|
||||
CurvesOpTest("Extrude All Points", "g_testAllPoints", "g_testAllPointsExpected", ['extrude'])
|
||||
]
|
||||
|
||||
curves_extrude_test = RunTest(tests)
|
||||
|
||||
command = list(sys.argv)
|
||||
for i, cmd in enumerate(command):
|
||||
if cmd == "--run-all-tests":
|
||||
curves_extrude_test.do_compare = True
|
||||
curves_extrude_test.run_all_tests()
|
||||
break
|
||||
elif cmd == "--run-test":
|
||||
curves_extrude_test.do_compare = False
|
||||
name = command[i + 1]
|
||||
curves_extrude_test.run_test(name)
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue