Fix #136378: Curve custom normals incorrectly propagated

Usually we prefer to propogate attributes based on name, even for
conversion between geometry types. Builtin attributes are a common
exception though. The Curve to Points node and the Curve to Mesh node
didn't correctly handle the "custom_normal" attribute, which shouldn't
be propagated. This has only been a problem since custom normals were
converted to a generic attribute on meshes in f9b627d29c.

Pull Request: https://projects.blender.org/blender/blender/pulls/136452
This commit is contained in:
Hans Goudey
2025-03-25 15:09:35 +01:00
committed by Hans Goudey
parent a062b334a9
commit fe52284be9
2 changed files with 51 additions and 21 deletions

View File

@@ -365,8 +365,13 @@ static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attribut
const AttributeFilter &attribute_filter)
{
/* The position attribute has special non-generic evaluation. */
if (id == "position") {
/* The position attribute has special non-generic evaluation. */
return false;
}
if (id == "custom_normal") {
/* The custom normal attribute is builtin on both meshes and curves, but has a different
* meaning and shouldn't be directly propagated. */
return false;
}
/* Don't propagate built-in curves attributes that are not built-in on meshes. */

View File

@@ -85,6 +85,7 @@ static void fill_rotation_attribute(const Span<float3> tangents,
}
static void copy_curve_domain_attributes(const AttributeAccessor curve_attributes,
const AttributeFilter &attribute_filter,
MutableAttributeAccessor point_attributes)
{
curve_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
@@ -94,6 +95,9 @@ static void copy_curve_domain_attributes(const AttributeAccessor curve_attribute
if (iter.domain != AttrDomain::Curve) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
if (iter.data_type == CD_PROP_STRING) {
return;
}
@@ -104,32 +108,47 @@ static void copy_curve_domain_attributes(const AttributeAccessor curve_attribute
});
}
static PointCloud *pointcloud_from_curves(bke::CurvesGeometry curves,
static PointCloud *pointcloud_from_curves(const bke::CurvesGeometry &curves,
const AttributeFilter &attribute_filter,
const std::optional<StringRef> &tangent_id,
const std::optional<StringRef> &normal_id,
const std::optional<StringRef> &rotation_id)
{
const AttributeAccessor curve_attributes = curves.attributes();
PointCloud *pointcloud = BKE_pointcloud_new_nomain(0);
CustomData_free(&pointcloud->pdata);
pointcloud->totpoint = curves.points_num();
MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
const bke::AttributeFilterFromFunc filter = [&](const StringRef name) {
if (attribute_filter.allow_skip(name)) {
return bke::AttributeFilter::Result::AllowSkip;
}
if (curve_attributes.is_builtin(name) && !point_attributes.is_builtin(name)) {
return bke::AttributeFilter::Result::AllowSkip;
}
return bke::AttributeFilter::Result::Process;
};
bke::copy_attributes(curves.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
filter,
pointcloud->attributes_for_write());
copy_curve_domain_attributes(curve_attributes, filter, point_attributes);
if (rotation_id) {
MutableAttributeAccessor attributes = curves.attributes_for_write();
const VArraySpan tangents = *attributes.lookup<float3>(*tangent_id, AttrDomain::Point);
const VArraySpan normals = *attributes.lookup<float3>(*normal_id, AttrDomain::Point);
SpanAttributeWriter<math::Quaternion> rotations =
attributes.lookup_or_add_for_write_only_span<math::Quaternion>(*rotation_id,
AttrDomain::Point);
const VArraySpan tangents = *curve_attributes.lookup<float3>(*tangent_id, AttrDomain::Point);
const VArraySpan normals = *curve_attributes.lookup<float3>(*normal_id, AttrDomain::Point);
SpanAttributeWriter rotations =
point_attributes.lookup_or_add_for_write_only_span<math::Quaternion>(*rotation_id,
AttrDomain::Point);
fill_rotation_attribute(tangents, normals, rotations.span);
rotations.finish();
}
/* Move the curve point custom data to the pointcloud, to avoid any copying. */
CustomData_free(&pointcloud->pdata);
pointcloud->pdata = curves.point_data;
CustomData_reset(&curves.point_data);
copy_curve_domain_attributes(curves.attributes(), pointcloud->attributes_for_write());
return pointcloud;
}
@@ -137,7 +156,8 @@ static void curve_to_points(GeometrySet &geometry_set,
GeoNodeExecParams params,
const GeometryNodeCurveResampleMode mode,
geometry::ResampleCurvesOutputAttributeIDs resample_attributes,
const std::optional<StringRef> &rotation_anonymous_id)
const std::optional<StringRef> &rotation_anonymous_id,
const AttributeFilter &attribute_filter)
{
switch (mode) {
case GEO_NODE_CURVE_RESAMPLE_COUNT: {
@@ -152,7 +172,8 @@ static void curve_to_points(GeometrySet &geometry_set,
fn::make_constant_field<bool>(true),
count,
resample_attributes);
PointCloud *pointcloud = pointcloud_from_curves(std::move(dst_curves),
PointCloud *pointcloud = pointcloud_from_curves(dst_curves,
attribute_filter,
resample_attributes.tangent_id,
resample_attributes.normal_id,
rotation_anonymous_id);
@@ -174,7 +195,8 @@ static void curve_to_points(GeometrySet &geometry_set,
fn::make_constant_field<bool>(true),
length,
resample_attributes);
PointCloud *pointcloud = pointcloud_from_curves(std::move(dst_curves),
PointCloud *pointcloud = pointcloud_from_curves(dst_curves,
attribute_filter,
resample_attributes.tangent_id,
resample_attributes.normal_id,
rotation_anonymous_id);
@@ -191,7 +213,8 @@ static void curve_to_points(GeometrySet &geometry_set,
const bke::CurvesFieldContext field_context{*src_curves_id, AttrDomain::Curve};
bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated(
src_curves, field_context, fn::make_constant_field<bool>(true), resample_attributes);
PointCloud *pointcloud = pointcloud_from_curves(std::move(dst_curves),
PointCloud *pointcloud = pointcloud_from_curves(dst_curves,
attribute_filter,
resample_attributes.tangent_id,
resample_attributes.normal_id,
rotation_anonymous_id);
@@ -265,7 +288,8 @@ static void grease_pencil_to_points(GeometrySet &geometry_set,
break;
}
}
pointcloud_by_layer[layer_index] = pointcloud_from_curves(std::move(dst_curves),
pointcloud_by_layer[layer_index] = pointcloud_from_curves(dst_curves,
attribute_filter,
resample_attributes.tangent_id,
resample_attributes.normal_id,
rotation_anonymous_id);
@@ -326,7 +350,8 @@ static void node_geo_exec(GeoNodeExecParams params)
const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Points");
if (geometry_set.has_curves()) {
curve_to_points(geometry_set, params, mode, resample_attributes, rotation_anonymous_id);
curve_to_points(
geometry_set, params, mode, resample_attributes, rotation_anonymous_id, attribute_filter);
}
if (geometry_set.has_grease_pencil()) {
grease_pencil_to_points(