Page MenuHome

Revision of NURBS knot generation modes
ClosedPublic

Authored by Laurynas Duburas (laurynas) on Jan 21 2022, 2:48 PM.

Details

Summary

Patch enables all 8 combinations of Nurbs modes: Cyclic, Bezier and Endpoint.
Also removes restriction on Bezier Nurbs order. In order 2 doesn’t look well, but so does in other modes

Most significant changes still are mode combinations and new ones bring new meaning. Bellow is a scheme showing NURBS with same control points in a modes. On sides knot structures are visualized.
Diagonal lines show by what knots particular control point is affected. Blue and cyan arrows show to which cp’s curve is clamped and which knots create that clamp. Purple line demonstrates where cycle in knot begins.

  • First two cases a), b) g) has always existed, do not bring any changes nor require explanation.
  • c) normal Bezier. It kind of always existed, but curve with order 4 has leading control point and clamps only to second one. My explanation is that it is made to mimic pure Blender Bezier control point structure.


Assuming that is true same leading cp is added to order 3 case (such didn’t exist in current version) also to orders 5 and 6.

  • d) now if Bezier and Endpoint are checked together Blender ignores them both. Patch treats such mode as Bezier, but without leading control point from case c). This mode should look more intuitive for user than c).
  • e) and f) same as two above only cyclic. Again Endpoint mode removes “leading cp”, so curve is clamped to the first one. In e) curve still clamps as it is Bezier, but starting from second cp.
  • h) clamps curve to first control point from both ends giving corner effect. Maybe not that usefull, but result is pretty intuitive.

Diff Detail

Repository
rB Blender
Branch
nurbs_knot_modes (branched from master)
Build Status
Buildable 20367
Build 20367: arc lint + arc unit

Event Timeline

Laurynas Duburas (laurynas) requested review of this revision.Jan 21 2022, 2:48 PM
Laurynas Duburas (laurynas) created this revision.

I'm sure your explanation is good, but my brain still hurts when I look at these diagrams. I think I'll need to have a fresh mind when I try to understand those properly.

The code looks good, the patch description is helpful, and you have a blend file that we can convert into regression tests, so on those fronts we're good.

I've been relying on help for reviewing these changes to NURBS. @Piotr Makal (pmakal), would you be interested in looking at this one too? @RedMser (RedMser), I think this might be an area of interest for you too.
I'll add you both as reviewers in case. Thanks.

release/scripts/startup/bl_ui/properties_data_curve.py
307–308

These lines can be remove completely actually, active is the default state of a new layout.

  • Redundant code lines removed
Laurynas Duburas (laurynas) marked an inline comment as done.Feb 7 2022, 10:32 AM
Piotr Makal (pmakal) requested changes to this revision.EditedFeb 9 2022, 7:06 PM

I've tested the patch and it generally looks good (big thanks for attaching the graphic showing knot vectors related to different NURBS flags, it is really helpful!). One issue that I've found is with validation in BKE_nurb_check_valid_u, BKE_nurb_check_valid_v and check_valid_size_and_order functions. Specifically the test: ((nu->flagu & CU_NURB_CYCLIC) == 0) || nu->pntsu % (nu->orderu - 1) == 0 (and similar ones in aforementioned functions). I understand what is the purpose of it. The problem is that users don't and it's just too easy to hit (for Bezier+Cyclic NURBS curves) as shown below:


In my opinion this needs a warning on UI level, whenever the condition is false and a curve disappears.
After this change the patch is good to go in my opinion.

This revision now requires changes to proceed.Feb 9 2022, 7:06 PM

As a side note, since this patch extends capabilities of Bezier flag in NURBS curves, it would be good to mention in Blender's documentation that such curves have limited usage in animation. Specifically the tangent calculations, when animating object on a path, are off. This is not specific for this patch (existed before, when Bezier flag was on), but again something that should be mentioned in docs IMHO.

Above .gif shows animation on two curves: curve on top is 6-point NURBS with Bezier+Endpoint flags, curve below is 4-point NURBS with Endpoint flag

Thanks for the testing. Some warning in the UI could be helpful in the "Active Spline" panel.

As a side note, since this patch extends capabilities of Bezier flag in NURBS curves, it would be good to mention in Blender's documentation that such curves have limited usage in animation. Specifically the tangent calculations, when animating object on a path, are off. This is not specific for this patch (existed before, when Bezier flag was on), but again something that should be mentioned in docs IMHO.

Do you think that's just a bug though? Usually we avoid documenting bugs in the manual since they can easily change. Building a similar setup to that in geometry nodes does not have the same problem (it may use a different definition for tangents, maybe that's it).

@Hans Goudey (HooglyBoogly) Yeah I think it's related to the way tangents are computed. I guess Follow Path constraint I was using to test this has a different way of determining this than the one you used in your geometry node setup. You are right, this shouldn't be documented then, I'm backing off from my suggestion :D

RedMser (RedMser) requested changes to this revision.Feb 11 2022, 3:29 PM

This needs versioning code, so that old files appear exactly the same after this patch. For example:

  • Create a NURBS Sphere without this patch
  • Save as blend file
  • Load file with this patch applied
  • Re-evaluate the curve by toggling any of the "active spline" flags on and off, and it turns into a cylinder

@Piotr Makal (pmakal)

(big thanks for attaching the graphic showing knot vectors related to different NURBS flags, it is really helpful!)

Had some sort of inspiration there, thanks for feedback.

@Hans Goudey (HooglyBoogly)

Some warning in the UI could be helpful in the "Active Spline" panel.

I imagined it as a message "Add 2 more points for geometry to appear" in status bar, but don't have bContext there. Also now it looks more like status of particular struct Nurb instance rather than of an operator or some tool.
What if to add char *status field to Nurb and show that string in a status bar when user activates it? (not in this patch).

Regarding "Active Spline" not sure what message and where to put. For ex. "Number of control points must be divisible of (order - 1) for Cyclic Bezier's" is a bit cumbersome and does it go to Bezier or Cycle checkbox?

@RedMser (RedMser)

This needs versioning code

Thanks. How it is done? This problem should affect all order = 2 Bezier Nurbs as patch changes knots structure for these.

RedMser (RedMser) added a comment.EditedFeb 11 2022, 4:49 PM

@Laurynas Duburas (laurynas)

@RedMser (RedMser)
This needs versioning code

Thanks. How it is done? This problem should affect all order = 2 Bezier Nurbs as patch changes knots structure for these.

Check out versioning_300.c, at the very end of the file you will find a block labelled Versioning code until next subversion bump goes here.

In this block, you can add the versioning code. For now, that code will run every time a blend file is loaded (which makes it easy to test), but once committed to master, a maintainer will move it to only run for old blend file versions.

To loop over all curve datablocks in the blend file, you do something like LISTBASE_FOREACH (Curve *, curve, &bmain->curves) { /* ... */ }

Thanks for the comments everyone.

Regarding "Active Spline" not sure what message and where to put. For ex. "Number of control points must be divisible of (order - 1) for Cyclic Bezier's" is a bit cumbersome and does it go to Bezier or Cycle checkbox?

Yes, it's not beautiful, but I think it would do the trick. I think it could be added at the end of the panel, just added with the UI code.

What if to add char *status field to Nurb and show that string in a status bar when user activates it? (not in this patch).

I generally view adding runtime information to DNA structs as a last resort, since it significantly complicates the mental model of how information flows. So I'd prefer to not do that at this point.

  • Versioning code for knot calculation patch
  • Cyclic Beziers restriction label

For versioning one case will differ in a new version. Endpoint Beziers having count(control point) < order + 1 in old version are hidden as they don't pass BKE_nurb_check_valid_u (case i in scheme). Though if count() test passes both Endpoint and Bezier switches are ignored when come together and there is no reason to fail on Bezier condition in BKE_nurb_check_valid_u. In new version they will appear as NURBS with all switches off.
Scheme file is created with Blender 3.0.0 and is intended to show that geometry doesn't change in a patched version on knot recalculation ( except case i).


RedMser's case with sphere is j) in scheme.

  • BKE_nurb_knot_calc_v call to recalculate v dimension knots
Piotr Makal (pmakal) requested changes to this revision.Feb 14 2022, 11:19 PM

Constantly displaying long label in UI is not going to cut it. Here is my proposition shown in gif below:


I achieved this with following lines in python code:

if act_spline.type == 'NURBS':
    if act_spline.use_bezier_u and act_spline.use_cyclic_u:
        if act_spline.point_count_u % (act_spline.order_u - 1) != 0:
            layout.separator()
            col = layout.column(align=True)
            col.label(text="Warning:")
            col.label(text="Spline points / order mismatch")

Although probably a better idea instead of hardcoding act_spline.point_count_u % (act_spline.order_u - 1) would be to expose BKE_nurb_check_valid_u (plus _v variant) in RNA with new property + RNA_def_property_boolean_funcs and just check for bool value in python code. What do you think @Hans Goudey (HooglyBoogly) ?
Warning message is up for a debate, but it should be short.

This revision now requires changes to proceed.Feb 14 2022, 11:19 PM

Agreed with @Piotr Makal (pmakal) here. And I think everything proposed there is reasonable.
I'd suggest keeping the warning on one line though, and using icon=ERROR instead of the "Warning:" text. I'd also suggest "Spline point count and order mismatch", if that's not too long.

I went a bit differently:



In my opinion "point count and order mismatch" tells too little.
icon=INFO seems more appropriate here because 75% point extrusions for Cyclic Beziers will end up with the message and it is neither user's nor software's error. Just work in progress.

Regarding BKE_nurb_check_valid_u in Python. If we stay with "n more point(s) needed ..." message it will not be very useful. Unless to refactor it to return message instead of boolean.
For me it makes some sense because current message code duplicates logic from 'BKE_nurb_check_valid_u(_v)' functions.

  • Code duplication removed

The versioning changes LGTM. I'll let the others handle the UI changes.

If there's another revision, I also noticed a minor grammar fix you could include, see inline comment.

source/blender/blenloader/intern/versioning_300.c
2588 ↗(On Diff #48412)

where -> were (fix for all comments in this block here).

I've tested newest diff. It certainly does look cool on UI side. I appreciate you went extra mile to achieve this and I agree that this UI message is definitely more helpful for artists. But on the other side... Well on the other side is us - developers - and from this perspective I have doubts. As suggested before, it would be better for this tip/warning logic to be on the backend/C++ side of things. And this was suggested even before it got more complex. Now there is more calculations happening on Python side which is detached from the backend logic and the true reason why this tip/warning code exists in the first place (knot vector calculation). I understand that moving this logic to C++ and exposing it in Python is not that small amount of work especially if we consider that this is just an UI message. This is why I suggested something simpler in the first place. In the end we have to remember that this code may be untouched for a while and from my own personal experience it is hard to untangle stuff like this after years have passed and people who wrote it are no longer available.

With all that being said I will leave the final decision to @Hans Goudey (HooglyBoogly) as I'm not a full-time/long-time Blender developer, so this type of stuff (code maintenance) is out of my competence area. From the point of features/functionality this patch looks good and is ready to go in my opinion.

@Hans Goudey (HooglyBoogly), @Piotr Makal (pmakal)
For RNA property I wrote util function:

uint32_t BKE_nurb_valid_msg(int pnts,
                            short order,
                            short flag,
                            short type,
                            bool is_surf,
                            const char *dir,
                            char *message_dst,
                            size_t maxncpy)
{
  const char *msg_template = "";
  uint16_t points_needed = 0;

  if (pnts <= 1) {
    msg_template = "At least two points required.";
  }
  else if (type == CU_NURBS) {
    if (pnts < order) {
      msg_template = "Must have more control points than Order";
    }
    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 ? "%d more %s row(s) needed for Bezier" :
                                 "%d more point(s) needed for Bezier";
      }
    }
  }

  if (message_dst) {
    return BLI_snprintf(message_dst, maxncpy, msg_template, points_needed, dir);
  }
  return msg_template[0] ? 1 : 0;
}

Also refactored BKE_nurb_check_valid_ to use it to avoid logic duplication:

bool BKE_nurb_check_valid_u(const Nurb *nu)
{
  return !BKE_nurb_valid_msg(
      nu->pntsu, nu->orderu, nu->flagu, nu->type, nu->pntsv > 1, "U", NULL, 0);
}

bool BKE_nurb_check_valid_v(const Nurb *nu)
{
  return !BKE_nurb_valid_msg(
      nu->pntsv, nu->orderv, nu->flagv, nu->type, nu->pntsv > 1, "V", NULL, 0);
}

But now started to doubt if it is worth to twist things that way.

Thanks for working to get the error reporting right.

BKE_nurb_valid_msg looks good to me. Yes, it's also not beautiful, but I think de-duplicating the logic (and especially keeping it out of Python, since it's rather low-level) is worth it.
A couple small comments though, it should probably return bool instead of int32_t, and it should use TIP_ on the strings assigned to msg_template.
Looks like it could be a static function too (named nurb_valid_msg?), to avoid exposing the details of the error implementation to other areas.

release/scripts/startup/bl_ui/properties_data_curve.py
339–364

The style guide (https://wiki.blender.org/wiki/Style_Guide) mentioned the maximum line length for python code is 120 characters

source/blender/blenkernel/intern/curve.cc
1185–1186

1.0 -> 1.0f

source/blender/blenloader/intern/versioning_300.c
2563 ↗(On Diff #48577)

OB_CURVE has become OB_CURVES_LEGACY in master

2569 ↗(On Diff #48577)

met -> used

2574 ↗(On Diff #48577)

cp -> control point

No need to abbreviate in comments

2590 ↗(On Diff #48577)

nurb->flagv = nurb->flagv | -> nurb->flagv |=

  • NURBS validation message code moved to C++
  • Minor fixes
Laurynas Duburas (laurynas) marked 5 inline comments as done.Feb 23 2022, 9:30 PM
Laurynas Duburas (laurynas) added inline comments.
source/blender/blenkernel/intern/curve.cc
4711

Left same name for BKE_nurb_valid_msg as it is needed in rna_curve.c

source/blender/makesrna/intern/rna_curve.c
591

64 bothers me here, but have no ideas

Tested the diff. Looks good to me!
Indeed hard-coding number 64 is a little bit weird but it is local enough in code that I don't have big issues with that. Kinda related to this are macros in BLI_path_utils.h (like FILE_MAX) that are used for IO c string allocations. I don't know if there is something like that for c strings that are passed to UI, but I assume not.

This revision is now accepted and ready to land.Feb 26 2022, 8:17 PM
release/scripts/startup/bl_ui/properties_data_curve.py
20

One small cleanup request - import collections can be removed it seems

This looks good to me, just a note about translation inline.

By the way, I think one of the goals with T95355: New Curves data block can be exposing a "Manual" mode for knots, so they can be assigned directly by the Python API. There's a fair amount of work required to get there, but I think it's a good goal. It would mean turning these separate checkboxes into a single enum (like CurveEval does at the moment).

source/blender/blenkernel/intern/curve.cc
4731

Using TIP_ here won't work, because the translation system won't know about the strings above. It also acts as a marker for the system that retrieves strings for translation from the codebase.

So it should be like this: msg_template = TIP_("Must have more control points than Order");

  • TIP_ moved to string literals

I did a bit of small cleanup when committing, the biggest change is changing msg to message for the RNA API.

Thanks for your contribution! And thanks for the revew @Piotr Makal (pmakal) and @RedMser (RedMser).

Oh, also, I'm not sure if the set spline type node is handling this new combination correctly, that might be worth checking. I had to add the new mode to the switch there, since there was no default.