UI: Asset Shelf (Experimental Feature) #104831

Closed
Julian Eisel wants to merge 399 commits from asset-shelf into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
16 changed files with 230 additions and 75 deletions
Showing only changes of commit 8bbe25c650 - Show all commits

View File

@ -188,6 +188,23 @@ using uchar = unsigned char;
using sycl::half;
/* math functions */
ccl_device_forceinline float __uint_as_float(uint x)
{
return sycl::bit_cast<float>(x);
}
ccl_device_forceinline uint __float_as_uint(float x)
{
return sycl::bit_cast<uint>(x);
}
ccl_device_forceinline float __int_as_float(int x)
{
return sycl::bit_cast<float>(x);
}
ccl_device_forceinline int __float_as_int(float x)
{
return sycl::bit_cast<int>(x);
}
#define fabsf(x) sycl::fabs((x))
#define copysignf(x, y) sycl::copysign((x), (y))
#define asinf(x) sycl::asin((x))

View File

@ -101,7 +101,6 @@ using std::isfinite;
using std::isnan;
using std::sqrt;
# else
using sycl::sqrt;
# define isfinite(x) sycl::isfinite((x))
# define isnan(x) sycl::isnan((x))
# endif
@ -207,7 +206,7 @@ ccl_device_inline float max4(float a, float b, float c, float d)
return max(max(a, b), max(c, d));
}
#if !defined(__KERNEL_METAL__)
#if !defined(__KERNEL_METAL__) && !defined(__KERNEL_ONEAPI__)
/* Int/Float conversion */
ccl_device_inline int as_int(uint i)
@ -795,7 +794,10 @@ ccl_device float bits_to_01(uint bits)
#if !defined(__KERNEL_GPU__)
# if defined(__GNUC__)
# define popcount(x) __builtin_popcount(x)
ccl_device_inline uint popcount(uint x)
{
return __builtin_popcount(x);
}
# else
ccl_device_inline uint popcount(uint x)
{

View File

@ -892,29 +892,6 @@ void GHOST_ContextCGL::initClear()
glClearColor(0.294, 0.294, 0.294, 0.000);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.000, 0.000, 0.000, 0.000);
#endif
}
else {
#if WITH_METAL
// TODO (mg_gpusw_apple) this path is never taken, this is legacy left from inital integration
// of metal and gl, the whole file should be cleaned up and stripped of the legacy path
id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
{
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
attachment.texture = m_defaultFramebufferMetalTexture[current_swapchain_index].texture;
attachment.loadAction = MTLLoadActionClear;
attachment.clearColor = MTLClearColorMake(0.294, 0.294, 0.294, 1.000);
attachment.storeAction = MTLStoreActionStore;
}
// encoding
{
id<MTLRenderCommandEncoder> enc = [cmdBuffer
renderCommandEncoderWithDescriptor:passDescriptor];
[enc endEncoding];
}
[cmdBuffer commit];
#endif
}
}

View File

@ -35,6 +35,7 @@
struct bContext;
struct uiBlock;
struct uiButViewItem;
struct uiLayout;
struct uiViewItemHandle;
struct ViewLink;
@ -91,6 +92,8 @@ class AbstractView {
*/
virtual bool begin_filtering(const bContext &C) const;
virtual void draw_overlays(const ARegion &region) const;
/**
* Makes \a item valid for display in this view. Behavior is undefined for items not registered
* with this.
@ -139,6 +142,8 @@ class AbstractViewItem {
* If this wasn't done, the behavior of items is undefined.
*/
AbstractView *view_ = nullptr;
/** Every visible item gets a button of type #UI_BTYPE_VIEW_ITEM during the layout building. */
uiButViewItem *view_item_but_ = nullptr;
bool is_activatable_ = true;
bool is_interactive_ = true;
bool is_active_ = false;

View File

@ -26,7 +26,6 @@
struct bContext;
struct uiBlock;
struct uiBut;
struct uiButViewItem;
struct uiLayout;
namespace blender::ui {
@ -119,6 +118,8 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
public:
virtual ~AbstractTreeView() = default;
void draw_overlays(const ARegion &region) const override;
void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
/** Visual feature: Define a number of item rows the view will always show at minimum. If there
@ -143,6 +144,11 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
* the actual state changes are done in a delayed manner through this function.
*/
void change_state_delayed();
void draw_hierarchy_lines(const ARegion &region) const;
void draw_hierarchy_lines_recursive(const ARegion &region,
const TreeViewOrItem &parent,
uint pos) const;
AbstractTreeViewItem *find_last_visible_descendant(const AbstractTreeViewItem &parent) const;
};
/** \} */
@ -170,8 +176,6 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain
protected:
/** This label is used as the default way to identifying an item within its parent. */
std::string label_{};
/** Every visible item gets a button of type #UI_BTYPE_VIEW_ITEM during the layout building. */
uiButViewItem *view_item_but_ = nullptr;
public:
virtual ~AbstractTreeViewItem() = default;
@ -247,7 +251,7 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain
void ensure_parents_uncollapsed();
uiButViewItem *view_item_button();
uiButViewItem *view_item_button() const;
private:
static void tree_row_click_fn(struct bContext *, void *, void *);
@ -258,6 +262,7 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain
void change_state_delayed();
void add_treerow_button(uiBlock &block);
int indent_width() const;
void add_indent(uiLayout &row) const;
void add_collapse_chevron(uiBlock &block) const;
void add_rename_button(uiLayout &row);

View File

@ -80,10 +80,6 @@
using blender::Vector;
/* prototypes. */
static void ui_but_to_pixelrect(rcti *rect,
const ARegion *region,
uiBlock *block,
const uiBut *but);
static void ui_def_but_rna__menu(bContext * /*C*/, uiLayout *layout, void *but_p);
static void ui_def_but_rna__panel_type(bContext * /*C*/, uiLayout *layout, void *but_p);
static void ui_def_but_rna__menu_type(bContext * /*C*/, uiLayout *layout, void *but_p);
@ -912,6 +908,7 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
case UI_BTYPE_VIEW_ITEM: {
uiButViewItem *view_item_oldbut = (uiButViewItem *)oldbut;
uiButViewItem *view_item_newbut = (uiButViewItem *)but;
ui_view_item_swap_button_pointers(view_item_newbut->view_item, view_item_oldbut->view_item);
std::swap(view_item_newbut->view_item, view_item_oldbut->view_item);
break;
}
@ -2069,11 +2066,7 @@ void ui_fontscale(float *points, float aspect)
*points /= aspect;
}
/* Project button or block (but==nullptr) to pixels in region-space. */
static void ui_but_to_pixelrect(rcti *rect,
const ARegion *region,
uiBlock *block,
const uiBut *but)
void ui_but_to_pixelrect(rcti *rect, const ARegion *region, const uiBlock *block, const uiBut *but)
{
rctf rectf;
@ -2169,6 +2162,8 @@ void UI_block_draw(const bContext *C, uiBlock *block)
UI_icon_draw_cache_end();
BLF_batch_draw_end();
ui_block_views_draw_overlays(region, block);
/* restore matrix */
GPU_matrix_pop_projection();
GPU_matrix_pop();

View File

@ -600,6 +600,12 @@ struct uiSafetyRct {
void ui_fontscale(float *points, float aspect);
/** Project button or block (but==nullptr) to pixels in region-space. */
void ui_but_to_pixelrect(rcti *rect,
const ARegion *region,
const uiBlock *block,
const uiBut *but);
void ui_block_to_region_fl(const ARegion *region, const uiBlock *block, float *r_x, float *r_y);
void ui_block_to_window_fl(const ARegion *region, const uiBlock *block, float *x, float *y);
void ui_block_to_window(const ARegion *region, const uiBlock *block, int *x, int *y);
@ -1461,12 +1467,17 @@ void ui_interface_tag_script_reload_queries();
void ui_block_free_views(uiBlock *block);
void ui_block_views_bounds_calc(const uiBlock *block);
void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params);
void ui_block_views_draw_overlays(const ARegion *region, const uiBlock *block);
uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
const uiViewHandle *new_view);
uiButViewItem *ui_block_view_find_matching_view_item_but_in_old_block(
const uiBlock *new_block, const uiViewItemHandle *new_item_handle);
/* abstract_view_item.cc */
void ui_view_item_swap_button_pointers(uiViewItemHandle *a_handle, uiViewItemHandle *b_handle);
/* interface_templates.cc */
uiListType *UI_UL_cache_file_layers();

View File

@ -83,6 +83,11 @@ bool AbstractView::begin_filtering(const bContext & /*C*/) const
return false;
}
void AbstractView::draw_overlays(const ARegion & /*region*/) const
{
/* Nothing by default. */
}
/** \} */
/* ---------------------------------------------------------------------- */

View File

@ -286,6 +286,11 @@ class ViewItemAPIWrapper {
return a.matches(b);
}
static void swap_button_pointers(AbstractViewItem &a, AbstractViewItem &b)
{
std::swap(a.view_item_but_, b.view_item_but_);
}
static bool can_rename(const AbstractViewItem &item)
{
const AbstractView &view = item.get_view();
@ -340,6 +345,16 @@ bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHand
return ViewItemAPIWrapper::matches(a, b);
}
void ui_view_item_swap_button_pointers(uiViewItemHandle *a_handle, uiViewItemHandle *b_handle)
{
if (!a_handle || !b_handle) {
return;
}
AbstractViewItem &a = reinterpret_cast<AbstractViewItem &>(*a_handle);
AbstractViewItem &b = reinterpret_cast<AbstractViewItem &>(*b_handle);
ViewItemAPIWrapper::swap_button_pointers(a, b);
}
bool UI_view_item_can_rename(const uiViewItemHandle *item_handle)
{
const AbstractViewItem &item = reinterpret_cast<const AbstractViewItem &>(*item_handle);

View File

@ -142,6 +142,13 @@ void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *l
}
}
void ui_block_views_draw_overlays(const ARegion *region, const uiBlock *block)
{
LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
view_link->view->draw_overlays(*region);
}
}
uiViewHandle *UI_region_view_find_at(const ARegion *region, const int xy[2], const int pad)
{
/* NOTE: Similar to #ui_but_find_mouse_over_ex(). */

View File

@ -13,6 +13,8 @@
#include "BLT_translation.h"
#include "GPU_immediate.h"
#include "interface_intern.hh"
#include "UI_interface.h"
@ -81,6 +83,92 @@ void AbstractTreeView::set_min_rows(int min_rows)
min_rows_ = min_rows;
}
AbstractTreeViewItem *AbstractTreeView::find_last_visible_descendant(
const AbstractTreeViewItem &parent) const
{
if (parent.is_collapsed()) {
return nullptr;
}
AbstractTreeViewItem *last_descendant = parent.children_.last().get();
while (!last_descendant->children_.is_empty() && !last_descendant->is_collapsed()) {
last_descendant = last_descendant->children_.last().get();
}
return last_descendant;
}
void AbstractTreeView::draw_hierarchy_lines_recursive(const ARegion &region,
const TreeViewOrItem &parent,
const uint pos) const
{
for (const auto &item : parent.children_) {
if (!item->is_collapsible() || item->is_collapsed()) {
continue;
}
draw_hierarchy_lines_recursive(region, *item, pos);
const AbstractTreeViewItem *first_descendant = item->children_.first().get();
const AbstractTreeViewItem *last_descendant = find_last_visible_descendant(*item);
if (!first_descendant->view_item_but_ || !last_descendant || !last_descendant->view_item_but_)
{
return;
}
const uiButViewItem &first_child_but = *first_descendant->view_item_button();
const uiButViewItem &last_child_but = *last_descendant->view_item_button();
BLI_assert(first_child_but.block == last_child_but.block);
const uiBlock *block = first_child_but.block;
rcti first_child_rect;
ui_but_to_pixelrect(&first_child_rect, &region, block, &first_child_but);
rcti last_child_rect;
ui_but_to_pixelrect(&last_child_rect, &region, block, &last_child_but);
/* Small vertical padding. */
const short line_padding = UI_UNIT_Y / 4.0f;
const float x = first_child_rect.xmin + first_descendant->indent_width() -
UI_ICON_SIZE * 0.5f + 2 * UI_SCALE_FAC;
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(pos, x, first_child_rect.ymax - line_padding);
immVertex2f(pos, x, last_child_rect.ymin + line_padding);
immEnd();
}
}
void AbstractTreeView::draw_hierarchy_lines(const ARegion &region) const
{
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
uchar col[4];
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2] / UI_SCALE_FAC, viewport_size[3] / UI_SCALE_FAC);
immUniform1i("colors_len", 0); /* "simple" mode */
immUniform1f("dash_width", 8.0f);
/* >= is 1.0 for un-dashed lines. */
immUniform1f("udash_factor", 1.0f);
UI_GetThemeColorBlend3ubv(TH_BACK, TH_TEXT, 0.4f, col);
col[3] = 255;
immUniformColor4ubv(col);
GPU_line_width(1.0f);
GPU_blend(GPU_BLEND_ALPHA);
draw_hierarchy_lines_recursive(region, *this, pos);
GPU_blend(GPU_BLEND_NONE);
immUnbindProgram();
}
void AbstractTreeView::draw_overlays(const ARegion &region) const
{
draw_hierarchy_lines(region);
}
void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
{
const AbstractTreeView &old_tree_view = dynamic_cast<const AbstractTreeView &>(old_view);
@ -162,14 +250,18 @@ void AbstractTreeViewItem::add_treerow_button(uiBlock &block)
UI_but_func_set(view_item_but_, tree_row_click_fn, view_item_but_, nullptr);
}
int AbstractTreeViewItem::indent_width() const
{
return count_parents() * UI_ICON_SIZE;
}
void AbstractTreeViewItem::add_indent(uiLayout &row) const
{
uiBlock *block = uiLayoutGetBlock(&row);
uiLayout *subrow = uiLayoutRow(&row, true);
uiLayoutSetFixedSize(subrow, true);
const float indent_size = count_parents() * UI_ICON_SIZE;
uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, "");
uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_width(), 0, nullptr, 0.0, 0.0, 0, 0, "");
/* Indent items without collapsing icon some more within their parent. Makes it clear that they
* are actually nested and not just a row at the same level without a chevron. */
@ -407,7 +499,7 @@ bool AbstractTreeViewItem::matches(const AbstractViewItem &other) const
return true;
}
uiButViewItem *AbstractTreeViewItem::view_item_button()
uiButViewItem *AbstractTreeViewItem::view_item_button() const
{
return view_item_but_;
}

View File

@ -52,7 +52,14 @@ std::optional<Mesh *> mesh_merge_by_distance_connected(const Mesh &mesh,
* this is not supported and will likely generate corrupted geometry.
*
* \param vert_dest_map_len: The number of non '-1' values in `vert_dest_map`. (not the size)
* \param do_mix_vert_data: If true, the groups of vertices in the `vert_dest_map_len`, defined by
* source vertices with the same target plus the target vertex, will have their custom data
* interpolated into the resulting vertex. If false, only the custom data of the target vertex will
* remain.
*/
Mesh *mesh_merge_verts(const Mesh &mesh, MutableSpan<int> vert_dest_map, int vert_dest_map_len);
Mesh *mesh_merge_verts(const Mesh &mesh,
MutableSpan<int> vert_dest_map,
int vert_dest_map_len,
const bool do_mix_vert_data);
} // namespace blender::geometry

View File

@ -87,6 +87,7 @@ struct WeldPoly {
struct WeldMesh {
/* Group of vertices to be merged. */
Array<int> vert_group_map; /* Maps the vertex group offset from the target vert index. */
Array<int> vert_groups_offs;
Array<int> vert_groups_buffer;
@ -356,14 +357,15 @@ static Vector<int> weld_vert_ctx_alloc_and_setup(MutableSpan<int> vert_dest_map,
static void weld_vert_groups_setup(Span<int> wvert,
Span<int> vert_dest_map,
const int vert_kill_len,
MutableSpan<int> r_vert_groups_map,
Array<int> &r_vert_groups_map,
Array<int> &r_vert_groups_buffer,
Array<int> &r_vert_groups_offs)
{
r_vert_groups_map.reinitialize(vert_dest_map.size());
r_vert_groups_map.fill(OUT_OF_CONTEXT);
const int vert_groups_len = wvert.size() - vert_kill_len;
/* Add +1 to allow calculation of the length of the last group. */
const int vert_groups_len = wvert.size() - vert_kill_len;
r_vert_groups_offs.reinitialize(vert_groups_len + 1);
r_vert_groups_offs.fill(0);
@ -1374,7 +1376,7 @@ static void weld_poly_find_doubles(const Span<int> corner_verts,
static void weld_mesh_context_create(const Mesh &mesh,
MutableSpan<int> vert_dest_map,
const int vert_kill_len,
MutableSpan<int> r_vert_group_map,
const bool do_vert_group,
WeldMesh *r_weld_mesh)
{
const Span<int2> edges = mesh.edges();
@ -1417,12 +1419,14 @@ static void weld_mesh_context_create(const Mesh &mesh,
edges.size(),
r_weld_mesh);
weld_vert_groups_setup(wvert,
vert_dest_map,
vert_kill_len,
r_vert_group_map,
r_weld_mesh->vert_groups_buffer,
r_weld_mesh->vert_groups_offs);
if (do_vert_group) {
weld_vert_groups_setup(wvert,
vert_dest_map,
vert_kill_len,
r_weld_mesh->vert_group_map,
r_weld_mesh->vert_groups_buffer,
r_weld_mesh->vert_groups_offs);
}
weld_edge_groups_setup(edges.size(),
r_weld_mesh->edge_kill_len,
@ -1545,7 +1549,8 @@ static void customdata_weld(
static Mesh *create_merged_mesh(const Mesh &mesh,
MutableSpan<int> vert_dest_map,
const int removed_vertex_count)
const int removed_vertex_count,
const bool do_mix_vert_data)
{
#ifdef USE_WELD_DEBUG_TIME
SCOPED_TIMER(__func__);
@ -1557,10 +1562,9 @@ static Mesh *create_merged_mesh(const Mesh &mesh,
const int totvert = mesh.totvert;
const int totedge = mesh.totedge;
Array<int> vert_group_map(totvert);
WeldMesh weld_mesh;
weld_mesh_context_create(mesh, vert_dest_map, removed_vertex_count, vert_group_map, &weld_mesh);
weld_mesh_context_create(
mesh, vert_dest_map, removed_vertex_count, do_mix_vert_data, &weld_mesh);
const int result_nverts = totvert - weld_mesh.vert_kill_len;
const int result_nedges = totedge - weld_mesh.edge_kill_len;
@ -1576,15 +1580,23 @@ static Mesh *create_merged_mesh(const Mesh &mesh,
/* Vertices. */
/* Be careful when setting values to this array as it uses the same buffer as #vert_group_map.
* This map will be used to adjust edges and loops to point to new vertex indices. */
MutableSpan<int> vert_final_map = vert_group_map;
MutableSpan<int> vert_final_map;
Array<int> vert_final_map_local;
if (!weld_mesh.vert_group_map.is_empty()) {
/* Be careful when setting values to this array as it uses the same buffer as #vert_group_map.
* This map will be used to adjust edges and loops to point to new vertex indices. */
vert_final_map = weld_mesh.vert_group_map;
}
else {
vert_final_map_local.reinitialize(totvert);
vert_final_map = vert_final_map_local;
}
int dest_index = 0;
for (int i = 0; i < totvert; i++) {
int source_index = i;
int count = 0;
while (i < totvert && vert_group_map[i] == OUT_OF_CONTEXT) {
while (i < totvert && vert_dest_map[i] == OUT_OF_CONTEXT) {
vert_final_map[i] = dest_index + count;
count++;
i++;
@ -1596,15 +1608,16 @@ static Mesh *create_merged_mesh(const Mesh &mesh,
if (i == totvert) {
break;
}
if (vert_group_map[i] != ELEM_MERGED) {
int *src_indices;
int count;
{
int *wgroup = &weld_mesh.vert_groups_offs[vert_group_map[i]];
src_indices = &weld_mesh.vert_groups_buffer[*wgroup];
count = *(wgroup + 1) - *wgroup;
if (vert_dest_map[i] == i) {
if (do_mix_vert_data) {
int *group_offs = &weld_mesh.vert_groups_offs[weld_mesh.vert_group_map[i]];
int *src_indices = &weld_mesh.vert_groups_buffer[*group_offs];
int count = *(group_offs + 1) - *group_offs;
customdata_weld(&mesh.vdata, &result->vdata, src_indices, count, dest_index);
}
else {
CustomData_copy_data(&mesh.vdata, &result->vdata, i, dest_index, 1);
}
customdata_weld(&mesh.vdata, &result->vdata, src_indices, count, dest_index);
vert_final_map[i] = dest_index;
dest_index++;
}
@ -1770,7 +1783,7 @@ std::optional<Mesh *> mesh_merge_by_distance_all(const Mesh &mesh,
return std::nullopt;
}
return create_merged_mesh(mesh, vert_dest_map, vert_kill_len);
return create_merged_mesh(mesh, vert_dest_map, vert_kill_len, true);
}
struct WeldVertexCluster {
@ -1869,12 +1882,15 @@ std::optional<Mesh *> mesh_merge_by_distance_connected(const Mesh &mesh,
}
}
return create_merged_mesh(mesh, vert_dest_map, vert_kill_len);
return create_merged_mesh(mesh, vert_dest_map, vert_kill_len, true);
}
Mesh *mesh_merge_verts(const Mesh &mesh, MutableSpan<int> vert_dest_map, int vert_dest_map_len)
Mesh *mesh_merge_verts(const Mesh &mesh,
MutableSpan<int> vert_dest_map,
int vert_dest_map_len,
const bool do_mix_vert_data)
{
return create_merged_mesh(mesh, vert_dest_map, vert_dest_map_len);
return create_merged_mesh(mesh, vert_dest_map, vert_dest_map_len, do_mix_vert_data);
}
/** \} */

View File

@ -826,7 +826,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd,
if (tot_doubles > 0) {
Mesh *tmp = result;
result = geometry::mesh_merge_verts(
*tmp, MutableSpan<int>{full_doubles_map, result->totvert}, tot_doubles);
*tmp, MutableSpan<int>{full_doubles_map, result->totvert}, tot_doubles, false);
BKE_id_free(nullptr, tmp);
}
MEM_freeN(full_doubles_map);

View File

@ -87,7 +87,7 @@ static Mesh *mirror_apply_on_axis(MirrorModifierData *mmd,
if (vert_merge_map_len) {
Mesh *tmp = result;
result = geometry::mesh_merge_verts(
*tmp, MutableSpan<int>{vert_merge_map, result->totvert}, vert_merge_map_len);
*tmp, MutableSpan<int>{vert_merge_map, result->totvert}, vert_merge_map_len, false);
BKE_id_free(nullptr, tmp);
}
MEM_freeN(vert_merge_map);

View File

@ -180,7 +180,8 @@ static Mesh *mesh_remove_doubles_on_axis(Mesh *result,
* Therefore the duplicate polygon test can be skipped. */
result = geometry::mesh_merge_verts(*tmp,
MutableSpan<int>{full_doubles_map, result->totvert},
int(tot_doubles * (step_tot - 1)));
int(tot_doubles * (step_tot - 1)),
false);
BKE_id_free(nullptr, tmp);
MEM_freeN(full_doubles_map);