diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index c18f64e27a3..e3fe20fcb31 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 3 +#define BLENDER_FILE_SUBVERSION 4 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_curve_to_mesh.hh b/source/blender/blenkernel/BKE_curve_to_mesh.hh index 470b907eb80..623a63af513 100644 --- a/source/blender/blenkernel/BKE_curve_to_mesh.hh +++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh @@ -10,6 +10,8 @@ struct Mesh; * \ingroup bke */ +#include "BLI_virtual_array_fwd.hh" + #include "BKE_attribute_filter.hh" namespace blender::bke { @@ -28,6 +30,7 @@ class CurvesGeometry; */ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, const CurvesGeometry &profile, + const VArray &scales, bool fill_caps, const bke::AttributeFilter &attribute_filter = {}); /** diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index fefe6bf1692..b2937864d86 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -189,15 +189,15 @@ static void fill_mesh_positions(const int main_point_num, const Span profile_positions, const Span tangents, const Span normals, - const Span radii, + const Span scales, MutableSpan mesh_positions) { if (profile_point_num == 1) { for (const int i_ring : IndexRange(main_point_num)) { float4x4 point_matrix = build_point_matrix( main_positions[i_ring], normals[i_ring], tangents[i_ring]); - if (!radii.is_empty()) { - point_matrix = math::scale(point_matrix, float3(radii[i_ring])); + if (!scales.is_empty()) { + point_matrix = math::scale(point_matrix, float3(scales[i_ring])); } mesh_positions[i_ring] = math::transform_point(point_matrix, profile_positions.first()); } @@ -206,8 +206,8 @@ static void fill_mesh_positions(const int main_point_num, for (const int i_ring : IndexRange(main_point_num)) { float4x4 point_matrix = build_point_matrix( main_positions[i_ring], normals[i_ring], tangents[i_ring]); - if (!radii.is_empty()) { - point_matrix = math::scale(point_matrix, float3(radii[i_ring])); + if (!scales.is_empty()) { + point_matrix = math::scale(point_matrix, float3(scales[i_ring])); } const int ring_vert_start = i_ring * profile_point_num; @@ -472,6 +472,7 @@ static void foreach_curve_combination(const CurvesInfo &info, static void build_mesh_positions(const CurvesInfo &curves_info, const ResultOffsets &offsets, + const VArray &scales, Vector &eval_buffer, Mesh &mesh) { @@ -499,9 +500,9 @@ static void build_mesh_positions(const CurvesInfo &curves_info, } const Span tangents = curves_info.main.evaluated_tangents(); const Span normals = curves_info.main.evaluated_normals(); - Span radii_eval; - if (const GVArray radii = *curves_info.main.attributes().lookup("radius", AttrDomain::Point)) { - radii_eval = evaluate_attribute(radii, curves_info.main, eval_buffer).typed(); + Span eval_scales; + if (!scales.is_empty() && scales.get_if_single() != 1.0f) { + eval_scales = evaluate_attribute(scales, curves_info.main, eval_buffer).typed(); } foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { fill_mesh_positions(info.main_points.size(), @@ -510,7 +511,7 @@ static void build_mesh_positions(const CurvesInfo &curves_info, profile_positions.slice(info.profile_points), tangents.slice(info.main_points), normals.slice(info.main_points), - radii_eval.is_empty() ? radii_eval : radii_eval.slice(info.main_points), + eval_scales.is_empty() ? eval_scales : eval_scales.slice(info.main_points), positions.slice(info.vert_range)); }); } @@ -811,6 +812,7 @@ static void write_sharp_bezier_edges(const CurvesInfo &curves_info, Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, const CurvesGeometry &profile, + const VArray &scales, const bool fill_caps, const AttributeFilter &attribute_filter) { @@ -871,7 +873,7 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, /* Make sure curve attributes can be interpolated. */ main.ensure_can_interpolate_to_evaluated(); - build_mesh_positions(curves_info, offsets, eval_buffer, *mesh); + build_mesh_positions(curves_info, offsets, scales, eval_buffer, *mesh); mesh->tag_overlapping_none(); if (!offsets.any_single_point_main) { @@ -996,7 +998,7 @@ static CurvesGeometry get_curve_single_vert() Mesh *curve_to_wire_mesh(const CurvesGeometry &curve, const AttributeFilter &attribute_filter) { static const CurvesGeometry vert_curve = get_curve_single_vert(); - return curve_to_mesh_sweep(curve, vert_curve, false, attribute_filter); + return curve_to_mesh_sweep(curve, vert_curve, {}, false, attribute_filter); } } // namespace blender::bke diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index fc377a666d5..e00387b6b30 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -3766,6 +3766,83 @@ static void version_geometry_normal_input_node(bNodeTree &ntree) } } +static void do_version_node_curve_to_mesh_scale_input(bNodeTree *tree) +{ + using namespace blender; + Set curve_to_mesh_nodes; + LISTBASE_FOREACH (bNode *, node, &tree->nodes) { + if (STREQ(node->idname, "GeometryNodeCurveToMesh")) { + curve_to_mesh_nodes.add(node); + } + } + + for (bNode *curve_to_mesh : curve_to_mesh_nodes) { + if (bke::node_find_socket(*curve_to_mesh, SOCK_IN, "Scale")) { + /* Make versioning idempotent. */ + continue; + } + version_node_add_socket_if_not_exist( + tree, curve_to_mesh, SOCK_IN, SOCK_FLOAT, PROP_NONE, "Scale", "Scale"); + + bNode &named_attribute = version_node_add_empty(*tree, "GeometryNodeInputNamedAttribute"); + NodeGeometryInputNamedAttribute *named_attribute_storage = + MEM_callocN(__func__); + named_attribute_storage->data_type = CD_PROP_FLOAT; + named_attribute.storage = named_attribute_storage; + named_attribute.parent = curve_to_mesh->parent; + named_attribute.location[0] = curve_to_mesh->location[0] - 25; + named_attribute.location[1] = curve_to_mesh->location[1]; + named_attribute.flag &= ~NODE_SELECT; + + bNodeSocket *name_input = version_node_add_socket_if_not_exist( + tree, &named_attribute, SOCK_IN, SOCK_STRING, PROP_NONE, "Name", "Name"); + STRNCPY(name_input->default_value_typed()->value, "radius"); + + version_node_add_socket_if_not_exist( + tree, &named_attribute, SOCK_OUT, SOCK_BOOLEAN, PROP_NONE, "Exists", "Exists"); + version_node_add_socket_if_not_exist( + tree, &named_attribute, SOCK_OUT, SOCK_FLOAT, PROP_NONE, "Attribute", "Attribute"); + + bNode &switch_node = version_node_add_empty(*tree, "GeometryNodeSwitch"); + NodeSwitch *switch_storage = MEM_callocN(__func__); + switch_storage->input_type = SOCK_FLOAT; + switch_node.storage = switch_storage; + switch_node.parent = curve_to_mesh->parent; + switch_node.location[0] = curve_to_mesh->location[0] - 25; + switch_node.location[1] = curve_to_mesh->location[1]; + switch_node.flag &= ~NODE_SELECT; + + version_node_add_socket_if_not_exist( + tree, &switch_node, SOCK_IN, SOCK_BOOLEAN, PROP_NONE, "Switch", "Switch"); + bNodeSocket *false_input = version_node_add_socket_if_not_exist( + tree, &switch_node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "False", "False"); + false_input->default_value_typed()->value = 1.0f; + + version_node_add_socket_if_not_exist( + tree, &switch_node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "True", "True"); + + version_node_add_link(*tree, + named_attribute, + *bke::node_find_socket(named_attribute, SOCK_OUT, "Exists"), + switch_node, + *bke::node_find_socket(switch_node, SOCK_IN, "Switch")); + version_node_add_link(*tree, + named_attribute, + *bke::node_find_socket(named_attribute, SOCK_OUT, "Attribute"), + switch_node, + *bke::node_find_socket(switch_node, SOCK_IN, "True")); + + version_node_add_socket_if_not_exist( + tree, &switch_node, SOCK_OUT, SOCK_FLOAT, PROP_NONE, "Output", "Output"); + + version_node_add_link(*tree, + switch_node, + *bke::node_find_socket(switch_node, SOCK_OUT, "Output"), + *curve_to_mesh, + *bke::node_find_socket(*curve_to_mesh, SOCK_IN, "Scale")); + } +} + static bool strip_effect_overdrop_to_alphaover(Strip *strip, void * /*user_data*/) { if (strip->type == STRIP_TYPE_OVERDROP_REMOVED) { @@ -5899,6 +5976,15 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) version_sequencer_update_overdrop(bmain); } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 4)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type == NTREE_GEOMETRY) { + do_version_node_curve_to_mesh_scale_input(ntree); + } + } + FOREACH_NODETREE_END; + } + /* Always run this versioning; meshes are written with the legacy format which always needs to * be converted to the new format on file load. Can be moved to a subversion check in a larger * breaking release. */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index 77008670f8f..4ff2113296a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -24,6 +24,8 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input("Profile Curve") .only_realized_data() .supported_type(GeometryComponent::Type::Curve); + b.add_input("Scale").default_value(1.0f).min(0.0f).field_on({0}).description( + "Scale of the profile at each point"); b.add_input("Fill Caps") .description( "If the profile spline is cyclic, fill the ends of the generated mesh with N-gons"); @@ -32,14 +34,22 @@ static void node_declare(NodeDeclarationBuilder &b) static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves, const GeometrySet &profile_set, + const fn::FieldContext &context, + const Field &scale_field, const bool fill_caps, const AttributeFilter &attribute_filter) { Mesh *mesh; if (profile_set.has_curves()) { const Curves *profile_curves = profile_set.get_curves(); + + FieldEvaluator evaluator{context, curves.points_num()}; + evaluator.add(scale_field); + evaluator.evaluate(); + + const VArray profile_scales = evaluator.get_evaluated(0); mesh = bke::curve_to_mesh_sweep( - curves, profile_curves->geometry.wrap(), fill_caps, attribute_filter); + curves, profile_curves->geometry.wrap(), profile_scales, fill_caps, attribute_filter); } else { mesh = bke::curve_to_wire_mesh(curves, attribute_filter); @@ -50,6 +60,7 @@ static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves, static void grease_pencil_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set, + const Field &scale_field, const bool fill_caps, const AttributeFilter &attribute_filter) { @@ -64,7 +75,10 @@ static void grease_pencil_to_mesh(GeometrySet &geometry_set, continue; } const bke::CurvesGeometry &curves = drawing->strokes(); - mesh_by_layer[layer_index] = curve_to_mesh(curves, profile_set, fill_caps, attribute_filter); + const bke::GreasePencilLayerFieldContext context{ + grease_pencil, bke::AttrDomain::Point, layer_index}; + mesh_by_layer[layer_index] = curve_to_mesh( + curves, profile_set, context, scale_field, fill_caps, attribute_filter); } if (mesh_by_layer.is_empty()) { @@ -104,6 +118,7 @@ static void node_geo_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input("Curve"); GeometrySet profile_set = params.extract_input("Profile Curve"); + const Field scale_field = params.extract_input>("Scale"); const bool fill_caps = params.extract_input("Fill Caps"); bke::GeometryComponentEditData::remember_deformed_positions_if_necessary(curve_set); @@ -112,7 +127,10 @@ static void node_geo_exec(GeoNodeExecParams params) curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (geometry_set.has_curves()) { const Curves &curves = *geometry_set.get_curves(); - Mesh *mesh = curve_to_mesh(curves.geometry.wrap(), profile_set, fill_caps, attribute_filter); + + const bke::CurvesFieldContext context{curves, bke::AttrDomain::Point}; + Mesh *mesh = curve_to_mesh( + curves.geometry.wrap(), profile_set, context, scale_field, fill_caps, attribute_filter); if (mesh != nullptr) { mesh->mat = static_cast(MEM_dupallocN(curves.mat)); mesh->totcol = curves.totcol; @@ -120,7 +138,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.replace_mesh(mesh); } if (geometry_set.has_grease_pencil()) { - grease_pencil_to_mesh(geometry_set, profile_set, fill_caps, attribute_filter); + grease_pencil_to_mesh(geometry_set, profile_set, scale_field, fill_caps, attribute_filter); } geometry_set.keep_only_during_modify({GeometryComponent::Type::Mesh}); });