diff options
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/blenkernel/BKE_blender_version.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_curve.h | 8 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_spline.hh | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curve.cc | 110 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curve_eval.cc | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/spline_nurbs.cc | 27 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_300.c | 60 | ||||
-rw-r--r-- | source/blender/editors/curve/editcurve.c | 2 | ||||
-rw-r--r-- | source/blender/editors/curve/editcurve_add.c | 4 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_curve.c | 63 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc | 1 |
11 files changed, 183 insertions, 97 deletions
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index a8a851bb228..1639a564508 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -25,7 +25,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 5 +#define BLENDER_FILE_SUBVERSION 6 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenkernel/BKE_curve.h b/source/blender/blenkernel/BKE_curve.h index 3f65ce83b3f..394d97223e3 100644 --- a/source/blender/blenkernel/BKE_curve.h +++ b/source/blender/blenkernel/BKE_curve.h @@ -269,6 +269,14 @@ void BKE_nurb_knot_calc_v(struct Nurb *nu); bool BKE_nurb_check_valid_u(const struct Nurb *nu); bool BKE_nurb_check_valid_v(const struct Nurb *nu); bool BKE_nurb_check_valid_uv(const struct Nurb *nu); +bool BKE_nurb_valid_message(int pnts, + short order, + short flag, + short type, + bool is_surf, + const char *dir, + char *message_dst, + size_t maxncpy); bool BKE_nurb_order_clamp_u(struct Nurb *nu); bool BKE_nurb_order_clamp_v(struct Nurb *nu); diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index 42b4702ee44..ed9b743b524 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -453,6 +453,7 @@ class NURBSpline final : public Spline { Normal, EndPoint, Bezier, + EndPointBezier, }; /** Method used to recalculate the knots vector when points are added or removed. */ diff --git a/source/blender/blenkernel/intern/curve.cc b/source/blender/blenkernel/intern/curve.cc index 6b7f7af44e8..0b619c1a969 100644 --- a/source/blender/blenkernel/intern/curve.cc +++ b/source/blender/blenkernel/intern/curve.cc @@ -17,8 +17,8 @@ #include "BLI_index_range.hh" #include "BLI_math.h" #include "BLI_math_vec_types.hh" +#include "BLI_string.h" #include "BLI_utildefines.h" - #include "BLT_translation.h" /* Allow using deprecated functionality for .blend file I/O. */ @@ -1156,12 +1156,13 @@ void BKE_nurb_bpoint_calc_plane(struct Nurb *nu, BPoint *bp, float r_plane[3]) static void calcknots(float *knots, const int pnts, const short order, const short flag) { const bool is_cyclic = flag & CU_NURB_CYCLIC; - const bool is_bezier = flag & CU_NURB_BEZIER && !(flag & CU_NURB_ENDPOINT); - const bool is_end_point = flag & CU_NURB_ENDPOINT && !(flag & CU_NURB_BEZIER); + const bool is_bezier = flag & CU_NURB_BEZIER; + const bool is_end_point = flag & CU_NURB_ENDPOINT; /* Inner knots are always repeated once except on Bezier case. */ const int repeat_inner = is_bezier ? order - 1 : 1; /* How many times to repeat 0.0 at the beginning of knot. */ - const int head = is_end_point && !is_cyclic ? order : (is_bezier ? order / 2 : 1); + const int head = is_end_point ? (order - (is_cyclic ? 1 : 0)) : + (is_bezier ? min_ii(2, repeat_inner) : 1); /* Number of knots replicating widths of the starting knots. * Covers both Cyclic and EndPoint cases. */ const int tail = is_cyclic ? 2 * order - 1 : (is_end_point ? order : 0); @@ -1171,11 +1172,17 @@ static void calcknots(float *knots, const int pnts, const short order, const sho int r = head; float current = 0.0f; - for (const int i : IndexRange(knot_count - tail)) { + const int offset = is_end_point && is_cyclic ? 1 : 0; + if (offset) { + knots[0] = current; + current += 1.0f; + } + + for (const int i : IndexRange(offset, knot_count - offset - tail)) { knots[i] = current; r--; if (r == 0) { - current += 1.0; + current += 1.0f; r = repeat_inner; } } @@ -4693,59 +4700,56 @@ void BKE_curve_nurbs_key_vert_tilts_apply(ListBase *lb, const float *key) } } -bool BKE_nurb_check_valid_u(const Nurb *nu) +bool BKE_nurb_valid_message(const int pnts, + const short order, + const short flag, + const short type, + const bool is_surf, + const char *dir, + char *message_dst, + const size_t maxncpy) { - if (nu->pntsu <= 1) { - return false; - } - if (nu->type != CU_NURBS) { - return true; /* not a nurb, lets assume its valid */ - } + const char *msg_template = ""; + uint16_t points_needed = 0; - if (nu->pntsu < nu->orderu) { - return false; + if (pnts <= 1) { + msg_template = TIP_("At least two points required."); } - if (((nu->flagu & CU_NURB_CYCLIC) == 0) && (nu->flagu & CU_NURB_BEZIER)) { - /* Bezier U Endpoints */ - if (nu->orderu == 4) { - if (nu->pntsu < 5) { - return false; /* bezier with 4 orderu needs 5 points */ - } + else if (type == CU_NURBS) { + if (pnts < order) { + msg_template = TIP_("Must have more control points than Order"); } - else { - if (nu->orderu != 3) { - return false; /* order must be 3 or 4 */ + else if (flag & CU_NURB_BEZIER) { + if (flag & CU_NURB_CYCLIC) { + const uint16_t remainder = pnts % (order - 1); + points_needed = remainder > 0 ? order - 1 - remainder : 0; + } + else if (((flag & CU_NURB_ENDPOINT) == 0) && pnts <= order) { + points_needed = order + 1 - pnts; + } + if (points_needed) { + msg_template = is_surf ? TIP_("%d more %s row(s) needed for Bezier") : + TIP_("%d more point(s) needed for Bezier"); } } } - return true; + + if (message_dst) { + BLI_snprintf(message_dst, maxncpy, msg_template, points_needed, dir); + } + return msg_template[0]; } -bool BKE_nurb_check_valid_v(const Nurb *nu) + +bool BKE_nurb_check_valid_u(const Nurb *nu) { - if (nu->pntsv <= 1) { - return false; - } - if (nu->type != CU_NURBS) { - return true; /* not a nurb, lets assume its valid */ - } + return !BKE_nurb_valid_message( + nu->pntsu, nu->orderu, nu->flagu, nu->type, nu->pntsv > 1, "U", nullptr, 0); +} - if (nu->pntsv < nu->orderv) { - return false; - } - if (((nu->flagv & CU_NURB_CYCLIC) == 0) && (nu->flagv & CU_NURB_BEZIER)) { - /* Bezier V Endpoints */ - if (nu->orderv == 4) { - if (nu->pntsv < 5) { - return false; /* bezier with 4 orderu needs 5 points */ - } - } - else { - if (nu->orderv != 3) { - return false; /* order must be 3 or 4 */ - } - } - } - return true; +bool BKE_nurb_check_valid_v(const Nurb *nu) +{ + return !BKE_nurb_valid_message( + nu->pntsv, nu->orderv, nu->flagv, nu->type, nu->pntsv > 1, "V", nullptr, 0); } bool BKE_nurb_check_valid_uv(const Nurb *nu) @@ -4767,10 +4771,6 @@ bool BKE_nurb_order_clamp_u(struct Nurb *nu) nu->orderu = max_ii(2, nu->pntsu); changed = true; } - if (((nu->flagu & CU_NURB_CYCLIC) == 0) && (nu->flagu & CU_NURB_BEZIER)) { - CLAMP(nu->orderu, 3, 4); - changed = true; - } return changed; } @@ -4781,10 +4781,6 @@ bool BKE_nurb_order_clamp_v(struct Nurb *nu) nu->orderv = max_ii(2, nu->pntsv); changed = true; } - if (((nu->flagv & CU_NURB_CYCLIC) == 0) && (nu->flagv & CU_NURB_BEZIER)) { - CLAMP(nu->orderv, 3, 4); - changed = true; - } return changed; } diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 1ffbed39216..59c2155255b 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -210,6 +210,8 @@ static NURBSpline::KnotsMode knots_mode_from_dna_nurb(const short flag) return NURBSpline::KnotsMode::EndPoint; case CU_NURB_BEZIER: return NURBSpline::KnotsMode::Bezier; + case CU_NURB_ENDPOINT | CU_NURB_BEZIER: + return NURBSpline::KnotsMode::EndPointBezier; default: return NURBSpline::KnotsMode::Normal; } diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index c2479e9e595..1e0fb874d11 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -142,15 +142,11 @@ bool NURBSpline::check_valid_size_and_order() const return false; } - if (!is_cyclic_ && this->knots_mode == KnotsMode::Bezier) { - if (order_ == 4) { - if (this->size() < 5) { - return false; - } - } - else if (order_ != 3) { + if (ELEM(this->knots_mode, KnotsMode::Bezier, KnotsMode::EndPointBezier)) { + if (this->knots_mode == KnotsMode::Bezier && this->size() <= order_) { return false; } + return (!is_cyclic_ || this->size() % (order_ - 1) == 0); } return true; @@ -166,12 +162,15 @@ void NURBSpline::calculate_knots() const { const KnotsMode mode = this->knots_mode; const int order = order_; - const bool is_bezier = mode == NURBSpline::KnotsMode::Bezier; - const bool is_end_point = mode == NURBSpline::KnotsMode::EndPoint; + const bool is_bezier = ELEM( + mode, NURBSpline::KnotsMode::Bezier, NURBSpline::KnotsMode::EndPointBezier); + const bool is_end_point = ELEM( + mode, NURBSpline::KnotsMode::EndPoint, NURBSpline::KnotsMode::EndPointBezier); /* Inner knots are always repeated once except on Bezier case. */ const int repeat_inner = is_bezier ? order - 1 : 1; /* How many times to repeat 0.0 at the beginning of knot. */ - const int head = is_end_point && !is_cyclic_ ? order : (is_bezier ? order / 2 : 1); + const int head = is_end_point ? (order - (is_cyclic_ ? 1 : 0)) : + (is_bezier ? min_ii(2, repeat_inner) : 1); /* Number of knots replicating widths of the starting knots. * Covers both Cyclic and EndPoint cases. */ const int tail = is_cyclic_ ? 2 * order - 1 : (is_end_point ? order : 0); @@ -182,7 +181,13 @@ void NURBSpline::calculate_knots() const int r = head; float current = 0.0f; - for (const int i : IndexRange(knots.size() - tail)) { + const int offset = is_end_point && is_cyclic_ ? 1 : 0; + if (offset) { + knots[0] = current; + current += 1.0f; + } + + for (const int i : IndexRange(offset, knots.size() - offset - tail)) { knots[i] = current; r--; if (r == 0) { diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index df4ae9ea2ce..accb3e521e1 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -42,6 +42,7 @@ #include "BKE_armature.h" #include "BKE_asset.h" #include "BKE_collection.h" +#include "BKE_curve.h" #include "BKE_deform.h" #include "BKE_fcurve.h" #include "BKE_fcurve_driver.h" @@ -2571,18 +2572,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } - /** - * Versioning code until next subversion bump goes here. - * - * \note Be sure to check when bumping the version: - * - "versioning_userdef.c", #blo_do_versions_userdef - * - "versioning_userdef.c", #do_versions_theme - * - * \note Keep this message at the bottom of the function. - */ - { - /* Keep this block, even when empty. */ - + if (!MAIN_VERSION_ATLEAST(bmain, 302, 6)) { LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { ToolSettings *ts = scene->toolsettings; if (ts->uv_relax_method == 0) { @@ -2600,5 +2590,51 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) tool_settings->snap_flag_node = tool_settings->snap_flag; tool_settings->snap_uv_flag |= tool_settings->snap_flag & SCE_SNAP; } + + /* Alter NURBS knot mode flags to fit new modes. */ + LISTBASE_FOREACH (Curve *, curve, &bmain->curves) { + LISTBASE_FOREACH (Nurb *, nurb, &curve->nurb) { + /* Previously other flags were ignored if CU_NURB_CYCLIC is set. */ + if (nurb->flagu & CU_NURB_CYCLIC) { + nurb->flagu = CU_NURB_CYCLIC; + } + /* CU_NURB_BEZIER and CU_NURB_ENDPOINT were ignored if combined. */ + else if (nurb->flagu & CU_NURB_BEZIER && nurb->flagu & CU_NURB_ENDPOINT) { + nurb->flagu &= ~(CU_NURB_BEZIER | CU_NURB_ENDPOINT); + BKE_nurb_knot_calc_u(nurb); + } + /* Bezier NURBS of order 3 were clamped to first control point. */ + else if (nurb->orderu == 3 && (nurb->flagu & CU_NURB_BEZIER)) { + nurb->flagu |= CU_NURB_ENDPOINT; + } + + /* Previously other flags were ignored if CU_NURB_CYCLIC is set. */ + if (nurb->flagv & CU_NURB_CYCLIC) { + nurb->flagv = CU_NURB_CYCLIC; + } + /* CU_NURB_BEZIER and CU_NURB_ENDPOINT were ignored if used together. */ + else if (nurb->flagv & CU_NURB_BEZIER && nurb->flagv & CU_NURB_ENDPOINT) { + nurb->flagv &= ~(CU_NURB_BEZIER | CU_NURB_ENDPOINT); + BKE_nurb_knot_calc_v(nurb); + } + /* Bezier NURBS of order 3 were clamped to first control point. */ + else if (nurb->orderv == 3 && (nurb->flagv & CU_NURB_BEZIER)) { + nurb->flagv |= CU_NURB_ENDPOINT; + } + } + } + } + + /** + * Versioning code until next subversion bump goes here. + * + * \note Be sure to check when bumping the version: + * - "versioning_userdef.c", #blo_do_versions_userdef + * - "versioning_userdef.c", #do_versions_theme + * + * \note Keep this message at the bottom of the function. + */ + { + /* Keep this block, even when empty. */ } } diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index ba9502b80bf..f992309ed00 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -4958,7 +4958,7 @@ bool ed_editnurb_spin( /* It is challenging to create a good approximation of a circle with uniform knots vector * (which is forced in Blender for cyclic NURBS curves). Here a NURBS circle is constructed * by connecting four Bezier arcs. */ - nu->flagv |= CU_NURB_CYCLIC | CU_NURB_BEZIER; + nu->flagv |= CU_NURB_CYCLIC | CU_NURB_BEZIER | CU_NURB_ENDPOINT; BKE_nurb_knot_calc_v(nu); } } diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c index d7201495f75..ba5a7409ba7 100644 --- a/source/blender/editors/curve/editcurve_add.c +++ b/source/blender/editors/curve/editcurve_add.c @@ -292,7 +292,7 @@ Nurb *ED_curve_add_nurbs_primitive( nu->pntsv = 1; nu->orderu = 3; nu->bp = (BPoint *)MEM_callocN(sizeof(BPoint) * nu->pntsu, "addNurbprim6"); - nu->flagu = CU_NURB_CYCLIC | CU_NURB_BEZIER; + nu->flagu = CU_NURB_CYCLIC | CU_NURB_BEZIER | CU_NURB_ENDPOINT; bp = nu->bp; for (a = 0; a < 8; a++) { @@ -407,7 +407,7 @@ Nurb *ED_curve_add_nurbs_primitive( mul_m4_v3(mat, bp->vec); bp++; } - nu->flagu = CU_NURB_BEZIER; + nu->flagu = CU_NURB_BEZIER | CU_NURB_ENDPOINT; BKE_nurb_knot_calc_u(nu); BLI_addtail(editnurb, nu); /* temporal for spin */ diff --git a/source/blender/makesrna/intern/rna_curve.c b/source/blender/makesrna/intern/rna_curve.c index 891de95a2a2..2c1ed483de6 100644 --- a/source/blender/makesrna/intern/rna_curve.c +++ b/source/blender/makesrna/intern/rna_curve.c @@ -562,6 +562,38 @@ static void rna_Curve_offset_set(PointerRNA *ptr, float value) cu->offset = 1.0f + value; } +static int rna_Nurb_valid_message_u_length(PointerRNA *ptr) +{ + char buff[64]; + Nurb *nu = (Nurb *)ptr->data; + BKE_nurb_valid_message( + nu->pntsu, nu->orderu, nu->flagu, nu->type, nu->pntsv > 1, "U", buff, sizeof(buff)); + return strlen(buff); +} + +static void rna_Nurb_valid_message_u(PointerRNA *ptr, char *value) +{ + Nurb *nu = (Nurb *)ptr->data; + BKE_nurb_valid_message( + nu->pntsu, nu->orderu, nu->flagu, nu->type, nu->pntsv > 1, "U", value, 64); +} + +static int rna_Nurb_valid_message_v_length(PointerRNA *ptr) +{ + char buff[64]; + Nurb *nu = (Nurb *)ptr->data; + BKE_nurb_valid_message( + nu->pntsv, nu->orderv, nu->flagv, nu->type, nu->pntsv > 1, "V", buff, sizeof(buff)); + return strlen(buff); +} + +static void rna_Nurb_valid_message_v(PointerRNA *ptr, char *value) +{ + Nurb *nu = (Nurb *)ptr->data; + BKE_nurb_valid_message( + nu->pntsv, nu->orderv, nu->flagv, nu->type, nu->pntsv > 1, "V", value, 64); +} + static int rna_Curve_body_length(PointerRNA *ptr); static void rna_Curve_body_get(PointerRNA *ptr, char *value) { @@ -1995,24 +2027,20 @@ static void rna_def_curve_nurb(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Cyclic V", "Make this surface a closed loop in the V direction"); RNA_def_property_update(prop, 0, "rna_Nurb_update_cyclic_v"); - /* NOTE: endpoint and bezier flags should never be on at the same time! */ prop = RNA_def_property(srna, "use_endpoint_u", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flagu", CU_NURB_ENDPOINT); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_ui_text( prop, "Endpoint U", - "Make this nurbs curve or surface meet the endpoints in the U direction " - "(Cyclic U must be disabled)"); + "Make this nurbs curve or surface meet the endpoints in the U direction"); RNA_def_property_update(prop, 0, "rna_Nurb_update_knot_u"); prop = RNA_def_property(srna, "use_endpoint_v", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flagv", CU_NURB_ENDPOINT); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_ui_text(prop, - "Endpoint V", - "Make this nurbs surface meet the endpoints in the V direction " - "(Cyclic V must be disabled)"); + RNA_def_property_ui_text( + prop, "Endpoint V", "Make this nurbs surface meet the endpoints in the V direction "); RNA_def_property_update(prop, 0, "rna_Nurb_update_knot_v"); prop = RNA_def_property(srna, "use_bezier_u", PROP_BOOLEAN, PROP_NONE); @@ -2021,19 +2049,28 @@ static void rna_def_curve_nurb(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Bezier U", - "Make this nurbs curve or surface act like a Bezier spline in the U direction " - "(Order U must be 3 or 4, Cyclic U must be disabled)"); + "Make this nurbs curve or surface act like a Bezier spline in the U direction"); RNA_def_property_update(prop, 0, "rna_Nurb_update_knot_u"); prop = RNA_def_property(srna, "use_bezier_v", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flagv", CU_NURB_BEZIER); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_ui_text(prop, - "Bezier V", - "Make this nurbs surface act like a Bezier spline in the V direction " - "(Order V must be 3 or 4, Cyclic V must be disabled)"); + RNA_def_property_ui_text( + prop, "Bezier V", "Make this nurbs surface act like a Bezier spline in the V direction"); RNA_def_property_update(prop, 0, "rna_Nurb_update_knot_v"); + prop = RNA_def_property(srna, "valid_message_u", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_string_funcs( + prop, "rna_Nurb_valid_message_u", "rna_Nurb_valid_message_u_length", NULL); + RNA_def_property_ui_text(prop, "Valid U", "Validation message for NURBS definition in U"); + + prop = RNA_def_property(srna, "valid_message_v", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_string_funcs( + prop, "rna_Nurb_valid_message_v", "rna_Nurb_valid_message_v_length", NULL); + RNA_def_property_ui_text(prop, "Valid V", "Validation message for NURBS definition in V"); + prop = RNA_def_property(srna, "use_smooth", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", CU_SMOOTH); RNA_def_property_ui_text(prop, "Smooth", "Smooth the normals of the surface or beveled curve"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc index 92c0cafa551..6928cde638c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -65,6 +65,7 @@ static void nurbs_to_bezier_assign(const Span<T> input, switch (knotsMode) { case NURBSpline::KnotsMode::Bezier: + case NURBSpline::KnotsMode::EndPointBezier: scale_input_assign<T>(input, 3, 1, r_output); break; case NURBSpline::KnotsMode::Normal: |