Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacques Lucke <jacques@blender.org>2022-09-18 19:20:45 +0300
committerJacques Lucke <jacques@blender.org>2022-09-18 19:20:45 +0300
commitd696514283857bfc771d2b38dfd0710c6a5d1046 (patch)
tree6a50704f994ed0c59e01aa1aec47294f51ff9ce2
parent06d85ad64ba30d734fee0cb0a9db8f9492b41c52 (diff)
parent3ff15a9e23bd8a20ee514944779a898f1fe5accb (diff)
Merge branch 'master' into temp-chunk-list
-rw-r--r--intern/ghost/GHOST_Types.h11
-rw-r--r--intern/ghost/intern/GHOST_EventPrinter.cpp7
-rw-r--r--intern/ghost/intern/GHOST_ModifierKeys.cpp25
-rw-r--r--intern/ghost/intern/GHOST_ModifierKeys.h3
-rw-r--r--intern/ghost/intern/GHOST_SystemCocoa.mm6
-rw-r--r--intern/ghost/intern/GHOST_SystemSDL.cpp7
-rw-r--r--intern/ghost/intern/GHOST_SystemWayland.cpp11
-rw-r--r--intern/ghost/intern/GHOST_SystemWin32.cpp25
-rw-r--r--intern/ghost/intern/GHOST_SystemX11.cpp14
-rw-r--r--intern/mikktspace/mikktspace.hh2
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py45
-rw-r--r--release/scripts/startup/nodeitems_builtins.py1
-rw-r--r--source/blender/blenkernel/BKE_attribute.hh35
-rw-r--r--source/blender/blenkernel/BKE_attribute_math.hh122
-rw-r--r--source/blender/blenkernel/BKE_curves.hh23
-rw-r--r--source/blender/blenkernel/BKE_node.h1
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc11
-rw-r--r--source/blender/blenkernel/intern/attribute_access_intern.hh40
-rw-r--r--source/blender/blenkernel/intern/brush.cc1
-rw-r--r--source/blender/blenkernel/intern/curve_catmull_rom.cc14
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curves.cc55
-rw-r--r--source/blender/blenkernel/intern/geometry_component_mesh.cc12
-rw-r--r--source/blender/blenkernel/intern/image.cc9
-rw-r--r--source/blender/blenkernel/intern/node.cc1
-rw-r--r--source/blender/blenlib/BLI_array_utils.hh75
-rw-r--r--source/blender/blenlib/BLI_index_range.hh17
-rw-r--r--source/blender/blenlib/intern/array_utils.cc13
-rw-r--r--source/blender/blenlib/tests/BLI_index_range_test.cc11
-rw-r--r--source/blender/blenloader/CMakeLists.txt9
-rw-r--r--source/blender/draw/intern/DRW_gpu_wrapper.hh76
-rw-r--r--source/blender/draw/intern/draw_debug.cc3
-rw-r--r--source/blender/editors/gpencil/gpencil_fill.c285
-rw-r--r--source/blender/geometry/intern/mesh_to_curve_convert.cc16
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h1
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c28
-rw-r--r--source/blender/makesdna/DNA_brush_enums.h8
-rw-r--r--source/blender/makesdna/DNA_brush_types.h7
-rw-r--r--source/blender/makesdna/DNA_collection_types.h1
-rw-r--r--source/blender/makesdna/DNA_gpencil_types.h2
-rw-r--r--source/blender/makesdna/DNA_object_types.h1
-rw-r--r--source/blender/makesrna/intern/rna_brush.c22
-rw-r--r--source/blender/makesrna/intern/rna_collection.c5
-rw-r--r--source/blender/makesrna/intern/rna_object.c5
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc6
-rw-r--r--source/blender/nodes/NOD_geometry.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/geometry/CMakeLists.txt1
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_face_set_boundaries.cc89
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc20
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc4
-rw-r--r--source/blender/python/gpu/gpu_py_buffer.c4
-rw-r--r--source/blender/python/gpu/gpu_py_framebuffer.c2
-rw-r--r--source/blender/python/gpu/gpu_py_texture.c2
-rw-r--r--source/blender/python/gpu/gpu_py_vertex_buffer.c4
-rw-r--r--source/blender/windowmanager/intern/wm_event_system.cc3
-rw-r--r--source/blender/windowmanager/intern/wm_window.c175
57 files changed, 1047 insertions, 335 deletions
diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h
index adc45285f94..b3799c53a8d 100644
--- a/intern/ghost/GHOST_Types.h
+++ b/intern/ghost/GHOST_Types.h
@@ -121,7 +121,8 @@ typedef enum {
GHOST_kModifierKeyRightAlt,
GHOST_kModifierKeyLeftControl,
GHOST_kModifierKeyRightControl,
- GHOST_kModifierKeyOS,
+ GHOST_kModifierKeyLeftOS,
+ GHOST_kModifierKeyRightOS,
GHOST_kModifierKeyNum
} GHOST_TModifierKey;
@@ -320,13 +321,17 @@ typedef enum {
GHOST_kKeyBackslash = 0x5C,
GHOST_kKeyAccentGrave = '`',
+ /* Modifiers: See #GHOST_KEY_IS_MODIFIER. */
GHOST_kKeyLeftShift = 0x100,
GHOST_kKeyRightShift,
GHOST_kKeyLeftControl,
GHOST_kKeyRightControl,
GHOST_kKeyLeftAlt,
GHOST_kKeyRightAlt,
- GHOST_kKeyOS, /* Command key on Apple, Windows key(s) on Windows. */
+ GHOST_kKeyLeftOS, /* Command key on Apple, Windows key(s) on Windows. */
+ GHOST_kKeyRightOS,
+ /* End modifiers. */
+
GHOST_kKeyGrLess, /* German PC only! */
GHOST_kKeyApp, /* Also known as menu key. */
@@ -400,6 +405,8 @@ typedef enum {
GHOST_kKeyMediaLast
} GHOST_TKey;
+#define GHOST_KEY_IS_MODIFIER(key) (key >= GHOST_kKeyLeftShift && key <= GHOST_kKeyRightOS);
+
typedef enum {
/** Grab not set. */
GHOST_kGrabDisable = 0,
diff --git a/intern/ghost/intern/GHOST_EventPrinter.cpp b/intern/ghost/intern/GHOST_EventPrinter.cpp
index 2620bcc075d..7c20cd701b0 100644
--- a/intern/ghost/intern/GHOST_EventPrinter.cpp
+++ b/intern/ghost/intern/GHOST_EventPrinter.cpp
@@ -220,8 +220,11 @@ void GHOST_EventPrinter::getKeyString(GHOST_TKey key, char str[32]) const
case GHOST_kKeyRightAlt:
tstr = "RightAlt";
break;
- case GHOST_kKeyOS:
- tstr = "OS";
+ case GHOST_kKeyLeftOS:
+ tstr = "LeftOS";
+ break;
+ case GHOST_kKeyRightOS:
+ tstr = "RightOS";
break;
case GHOST_kKeyApp:
tstr = "App";
diff --git a/intern/ghost/intern/GHOST_ModifierKeys.cpp b/intern/ghost/intern/GHOST_ModifierKeys.cpp
index d31dc8f0770..10f2e8019a7 100644
--- a/intern/ghost/intern/GHOST_ModifierKeys.cpp
+++ b/intern/ghost/intern/GHOST_ModifierKeys.cpp
@@ -42,8 +42,11 @@ GHOST_TKey GHOST_ModifierKeys::getModifierKeyCode(GHOST_TModifierKey mask)
case GHOST_kModifierKeyRightControl:
key = GHOST_kKeyRightControl;
break;
- case GHOST_kModifierKeyOS:
- key = GHOST_kKeyOS;
+ case GHOST_kModifierKeyLeftOS:
+ key = GHOST_kKeyLeftOS;
+ break;
+ case GHOST_kModifierKeyRightOS:
+ key = GHOST_kKeyRightOS;
break;
default:
// Should not happen
@@ -68,8 +71,10 @@ bool GHOST_ModifierKeys::get(GHOST_TModifierKey mask) const
return m_LeftControl;
case GHOST_kModifierKeyRightControl:
return m_RightControl;
- case GHOST_kModifierKeyOS:
- return m_OS;
+ case GHOST_kModifierKeyLeftOS:
+ return m_LeftOS;
+ case GHOST_kModifierKeyRightOS:
+ return m_RightOS;
default:
return false;
}
@@ -96,8 +101,11 @@ void GHOST_ModifierKeys::set(GHOST_TModifierKey mask, bool down)
case GHOST_kModifierKeyRightControl:
m_RightControl = down;
break;
- case GHOST_kModifierKeyOS:
- m_OS = down;
+ case GHOST_kModifierKeyLeftOS:
+ m_LeftOS = down;
+ break;
+ case GHOST_kModifierKeyRightOS:
+ m_RightOS = down;
break;
default:
break;
@@ -112,7 +120,8 @@ void GHOST_ModifierKeys::clear()
m_RightAlt = false;
m_LeftControl = false;
m_RightControl = false;
- m_OS = false;
+ m_LeftOS = false;
+ m_RightOS = false;
}
bool GHOST_ModifierKeys::equals(const GHOST_ModifierKeys &keys) const
@@ -120,5 +129,5 @@ bool GHOST_ModifierKeys::equals(const GHOST_ModifierKeys &keys) const
return (m_LeftShift == keys.m_LeftShift) && (m_RightShift == keys.m_RightShift) &&
(m_LeftAlt == keys.m_LeftAlt) && (m_RightAlt == keys.m_RightAlt) &&
(m_LeftControl == keys.m_LeftControl) && (m_RightControl == keys.m_RightControl) &&
- (m_OS == keys.m_OS);
+ (m_LeftOS == keys.m_LeftOS) && (m_RightOS == keys.m_RightOS);
}
diff --git a/intern/ghost/intern/GHOST_ModifierKeys.h b/intern/ghost/intern/GHOST_ModifierKeys.h
index ce1bf3df2ae..f83e861f8b6 100644
--- a/intern/ghost/intern/GHOST_ModifierKeys.h
+++ b/intern/ghost/intern/GHOST_ModifierKeys.h
@@ -68,5 +68,6 @@ struct GHOST_ModifierKeys {
/** Bitfield that stores the appropriate key state. */
uint8_t m_RightControl : 1;
/** Bitfield that stores the appropriate key state. */
- uint8_t m_OS : 1;
+ uint8_t m_LeftOS : 1;
+ uint8_t m_RightOS : 1;
};
diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm
index 5562db7d67f..fe5992343ab 100644
--- a/intern/ghost/intern/GHOST_SystemCocoa.mm
+++ b/intern/ghost/intern/GHOST_SystemCocoa.mm
@@ -856,7 +856,7 @@ GHOST_TSuccess GHOST_SystemCocoa::setMouseCursorPosition(int32_t x, int32_t y)
GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys &keys) const
{
- keys.set(GHOST_kModifierKeyOS, (m_modifierMask & NSEventModifierFlagCommand) ? true : false);
+ keys.set(GHOST_kModifierKeyLeftOS, (m_modifierMask & NSEventModifierFlagCommand) ? true : false);
keys.set(GHOST_kModifierKeyLeftAlt, (m_modifierMask & NSEventModifierFlagOption) ? true : false);
keys.set(GHOST_kModifierKeyLeftShift,
(m_modifierMask & NSEventModifierFlagShift) ? true : false);
@@ -1020,7 +1020,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleApplicationBecomeActiveEvent()
(modifiers & NSEventModifierFlagCommand) ? GHOST_kEventKeyDown :
GHOST_kEventKeyUp,
window,
- GHOST_kKeyOS,
+ GHOST_kKeyLeftOS,
false));
}
@@ -1901,7 +1901,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
[event timestamp] * 1000,
(modifiers & NSEventModifierFlagCommand) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp,
window,
- GHOST_kKeyOS,
+ GHOST_kKeyLeftOS,
false));
}
diff --git a/intern/ghost/intern/GHOST_SystemSDL.cpp b/intern/ghost/intern/GHOST_SystemSDL.cpp
index 6d0b2b8aa55..76770e735fa 100644
--- a/intern/ghost/intern/GHOST_SystemSDL.cpp
+++ b/intern/ghost/intern/GHOST_SystemSDL.cpp
@@ -161,7 +161,8 @@ GHOST_TSuccess GHOST_SystemSDL::getModifierKeys(GHOST_ModifierKeys &keys) const
keys.set(GHOST_kModifierKeyRightControl, (mod & KMOD_RCTRL) != 0);
keys.set(GHOST_kModifierKeyLeftAlt, (mod & KMOD_LALT) != 0);
keys.set(GHOST_kModifierKeyRightAlt, (mod & KMOD_RALT) != 0);
- keys.set(GHOST_kModifierKeyOS, (mod & (KMOD_LGUI | KMOD_RGUI)) != 0);
+ keys.set(GHOST_kModifierKeyLeftOS, (mod & KMOD_LGUI) != 0);
+ keys.set(GHOST_kModifierKeyRightOS, (mod & KMOD_RGUI) != 0);
return GHOST_kSuccess;
}
@@ -219,8 +220,8 @@ static GHOST_TKey convertSDLKey(SDL_Scancode key)
GXMAP(type, SDL_SCANCODE_RCTRL, GHOST_kKeyRightControl);
GXMAP(type, SDL_SCANCODE_LALT, GHOST_kKeyLeftAlt);
GXMAP(type, SDL_SCANCODE_RALT, GHOST_kKeyRightAlt);
- GXMAP(type, SDL_SCANCODE_LGUI, GHOST_kKeyOS);
- GXMAP(type, SDL_SCANCODE_RGUI, GHOST_kKeyOS);
+ GXMAP(type, SDL_SCANCODE_LGUI, GHOST_kKeyLeftOS);
+ GXMAP(type, SDL_SCANCODE_RGUI, GHOST_kKeyRightOS);
GXMAP(type, SDL_SCANCODE_APPLICATION, GHOST_kKeyApp);
GXMAP(type, SDL_SCANCODE_INSERT, GHOST_kKeyInsert);
diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 4c663e98824..0971245fc68 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -613,8 +613,8 @@ static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym)
GXMAP(gkey, XKB_KEY_Control_R, GHOST_kKeyRightControl);
GXMAP(gkey, XKB_KEY_Alt_L, GHOST_kKeyLeftAlt);
GXMAP(gkey, XKB_KEY_Alt_R, GHOST_kKeyRightAlt);
- GXMAP(gkey, XKB_KEY_Super_L, GHOST_kKeyOS);
- GXMAP(gkey, XKB_KEY_Super_R, GHOST_kKeyOS);
+ GXMAP(gkey, XKB_KEY_Super_L, GHOST_kKeyLeftOS);
+ GXMAP(gkey, XKB_KEY_Super_R, GHOST_kKeyRightOS);
GXMAP(gkey, XKB_KEY_Menu, GHOST_kKeyApp);
GXMAP(gkey, XKB_KEY_Caps_Lock, GHOST_kKeyCapsLock);
@@ -2185,8 +2185,8 @@ static void keyboard_handle_enter(void *data,
MOD_TEST_CASE(XKB_KEY_Control_R, GHOST_kKeyRightControl, ctrl);
MOD_TEST_CASE(XKB_KEY_Alt_L, GHOST_kKeyLeftAlt, alt);
MOD_TEST_CASE(XKB_KEY_Alt_R, GHOST_kKeyRightAlt, alt);
- MOD_TEST_CASE(XKB_KEY_Super_L, GHOST_kKeyOS, logo);
- MOD_TEST_CASE(XKB_KEY_Super_R, GHOST_kKeyOS, logo);
+ MOD_TEST_CASE(XKB_KEY_Super_L, GHOST_kKeyLeftOS, logo);
+ MOD_TEST_CASE(XKB_KEY_Super_R, GHOST_kKeyRightOS, logo);
}
#undef MOD_TEST
@@ -3069,7 +3069,8 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co
keys.set(GHOST_kModifierKeyRightControl, val);
val = MOD_TEST(state, seat->xkb_keymap_mod_index.logo);
- keys.set(GHOST_kModifierKeyOS, val);
+ keys.set(GHOST_kModifierKeyLeftOS, val);
+ keys.set(GHOST_kModifierKeyRightOS, val);
val = MOD_TEST(state, seat->xkb_keymap_mod_index.num);
keys.set(GHOST_kModifierKeyNum, val);
diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp
index 6cb36337b55..5a40feb70ae 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemWin32.cpp
@@ -456,14 +456,11 @@ GHOST_TSuccess GHOST_SystemWin32::getModifierKeys(GHOST_ModifierKeys &keys) cons
down = HIBYTE(::GetKeyState(VK_RCONTROL)) != 0;
keys.set(GHOST_kModifierKeyRightControl, down);
- bool lwindown = HIBYTE(::GetKeyState(VK_LWIN)) != 0;
- bool rwindown = HIBYTE(::GetKeyState(VK_RWIN)) != 0;
- if (lwindown || rwindown) {
- keys.set(GHOST_kModifierKeyOS, true);
- }
- else {
- keys.set(GHOST_kModifierKeyOS, false);
- }
+ down = HIBYTE(::GetKeyState(VK_LWIN)) != 0;
+ keys.set(GHOST_kModifierKeyLeftOS, down);
+ down = HIBYTE(::GetKeyState(VK_RWIN)) != 0;
+ keys.set(GHOST_kModifierKeyRightOS, down);
+
return GHOST_kSuccess;
}
@@ -751,8 +748,10 @@ GHOST_TKey GHOST_SystemWin32::convertKey(short vKey, short scanCode, short exten
key = (extend) ? GHOST_kKeyRightAlt : GHOST_kKeyLeftAlt;
break;
case VK_LWIN:
+ key = GHOST_kKeyLeftOS;
+ break;
case VK_RWIN:
- key = GHOST_kKeyOS;
+ key = GHOST_kKeyRightOS;
break;
case VK_APPS:
key = GHOST_kKeyApp;
@@ -1137,12 +1136,18 @@ GHOST_EventKey *GHOST_SystemWin32::processKeyEvent(GHOST_WindowWin32 *window, RA
GHOST_TKey key = system->hardKey(raw, &key_down);
GHOST_EventKey *event;
+ /* NOTE(@campbellbarton): key repeat in WIN32 also applies to modifier-keys.
+ * Check for this case and filter out modifier-repeat.
+ * Typically keyboard events are *not* filtered as part of GHOST's event handling.
+ * As other GHOST back-ends don't have the behavior, it's simplest not to send them through.
+ * Ideally it would be possible to check the key-map for keys that repeat but this doesn't look
+ * to be supported. */
bool is_repeat = false;
bool is_repeated_modifier = false;
if (key_down) {
if (system->m_keycode_last_repeat_key == vk) {
is_repeat = true;
- is_repeated_modifier = (key >= GHOST_kKeyLeftShift && key <= GHOST_kKeyRightAlt);
+ is_repeated_modifier = GHOST_KEY_IS_MODIFIER(key);
}
system->m_keycode_last_repeat_key = vk;
}
diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp
index bb98c0de19b..08ac0edb7ec 100644
--- a/intern/ghost/intern/GHOST_SystemX11.cpp
+++ b/intern/ghost/intern/GHOST_SystemX11.cpp
@@ -1065,7 +1065,8 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
case GHOST_kKeyLeftShift:
case GHOST_kKeyRightControl:
case GHOST_kKeyLeftControl:
- case GHOST_kKeyOS:
+ case GHOST_kKeyLeftOS:
+ case GHOST_kKeyRightOS:
case GHOST_kKey0:
case GHOST_kKey1:
case GHOST_kKey2:
@@ -1600,9 +1601,10 @@ GHOST_TSuccess GHOST_SystemX11::getModifierKeys(GHOST_ModifierKeys &keys) const
keys.set(GHOST_kModifierKeyLeftAlt, ((m_keyboard_vector[alt_l >> 3] >> (alt_l & 7)) & 1) != 0);
keys.set(GHOST_kModifierKeyRightAlt, ((m_keyboard_vector[alt_r >> 3] >> (alt_r & 7)) & 1) != 0);
/* super (windows) - only one GHOST-kModifierKeyOS, so mapping to either */
- keys.set(GHOST_kModifierKeyOS,
- (((m_keyboard_vector[super_l >> 3] >> (super_l & 7)) & 1) ||
- ((m_keyboard_vector[super_r >> 3] >> (super_r & 7)) & 1)) != 0);
+ keys.set(GHOST_kModifierKeyLeftOS,
+ ((m_keyboard_vector[super_l >> 3] >> (super_l & 7)) & 1) != 0);
+ keys.set(GHOST_kModifierKeyRightOS,
+ ((m_keyboard_vector[super_r >> 3] >> (super_r & 7)) & 1) != 0);
return GHOST_kSuccess;
}
@@ -1818,8 +1820,8 @@ static GHOST_TKey ghost_key_from_keysym(const KeySym key)
GXMAP(type, XK_Control_R, GHOST_kKeyRightControl);
GXMAP(type, XK_Alt_L, GHOST_kKeyLeftAlt);
GXMAP(type, XK_Alt_R, GHOST_kKeyRightAlt);
- GXMAP(type, XK_Super_L, GHOST_kKeyOS);
- GXMAP(type, XK_Super_R, GHOST_kKeyOS);
+ GXMAP(type, XK_Super_L, GHOST_kKeyLeftOS);
+ GXMAP(type, XK_Super_R, GHOST_kKeyRightOS);
GXMAP(type, XK_Insert, GHOST_kKeyInsert);
GXMAP(type, XK_Delete, GHOST_kKeyDelete);
diff --git a/intern/mikktspace/mikktspace.hh b/intern/mikktspace/mikktspace.hh
index 4b45fa86e14..93e22658a56 100644
--- a/intern/mikktspace/mikktspace.hh
+++ b/intern/mikktspace/mikktspace.hh
@@ -401,7 +401,7 @@ template<typename Mesh> class Mikktspace {
});
std::stable_partition(triangles.begin(), triangles.end(), [](const Triangle &tri) {
- return tri.markDegenerate;
+ return !tri.markDegenerate;
});
}
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index 78e7932433e..ecc72e1bbb8 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -1606,19 +1606,6 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel):
row.prop(gp_settings, "fill_layer_mode", text="Layers")
col.separator()
- row = col.row(align=True)
- row.prop(gp_settings, "extend_stroke_factor")
- row.prop(
- gp_settings,
- "show_fill_extend",
- icon='HIDE_OFF' if gp_settings.show_fill_extend else 'HIDE_ON',
- text="",
- )
-
- col.separator()
- col.prop(gp_settings, "fill_leak", text="Leak Size")
-
- col.separator()
col.prop(gp_settings, "fill_simplify_level", text="Simplify")
if gp_settings.fill_draw_mode != 'STROKE':
col = layout.column(align=False, heading="Ignore Transparent")
@@ -1878,6 +1865,37 @@ class VIEW3D_PT_tools_grease_pencil_brush_paint_falloff(GreasePencilBrushFalloff
return (settings and settings.brush and settings.brush.curve and gptool == 'TINT')
+class VIEW3D_PT_tools_grease_pencil_brush_gap_closure(View3DPanel, Panel):
+ bl_context = ".greasepencil_paint"
+ bl_parent_id = 'VIEW3D_PT_tools_grease_pencil_brush_advanced'
+ bl_label = "Gap Closure"
+ bl_category = "Tool"
+
+ @classmethod
+ def poll(cls, context):
+ brush = context.tool_settings.gpencil_paint.brush
+ return brush is not None and brush.gpencil_tool == 'FILL'
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ tool_settings = context.tool_settings
+ brush = tool_settings.gpencil_paint.brush
+ gp_settings = brush.gpencil_settings
+
+ col = layout.column()
+
+ col.prop(gp_settings, "extend_stroke_factor", text="Size")
+ row = col.row(align=True)
+ row.enabled = gp_settings.extend_stroke_factor > 0
+ row.prop(gp_settings, "fill_extend_mode", text="Mode")
+ row = col.row(align=True)
+ row.enabled = gp_settings.extend_stroke_factor > 0
+ row.prop(gp_settings, "show_fill_extend", text="Visual Aids")
+
+
# Grease Pencil stroke sculpting tools
class GreasePencilSculptPanel:
bl_context = ".greasepencil_sculpt"
@@ -2430,6 +2448,7 @@ classes = (
VIEW3D_PT_tools_grease_pencil_brush_post_processing,
VIEW3D_PT_tools_grease_pencil_brush_random,
VIEW3D_PT_tools_grease_pencil_brush_stabilizer,
+ VIEW3D_PT_tools_grease_pencil_brush_gap_closure,
VIEW3D_PT_tools_grease_pencil_paint_appearance,
VIEW3D_PT_tools_grease_pencil_sculpt_select,
VIEW3D_PT_tools_grease_pencil_sculpt_settings,
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index b7d09215676..89b729595db 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -128,6 +128,7 @@ def mesh_node_items(context):
yield NodeItem("GeometryNodeInputMeshEdgeVertices")
yield NodeItem("GeometryNodeInputMeshFaceArea")
yield NodeItem("GeometryNodeInputMeshFaceNeighbors")
+ yield NodeItem("GeometryNodeMeshFaceSetBoundaries")
yield NodeItem("GeometryNodeInputMeshFaceIsPlanar")
yield NodeItem("GeometryNodeInputShadeSmooth")
yield NodeItem("GeometryNodeInputMeshIsland")
diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh
index fbdacee139c..946a7d21580 100644
--- a/source/blender/blenkernel/BKE_attribute.hh
+++ b/source/blender/blenkernel/BKE_attribute.hh
@@ -16,6 +16,10 @@
struct Mesh;
struct PointCloud;
+namespace blender::fn {
+class MultiFunction;
+class GField;
+} // namespace blender::fn
namespace blender::bke {
@@ -163,6 +167,27 @@ template<typename T> struct AttributeReader {
};
/**
+ * A utility to make sure attribute values are valid, for attributes like "material_index" which
+ * can only be positive, or attributes that represent enum options. This is usually only necessary
+ * when writing attributes from an untrusted/arbitrary user input.
+ */
+struct AttributeValidator {
+ /**
+ * Single input, single output function that corrects attribute values if necessary.
+ */
+ const fn::MultiFunction *function;
+
+ operator bool() const
+ {
+ return this->function != nullptr;
+ }
+ /**
+ * Return a field that creates corrected attribute values.
+ */
+ fn::GField validate_field_if_necessary(const fn::GField &field) const;
+};
+
+/**
* Result when looking up an attribute from some geometry with read and write access. After writing
* to the attribute, the #finish method has to be called. This may invalidate caches based on this
* attribute.
@@ -343,7 +368,7 @@ struct AttributeAccessorFunctions {
eAttrDomain to_domain);
bool (*for_all)(const void *owner,
FunctionRef<bool(const AttributeIDRef &, const AttributeMetaData &)> fn);
-
+ AttributeValidator (*lookup_validator)(const void *owner, const AttributeIDRef &attribute_id);
GAttributeWriter (*lookup_for_write)(void *owner, const AttributeIDRef &attribute_id);
bool (*remove)(void *owner, const AttributeIDRef &attribute_id);
bool (*add)(void *owner,
@@ -498,6 +523,14 @@ class AttributeAccessor {
}
/**
+ * Same as the generic version above, but should be used when the type is known at compile time.
+ */
+ AttributeValidator lookup_validator(const AttributeIDRef &attribute_id) const
+ {
+ return fn_->lookup_validator(owner_, attribute_id);
+ }
+
+ /**
* Interpolate data from one domain to another.
*/
GVArray adapt_domain(const GVArray &varray,
diff --git a/source/blender/blenkernel/BKE_attribute_math.hh b/source/blender/blenkernel/BKE_attribute_math.hh
index 770937688d7..abd8b33b260 100644
--- a/source/blender/blenkernel/BKE_attribute_math.hh
+++ b/source/blender/blenkernel/BKE_attribute_math.hh
@@ -46,6 +46,58 @@ inline void convert_to_static_type(const eCustomDataType data_type, const Func &
}
/* -------------------------------------------------------------------- */
+/** \name Mix two values of the same type.
+ *
+ * This is just basic linear interpolation.
+ * \{ */
+
+template<typename T> T mix2(float factor, const T &a, const T &b);
+
+template<> inline bool mix2(const float factor, const bool &a, const bool &b)
+{
+ return ((1.0f - factor) * a + factor * b) >= 0.5f;
+}
+
+template<> inline int8_t mix2(const float factor, const int8_t &a, const int8_t &b)
+{
+ return static_cast<int8_t>(std::round((1.0f - factor) * a + factor * b));
+}
+
+template<> inline int mix2(const float factor, const int &a, const int &b)
+{
+ return static_cast<int>(std::round((1.0f - factor) * a + factor * b));
+}
+
+template<> inline float mix2(const float factor, const float &a, const float &b)
+{
+ return (1.0f - factor) * a + factor * b;
+}
+
+template<> inline float2 mix2(const float factor, const float2 &a, const float2 &b)
+{
+ return math::interpolate(a, b, factor);
+}
+
+template<> inline float3 mix2(const float factor, const float3 &a, const float3 &b)
+{
+ return math::interpolate(a, b, factor);
+}
+
+template<>
+inline ColorGeometry4f mix2(const float factor, const ColorGeometry4f &a, const ColorGeometry4f &b)
+{
+ return math::interpolate(a, b, factor);
+}
+
+template<>
+inline ColorGeometry4b mix2(const float factor, const ColorGeometry4b &a, const ColorGeometry4b &b)
+{
+ return math::interpolate(a, b, factor);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Mix three values of the same type.
*
* This is typically used to interpolate values within a triangle.
@@ -117,53 +169,85 @@ inline ColorGeometry4b mix3(const float3 &weights,
/** \} */
/* -------------------------------------------------------------------- */
-/** \name Mix two values of the same type.
+/** \name Mix four values of the same type.
*
- * This is just basic linear interpolation.
* \{ */
-template<typename T> T mix2(float factor, const T &a, const T &b);
+template<typename T>
+T mix4(const float4 &weights, const T &v0, const T &v1, const T &v2, const T &v3);
-template<> inline bool mix2(const float factor, const bool &a, const bool &b)
+template<>
+inline int8_t mix4(
+ const float4 &weights, const int8_t &v0, const int8_t &v1, const int8_t &v2, const int8_t &v3)
{
- return ((1.0f - factor) * a + factor * b) >= 0.5f;
+ return static_cast<int8_t>(
+ std::round(weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3));
}
-template<> inline int8_t mix2(const float factor, const int8_t &a, const int8_t &b)
+template<>
+inline bool mix4(
+ const float4 &weights, const bool &v0, const bool &v1, const bool &v2, const bool &v3)
{
- return static_cast<int8_t>(std::round((1.0f - factor) * a + factor * b));
+ return (weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3) >= 0.5f;
}
-template<> inline int mix2(const float factor, const int &a, const int &b)
+template<>
+inline int mix4(const float4 &weights, const int &v0, const int &v1, const int &v2, const int &v3)
{
- return static_cast<int>(std::round((1.0f - factor) * a + factor * b));
+ return static_cast<int>(
+ std::round(weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3));
}
-template<> inline float mix2(const float factor, const float &a, const float &b)
+template<>
+inline float mix4(
+ const float4 &weights, const float &v0, const float &v1, const float &v2, const float &v3)
{
- return (1.0f - factor) * a + factor * b;
+ return weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3;
}
-template<> inline float2 mix2(const float factor, const float2 &a, const float2 &b)
+template<>
+inline float2 mix4(
+ const float4 &weights, const float2 &v0, const float2 &v1, const float2 &v2, const float2 &v3)
{
- return math::interpolate(a, b, factor);
+ return weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3;
}
-template<> inline float3 mix2(const float factor, const float3 &a, const float3 &b)
+template<>
+inline float3 mix4(
+ const float4 &weights, const float3 &v0, const float3 &v1, const float3 &v2, const float3 &v3)
{
- return math::interpolate(a, b, factor);
+ return weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3;
}
template<>
-inline ColorGeometry4f mix2(const float factor, const ColorGeometry4f &a, const ColorGeometry4f &b)
+inline ColorGeometry4f mix4(const float4 &weights,
+ const ColorGeometry4f &v0,
+ const ColorGeometry4f &v1,
+ const ColorGeometry4f &v2,
+ const ColorGeometry4f &v3)
{
- return math::interpolate(a, b, factor);
+ ColorGeometry4f result;
+ interp_v4_v4v4v4v4(result, v0, v1, v2, v3, weights);
+ return result;
}
template<>
-inline ColorGeometry4b mix2(const float factor, const ColorGeometry4b &a, const ColorGeometry4b &b)
+inline ColorGeometry4b mix4(const float4 &weights,
+ const ColorGeometry4b &v0,
+ const ColorGeometry4b &v1,
+ const ColorGeometry4b &v2,
+ const ColorGeometry4b &v3)
{
- return math::interpolate(a, b, factor);
+ const float4 v0_f{&v0.r};
+ const float4 v1_f{&v1.r};
+ const float4 v2_f{&v2.r};
+ const float4 v3_f{&v3.r};
+ float4 mixed;
+ interp_v4_v4v4v4v4(mixed, v0_f, v1_f, v2_f, v3_f, weights);
+ return ColorGeometry4b{static_cast<uint8_t>(mixed[0]),
+ static_cast<uint8_t>(mixed[1]),
+ static_cast<uint8_t>(mixed[2]),
+ static_cast<uint8_t>(mixed[3])};
}
/** \} */
diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 9f150c13d6e..0d67152dec8 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -218,7 +218,7 @@ class CurvesGeometry : public ::CurvesGeometry {
/**
* How many evaluated points to create for each segment when evaluating Bezier,
- * Catmull Rom, and NURBS curves. On the curve domain.
+ * Catmull Rom, and NURBS curves. On the curve domain. Values must be one or greater.
*/
VArray<int> resolution() const;
/** Mutable access to curve resolution. Call #tag_topology_changed after changes. */
@@ -709,7 +709,7 @@ void interpolate_to_evaluated(const GSpan src,
const Span<int> evaluated_offsets,
GMutableSpan dst);
-void calculate_basis(const float parameter, float r_weights[4]);
+void calculate_basis(const float parameter, float4 &r_weights);
/**
* Interpolate the control point values for the given parameter on the piecewise segment.
@@ -720,22 +720,15 @@ void calculate_basis(const float parameter, float r_weights[4]);
template<typename T>
T interpolate(const T &a, const T &b, const T &c, const T &d, const float parameter)
{
- float n[4];
+ BLI_assert(0.0f <= parameter && parameter <= 1.0f);
+ float4 n;
calculate_basis(parameter, n);
- /* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify
- * supporting more types. */
- if constexpr (!is_same_any_v<T, float, float2, float3, float4, int8_t, int, int64_t>) {
- T return_value;
- attribute_math::DefaultMixer<T> mixer({&return_value, 1});
- mixer.mix_in(0, a, n[0] * 0.5f);
- mixer.mix_in(0, b, n[1] * 0.5f);
- mixer.mix_in(0, c, n[2] * 0.5f);
- mixer.mix_in(0, d, n[3] * 0.5f);
- mixer.finalize();
- return return_value;
+ if constexpr (is_same_any_v<T, float, float2, float3>) {
+ /* Save multiplications by adjusting weights after mix. */
+ return 0.5f * attribute_math::mix4<T>(n, a, b, c, d);
}
else {
- return 0.5f * (a * n[0] + b * n[1] + c * n[2] + d * n[3]);
+ return attribute_math::mix4<T>(n * 0.5f, a, b, c, d);
}
}
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 55bf24f943e..75654a86409 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1525,6 +1525,7 @@ struct TexResult;
#define GEO_NODE_INPUT_SHORTEST_EDGE_PATHS 1168
#define GEO_NODE_EDGE_PATHS_TO_CURVES 1169
#define GEO_NODE_EDGE_PATHS_TO_SELECTION 1170
+#define GEO_NODE_MESH_FACE_SET_BOUNDARIES 1171
/** \} */
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index e7c1e35f851..df7787986db 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -19,6 +19,8 @@
#include "BLI_math_vec_types.hh"
#include "BLI_span.hh"
+#include "FN_field.hh"
+
#include "BLT_translation.h"
#include "CLG_log.h"
@@ -945,6 +947,15 @@ GSpanAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write_only_span
return {};
}
+fn::GField AttributeValidator::validate_field_if_necessary(const fn::GField &field) const
+{
+ if (function) {
+ auto validate_op = fn::FieldOperation::Create(*function, {field});
+ return fn::GField(validate_op);
+ }
+ return field;
+}
+
Vector<AttributeTransferData> retrieve_attributes_for_transfer(
const bke::AttributeAccessor src_attributes,
bke::MutableAttributeAccessor dst_attributes,
diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh
index 8050f45da94..5fbca283399 100644
--- a/source/blender/blenkernel/intern/attribute_access_intern.hh
+++ b/source/blender/blenkernel/intern/attribute_access_intern.hh
@@ -53,6 +53,7 @@ class BuiltinAttributeProvider {
const CreatableEnum createable_;
const WritableEnum writable_;
const DeletableEnum deletable_;
+ const AttributeValidator validator_;
public:
BuiltinAttributeProvider(std::string name,
@@ -60,13 +61,15 @@ class BuiltinAttributeProvider {
const eCustomDataType data_type,
const CreatableEnum createable,
const WritableEnum writable,
- const DeletableEnum deletable)
+ const DeletableEnum deletable,
+ AttributeValidator validator = {})
: name_(std::move(name)),
domain_(domain),
data_type_(data_type),
createable_(createable),
writable_(writable),
- deletable_(deletable)
+ deletable_(deletable),
+ validator_(validator)
{
}
@@ -90,6 +93,11 @@ class BuiltinAttributeProvider {
{
return data_type_;
}
+
+ AttributeValidator validator() const
+ {
+ return validator_;
+ }
};
/**
@@ -241,9 +249,15 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider {
const CustomDataAccessInfo custom_data_access,
const AsReadAttribute as_read_attribute,
const AsWriteAttribute as_write_attribute,
- const UpdateOnChange update_on_write)
- : BuiltinAttributeProvider(
- std::move(attribute_name), domain, attribute_type, creatable, writable, deletable),
+ const UpdateOnChange update_on_write,
+ const AttributeValidator validator = {})
+ : BuiltinAttributeProvider(std::move(attribute_name),
+ domain,
+ attribute_type,
+ creatable,
+ writable,
+ deletable,
+ validator),
stored_type_(stored_type),
custom_data_access_(custom_data_access),
as_read_attribute_(as_read_attribute),
@@ -379,6 +393,21 @@ inline bool for_all(const void *owner,
}
template<const ComponentAttributeProviders &providers>
+inline AttributeValidator lookup_validator(const void * /*owner*/,
+ const blender::bke::AttributeIDRef &attribute_id)
+{
+ if (!attribute_id.is_named()) {
+ return {};
+ }
+ const BuiltinAttributeProvider *provider =
+ providers.builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (!provider) {
+ return {};
+ }
+ return provider->validator();
+}
+
+template<const ComponentAttributeProviders &providers>
inline bool contains(const void *owner, const blender::bke::AttributeIDRef &attribute_id)
{
bool found = false;
@@ -489,6 +518,7 @@ inline AttributeAccessorFunctions accessor_functions_for_providers()
lookup<providers>,
nullptr,
for_all<providers>,
+ lookup_validator<providers>,
lookup_for_write<providers>,
remove<providers>,
add<providers>};
diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc
index c206a04fecc..a998fc0a75f 100644
--- a/source/blender/blenkernel/intern/brush.cc
+++ b/source/blender/blenkernel/intern/brush.cc
@@ -982,7 +982,6 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type)
case GP_BRUSH_PRESET_FILL_AREA: {
brush->size = 5.0f;
- brush->gpencil_settings->fill_leak = 3;
brush->gpencil_settings->fill_threshold = 0.1f;
brush->gpencil_settings->fill_simplylvl = 1;
brush->gpencil_settings->fill_factor = 1.0f;
diff --git a/source/blender/blenkernel/intern/curve_catmull_rom.cc b/source/blender/blenkernel/intern/curve_catmull_rom.cc
index dac88948036..b5f1a7cc450 100644
--- a/source/blender/blenkernel/intern/curve_catmull_rom.cc
+++ b/source/blender/blenkernel/intern/curve_catmull_rom.cc
@@ -17,7 +17,7 @@ int calculate_evaluated_num(const int points_num, const bool cyclic, const int r
}
/* Adapted from Cycles #catmull_rom_basis_eval function. */
-void calculate_basis(const float parameter, float r_weights[4])
+void calculate_basis(const float parameter, float4 &r_weights)
{
const float t = parameter;
const float s = 1.0f - parameter;
@@ -139,11 +139,7 @@ void interpolate_to_evaluated(const GSpan src,
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
- /* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify
- * supporting more types. */
- if constexpr (is_same_any_v<T, float, float2, float3, float4, int8_t, int, int64_t>) {
- interpolate_to_evaluated(src.typed<T>(), cyclic, resolution, dst.typed<T>());
- }
+ interpolate_to_evaluated(src.typed<T>(), cyclic, resolution, dst.typed<T>());
});
}
@@ -154,11 +150,7 @@ void interpolate_to_evaluated(const GSpan src,
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
- /* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify
- * supporting more types. */
- if constexpr (is_same_any_v<T, float, float2, float3, float4, int8_t, int, int64_t>) {
- interpolate_to_evaluated(src.typed<T>(), cyclic, evaluated_offsets, dst.typed<T>());
- }
+ interpolate_to_evaluated(src.typed<T>(), cyclic, evaluated_offsets, dst.typed<T>());
});
}
diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc
index 83a35534c01..4ace68546ac 100644
--- a/source/blender/blenkernel/intern/geometry_component_curves.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curves.cc
@@ -12,6 +12,8 @@
#include "BKE_geometry_set.hh"
#include "BKE_lib_id.h"
+#include "FN_multi_function_builder.hh"
+
#include "attribute_access_intern.hh"
using blender::GVArray;
@@ -426,6 +428,12 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
make_array_write_attribute<float3>,
tag_component_positions_changed);
+ static const fn::CustomMF_SI_SO<int8_t, int8_t> handle_type_clamp{
+ "Handle Type Validate",
+ [](int8_t value) {
+ return std::clamp<int8_t>(value, BEZIER_HANDLE_FREE, BEZIER_HANDLE_ALIGN);
+ },
+ fn::CustomMF_presets::AllSpanOrSingle()};
static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right",
ATTR_DOMAIN_POINT,
CD_PROP_INT8,
@@ -436,7 +444,8 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
point_access,
make_array_read_attribute<int8_t>,
make_array_write_attribute<int8_t>,
- tag_component_topology_changed);
+ tag_component_topology_changed,
+ AttributeValidator{&handle_type_clamp});
static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left",
ATTR_DOMAIN_POINT,
@@ -448,7 +457,8 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
point_access,
make_array_read_attribute<int8_t>,
make_array_write_attribute<int8_t>,
- tag_component_topology_changed);
+ tag_component_topology_changed,
+ AttributeValidator{&handle_type_clamp});
static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight",
ATTR_DOMAIN_POINT,
@@ -462,6 +472,10 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
make_array_write_attribute<float>,
tag_component_positions_changed);
+ static const fn::CustomMF_SI_SO<int8_t, int8_t> nurbs_order_clamp{
+ "NURBS Order Validate",
+ [](int8_t value) { return std::max<int8_t>(value, 0); },
+ fn::CustomMF_presets::AllSpanOrSingle()};
static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order",
ATTR_DOMAIN_CURVE,
CD_PROP_INT8,
@@ -472,8 +486,15 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
curve_access,
make_array_read_attribute<int8_t>,
make_array_write_attribute<int8_t>,
- tag_component_topology_changed);
+ tag_component_topology_changed,
+ AttributeValidator{&nurbs_order_clamp});
+ static const fn::CustomMF_SI_SO<int8_t, int8_t> normal_mode_clamp{
+ "Normal Mode Validate",
+ [](int8_t value) {
+ return std::clamp<int8_t>(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_Z_UP);
+ },
+ fn::CustomMF_presets::AllSpanOrSingle()};
static BuiltinCustomDataLayerProvider normal_mode("normal_mode",
ATTR_DOMAIN_CURVE,
CD_PROP_INT8,
@@ -484,8 +505,15 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
curve_access,
make_array_read_attribute<int8_t>,
make_array_write_attribute<int8_t>,
- tag_component_normals_changed);
+ tag_component_normals_changed,
+ AttributeValidator{&normal_mode_clamp});
+ static const fn::CustomMF_SI_SO<int8_t, int8_t> knots_mode_clamp{
+ "Knots Mode Validate",
+ [](int8_t value) {
+ return std::clamp<int8_t>(value, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
+ },
+ fn::CustomMF_presets::AllSpanOrSingle()};
static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode",
ATTR_DOMAIN_CURVE,
CD_PROP_INT8,
@@ -496,8 +524,15 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
curve_access,
make_array_read_attribute<int8_t>,
make_array_write_attribute<int8_t>,
- tag_component_topology_changed);
+ tag_component_topology_changed,
+ AttributeValidator{&knots_mode_clamp});
+ static const fn::CustomMF_SI_SO<int8_t, int8_t> curve_type_clamp{
+ "Curve Type Validate",
+ [](int8_t value) {
+ return std::clamp<int8_t>(value, CURVE_TYPE_CATMULL_ROM, CURVE_TYPES_NUM);
+ },
+ fn::CustomMF_presets::AllSpanOrSingle()};
static BuiltinCustomDataLayerProvider curve_type("curve_type",
ATTR_DOMAIN_CURVE,
CD_PROP_INT8,
@@ -508,8 +543,13 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
curve_access,
make_array_read_attribute<int8_t>,
make_array_write_attribute<int8_t>,
- tag_component_curve_types_changed);
+ tag_component_curve_types_changed,
+ AttributeValidator{&curve_type_clamp});
+ static const fn::CustomMF_SI_SO<int, int> resolution_clamp{
+ "Resolution Validate",
+ [](int value) { return std::max<int>(value, 1); },
+ fn::CustomMF_presets::AllSpanOrSingle()};
static BuiltinCustomDataLayerProvider resolution("resolution",
ATTR_DOMAIN_CURVE,
CD_PROP_INT32,
@@ -520,7 +560,8 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
curve_access,
make_array_read_attribute<int>,
make_array_write_attribute<int>,
- tag_component_topology_changed);
+ tag_component_topology_changed,
+ AttributeValidator{&resolution_clamp});
static BuiltinCustomDataLayerProvider cyclic("cyclic",
ATTR_DOMAIN_CURVE,
diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc
index 715c7d6c743..255d0e92964 100644
--- a/source/blender/blenkernel/intern/geometry_component_mesh.cc
+++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc
@@ -14,6 +14,8 @@
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
+#include "FN_multi_function_builder.hh"
+
#include "attribute_access_intern.hh"
extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id);
@@ -1217,6 +1219,13 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
make_array_write_attribute<int>,
nullptr);
+ static const fn::CustomMF_SI_SO<int, int> material_index_clamp{
+ "Material Index Validate",
+ [](int value) {
+ /* Use #short for the maximum since many areas still use that type for indices. */
+ return std::clamp<int>(value, 0, std::numeric_limits<short>::max());
+ },
+ fn::CustomMF_presets::AllSpanOrSingle()};
static BuiltinCustomDataLayerProvider material_index("material_index",
ATTR_DOMAIN_FACE,
CD_PROP_INT32,
@@ -1227,7 +1236,8 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
face_access,
make_array_read_attribute<int>,
make_array_write_attribute<int>,
- nullptr);
+ nullptr,
+ AttributeValidator{&material_index_clamp});
static BuiltinCustomDataLayerProvider shade_smooth(
"shade_smooth",
diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc
index 44e0e7be97b..000e51c0150 100644
--- a/source/blender/blenkernel/intern/image.cc
+++ b/source/blender/blenkernel/intern/image.cc
@@ -5567,15 +5567,14 @@ bool BKE_image_remove_renderslot(Image *ima, ImageUser *iuser, int slot)
next_last_slot = current_slot;
}
- if (!iuser) {
+ if (iuser == nullptr || iuser->scene == nullptr) {
return false;
}
Render *re = RE_GetSceneRender(iuser->scene);
- if (!re) {
- return false;
+ if (re != nullptr) {
+ RE_SwapResult(re, &current_last_slot->render);
+ RE_SwapResult(re, &next_last_slot->render);
}
- RE_SwapResult(re, &current_last_slot->render);
- RE_SwapResult(re, &next_last_slot->render);
current_last_slot = next_last_slot;
}
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index c462d03e3da..1af04d3034c 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4748,6 +4748,7 @@ static void registerGeometryNodes()
register_node_type_geo_material_replace();
register_node_type_geo_material_selection();
register_node_type_geo_merge_by_distance();
+ register_node_type_geo_mesh_face_set_boundaries();
register_node_type_geo_mesh_primitive_circle();
register_node_type_geo_mesh_primitive_cone();
register_node_type_geo_mesh_primitive_cube();
diff --git a/source/blender/blenlib/BLI_array_utils.hh b/source/blender/blenlib/BLI_array_utils.hh
index cf2f948b0b4..95b3bde10f4 100644
--- a/source/blender/blenlib/BLI_array_utils.hh
+++ b/source/blender/blenlib/BLI_array_utils.hh
@@ -6,6 +6,7 @@
#include "BLI_generic_virtual_array.hh"
#include "BLI_index_mask.hh"
#include "BLI_task.hh"
+#include "BLI_virtual_array.hh"
namespace blender::array_utils {
@@ -25,6 +26,7 @@ inline void copy(const Span<T> src,
MutableSpan<T> dst,
const int64_t grain_size = 4096)
{
+ BLI_assert(src.size() == dst.size());
threading::parallel_for(selection.index_range(), grain_size, [&](const IndexRange range) {
for (const int64_t index : selection.slice(range)) {
dst[index] = src[index];
@@ -32,4 +34,77 @@ inline void copy(const Span<T> src,
});
}
+/**
+ * Fill the destination span by gathering indexed values from the `src` array.
+ */
+void gather(const GVArray &src, IndexMask indices, GMutableSpan dst, int64_t grain_size = 4096);
+
+/**
+ * Fill the destination span by gathering indexed values from the `src` array.
+ */
+template<typename T>
+inline void gather(const VArray<T> &src,
+ const IndexMask indices,
+ MutableSpan<T> dst,
+ const int64_t grain_size = 4096)
+{
+ BLI_assert(indices.size() == dst.size());
+ threading::parallel_for(indices.index_range(), grain_size, [&](const IndexRange range) {
+ src.materialize_compressed_to_uninitialized(indices.slice(range), dst.slice(range).data());
+ });
+}
+
+/**
+ * Fill the destination span by gathering indexed values from the `src` array.
+ */
+template<typename T, typename IndexT>
+inline void gather(const Span<T> src,
+ const IndexMask indices,
+ MutableSpan<T> dst,
+ const int64_t grain_size = 4096)
+{
+ BLI_assert(indices.size() == dst.size());
+ threading::parallel_for(indices.index_range(), grain_size, [&](const IndexRange range) {
+ for (const int64_t i : range) {
+ dst[i] = src[indices[i]];
+ }
+ });
+}
+
+/**
+ * Fill the destination span by gathering indexed values from the `src` array.
+ */
+template<typename T, typename IndexT>
+inline void gather(const Span<T> src,
+ const Span<IndexT> indices,
+ MutableSpan<T> dst,
+ const int64_t grain_size = 4096)
+{
+ BLI_assert(indices.size() == dst.size());
+ threading::parallel_for(indices.index_range(), grain_size, [&](const IndexRange range) {
+ for (const int64_t i : range) {
+ dst[i] = src[indices[i]];
+ }
+ });
+}
+
+/**
+ * Fill the destination span by gathering indexed values from the `src` array.
+ */
+template<typename T, typename IndexT>
+inline void gather(const VArray<T> &src,
+ const Span<IndexT> indices,
+ MutableSpan<T> dst,
+ const int64_t grain_size = 4096)
+{
+ BLI_assert(indices.size() == dst.size());
+ devirtualize_varray(src, [&](const auto &src) {
+ threading::parallel_for(indices.index_range(), grain_size, [&](const IndexRange range) {
+ for (const int64_t i : range) {
+ dst[i] = src[indices[i]];
+ }
+ });
+ });
+}
+
} // namespace blender::array_utils
diff --git a/source/blender/blenlib/BLI_index_range.hh b/source/blender/blenlib/BLI_index_range.hh
index a0f129c7324..ba49664e91f 100644
--- a/source/blender/blenlib/BLI_index_range.hh
+++ b/source/blender/blenlib/BLI_index_range.hh
@@ -140,6 +140,10 @@ class IndexRange {
{
return (a.size_ == b.size_) && (a.start_ == b.start_ || a.size_ == 0);
}
+ constexpr friend bool operator!=(IndexRange a, IndexRange b)
+ {
+ return !(a == b);
+ }
/**
* Get the amount of numbers in the range.
@@ -248,6 +252,19 @@ class IndexRange {
}
/**
+ * Returns a new range, that contains the intersection of the current one with the given range.
+ * Returns empty range is no intersection exists.
+ * Returned range is always a valid slice of this range.
+ */
+ constexpr IndexRange intersect(IndexRange other) const
+ {
+ const int64_t old_end = start_ + size_;
+ const int64_t new_start = std::min(old_end, std::max(start_, other.start_));
+ const int64_t new_end = std::max(new_start, std::min(old_end, other.start_ + other.size_));
+ return IndexRange(new_start, new_end - new_start);
+ }
+
+ /**
* Returns a new IndexRange with n elements removed from the beginning of the range.
* This invokes undefined behavior when n is negative.
*/
diff --git a/source/blender/blenlib/intern/array_utils.cc b/source/blender/blenlib/intern/array_utils.cc
index a0fc8810199..a837d6aceec 100644
--- a/source/blender/blenlib/intern/array_utils.cc
+++ b/source/blender/blenlib/intern/array_utils.cc
@@ -1,7 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
-#include "BLI_task.hh"
namespace blender::array_utils {
@@ -17,4 +16,16 @@ void copy(const GVArray &src,
});
}
+void gather(const GVArray &src,
+ const IndexMask indices,
+ GMutableSpan dst,
+ const int64_t grain_size)
+{
+ BLI_assert(src.type() == dst.type());
+ BLI_assert(indices.size() == dst.size());
+ threading::parallel_for(indices.index_range(), grain_size, [&](const IndexRange range) {
+ src.materialize_compressed_to_uninitialized(indices.slice(range), dst.slice(range).data());
+ });
+}
+
} // namespace blender::array_utils
diff --git a/source/blender/blenlib/tests/BLI_index_range_test.cc b/source/blender/blenlib/tests/BLI_index_range_test.cc
index 955691b3430..8ec7ad85d9c 100644
--- a/source/blender/blenlib/tests/BLI_index_range_test.cc
+++ b/source/blender/blenlib/tests/BLI_index_range_test.cc
@@ -126,6 +126,17 @@ TEST(index_range, Slice)
EXPECT_EQ(slice.last(), 12);
}
+TEST(index_range, Intersect)
+{
+ IndexRange range = IndexRange(5, 15);
+ EXPECT_EQ(range.intersect(IndexRange(2, 2)), IndexRange(5, 0));
+ EXPECT_EQ(range.intersect(IndexRange(4, 2)), IndexRange(5, 1));
+ EXPECT_EQ(range.intersect(IndexRange(3, 20)), IndexRange(5, 15));
+ EXPECT_EQ(range.intersect(IndexRange(5, 15)), IndexRange(5, 15));
+ EXPECT_EQ(range.intersect(IndexRange(15, 10)), IndexRange(15, 5));
+ EXPECT_EQ(range.intersect(IndexRange(22, 2)), IndexRange(20, 0));
+}
+
TEST(index_range, SliceRange)
{
IndexRange range = IndexRange(5, 15);
diff --git a/source/blender/blenloader/CMakeLists.txt b/source/blender/blenloader/CMakeLists.txt
index f99fb21e994..bf99ca2cd9a 100644
--- a/source/blender/blenloader/CMakeLists.txt
+++ b/source/blender/blenloader/CMakeLists.txt
@@ -88,11 +88,10 @@ if(WITH_TBB)
${TBB_INCLUDE_DIRS}
)
add_definitions(-DWITH_TBB)
- if(WIN32)
- # TBB includes Windows.h which will define min/max macros
- # that will collide with the stl versions.
- add_definitions(-DNOMINMAX)
- endif()
+endif()
+
+if(WIN32)
+ add_definitions(-DNOMINMAX)
endif()
blender_add_lib(bf_blenloader "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/draw/intern/DRW_gpu_wrapper.hh b/source/blender/draw/intern/DRW_gpu_wrapper.hh
index 890cd588527..90ba926f0d1 100644
--- a/source/blender/draw/intern/DRW_gpu_wrapper.hh
+++ b/source/blender/draw/intern/DRW_gpu_wrapper.hh
@@ -31,6 +31,10 @@
* discarding all data inside it.
* Data can be accessed using the [] operator.
*
+ * `draw::StorageVectorBuffer<T, len>`
+ * Same as `StorageArrayBuffer` but has a length counter and act like a `blender::Vector` you can
+ * clear and append to.
+ *
* `draw::StorageBuffer<T>`
* A storage buffer object class inheriting from T.
* Data can be accessed just like a normal T object.
@@ -313,8 +317,8 @@ class UniformBuffer : public T, public detail::UniformCommon<T, 1, false> {
template<
/** Type of the values stored in this uniform buffer. */
typename T,
- /** The number of values that can be stored in this uniform buffer. */
- int64_t len,
+ /** The number of values that can be stored in this storage buffer at creation. */
+ int64_t len = 16u / sizeof(T),
/** True if created on device and no memory host memory is allocated. */
bool device_only = false>
class StorageArrayBuffer : public detail::StorageCommon<T, len, device_only> {
@@ -357,6 +361,71 @@ class StorageArrayBuffer : public detail::StorageCommon<T, len, device_only> {
}
return this->data_[index];
}
+
+ int64_t size() const
+ {
+ return this->len_;
+ }
+};
+
+template<
+ /** Type of the values stored in this uniform buffer. */
+ typename T,
+ /** The number of values that can be stored in this storage buffer at creation. */
+ int64_t len = 16u / sizeof(T)>
+class StorageVectorBuffer : public StorageArrayBuffer<T, len, false> {
+ private:
+ /* Number of items, not the allocated length. */
+ int64_t item_len_ = 0;
+
+ public:
+ StorageVectorBuffer(const char *name = nullptr) : StorageArrayBuffer<T, len, false>(name){};
+ ~StorageVectorBuffer(){};
+
+ /**
+ * Set item count to zero but does not free memory or resize the buffer.
+ */
+ void clear()
+ {
+ item_len_ = 0;
+ }
+
+ /**
+ * Insert a new element at the end of the vector.
+ * This might cause a reallocation with the capacity is exceeded.
+ *
+ * This is similar to std::vector::push_back.
+ */
+ void append(const T &value)
+ {
+ this->append_as(value);
+ }
+ void append(T &&value)
+ {
+ this->append_as(std::move(value));
+ }
+ template<typename... ForwardT> void append_as(ForwardT &&...value)
+ {
+ if (item_len_ >= this->len_) {
+ size_t size = power_of_2_max_u(item_len_ + 1);
+ this->resize(size);
+ }
+ T *ptr = &this->data_[item_len_++];
+ new (ptr) T(std::forward<ForwardT>(value)...);
+ }
+
+ int64_t size() const
+ {
+ return item_len_;
+ }
+
+ bool is_empty() const
+ {
+ return this->size() == 0;
+ }
+
+ /* Avoid confusion with the other clear. */
+ void clear_to_zero() = delete;
};
template<
@@ -859,8 +928,7 @@ class TextureFromPool : public Texture, NonMovable {
* Dummy type to bind texture as image.
* It is just a GPUTexture in disguise.
*/
-class Image {
-};
+class Image {};
static inline Image *as_image(GPUTexture *tex)
{
diff --git a/source/blender/draw/intern/draw_debug.cc b/source/blender/draw/intern/draw_debug.cc
index b0662a42ea0..811185395fd 100644
--- a/source/blender/draw/intern/draw_debug.cc
+++ b/source/blender/draw/intern/draw_debug.cc
@@ -85,6 +85,9 @@ void DebugDraw::init()
gpu_draw_buf_.command.instance_first_array = 0;
gpu_draw_buf_used = false;
+ print_col_ = 0;
+ print_row_ = 0;
+
modelmat_reset();
}
diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 5305c764b3a..6eb49cd933a 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -67,6 +67,7 @@
#define LEAK_HORZ 0
#define LEAK_VERT 1
+#define FILL_LEAK 3.0f
#define MIN_WINDOW_SIZE 128
/* Set to 1 to debug filling internal image. By default, the value must be 0. */
@@ -140,6 +141,8 @@ typedef struct tGPDfill {
int fill_simplylvl;
/** boundary limits drawing mode */
int fill_draw_mode;
+ /** types of extensions **/
+ int fill_extend_mode;
/* scaling factor */
float fill_factor;
@@ -197,7 +200,8 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
/* free stroke */
- if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) {
+ if ((gps->flag & GP_STROKE_NOFILL) &&
+ (gps->flag & GP_STROKE_TAG || gps->flag & GP_STROKE_HELP)) {
BLI_remlink(&gpf->strokes, gps);
BKE_gpencil_free_stroke(gps);
}
@@ -209,6 +213,64 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_
}
}
+static bool extended_bbox_overlap(
+ float min1[3], float max1[3], float min2[3], float max2[3], float extend)
+{
+ for (int axis = 0; axis < 3; axis++) {
+ float intersection_min = max_ff(min1[axis], min2[axis]) - extend;
+ float intersection_max = min_ff(max1[axis], max2[axis]) + extend;
+ if (intersection_min > intersection_max) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void add_stroke_extension(bGPDframe *gpf, bGPDstroke *gps, float p1[3], float p2[3])
+{
+ bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ bGPDspoint *pt = &gps_new->points[0];
+ copy_v3_v3(&pt->x, p1);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &gps_new->points[1];
+ copy_v3_v3(&pt->x, p2);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+}
+
+static void add_endpoint_radius_help(bGPDframe *gpf,
+ bGPDstroke *gps,
+ const float endpoint[3],
+ const float radius,
+ const bool focused)
+{
+ float circumference = 2.0f * M_PI * radius;
+ float vertex_spacing = 0.005f;
+ int num_vertices = min_ii(max_ii((int)ceilf(circumference / vertex_spacing), 3), 40);
+
+ bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, num_vertices, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_CYCLIC | GP_STROKE_HELP;
+ if (focused) {
+ gps_new->flag |= GP_STROKE_TAG;
+ }
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ for (int i = 0; i < num_vertices; i++) {
+ float angle = ((float)i / (float)num_vertices) * 2.0f * M_PI;
+ bGPDspoint *pt = &gps_new->points[i];
+ pt->x = endpoint[0] + radius * cosf(angle);
+ pt->y = endpoint[1];
+ pt->z = endpoint[2] + radius * sinf(angle);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+ }
+}
+
static void extrapolate_points_by_length(bGPDspoint *a,
bGPDspoint *b,
float length,
@@ -235,6 +297,9 @@ static void gpencil_create_extensions(tGPDfill *tgpf)
const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active);
BLI_assert(gpl_active_index >= 0);
+ float connection_dist = tgpf->fill_extend_fac * 0.1f;
+ GSet *connected_endpoints = BLI_gset_ptr_new(__func__);
+
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if (gpl->flag & GP_LAYER_HIDE) {
continue;
@@ -266,41 +331,158 @@ static void gpencil_create_extensions(tGPDfill *tgpf)
continue;
}
- /* Extend start. */
- bGPDspoint *pt0 = &gps->points[1];
- bGPDspoint *pt1 = &gps->points[0];
- bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
- gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
- BLI_addtail(&gpf->strokes, gps_new);
-
- bGPDspoint *pt = &gps_new->points[0];
- copy_v3_v3(&pt->x, &pt1->x);
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
-
- pt = &gps_new->points[1];
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
- extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x);
-
- /* Extend end. */
- pt0 = &gps->points[gps->totpoints - 2];
- pt1 = &gps->points[gps->totpoints - 1];
- gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
- gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
- BLI_addtail(&gpf->strokes, gps_new);
-
- pt = &gps_new->points[0];
- copy_v3_v3(&pt->x, &pt1->x);
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
-
- pt = &gps_new->points[1];
- pt->strength = 1.0f;
- pt->pressure = 1.0f;
- extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x);
+ /* Find points of high curvature. */
+ float tan1[3];
+ float tan2[3];
+ float d1;
+ float d2;
+ float total_length = 0.f;
+ for (int i = 1; i < gps->totpoints; i++) {
+ if (i > 1) {
+ copy_v3_v3(tan1, tan2);
+ d1 = d2;
+ }
+ bGPDspoint *pt1 = &gps->points[i - 1];
+ bGPDspoint *pt2 = &gps->points[i];
+ sub_v3_v3v3(tan2, &pt2->x, &pt1->x);
+ d2 = normalize_v3(tan2);
+ total_length += d2;
+ if (i > 1) {
+ if (tgpf->fill_extend_mode == GP_FILL_EMODE_RADIUS) {
+ continue;
+ }
+ float curvature[3];
+ sub_v3_v3v3(curvature, tan2, tan1);
+ float k = normalize_v3(curvature);
+ k /= min_ff(d1, d2);
+ float radius = 1.f / k;
+ /*
+ * The smaller the radius of curvature, the sharper the corner.
+ * The thicker the line, the larger the radius of curvature it
+ * takes to be visually indistinguishable from an endpoint.
+ */
+ float min_radius = gps->thickness * 0.0001f;
+
+ if (radius < min_radius) {
+ /* Extend along direction of curvature. */
+ bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ bGPDspoint *pt = &gps_new->points[0];
+ copy_v3_v3(&pt->x, &pt1->x);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &gps_new->points[1];
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+ mul_v3_fl(curvature, -connection_dist);
+ add_v3_v3v3(&pt->x, &pt1->x, curvature);
+ }
+ }
+ }
+
+ if (tgpf->fill_extend_mode != GP_FILL_EMODE_RADIUS) {
+ /* Extend start. */
+ bGPDspoint *pt0 = &gps->points[1];
+ bGPDspoint *pt1 = &gps->points[0];
+ bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ bGPDspoint *pt = &gps_new->points[0];
+ copy_v3_v3(&pt->x, &pt1->x);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &gps_new->points[1];
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+ extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x);
+
+ /* Extend end. */
+ pt0 = &gps->points[gps->totpoints - 2];
+ pt1 = &gps->points[gps->totpoints - 1];
+ gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness);
+ gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG;
+ BLI_addtail(&gpf->strokes, gps_new);
+
+ pt = &gps_new->points[0];
+ copy_v3_v3(&pt->x, &pt1->x);
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+
+ pt = &gps_new->points[1];
+ pt->strength = 1.0f;
+ pt->pressure = 1.0f;
+ extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x);
+ }
+
+ /* Connect endpoints within a radius */
+ if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) {
+ continue;
+ }
+ float *stroke1_start = &gps->points[0].x;
+ float *stroke1_end = &gps->points[gps->totpoints - 1].x;
+ /* Connect the start of the stroke to its own end if the whole stroke
+ * isn't already so short that it's within that distance
+ */
+ if (len_v3v3(stroke1_start, stroke1_end) < connection_dist &&
+ total_length > connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_start, stroke1_end);
+ BLI_gset_add(connected_endpoints, stroke1_start);
+ BLI_gset_add(connected_endpoints, stroke1_end);
+ }
+ for (bGPDstroke *gps2 = (bGPDstroke *)(((Link *)gps)->next); gps2 != NULL;
+ gps2 = (bGPDstroke *)(((Link *)gps2)->next)) {
+ /* Don't check distance to temporary extensions. */
+ if ((gps2->flag & GP_STROKE_NOFILL) && (gps2->flag & GP_STROKE_TAG)) {
+ continue;
+ }
+
+ /* Don't check endpoint distances unless the bounding boxes of the strokes
+ are close enough together that they can plausibly be connected. */
+ if (!extended_bbox_overlap(gps->boundbox_min,
+ gps->boundbox_max,
+ gps2->boundbox_min,
+ gps2->boundbox_max,
+ connection_dist)) {
+ continue;
+ }
+
+ float *stroke2_start = &gps2->points[0].x;
+ float *stroke2_end = &gps2->points[gps2->totpoints - 1].x;
+ if (len_v3v3(stroke1_start, stroke2_start) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_start, stroke2_start);
+ BLI_gset_add(connected_endpoints, stroke1_start);
+ BLI_gset_add(connected_endpoints, stroke2_start);
+ }
+ if (len_v3v3(stroke1_start, stroke2_end) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_start, stroke2_end);
+ BLI_gset_add(connected_endpoints, stroke1_start);
+ BLI_gset_add(connected_endpoints, stroke2_end);
+ }
+ if (len_v3v3(stroke1_end, stroke2_start) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_end, stroke2_start);
+ BLI_gset_add(connected_endpoints, stroke1_end);
+ BLI_gset_add(connected_endpoints, stroke2_start);
+ }
+ if (len_v3v3(stroke1_end, stroke2_end) < connection_dist) {
+ add_stroke_extension(gpf, gps, stroke1_end, stroke2_end);
+ BLI_gset_add(connected_endpoints, stroke1_end);
+ BLI_gset_add(connected_endpoints, stroke2_end);
+ }
+ }
+
+ bool start_connected = BLI_gset_haskey(connected_endpoints, stroke1_start);
+ bool end_connected = BLI_gset_haskey(connected_endpoints, stroke1_end);
+ add_endpoint_radius_help(gpf, gps, stroke1_start, connection_dist, start_connected);
+ add_endpoint_radius_help(gpf, gps, stroke1_end, connection_dist, end_connected);
}
}
+
+ BLI_gset_free(connected_endpoints, NULL);
}
static void gpencil_update_extend(tGPDfill *tgpf)
@@ -322,15 +504,16 @@ static bool gpencil_stroke_is_drawable(tGPDfill *tgpf, bGPDstroke *gps)
const bool show_help = (tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) != 0;
const bool show_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) != 0;
const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG);
+ const bool is_extend_help = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_HELP);
if ((!show_help) && (show_extend)) {
- if (!is_extend) {
+ if (!is_extend && !is_extend_help) {
return false;
}
}
if ((show_help) && (!show_extend)) {
- if (is_extend) {
+ if (is_extend || is_extend_help) {
return false;
}
}
@@ -357,13 +540,29 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf,
float fpt[3];
float col[4];
const float extend_col[4] = {0.0f, 1.0f, 1.0f, 1.0f};
- const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG);
+ const float help_col[4] = {1.0f, 0.0f, 0.5f, 1.0f};
+ const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG) &&
+ !(gps->flag & GP_STROKE_HELP);
+ const bool is_help = gps->flag & GP_STROKE_HELP;
if (!gpencil_stroke_is_drawable(tgpf, gps)) {
return;
}
- if ((is_extend) && (!tgpf->is_render)) {
+ if (is_help && tgpf->is_render) {
+ /* Help strokes are for display only and shouldn't render. */
+ return;
+ }
+ else if (is_help) {
+ /* Color help strokes that won't affect fill or render separately from
+ * extended strokes, as they will affect them. */
+ copy_v4_v4(col, help_col);
+
+ /* If there is contact, hide the circles to avoid noise and keep the focus
+ * in the pending gaps. */
+ col[3] = (gps->flag & GP_STROKE_TAG) ? 0.0f : 0.5f;
+ }
+ else if ((is_extend) && (!tgpf->is_render)) {
copy_v4_v4(col, extend_col);
}
else {
@@ -379,7 +578,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf,
immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR);
/* draw stroke curve */
- GPU_line_width((!is_extend) ? thickness : thickness * 2.0f);
+ GPU_line_width((!is_extend && !is_help) ? thickness : thickness * 2.0f);
immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints + cyclic_add);
const bGPDspoint *pt = points;
@@ -390,7 +589,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf,
CLAMP(alpha, 0.0f, 1.0f);
col[3] = alpha <= thershold ? 0.0f : 1.0f;
}
- else {
+ else if (!is_help) {
col[3] = 1.0f;
}
/* set point */
@@ -582,7 +781,8 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4])
/* Normal strokes. */
if (ELEM(tgpf->fill_draw_mode, GP_FILL_DMODE_STROKE, GP_FILL_DMODE_BOTH)) {
- if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0)) {
+ if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0) &&
+ ((gps->flag & GP_STROKE_HELP) == 0)) {
ED_gpencil_draw_fill(&tgpw);
}
}
@@ -1777,10 +1977,11 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op)
tgpf->fill_threshold = brush->gpencil_settings->fill_threshold;
tgpf->fill_simplylvl = brush->gpencil_settings->fill_simplylvl;
tgpf->fill_draw_mode = brush->gpencil_settings->fill_draw_mode;
+ tgpf->fill_extend_mode = brush->gpencil_settings->fill_extend_mode;
tgpf->fill_extend_fac = brush->gpencil_settings->fill_extend_fac;
tgpf->fill_factor = max_ff(GPENCIL_MIN_FILL_FAC,
min_ff(brush->gpencil_settings->fill_factor, GPENCIL_MAX_FILL_FAC));
- tgpf->fill_leak = (int)ceil((float)brush->gpencil_settings->fill_leak * tgpf->fill_factor);
+ tgpf->fill_leak = (int)ceil(FILL_LEAK * tgpf->fill_factor);
int totcol = tgpf->ob->totcol;
diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc
index 22961504015..c2a9b16c8b6 100644
--- a/source/blender/geometry/intern/mesh_to_curve_convert.cc
+++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
+#include "BLI_array_utils.hh"
#include "BLI_devirtualize_parameters.hh"
#include "BLI_set.hh"
#include "BLI_task.hh"
@@ -18,19 +19,6 @@
namespace blender::geometry {
-template<typename T>
-static void copy_with_map(const VArray<T> &src, Span<int> map, MutableSpan<T> dst)
-{
- devirtualize_varray(src, [&](const auto &src) {
- threading::parallel_for(map.index_range(), 1024, [&](const IndexRange range) {
- for (const int i : range) {
- const int vert_index = map[i];
- dst[i] = src[vert_index];
- }
- });
- });
-}
-
bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh,
const Span<int> vert_indices,
const Span<int> curve_offsets,
@@ -71,7 +59,7 @@ bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh,
using T = decltype(dummy);
bke::SpanAttributeWriter<T> attribute =
curves_attributes.lookup_or_add_for_write_only_span<T>(attribute_id, ATTR_DOMAIN_POINT);
- copy_with_map<T>(mesh_attribute.typed<T>(), vert_indices, attribute.span);
+ array_utils::gather<T>(mesh_attribute.typed<T>(), vert_indices, attribute.span);
attribute.finish();
});
}
diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
index ae013a7dd02..22037d10a71 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
+++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
@@ -442,6 +442,7 @@ typedef enum eLineartTriangleFlags {
LRT_TRIANGLE_INTERSECTION_ONLY = (1 << 3),
LRT_TRIANGLE_NO_INTERSECTION = (1 << 4),
LRT_TRIANGLE_MAT_BACK_FACE_CULLING = (1 << 5),
+ LRT_TRIANGLE_FORCE_INTERSECTION = (1 << 6),
} eLineartTriangleFlags;
#define LRT_SHADOW_MASK_UNDEFINED 0
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
index c4a235d06bc..6d0ede03680 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
@@ -1905,6 +1905,9 @@ static void lineart_load_tri_task(void *__restrict userdata,
if (ob_info->usage == OBJECT_LRT_INTERSECTION_ONLY) {
tri->flags |= LRT_TRIANGLE_INTERSECTION_ONLY;
}
+ else if (ob_info->usage == OBJECT_LRT_FORCE_INTERSECTION) {
+ tri->flags |= LRT_TRIANGLE_FORCE_INTERSECTION;
+ }
else if (ob_info->usage == OBJECT_LRT_NO_INTERSECTION ||
ob_info->usage == OBJECT_LRT_OCCLUSION_ONLY) {
tri->flags |= LRT_TRIANGLE_NO_INTERSECTION;
@@ -2252,7 +2255,7 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info,
}
if (usage == OBJECT_LRT_INHERIT || usage == OBJECT_LRT_INCLUDE ||
- usage == OBJECT_LRT_NO_INTERSECTION) {
+ usage == OBJECT_LRT_NO_INTERSECTION || usage == OBJECT_LRT_FORCE_INTERSECTION) {
lineart_add_edge_to_array_thread(ob_info, la_edge);
}
@@ -2281,7 +2284,7 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info,
la_edge->edge_identifier = LRT_EDGE_IDENTIFIER(ob_info, la_edge);
BLI_addtail(&la_edge->segments, la_seg);
if (usage == OBJECT_LRT_INHERIT || usage == OBJECT_LRT_INCLUDE ||
- usage == OBJECT_LRT_NO_INTERSECTION) {
+ usage == OBJECT_LRT_NO_INTERSECTION || usage == OBJECT_LRT_FORCE_INTERSECTION) {
lineart_add_edge_to_array_thread(ob_info, la_edge);
if (shadow_eln) {
LineartEdge *shadow_e = lineart_find_matching_edge(shadow_eln, la_edge->edge_identifier);
@@ -2382,6 +2385,8 @@ static int lineart_usage_check(Collection *c, Object *ob, bool is_render)
return OBJECT_LRT_INTERSECTION_ONLY;
case COLLECTION_LRT_NO_INTERSECTION:
return OBJECT_LRT_NO_INTERSECTION;
+ case COLLECTION_LRT_FORCE_INTERSECTION:
+ return OBJECT_LRT_FORCE_INTERSECTION;
}
return OBJECT_LRT_INHERIT;
}
@@ -3410,10 +3415,11 @@ static void lineart_triangle_intersect_in_bounding_area(LineartTriangle *tri,
}
tt->testing_e[th->thread_id] = (LineartEdge *)tri;
- if ((testing_triangle->flags & LRT_TRIANGLE_NO_INTERSECTION) ||
- ((testing_triangle->flags & LRT_TRIANGLE_INTERSECTION_ONLY) &&
- (tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY))) {
- continue;
+ if(!((testing_triangle->flags | tri->flags) & LRT_TRIANGLE_FORCE_INTERSECTION)){
+ if ((testing_triangle->flags & LRT_TRIANGLE_NO_INTERSECTION) ||
+ (testing_triangle->flags & tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY)) {
+ continue;
+ }
}
double *RG0 = testing_triangle->v[0]->gloc, *RG1 = testing_triangle->v[1]->gloc,
@@ -4535,14 +4541,8 @@ static void lineart_add_triangles_worker(TaskPool *__restrict UNUSED(pool), Line
_dir_control++;
for (co = x1; co <= x2; co++) {
for (r = y1; r <= y2; r++) {
- lineart_bounding_area_link_triangle(ld,
- &ld->qtree.initials[r * ld->qtree.count_x + co],
- tri,
- 0,
- 1,
- 0,
- (!(tri->flags & LRT_TRIANGLE_NO_INTERSECTION)),
- th);
+ lineart_bounding_area_link_triangle(
+ ld, &ld->qtree.initials[r * ld->qtree.count_x + co], tri, 0, 1, 0, 1, th);
}
}
} /* Else throw away. */
diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h
index 988853e6694..97abf0fec5e 100644
--- a/source/blender/makesdna/DNA_brush_enums.h
+++ b/source/blender/makesdna/DNA_brush_enums.h
@@ -118,13 +118,19 @@ typedef enum eGPDbrush_Flag2 {
GP_BRUSH_USE_UV_RAND_PRESS = (1 << 11),
} eGPDbrush_Flag2;
-/* BrushGpencilSettings->gp_fill_draw_mode */
+/* BrushGpencilSettings->fill_draw_mode */
typedef enum eGP_FillDrawModes {
GP_FILL_DMODE_BOTH = 0,
GP_FILL_DMODE_STROKE = 1,
GP_FILL_DMODE_CONTROL = 2,
} eGP_FillDrawModes;
+/* BrushGpencilSettings->fill_extend_mode */
+typedef enum eGP_FillExtendModes {
+ GP_FILL_EMODE_RADIUS = 0,
+ GP_FILL_EMODE_EXTEND = 1,
+} eGP_FillExtendModes;
+
/* BrushGpencilSettings->fill_layer_mode */
typedef enum eGP_FillLayerModes {
GP_FILL_GPLMODE_VISIBLE = 0,
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index 33f5d8eea12..761a36e8d1f 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -58,11 +58,10 @@ typedef struct BrushGpencilSettings {
/** Factor for transparency. */
float fill_threshold;
- /** Number of pixel to consider the leak is too small (x 2). */
- short fill_leak;
+ char _pad2[2];
/* Type of caps: eGPDstroke_Caps. */
int8_t caps_type;
- char _pad;
+ char _pad[5];
int flag2;
@@ -70,6 +69,8 @@ typedef struct BrushGpencilSettings {
int fill_simplylvl;
/** Type of control lines drawing mode. */
int fill_draw_mode;
+ /** Type of gap filling extension to use. */
+ int fill_extend_mode;
/** Icon identifier. */
int icon_id;
diff --git a/source/blender/makesdna/DNA_collection_types.h b/source/blender/makesdna/DNA_collection_types.h
index a3e5eb4e944..75bfc2575a5 100644
--- a/source/blender/makesdna/DNA_collection_types.h
+++ b/source/blender/makesdna/DNA_collection_types.h
@@ -36,6 +36,7 @@ enum eCollectionLineArt_Usage {
COLLECTION_LRT_EXCLUDE = (1 << 1),
COLLECTION_LRT_INTERSECTION_ONLY = (1 << 2),
COLLECTION_LRT_NO_INTERSECTION = (1 << 3),
+ COLLECTION_LRT_FORCE_INTERSECTION = (1 << 4),
};
enum eCollectionLineArt_Flags {
diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h
index 6a2f25f3975..17b25021277 100644
--- a/source/blender/makesdna/DNA_gpencil_types.h
+++ b/source/blender/makesdna/DNA_gpencil_types.h
@@ -347,6 +347,8 @@ typedef enum eGPDstroke_Flag {
/* Flag to indicated that the editcurve has been changed and the stroke needs to be updated with
* the curve data */
GP_STROKE_NEEDS_CURVE_UPDATE = (1 << 9),
+ /* Flag to indicate that a stroke is used only for help, and will not affect rendering or fill */
+ GP_STROKE_HELP = (1 << 10),
/* only for use with stroke-buffer (while drawing arrows) */
GP_STROKE_USE_ARROW_START = (1 << 12),
/* only for use with stroke-buffer (while drawing arrows) */
diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h
index add11d61db8..8296855ec29 100644
--- a/source/blender/makesdna/DNA_object_types.h
+++ b/source/blender/makesdna/DNA_object_types.h
@@ -231,6 +231,7 @@ enum eObjectLineArt_Usage {
OBJECT_LRT_EXCLUDE = (1 << 2),
OBJECT_LRT_INTERSECTION_ONLY = (1 << 3),
OBJECT_LRT_NO_INTERSECTION = (1 << 4),
+ OBJECT_LRT_FORCE_INTERSECTION = (1 << 5),
};
enum eObjectLineArt_Flags {
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index a6778ef67da..bd0e14e4b59 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -286,6 +286,11 @@ static EnumPropertyItem rna_enum_gpencil_fill_draw_modes_items[] = {
{GP_FILL_DMODE_CONTROL, "CONTROL", 0, "Edit Lines", "Use edit lines as fill boundary limits"},
{0, NULL, 0, NULL, NULL}};
+static EnumPropertyItem rna_enum_gpencil_fill_extend_modes_items[] = {
+ {GP_FILL_EMODE_RADIUS, "RADIUS", 0, "Radius", "Connect endpoints that are close together"},
+ {GP_FILL_EMODE_EXTEND, "EXTEND", 0, "Extend", "Extend strokes in straight lines"},
+ {0, NULL, 0, NULL, NULL}};
+
static EnumPropertyItem rna_enum_gpencil_fill_layers_modes_items[] = {
{GP_FILL_GPLMODE_VISIBLE, "VISIBLE", 0, "Visible", "Visible layers"},
{GP_FILL_GPLMODE_ACTIVE, "ACTIVE", 0, "Active", "Only active layer"},
@@ -1486,14 +1491,6 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
- /* fill leak size */
- prop = RNA_def_property(srna, "fill_leak", PROP_INT, PROP_PIXEL);
- RNA_def_property_int_sdna(prop, NULL, "fill_leak");
- RNA_def_property_range(prop, 0, 100);
- RNA_def_property_ui_text(prop, "Leak Size", "Size in pixels to consider the leak closed");
- RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
- RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
-
/* fill factor size */
prop = RNA_def_property(srna, "fill_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "fill_factor");
@@ -1645,7 +1642,14 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
RNA_def_property_range(prop, 0.0f, 10.0f);
RNA_def_property_float_default(prop, 0.0f);
RNA_def_property_ui_text(
- prop, "Stroke Extension", "Strokes end extension for closing gaps, use zero to disable");
+ prop, "Closure Size", "Strokes end extension for closing gaps, use zero to disable");
+ RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
+
+ prop = RNA_def_property(srna, "fill_extend_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "fill_extend_mode");
+ RNA_def_property_enum_items(prop, rna_enum_gpencil_fill_extend_modes_items);
+ RNA_def_property_ui_text(
+ prop, "Closure Mode", "Types of stroke extensions used for closing gaps");
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
/* Number of pixels to dilate fill area. Negative values contract the filled area. */
diff --git a/source/blender/makesrna/intern/rna_collection.c b/source/blender/makesrna/intern/rna_collection.c
index 84ddea368e7..833495dce7a 100644
--- a/source/blender/makesrna/intern/rna_collection.c
+++ b/source/blender/makesrna/intern/rna_collection.c
@@ -599,6 +599,11 @@ void RNA_def_collections(BlenderRNA *brna)
0,
"No Intersection",
"Include this collection but do not generate intersection lines"},
+ {COLLECTION_LRT_FORCE_INTERSECTION,
+ "FORCE_INTERSECTION",
+ 0,
+ "Force Intersection",
+ "Generate intersection lines even with objects that disabled intersection"},
{0, NULL, 0, NULL, NULL}};
prop = RNA_def_property(srna, "lineart_usage", PROP_ENUM, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c
index d60e732ba51..46a89d3a6e0 100644
--- a/source/blender/makesrna/intern/rna_object.c
+++ b/source/blender/makesrna/intern/rna_object.c
@@ -2922,6 +2922,11 @@ static void rna_def_object_lineart(BlenderRNA *brna)
0,
"No Intersection",
"Include this object but do not generate intersection lines"},
+ {OBJECT_LRT_FORCE_INTERSECTION,
+ "FORCE_INTERSECTION",
+ 0,
+ "Force Intersection",
+ "Generate intersection lines even with objects that disabled intersection"},
{0, NULL, 0, NULL, NULL},
};
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index ff8f851c1e8..2910f44e37a 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -114,7 +114,9 @@ using blender::StringRef;
using blender::StringRefNull;
using blender::Vector;
using blender::bke::AttributeMetaData;
+using blender::bke::AttributeValidator;
using blender::fn::Field;
+using blender::fn::FieldOperation;
using blender::fn::GField;
using blender::fn::ValueOrField;
using blender::fn::ValueOrFieldCPPType;
@@ -1046,13 +1048,15 @@ static Vector<OutputAttributeToStore> compute_attributes_to_store(
blender::fn::FieldEvaluator field_evaluator{field_context, domain_size};
for (const OutputAttributeInfo &output_info : outputs_info) {
const CPPType &type = output_info.field.cpp_type();
+ const AttributeValidator validator = attributes.lookup_validator(output_info.name);
OutputAttributeToStore store{
component_type,
domain,
output_info.name,
GMutableSpan{
type, MEM_malloc_arrayN(domain_size, type.size(), __func__), domain_size}};
- field_evaluator.add_with_destination(output_info.field, store.data);
+ GField field = validator.validate_field_if_necessary(output_info.field);
+ field_evaluator.add_with_destination(std::move(field), store.data);
attributes_to_store.append(store);
}
field_evaluator.evaluate();
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index e16cd7a253f..63af2c71d45 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -95,6 +95,7 @@ void register_node_type_geo_join_geometry(void);
void register_node_type_geo_material_replace(void);
void register_node_type_geo_material_selection(void);
void register_node_type_geo_merge_by_distance(void);
+void register_node_type_geo_mesh_face_set_boundaries(void);
void register_node_type_geo_mesh_primitive_circle(void);
void register_node_type_geo_mesh_primitive_cone(void);
void register_node_type_geo_mesh_primitive_cube(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index d587da823f1..73fb2bf32c8 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -352,6 +352,7 @@ DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry,
DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "Provide a selection of faces that use the specified material")
DefNode(GeometryNode, GEO_NODE_MERGE_BY_DISTANCE, def_geo_merge_by_distance,"MERGE_BY_DISTANCE", MergeByDistance, "Merge by Distance", "Merge vertices or points within a given distance")
DefNode(GeometryNode, GEO_NODE_MESH_BOOLEAN, def_geo_boolean, "MESH_BOOLEAN", MeshBoolean, "Mesh Boolean", "Cut, subtract, or join multiple mesh inputs")
+DefNode(GeometryNode, GEO_NODE_MESH_FACE_SET_BOUNDARIES, 0, "MESH_FACE_SET_BOUNDARIES", MeshFaceSetBoundaries, "Face Set Boundaries", "Find edges on the boundaries between face sets")
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Mesh Circle", "Generate a circular ring of edges")
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE",MeshCone, "Cone", "Generate a cone mesh")
DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE",MeshCube, "Cube", "Generate a cuboid mesh with variable side lengths and subdivisions")
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index 31c00cc6b82..cc3d00ea21f 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -105,6 +105,7 @@ set(SRC
nodes/node_geo_material_replace.cc
nodes/node_geo_material_selection.cc
nodes/node_geo_merge_by_distance.cc
+ nodes/node_geo_mesh_face_set_boundaries.cc
nodes/node_geo_mesh_primitive_circle.cc
nodes/node_geo_mesh_primitive_cone.cc
nodes/node_geo_mesh_primitive_cube.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_face_set_boundaries.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_face_set_boundaries.cc
new file mode 100644
index 00000000000..88cccfb2f94
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_face_set_boundaries.cc
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_mesh.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_face_set_boundaries_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Face Set"))
+ .default_value(0)
+ .hide_value()
+ .supports_field()
+ .description(N_("An identifier for the group of each face. All contiguous faces with the "
+ "same value are in the same region"));
+ b.add_output<decl::Bool>(N_("Boundary Edges"))
+ .field_source()
+ .description(N_("The edges that lie on the boundaries between the different face sets"));
+}
+
+class BoundaryFieldInput final : public bke::MeshFieldInput {
+ private:
+ const Field<int> face_set;
+
+ public:
+ BoundaryFieldInput(const Field<int> face_set)
+ : bke::MeshFieldInput(CPPType::get<bool>(), "Boundary Field"), face_set(face_set)
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ const bke::MeshFieldContext face_context{mesh, ATTR_DOMAIN_FACE};
+ FieldEvaluator face_evaluator{face_context, mesh.totpoly};
+ face_evaluator.add(face_set);
+ face_evaluator.evaluate();
+ const VArray<int> face_set = face_evaluator.get_evaluated<int>(0);
+
+ Array<bool> boundary(mesh.totedge, false);
+ Array<bool> edge_visited(mesh.totedge, false);
+ Array<int> edge_face_set(mesh.totedge, 0);
+ const Span<MPoly> polys = mesh.polys();
+ const Span<MLoop> loops = mesh.loops();
+ for (const int i : polys.index_range()) {
+ const MPoly &poly = polys[i];
+ for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
+ const int edge = loop.e;
+ if (edge_visited[edge]) {
+ if (edge_face_set[edge] != face_set[i]) {
+ /* This edge is connected to two faces on different face sets. */
+ boundary[edge] = true;
+ }
+ }
+ edge_visited[edge] = true;
+ edge_face_set[edge] = face_set[i];
+ }
+ }
+ return mesh.attributes().adapt_domain<bool>(
+ VArray<bool>::ForContainer(std::move(boundary)), ATTR_DOMAIN_EDGE, domain);
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> face_set_field = params.extract_input<Field<int>>("Face Set");
+ Field<bool> face_set_boundaries{std::make_shared<BoundaryFieldInput>(face_set_field)};
+ params.set_output("Boundary Edges", std::move(face_set_boundaries));
+}
+
+} // namespace blender::nodes::node_geo_mesh_face_set_boundaries_cc
+
+void register_node_type_geo_mesh_face_set_boundaries()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_face_set_boundaries_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_MESH_FACE_SET_BOUNDARIES, "Face Set Boundaries", NODE_CLASS_INPUT);
+ ntype.declare = file_ns::node_declare;
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc
index a1d6695b33b..ce06ccbda75 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include "BLI_array_utils.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
@@ -44,17 +45,6 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
node->storage = data;
}
-static void materialize_compressed_to_uninitialized_threaded(const GVArray &src,
- const IndexMask mask,
- GMutableSpan dst)
-{
- BLI_assert(src.type() == dst.type());
- BLI_assert(mask.size() == dst.size());
- threading::parallel_for(mask.index_range(), 4096, [&](IndexRange range) {
- src.materialize_compressed_to_uninitialized(mask.slice(range), dst.slice(range).data());
- });
-}
-
static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
Field<float3> &position_field,
Field<float> &radius_field,
@@ -88,14 +78,12 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
GSpanAttributeWriter position = dst_attributes.lookup_or_add_for_write_only_span(
"position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3);
- materialize_compressed_to_uninitialized_threaded(
- evaluator.get_evaluated(0), selection, position.span);
+ array_utils::gather(evaluator.get_evaluated(0), selection, position.span);
position.finish();
GSpanAttributeWriter radius = dst_attributes.lookup_or_add_for_write_only_span(
"radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT);
- materialize_compressed_to_uninitialized_threaded(
- evaluator.get_evaluated(1), selection, radius.span);
+ array_utils::gather(evaluator.get_evaluated(1), selection, radius.span);
radius.finish();
Map<AttributeIDRef, AttributeKind> attributes;
@@ -112,7 +100,7 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
attribute_id, ATTR_DOMAIN_POINT, data_type);
if (dst && src) {
- materialize_compressed_to_uninitialized_threaded(src, selection, dst.span);
+ array_utils::gather(src, selection, dst.span);
dst.finish();
}
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc
index f6dded56315..bb9ac9b5d4c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc
@@ -24,11 +24,13 @@ static void set_material_index_in_component(GeometryComponent &component,
MutableAttributeAccessor attributes = *component.attributes_for_write();
bke::GeometryFieldContext field_context{component, domain};
+ const bke::AttributeValidator validator = attributes.lookup_validator("material_index");
AttributeWriter<int> indices = attributes.lookup_or_add_for_write<int>("material_index", domain);
fn::FieldEvaluator evaluator{field_context, domain_size};
evaluator.set_selection(selection_field);
- evaluator.add_with_destination(index_field, indices.varray);
+ evaluator.add_with_destination(validator.validate_field_if_necessary(index_field),
+ indices.varray);
evaluator.evaluate();
indices.finish();
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc
index 2a590f5bf4a..ad31c8a2191 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc
@@ -103,6 +103,7 @@ static void try_capture_field_on_geometry(GeometryComponent &component,
const CPPType &type = field.cpp_type();
const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(type);
+ const bke::AttributeValidator validator = attributes.lookup_validator(name);
/* Could avoid allocating a new buffer if:
* - We are writing to an attribute that exists already with the correct domain and type.
@@ -110,7 +111,8 @@ static void try_capture_field_on_geometry(GeometryComponent &component,
void *buffer = MEM_mallocN(type.size() * domain_size, __func__);
fn::FieldEvaluator evaluator{field_context, &mask};
- evaluator.add_with_destination(field, GMutableSpan{type, buffer, domain_size});
+ evaluator.add_with_destination(validator.validate_field_if_necessary(field),
+ GMutableSpan{type, buffer, domain_size});
evaluator.evaluate();
if (GAttributeWriter attribute = attributes.lookup_for_write(name)) {
diff --git a/source/blender/python/gpu/gpu_py_buffer.c b/source/blender/python/gpu/gpu_py_buffer.c
index 020535d002a..9a415b7f2c4 100644
--- a/source/blender/python/gpu/gpu_py_buffer.c
+++ b/source/blender/python/gpu/gpu_py_buffer.c
@@ -100,7 +100,7 @@ static bool pygpu_buffer_pyobj_as_shape(PyObject *shape_obj,
}
else {
PyErr_Format(PyExc_TypeError,
- "invalid second argument argument expected a sequence "
+ "invalid second argument expected a sequence "
"or an int, not a %.200s",
Py_TYPE(shape_obj)->tp_name);
}
@@ -655,7 +655,7 @@ PyDoc_STRVAR(
"\n"
" :arg format: Format type to interpret the buffer.\n"
" Possible values are `FLOAT`, `INT`, `UINT`, `UBYTE`, `UINT_24_8` and `10_11_11_REV`.\n"
- " :type type: str\n"
+ " :type format: str\n"
" :arg dimensions: Array describing the dimensions.\n"
" :type dimensions: int\n"
" :arg data: Optional data array.\n"
diff --git a/source/blender/python/gpu/gpu_py_framebuffer.c b/source/blender/python/gpu/gpu_py_framebuffer.c
index 9bb2a9137f4..d4c1c506450 100644
--- a/source/blender/python/gpu/gpu_py_framebuffer.c
+++ b/source/blender/python/gpu/gpu_py_framebuffer.c
@@ -471,7 +471,7 @@ PyDoc_STRVAR(
" :type slot: int\n"
" :param format: The format that describes the content of a single channel.\n"
" Possible values are `FLOAT`, `INT`, `UINT`, `UBYTE`, `UINT_24_8` and `10_11_11_REV`.\n"
- " :type type: str\n"
+ " :type format: str\n"
" :arg data: Optional Buffer object to fill with the pixels values.\n"
" :type data: :class:`gpu.types.Buffer`\n"
" :return: The Buffer with the read pixels.\n"
diff --git a/source/blender/python/gpu/gpu_py_texture.c b/source/blender/python/gpu/gpu_py_texture.c
index 7a23f7ac91f..388c4836803 100644
--- a/source/blender/python/gpu/gpu_py_texture.c
+++ b/source/blender/python/gpu/gpu_py_texture.c
@@ -282,7 +282,7 @@ PyDoc_STRVAR(
"\n"
" :param format: The format that describes the content of a single item.\n"
" Possible values are `FLOAT`, `INT`, `UINT`, `UBYTE`, `UINT_24_8` and `10_11_11_REV`.\n"
- " :type type: str\n"
+ " :type format: str\n"
" :arg value: sequence each representing the value to fill.\n"
" :type value: sequence of 1, 2, 3 or 4 values\n");
static PyObject *pygpu_texture_clear(BPyGPUTexture *self, PyObject *args, PyObject *kwds)
diff --git a/source/blender/python/gpu/gpu_py_vertex_buffer.c b/source/blender/python/gpu/gpu_py_vertex_buffer.c
index ab2ff59a689..8cedfb5cdb7 100644
--- a/source/blender/python/gpu/gpu_py_vertex_buffer.c
+++ b/source/blender/python/gpu/gpu_py_vertex_buffer.c
@@ -328,9 +328,9 @@ PyDoc_STRVAR(pygpu_vertbuf__tp_doc,
" Contains a VBO.\n"
"\n"
" :param format: Vertex format.\n"
- " :type buf: :class:`gpu.types.GPUVertFormat`\n"
+ " :type format: :class:`gpu.types.GPUVertFormat`\n"
" :param len: Amount of vertices that will fit into this buffer.\n"
- " :type type: `int`\n");
+ " :type len: int\n");
PyTypeObject BPyGPUVertBuf_Type = {
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUVertBuf",
.tp_basicsize = sizeof(BPyGPUVertBuf),
diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc
index 4209e1e5e00..841df253dab 100644
--- a/source/blender/windowmanager/intern/wm_event_system.cc
+++ b/source/blender/windowmanager/intern/wm_event_system.cc
@@ -4733,7 +4733,8 @@ static int convert_key(GHOST_TKey key)
return EVT_LEFTCTRLKEY;
case GHOST_kKeyRightControl:
return EVT_RIGHTCTRLKEY;
- case GHOST_kKeyOS:
+ case GHOST_kKeyLeftOS:
+ case GHOST_kKeyRightOS:
return EVT_OSKEY;
case GHOST_kKeyLeftAlt:
return EVT_LEFTALTKEY;
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index d2339d7efad..b61ebdd11be 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -120,6 +120,36 @@ static struct WMInitStruct {
};
/* -------------------------------------------------------------------- */
+/** \name Modifier Constants
+ * \{ */
+
+static const struct {
+ uint8_t flag;
+ GHOST_TKey ghost_key_pair[2];
+ GHOST_TModifierKey ghost_mask_pair[2];
+} g_modifier_table[] = {
+ {KM_SHIFT,
+ {GHOST_kKeyLeftShift, GHOST_kKeyRightShift},
+ {GHOST_kModifierKeyLeftShift, GHOST_kModifierKeyRightShift}},
+ {KM_CTRL,
+ {GHOST_kKeyLeftControl, GHOST_kKeyRightControl},
+ {GHOST_kModifierKeyLeftControl, GHOST_kModifierKeyRightControl}},
+ {KM_ALT,
+ {GHOST_kKeyLeftAlt, GHOST_kKeyRightAlt},
+ {GHOST_kModifierKeyLeftAlt, GHOST_kModifierKeyRightAlt}},
+ {KM_OSKEY,
+ {GHOST_kKeyLeftOS, GHOST_kKeyRightOS},
+ {GHOST_kModifierKeyLeftOS, GHOST_kModifierKeyRightOS}},
+};
+
+enum ModSide {
+ MOD_SIDE_LEFT = 0,
+ MOD_SIDE_RIGHT = 1,
+};
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Window Open & Close
* \{ */
@@ -969,43 +999,18 @@ void wm_cursor_position_get(wmWindow *win, int *r_x, int *r_y)
wm_cursor_position_from_ghost_client_coords(win, r_x, r_y);
}
-typedef enum {
- SHIFT = 's',
- CONTROL = 'c',
- ALT = 'a',
- OS = 'C',
-} modifierKeyType;
-
/** Check if specified modifier key type is pressed. */
-static bool query_qual(modifierKeyType qual)
-{
- GHOST_TModifierKey left, right;
- switch (qual) {
- case SHIFT:
- left = GHOST_kModifierKeyLeftShift;
- right = GHOST_kModifierKeyRightShift;
- break;
- case CONTROL:
- left = GHOST_kModifierKeyLeftControl;
- right = GHOST_kModifierKeyRightControl;
- break;
- case OS:
- left = right = GHOST_kModifierKeyOS;
- break;
- case ALT:
- default:
- left = GHOST_kModifierKeyLeftAlt;
- right = GHOST_kModifierKeyRightAlt;
- break;
- }
-
- bool val = false;
- GHOST_GetModifierKeyState(g_system, left, &val);
- if (!val) {
- GHOST_GetModifierKeyState(g_system, right, &val);
+static uint8_t wm_ghost_modifier_query(const enum ModSide side)
+{
+ uint8_t result = 0;
+ for (int i = 0; i < ARRAY_SIZE(g_modifier_table); i++) {
+ bool val = false;
+ GHOST_GetModifierKeyState(g_system, g_modifier_table[i].ghost_mask_pair[side], &val);
+ if (val) {
+ result |= g_modifier_table[i].flag;
+ }
}
-
- return val;
+ return result;
}
static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate)
@@ -1127,80 +1132,56 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_pt
win->active = 0; /* XXX */
break;
case GHOST_kEventWindowActivate: {
- const int keymodifier = ((query_qual(SHIFT) ? KM_SHIFT : 0) |
- (query_qual(CONTROL) ? KM_CTRL : 0) |
- (query_qual(ALT) ? KM_ALT : 0) | (query_qual(OS) ? KM_OSKEY : 0));
/* No context change! C->wm->windrawable is drawable, or for area queues. */
wm->winactive = win;
win->active = 1;
+
/* bad ghost support for modifier keys... so on activate we set the modifiers again */
- /* TODO: This is not correct since a modifier may be held when a window is activated...
- * better solve this at ghost level. attempted fix r54450 but it caused bug T34255.
- *
- * For now don't send GHOST_kEventKeyDown events, just set the 'eventstate'.
- */
- GHOST_TEventKeyData kdata = {
- .key = GHOST_kKeyUnknown,
- .utf8_buf = {'\0'},
- .is_repeat = false,
+ const uint8_t keymodifier_sided[2] = {
+ wm_ghost_modifier_query(MOD_SIDE_LEFT),
+ wm_ghost_modifier_query(MOD_SIDE_RIGHT),
};
- if (win->eventstate->modifier & KM_SHIFT) {
- if ((keymodifier & KM_SHIFT) == 0) {
- kdata.key = GHOST_kKeyLeftShift;
- wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
- }
- }
-#ifdef USE_WIN_ACTIVATE
- else {
- if (keymodifier & KM_SHIFT) {
- win->eventstate->modifier |= KM_SHIFT;
- }
- }
-#endif
- if (win->eventstate->modifier & KM_CTRL) {
- if ((keymodifier & KM_CTRL) == 0) {
- kdata.key = GHOST_kKeyLeftControl;
- wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
- }
- }
-#ifdef USE_WIN_ACTIVATE
- else {
- if (keymodifier & KM_CTRL) {
- win->eventstate->modifier |= KM_CTRL;
- }
- }
-#endif
- if (win->eventstate->modifier & KM_ALT) {
- if ((keymodifier & KM_ALT) == 0) {
- kdata.key = GHOST_kKeyLeftAlt;
- wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
- }
- }
-#ifdef USE_WIN_ACTIVATE
- else {
- if (keymodifier & KM_ALT) {
- win->eventstate->modifier |= KM_ALT;
- }
- }
-#endif
- if (win->eventstate->modifier & KM_OSKEY) {
- if ((keymodifier & KM_OSKEY) == 0) {
- kdata.key = GHOST_kKeyOS;
- wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
- }
- }
+ const uint8_t keymodifier = keymodifier_sided[0] | keymodifier_sided[1];
+ const uint8_t keymodifier_eventstate = win->eventstate->modifier;
+ if (keymodifier != keymodifier_eventstate) {
+ GHOST_TEventKeyData kdata = {
+ .key = GHOST_kKeyUnknown,
+ .utf8_buf = {'\0'},
+ .is_repeat = false,
+ };
+ for (int i = 0; i < ARRAY_SIZE(g_modifier_table); i++) {
+ if (keymodifier_eventstate & g_modifier_table[i].flag) {
+ if ((keymodifier & g_modifier_table[i].flag) == 0) {
+ for (int side = 0; side < 2; side++) {
+ if ((keymodifier_sided[side] & g_modifier_table[i].flag) == 0) {
+ kdata.key = g_modifier_table[i].ghost_key_pair[side];
+ wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
+ /* Only ever send one release event
+ * (currently releasing multiple isn't needed and only confuses logic). */
+ break;
+ }
+ }
+ }
+ }
#ifdef USE_WIN_ACTIVATE
- else {
- if (keymodifier & KM_OSKEY) {
- win->eventstate->modifier |= KM_OSKEY;
- }
- }
+ else {
+ if (keymodifier & g_modifier_table[i].flag) {
+ for (int side = 0; side < 2; side++) {
+ if (keymodifier_sided[side] & g_modifier_table[i].flag) {
+ kdata.key = g_modifier_table[i].ghost_key_pair[side];
+ wm_event_add_ghostevent(wm, win, GHOST_kEventKeyDown, &kdata);
+ }
+ }
+ }
+ }
#endif
#undef USE_WIN_ACTIVATE
+ }
+ }
/* keymodifier zero, it hangs on hotkeys that open windows otherwise */
win->eventstate->keymodifier = 0;