Fix #143450: Bevel modifier crash with free normals

If the BMesh already has a "custom_normal" attribute with the wrong
type, the call to `BM_lnorspace_update` won't be able to add the
attribute with the expected name, and the bevel code ends up using an
invalid offset to access the data.

For the fix, first just guard against that case. But also make sure the
harden normals functionality still works when the input mesh has free
normals. They will no be converted to tangent space normals as
necessary, in bevel and in other BMesh code that requires that
custom normal storage format.

Pull Request: https://projects.blender.org/blender/blender/pulls/143489
This commit is contained in:
Hans Goudey
2025-08-05 04:45:49 +02:00
committed by Hans Goudey
parent 9d449ffcba
commit f5f9c4f444
2 changed files with 67 additions and 2 deletions

View File

@@ -1877,14 +1877,73 @@ void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor)
#endif
}
/**
* Make sure the corner fan (tangent space) style custom normals exist on the BMesh. If free vector
* custom normals exist, they'll be converted. This is often necessary for BMesh editing tools that
* don't (yet) support free normals.
*/
static void bm_lnorspace_ensure_from_free_normals(BMesh *bm)
{
/* Zero values tell the normals calculation code to use the automatic normals (rather than any
* custom normal vector). */
Array<float3> lnors(bm->totloop, float3(0));
const int vert_free_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT3, "custom_normal");
const int edge_free_offset = CustomData_get_offset_named(
&bm->edata, CD_PROP_FLOAT3, "custom_normal");
const int face_free_offset = CustomData_get_offset_named(
&bm->pdata, CD_PROP_FLOAT3, "custom_normal");
const int loop_free_offset = CustomData_get_offset_named(
&bm->ldata, CD_PROP_FLOAT3, "custom_normal");
if (vert_free_offset != -1) {
int loop_index = 0;
BMFace *f;
BMLoop *l;
BMIter fiter, liter;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
lnors[loop_index++] = float3(BM_ELEM_CD_GET_FLOAT_P(l->v, vert_free_offset));
}
}
BM_data_layer_free_named(bm, &bm->vdata, "custom_normal");
}
else if (edge_free_offset != -1) {
BM_data_layer_free_named(bm, &bm->edata, "custom_normal");
}
else if (face_free_offset != -1) {
int loop_index = 0;
BMFace *f;
BMLoop *l;
BMIter fiter, liter;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
lnors[loop_index++] = float3(BM_ELEM_CD_GET_FLOAT_P(f, face_free_offset));
}
}
BM_data_layer_free_named(bm, &bm->pdata, "custom_normal");
}
else if (loop_free_offset != -1) {
int loop_index = 0;
BMFace *f;
BMLoop *l;
BMIter fiter, liter;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
lnors[loop_index++] = float3(BM_ELEM_CD_GET_FLOAT_P(l, loop_free_offset));
}
}
BM_data_layer_free_named(bm, &bm->ldata, "custom_normal");
}
BM_lnorspacearr_store(bm, lnors);
}
void BM_lnorspace_update(BMesh *bm)
{
if (bm->lnor_spacearr == nullptr) {
bm->lnor_spacearr = MEM_callocN<MLoopNorSpaceArray>(__func__);
}
if (bm->lnor_spacearr->lspacearr == nullptr) {
Array<float3> lnors(bm->totloop, float3(0));
BM_lnorspacearr_store(bm, lnors);
bm_lnorspace_ensure_from_free_normals(bm);
}
else if (bm->spacearr_dirty & (BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL)) {
BM_lnorspace_rebuild(bm, false);

View File

@@ -2675,6 +2675,12 @@ static void bevel_harden_normals(BevelParams *bp, BMesh *bm)
cd_clnors_offset = CustomData_get_offset_named(&bm->ldata, CD_PROP_INT16_2D, "custom_normal");
}
/* If the custom normals attribute still hasn't been added with the correct type, at least don't
* crash. */
if (cd_clnors_offset == -1) {
return;
}
BMIter fiter;
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {