diff --git a/source/blender/blenkernel/BKE_customdata.hh b/source/blender/blenkernel/BKE_customdata.hh index 17e19b3c71c..a8878985ad6 100644 --- a/source/blender/blenkernel/BKE_customdata.hh +++ b/source/blender/blenkernel/BKE_customdata.hh @@ -343,8 +343,8 @@ void CustomData_copy_data_layer(const CustomData *source, int dst_index, int count); void CustomData_copy_elements(eCustomDataType type, - void *src_data_ofs, - void *dst_data_ofs, + const void *src_data, + void *dst_data, int count); /** diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 30d74888261..602597ed52c 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -3257,17 +3257,17 @@ void CustomData_set_only_copy(const CustomData *data, const eCustomDataMask mask } void CustomData_copy_elements(const eCustomDataType type, - void *src_data_ofs, - void *dst_data_ofs, + const void *src_data, + void *dst_data, const int count) { const LayerTypeInfo *typeInfo = layerType_getInfo(type); if (typeInfo->copy) { - typeInfo->copy(src_data_ofs, dst_data_ofs, count); + typeInfo->copy(src_data, dst_data, count); } else { - memcpy(dst_data_ofs, src_data_ofs, size_t(count) * typeInfo->size); + memcpy(dst_data, src_data, size_t(count) * typeInfo->size); } } diff --git a/source/blender/editors/mesh/mesh_join.cc b/source/blender/editors/mesh/mesh_join.cc index 6e9c2bec508..5e6ce5dce8b 100644 --- a/source/blender/editors/mesh/mesh_join.cc +++ b/source/blender/editors/mesh/mesh_join.cc @@ -59,12 +59,8 @@ static VectorSet join_vertex_groups(const Span obje Mesh &dst_mesh) { VectorSet vertex_group_names; - LISTBASE_FOREACH (const bDeformGroup *, dg, &dst_mesh.vertex_group_names) { - vertex_group_names.add_new(dg->name); - } - bool any_vertex_group_data = false; - for (const int i : objects_to_join.index_range().drop_front(1)) { + for (const int i : objects_to_join.index_range()) { 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) { @@ -81,9 +77,9 @@ static VectorSet join_vertex_groups(const Span obje MDeformVert *dvert = (MDeformVert *)CustomData_add_layer( &dst_mesh.vert_data, CD_MDEFORMVERT, CD_CONSTRUCT, dst_mesh.verts_num); - for (const int i : objects_to_join.index_range().drop_front(1)) { + for (const int i : objects_to_join.index_range()) { 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()); + const Span src_dverts = src_mesh.deform_verts(); if (src_dverts.is_empty()) { continue; } @@ -106,29 +102,48 @@ static VectorSet join_vertex_groups(const Span obje return vertex_group_names; } -static void join_positions_and_shape_keys(Main *bmain, - const Span objects_to_join, - const OffsetIndices vert_ranges, - const float4x4 &world_to_dst_mesh, - Mesh &dst_mesh) +static void join_positions(const Span objects_to_join, + const OffsetIndices vert_ranges, + const float4x4 &world_to_dst_mesh, + Mesh &dst_mesh) { + MutableSpan dst_positions = dst_mesh.vert_positions_for_write(); + for (const int i : objects_to_join.index_range()) { + 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(); + const float4x4 transform = world_to_dst_mesh * src_object.object_to_world(); + math::transform_points(src_positions, transform, dst_positions.slice(dst_range)); + } +} + +static void join_shape_keys(Main *bmain, + const Span objects_to_join, + const OffsetIndices vert_ranges, + const float4x4 &world_to_active_mesh, + Mesh &active_mesh) +{ + const int dst_verts_num = vert_ranges.total_size(); Vector key_blocks; VectorSet key_names; - if (Key *key = dst_mesh.key) { + if (Key *key = active_mesh.key) { LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { + kb->data = MEM_reallocN(kb->data, sizeof(float3) * dst_verts_num); + kb->totelem = dst_verts_num; 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(bmain, &dst_mesh.id); - dst_mesh.key->type = KEY_RELATIVE; + if (!active_mesh.key) { + active_mesh.key = BKE_key_add(bmain, &active_mesh.id); + active_mesh.key->type = KEY_RELATIVE; } }; - MutableSpan dst_positions = dst_mesh.vert_positions_for_write(); + const Span active_mesh_positions = active_mesh.vert_positions(); for (const int i : objects_to_join.index_range().drop_front(1)) { const Key *src_key = static_cast(objects_to_join[i]->data)->key; @@ -138,15 +153,14 @@ static void join_positions_and_shape_keys(Main *bmain, 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); + KeyBlock *dst_kb = BKE_keyblock_add(active_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; + dst_kb->data = MEM_malloc_arrayN(dst_verts_num, __func__); + dst_kb->totelem = dst_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())); + key_data.take_front(vert_ranges[0].size()).copy_from(active_mesh_positions); /* Remap `KeyBlock::relative`. */ if (const KeyBlock *src_kb_relative = static_cast( @@ -158,27 +172,28 @@ static void join_positions_and_shape_keys(Main *bmain, } } + Key *dst_key = active_mesh.key; + if (!dst_key) { + 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 = 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)); + const Span src_positions = src_mesh.vert_positions(); + const float4x4 transform = world_to_active_mesh * src_object.object_to_world(); - 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)); - } + 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 { + math::transform_points(src_positions, transform, key_data.slice(dst_range)); } } } @@ -247,7 +262,7 @@ static void join_generic_attributes(const Span objects_to_join, const bke::AttrType data_type = 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)) { + for (const int i : objects_to_join.index_range()) { 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); @@ -268,7 +283,7 @@ static void join_generic_attributes(const Span objects_to_join, } }(); - src.materialize(IndexRange(dst_range.size()), dst.span.slice(dst_range).data()); + src.materialize(dst.span.slice(dst_range).data()); } dst.finish(); } @@ -301,13 +316,13 @@ static VectorSet join_materials(const Span objects_t return materials; } - bke::SpanAttributeWriter dst_material_indices = dst_attributes.lookup_or_add_for_write_span( + bke::SpanAttributeWriter dst_attr = dst_attributes.lookup_or_add_for_write_only_span( "material_index", bke::AttrDomain::Face); - if (!dst_material_indices) { + if (!dst_attr) { return {}; } - for (const int i : objects_to_join.index_range().drop_front(1)) { + for (const int i : objects_to_join.index_range()) { const Object &src_object = *objects_to_join[i]; const IndexRange dst_range = face_ranges[i]; const Mesh &src_mesh = *static_cast(src_object.data); @@ -319,14 +334,13 @@ static VectorSet join_materials(const Span objects_t 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)); + dst_attr.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)); + material_indices.materialize(dst_range.index_range(), dst_attr.span.slice(dst_range)); continue; } @@ -341,11 +355,11 @@ static VectorSet join_materials(const Span objects_t 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]; + dst_attr.span[dst_range[face]] = index_map[src]; } } - dst_material_indices.finish(); + dst_attr.finish(); return materials; } @@ -357,18 +371,23 @@ static void join_face_sets(const Span objects_to_join, const OffsetIndices face_ranges, Mesh &dst_mesh) { - bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); - bke::SpanAttributeWriter dst_face_sets = dst_attributes.lookup_for_write_span( - ".sculpt_face_set"); - if (!dst_face_sets) { - return; - } - if (dst_face_sets.domain != bke::AttrDomain::Face) { + if (std::none_of(objects_to_join.begin(), objects_to_join.end(), [](const Object *object) { + const Mesh &mesh = *static_cast(object->data); + return mesh.attributes().contains(".sculpt_face_set"); + })) + { return; } - int max_face_set = 1; - for (const int i : objects_to_join.index_range().drop_front(1)) { + bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); + bke::SpanAttributeWriter dst_face_sets = dst_attributes.lookup_or_add_for_write_span( + ".sculpt_face_set", bke::AttrDomain::Face); + if (!dst_face_sets) { + return; + } + + int max_face_set = 0; + for (const int i : objects_to_join.index_range()) { const Object &src_object = *objects_to_join[i]; const IndexRange dst_range = face_ranges[i]; const Mesh &src_mesh = *static_cast(src_object.data); @@ -463,10 +482,9 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) /* Only join meshes if there are verts to join, * there aren't too many, and we only had one mesh selected. */ - Mesh *dst_mesh = (Mesh *)active_object->data; - Key *key = dst_mesh->key; + Mesh *active_mesh = (Mesh *)active_object->data; - if (ELEM(vert_ranges.total_size(), 0, dst_mesh->verts_num)) { + if (ELEM(vert_ranges.total_size(), 0, active_mesh->verts_num)) { BKE_report(op->reports, RPT_WARNING, "No mesh data to join"); return OPERATOR_CANCELLED; } @@ -480,39 +498,21 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - 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); - } - 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) { - kb->data = MEM_reallocN(kb->data, sizeof(float3) * dst_mesh->verts_num); - kb->totelem = dst_mesh->verts_num; - } - } - - BKE_mesh_runtime_clear_geometry(dst_mesh); + Mesh *dst_mesh = BKE_mesh_new_nomain(vert_ranges.total_size(), + edge_ranges.total_size(), + face_ranges.total_size(), + corner_ranges.total_size()); /* 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()); - join_positions_and_shape_keys( - bmain, objects_to_join, vert_ranges, world_to_active_object, *dst_mesh); + join_shape_keys(bmain, objects_to_join, vert_ranges, world_to_active_object, *active_mesh); + join_positions(objects_to_join, vert_ranges, world_to_active_object, *dst_mesh); MutableSpan dst_edges = dst_mesh->edges_for_write(); - for (const int i : objects_to_join.index_range().drop_front(1)) { + for (const int i : objects_to_join.index_range()) { const Object &src_object = *objects_to_join[i]; const IndexRange dst_range = edge_ranges[i]; const Mesh &src_mesh = *static_cast(src_object.data); @@ -523,7 +523,7 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) } MutableSpan dst_corner_verts = dst_mesh->corner_verts_for_write(); - for (const int i : objects_to_join.index_range().drop_front(1)) { + for (const int i : objects_to_join.index_range()) { const Object &src_object = *objects_to_join[i]; const IndexRange dst_range = corner_ranges[i]; const Mesh &src_mesh = *static_cast(src_object.data); @@ -534,7 +534,7 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) } MutableSpan dst_corner_edges = dst_mesh->corner_edges_for_write(); - for (const int i : objects_to_join.index_range().drop_front(1)) { + for (const int i : objects_to_join.index_range()) { const Object &src_object = *objects_to_join[i]; const IndexRange dst_range = corner_ranges[i]; const Mesh &src_mesh = *static_cast(src_object.data); @@ -545,7 +545,7 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) } MutableSpan dst_face_offsets = dst_mesh->face_offsets_for_write(); - for (const int i : objects_to_join.index_range().drop_front(1)) { + for (const int i : objects_to_join.index_range()) { const Object &src_object = *objects_to_join[i]; const IndexRange dst_range = face_ranges[i]; const Mesh &src_mesh = *static_cast(src_object.data); @@ -556,24 +556,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) } 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); @@ -589,6 +571,50 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) corner_ranges, *dst_mesh); + /* Copy multires data to the out-of-main mesh. */ + if (get_multires_modifier(scene, active_object, true)) { + if (std::any_of(objects_to_join.begin(), objects_to_join.end(), [](const Object *object) { + const Mesh &src_mesh = *static_cast(object->data); + return CustomData_has_layer(&src_mesh.corner_data, CD_MDISPS); + })) + { + MDisps *dst = static_cast(CustomData_add_layer( + &dst_mesh->corner_data, CD_MDISPS, CD_CONSTRUCT, dst_mesh->corners_num)); + for (const int i : objects_to_join.index_range()) { + const Mesh &src_mesh = *static_cast(objects_to_join[i]->data); + if (const void *src = CustomData_get_layer(&src_mesh.corner_data, CD_MDISPS)) { + CustomData_copy_elements( + CD_MDISPS, src, &dst[corner_ranges[i].first()], src_mesh.corners_num); + } + } + } + if (std::any_of(objects_to_join.begin(), objects_to_join.end(), [](const Object *object) { + const Mesh &src_mesh = *static_cast(object->data); + return CustomData_has_layer(&src_mesh.corner_data, CD_GRID_PAINT_MASK); + })) + { + GridPaintMask *dst = static_cast(CustomData_add_layer( + &dst_mesh->corner_data, CD_GRID_PAINT_MASK, CD_CONSTRUCT, dst_mesh->corners_num)); + for (const int i : objects_to_join.index_range()) { + const Mesh &src_mesh = *static_cast(objects_to_join[i]->data); + if (const void *src = CustomData_get_layer(&src_mesh.corner_data, CD_GRID_PAINT_MASK)) { + CustomData_copy_elements( + CD_GRID_PAINT_MASK, src, &dst[corner_ranges[i].first()], src_mesh.corners_num); + } + } + } + } + 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); + } + } + + BKE_mesh_nomain_to_mesh(dst_mesh, active_mesh, active_object, false); + for (Object *object : objects_to_join.as_span().drop_front(1)) { object::base_free_and_unlink(bmain, scene, object); } @@ -600,13 +626,13 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) } } for (const int a : IndexRange(active_object->totcol)) { - if (Material *ma = dst_mesh->mat[a]) { + if (Material *ma = active_mesh->mat[a]) { id_us_min(&ma->id); } } MEM_SAFE_FREE(active_object->mat); MEM_SAFE_FREE(active_object->matbits); - MEM_SAFE_FREE(dst_mesh->mat); + MEM_SAFE_FREE(active_mesh->mat); /* If the object had no slots, don't add an empty one. */ if (active_object->totcol == 0 && materials.size() == 1 && materials[0] == nullptr) { @@ -616,9 +642,9 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) const int totcol = materials.size(); if (totcol) { VectorData data = materials.extract_vector().release(); - dst_mesh->mat = data.data; + active_mesh->mat = data.data; for (const int i : IndexRange(totcol)) { - if (Material *ma = dst_mesh->mat[i]) { + if (Material *ma = active_mesh->mat[i]) { id_us_plus((ID *)ma); } } @@ -626,14 +652,16 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) active_object->matbits = MEM_calloc_arrayN(totcol, __func__); } - active_object->totcol = dst_mesh->totcol = totcol; + active_object->totcol = active_mesh->totcol = totcol; /* other mesh users */ - BKE_objects_materials_sync_length_all(bmain, (ID *)dst_mesh); + BKE_objects_materials_sync_length_all(bmain, &active_mesh->id); /* ensure newly inserted keys are time sorted */ - if (key && (key->type != KEY_RELATIVE)) { - BKE_key_sort(key); + if (Key *key = active_mesh->key) { + if (key->type != KEY_RELATIVE) { + BKE_key_sort(key); + } } /* Due to dependency cycle some other object might access old derived data. */ diff --git a/tests/python/mesh_join.py b/tests/python/mesh_join.py index cbe20a22f3a..abfc07580bc 100644 --- a/tests/python/mesh_join.py +++ b/tests/python/mesh_join.py @@ -58,8 +58,8 @@ class TestMeshJoin(unittest.TestCase): self.assertEqual(joined_attr.data[0].value, 1) self.assertEqual(joined_attr.data[1].value, 1) self.assertEqual(joined_attr.data[2].value, 45) - self.assertEqual(joined_attr.data[9].value, 53) - self.assertEqual(joined_attr.data[20].value, 1) + self.assertEqual(joined_attr.data[9].value, 98) + self.assertEqual(joined_attr.data[20].value, 46) def test_materials_simple(self): bpy.ops.object.select_all(action='SELECT') @@ -91,11 +91,11 @@ class TestMeshJoin(unittest.TestCase): cube_1 = bpy.context.view_layer.objects.active cube_1.data.materials.append(mat_1) material_indices = cube_1.data.attributes.new(name="material_index", type='INT', domain='FACE') - material_indices.data.foreach_set('value', [0, 1, 1, 700, 1, 2]) bpy.ops.mesh.primitive_cube_add() cube_2 = bpy.context.view_layer.objects.active material_indices = cube_2.data.attributes.new(name="material_index", type='INT', domain='FACE') + material_indices.data.foreach_set('value', [0, 1, 1, 700, 1, 2]) bpy.ops.object.select_all(action='SELECT') bpy.context.view_layer.objects.active = cube_1 @@ -106,7 +106,7 @@ class TestMeshJoin(unittest.TestCase): material_indices = cube_1.data.attributes["material_index"] self.assertTrue(material_indices) material_index_data = [m.value for m in material_indices.data] - self.assertEqual(material_index_data, [0, 1, 1, 700, 1, 2] + [0] * 6) + self.assertEqual(material_index_data, [0] * 6 + [0, 1, 1, 700, 1, 2]) def test_materials(self): bpy.ops.object.select_all(action='SELECT')