Cleanup: Move mesh join objects implementation to separate file
This commit is contained in:
@@ -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
|
||||
|
||||
701
source/blender/editors/mesh/mesh_join.cc
Normal file
701
source/blender/editors/mesh/mesh_join.cc
Normal file
@@ -0,0 +1,701 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edmesh
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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<Material *> &matar,
|
||||
int *vertofs,
|
||||
int *edgeofs,
|
||||
int *loopofs,
|
||||
int *polyofs)
|
||||
{
|
||||
int a;
|
||||
|
||||
Mesh *mesh = static_cast<Mesh *>(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<float(*)[3]>(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<float(*)[3]>(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<int> 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<int *>(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<int> 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<int> face_sets = attributes.lookup_for_write_span<int>(
|
||||
".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<Mesh *>(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<Material *> 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<Mesh *>(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<bDeformGroup>(__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<int>(mesh->key->totkey, __func__);
|
||||
KeyBlock **kb_map = MEM_malloc_arrayN<KeyBlock *>(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<int>(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<Mesh *>(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<Material *>(totcol, __func__);
|
||||
std::copy_n(matar.data(), totcol, mesh->mat);
|
||||
ob->mat = MEM_calloc_arrayN<Material *>(totcol, __func__);
|
||||
ob->matbits = MEM_calloc_arrayN<char>(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;
|
||||
}
|
||||
@@ -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<Material *> &matar,
|
||||
int *vertofs,
|
||||
int *edgeofs,
|
||||
int *loopofs,
|
||||
int *polyofs)
|
||||
{
|
||||
int a;
|
||||
|
||||
Mesh *mesh = static_cast<Mesh *>(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<float(*)[3]>(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<float(*)[3]>(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<int> 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<int *>(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<int> 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<int> face_sets = attributes.lookup_for_write_span<int>(
|
||||
".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<Mesh *>(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<Material *> 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<Mesh *>(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<bDeformGroup>(__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<int>(mesh->key->totkey, __func__);
|
||||
KeyBlock **kb_map = MEM_malloc_arrayN<KeyBlock *>(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<int>(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<Mesh *>(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<Material *>(totcol, __func__);
|
||||
std::copy_n(matar.data(), totcol, mesh->mat);
|
||||
ob->mat = MEM_calloc_arrayN<Material *>(totcol, __func__);
|
||||
ob->matbits = MEM_calloc_arrayN<char>(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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user