Geometry Node: Index of Nearest #104619

Merged
Jacques Lucke merged 31 commits from mod_moder/blender:index_of_nearest into main 2023-04-22 13:12:03 +02:00

Geometry Node: Index of Nearest
A node to find the index of the closest element to itself.

image

See: #102387

Geometry Node: Index of Nearest A node to find the index of the closest element to itself. ![image](/attachments/dd227864-dc94-4f9c-95e0-8136cc15bea6) | | | | -------- | -------- | | <video controls src="https://projects.blender.org/attachments/0cf06058-77de-4d8c-98cd-30afa5a41f82"> | <video controls src="https://projects.blender.org/attachments/f514d93f-7aa4-495b-808f-7ffee2bb5ca5"> | | <video controls src="https://projects.blender.org/attachments/125d7f15-58fd-42ae-92c9-b5a1137627d4"> | <video controls src="https://projects.blender.org/attachments/6a003d69-6e21-4000-b944-c2d1253a0ff9"> | See: https://projects.blender.org/blender/blender/issues/102387
Iliya Katushenock added 1 commit 2023-02-11 16:14:39 +01:00
Iliya Katushenock requested review from Jacques Lucke 2023-02-11 16:14:50 +01:00
Iliya Katushenock requested review from Hans Goudey 2023-02-11 16:14:50 +01:00
Brecht Van Lommel added this to the Nodes & Physics project 2023-02-13 09:17:37 +01:00
Hans Goudey added the
Interest
Geometry Nodes
label 2023-02-16 20:42:57 +01:00
Member

Nice idea, and thanks for the clear design task. I think it would be interesting to align the naming a bit closer to the corresponding "Sample Nearest" node that has a geometry socket. Also the "Group ID to Search" name could probably be simplified as "Search ID".

Maybe we should talk a bit about naming in a sub-module meeting.

It's also a bit hard for me to be confident about the design without seeing alternative ideas explored, i.e. what are the "first principles" here and what abstractions can we use to solve the use cases.

Nice idea, and thanks for the clear design task. I think it would be interesting to align the naming a bit closer to the corresponding "Sample Nearest" node that has a geometry socket. Also the "Group ID to Search" name could probably be simplified as "Search ID". Maybe we should talk a bit about naming in a sub-module meeting. It's also a bit hard for me to be confident about the design without seeing alternative ideas explored, i.e. what are the "first principles" here and what abstractions can we use to solve the use cases.
Author
Member

Traversing neighbors in order, with a field callback for each i-other pair, and the ability to return an array of indices would be the most general solution. Though that would be a mega node given the need for various optimization techniques and support for arbitrary geometry/geometry in context.

Traversing neighbors in order, with a field callback for each i-other pair, and the ability to return an array of indices would be the most general solution. Though that would be a mega node given the need for various optimization techniques and support for arbitrary geometry/geometry in context.
Author
Member

About groups.
I see that the inverse option would be very useful. That is, don't look for the nearest one in group A, as the search is for group A, but look for all of non-A, for every A. But this only makes sense as a field. And this greatly increases the complexity of the node. May be added later.

About groups. I see that the inverse option would be very useful. That is, don't look for the nearest one in group A, as the search is for group A, but look for all of non-A, for every A. But this only makes sense as a field. And this greatly increases the complexity of the node. May be added later.
Iliya Katushenock added 1 commit 2023-02-26 11:27:05 +01:00
Iliya Katushenock added 1 commit 2023-03-21 19:20:57 +01:00
Iliya Katushenock added 1 commit 2023-03-21 19:24:08 +01:00
buildbot/vexp-code-patch-coordinator Build done. Details
3edcc6a4a8
Merge branch 'main' into index_of_nearest
Member

@blender-bot package

@blender-bot package
Member

Package build started. Download here when ready.

Package build started. [Download here](https://builder.blender.org/download/patch/PR104619) when ready.
Iliya Katushenock added 2 commits 2023-04-04 16:38:58 +02:00
9b15dc86c2 Fix node name
Fix node name and also old missing bug with indices for empty result
Hans Goudey requested changes 2023-04-04 23:41:47 +02:00
@ -107,1 +107,4 @@
}
template<typename Fn>
inline int BLI_kdtree_nd_(find_nearest_cb)(const KDTree *tree,
Member

Might as well be consistent with the other function here and use the _cpp suffix too.

Might as well be consistent with the other function here and use the `_cpp` suffix too.
mod_moder marked this conversation as resolved
@ -324,6 +324,7 @@ DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Fac
DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large")
DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image")
DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture")
DefNode(GeometryNode, GEO_NODE_INDEX_OF_NEAREST, 0, "INDEX_OF_NEAREST", IndexOfNearest, "Index of Nearest", "Index of nearest element by groups condition")
Member

For the description, I'd suggest:
Find the nearest element in the a group

Adding . Similar to the \"Sample Nearest\" node might be helpful too.

For the description, I'd suggest: `Find the nearest element in the a group` Adding `. Similar to the \"Sample Nearest\" node` might be helpful too.
mod_moder marked this conversation as resolved
@ -0,0 +8,4 @@
#include "BLI_multi_value_map.hh"
#include "BLI_task.hh"
#include "BKE_geometry_fields.hh"
Member

BKE_geometry_fields.hh is already included indirectly by node_geometry_util.hh

`BKE_geometry_fields.hh` is already included indirectly by `node_geometry_util.hh`
mod_moder marked this conversation as resolved
@ -0,0 +12,4 @@
#include "node_geometry_util.hh"
#include "UI_interface.h"
Member

I think these UI includes are unused

I think these UI includes are unused
mod_moder marked this conversation as resolved
@ -0,0 +21,4 @@
{
b.add_input<decl::Vector>("Position").implicit_field(implicit_field_inputs::position);
b.add_input<decl::Int>("Self Group ID").supports_field().hide_value().default_value(0);
Member

These identifiers/names are missing the translation macro N_

These identifiers/names are missing the translation macro `N_`
mod_moder marked this conversation as resolved
@ -0,0 +22,4 @@
b.add_input<decl::Vector>("Position").implicit_field(implicit_field_inputs::position);
b.add_input<decl::Int>("Self Group ID").supports_field().hide_value().default_value(0);
b.add_input<decl::Int>("Group ID to Search").supports_field().hide_value().default_value(0);
Member

We settled on Self Group ID and Nearest Group ID in the module meeting.

We settled on `Self Group ID` and `Nearest Group ID` in the module meeting.
mod_moder marked this conversation as resolved
@ -0,0 +68,4 @@
const VArray<int> group = evaluator.get_evaluated<int>(1);
const VArray<int> search_group = evaluator.get_evaluated<int>(2);
const bool group_use = !group.is_single();
Member

I'd change these names to use_group and use_search_group for consistency with your other variable names and because use at front sounds more natural.

I'd change these names to `use_group` and `use_search_group` for consistency with your other variable names and because `use` at front sounds more natural.
mod_moder marked this conversation as resolved
@ -0,0 +95,4 @@
}
});
for (int key : in_group.keys()) {
Member

int key -> const int key

`int key` -> `const int key`
mod_moder marked this conversation as resolved
@ -0,0 +162,4 @@
{
const Field<float3> position = params.extract_input<Field<float3>>("Position");
const Field<int> self_group = params.extract_input<Field<int>>("Self Group ID");
Member

If using std::move with these variables, don't declare them const.

I think it would be fine to retrieve them directly in make_shared though, rather than having temporary variables.

If using `std::move` with these variables, don't declare them const. I think it would be fine to retrieve them directly in `make_shared` though, rather than having temporary variables.
mod_moder marked this conversation as resolved
Hans Goudey requested changes 2023-04-04 23:43:22 +02:00
@ -0,0 +75,4 @@
MultiValueMap<int, int64_t> out_group;
Array<int> indices(mask.min_array_size());
threading::parallel_invoke((indices.size() > 512) && group_to_find_use && group_use,
Member

This comment is a bit vague, but I'm finding it hard to keep track of the separate group_use and group_to_find_use conditions. I wonder if the first condition could be handled with by splitting logic into a separate function, like SampleCurveFunction does with its sample_curve lambda.

This comment is a bit vague, but I'm finding it hard to keep track of the separate `group_use` and `group_to_find_use` conditions. I wonder if the first condition could be handled with by splitting logic into a separate function, like `SampleCurveFunction` does with its `sample_curve` lambda.
Iliya Katushenock added 2 commits 2023-04-05 18:40:09 +02:00
Iliya Katushenock reviewed 2023-04-05 18:43:12 +02:00
Iliya Katushenock requested review from Hans Goudey 2023-04-05 18:43:32 +02:00
Iliya Katushenock added 1 commit 2023-04-05 19:05:05 +02:00
Iliya Katushenock added 1 commit 2023-04-05 23:06:48 +02:00
First-time contributor

About groups.
I see that the inverse option would be very useful. That is, don't look for the nearest one in group A, as the search is for group A, but look for all of non-A, for every A. But this only makes sense as a field. And this greatly increases the complexity of the node. May be added later.

This would be fantastic. Would love to use this to grow non-intersecting things like plants or rivers. Would be really awesome!

> About groups. > I see that the inverse option would be very useful. That is, don't look for the nearest one in group A, as the search is for group A, but look for all of non-A, for every A. But this only makes sense as a field. And this greatly increases the complexity of the node. May be added later. This would be fantastic. Would love to use this to grow non-intersecting things like plants or rivers. Would be really awesome!
Iliya Katushenock added 1 commit 2023-04-14 05:54:01 +02:00
Iliya Katushenock added this to the 3.6 LTS milestone 2023-04-18 21:16:08 +02:00
Iliya Katushenock added 2 commits 2023-04-18 21:35:07 +02:00
Hans Goudey requested changes 2023-04-18 23:09:34 +02:00
@ -0,0 +77,4 @@
for (const int key : in_group.keys()) {
/* Never empty. */
const IndexMask self_points(use_group ? IndexMask(in_group.lookup(key)) : mask);
Member

No strong opinion, but using regular = assignment seems more consistent here.

No strong opinion, but using regular `=` assignment seems more consistent here.
mod_moder marked this conversation as resolved
@ -0,0 +86,4 @@
continue;
}
KDTree_3d *tree = BLI_kdtree_3d_new(search_points.size());
Member

It seems like creating a KD tree for every group ID will mean that the KD tree for the same set of search points will potentially be built multiple times. There should probably be some way to avoid building a tree multiple times for the same key.

Overall this area looks much cleaner than the last time I checked though!

It seems like creating a KD tree for every group ID will mean that the KD tree for the same set of search points will potentially be built multiple times. There should probably be some way to avoid building a tree multiple times for the same key. Overall this area looks much cleaner than the last time I checked though!
Author
Member

After you point it out... it seems I made some kind of logical error in the implementation of switching groups when iterating over them. Or not .. i need to redo this part of the code

After you point it out... it seems I made some kind of logical error in the implementation of switching groups when iterating over them. Or not .. i need to redo this part of the code
Iliya Katushenock added 2 commits 2023-04-19 17:15:41 +02:00
Iliya Katushenock requested review from Hans Goudey 2023-04-19 17:17:10 +02:00
Iliya Katushenock reviewed 2023-04-20 21:25:01 +02:00
@ -0,0 +119,4 @@
/* The grain size should be larger as each tree gets smaller. */
const int avg_tree_size = group_ids.size() / group_mask_storage.size();
const int grain_size = std::max(8192 / avg_tree_size, 1);
Author
Member

Are grain_size is a power of 2?

Are `grain_size` is a power of 2?
Member

They don't need to be powers of two, though I think it can be slightly beneficial. Since these numbers are a bit arbitrary anyway I started using powers of two.

They don't need to be powers of two, though I think it can be slightly beneficial. Since these numbers are a bit arbitrary anyway I started using powers of two.
Author
Member

I remember fixing a bug, only related to the fact that there was no power of 2x.
For the blur node, I made the search function for the top bit part in the threading namespace.

I remember fixing a bug, only related to the fact that there was no power of 2x. For the blur node, I made the search function for the top bit part in the threading namespace.
Iliya Katushenock reviewed 2023-04-20 21:48:04 +02:00
@ -0,0 +122,4 @@
const int grain_size = std::max(8192 / avg_tree_size, 1);
threading::parallel_for(tree_masks.index_range(), grain_size, [&](const IndexRange range) {
for (const int i : range) {
const IndexMask tree_mask = tree_masks[i];
Author
Member

Why tree_masks[i] and evaluate_masks[i] is groups with the same id? this isn't sorted somether or protected, that mask isn't avoid all elements of some grouop and create offset for all other groups even all of that added in same order.

It seems that trying to use a mask to multiply all user groups by groups from the mask leads to much more overhead in most cases.
If you don't do some kind of analogue of a tree, then it's like doing a boolean through mask1 * mask2. When there is something like bvh. so I think it's easier to just calculate everything. I'm not sure there are many cases now where the mask can actually be a small part.

Why `tree_masks[i]` and `evaluate_masks[i]` is groups with the same id? this isn't sorted somether or protected, that mask isn't avoid all elements of some grouop and create offset for all other groups even all of that added in same order. It seems that trying to use a mask to multiply all user groups by groups from the mask leads to much more overhead in most cases. If you don't do some kind of analogue of a tree, then it's like doing a boolean through mask1 * mask2. When there is something like bvh. so I think it's easier to just calculate everything. I'm not sure there are many cases now where the mask can actually be a small part.
Member

Oh, great point! I think it's still worth having a separate evaluation mask. Even just the set position node with a small selection would benefit from it, I think kd tree lookups are fairly expensive. I guess both masks have to be created at the same time.

Oh, great point! I think it's still worth having a separate evaluation mask. Even just the set position node with a small selection would benefit from it, I think kd tree lookups are fairly expensive. I guess both masks have to be created at the same time.
Iliya Katushenock added 3 commits 2023-04-21 00:00:32 +02:00
Jacques Lucke requested changes 2023-04-21 13:22:30 +02:00
@ -108,0 +110,4 @@
inline int BLI_kdtree_nd_(find_nearest_cb_cpp)(const KDTree *tree,
const float co[KD_DIMS],
KDTreeNearest *r_nearest,
const Fn &fn)
Member

Can just use Fn &&fn, then you also don't need the const cast below I think.

Can just use `Fn &&fn`, then you also don't need the const cast below I think.
Author
Member

Should i also change one other _cpp wrapper above?

Should i also change one other _cpp wrapper above?
Member

Just keep other code as is right now.

Just keep other code as is right now.
@ -0,0 +63,4 @@
MutableSpan<int> r_indices)
{
threading::parallel_for(mask.index_range(), 512, [&](const IndexRange range) {
mask.slice(range).foreach_index([&tree, positions, r_indices](const int index) {
Member

Can just use a normal for loop here. The performance benefit of using foreach_index is negligible, because most time is spend in the kdtree traversal.

Can just use a normal for loop here. The performance benefit of using `foreach_index` is negligible, because most time is spend in the kdtree traversal.
@ -0,0 +108,4 @@
VectorSet<int> group_indexing;
Vector<Vector<int>> mask_indices;
Vector<Vector<int>> tree_indices;
Vector<KDTree_3d *> forest;
Member

While forest is used as a technical term for a graph containing multiple trees, I don't think the term should be used for a collection of multiple independent kd trees. Just use kdtrees.

While forest is used as a technical term for a graph containing multiple trees, I don't think the term should be used for a collection of multiple independent kd trees. Just use `kdtrees`.
Author
Member

If i do merge parallel_fors below, i can delete this vector.

If i do merge `parallel_for`s below, i can delete this vector.
mod_moder marked this conversation as resolved
@ -0,0 +130,4 @@
}
}
threading::parallel_for(group_indexing.index_range(), 8, [&](const IndexRange range) {
Member

Feels like this parallel_for loop and the one below can be combined into one. This could potentially improve multi-threaded performance.

Feels like this `parallel_for` loop and the one below can be combined into one. This could potentially improve multi-threaded performance.
Iliya Katushenock requested review from Jacques Lucke 2023-04-21 14:02:46 +02:00
Iliya Katushenock added 2 commits 2023-04-21 14:02:52 +02:00
Hans Goudey requested changes 2023-04-21 14:21:47 +02:00
@ -0,0 +28,4 @@
return tree;
}
static KDTree_3d *build_kdtree(const Span<float3> &positions, const IndexRange range)
Member

Pass spans by value

Pass spans by value
mod_moder marked this conversation as resolved
@ -0,0 +48,4 @@
Array<int> indices(mask.min_array_size());
const auto nearest_for = [this, &positions](const IndexMask mask, MutableSpan<int> r_indices) {
Member

I don't think there's a benefit to specifying the capture in a case like this (instead of [&])

I don't think there's a benefit to specifying the capture in a case like this (instead of `[&]`)
@ -0,0 +112,4 @@
group_indexing.add(group_id);
}
Vector<Vector<int>> mask_indices(group_indexing.size());
Member

TBH I think it's better to just use int64_t here to avoid duplicating the functions above. Eventually when IndexMask is refactored, these could benefit from using that, and the int64_t will make that more clear too.

TBH I think it's better to just use `int64_t` here to avoid duplicating the functions above. Eventually when `IndexMask` is refactored, these could benefit from using that, and the `int64_t` will make that more clear too.
@ -0,0 +113,4 @@
}
Vector<Vector<int>> mask_indices(group_indexing.size());
Vector<Vector<int>> tree_indices(group_indexing.size());
Member

Vector -> Array here

`Vector` -> `Array` here
mod_moder marked this conversation as resolved
@ -0,0 +127,4 @@
threading::parallel_invoke(
mask.size() + domain_size > 1024,
[&]() { build_group_masks(mask, mask_indices); },
Member

I do think one of these indices could be skipped if the selection is complete

I do think one of these indices could be skipped if the selection is complete
@ -0,0 +201,4 @@
return VArray<bool>::ForSingle(true, mask.min_array_size());
}
/* When a group ID is contained in the set, it means there is only one element with that ID. */
Member

Oops, I forgot to remove this comment.

Oops, I forgot to remove this comment.
mod_moder marked this conversation as resolved
Iliya Katushenock requested review from Hans Goudey 2023-04-21 14:51:19 +02:00
Iliya Katushenock added 1 commit 2023-04-21 14:51:26 +02:00
Hans Goudey requested changes 2023-04-21 14:54:48 +02:00
@ -0,0 +91,4 @@
group_indexing.add(group_id);
}
const bool mask_is_cheap = mask.size() < domain_size / 2;
Member

We can't selectively ignore the mask actually, it may be incorrect to write the output to non-selected indices. So this check really has to be mask.size() == domain_size

We can't selectively ignore the mask actually, it may be incorrect to write the output to non-selected indices. So this check really has to be `mask.size() == domain_size`
Author
Member

I see.
Just if domain size is 10000, mask is 9999, is no much sense to compute the cheap mask. I think, is better to just allocate all elements if cheap mask is used.

I see. Just if domain size is 10000, mask is 9999, is no much sense to compute the cheap mask. I think, is better to just allocate all elements if cheap mask is used.
mod_moder marked this conversation as resolved
@ -0,0 +111,4 @@
};
threading::parallel_invoke(
domain_size > 1024 && mask_is_cheap,
Member

I think this should be !mask_is_cheap

I think this should be `!mask_is_cheap`
Author
Member

I was isn't invented the best name, mask_is_cheap == true if computing of indices make sense and cheap mask is used.

I was isn't invented the best name, `mask_is_cheap` == true if computing of indices make sense and cheap mask is used.
mod_moder marked this conversation as resolved
Iliya Katushenock requested review from Hans Goudey 2023-04-21 15:22:06 +02:00
Iliya Katushenock added 1 commit 2023-04-21 15:22:08 +02:00
Hans Goudey requested changes 2023-04-21 15:55:52 +02:00
@ -0,0 +86,4 @@
}
VectorSet<int> group_indexing;
Member

Remove empty line between group_indexing declaration and loop that creates it

Remove empty line between `group_indexing` declaration and loop that creates it
mod_moder marked this conversation as resolved
@ -0,0 +92,4 @@
group_indexing.add(group_id);
}
const bool use_cheap_mask = mask.size() < domain_size / 2;
Member

Replace this with const bool mask_is_full = mask.size() == domain_size;

Replace this with `const bool mask_is_full = mask.size() == domain_size;`
mod_moder marked this conversation as resolved
@ -0,0 +102,4 @@
mask_indices.reinitialize(group_indexing.size());
}
else {
result.reinitialize(domain_size);
Member

The result array should never need to be larger than mask.min_array_size()

The result array should never need to be larger than `mask.min_array_size()`
mod_moder marked this conversation as resolved
@ -0,0 +118,4 @@
threading::parallel_invoke(
domain_size > 1024 && use_cheap_mask,
[&]() {
Member

domain_size > 1024 && use_cheap_mask -> domain_size > 1024 && !mask_is_full

`domain_size > 1024 && use_cheap_mask` -> `domain_size > 1024 && !mask_is_full`
@ -0,0 +127,4 @@
threading::parallel_for(group_indexing.index_range(), 256, [&](const IndexRange range) {
for (const int index : range) {
const Span<int64_t> mask_of_tree = tree_indices[index];
Member

Use IndexMask instead of Span<int64_t> for these mask_of_tree and mask variables

Use `IndexMask` instead of `Span<int64_t>` for these `mask_of_tree` and `mask` variables
mod_moder marked this conversation as resolved
@ -0,0 +128,4 @@
threading::parallel_for(group_indexing.index_range(), 256, [&](const IndexRange range) {
for (const int index : range) {
const Span<int64_t> mask_of_tree = tree_indices[index];
KDTree_3d &tree = *build_kdtree(positions, mask_of_tree);
Member

References typically indicate a lack of ownership, better to stick with a pointer here

References typically indicate a lack of ownership, better to stick with a pointer here
mod_moder marked this conversation as resolved
Iliya Katushenock requested review from Hans Goudey 2023-04-21 16:28:26 +02:00
Iliya Katushenock added 2 commits 2023-04-21 16:28:27 +02:00
Hans Goudey approved these changes 2023-04-21 21:43:07 +02:00
Hans Goudey left a comment
Member

Looks very nice now, thanks for going along with my requests/edits.

Looks very nice now, thanks for going along with my requests/edits.
Iliya Katushenock added 2 commits 2023-04-22 12:00:21 +02:00
Jacques Lucke approved these changes 2023-04-22 13:07:54 +02:00
Jacques Lucke merged commit 15f9e42c4f into main 2023-04-22 13:12:03 +02:00
Iliya Katushenock deleted branch index_of_nearest 2023-04-22 13:12:43 +02:00
Sign in to join this conversation.
No reviewers
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No Assignees
5 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#104619
No description provided.