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