diff --git a/source/blender/blenkernel/BKE_customdata.hh b/source/blender/blenkernel/BKE_customdata.hh index de51e6be5c9..17e19b3c71c 100644 --- a/source/blender/blenkernel/BKE_customdata.hh +++ b/source/blender/blenkernel/BKE_customdata.hh @@ -342,8 +342,6 @@ void CustomData_copy_data_layer(const CustomData *source, int src_index, int dst_index, int count); -void CustomData_copy_data_named( - const CustomData *source, CustomData *dest, int source_index, int dest_index, int count); void CustomData_copy_elements(eCustomDataType type, void *src_data_ofs, void *dst_data_ofs, diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 8d268de5ab6..ece381d5531 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -3313,25 +3313,6 @@ void CustomData_copy_data_layer(const CustomData *source, } } -void CustomData_copy_data_named(const CustomData *source, - CustomData *dest, - const int source_index, - const int dest_index, - const int count) -{ - /* copies a layer at a time */ - for (int src_i = 0; src_i < source->totlayer; src_i++) { - - int dest_i = CustomData_get_named_layer_index( - dest, eCustomDataType(source->layers[src_i].type), source->layers[src_i].name); - - /* if we found a matching layer, copy the data */ - if (dest_i != -1) { - CustomData_copy_data_layer(source, dest, src_i, dest_i, source_index, dest_index, count); - } - } -} - void CustomData_copy_data(const CustomData *source, CustomData *dest, const int source_index, diff --git a/source/blender/editors/mesh/mesh_join.cc b/source/blender/editors/mesh/mesh_join.cc index 71aa474a065..dcc998dba4a 100644 --- a/source/blender/editors/mesh/mesh_join.cc +++ b/source/blender/editors/mesh/mesh_join.cc @@ -13,7 +13,6 @@ #include "BLI_listbase.h" #include "BLI_math_matrix.h" #include "BLI_math_matrix.hh" -#include "BLI_math_vector.h" #include "BLI_vector.hh" #include "BLI_virtual_array.hh" @@ -43,6 +42,7 @@ #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_query.hh" +#include "ED_geometry.hh" #include "ED_mesh.hh" #include "ED_object.hh" #include "ED_view3d.hh" @@ -54,240 +54,330 @@ namespace blender::ed::mesh { -/* join selected meshes into the active mesh, context sensitive - * return 0 if no join is made (error) and 1 if the join is done */ - -static void join_mesh_single(Depsgraph *depsgraph, - Main *bmain, - Scene *scene, - Object *ob_dst, - Object *ob_src, - const float4x4 &world_to_active_object, - MutableSpan dst_positions, - MutableSpan dst_edges, - MutableSpan dst_corner_verts, - MutableSpan dst_corner_edges, - MutableSpan dst_face_offsets, - CustomData *vert_data, - CustomData *edge_data, - CustomData *face_data, - CustomData *corner_data, - int verts_num, - int edges_num, - int faces_num, - int corners_num, - Key *key, - Key *nkey, - Vector &materials, - const IndexRange vert_range, - const IndexRange edge_range, - const IndexRange face_range, - const IndexRange corner_range) +static VectorSet join_vertex_groups(const Span objects_to_join, + const OffsetIndices vert_ranges, + Mesh &dst_mesh) { - int a; + VectorSet vertex_group_names; + LISTBASE_FOREACH (const bDeformGroup *, dg, &dst_mesh.vertex_group_names) { + vertex_group_names.add_new(dg->name); + } - Mesh *mesh_src = static_cast(ob_src->data); - - if (mesh_src->verts_num) { - /* standard data */ - CustomData_merge_layout( - &mesh_src->vert_data, vert_data, CD_MASK_MESH.vmask, CD_SET_DEFAULT, verts_num); - CustomData_copy_data_named( - &mesh_src->vert_data, vert_data, 0, vert_range.start(), mesh_src->verts_num); - - /* vertex groups */ - MDeformVert *dvert = (MDeformVert *)CustomData_get_layer_for_write( - vert_data, CD_MDEFORMVERT, verts_num); - const MDeformVert *dvert_src = (const MDeformVert *)CustomData_get_layer(&mesh_src->vert_data, - CD_MDEFORMVERT); - - /* Remap to correct new vgroup indices, if needed. */ - if (dvert_src) { - BLI_assert(dvert != nullptr); - - /* Build src to merged mapping of vgroup indices. */ - int *vgroup_index_map; - int vgroup_index_map_len; - vgroup_index_map = BKE_object_defgroup_index_map_create( - ob_src, ob_dst, &vgroup_index_map_len); - BKE_object_defgroup_index_map_apply( - &dvert[vert_range.start()], mesh_src->verts_num, vgroup_index_map, vgroup_index_map_len); - if (vgroup_index_map != nullptr) { - MEM_freeN(vgroup_index_map); + bool any_vertex_group_data = false; + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Mesh &mesh = *static_cast(objects_to_join[i]->data); + any_vertex_group_data |= CustomData_has_layer(&mesh.vert_data, CD_MDEFORMVERT); + LISTBASE_FOREACH (const bDeformGroup *, dg, &mesh.vertex_group_names) { + if (vertex_group_names.add_as(dg->name)) { + BLI_addtail(&dst_mesh.vertex_group_names, BKE_defgroup_duplicate(dg)); } } + } - /* if this is the object we're merging into, no need to do anything */ - if (ob_src != ob_dst) { - float cmat[4][4]; + if (!any_vertex_group_data) { + return vertex_group_names; + } - /* Watch this: switch matrix multiplication order really goes wrong. */ - mul_m4_m4m4(cmat, world_to_active_object.ptr(), ob_src->object_to_world().ptr()); + MDeformVert *dvert = (MDeformVert *)CustomData_add_layer( + &dst_mesh.vert_data, CD_MDEFORMVERT, CD_CONSTRUCT, dst_mesh.verts_num); - math::transform_points(float4x4(cmat), dst_positions.slice(vert_range)); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Mesh &src_mesh = *static_cast(objects_to_join[i]->data); + const Span src_dverts = src_mesh.deform_verts().take_front(vert_ranges[i].size()); + if (src_dverts.is_empty()) { + continue; + } + Vector index_map; + LISTBASE_FOREACH (const bDeformGroup *, dg, &dst_mesh.vertex_group_names) { + index_map.append(vertex_group_names.index_of_as(dg->name)); + } + for (const int vert : src_dverts.index_range()) { + const MDeformVert &src = src_dverts[vert]; + MDeformVert &dst = dvert[vert_ranges[i][vert]]; + dst = src; + dst.dw = MEM_malloc_arrayN(src.totweight, __func__); + for (const int weight : IndexRange(src.totweight)) { + dst.dw[weight].def_nr = index_map[src.dw[weight].def_nr]; + dst.dw[weight].weight = src.dw[weight].weight; + } + } + } - /* For each shape-key in destination mesh: - * - if there's a matching one, copy it across - * (will need to transform vertices into new space...). - * - otherwise, just copy its own coordinates of mesh - * (no need to transform vertex coordinates into new space). - */ - if (key) { - /* if this mesh has any shape-keys, check first, otherwise just copy coordinates */ - LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { - MutableSpan key_data(static_cast(kb->data), kb->totelem); - if (const KeyBlock *src_kb = mesh_src->key ? - BKE_keyblock_find_name(mesh_src->key, kb->name) : - nullptr) - { - const Span src_kb_data(static_cast(src_kb->data), src_kb->totelem); - math::transform_points(src_kb_data, float4x4(cmat), key_data); - } - else { - key_data.slice(vert_range).copy_from(dst_positions.slice(vert_range)); - } + return vertex_group_names; +} + +static void join_positions_and_shape_keys(const Span objects_to_join, + const OffsetIndices vert_ranges, + const float4x4 &world_to_dst_mesh, + Mesh &dst_mesh) +{ + Vector key_blocks; + VectorSet key_names; + if (Key *key = dst_mesh.key) { + LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { + key_names.add_new(kb->name); + key_blocks.append(kb); + } + } + + const auto ensure_dst_key = [&]() { + if (!dst_mesh.key) { + dst_mesh.key = BKE_key_add(nullptr, nullptr); + dst_mesh.key->type = KEY_RELATIVE; + } + }; + + MutableSpan dst_positions = dst_mesh.vert_positions_for_write(); + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Key *src_key = static_cast(objects_to_join[i]->data)->key; + if (!src_key) { + continue; + } + ensure_dst_key(); + LISTBASE_FOREACH (const KeyBlock *, src_kb, &src_key->block) { + if (key_names.add_as(src_kb->name)) { + KeyBlock *dst_kb = BKE_keyblock_add(dst_mesh.key, src_kb->name); + BKE_keyblock_copy_settings(dst_kb, src_kb); + dst_kb->data = MEM_malloc_arrayN(dst_mesh.verts_num, __func__); + dst_kb->totelem = dst_mesh.verts_num; + + /* Initialize the new shape key data with the base positions for the active object. */ + MutableSpan key_data(static_cast(dst_kb->data), dst_kb->totelem); + key_data.take_front(vert_ranges[0].size()) + .copy_from(dst_positions.take_front(vert_ranges[0].size())); + + /* Remap `KeyBlock::relative`. */ + if (const KeyBlock *src_kb_relative = static_cast( + BLI_findlink(&src_key->block, src_kb->relative))) + { + dst_kb->relative = key_names.index_of_as(src_kb_relative->name); } } } + } + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = vert_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_positions = src_mesh.vert_positions().take_front(dst_range.size()); + const float4x4 transform = world_to_dst_mesh * src_object.object_to_world(); + math::transform_points(src_positions, transform, dst_positions.slice(dst_range)); + + if (Key *dst_key = dst_mesh.key) { + LISTBASE_FOREACH (KeyBlock *, kb, &dst_key->block) { + MutableSpan key_data(static_cast(kb->data), kb->totelem); + if (const KeyBlock *src_kb = src_mesh.key ? + BKE_keyblock_find_name(src_mesh.key, kb->name) : + nullptr) + { + const Span src_kb_data(static_cast(src_kb->data), dst_range.size()); + math::transform_points(src_kb_data, transform, key_data.slice(dst_range)); + } + else { + key_data.slice(dst_range).copy_from(dst_positions.slice(dst_range)); + } + } + } + } +} + +static void join_generic_attributes(const Span objects_to_join, + const VectorSet &all_vertex_group_names, + const OffsetIndices vert_ranges, + const OffsetIndices edge_ranges, + const OffsetIndices face_ranges, + const OffsetIndices corner_ranges, + Mesh &dst_mesh) +{ + Set skip_names{"position", + ".edge_verts", + ".corner_vert", + ".corner_edge", + "material_index", + ".sculpt_face_set"}; + + bke::GeometrySet::GatheredAttributes attr_info; + for (const int i : objects_to_join.index_range()) { + const Mesh &mesh = *static_cast(objects_to_join[i]->data); + mesh.attributes().foreach_attribute([&](const bke::AttributeIter &attr) { + if (skip_names.contains(attr.name) || all_vertex_group_names.contains(attr.name)) { + return; + } + attr_info.add(attr.name, {attr.domain, attr.data_type}); + }); + } + + bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); + + const Set attribute_names = dst_attributes.all_ids(); + for (const int attr_i : attr_info.names.index_range()) { + const StringRef name = attr_info.names[attr_i]; + const bke::AttrDomain domain = attr_info.kinds[attr_i].domain; + const bke::AttrType data_type = attr_info.kinds[attr_i].data_type; + if (const std::optional meta_data = dst_attributes.lookup_meta_data( + name)) + { + if (meta_data->domain != domain || meta_data->data_type != data_type) { + AttributeOwner owner = AttributeOwner::from_id(&dst_mesh.id); + geometry::convert_attribute( + owner, dst_attributes, name, meta_data->domain, meta_data->data_type, nullptr); + } + } else { - /* for each shape-key in destination mesh: - * - if it was an 'original', copy the appropriate data from nkey - * - otherwise, copy across plain coordinates (no need to transform coordinates) - */ - if (key) { - LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { - MutableSpan key_data(static_cast(kb->data), kb->totelem); - if (const KeyBlock *src_kb = nkey ? BKE_keyblock_find_name(nkey, kb->name) : nullptr) { - const Span src_kb_data(static_cast(src_kb->data), src_kb->totelem); - key_data.slice(vert_range).copy_from(src_kb_data); - } - else { - key_data.slice(vert_range).copy_from(dst_positions.slice(vert_range)); - } + dst_attributes.add(name, domain, data_type, bke::AttributeInitConstruct()); + } + } + + for (const int attr_i : attr_info.names.index_range()) { + const StringRef name = attr_info.names[attr_i]; + const bke::AttrDomain domain = attr_info.kinds[attr_i].domain; + const bke::AttrType data_type = attr_info.kinds[attr_i].data_type; + + bke::GSpanAttributeWriter dst = dst_attributes.lookup_for_write_span(name); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Mesh &src_mesh = *static_cast(objects_to_join[i]->data); + const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + const GVArray src = *src_attributes.lookup_or_default(name, domain, data_type); + + const IndexRange dst_range = [&]() { + switch (domain) { + case bke::AttrDomain::Point: + return vert_ranges[i]; + case bke::AttrDomain::Edge: + return edge_ranges[i]; + case bke::AttrDomain::Face: + return face_ranges[i]; + case bke::AttrDomain::Corner: + return corner_ranges[i]; + default: + BLI_assert_unreachable(); + return IndexRange(); } + }(); + + src.materialize(IndexRange(dst_range.size()), dst.span.slice(dst_range).data()); + } + dst.finish(); + } +} + +static VectorSet join_materials(const Span objects_to_join, + const OffsetIndices face_ranges, + Mesh &dst_mesh) +{ + VectorSet materials; + for (const int i : objects_to_join.index_range()) { + const Object &src_object = *objects_to_join[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + if (src_mesh.totcol == 0) { + materials.add(nullptr); + continue; + } + for (const int material_index : IndexRange(src_mesh.totcol)) { + Material *material = BKE_object_material_get(&const_cast(src_object), + material_index + 1); + if (materials.size() < MAXMAT) { + materials.add(material); } } } - if (mesh_src->edges_num) { - CustomData_merge_layout( - &mesh_src->edge_data, edge_data, CD_MASK_MESH.emask, CD_SET_DEFAULT, edges_num); - CustomData_copy_data_named( - &mesh_src->edge_data, edge_data, 0, edge_range.start(), mesh_src->edges_num); + bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); + if (materials.size() <= 1) { + dst_attributes.remove("material_index"); + return materials; + } - for (int2 &edge : dst_edges.slice(edge_range)) { - edge += vert_range.start(); + bke::SpanAttributeWriter dst_material_indices = dst_attributes.lookup_or_add_for_write_span( + "material_index", bke::AttrDomain::Face); + if (!dst_material_indices) { + return {}; + } + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = face_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + + const VArray material_indices = *src_attributes.lookup("material_index", + bke::AttrDomain::Face); + if (material_indices.is_empty()) { + Material *first_material = src_mesh.totcol == 0 ? + nullptr : + BKE_object_material_get(&const_cast(src_object), 1); + dst_material_indices.span.slice(dst_range).fill(materials.index_of(first_material)); + continue; + } + + if (src_mesh.totcol == 0) { + /* These material indices are invalid, but copy them anyway to avoid destroying user data. */ + material_indices.materialize(dst_range.index_range(), + dst_material_indices.span.slice(dst_range)); + continue; + } + + Array index_map(src_mesh.totcol); + for (const int material_index : IndexRange(src_mesh.totcol)) { + Material *material = BKE_object_material_get(&const_cast(src_object), + material_index + 1); + const int dst_index = materials.index_of_try(material); + index_map[material_index] = dst_index == -1 ? 0 : dst_index; + } + + const int max = src_mesh.totcol - 1; + for (const int face : dst_range.index_range()) { + const int src = std::clamp(material_indices[face], 0, max); + dst_material_indices.span[dst_range[face]] = index_map[src]; } } - if (mesh_src->corners_num) { - if (ob_src != ob_dst) { - MultiresModifierData *mmd; + dst_material_indices.finish(); - multiresModifier_prepare_join(depsgraph, scene, ob_src, ob_dst); - - if ((mmd = get_multires_modifier(scene, ob_src, true))) { - object::iter_other(bmain, ob_src, true, object::multires_update_totlevels, &mmd->totlvl); - } - } - - CustomData_merge_layout( - &mesh_src->corner_data, corner_data, CD_MASK_MESH.lmask, CD_SET_DEFAULT, corners_num); - CustomData_copy_data_named( - &mesh_src->corner_data, corner_data, 0, corner_range.start(), mesh_src->corners_num); - - for (int &vert : dst_corner_verts.slice(corner_range)) { - vert += vert_range.start(); - } - for (int &edge : dst_corner_edges.slice(corner_range)) { - edge += edge_range.start(); - } - } - - /* Make remapping for material indices. Assume at least one slot, - * that will be null if there are no actual slots. */ - const int totcol = std::max(ob_src->totcol, 1); - Vector matmap(totcol); - if (mesh_src->faces_num) { - for (a = 1; a <= totcol; a++) { - Material *ma = (a <= ob_src->totcol) ? BKE_object_material_get(ob_src, a) : nullptr; - - /* Try to reuse existing slot. */ - int b = 0; - for (; b < materials.size(); b++) { - if (ma == materials[b]) { - matmap[a - 1] = b; - break; - } - } - - if (b == materials.size()) { - if (materials.size() == MAXMAT) { - /* Reached max limit of materials, use first slot. */ - matmap[a - 1] = 0; - } - else { - /* Add new slot. */ - matmap[a - 1] = materials.size(); - materials.append(ma); - if (ma) { - id_us_plus(&ma->id); - } - } - } - } - - CustomData_merge_layout( - &mesh_src->face_data, face_data, CD_MASK_MESH.pmask, CD_SET_DEFAULT, faces_num); - CustomData_copy_data_named( - &mesh_src->face_data, face_data, 0, face_range.start(), mesh_src->faces_num); - - /* Apply matmap. In case we don't have material indices yet, create them if more than one - * material is the result of joining. */ - int *material_indices = static_cast(CustomData_get_layer_named_for_write( - face_data, CD_PROP_INT32, "material_index", faces_num)); - if (!material_indices && materials.size() > 1) { - material_indices = (int *)CustomData_add_layer_named( - face_data, CD_PROP_INT32, CD_SET_DEFAULT, faces_num, "material_index"); - } - if (material_indices) { - for (a = 0; a < mesh_src->faces_num; a++) { - /* Clamp invalid slots, matching #BKE_object_material_get_p. */ - const int mat_index = std::clamp(material_indices[a + face_range.start()], 0, totcol - 1); - material_indices[a + face_range.start()] = matmap[mat_index]; - } - } - - const Span src_face_offsets = mesh_src->face_offsets(); - for (const int i : face_range.index_range()) { - dst_face_offsets[face_range[i]] = src_face_offsets[i] + corner_range.start(); - } - } + return materials; } /* Face Sets IDs are a sparse sequence, so this function offsets all the IDs by face_set_offset and * updates face_set_offset with the maximum ID value. This way, when used in multiple meshes, all * of them will have different IDs for their Face Sets. */ -static void mesh_join_offset_face_sets_ID(Mesh *mesh, int *face_set_offset) +static void join_face_sets(const Span objects_to_join, + const OffsetIndices face_ranges, + Mesh &dst_mesh) { - bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); - bke::SpanAttributeWriter face_sets = attributes.lookup_for_write_span( + bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); + bke::SpanAttributeWriter dst_face_sets = dst_attributes.lookup_for_write_span( ".sculpt_face_set"); - if (!face_sets) { + if (!dst_face_sets) { + return; + } + if (dst_face_sets.domain != bke::AttrDomain::Face) { return; } - int max_face_set = 0; - for (const int i : face_sets.span.index_range()) { - /* As face sets encode the visibility in the integer sign, the offset needs to be added or - * subtracted depending on the initial sign of the integer to get the new ID. */ - if (face_sets.span[i] <= *face_set_offset) { - face_sets.span[i] += *face_set_offset; + int max_face_set = 1; + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = face_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + const VArraySpan src_face_sets = *src_attributes.lookup(".sculpt_face_set", + bke::AttrDomain::Face); + if (src_face_sets.is_empty()) { + dst_face_sets.span.slice(dst_range).fill(max_face_set); } - max_face_set = max_ii(max_face_set, face_sets.span[i]); + else { + for (const int face : dst_range.index_range()) { + dst_face_sets.span[dst_range[face]] = src_face_sets[face] + max_face_set; + } + max_face_set = std::max( + max_face_set, + *std::max_element(src_face_sets.begin(), src_face_sets.begin() + dst_range.size())); + } + max_face_set++; } - *face_set_offset = max_face_set; - face_sets.finish(); + dst_face_sets.finish(); } wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) @@ -328,8 +418,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) objects_to_join.prepend(active_object); } - int haskey = 0; - Array vert_offset_data(objects_to_join.size() + 1); Array edge_offset_data(objects_to_join.size() + 1); Array face_offset_data(objects_to_join.size() + 1); @@ -340,9 +428,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) edge_offset_data[i] = mesh.edges_num; face_offset_data[i] = mesh.faces_num; corner_offset_data[i] = mesh.corners_num; - if (mesh.key) { - haskey++; - } } const OffsetIndices vert_ranges = offset_indices::accumulate_counts_to_offsets( @@ -384,201 +469,117 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Active object materials in new main array, is nicer start! */ - Vector materials; - for (const int a : IndexRange(active_object->totcol)) { - materials.append(BKE_object_material_get(active_object, a + 1)); - id_us_plus((ID *)materials[a]); - /* increase id->us : will be lowered later */ + CustomData_realloc(&dst_mesh->vert_data, dst_mesh->verts_num, vert_ranges.total_size()); + CustomData_realloc(&dst_mesh->edge_data, dst_mesh->edges_num, edge_ranges.total_size()); + CustomData_realloc(&dst_mesh->face_data, dst_mesh->faces_num, face_ranges.total_size()); + CustomData_realloc(&dst_mesh->corner_data, dst_mesh->corners_num, corner_ranges.total_size()); + if (face_ranges.total_size() != dst_mesh->faces_num) { + implicit_sharing::resize_trivial_array(&dst_mesh->face_offset_indices, + &dst_mesh->runtime->face_offsets_sharing_info, + dst_mesh->faces_num, + face_ranges.total_size() + 1); } - - /* - If destination mesh had shape-keys, move them somewhere safe, and set up placeholders - * with arrays that are large enough to hold shape-key data for all meshes. - * - If destination mesh didn't have shape-keys, but we encountered some in the meshes we're - * joining, set up a new key-block and assign to the mesh. - */ - Key *nkey = nullptr; - if (key) { - /* make a duplicate copy that will only be used here... (must remember to free it!) */ - nkey = (Key *)BKE_id_copy(bmain, &key->id); - - /* for all keys in old block, clear data-arrays */ + dst_mesh->verts_num = vert_ranges.total_size(); + dst_mesh->edges_num = edge_ranges.total_size(); + dst_mesh->faces_num = face_ranges.total_size(); + dst_mesh->corners_num = corner_ranges.total_size(); + if (Key *key = dst_mesh->key) { LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { - if (kb->data) { - MEM_freeN(kb->data); - } - kb->data = MEM_callocN(sizeof(float[3]) * vert_ranges.total_size(), "join_shapekey"); - kb->totelem = vert_ranges.total_size(); - } - } - else if (haskey) { - /* add a new key-block and add to the mesh */ - key = dst_mesh->key = BKE_key_add(bmain, (ID *)dst_mesh); - key->type = KEY_RELATIVE; - } - - /* Update face_set_id_offset with the face set data in the active object first. This way the Face - * Sets IDs in the active object are not the ones that are modified. */ - int face_set_id_offset = 0; - mesh_join_offset_face_sets_ID(dst_mesh, &face_set_id_offset); - - /* Copy materials, vertex-groups, face sets & face-maps across objects. */ - for (const Object *ob_iter : objects_to_join) { - if (ob_iter == active_object) { - continue; - } - Mesh *mesh = static_cast(ob_iter->data); - - /* Join this object's vertex groups to the base one's */ - LISTBASE_FOREACH (bDeformGroup *, dg, &mesh->vertex_group_names) { - /* See if this group exists in the object (if it doesn't, add it to the end) */ - if (!BKE_object_defgroup_find_name(active_object, dg->name)) { - bDeformGroup *odg = MEM_mallocN(__func__); - memcpy(odg, dg, sizeof(bDeformGroup)); - BLI_addtail(&dst_mesh->vertex_group_names, odg); - } - } - if (!BLI_listbase_is_empty(&dst_mesh->vertex_group_names) && - mesh->vertex_group_active_index == 0) - { - mesh->vertex_group_active_index = 1; - } - - mesh_join_offset_face_sets_ID(mesh, &face_set_id_offset); - - if (mesh->verts_num) { - /* If this mesh has shape-keys, - * check if destination mesh already has matching entries too. */ - if (mesh->key && key) { - /* for remapping KeyBlock.relative */ - int *index_map = MEM_malloc_arrayN(mesh->key->totkey, __func__); - KeyBlock **kb_map = MEM_malloc_arrayN(mesh->key->totkey, __func__); - - int i; - LISTBASE_FOREACH_INDEX (KeyBlock *, kb, &mesh->key->block, i) { - BLI_assert(i < mesh->key->totkey); - - KeyBlock *kbn = BKE_keyblock_find_name(key, kb->name); - /* if key doesn't exist in destination mesh, add it */ - if (kbn) { - index_map[i] = BLI_findindex(&key->block, kbn); - } - else { - index_map[i] = key->totkey; - - kbn = BKE_keyblock_add(key, kb->name); - - BKE_keyblock_copy_settings(kbn, kb); - - /* adjust settings to fit (allocate a new data-array) */ - kbn->data = MEM_callocN(sizeof(float[3]) * vert_ranges.total_size(), - "joined_shapekey"); - kbn->totelem = vert_ranges.total_size(); - } - - kb_map[i] = kbn; - } - - /* remap relative index values */ - LISTBASE_FOREACH_INDEX (KeyBlock *, kb, &mesh->key->block, i) { - /* sanity check, should always be true */ - if (LIKELY(kb->relative < mesh->key->totkey)) { - kb_map[i]->relative = index_map[kb->relative]; - } - } - - MEM_freeN(index_map); - MEM_freeN(kb_map); - } + kb->data = MEM_reallocN(kb->data, sizeof(float3) * dst_mesh->verts_num); + kb->totelem = dst_mesh->verts_num; } } - /* setup new data for destination mesh */ - CustomData vert_data; - CustomData edge_data; - CustomData face_data; - CustomData corner_data; - CustomData_reset(&vert_data); - CustomData_reset(&edge_data); - CustomData_reset(&corner_data); - CustomData_reset(&face_data); - - MutableSpan vert_positions( - (float3 *)CustomData_add_layer_named( - &vert_data, CD_PROP_FLOAT3, CD_SET_DEFAULT, vert_ranges.total_size(), "position"), - vert_ranges.total_size()); - MutableSpan edge( - (int2 *)CustomData_add_layer_named( - &edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, edge_ranges.total_size(), ".edge_verts"), - edge_ranges.total_size()); - MutableSpan corner_verts( - (int *)CustomData_add_layer_named( - &corner_data, CD_PROP_INT32, CD_CONSTRUCT, corner_ranges.total_size(), ".corner_vert"), - corner_ranges.total_size()); - MutableSpan corner_edges( - (int *)CustomData_add_layer_named( - &corner_data, CD_PROP_INT32, CD_CONSTRUCT, corner_ranges.total_size(), ".corner_edge"), - corner_ranges.total_size()); - int *face_offsets = MEM_malloc_arrayN(face_ranges.total_size() + 1, __func__); - face_offsets[face_ranges.total_size()] = corner_ranges.total_size(); + BKE_mesh_runtime_clear_geometry(dst_mesh); /* Inverse transform for all selected meshes in this object, * See #object_join_exec for detailed comment on why the safe version is used. */ float4x4 world_to_active_object; invert_m4_m4_safe_ortho(world_to_active_object.ptr(), active_object->object_to_world().ptr()); - for (const int i : objects_to_join.index_range()) { - Object *ob_iter = objects_to_join[i]; - join_mesh_single(depsgraph, - bmain, - scene, - active_object, - ob_iter, - world_to_active_object, - vert_positions, - edge, - corner_verts, - corner_edges, - {face_offsets, face_ranges.total_size()}, - &vert_data, - &edge_data, - &face_data, - &corner_data, - vert_ranges.total_size(), - edge_ranges.total_size(), - face_ranges.total_size(), - corner_ranges.total_size(), - key, - nkey, - materials, - vert_ranges[i], - edge_ranges[i], - face_ranges[i], - corner_ranges[i]); + join_positions_and_shape_keys(objects_to_join, vert_ranges, world_to_active_object, *dst_mesh); - /* free base, now that data is merged */ - if (ob_iter != active_object) { - object::base_free_and_unlink(bmain, scene, ob_iter); + MutableSpan dst_edges = dst_mesh->edges_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = edge_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_edges = src_mesh.edges(); + for (const int edge : dst_range.index_range()) { + dst_edges[dst_range[edge]] = src_edges[edge] + int(vert_ranges[i].start()); } } - BKE_mesh_clear_geometry(dst_mesh); - - if (face_offsets) { - dst_mesh->face_offset_indices = face_offsets; - dst_mesh->runtime->face_offsets_sharing_info = implicit_sharing::info_for_mem_free( - face_offsets); + MutableSpan dst_corner_verts = dst_mesh->corner_verts_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = corner_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_corner_verts = src_mesh.corner_verts(); + for (const int corner : dst_range.index_range()) { + dst_corner_verts[dst_range[corner]] = src_corner_verts[corner] + int(vert_ranges[i].start()); + } } - dst_mesh->verts_num = vert_ranges.total_size(); - dst_mesh->edges_num = edge_ranges.total_size(); - dst_mesh->faces_num = face_ranges.total_size(); - dst_mesh->corners_num = corner_ranges.total_size(); + MutableSpan dst_corner_edges = dst_mesh->corner_edges_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = corner_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_corner_edges = src_mesh.corner_edges(); + for (const int corner : dst_range.index_range()) { + dst_corner_edges[dst_range[corner]] = src_corner_edges[corner] + int(edge_ranges[i].start()); + } + } - dst_mesh->vert_data = vert_data; - dst_mesh->edge_data = edge_data; - dst_mesh->corner_data = corner_data; - dst_mesh->face_data = face_data; + MutableSpan dst_face_offsets = dst_mesh->face_offsets_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = face_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_face_offsets = src_mesh.face_offsets(); + for (const int face : dst_range.index_range()) { + dst_face_offsets[dst_range[face]] = src_face_offsets[face] + corner_ranges[i].start(); + } + } + dst_face_offsets.last() = dst_mesh->corners_num; + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Key *src_key = src_mesh.key; + if (!src_key) { + continue; + } + } + + for (const int i : objects_to_join.index_range().drop_front(1)) { + Object &src_object = *objects_to_join[i]; + multiresModifier_prepare_join(depsgraph, scene, &src_object, active_object); + if (MultiresModifierData *mmd = get_multires_modifier(scene, &src_object, true)) { + object::iter_other( + bmain, &src_object, true, object::multires_update_totlevels, &mmd->totlvl); + } + } + + join_face_sets(objects_to_join, face_ranges, *dst_mesh); + + VectorSet materials = join_materials(objects_to_join, face_ranges, *dst_mesh); + + VectorSet vertex_group_names = join_vertex_groups( + objects_to_join, vert_ranges, *dst_mesh); + + join_generic_attributes(objects_to_join, + vertex_group_names, + vert_ranges, + edge_ranges, + face_ranges, + corner_ranges, + *dst_mesh); + + for (Object *object : objects_to_join.as_span().drop_front(1)) { + object::base_free_and_unlink(bmain, scene, object); + } /* old material array */ for (const int a : IndexRange(active_object->totcol)) { @@ -602,8 +603,13 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) const int totcol = materials.size(); if (totcol) { - dst_mesh->mat = MEM_calloc_arrayN(totcol, __func__); - std::copy_n(materials.data(), totcol, dst_mesh->mat); + VectorData data = materials.extract_vector().release(); + dst_mesh->mat = data.data; + for (const int i : IndexRange(totcol)) { + if (Material *ma = dst_mesh->mat[i]) { + id_us_plus((ID *)ma); + } + } active_object->mat = MEM_calloc_arrayN(totcol, __func__); active_object->matbits = MEM_calloc_arrayN(totcol, __func__); } @@ -613,12 +619,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) /* other mesh users */ BKE_objects_materials_sync_length_all(bmain, (ID *)dst_mesh); - /* Free temporary copy of destination shape-keys (if applicable). */ - if (nkey) { - /* We can assume nobody is using that ID currently. */ - BKE_id_free_ex(bmain, nkey, LIB_ID_FREE_NO_UI_USER, false); - } - /* ensure newly inserted keys are time sorted */ if (key && (key->type != KEY_RELATIVE)) { BKE_key_sort(key);