Merge branch 'master' into refactor-mesh-position-generic

This commit is contained in:
Hans Goudey 2022-12-06 11:47:06 -06:00
commit 198c505b6c
22 changed files with 275 additions and 140 deletions

View File

@ -1670,7 +1670,7 @@ class CyclesPreferences(bpy.types.AddonPreferences):
col.label(text="and Windows driver version 101.3430 or newer", icon='BLANK1')
elif sys.platform.startswith("linux"):
col.label(text="Requires Intel GPU with Xe-HPG architecture and", icon='BLANK1')
col.label(text=" - Linux driver version xx.xx.23904 or newer", icon='BLANK1')
col.label(text=" - intel-level-zero-gpu version 1.3.23904 or newer", icon='BLANK1')
col.label(text=" - oneAPI Level-Zero Loader", icon='BLANK1')
elif device_type == 'METAL':
col.label(text="Requires Apple Silicon with macOS 12.2 or newer", icon='BLANK1')

View File

@ -796,11 +796,11 @@ ccl_device float bits_to_01(uint bits)
ccl_device_inline uint popcount(uint x)
{
/* TODO(Stefan): pop-count intrinsic for Windows with fallback for older CPUs. */
uint i = x & 0xaaaaaaaa;
uint i = x;
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
i = (((i + (i >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
return i & 1;
return i;
}
# endif
#elif defined(__KERNEL_ONEAPI__)

View File

@ -101,7 +101,7 @@ bool win32_chk(bool result, const char *file, int line, const char *text)
# ifndef NDEBUG
_ftprintf(
stderr, "%s(%d):[%s] -> Win32 Error# (%lu): %s", file, line, text, ulong(error), msg);
stderr, "%s:%d: [%s] -> Win32 Error# (%lu): %s", file, line, text, ulong(error), msg);
# else
_ftprintf(stderr, "Win32 Error# (%lu): %s", ulong(error), msg);
# endif

View File

@ -123,7 +123,7 @@ static bool egl_chk(bool result,
#ifndef NDEBUG
fprintf(stderr,
"%s(%d):[%s] -> EGL Error (0x%04X): %s: %s\n",
"%s:%d: [%s] -> EGL Error (0x%04X): %s: %s\n",
file,
line,
text,

View File

@ -1049,9 +1049,9 @@ void GHOST_SystemWin32::processPointerEvent(
}
}
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window,
const int32_t screen_co[2])
{
int32_t x_screen, y_screen;
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
if (window->getTabletData().Active != GHOST_kTabletModeNone) {
@ -1059,8 +1059,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
return NULL;
}
system->getCursorPosition(x_screen, y_screen);
int32_t x_screen = screen_co[0], y_screen = screen_co[1];
if (window->getCursorGrabModeIsWarp()) {
/* WORKAROUND:
* Sometimes Windows ignores `SetCursorPos()` or `SendInput()` calls or the mouse event is
@ -1834,7 +1833,10 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam,
}
}
event = processCursorEvent(window);
const int32_t window_co[2] = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
int32_t screen_co[2];
window->clientToScreen(UNPACK2(window_co), UNPACK2(screen_co));
event = processCursorEvent(window, screen_co);
break;
}
@ -1876,7 +1878,10 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam,
WINTAB_PRINTF("HWND %p mouse leave\n", window->getHWND());
window->m_mousePresent = false;
if (window->getTabletData().Active == GHOST_kTabletModeNone) {
event = processCursorEvent(window);
/* FIXME: document why the cursor motion event on mouse leave is needed. */
int32_t screen_co[2] = {0, 0};
system->getCursorPosition(screen_co[0], screen_co[1]);
event = processCursorEvent(window, screen_co);
}
GHOST_Wintab *wt = window->getWintab();
if (wt) {

View File

@ -331,7 +331,8 @@ class GHOST_SystemWin32 : public GHOST_System {
* \param window: The window receiving the event (the active window).
* \return The event created.
*/
static GHOST_EventCursor *processCursorEvent(GHOST_WindowWin32 *window);
static GHOST_EventCursor *processCursorEvent(GHOST_WindowWin32 *window,
const int32_t screen_co[2]);
/**
* Handles a mouse wheel event.

View File

@ -40,6 +40,25 @@
</screenshot>
</screenshots>
<releases>
<release version="3.4" date="2022-12-07">
<description>
<p>New features:</p>
<ul>
<li>Cycles Path Guiding</li>
<li>Sculpt geometry-based relax brush</li>
<li>Viewport overlay for Geometry Nodes Viewer Node</li>
<li>EEVEE headless rendering support on Linux</li>
</ul>
<p>Enhancements:</p>
<ul>
<li>Many new Geometry Nodes for meshes, curves and more</li>
<li>NLA editor usability improvements</li>
<li>FFmpeg AV1 codec encoding</li>
<li>Improved font thumbnails</li>
<li>Blender as a Python Module</li>
</ul>
</description>
</release>
<release version="3.3" date="2022-09-07">
<description>
<p>New features:</p>

View File

@ -2137,9 +2137,6 @@ def km_node_editor(params):
)),
("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
("node.move_detach_links",
{"type": 'D', "value": 'PRESS', "alt": True},
{"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
("node.move_detach_links_release",
{"type": params.action_mouse, "value": 'CLICK_DRAG', "alt": True},
{"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])])]}),

View File

@ -88,8 +88,16 @@ class CURVES_UL_attributes(UIList):
flags = []
indices = [i for i in range(len(attributes))]
for item in attributes:
flags.append(0 if item.is_internal else self.bitflag_filter_item)
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
# Filtering internal attributes
for idx, item in enumerate(attributes):
flags[idx] = 0 if item.is_internal else flags[idx]
return flags, indices

View File

@ -537,8 +537,16 @@ class MESH_UL_attributes(UIList):
flags = []
indices = [i for i in range(len(attributes))]
for item in attributes:
flags.append(0 if item.is_internal else self.bitflag_filter_item)
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
# Filtering internal attributes
for idx, item in enumerate(attributes):
flags[idx] = 0 if item.is_internal else flags[idx]
return flags, indices
@ -626,20 +634,26 @@ class ColorAttributesListBase():
}
def filter_items(self, _context, data, property):
attrs = getattr(data, property)
ret = []
idxs = []
attributes = getattr(data, property)
flags = []
indices = [i for i in range(len(attributes))]
for idx, item in enumerate(attrs):
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
for idx, item in enumerate(attributes):
skip = (
(item.domain not in {"POINT", "CORNER"}) or
(item.data_type not in {"FLOAT_COLOR", "BYTE_COLOR"}) or
item.is_internal
)
ret.append(0 if skip else self.bitflag_filter_item)
idxs.append(idx)
flags[idx] = 0 if skip else flags[idx]
return ret, idxs
return flags, indices
class MESH_UL_color_attributes(UIList, ColorAttributesListBase):

View File

@ -70,8 +70,16 @@ class POINTCLOUD_UL_attributes(UIList):
flags = []
indices = [i for i in range(len(attributes))]
for item in attributes:
flags.append(0 if item.is_internal else self.bitflag_filter_item)
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
# Filtering internal attributes
for idx, item in enumerate(attributes):
flags[idx] = 0 if item.is_internal else flags[idx]
return flags, indices

View File

@ -51,6 +51,23 @@ static bool BLI_path_is_abs(const char *name);
// #define DEBUG_STRSIZE
/**
* On UNIX it only makes sense to treat `/` as a path separator.
* On WIN32 either may be used.
*/
static bool is_sep_native_compat(const char ch)
{
if (ch == SEP) {
return true;
}
#ifdef WIN32
if (ch == ALTSEP) {
return true;
}
#endif
return false;
}
/* implementation */
int BLI_path_sequence_decode(const char *string, char *head, char *tail, ushort *r_digits_len)
@ -1450,7 +1467,7 @@ size_t BLI_path_append(char *__restrict dst, const size_t maxlen, const char *__
size_t dirlen = BLI_strnlen(dst, maxlen);
/* Inline #BLI_path_slash_ensure. */
if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
if ((dirlen > 0) && !is_sep_native_compat(dst[dirlen - 1])) {
dst[dirlen++] = SEP;
dst[dirlen] = '\0';
}
@ -1467,7 +1484,7 @@ size_t BLI_path_append_dir(char *__restrict dst, const size_t maxlen, const char
size_t dirlen = BLI_path_append(dst, maxlen, dir);
if (dirlen + 1 < maxlen) {
/* Inline #BLI_path_slash_ensure. */
if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
if ((dirlen > 0) && !is_sep_native_compat(dst[dirlen - 1])) {
dst[dirlen++] = SEP;
dst[dirlen] = '\0';
}
@ -1522,7 +1539,7 @@ size_t BLI_path_join_array(char *__restrict dst,
bool has_trailing_slash = false;
if (ofs != 0) {
size_t len = ofs;
while ((len != 0) && (path[len - 1] == SEP)) {
while ((len != 0) && is_sep_native_compat(path[len - 1])) {
len -= 1;
}
@ -1536,18 +1553,18 @@ size_t BLI_path_join_array(char *__restrict dst,
path = path_array[path_index];
has_trailing_slash = false;
const char *path_init = path;
while (path[0] == SEP) {
while (is_sep_native_compat(path[0])) {
path++;
}
size_t len = strlen(path);
if (len != 0) {
while ((len != 0) && (path[len - 1] == SEP)) {
while ((len != 0) && is_sep_native_compat(path[len - 1])) {
len -= 1;
}
if (len != 0) {
/* the very first path may have a slash at the end */
if (ofs && (dst[ofs - 1] != SEP)) {
if (ofs && !is_sep_native_compat(dst[ofs - 1])) {
dst[ofs++] = SEP;
if (ofs == dst_last) {
break;
@ -1570,7 +1587,7 @@ size_t BLI_path_join_array(char *__restrict dst,
}
if (has_trailing_slash) {
if ((ofs != dst_last) && (ofs != 0) && (dst[ofs - 1] != SEP)) {
if ((ofs != dst_last) && (ofs != 0) && !is_sep_native_compat(dst[ofs - 1])) {
dst[ofs++] = SEP;
}
}
@ -1598,7 +1615,7 @@ static bool path_name_at_index_forward(const char *__restrict path,
int i = 0;
while (true) {
const char c = path[i];
if (ELEM(c, SEP, '\0')) {
if ((c == '\0') || is_sep_native_compat(c)) {
if (prev + 1 != i) {
prev += 1;
/* Skip '/./' (behave as if they don't exist). */
@ -1633,7 +1650,7 @@ static bool path_name_at_index_backward(const char *__restrict path,
int i = prev - 1;
while (true) {
const char c = i >= 0 ? path[i] : '\0';
if (ELEM(c, SEP, '\0')) {
if ((c == '\0') || is_sep_native_compat(c)) {
if (prev - 1 != i) {
i += 1;
/* Skip '/./' (behave as if they don't exist). */
@ -1732,7 +1749,7 @@ int BLI_path_slash_ensure(char *string, size_t string_maxlen)
{
int len = strlen(string);
BLI_assert(len < string_maxlen);
if (len == 0 || string[len - 1] != SEP) {
if (len == 0 || !is_sep_native_compat(string[len - 1])) {
/* Avoid unlikely buffer overflow. */
if (len + 1 < string_maxlen) {
string[len] = SEP;
@ -1747,7 +1764,7 @@ void BLI_path_slash_rstrip(char *string)
{
int len = strlen(string);
while (len) {
if (string[len - 1] == SEP) {
if (is_sep_native_compat(string[len - 1])) {
string[len - 1] = '\0';
len--;
}

View File

@ -21,6 +21,7 @@ namespace blender::editor::animation::tests {
const float KEYLIST_NEAR_ERROR = 0.1;
const float FRAME_STEP = 0.005;
/* Build FCurve with keys on frames 10, 20, and 30. */
static void build_fcurve(FCurve &fcurve)
{
fcurve.totvert = 3;
@ -51,92 +52,88 @@ static void assert_act_key_column(const ActKeyColumn *column,
const std::optional<float> expected_frame)
{
if (expected_frame.has_value()) {
EXPECT_NE(column, nullptr);
ASSERT_NE(column, nullptr) << "Expected a frame to be found at " << *expected_frame;
EXPECT_NEAR(column->cfra, *expected_frame, KEYLIST_NEAR_ERROR);
}
else {
EXPECT_EQ(column, nullptr);
EXPECT_EQ(column, nullptr) << "Expected no frame to be found, but found " << column->cfra;
}
}
using KeylistFindFunction = std::function<const ActKeyColumn *(const AnimKeylist *, float)>;
static float check_keylist_find_range(const AnimKeylist *keylist,
KeylistFindFunction keylist_find_func,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
static void check_keylist_find_range(const AnimKeylist *keylist,
KeylistFindFunction keylist_find_func,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
{
float cfra = frame_from;
for (; cfra < frame_to; cfra += FRAME_STEP) {
const ActKeyColumn *found = keylist_find_func(keylist, cfra);
assert_act_key_column(found, expected_frame);
}
return cfra;
}
static float check_keylist_find_next_range(const AnimKeylist *keylist,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
static void check_keylist_find_next_range(const AnimKeylist *keylist,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
{
return check_keylist_find_range(
keylist, ED_keylist_find_next, frame_from, frame_to, expected_frame);
check_keylist_find_range(keylist, ED_keylist_find_next, frame_from, frame_to, expected_frame);
}
TEST(keylist, find_next)
{
AnimKeylist *keylist = create_test_keylist();
float cfra = check_keylist_find_next_range(keylist, 0.0f, 9.99f, 10.0f);
cfra = check_keylist_find_next_range(keylist, cfra, 19.99f, 20.0f);
cfra = check_keylist_find_next_range(keylist, cfra, 29.99f, 30.0f);
cfra = check_keylist_find_next_range(keylist, cfra, 39.99f, std::nullopt);
check_keylist_find_next_range(keylist, 0.0f, 9.99f, 10.0f);
check_keylist_find_next_range(keylist, 10.0f, 19.99f, 20.0f);
check_keylist_find_next_range(keylist, 20.0f, 29.99f, 30.0f);
check_keylist_find_next_range(keylist, 30.0f, 39.99f, std::nullopt);
ED_keylist_free(keylist);
}
static float check_keylist_find_prev_range(const AnimKeylist *keylist,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
static void check_keylist_find_prev_range(const AnimKeylist *keylist,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
{
return check_keylist_find_range(
keylist, ED_keylist_find_prev, frame_from, frame_to, expected_frame);
check_keylist_find_range(keylist, ED_keylist_find_prev, frame_from, frame_to, expected_frame);
}
TEST(keylist, find_prev)
{
AnimKeylist *keylist = create_test_keylist();
float cfra = check_keylist_find_prev_range(keylist, 0.0f, 10.01f, std::nullopt);
cfra = check_keylist_find_prev_range(keylist, cfra, 20.01f, 10.0f);
cfra = check_keylist_find_prev_range(keylist, cfra, 30.01f, 20.0f);
cfra = check_keylist_find_prev_range(keylist, cfra, 49.99f, 30.0f);
check_keylist_find_prev_range(keylist, 0.0f, 10.00f, std::nullopt);
check_keylist_find_prev_range(keylist, 10.01f, 20.00f, 10.0f);
check_keylist_find_prev_range(keylist, 20.01f, 30.00f, 20.0f);
check_keylist_find_prev_range(keylist, 30.01f, 49.99f, 30.0f);
ED_keylist_free(keylist);
}
static float check_keylist_find_exact_range(const AnimKeylist *keylist,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
static void check_keylist_find_exact_range(const AnimKeylist *keylist,
const float frame_from,
const float frame_to,
const std::optional<float> expected_frame)
{
return check_keylist_find_range(
keylist, ED_keylist_find_exact, frame_from, frame_to, expected_frame);
check_keylist_find_range(keylist, ED_keylist_find_exact, frame_from, frame_to, expected_frame);
}
TEST(keylist, find_exact)
{
AnimKeylist *keylist = create_test_keylist();
float cfra = check_keylist_find_exact_range(keylist, 0.0f, 9.99f, std::nullopt);
cfra = check_keylist_find_exact_range(keylist, cfra, 10.01f, 10.0f);
cfra = check_keylist_find_exact_range(keylist, cfra, 19.99f, std::nullopt);
cfra = check_keylist_find_exact_range(keylist, cfra, 20.01f, 20.0f);
cfra = check_keylist_find_exact_range(keylist, cfra, 29.99f, std::nullopt);
cfra = check_keylist_find_exact_range(keylist, cfra, 30.01f, 30.0f);
cfra = check_keylist_find_exact_range(keylist, cfra, 49.99f, std::nullopt);
check_keylist_find_exact_range(keylist, 0.0f, 9.99f, std::nullopt);
check_keylist_find_exact_range(keylist, 9.9901f, 10.01f, 10.0f);
check_keylist_find_exact_range(keylist, 10.01f, 19.99f, std::nullopt);
check_keylist_find_exact_range(keylist, 19.9901f, 20.01f, 20.0f);
check_keylist_find_exact_range(keylist, 20.01f, 29.99f, std::nullopt);
check_keylist_find_exact_range(keylist, 29.9901f, 30.01f, 30.0f);
check_keylist_find_exact_range(keylist, 30.01f, 49.99f, std::nullopt);
ED_keylist_free(keylist);
}

View File

@ -5850,7 +5850,7 @@ void SCULPT_OT_brush_stroke(wmOperatorType *ot)
ot->ui = sculpt_redo_empty_ui;
/* Flags (sculpt does own undo? (ton)). */
ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO;
ot->flag = OPTYPE_BLOCKING;
/* Properties. */

View File

@ -910,13 +910,15 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven
v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR;
}
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
/* Color data is not available in multi-resolution or dynamic topology. */
if (!SCULPT_handles_colors_report(ss, op->reports)) {
return OPERATOR_CANCELLED;
}
MultiresModifierData *mmd = BKE_sculpt_multires_active(CTX_data_scene(C), ob);
BKE_sculpt_mask_layers_ensure(depsgraph, CTX_data_main(C), ob, mmd);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
SCULPT_vertex_random_access_ensure(ss);
/* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move,

View File

@ -106,16 +106,22 @@ static int viewroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
break;
}
}
else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
/* Note this does not remove auto-keys on locked cameras. */
copy_qt_qt(vod->rv3d->viewquat, vod->init.quat);
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
viewops_data_free(C, op->customdata);
op->customdata = NULL;
return OPERATOR_CANCELLED;
else if (event->type == vod->init.event_type) {
/* Check `vod->init.event_type` first in case RMB was used to invoke.
* in this case confirming takes precedence over canceling, see: T102937. */
if (event->val == KM_RELEASE) {
event_code = VIEW_CONFIRM;
}
}
else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
event_code = VIEW_CONFIRM;
else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
if (event->val == KM_PRESS) {
/* Note this does not remove auto-keys on locked cameras. */
copy_qt_qt(vod->rv3d->viewquat, vod->init.quat);
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
viewops_data_free(C, op->customdata);
op->customdata = NULL;
return OPERATOR_CANCELLED;
}
}
if (event_code == VIEW_APPLY) {

View File

@ -8,6 +8,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_boxpack_2d.h"
#include "BLI_convexhull_2d.h"
#include "BLI_ghash.h"
@ -16,6 +17,7 @@
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_rand.h"
#include "BLI_vector.hh"
#include "eigen_capi.h"
@ -1122,32 +1124,6 @@ static PFace *p_face_add_fill(ParamHandle *handle, PChart *chart, PVert *v1, PVe
return f;
}
static bool p_quad_split_direction(ParamHandle *handle, const float **co, const ParamKey *vkeys)
{
/* Slight bias to prefer one edge over the other in case they are equal, so
* that in symmetric models we choose the same split direction instead of
* depending on floating point errors to decide. */
float bias = 1.0f + 1e-6f;
float fac = len_v3v3(co[0], co[2]) * bias - len_v3v3(co[1], co[3]);
bool dir = (fac <= 0.0f);
/* The face exists check is there because of a special case:
* when two quads share three vertices, they can each be split into two triangles,
* resulting in two identical triangles. For example in Suzanne's nose. */
if (dir) {
if (p_face_exists(handle, vkeys, 0, 1, 2) || p_face_exists(handle, vkeys, 0, 2, 3)) {
return !dir;
}
}
else {
if (p_face_exists(handle, vkeys, 0, 1, 3) || p_face_exists(handle, vkeys, 1, 2, 3)) {
return !dir;
}
}
return dir;
}
/* Construction: boundary filling */
static void p_chart_boundaries(PChart *chart, PEdge **r_outer)
@ -3895,21 +3871,75 @@ void GEO_uv_parametrizer_face_add(ParamHandle *phandle,
BLI_assert(nverts >= 3);
param_assert(phandle->state == PHANDLE_STATE_ALLOCATED);
if (nverts > 4) {
if (nverts > 3) {
/* Protect against (manifold) geometry which has a non-manifold triangulation.
* See T102543. */
blender::Vector<int, 32> permute;
permute.reserve(nverts);
for (int i = 0; i < nverts; i++) {
permute.append_unchecked(i);
}
int i = nverts - 1;
while (i >= 0) {
/* Just check the "ears" of the n-gon.
* For quads, this is sufficient.
* For pents and higher, we might miss internal duplicate triangles, but note
* that such cases are rare if the source geometry is manifold and non-intersecting. */
int pm = permute.size();
BLI_assert(pm > 3);
int i0 = permute[i];
int i1 = permute[(i + 1) % pm];
int i2 = permute[(i + 2) % pm];
if (!p_face_exists(phandle, vkeys, i0, i1, i2)) {
i--; /* ...All good...*/
continue;
}
/* An existing triangle has already been inserted. As a heuristic, attempt to add the
* *previous* triangle. \note: Should probably call `GEO_uv_parametrizer_face_add` instead of
* `p_face_add_construct`. */
int iprev = permute[(i + pm - 1) % pm];
p_face_add_construct(phandle, key, vkeys, co, uv, iprev, i0, i1, pin, select);
permute.remove(i);
if (permute.size() == 3) {
break;
}
}
if (permute.size() != nverts) {
int pm = permute.size();
/* Add the remaining pm-gon. */
blender::Array<ParamKey> vkeys_sub(pm);
blender::Array<const float *> co_sub(pm);
blender::Array<float *> uv_sub(pm);
blender::Array<bool> pin_sub(pm);
blender::Array<bool> select_sub(pm);
for (int i = 0; i < pm; i++) {
int j = permute[i];
vkeys_sub[i] = vkeys[j];
co_sub[i] = co[j];
uv_sub[i] = uv[j];
pin_sub[i] = pin && pin[j];
select_sub[i] = select && select[j];
}
p_add_ngon(phandle,
key,
pm,
&vkeys_sub.first(),
&co_sub.first(),
&uv_sub.first(),
&pin_sub.first(),
&select_sub.first());
return; /* Nothing more to do. */
}
/* No "ears" have previously been inserted. Continue as normal. */
}
if (nverts > 3) {
/* ngon */
p_add_ngon(phandle, key, nverts, vkeys, co, uv, pin, select);
}
else if (nverts == 4) {
/* quad */
if (p_quad_split_direction(phandle, co, vkeys)) {
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select);
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 2, 3, pin, select);
}
else {
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 3, pin, select);
p_face_add_construct(phandle, key, vkeys, co, uv, 1, 2, 3, pin, select);
}
}
else if (!p_face_exists(phandle, vkeys, 0, 1, 2)) {
/* triangle */
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select);

View File

@ -2215,8 +2215,8 @@ struct ImBuf *imb_load_filepath_thumbnail_openexr(const char *filepath,
float scale_factor = MIN2(float(max_thumb_size) / float(source_w),
float(max_thumb_size) / float(source_h));
int dest_w = int(source_w * scale_factor);
int dest_h = int(source_h * scale_factor);
int dest_w = MAX2(int(source_w * scale_factor), 1);
int dest_h = MAX2(int(source_h * scale_factor), 1);
struct ImBuf *ibuf = IMB_allocImBuf(dest_w, dest_h, 32, IB_rectfloat);

View File

@ -112,8 +112,8 @@ struct ImBuf *imb_load_filepath_thumbnail_webp(const char *filepath,
*r_height = (size_t)config.input.height;
const float scale = (float)max_thumb_size / MAX2(config.input.width, config.input.height);
const int dest_w = (int)(config.input.width * scale);
const int dest_h = (int)(config.input.height * scale);
const int dest_w = MAX2((int)(config.input.width * scale), 1);
const int dest_h = MAX2((int)(config.input.height * scale), 1);
colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE);
struct ImBuf *ibuf = IMB_allocImBuf(dest_w, dest_h, 32, IB_rect);

View File

@ -1033,6 +1033,7 @@ static void rna_def_tex_slot(BlenderRNA *brna)
RNA_def_property_string_funcs(
prop, "rna_TexPaintSlot_name_get", "rna_TexPaintSlot_name_length", NULL);
RNA_def_property_ui_text(prop, "Name", "Name of the slot");
RNA_def_struct_name_property(srna, prop);
prop = RNA_def_property(srna, "icon_value", PROP_INT, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

View File

@ -1052,10 +1052,10 @@ static void shader_node_disconnect_inactive_mix_branch(bNodeTree *ntree,
static void ntree_shader_disconnect_inactive_mix_branches(bNodeTree *ntree)
{
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == SH_NODE_MIX_SHADER) {
if (node->typeinfo->type == SH_NODE_MIX_SHADER) {
shader_node_disconnect_inactive_mix_branch(ntree, node, 0, 1, 2, true);
}
else if (node->type == SH_NODE_MIX) {
else if (node->typeinfo->type == SH_NODE_MIX) {
const NodeShaderMix *storage = static_cast<NodeShaderMix *>(node->storage);
if (storage->data_type == SOCK_FLOAT) {
shader_node_disconnect_inactive_mix_branch(ntree, node, 0, 2, 3, storage->clamp_factor);

View File

@ -43,6 +43,7 @@
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
#include "BKE_undo_system.h"
#include "BKE_workspace.h"
#include "BKE_sound.h"
@ -928,6 +929,17 @@ void WM_reportf(eReportType type, const char *format, ...)
/** \name Operator Logic
* \{ */
/**
* Return the active undo step as an identifier for the purpose of comparison only.
*/
static intptr_t wm_operator_undo_active_id(const wmWindowManager *wm)
{
if (wm->undo_stack) {
return intptr_t(wm->undo_stack->step_active);
}
return -1;
}
bool WM_operator_poll(bContext *C, wmOperatorType *ot)
{
@ -1059,7 +1071,12 @@ static bool wm_operator_register_check(wmWindowManager *wm, wmOperatorType *ot)
return wm && (wm->op_undo_depth == 0) && (ot->flag & (OPTYPE_REGISTER | OPTYPE_UNDO));
}
static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat, const bool store)
/**
* \param has_undo_step: True when an undo step was added,
* needed when the operator doesn't use #OPTYPE_UNDO, #OPTYPE_UNDO_GROUPED but adds an undo step.
*/
static void wm_operator_finished(
bContext *C, wmOperator *op, const bool repeat, const bool store, const bool has_undo_step)
{
wmWindowManager *wm = CTX_wm_manager(C);
enum {
@ -1090,6 +1107,11 @@ static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat,
hud_status = CLEAR;
}
}
else if (has_undo_step) {
if (repeat == 0) {
hud_status = CLEAR;
}
}
}
if (repeat == 0) {
@ -1150,6 +1172,7 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons
return retval;
}
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
if (op->type->exec) {
if (op->type->flag & OPTYPE_UNDO) {
wm->op_undo_depth++;
@ -1171,7 +1194,9 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons
}
if (retval & OPERATOR_FINISHED) {
wm_operator_finished(C, op, repeat, store && wm->op_undo_depth == 0);
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
wm_operator_finished(C, op, repeat, store && wm->op_undo_depth == 0, has_undo_step);
}
else if (repeat == 0) {
/* WARNING: modal from exec is bad practice, but avoid crashing. */
@ -1412,6 +1437,7 @@ static int wm_operator_invoke(bContext *C,
if (WM_operator_poll(C, ot)) {
wmWindowManager *wm = CTX_wm_manager(C);
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
/* If `reports == nullptr`, they'll be initialized. */
wmOperator *op = wm_operator_create(wm, ot, properties, reports);
@ -1480,8 +1506,9 @@ static int wm_operator_invoke(bContext *C,
/* Do nothing, #wm_operator_exec() has been called somewhere. */
}
else if (retval & OPERATOR_FINISHED) {
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
const bool store = !is_nested_call && use_last_properties;
wm_operator_finished(C, op, false, store);
wm_operator_finished(C, op, false, store, has_undo_step);
}
else if (retval & OPERATOR_RUNNING_MODAL) {
/* Take ownership of reports (in case python provided own). */
@ -2378,6 +2405,7 @@ static int wm_handler_operator_call(bContext *C,
wmEvent_ModalMapStore event_backup;
wm_event_modalkeymap_begin(C, op, event, &event_backup);
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
if (ot->flag & OPTYPE_UNDO) {
wm->op_undo_depth++;
}
@ -2413,7 +2441,9 @@ static int wm_handler_operator_call(bContext *C,
/* Important to run 'wm_operator_finished' before setting the context members to null. */
if (retval & OPERATOR_FINISHED) {
wm_operator_finished(C, op, false, true);
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
wm_operator_finished(C, op, false, true, has_undo_step);
handler->op = nullptr;
}
else if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) {