diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index d35837e5f47..4268894a529 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -44,6 +44,7 @@ set(SRC editmesh_undo.cc editmesh_utils.cc mesh_data.cc + mesh_join.cc mesh_mirror.cc mesh_ops.cc meshtools.cc diff --git a/source/blender/editors/mesh/mesh_join.cc b/source/blender/editors/mesh/mesh_join.cc new file mode 100644 index 00000000000..92222d3de86 --- /dev/null +++ b/source/blender/editors/mesh/mesh_join.cc @@ -0,0 +1,701 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edmesh + */ + +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_math_matrix.h" +#include "BLI_math_vector.h" +#include "BLI_vector.hh" +#include "BLI_virtual_array.hh" + +#include "DNA_key_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_attribute.hh" +#include "BKE_context.hh" +#include "BKE_customdata.hh" +#include "BKE_deform.hh" +#include "BKE_key.hh" +#include "BKE_lib_id.hh" +#include "BKE_material.hh" +#include "BKE_mesh.hh" +#include "BKE_mesh_runtime.hh" +#include "BKE_multires.hh" +#include "BKE_object.hh" +#include "BKE_object_deform.h" +#include "BKE_report.hh" + +#include "DEG_depsgraph.hh" +#include "DEG_depsgraph_build.hh" +#include "DEG_depsgraph_query.hh" + +#include "ED_mesh.hh" +#include "ED_object.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "mesh_intern.hh" + +using blender::float3; +using blender::int2; +using blender::MutableSpan; +using blender::Span; + +/* 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 float imat[4][4], + float3 **vert_positions_pp, + blender::int2 **medge_pp, + int **corner_verts_pp, + int **corner_edges_pp, + int *all_face_offsets, + CustomData *vert_data, + CustomData *edge_data, + CustomData *ldata, + CustomData *face_data, + int totvert, + int totedge, + int totloop, + int faces_num, + Key *key, + Key *nkey, + blender::Vector &matar, + int *vertofs, + int *edgeofs, + int *loopofs, + int *polyofs) +{ + int a; + + Mesh *mesh = static_cast(ob_src->data); + float3 *vert_positions = *vert_positions_pp; + blender::int2 *edge = *medge_pp; + int *corner_verts = *corner_verts_pp; + int *corner_edges = *corner_edges_pp; + + if (mesh->verts_num) { + /* standard data */ + CustomData_merge_layout( + &mesh->vert_data, vert_data, CD_MASK_MESH.vmask, CD_SET_DEFAULT, totvert); + CustomData_copy_data_named(&mesh->vert_data, vert_data, 0, *vertofs, mesh->verts_num); + + /* vertex groups */ + MDeformVert *dvert = (MDeformVert *)CustomData_get_for_write( + vert_data, *vertofs, CD_MDEFORMVERT, totvert); + const MDeformVert *dvert_src = (const MDeformVert *)CustomData_get_layer(&mesh->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, mesh->verts_num, vgroup_index_map, vgroup_index_map_len); + if (vgroup_index_map != nullptr) { + MEM_freeN(vgroup_index_map); + } + } + + /* if this is the object we're merging into, no need to do anything */ + if (ob_src != ob_dst) { + float cmat[4][4]; + + /* Watch this: switch matrix multiplication order really goes wrong. */ + mul_m4_m4m4(cmat, imat, ob_src->object_to_world().ptr()); + + /* transform vertex coordinates into new space */ + for (a = 0; a < mesh->verts_num; a++) { + mul_m4_v3(cmat, vert_positions[a]); + } + + /* 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) { + /* get pointer to where to write data for this mesh in shape-key's data array */ + float(*cos)[3] = ((float(*)[3])kb->data) + *vertofs; + + /* Check if this mesh has such a shape-key. */ + KeyBlock *okb = mesh->key ? BKE_keyblock_find_name(mesh->key, kb->name) : nullptr; + if (okb) { + /* copy this mesh's shape-key to the destination shape-key + * (need to transform first) */ + float(*ocos)[3] = static_cast(okb->data); + for (a = 0; a < mesh->verts_num; a++, cos++, ocos++) { + copy_v3_v3(*cos, *ocos); + mul_m4_v3(cmat, *cos); + } + } + else { + /* Copy this mesh's vertex coordinates to the destination shape-key. */ + for (a = 0; a < mesh->verts_num; a++, cos++) { + copy_v3_v3(*cos, vert_positions[a]); + } + } + } + } + } + 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) { + /* get pointer to where to write data for this mesh in shape-key's data array */ + float(*cos)[3] = ((float(*)[3])kb->data) + *vertofs; + + /* Check if this was one of the original shape-keys. */ + KeyBlock *okb = nkey ? BKE_keyblock_find_name(nkey, kb->name) : nullptr; + if (okb) { + /* copy this mesh's shape-key to the destination shape-key */ + float(*ocos)[3] = static_cast(okb->data); + for (a = 0; a < mesh->verts_num; a++, cos++, ocos++) { + copy_v3_v3(*cos, *ocos); + } + } + else { + /* Copy base-coordinates to the destination shape-key. */ + for (a = 0; a < mesh->verts_num; a++, cos++) { + copy_v3_v3(*cos, vert_positions[a]); + } + } + } + } + } + } + + if (mesh->edges_num) { + CustomData_merge_layout( + &mesh->edge_data, edge_data, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge); + CustomData_copy_data_named(&mesh->edge_data, edge_data, 0, *edgeofs, mesh->edges_num); + + for (a = 0; a < mesh->edges_num; a++, edge++) { + (*edge) += *vertofs; + } + } + + if (mesh->corners_num) { + if (ob_src != ob_dst) { + MultiresModifierData *mmd; + + multiresModifier_prepare_join(depsgraph, scene, ob_src, ob_dst); + + if ((mmd = get_multires_modifier(scene, ob_src, true))) { + blender::ed::object::iter_other( + bmain, ob_src, true, blender::ed::object::multires_update_totlevels, &mmd->totlvl); + } + } + + CustomData_merge_layout( + &mesh->corner_data, ldata, CD_MASK_MESH.lmask, CD_SET_DEFAULT, totloop); + CustomData_copy_data_named(&mesh->corner_data, ldata, 0, *loopofs, mesh->corners_num); + + for (a = 0; a < mesh->corners_num; a++) { + corner_verts[a] += *vertofs; + corner_edges[a] += *edgeofs; + } + } + + /* 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); + blender::Vector matmap(totcol); + if (mesh->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 < matar.size(); b++) { + if (ma == matar[b]) { + matmap[a - 1] = b; + break; + } + } + + if (b == matar.size()) { + if (matar.size() == MAXMAT) { + /* Reached max limit of materials, use first slot. */ + matmap[a - 1] = 0; + } + else { + /* Add new slot. */ + matmap[a - 1] = matar.size(); + matar.append(ma); + if (ma) { + id_us_plus(&ma->id); + } + } + } + } + + CustomData_merge_layout( + &mesh->face_data, face_data, CD_MASK_MESH.pmask, CD_SET_DEFAULT, faces_num); + CustomData_copy_data_named(&mesh->face_data, face_data, 0, *polyofs, mesh->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 && matar.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->faces_num; a++) { + /* Clamp invalid slots, matching #BKE_object_material_get_p. */ + const int mat_index = std::clamp(material_indices[a + *polyofs], 0, totcol - 1); + material_indices[a + *polyofs] = matmap[mat_index]; + } + } + + const Span src_face_offsets = mesh->face_offsets(); + int *face_offsets = all_face_offsets + *polyofs; + for (const int i : blender::IndexRange(mesh->faces_num)) { + face_offsets[i] = src_face_offsets[i] + *loopofs; + } + } + + /* these are used for relinking (cannot be set earlier, or else reattaching goes wrong) */ + *vertofs += mesh->verts_num; + *vert_positions_pp += mesh->verts_num; + *edgeofs += mesh->edges_num; + *medge_pp += mesh->edges_num; + *loopofs += mesh->corners_num; + *corner_verts_pp += mesh->corners_num; + *corner_edges_pp += mesh->corners_num; + *polyofs += mesh->faces_num; +} + +/* 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) +{ + using namespace blender; + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + bke::SpanAttributeWriter face_sets = attributes.lookup_for_write_span( + ".sculpt_face_set"); + if (!face_sets) { + 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; + } + max_face_set = max_ii(max_face_set, face_sets.span[i]); + } + *face_set_offset = max_face_set; + face_sets.finish(); +} + +wmOperatorStatus ED_mesh_join_objects_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + Material *ma; + Mesh *mesh; + blender::int2 *edge = nullptr; + Key *key, *nkey = nullptr; + float imat[4][4]; + int a, totedge = 0, totvert = 0; + int totloop = 0, faces_num = 0, vertofs; + int i, haskey = 0, edgeofs, loopofs, polyofs; + bool ok = false, join_parent = false; + CustomData vert_data, edge_data, ldata, face_data; + + if (ob->mode & OB_MODE_EDIT) { + BKE_report(op->reports, RPT_WARNING, "Cannot join while in edit mode"); + return OPERATOR_CANCELLED; + } + + /* ob is the object we are adding geometry to */ + if (!ob || ob->type != OB_MESH) { + BKE_report(op->reports, RPT_WARNING, "Active object is not a mesh"); + return OPERATOR_CANCELLED; + } + + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + /* count & check */ + CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { + if (ob_iter->type == OB_MESH) { + mesh = static_cast(ob_iter->data); + + totvert += mesh->verts_num; + totedge += mesh->edges_num; + totloop += mesh->corners_num; + faces_num += mesh->faces_num; + + if (ob_iter == ob) { + ok = true; + } + + if ((ob->parent != nullptr) && (ob_iter == ob->parent)) { + join_parent = true; + } + + /* Check for shape-keys. */ + if (mesh->key) { + haskey++; + } + } + } + CTX_DATA_END; + + /* Apply parent transform if the active object's parent was joined to it. + * NOTE: This doesn't apply recursive parenting. */ + if (join_parent) { + ob->parent = nullptr; + BKE_object_apply_mat4_ex(ob, ob->object_to_world().ptr(), ob->parent, ob->parentinv, false); + } + + /* that way the active object is always selected */ + if (ok == false) { + BKE_report(op->reports, RPT_WARNING, "Active object is not a selected mesh"); + return OPERATOR_CANCELLED; + } + + /* Only join meshes if there are verts to join, + * there aren't too many, and we only had one mesh selected. */ + mesh = (Mesh *)ob->data; + key = mesh->key; + + if (ELEM(totvert, 0, mesh->verts_num)) { + BKE_report(op->reports, RPT_WARNING, "No mesh data to join"); + return OPERATOR_CANCELLED; + } + + if (totvert > MESH_MAX_VERTS) { + BKE_reportf(op->reports, + RPT_WARNING, + "Joining results in %d vertices, limit is %ld", + totvert, + MESH_MAX_VERTS); + return OPERATOR_CANCELLED; + } + + /* Active object materials in new main array, is nicer start! */ + blender::Vector matar; + for (a = 0; a < ob->totcol; a++) { + matar.append(BKE_object_material_get(ob, a + 1)); + id_us_plus((ID *)matar[a]); + /* increase id->us : will be lowered later */ + } + + /* - 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. + */ + 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 */ + LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { + if (kb->data) { + MEM_freeN(kb->data); + } + kb->data = MEM_callocN(sizeof(float[3]) * totvert, "join_shapekey"); + kb->totelem = totvert; + } + } + else if (haskey) { + /* add a new key-block and add to the mesh */ + key = mesh->key = BKE_key_add(bmain, (ID *)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. */ + Mesh *mesh_active = BKE_mesh_from_object(ob); + int face_set_id_offset = 0; + mesh_join_offset_face_sets_ID(mesh_active, &face_set_id_offset); + + /* Copy materials, vertex-groups, face sets & face-maps across objects. */ + CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { + /* only act if a mesh, and not the one we're joining to */ + if ((ob != ob_iter) && (ob_iter->type == OB_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(ob, dg->name)) { + bDeformGroup *odg = MEM_mallocN(__func__); + memcpy(odg, dg, sizeof(bDeformGroup)); + BLI_addtail(&mesh_active->vertex_group_names, odg); + } + } + if (!BLI_listbase_is_empty(&mesh_active->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__); + + 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]) * totvert, "joined_shapekey"); + kbn->totelem = totvert; + } + + 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); + } + } + } + } + CTX_DATA_END; + + /* setup new data for destination mesh */ + CustomData_reset(&vert_data); + CustomData_reset(&edge_data); + CustomData_reset(&ldata); + CustomData_reset(&face_data); + + float3 *vert_positions = (float3 *)CustomData_add_layer_named( + &vert_data, CD_PROP_FLOAT3, CD_SET_DEFAULT, totvert, "position"); + edge = (int2 *)CustomData_add_layer_named( + &edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, totedge, ".edge_verts"); + int *corner_verts = (int *)CustomData_add_layer_named( + &ldata, CD_PROP_INT32, CD_CONSTRUCT, totloop, ".corner_vert"); + int *corner_edges = (int *)CustomData_add_layer_named( + &ldata, CD_PROP_INT32, CD_CONSTRUCT, totloop, ".corner_edge"); + int *face_offsets = MEM_malloc_arrayN(faces_num + 1, __func__); + face_offsets[faces_num] = totloop; + + vertofs = 0; + edgeofs = 0; + loopofs = 0; + polyofs = 0; + + /* Inverse transform for all selected meshes in this object, + * See #object_join_exec for detailed comment on why the safe version is used. */ + invert_m4_m4_safe_ortho(imat, ob->object_to_world().ptr()); + + /* Add back active mesh first. + * This allows to keep things similar as they were, as much as possible + * (i.e. data from active mesh will remain first ones in new result of the merge, + * in same order for CD layers, etc). See also #50084. + */ + join_mesh_single(depsgraph, + bmain, + scene, + ob, + ob, + imat, + &vert_positions, + &edge, + &corner_verts, + &corner_edges, + face_offsets, + &vert_data, + &edge_data, + &ldata, + &face_data, + totvert, + totedge, + totloop, + faces_num, + key, + nkey, + matar, + &vertofs, + &edgeofs, + &loopofs, + &polyofs); + + CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { + if (ob_iter == ob) { + continue; + } + /* only join if this is a mesh */ + if (ob_iter->type == OB_MESH) { + join_mesh_single(depsgraph, + bmain, + scene, + ob, + ob_iter, + imat, + &vert_positions, + &edge, + &corner_verts, + &corner_edges, + face_offsets, + &vert_data, + &edge_data, + &ldata, + &face_data, + totvert, + totedge, + totloop, + faces_num, + key, + nkey, + matar, + &vertofs, + &edgeofs, + &loopofs, + &polyofs); + + /* free base, now that data is merged */ + if (ob_iter != ob) { + blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter); + } + } + } + CTX_DATA_END; + + /* return to mesh we're merging to */ + mesh = static_cast(ob->data); + + BKE_mesh_clear_geometry(mesh); + + if (faces_num) { + mesh->face_offset_indices = face_offsets; + mesh->runtime->face_offsets_sharing_info = blender::implicit_sharing::info_for_mem_free( + face_offsets); + } + + mesh->verts_num = totvert; + mesh->edges_num = totedge; + mesh->corners_num = totloop; + mesh->faces_num = faces_num; + + mesh->vert_data = vert_data; + mesh->edge_data = edge_data; + mesh->corner_data = ldata; + mesh->face_data = face_data; + + /* old material array */ + for (a = 1; a <= ob->totcol; a++) { + ma = ob->mat[a - 1]; + if (ma) { + id_us_min(&ma->id); + } + } + for (a = 1; a <= mesh->totcol; a++) { + ma = mesh->mat[a - 1]; + if (ma) { + id_us_min(&ma->id); + } + } + MEM_SAFE_FREE(ob->mat); + MEM_SAFE_FREE(ob->matbits); + MEM_SAFE_FREE(mesh->mat); + + /* If the object had no slots, don't add an empty one. */ + if (ob->totcol == 0 && matar.size() == 1 && matar[0] == nullptr) { + matar.clear(); + } + + const int totcol = matar.size(); + if (totcol) { + mesh->mat = MEM_calloc_arrayN(totcol, __func__); + std::copy_n(matar.data(), totcol, mesh->mat); + ob->mat = MEM_calloc_arrayN(totcol, __func__); + ob->matbits = MEM_calloc_arrayN(totcol, __func__); + } + + ob->totcol = mesh->totcol = totcol; + + /* other mesh users */ + BKE_objects_materials_sync_length_all(bmain, (ID *)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); + } + + /* Due to dependency cycle some other object might access old derived data. */ + BKE_object_free_derived_caches(ob); + + DEG_relations_tag_update(bmain); /* removed objects, need to rebuild dag */ + + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); + WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); + + return OPERATOR_FINISHED; +} diff --git a/source/blender/editors/mesh/meshtools.cc b/source/blender/editors/mesh/meshtools.cc index e5bdcc51553..7809bc96b2d 100644 --- a/source/blender/editors/mesh/meshtools.cc +++ b/source/blender/editors/mesh/meshtools.cc @@ -14,16 +14,13 @@ #include "MEM_guardedalloc.h" #include "BLI_listbase.h" -#include "BLI_math_matrix.h" #include "BLI_math_vector.h" #include "BLI_vector.hh" #include "BLI_virtual_array.hh" #include "DNA_key_types.h" -#include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "DNA_modifier_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" @@ -32,7 +29,6 @@ #include "BKE_attribute.hh" #include "BKE_context.hh" #include "BKE_customdata.hh" -#include "BKE_deform.hh" #include "BKE_editmesh.hh" #include "BKE_key.hh" #include "BKE_layer.hh" @@ -43,7 +39,6 @@ #include "BKE_mesh_runtime.hh" #include "BKE_multires.hh" #include "BKE_object.hh" -#include "BKE_object_deform.h" #include "BKE_paint.hh" #include "BKE_paint_bvh.hh" #include "BKE_report.hh" @@ -69,654 +64,6 @@ using blender::int2; using blender::MutableSpan; using blender::Span; -/* * ********************** no editmode!!! *********** */ - -/*********************** JOIN ***************************/ - -/* 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 float imat[4][4], - float3 **vert_positions_pp, - blender::int2 **medge_pp, - int **corner_verts_pp, - int **corner_edges_pp, - int *all_face_offsets, - CustomData *vert_data, - CustomData *edge_data, - CustomData *ldata, - CustomData *face_data, - int totvert, - int totedge, - int totloop, - int faces_num, - Key *key, - Key *nkey, - blender::Vector &matar, - int *vertofs, - int *edgeofs, - int *loopofs, - int *polyofs) -{ - int a; - - Mesh *mesh = static_cast(ob_src->data); - float3 *vert_positions = *vert_positions_pp; - blender::int2 *edge = *medge_pp; - int *corner_verts = *corner_verts_pp; - int *corner_edges = *corner_edges_pp; - - if (mesh->verts_num) { - /* standard data */ - CustomData_merge_layout( - &mesh->vert_data, vert_data, CD_MASK_MESH.vmask, CD_SET_DEFAULT, totvert); - CustomData_copy_data_named(&mesh->vert_data, vert_data, 0, *vertofs, mesh->verts_num); - - /* vertex groups */ - MDeformVert *dvert = (MDeformVert *)CustomData_get_for_write( - vert_data, *vertofs, CD_MDEFORMVERT, totvert); - const MDeformVert *dvert_src = (const MDeformVert *)CustomData_get_layer(&mesh->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, mesh->verts_num, vgroup_index_map, vgroup_index_map_len); - if (vgroup_index_map != nullptr) { - MEM_freeN(vgroup_index_map); - } - } - - /* if this is the object we're merging into, no need to do anything */ - if (ob_src != ob_dst) { - float cmat[4][4]; - - /* Watch this: switch matrix multiplication order really goes wrong. */ - mul_m4_m4m4(cmat, imat, ob_src->object_to_world().ptr()); - - /* transform vertex coordinates into new space */ - for (a = 0; a < mesh->verts_num; a++) { - mul_m4_v3(cmat, vert_positions[a]); - } - - /* 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) { - /* get pointer to where to write data for this mesh in shape-key's data array */ - float(*cos)[3] = ((float(*)[3])kb->data) + *vertofs; - - /* Check if this mesh has such a shape-key. */ - KeyBlock *okb = mesh->key ? BKE_keyblock_find_name(mesh->key, kb->name) : nullptr; - if (okb) { - /* copy this mesh's shape-key to the destination shape-key - * (need to transform first) */ - float(*ocos)[3] = static_cast(okb->data); - for (a = 0; a < mesh->verts_num; a++, cos++, ocos++) { - copy_v3_v3(*cos, *ocos); - mul_m4_v3(cmat, *cos); - } - } - else { - /* Copy this mesh's vertex coordinates to the destination shape-key. */ - for (a = 0; a < mesh->verts_num; a++, cos++) { - copy_v3_v3(*cos, vert_positions[a]); - } - } - } - } - } - 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) { - /* get pointer to where to write data for this mesh in shape-key's data array */ - float(*cos)[3] = ((float(*)[3])kb->data) + *vertofs; - - /* Check if this was one of the original shape-keys. */ - KeyBlock *okb = nkey ? BKE_keyblock_find_name(nkey, kb->name) : nullptr; - if (okb) { - /* copy this mesh's shape-key to the destination shape-key */ - float(*ocos)[3] = static_cast(okb->data); - for (a = 0; a < mesh->verts_num; a++, cos++, ocos++) { - copy_v3_v3(*cos, *ocos); - } - } - else { - /* Copy base-coordinates to the destination shape-key. */ - for (a = 0; a < mesh->verts_num; a++, cos++) { - copy_v3_v3(*cos, vert_positions[a]); - } - } - } - } - } - } - - if (mesh->edges_num) { - CustomData_merge_layout( - &mesh->edge_data, edge_data, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge); - CustomData_copy_data_named(&mesh->edge_data, edge_data, 0, *edgeofs, mesh->edges_num); - - for (a = 0; a < mesh->edges_num; a++, edge++) { - (*edge) += *vertofs; - } - } - - if (mesh->corners_num) { - if (ob_src != ob_dst) { - MultiresModifierData *mmd; - - multiresModifier_prepare_join(depsgraph, scene, ob_src, ob_dst); - - if ((mmd = get_multires_modifier(scene, ob_src, true))) { - blender::ed::object::iter_other( - bmain, ob_src, true, blender::ed::object::multires_update_totlevels, &mmd->totlvl); - } - } - - CustomData_merge_layout( - &mesh->corner_data, ldata, CD_MASK_MESH.lmask, CD_SET_DEFAULT, totloop); - CustomData_copy_data_named(&mesh->corner_data, ldata, 0, *loopofs, mesh->corners_num); - - for (a = 0; a < mesh->corners_num; a++) { - corner_verts[a] += *vertofs; - corner_edges[a] += *edgeofs; - } - } - - /* 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); - blender::Vector matmap(totcol); - if (mesh->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 < matar.size(); b++) { - if (ma == matar[b]) { - matmap[a - 1] = b; - break; - } - } - - if (b == matar.size()) { - if (matar.size() == MAXMAT) { - /* Reached max limit of materials, use first slot. */ - matmap[a - 1] = 0; - } - else { - /* Add new slot. */ - matmap[a - 1] = matar.size(); - matar.append(ma); - if (ma) { - id_us_plus(&ma->id); - } - } - } - } - - CustomData_merge_layout( - &mesh->face_data, face_data, CD_MASK_MESH.pmask, CD_SET_DEFAULT, faces_num); - CustomData_copy_data_named(&mesh->face_data, face_data, 0, *polyofs, mesh->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 && matar.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->faces_num; a++) { - /* Clamp invalid slots, matching #BKE_object_material_get_p. */ - const int mat_index = std::clamp(material_indices[a + *polyofs], 0, totcol - 1); - material_indices[a + *polyofs] = matmap[mat_index]; - } - } - - const Span src_face_offsets = mesh->face_offsets(); - int *face_offsets = all_face_offsets + *polyofs; - for (const int i : blender::IndexRange(mesh->faces_num)) { - face_offsets[i] = src_face_offsets[i] + *loopofs; - } - } - - /* these are used for relinking (cannot be set earlier, or else reattaching goes wrong) */ - *vertofs += mesh->verts_num; - *vert_positions_pp += mesh->verts_num; - *edgeofs += mesh->edges_num; - *medge_pp += mesh->edges_num; - *loopofs += mesh->corners_num; - *corner_verts_pp += mesh->corners_num; - *corner_edges_pp += mesh->corners_num; - *polyofs += mesh->faces_num; -} - -/* 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) -{ - using namespace blender; - bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); - bke::SpanAttributeWriter face_sets = attributes.lookup_for_write_span( - ".sculpt_face_set"); - if (!face_sets) { - 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; - } - max_face_set = max_ii(max_face_set, face_sets.span[i]); - } - *face_set_offset = max_face_set; - face_sets.finish(); -} - -wmOperatorStatus ED_mesh_join_objects_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - Object *ob = CTX_data_active_object(C); - Material *ma; - Mesh *mesh; - blender::int2 *edge = nullptr; - Key *key, *nkey = nullptr; - float imat[4][4]; - int a, totedge = 0, totvert = 0; - int totloop = 0, faces_num = 0, vertofs; - int i, haskey = 0, edgeofs, loopofs, polyofs; - bool ok = false, join_parent = false; - CustomData vert_data, edge_data, ldata, face_data; - - if (ob->mode & OB_MODE_EDIT) { - BKE_report(op->reports, RPT_WARNING, "Cannot join while in edit mode"); - return OPERATOR_CANCELLED; - } - - /* ob is the object we are adding geometry to */ - if (!ob || ob->type != OB_MESH) { - BKE_report(op->reports, RPT_WARNING, "Active object is not a mesh"); - return OPERATOR_CANCELLED; - } - - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - - /* count & check */ - CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { - if (ob_iter->type == OB_MESH) { - mesh = static_cast(ob_iter->data); - - totvert += mesh->verts_num; - totedge += mesh->edges_num; - totloop += mesh->corners_num; - faces_num += mesh->faces_num; - - if (ob_iter == ob) { - ok = true; - } - - if ((ob->parent != nullptr) && (ob_iter == ob->parent)) { - join_parent = true; - } - - /* Check for shape-keys. */ - if (mesh->key) { - haskey++; - } - } - } - CTX_DATA_END; - - /* Apply parent transform if the active object's parent was joined to it. - * NOTE: This doesn't apply recursive parenting. */ - if (join_parent) { - ob->parent = nullptr; - BKE_object_apply_mat4_ex(ob, ob->object_to_world().ptr(), ob->parent, ob->parentinv, false); - } - - /* that way the active object is always selected */ - if (ok == false) { - BKE_report(op->reports, RPT_WARNING, "Active object is not a selected mesh"); - return OPERATOR_CANCELLED; - } - - /* Only join meshes if there are verts to join, - * there aren't too many, and we only had one mesh selected. */ - mesh = (Mesh *)ob->data; - key = mesh->key; - - if (ELEM(totvert, 0, mesh->verts_num)) { - BKE_report(op->reports, RPT_WARNING, "No mesh data to join"); - return OPERATOR_CANCELLED; - } - - if (totvert > MESH_MAX_VERTS) { - BKE_reportf(op->reports, - RPT_WARNING, - "Joining results in %d vertices, limit is %ld", - totvert, - MESH_MAX_VERTS); - return OPERATOR_CANCELLED; - } - - /* Active object materials in new main array, is nicer start! */ - blender::Vector matar; - for (a = 0; a < ob->totcol; a++) { - matar.append(BKE_object_material_get(ob, a + 1)); - id_us_plus((ID *)matar[a]); - /* increase id->us : will be lowered later */ - } - - /* - 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. - */ - 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 */ - LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { - if (kb->data) { - MEM_freeN(kb->data); - } - kb->data = MEM_callocN(sizeof(float[3]) * totvert, "join_shapekey"); - kb->totelem = totvert; - } - } - else if (haskey) { - /* add a new key-block and add to the mesh */ - key = mesh->key = BKE_key_add(bmain, (ID *)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. */ - Mesh *mesh_active = BKE_mesh_from_object(ob); - int face_set_id_offset = 0; - mesh_join_offset_face_sets_ID(mesh_active, &face_set_id_offset); - - /* Copy materials, vertex-groups, face sets & face-maps across objects. */ - CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { - /* only act if a mesh, and not the one we're joining to */ - if ((ob != ob_iter) && (ob_iter->type == OB_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(ob, dg->name)) { - bDeformGroup *odg = MEM_mallocN(__func__); - memcpy(odg, dg, sizeof(bDeformGroup)); - BLI_addtail(&mesh_active->vertex_group_names, odg); - } - } - if (!BLI_listbase_is_empty(&mesh_active->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__); - - 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]) * totvert, "joined_shapekey"); - kbn->totelem = totvert; - } - - 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); - } - } - } - } - CTX_DATA_END; - - /* setup new data for destination mesh */ - CustomData_reset(&vert_data); - CustomData_reset(&edge_data); - CustomData_reset(&ldata); - CustomData_reset(&face_data); - - float3 *vert_positions = (float3 *)CustomData_add_layer_named( - &vert_data, CD_PROP_FLOAT3, CD_SET_DEFAULT, totvert, "position"); - edge = (int2 *)CustomData_add_layer_named( - &edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, totedge, ".edge_verts"); - int *corner_verts = (int *)CustomData_add_layer_named( - &ldata, CD_PROP_INT32, CD_CONSTRUCT, totloop, ".corner_vert"); - int *corner_edges = (int *)CustomData_add_layer_named( - &ldata, CD_PROP_INT32, CD_CONSTRUCT, totloop, ".corner_edge"); - int *face_offsets = MEM_malloc_arrayN(faces_num + 1, __func__); - face_offsets[faces_num] = totloop; - - vertofs = 0; - edgeofs = 0; - loopofs = 0; - polyofs = 0; - - /* Inverse transform for all selected meshes in this object, - * See #object_join_exec for detailed comment on why the safe version is used. */ - invert_m4_m4_safe_ortho(imat, ob->object_to_world().ptr()); - - /* Add back active mesh first. - * This allows to keep things similar as they were, as much as possible - * (i.e. data from active mesh will remain first ones in new result of the merge, - * in same order for CD layers, etc). See also #50084. - */ - join_mesh_single(depsgraph, - bmain, - scene, - ob, - ob, - imat, - &vert_positions, - &edge, - &corner_verts, - &corner_edges, - face_offsets, - &vert_data, - &edge_data, - &ldata, - &face_data, - totvert, - totedge, - totloop, - faces_num, - key, - nkey, - matar, - &vertofs, - &edgeofs, - &loopofs, - &polyofs); - - CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) { - if (ob_iter == ob) { - continue; - } - /* only join if this is a mesh */ - if (ob_iter->type == OB_MESH) { - join_mesh_single(depsgraph, - bmain, - scene, - ob, - ob_iter, - imat, - &vert_positions, - &edge, - &corner_verts, - &corner_edges, - face_offsets, - &vert_data, - &edge_data, - &ldata, - &face_data, - totvert, - totedge, - totloop, - faces_num, - key, - nkey, - matar, - &vertofs, - &edgeofs, - &loopofs, - &polyofs); - - /* free base, now that data is merged */ - if (ob_iter != ob) { - blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter); - } - } - } - CTX_DATA_END; - - /* return to mesh we're merging to */ - mesh = static_cast(ob->data); - - BKE_mesh_clear_geometry(mesh); - - if (faces_num) { - mesh->face_offset_indices = face_offsets; - mesh->runtime->face_offsets_sharing_info = blender::implicit_sharing::info_for_mem_free( - face_offsets); - } - - mesh->verts_num = totvert; - mesh->edges_num = totedge; - mesh->corners_num = totloop; - mesh->faces_num = faces_num; - - mesh->vert_data = vert_data; - mesh->edge_data = edge_data; - mesh->corner_data = ldata; - mesh->face_data = face_data; - - /* old material array */ - for (a = 1; a <= ob->totcol; a++) { - ma = ob->mat[a - 1]; - if (ma) { - id_us_min(&ma->id); - } - } - for (a = 1; a <= mesh->totcol; a++) { - ma = mesh->mat[a - 1]; - if (ma) { - id_us_min(&ma->id); - } - } - MEM_SAFE_FREE(ob->mat); - MEM_SAFE_FREE(ob->matbits); - MEM_SAFE_FREE(mesh->mat); - - /* If the object had no slots, don't add an empty one. */ - if (ob->totcol == 0 && matar.size() == 1 && matar[0] == nullptr) { - matar.clear(); - } - - const int totcol = matar.size(); - if (totcol) { - mesh->mat = MEM_calloc_arrayN(totcol, __func__); - std::copy_n(matar.data(), totcol, mesh->mat); - ob->mat = MEM_calloc_arrayN(totcol, __func__); - ob->matbits = MEM_calloc_arrayN(totcol, __func__); - } - - ob->totcol = mesh->totcol = totcol; - - /* other mesh users */ - BKE_objects_materials_sync_length_all(bmain, (ID *)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); - } - - /* Due to dependency cycle some other object might access old derived data. */ - BKE_object_free_derived_caches(ob); - - DEG_relations_tag_update(bmain); /* removed objects, need to rebuild dag */ - - DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - - DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); - WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); - - return OPERATOR_FINISHED; -} - /* -------------------------------------------------------------------- */ /** \name Join as Shapes *