Geometry Nodes: reorganize panels in modifier #117170

Merged
Jacques Lucke merged 6 commits from JacquesLucke/blender:panel-reorganization into main 2024-01-17 13:40:38 +01:00
49 changed files with 2279 additions and 816 deletions
Showing only changes of commit f3391b4365 - Show all commits

View File

@ -21,7 +21,8 @@
/* If JEMALLOC is used, it reads this global variable and enables background
* threads to purge dirty pages. Otherwise we release memory too slowly or not
* at all if the thread that did the allocation stays inactive. */
const char *malloc_conf = "background_thread:true,dirty_decay_ms:4000";
const char *malloc_conf =
"background_thread:true,dirty_decay_ms:4000,thp:always,metadata_thp:always";
#endif
/* NOTE: Keep in sync with MEM_use_lockfree_allocator(). */

View File

@ -373,8 +373,32 @@ def enable(module_name, *, default_set=False, persistent=False, handle_error=Non
# If the add-on doesn't exist, don't print full trace-back because the back-trace is in this case
# is verbose without any useful details. A missing path is better communicated in a short message.
# Account for `ImportError` & `ModuleNotFoundError`.
if isinstance(ex, ImportError) and ex.name == module_name:
print("Add-on not loaded:", repr(module_name), "cause:", str(ex))
if isinstance(ex, ImportError):
if ex.name == module_name:
print("Add-on not loaded: \"%s\", cause: %s" % (module_name, str(ex)))
# Issue with an add-on from an extension repository, report a useful message.
elif module_name.startswith(ex.name + ".") and module_name.startswith(_ext_base_pkg_idname + "."):
repo_id = module_name[len(_ext_base_pkg_idname) + 1:].rpartition(".")[0]
repo = next(
(repo for repo in _preferences.filepaths.extension_repos if repo.module == repo_id),
None,
)
if repo is None:
print(
"Add-on not loaded: \"%s\", cause: extension repository \"%s\" doesn't exist" %
(module_name, repo_id)
)
elif not repo.enabled:
print(
"Add-on not loaded: \"%s\", cause: extension repository \"%s\" is disabled" %
(module_name, repo_id)
)
else:
# The repository exists and is enabled, it should have imported.
print("Add-on not loaded: \"%s\", cause: %s" % (module_name, str(ex)))
else:
handle_error(ex)
else:
handle_error(ex)

View File

@ -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'}:

View File

@ -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},

View File

@ -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')]}),

View File

@ -71,7 +71,7 @@ class OBJECT_MT_modifier_add(ModifierAddMenu, Menu):
if geometry_nodes_supported:
self.operator_modifier_add(layout, 'NODES')
layout.separator()
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}:
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE', 'GREASEPENCIL'}:
layout.menu("OBJECT_MT_modifier_add_edit")
if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'VOLUME'}:
layout.menu("OBJECT_MT_modifier_add_generate")
@ -105,6 +105,8 @@ class OBJECT_MT_modifier_add_edit(ModifierAddMenu, Menu):
self.operator_modifier_add(layout, 'VERTEX_WEIGHT_EDIT')
self.operator_modifier_add(layout, 'VERTEX_WEIGHT_MIX')
self.operator_modifier_add(layout, 'VERTEX_WEIGHT_PROXIMITY')
if ob_type == 'GREASEPENCIL':
self.operator_modifier_add(layout, 'GREASE_PENCIL_OPACITY')
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)

View File

@ -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):

View File

@ -375,7 +375,11 @@ enum {
BLF_BAD_FONT = 1 << 16,
/** This font is managed by the FreeType cache subsystem. */
BLF_CACHED = 1 << 17,
/** At small sizes glyphs are rendered at multiple sub-pixel positions. */
/**
* At small sizes glyphs are rendered at multiple sub-pixel positions.
*
* \note Can be checked without checking #BLF_MONOSPACED which can be assumed to be disabled.
*/
BLF_RENDER_SUBPIXELAA = 1 << 18,
};

View File

@ -1322,7 +1322,7 @@ GlyphBLF *blf_glyph_ensure(FontBLF *font, GlyphCacheBLF *gc, const uint charcode
#ifdef BLF_SUBPIXEL_AA
GlyphBLF *blf_glyph_ensure_subpixel(FontBLF *font, GlyphCacheBLF *gc, GlyphBLF *g, int32_t pen_x)
{
if (!(font->flags & BLF_RENDER_SUBPIXELAA) || (font->flags & BLF_MONOCHROME)) {
if (!(font->flags & BLF_RENDER_SUBPIXELAA)) {
/* Not if we are in mono mode (aliased) or the feature is turned off. */
return g;
}

View File

@ -813,7 +813,7 @@ bool BKE_image_render_write_exr(ReportList *reports,
/* We only store RGBA passes as half float, for
* others precision loss can be problematic. */
const bool pass_RGBA = STR_ELEM(rp->chan_id, "RGB", "RGBA", "R", "G", "B", "A");
const bool pass_RGBA = RE_RenderPassIsColor(rp);
const bool pass_half_float = half_float && pass_RGBA;
/* Color-space conversion only happens on RGBA passes. */

View File

@ -608,7 +608,8 @@ bool bNodeTree::node_id_path_from_nested_node_ref(const int32_t nested_node_id,
return group->node_id_path_from_nested_node_ref(ref->path.id_in_node, r_node_ids);
}
const bNode *bNodeTree::find_nested_node(const int32_t nested_node_id) const
const bNode *bNodeTree::find_nested_node(const int32_t nested_node_id,
const bNodeTree **r_tree) const
{
const bNestedNodeRef *ref = this->find_nested_node_ref(nested_node_id);
if (ref == nullptr) {
@ -620,11 +621,14 @@ const bNode *bNodeTree::find_nested_node(const int32_t nested_node_id) const
return nullptr;
}
if (!node->is_group()) {
if (r_tree) {
*r_tree = this;
}
return node;
}
const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id);
if (group == nullptr) {
return nullptr;
}
return group->find_nested_node(ref->path.id_in_node);
return group->find_nested_node(ref->path.id_in_node, r_tree);
}

View File

@ -11,8 +11,8 @@
#include "BLI_filereader.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
struct GHash;
struct Main;
struct Scene;
@ -45,7 +45,7 @@ struct MemFileWriteData {
MemFileChunk *reference_current_chunk;
/** Maps an ID session uuid to its first reference MemFileChunk, if existing. */
GHash *id_session_uuid_mapping;
blender::Map<uint, MemFileChunk *> id_session_uuid_mapping;
};
struct MemFileUndoData {
@ -80,25 +80,25 @@ void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t s
*/
/* **************** support for memory-write, for undo buffers *************** */
extern void BLO_memfile_free(MemFile *memfile);
void BLO_memfile_free(MemFile *memfile);
/**
* Result is that 'first' is being freed.
* To keep the #MemFile linked list of consistent, `first` is always first in list.
*/
extern void BLO_memfile_merge(MemFile *first, MemFile *second);
void BLO_memfile_merge(MemFile *first, MemFile *second);
/**
* Clear is_identical_future before adding next memfile.
*/
extern void BLO_memfile_clear_future(MemFile *memfile);
void BLO_memfile_clear_future(MemFile *memfile);
/* Utilities. */
extern Main *BLO_memfile_main_get(MemFile *memfile, Main *bmain, Scene **r_scene);
Main *BLO_memfile_main_get(MemFile *memfile, Main *bmain, Scene **r_scene);
/**
* Saves .blend using undo buffer.
*
* \return success.
*/
extern bool BLO_memfile_write_file(MemFile *memfile, const char *filepath);
bool BLO_memfile_write_file(MemFile *memfile, const char *filepath);
FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction);

View File

@ -25,7 +25,6 @@
#include "DNA_listBase.h"
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLO_readfile.h"
#include "BLO_undofile.hh"
@ -54,27 +53,20 @@ void BLO_memfile_merge(MemFile *first, MemFile *second)
{
/* We use this mapping to store the memory buffers from second memfile chunks which are not owned
* by it (i.e. shared with some previous memory steps). */
GHash *buffer_to_second_memchunk = BLI_ghash_new(
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
blender::Map<const char *, MemFileChunk *> buffer_to_second_memchunk;
/* First, detect all memchunks in second memfile that are not owned by it. */
for (MemFileChunk *sc = static_cast<MemFileChunk *>(second->chunks.first); sc != nullptr;
sc = static_cast<MemFileChunk *>(sc->next))
{
LISTBASE_FOREACH (MemFileChunk *, sc, &second->chunks) {
if (sc->is_identical) {
BLI_ghash_insert(buffer_to_second_memchunk, (void *)sc->buf, sc);
buffer_to_second_memchunk.add(sc->buf, sc);
}
}
/* Now, check all chunks from first memfile (the one we are removing), and if a memchunk owned by
* it is also used by the second memfile, transfer the ownership. */
for (MemFileChunk *fc = static_cast<MemFileChunk *>(first->chunks.first); fc != nullptr;
fc = static_cast<MemFileChunk *>(fc->next))
{
LISTBASE_FOREACH (MemFileChunk *, fc, &first->chunks) {
if (!fc->is_identical) {
MemFileChunk *sc = static_cast<MemFileChunk *>(
BLI_ghash_lookup(buffer_to_second_memchunk, fc->buf));
if (sc != nullptr) {
if (MemFileChunk *sc = buffer_to_second_memchunk.lookup_default(fc->buf, nullptr)) {
BLI_assert(sc->is_identical);
sc->is_identical = false;
fc->is_identical = true;
@ -85,8 +77,6 @@ void BLO_memfile_merge(MemFile *first, MemFile *second)
}
}
BLI_ghash_free(buffer_to_second_memchunk, nullptr, nullptr);
BLO_memfile_free(first);
}
@ -113,22 +103,11 @@ void BLO_memfile_write_init(MemFileWriteData *mem_data,
* current Main data-base broke the order matching with the memchunks from previous step.
*/
if (reference_memfile != nullptr) {
mem_data->id_session_uuid_mapping = BLI_ghash_new(
BLI_ghashutil_inthash_p_simple, BLI_ghashutil_intcmp, __func__);
uint current_session_uuid = MAIN_ID_SESSION_UUID_UNSET;
LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &reference_memfile->chunks) {
if (!ELEM(mem_chunk->id_session_uuid, MAIN_ID_SESSION_UUID_UNSET, current_session_uuid)) {
current_session_uuid = mem_chunk->id_session_uuid;
void **entry;
if (!BLI_ghash_ensure_p(mem_data->id_session_uuid_mapping,
POINTER_FROM_UINT(current_session_uuid),
&entry))
{
*entry = mem_chunk;
}
else {
BLI_assert_unreachable();
}
mem_data->id_session_uuid_mapping.add_new(current_session_uuid, mem_chunk);
}
}
}
@ -136,9 +115,7 @@ void BLO_memfile_write_init(MemFileWriteData *mem_data,
void BLO_memfile_write_finalize(MemFileWriteData *mem_data)
{
if (mem_data->id_session_uuid_mapping != nullptr) {
BLI_ghash_free(mem_data->id_session_uuid_mapping, nullptr, nullptr);
}
mem_data->id_session_uuid_mapping.clear_and_shrink();
}
void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)

View File

@ -435,7 +435,7 @@ struct BlendWriter {
static WriteData *writedata_new(WriteWrap *ww)
{
WriteData *wd = static_cast<WriteData *>(MEM_callocN(sizeof(*wd), "writedata"));
WriteData *wd = MEM_new<WriteData>(__func__);
wd->sdna = DNA_sdna_current_get();
@ -487,7 +487,7 @@ static void writedata_free(WriteData *wd)
if (wd->buffer.buf) {
MEM_freeN(wd->buffer.buf);
}
MEM_freeN(wd);
MEM_delete(wd);
}
/** \} */
@ -620,14 +620,13 @@ static void mywrite_id_begin(WriteData *wd, ID *id)
MemFileChunk *prev_memchunk = curr_memchunk != nullptr ?
static_cast<MemFileChunk *>(curr_memchunk->prev) :
nullptr;
if (wd->mem.id_session_uuid_mapping != nullptr &&
(curr_memchunk == nullptr || curr_memchunk->id_session_uuid != id->session_uuid ||
if ((curr_memchunk == nullptr || curr_memchunk->id_session_uuid != id->session_uuid ||
(prev_memchunk != nullptr &&
(prev_memchunk->id_session_uuid == curr_memchunk->id_session_uuid))))
{
void *ref = BLI_ghash_lookup(wd->mem.id_session_uuid_mapping,
POINTER_FROM_UINT(id->session_uuid));
if (ref != nullptr) {
if (MemFileChunk *ref = wd->mem.id_session_uuid_mapping.lookup_default(id->session_uuid,
nullptr))
{
wd->mem.reference_current_chunk = static_cast<MemFileChunk *>(ref);
}
/* Else, no existing memchunk found, i.e. this is supposed to be a new ID. */

View File

@ -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,

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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];

View File

@ -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;
}

View File

@ -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;
}

View File

@ -460,8 +460,10 @@ void uiStyleInit()
if (U.text_render & USER_TEXT_DISABLE_AA) {
flag_enable |= BLF_MONOCHROME;
}
if (U.text_render & USER_TEXT_RENDER_SUBPIXELAA) {
flag_enable |= BLF_RENDER_SUBPIXELAA;
else {
if (U.text_render & USER_TEXT_RENDER_SUBPIXELAA) {
flag_enable |= BLF_RENDER_SUBPIXELAA;
}
}
LISTBASE_FOREACH (uiFont *, font, &U.uifonts) {

View File

@ -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;

View File

@ -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))
{

View File

@ -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
)

View File

@ -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

View File

@ -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` */

View File

@ -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);

View File

@ -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);
}
/** \} */

View File

@ -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;

View File

@ -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,235 @@ 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<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);
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.");
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();
}
}
static void add_subsample(const float src[4], float dst[4])
{
add_v4_v4(dst, src);
}
static void add_subsample(const uchar src[4], float dst[4])
{
float premul[4];
straight_uchar_to_premul_float(premul, src);
add_v4_v4(dst, premul);
}
static void store_premul_float_sample(const float sample[4], float dst[4])
{
copy_v4_v4(dst, sample);
}
static void store_premul_float_sample(const float sample[4], uchar dst[4])
{
premul_float_to_straight_uchar(dst, sample);
}
template<int SrcChannels> static void store_sample(const uchar *sample, uchar *dst)
{
BLI_STATIC_ASSERT(SrcChannels == 4, "Unsigned chars always have 4 channels.");
copy_v4_v4_uchar(dst, sample);
}
template<int SrcChannels> static void store_sample(const float *sample, float *dst)
{
if constexpr (SrcChannels == 4) {
copy_v4_v4(dst, sample);
}
else if constexpr (SrcChannels == 3) {
copy_v4_fl4(dst, sample[0], sample[1], sample[2], 1.0f);
}
else if constexpr (SrcChannels == 2) {
copy_v4_fl4(dst, sample[0], sample[1], 0.0f, 1.0f);
}
else if constexpr (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();
}
}
/* 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: accumulate them premultiplied,
* divide by sample count and write out (un-premultiplying if writing out
* to byte image). */
const float inv_count = 1.0f / ctx.subsampling_deltas.size();
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;
float sample[4] = {};
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(sub_sample, sample);
}
}
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;
}
mul_v4_v4fl(sample, sample, inv_count);
store_premul_float_sample(sample, output);
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);
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<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 +371,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 +384,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);
}
});
}
}

View File

@ -71,9 +71,9 @@ TEST(imbuf_transform, nearest_subsample3_2x_smaller)
{
ImBuf *res = transform_2x_smaller(IMB_FILTER_NEAREST, 3);
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
EXPECT_EQ(got[0], ColorTheme4b(226, 168, 113, 255));
EXPECT_EQ(got[1], ColorTheme4b(133, 55, 31, 16));
EXPECT_EQ(got[2], ColorTheme4b(55, 22, 64, 254));
EXPECT_EQ(got[0], ColorTheme4b(227, 170, 113, 255));
EXPECT_EQ(got[1], ColorTheme4b(133, 55, 31, 17));
EXPECT_EQ(got[2], ColorTheme4b(56, 22, 64, 253));
IMB_freeImBuf(res);
}

View File

@ -801,4 +801,11 @@
.mat_ofs = 0, \
}
#define _DNA_DEFAULT_GreasePencilOpacityModifierData \
{ \
.color_mode = MOD_GREASE_PENCIL_COLOR_BOTH, \
.color_factor = 1.0f, \
.hardness_factor = 1.0f, \
}
/* clang-format off */

View File

@ -93,6 +93,7 @@ typedef enum ModifierType {
eModifierType_MeshToVolume = 58,
eModifierType_VolumeDisplace = 59,
eModifierType_VolumeToMesh = 60,
eModifierType_GreasePencilOpacity = 61,
NUM_MODIFIER_TYPES,
} ModifierType;
@ -2484,3 +2485,65 @@ typedef enum VolumeToMeshResolutionMode {
typedef enum VolumeToMeshFlag {
VOLUME_TO_MESH_USE_SMOOTH_SHADE = 1 << 0,
} VolumeToMeshFlag;
/**
* Common influence data for grease pencil modifiers.
* Not all parts may be used by all modifier types.
*/
typedef struct GreasePencilModifierInfluenceData {
/** GreasePencilModifierInfluenceFlag */
int flag;
char _pad1[4];
/** Filter by layer name. */
char layer_name[64];
/** Filter by stroke material. */
struct Material *material;
/** Filter by layer pass. */
int layer_pass;
/** Filter by material pass. */
int material_pass;
/** #MAX_VGROUP_NAME. */
char vertex_group_name[64];
struct CurveMapping *custom_curve;
void *_pad2;
} GreasePencilModifierInfluenceData;
typedef enum GreasePencilModifierInfluenceFlag {
GREASE_PENCIL_INFLUENCE_INVERT_LAYER_FILTER = (1 << 0),
GREASE_PENCIL_INFLUENCE_USE_LAYER_PASS_FILTER = (1 << 1),
GREASE_PENCIL_INFLUENCE_INVERT_LAYER_PASS_FILTER = (1 << 2),
GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_FILTER = (1 << 3),
GREASE_PENCIL_INFLUENCE_USE_MATERIAL_PASS_FILTER = (1 << 4),
GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_PASS_FILTER = (1 << 5),
GREASE_PENCIL_INFLUENCE_INVERT_VERTEX_GROUP = (1 << 6),
GREASE_PENCIL_INFLUENCE_USE_CUSTOM_CURVE = (1 << 7),
} GreasePencilModifierInfluenceFlag;
typedef struct GreasePencilOpacityModifierData {
ModifierData modifier;
GreasePencilModifierInfluenceData influence;
/** GreasePencilOpacityModifierFlag */
int flag;
/** GreasePencilModifierColorMode */
char color_mode;
char _pad1[3];
float color_factor;
float hardness_factor;
void *_pad2;
} GreasePencilOpacityModifierData;
/** Which attributes are affected by color modifiers. */
typedef enum GreasePencilModifierColorMode {
MOD_GREASE_PENCIL_COLOR_STROKE = 0,
MOD_GREASE_PENCIL_COLOR_FILL = 1,
MOD_GREASE_PENCIL_COLOR_BOTH = 2,
MOD_GREASE_PENCIL_COLOR_HARDNESS = 3,
} GreasePencilModifierColorMode;
typedef enum GreasePencilOpacityModifierFlag {
MOD_GREASE_PENCIL_OPACITY_OPEN_INFLUENCE_PANEL = (1 << 0),
/* Use vertex group as opacity factors instead of influence. */
MOD_GREASE_PENCIL_OPACITY_USE_WEIGHT_AS_FACTOR = (1 << 1),
/* Set the opacity for every point in a stroke, otherwise multiply existing opacity. */
MOD_GREASE_PENCIL_OPACITY_USE_UNIFORM_OPACITY = (1 << 2),
} GreasePencilOpacityModifierFlag;

View File

@ -728,7 +728,7 @@ typedef struct bNodeTree {
const bNestedNodeRef *nested_node_ref_from_node_id_path(blender::Span<int> node_ids) const;
[[nodiscard]] bool node_id_path_from_nested_node_ref(const int32_t nested_node_id,
blender::Vector<int32_t> &r_node_ids) const;
const bNode *find_nested_node(int32_t nested_node_id) const;
const bNode *find_nested_node(int32_t nested_node_id, const bNodeTree **r_tree = nullptr) const;
/**
* Update a run-time cache for the node tree based on its current state. This makes many methods

View File

@ -322,6 +322,7 @@ SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierSegment);
SDNA_DEFAULT_DECL_STRUCT(ShrinkwrapGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(EnvelopeGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilOpacityModifierData);
#undef SDNA_DEFAULT_DECL_STRUCT
@ -566,6 +567,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(DashGpencilModifierSegment),
SDNA_DEFAULT_DECL(ShrinkwrapGpencilModifierData),
SDNA_DEFAULT_DECL(EnvelopeGpencilModifierData),
SDNA_DEFAULT_DECL(GreasePencilOpacityModifierData),
};
#undef SDNA_DEFAULT_DECL
#undef SDNA_DEFAULT_DECL_EX

View File

@ -100,6 +100,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
ICON_MOD_VERTEX_WEIGHT,
"Vertex Weight Proximity",
"Set the vertex group weights based on the distance to another target object"},
{eModifierType_GreasePencilOpacity,
"GREASE_PENCIL_OPACITY",
ICON_MOD_OPACITY,
"Opacity",
"Change the opacity of the strokes"},
RNA_ENUM_ITEM_HEADING(N_("Generate"), nullptr),
{eModifierType_Array,
@ -668,11 +673,13 @@ const EnumPropertyItem rna_enum_subdivision_boundary_smooth_items[] = {
#ifdef RNA_RUNTIME
# include "DNA_curve_types.h"
# include "DNA_fluid_types.h"
# include "DNA_material_types.h"
# include "DNA_particle_types.h"
# include "BKE_cachefile.h"
# include "BKE_context.hh"
# include "BKE_deform.h"
# include "BKE_material.h"
# include "BKE_mesh_runtime.hh"
# include "BKE_modifier.hh"
# include "BKE_object.hh"
@ -1705,6 +1712,111 @@ static IDProperty **rna_NodesModifier_properties(PointerRNA *ptr)
NodesModifierSettings *settings = &nmd->settings;
return &settings->properties;
}
static const NodesModifierData *find_nodes_modifier_by_bake(const Object &object,
const NodesModifierBake &bake)
{
LISTBASE_FOREACH (const ModifierData *, md, &object.modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
}
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
const blender::Span<NodesModifierBake> bakes{nmd->bakes, nmd->bakes_num};
if (bakes.contains_ptr(&bake)) {
return nmd;
}
}
return nullptr;
}
static PointerRNA rna_NodesModifierBake_node_get(PointerRNA *ptr)
{
const Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
const NodesModifierBake *bake = static_cast<NodesModifierBake *>(ptr->data);
const NodesModifierData *nmd = find_nodes_modifier_by_bake(*ob, *bake);
if (!nmd->node_group) {
return PointerRNA_NULL;
}
const bNodeTree *tree;
const bNode *node = nmd->node_group->find_nested_node(bake->id, &tree);
if (!node) {
return PointerRNA_NULL;
}
BLI_assert(tree != nullptr);
return RNA_pointer_create(const_cast<ID *>(&tree->id), &RNA_Node, const_cast<bNode *>(node));
}
bool rna_GreasePencilModifier_material_poll(PointerRNA *ptr, PointerRNA value)
{
Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
Material *ma = reinterpret_cast<Material *>(value.owner_id);
return BKE_object_material_index_get(ob, ma) != -1;
}
/* Write material to a generic target pointer without the final modifier struct. */
static void rna_GreasePencilModifier_material_set(PointerRNA *ptr,
PointerRNA value,
ReportList *reports,
Material **ma_target)
{
Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
Material *ma = reinterpret_cast<Material *>(value.owner_id);
if (ma == nullptr || BKE_object_material_index_get(ob, ma) != -1) {
id_lib_extern(reinterpret_cast<ID *>(ob));
*ma_target = ma;
}
else {
BKE_reportf(
reports,
RPT_ERROR,
"Cannot assign material '%s', it has to be used by the grease pencil object already",
ma->id.name);
}
}
# define RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(_type) \
static void rna_##_type##Modifier_material_filter_set( \
PointerRNA *ptr, PointerRNA value, ReportList *reports) \
{ \
_type##ModifierData *tmd = static_cast<_type##ModifierData *>(ptr->data); \
rna_GreasePencilModifier_material_set(ptr, value, reports, &tmd->influence.material); \
}
# define RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(_type) \
static void rna_##_type##Modifier_vertex_group_name_set(PointerRNA *ptr, const char *value) \
{ \
_type##ModifierData *tmd = static_cast<_type##ModifierData *>(ptr->data); \
rna_object_vgroup_name_set(ptr, \
value, \
tmd->influence.vertex_group_name, \
sizeof(tmd->influence.vertex_group_name)); \
}
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilOpacity);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOpacity);
static void rna_GreasePencilOpacityModifier_opacity_factor_range(
PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax)
{
GreasePencilOpacityModifierData *omd = static_cast<GreasePencilOpacityModifierData *>(ptr->data);
*min = 0.0f;
*softmin = 0.0f;
*softmax = (omd->flag & MOD_GREASE_PENCIL_OPACITY_USE_UNIFORM_OPACITY) ? 1.0f : 2.0f;
*max = *softmax;
}
static void rna_GreasePencilOpacityModifier_opacity_factor_max_set(PointerRNA *ptr, float value)
{
GreasePencilOpacityModifierData *omd = static_cast<GreasePencilOpacityModifierData *>(ptr->data);
omd->color_factor = (omd->flag & MOD_GREASE_PENCIL_OPACITY_USE_UNIFORM_OPACITY) ?
std::min(value, 1.0f) :
value;
}
#else
static void rna_def_modifier_panel_open_prop(StructRNA *srna, const char *identifier, const int id)
@ -7118,6 +7230,24 @@ static void rna_def_modifier_nodes_bake(BlenderRNA *brna)
RNA_def_property_enum_items(prop, bake_mode_items);
RNA_def_property_ui_text(prop, "Bake Mode", "");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "bake_id", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(prop,
"Bake ID",
"Identifier for this bake which remains unchanged even when the bake "
"node is renamed, grouped or ungrouped");
RNA_def_property_int_sdna(prop, nullptr, "id");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "node", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "Node");
RNA_def_property_ui_text(prop,
"Node",
"Bake node or simulation output node that corresponds to this bake. "
"This node may be deeply nested in the modifier node group. It can be "
"none in some cases like missing linked data blocks");
RNA_def_property_pointer_funcs(
prop, "rna_NodesModifierBake_node_get", nullptr, nullptr, nullptr);
}
static void rna_def_modifier_nodes_bakes(BlenderRNA *brna)
@ -7424,6 +7554,186 @@ static void rna_def_modifier_volume_to_mesh(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_modifier_grease_pencil_layer_filter(StructRNA *srna)
{
PropertyRNA *prop;
prop = RNA_def_property(srna, "layer_filter", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, nullptr, "influence.layer_name");
RNA_def_property_ui_text(prop, "Layer", "Layer name");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_layer_pass_filter", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_USE_LAYER_PASS_FILTER);
RNA_def_property_ui_text(prop, "Use Layer Pass", "Use layer pass filter");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "layer_pass_filter", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "influence.layer_pass");
RNA_def_property_range(prop, 0, 100);
RNA_def_property_ui_text(prop, "Layer Pass", "Layer pass filter");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "invert_layer_filter", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_INVERT_LAYER_FILTER);
RNA_def_property_ui_text(prop, "Invert Layer", "Invert layer filter");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "invert_layer_pass_filter", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_INVERT_LAYER_PASS_FILTER);
RNA_def_property_ui_text(prop, "Invert Layer Pass", "Invert layer pass filter");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
}
static void rna_def_modifier_grease_pencil_material_filter(StructRNA *srna,
const char *material_set_fn)
{
PropertyRNA *prop;
prop = RNA_def_property(srna, "material_filter", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "influence.material");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(
prop, nullptr, material_set_fn, nullptr, "rna_GreasePencilModifier_material_poll");
RNA_def_property_ui_text(prop, "Material", "Material used for filtering");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_material_pass_filter", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_USE_MATERIAL_PASS_FILTER);
RNA_def_property_ui_text(prop, "Use Material Pass", "Use material pass filter");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "material_pass_filter", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "influence.material_pass");
RNA_def_property_range(prop, 0, 100);
RNA_def_property_ui_text(prop, "Material Pass", "Material pass");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "invert_material_filter", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_FILTER);
RNA_def_property_ui_text(prop, "Invert Material", "Invert material filter");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "invert_material_pass_filter", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_PASS_FILTER);
RNA_def_property_ui_text(prop, "Invert Material Pass", "Invert material pass filter");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
}
static void rna_def_modifier_grease_pencil_vertex_group(StructRNA *srna,
const char *vertex_group_name_set_fn)
{
PropertyRNA *prop;
prop = RNA_def_property(srna, "vertex_group_name", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, nullptr, "influence.vertex_group_name");
RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modulating the deform");
RNA_def_property_string_funcs(prop, nullptr, nullptr, vertex_group_name_set_fn);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "invert_vertex_group", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_INVERT_VERTEX_GROUP);
RNA_def_property_ui_text(prop, "Invert Vertex Group", "Invert vertex group weights");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
}
static void rna_def_modifier_grease_pencil_custom_curve(StructRNA *srna)
{
PropertyRNA *prop;
prop = RNA_def_property(srna, "use_custom_curve", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "influence.flag", GREASE_PENCIL_INFLUENCE_USE_CUSTOM_CURVE);
RNA_def_property_ui_text(
prop, "Use Custom Curve", "Use a custom curve to define a factor along the strokes");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "custom_curve", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "influence.custom_curve");
RNA_def_property_ui_text(prop, "Curve", "Custom curve to apply effect");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
}
static void rna_def_modifier_grease_pencil_opacity(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
static const EnumPropertyItem color_mode_items[] = {
{MOD_GREASE_PENCIL_COLOR_BOTH, "BOTH", 0, "Stroke & Fill", "Modify fill and stroke colors"},
{MOD_GREASE_PENCIL_COLOR_STROKE, "STROKE", 0, "Stroke", "Modify stroke color only"},
{MOD_GREASE_PENCIL_COLOR_FILL, "FILL", 0, "Fill", "Modify fill color only"},
{MOD_GREASE_PENCIL_COLOR_HARDNESS, "HARDNESS", 0, "Hardness", "Modify stroke hardness"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "GreasePencilOpacityModifier", "Modifier");
RNA_def_struct_ui_text(srna, "Grease Pencil Opacity Modifier", "");
RNA_def_struct_sdna(srna, "GreasePencilOpacityModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_OPACITY);
rna_def_modifier_grease_pencil_layer_filter(srna);
rna_def_modifier_grease_pencil_material_filter(
srna, "rna_GreasePencilOpacityModifier_material_filter_set");
rna_def_modifier_grease_pencil_vertex_group(
srna, "rna_GreasePencilOpacityModifier_vertex_group_name_set");
rna_def_modifier_grease_pencil_custom_curve(srna);
prop = RNA_def_property(srna, "open_influence_panel", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "flag", MOD_GREASE_PENCIL_OPACITY_OPEN_INFLUENCE_PANEL);
RNA_def_property_ui_text(prop, "Open Influence Panel", "Open the influence panel");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, nullptr);
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "color_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, color_mode_items);
RNA_def_property_ui_text(prop, "Mode", "Attributes to modify");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "color_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "color_factor");
RNA_def_property_ui_range(prop, 0, 2.0, 0.1, 2);
RNA_def_property_float_funcs(prop,
nullptr,
"rna_GreasePencilOpacityModifier_opacity_factor_max_set",
"rna_GreasePencilOpacityModifier_opacity_factor_range");
RNA_def_property_ui_text(prop, "Opacity Factor", "Factor of opacity");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "hardness_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "hardness_factor");
RNA_def_property_range(prop, 0.0, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0, FLT_MAX, 0.1, 2);
RNA_def_property_ui_text(prop, "Hardness Factor", "Factor of stroke hardness");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_weight_as_factor", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "flag", MOD_GREASE_PENCIL_OPACITY_USE_WEIGHT_AS_FACTOR);
RNA_def_property_ui_text(
prop, "Use Weight as Factor", "Use vertex group weight as factor instead of influence");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_uniform_opacity", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "flag", MOD_GREASE_PENCIL_OPACITY_USE_UNIFORM_OPACITY);
RNA_def_property_ui_text(
prop, "Uniform Opacity", "Replace the stroke opacity instead of modulating each point");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
RNA_define_lib_overridable(false);
}
void RNA_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@ -7584,6 +7894,7 @@ void RNA_def_modifier(BlenderRNA *brna)
rna_def_modifier_mesh_to_volume(brna);
rna_def_modifier_volume_displace(brna);
rna_def_modifier_volume_to_mesh(brna);
rna_def_modifier_grease_pencil_opacity(brna);
}
#endif

View File

@ -782,6 +782,7 @@ static uiLayout *rna_uiLayoutColumnWithHeading(
struct uiLayout *rna_uiLayoutPanelProp(uiLayout *layout,
bContext *C,
ReportList *reports,
PointerRNA *data,
const char *property,
const char *text,
@ -789,11 +790,17 @@ struct uiLayout *rna_uiLayoutPanelProp(uiLayout *layout,
const bool translate)
{
text = rna_translate_ui_text(text, text_ctxt, nullptr, nullptr, translate);
Panel *panel = uiLayoutGetRootPanel(layout);
if (panel == nullptr) {
BKE_reportf(reports, RPT_ERROR, "Layout panels can not be used in this context");
return nullptr;
}
return uiLayoutPanel(C, layout, text, data, property);
}
struct uiLayout *rna_uiLayoutPanel(uiLayout *layout,
bContext *C,
ReportList *reports,
const char *idname,
const char *text,
const char *text_ctxt,
@ -802,6 +809,10 @@ struct uiLayout *rna_uiLayoutPanel(uiLayout *layout,
{
text = RNA_translate_ui_text(text, text_ctxt, nullptr, nullptr, translate);
Panel *panel = uiLayoutGetRootPanel(layout);
if (panel == nullptr) {
BKE_reportf(reports, RPT_ERROR, "Layout panels can not be used in this context");
return nullptr;
}
LayoutPanelState *state = BKE_panel_layout_panel_state_ensure(panel, idname, default_closed);
PointerRNA state_ptr = RNA_pointer_create(nullptr, &RNA_LayoutPanelState, state);
return uiLayoutPanel(C, layout, text, &state_ptr, "is_open");
@ -1079,7 +1090,7 @@ void RNA_api_ui_layout(StructRNA *srna)
RNA_def_function_ui_description(func,
"Creates a collapsable panel. Whether it is open or closed is "
"stored in the region using the given idname");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
parm = RNA_def_string(func, "idname", nullptr, 0, "", "Identifier of the panel");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
api_ui_item_common_text(func);
@ -1102,7 +1113,7 @@ void RNA_api_ui_layout(StructRNA *srna)
"region, it is stored in the provided boolean property. This should be used when multiple "
"instances of the same panel can exist. For example one for every item in a collection "
"property or list");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
parm = RNA_def_pointer(
func, "data", "AnyType", "", "Data from which to take the open-state property");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);

View File

@ -44,6 +44,8 @@ set(SRC
intern/MOD_edgesplit.cc
intern/MOD_explode.cc
intern/MOD_fluid.cc
intern/MOD_grease_pencil_opacity.cc
intern/MOD_grease_pencil_util.cc
intern/MOD_hook.cc
intern/MOD_laplaciandeform.cc
intern/MOD_laplaciansmooth.cc
@ -97,6 +99,7 @@ set(SRC
MOD_modifiertypes.hh
MOD_nodes.hh
intern/MOD_grease_pencil_util.hh
intern/MOD_meshcache_util.hh
intern/MOD_solidify_util.hh
intern/MOD_ui_common.hh

View File

@ -73,6 +73,7 @@ extern ModifierTypeInfo modifierType_Nodes;
extern ModifierTypeInfo modifierType_MeshToVolume;
extern ModifierTypeInfo modifierType_VolumeDisplace;
extern ModifierTypeInfo modifierType_VolumeToMesh;
extern ModifierTypeInfo modifierType_GreasePencilOpacity;
/* MOD_util.cc */

View File

@ -0,0 +1,339 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#include "MEM_guardedalloc.h"
#include "DNA_defaults.h"
#include "DNA_modifier_types.h"
#include "DNA_scene_types.h"
#include "BKE_colortools.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_modifier.hh"
#include "BLO_read_write.hh"
#include "DEG_depsgraph_query.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BLT_translation.h"
#include "WM_types.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.h"
#include "MOD_grease_pencil_util.hh"
#include "MOD_modifiertypes.hh"
#include "MOD_ui_common.hh"
#include <iostream>
namespace blender {
using bke::greasepencil::Drawing;
using bke::greasepencil::FramesMapKey;
using bke::greasepencil::Layer;
static void init_data(ModifierData *md)
{
auto *omd = reinterpret_cast<GreasePencilOpacityModifierData *>(md);
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(omd, modifier));
MEMCPY_STRUCT_AFTER(omd, DNA_struct_default_get(GreasePencilOpacityModifierData), modifier);
modifier::greasepencil::init_influence_data(&omd->influence, true);
}
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
{
const auto *omd = reinterpret_cast<const GreasePencilOpacityModifierData *>(md);
auto *tomd = reinterpret_cast<GreasePencilOpacityModifierData *>(target);
modifier::greasepencil::free_influence_data(&tomd->influence);
BKE_modifier_copydata_generic(md, target, flag);
modifier::greasepencil::copy_influence_data(&omd->influence, &tomd->influence, flag);
}
static void free_data(ModifierData *md)
{
auto *omd = reinterpret_cast<GreasePencilOpacityModifierData *>(md);
modifier::greasepencil::free_influence_data(&omd->influence);
}
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
{
auto *omd = reinterpret_cast<GreasePencilOpacityModifierData *>(md);
modifier::greasepencil::foreach_influence_ID_link(&omd->influence, ob, walk, user_data);
}
static void modify_stroke_color(const GreasePencilOpacityModifierData &omd,
bke::CurvesGeometry &curves,
const IndexMask &curves_mask)
{
const bool use_uniform_opacity = (omd.flag & MOD_GREASE_PENCIL_OPACITY_USE_UNIFORM_OPACITY);
const bool use_weight_as_factor = (omd.flag & MOD_GREASE_PENCIL_OPACITY_USE_WEIGHT_AS_FACTOR);
const bool invert_vertex_group = (omd.influence.flag &
GREASE_PENCIL_INFLUENCE_INVERT_VERTEX_GROUP);
const bool use_curve = (omd.influence.flag & GREASE_PENCIL_INFLUENCE_USE_CUSTOM_CURVE);
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::SpanAttributeWriter<float> opacities = attributes.lookup_or_add_for_write_span<float>(
"opacity", bke::AttrDomain::Point);
const VArray<float> vgroup_weights =
attributes
.lookup_or_default<float>(omd.influence.vertex_group_name, bke::AttrDomain::Point, 1.0f)
.varray;
curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
for (const int64_t point_i : points) {
const float curve_input = points.size() >= 2 ?
(float(point_i - points.first()) / float(points.size() - 1)) :
0.0f;
const float curve_factor = use_curve ? BKE_curvemapping_evaluateF(
omd.influence.custom_curve, 0, curve_input) :
1.0f;
if (use_uniform_opacity) {
opacities.span[point_i] = std::clamp(omd.color_factor * curve_factor, 0.0f, 1.0f);
}
else if (use_weight_as_factor) {
/* Use vertex group weights as opacity factors. */
opacities.span[point_i] = std::clamp(
omd.color_factor * curve_factor * vgroup_weights[point_i], 0.0f, 1.0f);
}
else {
/* Use vertex group weights as influence factors. */
const float vgroup_weight = vgroup_weights[point_i];
const float vgroup_influence = invert_vertex_group ? 1.0f - vgroup_weight : vgroup_weight;
opacities.span[point_i] = std::clamp(
opacities.span[point_i] + omd.color_factor * curve_factor * vgroup_influence - 1.0f,
0.0f,
1.0f);
}
}
});
opacities.finish();
}
static void modify_fill_color(const GreasePencilOpacityModifierData &omd,
bke::CurvesGeometry &curves,
const IndexMask &curves_mask)
{
const bool use_vgroup_opacity = (omd.flag & MOD_GREASE_PENCIL_OPACITY_USE_WEIGHT_AS_FACTOR);
const bool invert_vertex_group = (omd.influence.flag &
GREASE_PENCIL_INFLUENCE_INVERT_VERTEX_GROUP);
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
/* Fill color opacity per stroke. */
bke::SpanAttributeWriter<float> fill_opacities = attributes.lookup_or_add_for_write_span<float>(
"fill_opacity", bke::AttrDomain::Curve);
VArray<float> vgroup_weights = attributes
.lookup_or_default<float>(omd.influence.vertex_group_name,
bke::AttrDomain::Point,
1.0f)
.varray;
curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) {
if (use_vgroup_opacity) {
/* Use the first stroke point as vertex weight. */
const IndexRange points = points_by_curve[curve_i];
const float stroke_weight = points.is_empty() ? 1.0f : vgroup_weights[points.first()];
const float stroke_influence = invert_vertex_group ? 1.0f - stroke_weight : stroke_weight;
fill_opacities.span[curve_i] = std::clamp(stroke_influence, 0.0f, 1.0f);
}
else {
fill_opacities.span[curve_i] = std::clamp(omd.color_factor, 0.0f, 1.0f);
}
});
fill_opacities.finish();
}
static void modify_hardness(const GreasePencilOpacityModifierData &omd,
bke::CurvesGeometry &curves,
const IndexMask &curves_mask)
{
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::SpanAttributeWriter<float> hardnesses = attributes.lookup_for_write_span<float>("hardness");
if (!hardnesses) {
return;
}
curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) {
hardnesses.span[curve_i] = std::clamp(
hardnesses.span[curve_i] * omd.hardness_factor, 0.0f, 1.0f);
});
hardnesses.finish();
}
static void modify_curves(ModifierData *md,
const ModifierEvalContext *ctx,
bke::CurvesGeometry &curves)
{
auto *omd = reinterpret_cast<GreasePencilOpacityModifierData *>(md);
IndexMaskMemory mask_memory;
IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
ctx->object, curves, omd->influence, mask_memory);
switch (omd->color_mode) {
case MOD_GREASE_PENCIL_COLOR_STROKE:
modify_stroke_color(*omd, curves, curves_mask);
break;
case MOD_GREASE_PENCIL_COLOR_FILL:
modify_fill_color(*omd, curves, curves_mask);
break;
case MOD_GREASE_PENCIL_COLOR_BOTH:
modify_stroke_color(*omd, curves, curves_mask);
modify_fill_color(*omd, curves, curves_mask);
break;
case MOD_GREASE_PENCIL_COLOR_HARDNESS:
modify_hardness(*omd, curves, curves_mask);
break;
}
}
static void modify_geometry_set(ModifierData *md,
const ModifierEvalContext *ctx,
bke::GeometrySet *geometry_set)
{
auto *omd = reinterpret_cast<GreasePencilOpacityModifierData *>(md);
const Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
const int frame = scene->r.cfra;
GreasePencil *grease_pencil = geometry_set->get_grease_pencil_for_write();
if (grease_pencil == nullptr) {
return;
}
IndexMaskMemory mask_memory;
IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
*grease_pencil, omd->influence, mask_memory);
Vector<Drawing *> drawings = modifier::greasepencil::get_drawings_for_write(
*grease_pencil, layer_mask, frame);
threading::parallel_for_each(
drawings, [&](Drawing *drawing) { modify_curves(md, ctx, drawing->strokes_for_write()); });
}
static void panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA ob_ptr;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
uiLayoutSetPropSep(layout, true);
const GreasePencilModifierColorMode color_mode = GreasePencilModifierColorMode(
RNA_enum_get(ptr, "color_mode"));
uiItemR(layout, ptr, "color_mode", UI_ITEM_NONE, nullptr, ICON_NONE);
if (color_mode == MOD_GREASE_PENCIL_COLOR_HARDNESS) {
uiItemR(layout, ptr, "hardness_factor", UI_ITEM_NONE, nullptr, ICON_NONE);
}
else {
const bool use_uniform_opacity = RNA_boolean_get(ptr, "use_uniform_opacity");
const bool use_weight_as_factor = RNA_boolean_get(ptr, "use_weight_as_factor");
uiItemR(layout, ptr, "use_uniform_opacity", UI_ITEM_NONE, nullptr, ICON_NONE);
const char *text = (use_uniform_opacity) ? IFACE_("Opacity") : IFACE_("Opacity Factor");
uiLayout *row = uiLayoutRow(layout, true);
uiLayoutSetActive(row, !use_weight_as_factor || use_uniform_opacity);
uiItemR(row, ptr, "color_factor", UI_ITEM_NONE, text, ICON_NONE);
if (!use_uniform_opacity) {
uiLayout *sub = uiLayoutRow(row, true);
uiLayoutSetActive(sub, true);
uiItemR(row, ptr, "use_weight_as_factor", UI_ITEM_NONE, "", ICON_MOD_VERTEX_WEIGHT);
}
}
if (uiLayout *influence_panel = uiLayoutPanel(
C, layout, "Influence", ptr, "open_influence_panel"))
{
modifier::greasepencil::draw_layer_filter_settings(C, influence_panel, ptr);
modifier::greasepencil::draw_material_filter_settings(C, influence_panel, ptr);
modifier::greasepencil::draw_vertex_group_settings(C, influence_panel, ptr);
modifier::greasepencil::draw_custom_curve_settings(C, influence_panel, ptr);
}
modifier_panel_end(layout, ptr);
}
static void panel_register(ARegionType *region_type)
{
modifier_panel_register(region_type, eModifierType_GreasePencilOpacity, panel_draw);
}
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
{
const auto *omd = reinterpret_cast<const GreasePencilOpacityModifierData *>(md);
BLO_write_struct(writer, GreasePencilOpacityModifierData, omd);
modifier::greasepencil::write_influence_data(writer, &omd->influence);
}
static void blend_read(BlendDataReader *reader, ModifierData *md)
{
auto *omd = reinterpret_cast<GreasePencilOpacityModifierData *>(md);
modifier::greasepencil::read_influence_data(reader, &omd->influence);
}
} // namespace blender
ModifierTypeInfo modifierType_GreasePencilOpacity = {
/*idname*/ "GreasePencilOpacity",
/*name*/ N_("Opacity"),
/*struct_name*/ "GreasePencilOpacityModifierData",
/*struct_size*/ sizeof(GreasePencilOpacityModifierData),
/*srna*/ &RNA_GreasePencilOpacityModifier,
/*type*/ ModifierTypeType::Nonconstructive,
/*flags*/
static_cast<ModifierTypeFlag>(
eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode |
eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping),
/*icon*/ ICON_MOD_OPACITY,
/*copy_data*/ blender::copy_data,
/*deform_verts*/ nullptr,
/*deform_matrices*/ nullptr,
/*deform_verts_EM*/ nullptr,
/*deform_matrices_EM*/ nullptr,
/*modify_mesh*/ nullptr,
/*modify_geometry_set*/ blender::modify_geometry_set,
/*init_data*/ blender::init_data,
/*required_data_mask*/ nullptr,
/*free_data*/ blender::free_data,
/*is_disabled*/ nullptr,
/*update_depsgraph*/ nullptr,
/*depends_on_time*/ nullptr,
/*depends_on_normals*/ nullptr,
/*foreach_ID_link*/ blender::foreach_ID_link,
/*foreach_tex_link*/ nullptr,
/*free_runtime_data*/ nullptr,
/*panel_register*/ blender::panel_register,
/*blend_write*/ blender::blend_write,
/*blend_read*/ blender::blend_read,
};

View File

@ -0,0 +1,337 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#include "MOD_grease_pencil_util.hh"
#include "BLI_set.hh"
#include "DNA_grease_pencil_types.h"
#include "DNA_material_types.h"
#include "DNA_modifier_types.h"
#include "DNA_screen_types.h"
#include "BKE_colortools.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_lib_query.h"
#include "BKE_material.h"
#include "BLO_read_write.hh"
#include "DNA_defaults.h"
#include "DEG_depsgraph_query.hh"
#include "MOD_ui_common.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
#include "UI_interface.hh"
namespace blender::modifier::greasepencil {
using bke::greasepencil::Drawing;
using bke::greasepencil::Layer;
void init_influence_data(GreasePencilModifierInfluenceData *influence_data,
const bool has_custom_curve)
{
if (has_custom_curve) {
influence_data->custom_curve = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
BKE_curvemapping_init(influence_data->custom_curve);
}
}
void copy_influence_data(const GreasePencilModifierInfluenceData *influence_data_src,
GreasePencilModifierInfluenceData *influence_data_dst,
const int /*flag*/)
{
memcpy(influence_data_dst, influence_data_src, sizeof(GreasePencilModifierInfluenceData));
influence_data_dst->custom_curve = BKE_curvemapping_copy(influence_data_src->custom_curve);
}
void free_influence_data(GreasePencilModifierInfluenceData *influence_data)
{
if (influence_data->custom_curve) {
BKE_curvemapping_free(influence_data->custom_curve);
influence_data->custom_curve = nullptr;
}
}
void foreach_influence_ID_link(GreasePencilModifierInfluenceData *influence_data,
Object *ob,
IDWalkFunc walk,
void *user_data)
{
walk(user_data, ob, (ID **)&influence_data->material, IDWALK_CB_USER);
}
void write_influence_data(BlendWriter *writer,
const GreasePencilModifierInfluenceData *influence_data)
{
if (influence_data->custom_curve) {
BKE_curvemapping_blend_write(writer, influence_data->custom_curve);
}
}
void read_influence_data(BlendDataReader *reader,
GreasePencilModifierInfluenceData *influence_data)
{
BLO_read_data_address(reader, &influence_data->custom_curve);
if (influence_data->custom_curve) {
BKE_curvemapping_blend_read(reader, influence_data->custom_curve);
/* Make sure the internal table exists. */
BKE_curvemapping_init(influence_data->custom_curve);
}
}
void draw_layer_filter_settings(const bContext * /*C*/, uiLayout *layout, PointerRNA *ptr)
{
PointerRNA ob_ptr = RNA_pointer_create(ptr->owner_id, &RNA_Object, ptr->owner_id);
PointerRNA obj_data_ptr = RNA_pointer_get(&ob_ptr, "data");
const bool use_layer_pass = RNA_boolean_get(ptr, "use_layer_pass_filter");
uiLayout *row, *col, *sub, *subsub;
uiLayoutSetPropSep(layout, true);
col = uiLayoutColumn(layout, true);
row = uiLayoutRow(col, true);
uiLayoutSetPropDecorate(row, false);
uiItemPointerR(row, ptr, "layer_filter", &obj_data_ptr, "layers", nullptr, ICON_GREASEPENCIL);
sub = uiLayoutRow(row, true);
uiItemR(sub, ptr, "invert_layer_filter", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT);
row = uiLayoutRowWithHeading(col, true, "Layer Pass");
uiLayoutSetPropDecorate(row, false);
sub = uiLayoutRow(row, true);
uiItemR(sub, ptr, "use_layer_pass_filter", UI_ITEM_NONE, "", ICON_NONE);
subsub = uiLayoutRow(sub, true);
uiLayoutSetActive(subsub, use_layer_pass);
uiItemR(subsub, ptr, "layer_pass_filter", UI_ITEM_NONE, "", ICON_NONE);
uiItemR(subsub, ptr, "invert_layer_pass_filter", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT);
}
void draw_material_filter_settings(const bContext * /*C*/, uiLayout *layout, PointerRNA *ptr)
{
PointerRNA ob_ptr = RNA_pointer_create(ptr->owner_id, &RNA_Object, ptr->owner_id);
PointerRNA obj_data_ptr = RNA_pointer_get(&ob_ptr, "data");
const bool use_material_pass = RNA_boolean_get(ptr, "use_material_pass_filter");
uiLayout *row, *col, *sub, *subsub;
uiLayoutSetPropSep(layout, true);
col = uiLayoutColumn(layout, true);
row = uiLayoutRow(col, true);
uiLayoutSetPropDecorate(row, false);
uiItemPointerR(
row, ptr, "material_filter", &obj_data_ptr, "materials", nullptr, ICON_SHADING_TEXTURE);
sub = uiLayoutRow(row, true);
uiItemR(sub, ptr, "invert_material_filter", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT);
row = uiLayoutRowWithHeading(col, true, "Material Pass");
uiLayoutSetPropDecorate(row, false);
sub = uiLayoutRow(row, true);
uiItemR(sub, ptr, "use_material_pass_filter", UI_ITEM_NONE, "", ICON_NONE);
subsub = uiLayoutRow(sub, true);
uiLayoutSetActive(subsub, use_material_pass);
uiItemR(subsub, ptr, "material_pass_filter", UI_ITEM_NONE, "", ICON_NONE);
uiItemR(subsub, ptr, "invert_material_pass_filter", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT);
}
void draw_vertex_group_settings(const bContext * /*C*/, uiLayout *layout, PointerRNA *ptr)
{
PointerRNA ob_ptr = RNA_pointer_create(ptr->owner_id, &RNA_Object, ptr->owner_id);
bool has_vertex_group = RNA_string_length(ptr, "vertex_group_name") != 0;
uiLayout *row, *col, *sub;
uiLayoutSetPropSep(layout, true);
col = uiLayoutColumn(layout, true);
row = uiLayoutRow(col, true);
uiLayoutSetPropDecorate(row, false);
uiItemPointerR(row, ptr, "vertex_group_name", &ob_ptr, "vertex_groups", nullptr, ICON_NONE);
sub = uiLayoutRow(row, true);
uiLayoutSetActive(sub, has_vertex_group);
uiLayoutSetPropDecorate(sub, false);
uiItemR(sub, ptr, "invert_vertex_group", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT);
}
void draw_custom_curve_settings(const bContext * /*C*/, uiLayout *layout, PointerRNA *ptr)
{
bool use_custom_curve = RNA_boolean_get(ptr, "use_custom_curve");
uiLayout *row;
uiLayoutSetPropSep(layout, true);
row = uiLayoutRow(layout, true);
uiLayoutSetPropDecorate(row, false);
uiItemR(row, ptr, "use_custom_curve", UI_ITEM_NONE, "Custom Curve", ICON_NONE);
if (use_custom_curve) {
uiTemplateCurveMapping(row, ptr, "custom_curve", 0, false, false, false, false);
}
}
/**
* Get a list of pass IDs used by grease pencil materials.
* This way the material pass can be looked up by index instead of having to get the material for
* each curve.
*/
static Vector<int> get_grease_pencil_material_passes(const Object *ob)
{
short *totcol = BKE_object_material_len_p(const_cast<Object *>(ob));
Vector<int> result(*totcol);
Material *ma = nullptr;
for (short i = 0; i < *totcol; i++) {
ma = BKE_object_material_get(const_cast<Object *>(ob), i + 1);
/* Pass index of the grease pencil material. */
result[i] = ma->gp_style->index;
}
return result;
}
static IndexMask get_filtered_layer_mask(const GreasePencil &grease_pencil,
const std::optional<StringRef> layer_name_filter,
const std::optional<int> layer_pass_filter,
const bool layer_filter_invert,
const bool layer_pass_filter_invert,
IndexMaskMemory &memory)
{
const IndexMask full_mask = grease_pencil.layers().index_range();
if (!layer_name_filter && !layer_pass_filter) {
return full_mask;
}
bke::AttributeAccessor layer_attributes = grease_pencil.attributes();
const Span<const Layer *> layers = grease_pencil.layers();
const VArray<int> layer_passes =
layer_attributes.lookup_or_default<int>("pass", bke::AttrDomain::Layer, 0).varray;
IndexMask result = IndexMask::from_predicate(
full_mask, GrainSize(4096), memory, [&](const int64_t layer_i) {
if (layer_name_filter) {
const Layer &layer = *layers[layer_i];
const bool match = (layer.name() == layer_name_filter.value());
if (match == layer_filter_invert) {
return false;
}
}
if (layer_pass_filter) {
const int layer_pass = layer_passes.get(layer_i);
const bool match = (layer_pass == layer_pass_filter.value());
if (match == layer_pass_filter_invert) {
return false;
}
}
return true;
});
return result;
}
IndexMask get_filtered_layer_mask(const GreasePencil &grease_pencil,
const GreasePencilModifierInfluenceData &influence_data,
IndexMaskMemory &memory)
{
return get_filtered_layer_mask(
grease_pencil,
influence_data.layer_name[0] != '\0' ?
std::make_optional<StringRef>(influence_data.layer_name) :
std::nullopt,
(influence_data.flag & GREASE_PENCIL_INFLUENCE_USE_LAYER_PASS_FILTER) ?
std::make_optional<int>(influence_data.layer_pass) :
std::nullopt,
influence_data.flag & GREASE_PENCIL_INFLUENCE_INVERT_LAYER_FILTER,
influence_data.flag & GREASE_PENCIL_INFLUENCE_INVERT_LAYER_PASS_FILTER,
memory);
}
static IndexMask get_filtered_stroke_mask(const Object *ob,
const bke::CurvesGeometry &curves,
const Material *material_filter,
const std::optional<int> material_pass_filter,
const bool material_filter_invert,
const bool material_pass_filter_invert,
IndexMaskMemory &memory)
{
const IndexMask full_mask = curves.curves_range();
if (!material_filter && !material_pass_filter) {
return full_mask;
}
const int material_filter_index = BKE_object_material_index_get(
const_cast<Object *>(ob), const_cast<Material *>(material_filter));
const Vector<int> material_pass_by_index = get_grease_pencil_material_passes(ob);
bke::AttributeAccessor attributes = curves.attributes();
VArray<int> stroke_materials =
attributes.lookup_or_default<int>("material_index", bke::AttrDomain::Curve, 0).varray;
IndexMask result = IndexMask::from_predicate(
full_mask, GrainSize(4096), memory, [&](const int64_t stroke_i) {
const int material_index = stroke_materials.get(stroke_i);
if (material_filter != nullptr) {
const bool match = (material_index == material_filter_index);
if (match == material_filter_invert) {
return false;
}
}
if (material_pass_filter) {
const int material_pass = material_pass_by_index[material_index];
const bool match = (material_pass == material_pass_filter.value());
if (match == material_pass_filter_invert) {
return false;
}
}
return true;
});
return result;
}
IndexMask get_filtered_stroke_mask(const Object *ob,
const bke::CurvesGeometry &curves,
const GreasePencilModifierInfluenceData &influence_data,
IndexMaskMemory &memory)
{
return get_filtered_stroke_mask(
ob,
curves,
influence_data.material,
(influence_data.flag & GREASE_PENCIL_INFLUENCE_USE_MATERIAL_PASS_FILTER) ?
std::make_optional<int>(influence_data.material_pass) :
std::nullopt,
influence_data.flag & GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_FILTER,
influence_data.flag & GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_PASS_FILTER,
memory);
}
Vector<bke::greasepencil::Drawing *> get_drawings_for_write(GreasePencil &grease_pencil,
const IndexMask &layer_mask,
int frame)
{
/* Set of unique drawing indices. */
Set<int> drawing_indices;
for (const int64_t i : layer_mask.index_range()) {
Layer *layer = grease_pencil.layers_for_write()[layer_mask[i]];
const int drawing_index = layer->drawing_index_at(frame);
if (drawing_index >= 0) {
drawing_indices.add(drawing_index);
}
}
/* List of owned drawings, ignore drawing references to other data blocks. */
Vector<bke::greasepencil::Drawing *> drawings;
for (const int drawing_index : drawing_indices) {
GreasePencilDrawingBase *drawing_base = grease_pencil.drawing(drawing_index);
if (drawing_base->type == GP_DRAWING) {
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
drawings.append(&drawing->wrap());
}
}
return drawings;
}
} // namespace blender::modifier::greasepencil

View File

@ -0,0 +1,66 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#pragma once
#include "BLI_index_mask.hh"
#include "BLI_vector.hh"
#include "BKE_modifier.hh"
struct ARegionType;
struct bContext;
struct GreasePencil;
struct GreasePencilModifierInfluenceData;
struct GreasePencilModifierLayerFilter;
struct GreasePencilModifierMaterialFilter;
struct PanelType;
struct PointerRNA;
struct uiLayout;
namespace blender::bke {
class CurvesGeometry;
namespace greasepencil {
class Drawing;
}
} // namespace blender::bke
namespace blender::modifier::greasepencil {
void init_influence_data(GreasePencilModifierInfluenceData *influence_data, bool has_custom_curve);
void copy_influence_data(const GreasePencilModifierInfluenceData *influence_data_src,
GreasePencilModifierInfluenceData *influence_data_dst,
int flag);
void free_influence_data(GreasePencilModifierInfluenceData *influence_data);
void foreach_influence_ID_link(GreasePencilModifierInfluenceData *influence_data,
Object *ob,
IDWalkFunc walk,
void *user_data);
void write_influence_data(BlendWriter *writer,
const GreasePencilModifierInfluenceData *influence_data);
void read_influence_data(BlendDataReader *reader,
GreasePencilModifierInfluenceData *influence_data);
void draw_layer_filter_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr);
void draw_material_filter_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr);
void draw_vertex_group_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr);
void draw_custom_curve_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr);
IndexMask get_filtered_layer_mask(const GreasePencil &grease_pencil,
const GreasePencilModifierInfluenceData &influence_data,
IndexMaskMemory &memory);
IndexMask get_filtered_stroke_mask(const Object *ob,
const bke::CurvesGeometry &curves,
const GreasePencilModifierInfluenceData &influence_data,
IndexMaskMemory &memory);
Vector<bke::greasepencil::Drawing *> get_drawings_for_write(GreasePencil &grease_pencil,
const IndexMask &layer_mask,
int frame);
} // namespace blender::modifier::greasepencil

View File

@ -270,5 +270,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
INIT_TYPE(VolumeDisplace);
INIT_TYPE(VolumeToMesh);
INIT_TYPE(Nodes);
INIT_TYPE(GreasePencilOpacity);
#undef INIT_TYPE
}

View File

@ -15,6 +15,7 @@
#include "BKE_context.hh"
#include "BKE_main.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.h"
#include "WM_api.hh"
@ -49,6 +50,20 @@ static void bpy_rna_context_temp_set_screen_for_window(bContext *C, wmWindow *wi
WM_window_set_active_screen(win, workspace, screen);
}
/**
* Switching to or away from this screen is not supported.
*/
static bool wm_check_screen_switch_supported(const bScreen *screen)
{
if (screen->temp != 0) {
return false;
}
if (BKE_screen_is_fullscreen_area(screen)) {
return false;
}
return true;
}
static bool wm_check_window_exists(const Main *bmain, const wmWindow *win)
{
LISTBASE_FOREACH (wmWindowManager *, wm, &bmain->wm) {
@ -233,9 +248,17 @@ static PyObject *bpy_rna_context_temp_override_enter(BPyContextTempOverride *sel
/* Skip some checks when the screen is unchanged. */
if (self->ctx_init.screen_is_set) {
if (screen->temp != 0) {
/* Switching away from a temporary screen isn't supported. */
if ((self->ctx_init.screen != nullptr) &&
!wm_check_screen_switch_supported(self->ctx_init.screen))
{
PyErr_SetString(PyExc_TypeError,
"Overriding context with temporary screen is not supported");
"Overriding context with an active temporary screen isn't supported");
return nullptr;
}
if (!wm_check_screen_switch_supported(screen)) {
PyErr_SetString(PyExc_TypeError,
"Overriding context with temporary screen isn't supported");
return nullptr;
}
if (BKE_workspace_layout_find_global(bmain, screen, nullptr) == nullptr) {
@ -308,7 +331,12 @@ static PyObject *bpy_rna_context_temp_override_exit(BPyContextTempOverride *self
if (self->ctx_temp_orig.screen && wm_check_screen_exists(bmain, self->ctx_temp_orig.screen)) {
wmWindow *win = self->ctx_temp.win_is_set ? self->ctx_temp.win : self->ctx_init.win;
if (win && wm_check_window_exists(bmain, win)) {
bpy_rna_context_temp_set_screen_for_window(C, win, self->ctx_temp_orig.screen);
/* Disallow switching away from temporary-screens & full-screen areas, while it could be
* useful to support this closing a these screens uses different and more involved logic
* compared with switching between user managed screens, see: #117188. */
if (wm_check_screen_switch_supported(WM_window_get_active_screen(win))) {
bpy_rna_context_temp_set_screen_for_window(C, win, self->ctx_temp_orig.screen);
}
}
}
}
@ -523,6 +551,10 @@ PyDoc_STRVAR(bpy_context_temp_override_doc,
" :type window: :class:`bpy.types.Window`\n"
" :arg screen: Screen override or None.\n"
"\n"
" .. note:: Switching to or away from full-screen areas & temporary screens "
"isn't supported. Passing in these screens will raise an exception, "
"actions that leave the context such screens won't restore the prior screen.\n"
"\n"
" .. note:: Changing the screen has wider implications "
"than other arguments as it will also change the works-space "
"and potentially the scene (when pinned).\n"

View File

@ -502,6 +502,9 @@ RenderResult *RE_DuplicateRenderResult(RenderResult *rr);
struct ImBuf *RE_RenderPassEnsureImBuf(RenderPass *render_pass);
struct ImBuf *RE_RenderViewEnsureImBuf(const RenderResult *render_result, RenderView *render_view);
/* Returns true if the pass is a color (as opposite of data) and needs to be color managed. */
bool RE_RenderPassIsColor(const RenderPass *render_pass);
#ifdef __cplusplus
}
#endif

View File

@ -167,6 +167,31 @@ void render_result_views_shallowdelete(RenderResult *rr)
/** \name New
* \{ */
static int get_num_planes_for_pass_ibuf(const RenderPass &render_pass)
{
switch (render_pass.channels) {
case 1:
return R_IMF_PLANES_BW;
case 3:
return R_IMF_PLANES_RGB;
case 4:
return R_IMF_PLANES_RGBA;
}
/* Fallback to a commonly used default value of planes for odd-ball number of channel. */
return R_IMF_PLANES_RGBA;
}
static void assign_render_pass_ibuf_colorspace(RenderPass &render_pass)
{
if (RE_RenderPassIsColor(&render_pass)) {
return;
}
const char *data_colorspace = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA);
IMB_colormanagement_assign_float_colorspace(render_pass.ibuf, data_colorspace);
}
static void render_layer_allocate_pass(RenderResult *rr, RenderPass *rp)
{
if (rp->ibuf && rp->ibuf->float_buffer.data) {
@ -179,9 +204,10 @@ static void render_layer_allocate_pass(RenderResult *rr, RenderPass *rp)
const size_t rectsize = size_t(rr->rectx) * rr->recty * rp->channels;
float *buffer_data = MEM_cnew_array<float>(rectsize, rp->name);
rp->ibuf = IMB_allocImBuf(rr->rectx, rr->recty, 32, 0);
rp->ibuf = IMB_allocImBuf(rr->rectx, rr->recty, get_num_planes_for_pass_ibuf(*rp), 0);
rp->ibuf->channels = rp->channels;
IMB_assign_float_buffer(rp->ibuf, buffer_data, IB_TAKE_OWNERSHIP);
assign_render_pass_ibuf_colorspace(*rp);
if (STREQ(rp->name, RE_PASSNAME_VECTOR)) {
/* initialize to max speed */
@ -680,6 +706,7 @@ RenderResult *render_result_new_from_exr(
RenderResult *rr = MEM_cnew<RenderResult>(__func__);
const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
COLOR_ROLE_SCENE_LINEAR);
const char *data_colorspace = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA);
rr->rectx = rectx;
rr->recty = recty;
@ -696,7 +723,7 @@ RenderResult *render_result_new_from_exr(
rpass->rectx = rectx;
rpass->recty = recty;
if (rpass->channels >= 3) {
if (RE_RenderPassIsColor(rpass)) {
IMB_colormanagement_transform(rpass->ibuf->float_buffer.data,
rpass->rectx,
rpass->recty,
@ -705,6 +732,9 @@ RenderResult *render_result_new_from_exr(
to_colorspace,
predivide);
}
else {
IMB_colormanagement_assign_float_colorspace(rpass->ibuf, data_colorspace);
}
}
}
@ -1302,8 +1332,10 @@ RenderResult *RE_DuplicateRenderResult(RenderResult *rr)
ImBuf *RE_RenderPassEnsureImBuf(RenderPass *render_pass)
{
if (!render_pass->ibuf) {
render_pass->ibuf = IMB_allocImBuf(render_pass->rectx, render_pass->recty, 32, 0);
render_pass->ibuf = IMB_allocImBuf(
render_pass->rectx, render_pass->recty, get_num_planes_for_pass_ibuf(*render_pass), 0);
render_pass->ibuf->channels = render_pass->channels;
assign_render_pass_ibuf_colorspace(*render_pass);
}
return render_pass->ibuf;
@ -1318,4 +1350,9 @@ ImBuf *RE_RenderViewEnsureImBuf(const RenderResult *render_result, RenderView *r
return render_view->ibuf;
}
bool RE_RenderPassIsColor(const RenderPass *render_pass)
{
return STR_ELEM(render_pass->chan_id, "RGB", "RGBA", "R", "G", "B", "A");
}
/** \} */

View File

@ -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

View File

@ -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()