diff options
author | Jacques Lucke <jacques@blender.org> | 2022-09-18 19:20:45 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2022-09-18 19:20:45 +0300 |
commit | d696514283857bfc771d2b38dfd0710c6a5d1046 (patch) | |
tree | 6a50704f994ed0c59e01aa1aec47294f51ff9ce2 | |
parent | 06d85ad64ba30d734fee0cb0a9db8f9492b41c52 (diff) | |
parent | 3ff15a9e23bd8a20ee514944779a898f1fe5accb (diff) |
Merge branch 'master' into temp-chunk-list
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, ¤t_last_slot->render); + RE_SwapResult(re, &next_last_slot->render); } - RE_SwapResult(re, ¤t_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; |