Page MenuHome

Spline IK "Bone Original" Y-scale Causes Scaling
Closed, ResolvedPublicBUG

Description

System Information
Operating system: Linux-5.4.0-48-generic-x86_64-with-debian-bullseye-sid 64 Bits
Graphics card: GeForce GTX 1080/PCIe/SSE2 NVIDIA Corporation 4.5.0 NVIDIA 450.80.02

Blender Version
Broken: version: 2.91.0 Alpha, branch: master, commit date: 2020-10-14 13:24, hash: rBfecb276ef7b3
Worked: N/A

Short description of error
Spline IK using Bone Original Y-Scale method still causes scaling, even if the bones are perfectly aligned to the curve in edit mode. Even if the spline IK chain appears to be the same in pose mode as in edit mode, repeatedly CTRL-A and "Apply Pose as Rest Pose" will reveal the problem --- watch as the bones shrink, shrink, shrink!

Pose Position:


Rest Position:

I think this is caused by T77330, as scaling the bone in the beginning of the chain by reciprocal of its scale seems to right the scaling in the whole chain (although this isn't precise enough to be reliable in a rig).

Exact steps for others to reproduce the error


The attached .blend file has two spline IK chains, one that has the error in a visual way, and another that shows how pernicious the error is -- it looks perfect to the naked eye, but running the provided Python script will reveal that there is, in fact, a tiny bit of posing happening due to the constraint:

BoneChain.000
<Matrix 4x4 (0.9910,  0.0010,  0.0030, -0.0150)
                      (0.0090,  0.9970, -0.0050,  0.0200)
                      (0.0110, -0.0030,  0.9940,  0.0250)
                      (0.0000,  0.0000,  0.0000,  1.0000)>
location <Vector (-0.0155, 0.0200, 0.0251)>
0.33102211487637834
Scale <Vector (0.9914, 0.9971, 0.9940)>

These sorts of tiny pose-changes are especially dangerous, since riggers are unaware of them unless they have a tool for checking, or have had enough (bad) experience with Spline IK to suspect it!

Event Timeline

Philipp Oeser (lichtwerk) changed the task status from Needs Triage to Confirmed.Oct 14 2020, 5:46 PM
Philipp Oeser (lichtwerk) changed the subtype of this task from "Report" to "Bug".

Can confirm the shrinking, not familiar enough with that part of the code to tell if this has the same roots as T77330.
Will dare classifying as a bug.

@Joseph Brandenburg (TheAngerSpecialist) could you test with older versions of Blender to see whether this was recently introduced, or an older issue?

The script contains r.angle*57.2958; if you want to show small detail in errors, be sure to use math.degrees() instead. Not only does it convey what the code is supposed to be doing, but it's also more precise.

I can't reproduce the issue, by the way. When I run the script it outputs this:

BoneChain.000
<Matrix 4x4 (1.0000, -0.0150, -0.0160, 0.0000)
            (0.0150,  1.0000, -0.0030, 0.0290)
            (0.0160,  0.0030,  1.0000, 0.0310)
            (0.0000,  0.0000,  0.0000, 1.0000)>
location <Vector (0.0001, 0.0293, 0.0308)>
1.2592576447263326
Scale <Vector (1.0001, 1.0001, 1.0002)>

Still, the scale is not a perfect 1.000000000000000000, but it's much closer than the reported values. This was with the master branch @ rBd6fd03616ec2fab8

Ah, so first -- the script only tests the selected pose bones. Second -- the real danger is in the cumulative effect of the scaling. If the first bone has a little scale, and the second, and the third.... who knows what the last bone will have! And finally, the best way to see the result of the bug is to repeatedly CTRL-A and "Apply Pose as Rest Pose" -- you can see it shrink, shrink, shrink. I was able to confirm it in 2.80. "Bone Original" scaling didn't exist prior to that, but in 2.79, if you disable "Y-Stretch" then there is a similar problem.

Now I forgot to turn off "Rest Position" for one of the chains, if you disable it then that one has the problem, too. Switching the top chain from Pose to Rest position will also show the issue more clearly.

Why this is a problem: Often, when setting up a rig, the position and rotation of some bones is set by some constraints, and must be "settled"... if the rigger accidentally applies the pose on the whole rig, instead of only the selected, then the rig may break. Moreover-- it's just wrong for a rig to have posing when the rigger doesn't want it! Most of the constraints are rock-solid, including normal IK. Spline IK is the exception.

Thanks for this bit:

The script contains r.angle*57.2958; if you want to show small detail in errors, be sure to use math.degrees() instead. Not only does it convey what the code is supposed to be doing, but it's also more precise.

I didn't know about this! Although radians would work there, just as well. I only converted to degrees so that I could understand the magnitude of the problem. The check was done on the radian value.

So: I have some thoughts on why this happens, and it is actually related to the other bug I reported with this one, T81707

The problem, it seems, is that when Spline IK evaluation looks to get the next point, it travels the correct distance along the curve's 2D U-dimension. (Look at where_on_path around line 300 of blenkernel/intern/armature_update.c -- see how the two points are discovered). However, this is almost certainly a shorter distance in World-Space -- it will only be the same if the curve is completely straight (as the bone is). Any curves in the path between the head and tail point will increase the U-distance (U as in UV, U being the dimension of path length in this case) along the curve, but the world-space distance between the points remains the same, because it is always a straight line. Because of this, the bone will get the wrong point on the curve, and it will *always* be shorter than it should be, unless the curve is a straight line (which is pretty useless, anyways).

This is related to the other bug, as I mentioned, and it isn't obvious why. But the reason is that the above is assumed to give the correct points, so the scaling applied to the bone is incorrect. Since the joints are never snapped onto the curve in world-space, the wrong assumption leads to floating joints. So I think that fixing this bug will likely fix the other one.

Anyhow, here's two possibilities for fixing it:

  • Adjust the scale of this bit of code float pointEnd = pointStart + curveLen * baseScale * state->curve_scale; with the difference between the world-space and curve u-space distance, or
  • Use a different method entirely to get the next point in the curve: Intersect the curve with a sphere centered at the bone's head with radius of the bone's length. Get a list of points, and compare the location in u-space along the curve for each of them. Choose the first point that is greater than the starting point in u-space. Now, because these points were found by intersecting a sphere with the path, they are in world-space, and they have the correct distance. The u-space of the head-point must be less than the tail-point, because it comes earlier on the path, and the next point in u-space must be chosen in case a path curves in on itself (this is why there can be multiple intersections with the sphere).

The first method probably isn't accurate enough for me to like it. The second method seems more costly in terms of performance, and probably needs to solve from the base all the way to the end, every time, in that order. I don't think it can work unless it goes in order.

EDIT: There is a third method I forgot to include because it seems silly to me, but... it's also possible to just go down the curve and check if the WS distance is correct, to find the right point by brute-force. I've tested a similar problem-solving approach in the past using a bisection method... but while I think this would work, it seems very hacky to me!

OK, turns out it was just calculating the distances incorrectly, no need to change the algorithm in any major way.

Capturing a realization from talking on blender.chat for reference: upping the value of curve resolution preview seems to fix both the scaling down with apply pose in the top chain and the error reports in the bottom one.
Is there a way to use 'limit curve' without getting too slow/recursing infinitely (or using a higher resolution independent of the settings?)
Oddly improving this might make https://developer.blender.org/T77330 seem worse, if the bones are snapping to a high resolution curve, but the render preview is much lower.

Further testing (not with the patch):
Lowering the curve resolution to 1 makes the behavior much, much worse. it does look very fishy / that it messes up the moment we reach the first 'segment' in the low resolution curve, and compounds thereafter.

In the final (selected) armature/curve pair, the resolution is 64. This brings up the question: which 'curve' should the chain constrain to (all contain issues):

  • viewport preview resolution (if curve is rendered, there'll be a potential mismatch on render)
  • matches curve resolution in viewport/render (jump in pose at rendertime)
  • render resolution ( doesn't appear to match in viewport)
  • limit curve (doesn't match either viewport or render)
  • setable in constraint (extra complexity)

Yeah, the curve resolution has a big effect. And worse, I don't think that the curves are being evaluated the same way that the mesh is, hence T81707 .

This particular bug is caused by an incorrect calculation that's fed into an incorrect solution to the problem. So, the Spline IK evaluation starts out by recording data about the spline IK tree -- it records the World-Space length of the bone chain and the factor along this distance for each joint in the chain (as a percentage of the total distance). Then it compares this distance to the length of the curve by dividing the chain's total length with the curve's (this value is named curve_scale in the master). Now, this curve_scale parameter is used to scale the values of each joint that were calculated earlier.
There are two problems with this method - first, the total length of the curve and the total length of the bones are almost always different when the bones are fit to the curve. The chain of bones is usually lower-resolution than the curve, and the curve takes a longer path from one point to another. The bones are straight, but the curve is curved. You can see that the curve will be longer if you think about the turns of a road versus the straight path to your destination -- the bone takes the straight path, skipping the curvy bits.
So, to begin with, the curve_scale value is wrong, it will always scale the bones down below their rest-pose length. It *should* be comparing the rest-pose length of the bones to the length they would have if they were fit to the curve. Anyways, even after correcting for this, there's the problem of snapping to a distance along the curve- in a curve that is very curvy, e.g. a harsh zig-zag, the bones may still be the wrong length.
Here's an extreme example, using Even Divisions and Fit To Curve:


Here, the green represents the distance that Spline IK is traveling along the curve. You can see that the green section actually is even for each bone. Unfortunately, because the bones are straight, they don't look even at all, since they are snapping to even divisions of the curve, not the chain.

As it pertains to Y Original scale mode and None, there's a similar problem. The algorithm essentially calculates the Fit To Curve factor for a joint, then scales the factor along the curve down using curve_scale. That won't guarentee that the bones aren't being scaled, since that will just pick a different, wrong part of the path.
I haven't solved this corner case yet, but I have written a bit of code that tries to correct it. I think the right solution to this problem is a search that finds the right point by finding points on the curve that give the bone the right scale, and then choosing the one that is nearest. Unfortunately, this is a difficult solution that seems inefficient.
I've gone right back around to thinking the best solution is to evaluate the curve as a mesh and do sphere-intersection to get the points that are the right distance from the head to the tail of the bone. This would solve both this and T81707, make the constraint more predictable, and make the code easier to write. I tried to do this early on but couldn't figure out how to get the mesh evaluation.

Oh, as for this:

which 'curve' should the chain constrain to

Limit curve has the problem that it is calculated iteratively, if I recall correctly. So it seems to have a resolution no matter what.
I think the constraint should do what it's already doing - use the viewport resolution in the viewport and the render resolution in the render. Render Resolution defaults to 0, which is "automatic", anyways. If the user gets confused about a mismatch, it's only because they've specifically set it to be mismatched!


Don't know why the image didn't show in my previous post!

Ah, you can see what I described there in two different ways. The very-low-resolution curve is shorter in World-Space than the bones, so they end up longer than the curve with Y-Original. The opposite is true in the high-resolution curve.

So it seems the algorithm should subdivide the curve into straight lengths based on the length of the bones, rather than taking the fit to curve factor and scaling? I'm a bit out of my depth but I can see how the latter will lead to the issues we're seeing - it seems the bone original mode needs to have a different algorithm or you'll see these issues - curve resolution is a compounding factor (In the example I uploaded, I took the 'working' curve at a resolution of 64, and subdivided it a looot of time and made it gnarly- now even with the the high resolution, apply pose as rest pose results in the recursive shrinking)

OK, I spent some time coding and have a couple of gifs showing the corner case that is... actually a big problem. Take a look:
Master Branch

Exact Length Preserved (haven't pushed these changes yet, so the current patch just fails here):

So, as you can see, n the master build, the bones move smoothly, but their length is badly affected by the extreme deformation in the curve.
In the second image, the bones preserve their length -- I hacked together a very crude function that checks every point in the curve and picks the one with the right length. Please ignore the jumping around at the end, that is probably just an artifact of the imprecise hack I wrote to do it.

Edit: OOPS, I could have sworn I was on the patch page when I was writing this! I meant to put this over there.
Anyways, it turns out, if the bones preserve their original length exactly, this can lead to weird snapping which seems altogether undesirable for animation.

I would appreciate a second pair of eyes to look at this, since I don't know what the desired behavior is in extreme cases like this. Perhaps it's safe to enforce the correct length and users will just have to use a higher-resolution bone-chain to compensate.

@bassam kurdali (bassamk) I don't want to subdivide the curve, that sounds like a mathy nightmare. What I'd like is a mesh representation of the curve that I can do sphere-intersections on, instead of having to ask the curve "tell me the world-space location at 65% of your length" (or whatever the percentage happens to be). The workflow is kinda backwards. I don't know the correct point along the curve's length, so I have to keep asking and checking, asking and checking. It would be better by far to just do the very simple geometry on a mesh-approximation of the curve -- this would also be predictable in regards to curve resolution, since the calculation would be performed on exactly the same data. This would solve both of the Spline IK bugs I reported last month, too. I just need help figuring out how to do it. Trying to go through the curves' Runtime to get a mesh crashes blender, so I think I probably have to create the mesh first with some unknown function. Maybe I need an evaluated depsgraph... not sure.

In the second image, the bones preserve their length -- I hacked together a very crude function that checks every point in the curve and picks the one with the right length.

Are you talking about control points of the curve? Because mathematically speaking there are infinitely many points on the curve.

I don't want to subdivide the curve, that sounds like a mathy nightmare.

Subdividing a Bézier curve isn't that hard. See A Primer on Bézier Curves: Splitting Curves for the math.

What I'd like is a mesh representation of the curve that I can do sphere-intersections on, instead of having to ask the curve "tell me the world-space location at 65% of your length" (or whatever the percentage happens to be).

That seems to be harder indeed (see Primer: Arc Length), but can be approximated well.

I'm not sure whether a mesh representation is the best way to go, but it looks like at least some discretisation into line segments would work well.

Are you talking about control points of the curve? Because mathematically speaking there are infinitely many points on the curve.

Yes, there are infinitely many points! So I wrote a function that attempts to check each one, very crudely - it just brute-force searches from the start of the curve to the end of the curve with a very small step value. It was just a proof of concept to see what would happen, and the jumpy motion in this case is pretty ugly and undesirable.

To clarify the mesh representation thing: Don't we already have one, the mesh that is displayed in the viewport? Can't we reuse that? If it's possible to access this mesh I can hack together a proof of concept for what I'm talking about.

ask the curve "tell me the world-space location at 65% of your length" (or whatever the percentage happens to be).

This is what Spline IK is currently doing. where_on_path in path_anim.c is the function that does this -- give a curve-time between 0 and 1, inclusive, and it tells you the location and normal at that point, along with the radius. Although, it produces different points than the mesh the user sees in the viewport. Spline IK gets the points to snap the bones to with this function. And that's why it isn't always reliable! It would be better to specify a length (as in the radius of a sphere) and get the first point that is at that distance (which would be easy to find by intersecting a sphere with a mesh).

I'm starting to wonder if we need to add a new option to Spline IK Y-Scale mode -- "Bone Original (Smooth)" which would preserve the legacy behavior but correct the calculation of the curve_scale parameter (which is the primary cause of this bug), and a "Bone Original (Strict)" that would guarantee the bones keep their correct length (original length multiplied by the y-scale), even if it produces choppy motion. The second would be a new feature, but it's what I thought Bone Original would do until I investigated it, and I imagine it could be useful.... but fixing the behavior of the existing Bone Original is good enough for me, and it's already done. The second idea can be left for later.

So here's what I am proposing: I will remove the code in my patch that adjusts each individual bone's length, but keep the code that fixes the calculation of the curve_scale parameter. This will preserve the intended behavior and make the existing feature usable. It would be a compromise between perfectly preserving the correct length of each bone and avoiding choppy motion when scaling the bone to move the spline IK chain along the curve. It *will* result in a change in existing .blend files that use the feature, but I don't think the feature was actually usable before the fix, so I'm not sure what that will entail as far as introducing the change and avoiding compatibility issues.

Thanks again!

ask the curve "tell me the world-space location at 65% of your length" (or whatever the percentage happens to be).

This is what Spline IK is currently doing. where_on_path in path_anim.c is the function that does this -- give a curve-time between 0 and 1, inclusive, and it tells you the location and normal at that point, along with the radius.

There is a difference between t=0.65 and "65% of your length". As the curve accelerates and decelerates, t doesn't linearly map to the distance over the curve (see Primer: Tracing).

Although, it produces different points than the mesh the user sees in the viewport. Spline IK gets the points to snap the bones to with this function. And that's why it isn't always reliable! It would be better to specify a length (as in the radius of a sphere) and get the first point that is at that distance (which would be easy to find by intersecting a sphere with a mesh).

The problem is that a sphere-mesh intersection test can result in more than one point. You'd have to find the one with the lowest t-value, which requires some extra math again. I do like the approach of a sphere intersection check, but my gut feeling says it should be possible to implement this without doing a conversion to mesh first. I think the curve-curve intersection approach might work for curve-sphere intersections as well. Just to get some confirmation we're on the right path here, I've asked the author of A Primer on Bézier Curves, he's a good friend of mine ;)

I'm starting to wonder if we need to add a new option to Spline IK Y-Scale mode -- "Bone Original (Smooth)" which would preserve the legacy behavior but correct the calculation of the curve_scale parameter (which is the primary cause of this bug), and a "Bone Original (Strict)" that would guarantee the bones keep their correct length (original length multiplied by the y-scale), even if it produces choppy motion. The second would be a new feature, but it's what I thought Bone Original would do until I investigated it, and I imagine it could be useful.... but fixing the behavior of the existing Bone Original is good enough for me, and it's already done. The second idea can be left for later.

I'm not that familiar with how the Spline IK behaves with the various options it has, so I don't really grasp the nuance of what you're saying.

What would you say to implementing a robust intersection test first, so that at least we know that the placements of the bones along the curve are correct? Then we can plug in bones of different lengths any way we want, and get reliable results. If there are still issues after that, we can tackle those separately.

t doesn't linearly map to the distance over the curve

Ah, I think I knew that... but either Spline IK assumes it is linear, or where_on_path corrects for this, because Even Divisions uses even steps:

	    float segmentLen = (1.0f / (float)segcount);
	    ikData->points[0] = 1.0f;
	    /* perform binding of the joints to parametric positions along the curve based
	     * proportion of the total length that each bone occupies
	     */
	    for (int i = 0; i < segcount; i++) {
	      /* 'head' joints, traveling towards the root of the chain
	      if ((ikData->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totLength == 0.0f)) {
	        ikData->points[i + 1] = ikData->points[i] - segmentLen;

So for four bones you'd have [1.0, 0.75, 0.5, 0.25] curve times.

The problem is that a sphere-mesh intersection test can result in more than one point. You'd have to find the one with the lowest t-value, which requires some extra math again.

I've thought about this, too -- Blender's sphere intersection function takes a segment, a point, and a radius. The point is the head of the bone, and the radius is its length. And for the segment, we need a segment on the mesh representation of the curve. If we keep track of which segment the last bone fell on, we can start at that segment and test each segment in order until we get an intersection. We're almost guaranteed to get a single intersection, and then we can move on, knowing that the first correct solution was chosen (because the segments on the mesh are always in order). We can do the simple math for determining which point to use if two intersections are found. After all, the exact t value isn't needed. All that we need to know is which is further down the curve (since the bone must be in the middle of a segment to intersect with it twice -- the first intersection is wrong! This is a simple comparison between the distance-squared (or distance if we feel like wasting CPU cycles :P ). Here's a visual example:

At any rate, doing a sphere intersection test to get the points would also change the behavior of Fit-To-Curve, since it would also resolve T81707 . Maybe we don't want to do that? We could also keep the where_on_path method for finding points, but I think it's a bad idea to use different methods depending on the Y-Scale Mode.

As for the curve-curve intersection test, I'm worried that this will be difficult to do in 3D. I think I would have to rotate a circular curve around a central point until I hit something, but I might miss the point I'm looking for if I rotate too far in one step. Anyhow, thank you for pointing me to the Bezier Primer... I've been meaning to read it but I can't really take the time to understand it until the weekend.

What would you say to implementing a robust intersection test first, so that at least we know that the placements of the bones along the curve are correct? Then we can plug in bones of different lengths any way we want, and get reliable results. If there are still issues after that, we can tackle those separately.

Well, the question I was asking here is: would that be solving a different bug? Is the intended behavior of Bone Original Y-scale mode to make a Spline IK smoothly stretch along the length of a curve or to preserve each bone's scale exactly? It isn't clear to me after playing with them. Lets say we have a curve that is much higher resolution than the bones and contains big curves or jagged shapes. So, in the first case, the exact scale of a bone may not be preserved in Spline IK, but the whole chain will move smoothly through the curve when the first bone is scaled up and down. In the second case, the bones' scale will be preserved exactly, but there may be jagged jumps in the bones' position as they fit themselves to the jagged shape of the curve. (See the .gifs from a few comments prior). The current behavior in the master branch is this first case, but a wrong calculation renders the feature useless in a production setting. I've already corrected this calculation, but small inconsistencies remain. Correcting these inconsistencies would give us the second case -- exact preservation of each bones' Y-scale, along with the potentially jagged and abrupt motion. I don't know if that should be the goal for fixing this bug, or if the first case should be the goal and the second case should be left for another patch. Of course, we could always tell people "use a higher-resolution bone chain to improve the smoothness of animation".

I'm a rigger, so I tend to like the second case because it's reliable and it means that things won't move when I leave Edit Mode for Pose Mode. I could write a paragraph going into the advantages and disadvantages of each.

What would you say to implementing a robust intersection test first?

🤔 I'm not sure it's necessary to do this just yet. My current brute-force method for fitting the bones is a close approximation of how that would look to the end-user (I haven't updated the patch with this yet). The goal is to preserve the scale of the bones. Intersection or brute-force is an implementation detail, IMO. What's most important is choosing the behavior we want to ship with Blender! I do think, however, that the sphere-intersection method will be the easiest to program, the easiest to read, and the easiest for the end user to interact with (because it would also solve the "floating points" bug).

Thanks, I hope this isn't too long-winded of a response.

The curve can intersect the bone-sphere an arbitrary number of times:

For the rest, I'll have to invest some more time to study Spline IK and see how it's supposed to work.

Yes, of course! But you can only test one *segment* of the curve at once. So if you test the segments in order, you'll get the right point first and quit testing before you get the wrong ones. You go down the curve testing for the intersection with straight line-segments that are part of the mesh parameterize-ation of the curve. If you want, I can make a Python mockup during the weekend.

Thanks! I'm working on some visual presentations of the two behaviors I described above. Will post them hear and in blender.chat when they're done.

APROXIMATE Method

Smooth motion, but bones' scale is incorrect at times (sharp corners).

EXACT method

Sometime the motion is jagged and abrupt, but the bones' scale is reliable.

I've also posted this in blender.chat:
https://blender.chat/channel/animation-module?msg=CxttuAdDmLMWgxzK2

For completeness, my chat with the Primer author:

Your Approximate method looks pretty good though ;-)

OK, we talked about this in the animation & rigging meeting -- Dr. Stüvel asked me to ping @Brecht Van Lommel (brecht) for feedback about the desired behavior of this constraint.

So, to summarize for you, so you don't have to go back and read all of that ☝️ : The current master Blender has a bug - Spline IK set to "Bone Orignal" Y scale mode will not stay in place when switching from Edit Mode to Pose Mode, and if one applies the constraint, it will shrink. Keep applying the constraint and it keeps shrinking. Moreover, because of the way locations on the spline are chosen for snapping, it is possible for bones to have a different scale than expected if e.g. going around a sharp turn in the spline. (See example a). The manual implies that the original Y-axis scale should be preserved (See example b). The current behavior creates smooth motion, which is nice for animators, but it's less predictable for the rigger and is a more difficult behavior to preserve without also preserving this bug! On the other hand, exactly preserving the Y scale may introduce an annoying jagged motion in cases where the number of bones in the chain is lower than the number of segments in the evaluated spline (the bone-chain is lower resolution than the spline, also note that the spline is not evaluated exactly as it is in the viewport, see T81707). Please see the videos above for a visual example of the motion.

Dr. Stüvel has pointed me to a way of solving the exact solution, that would preserve the scale of the bones exactly. Using a hacked-together brute-force solution I have confirmed that this will solve the bug. On the other hand, we could aim to preserve the behavior in the current master. I haven't figured out a general solution to this problem yet, but I'm part of the way there.

According to the wiki, you (Brecht) are the 'owner' of IK. So I'm asking you what the desired behavior is, or if we should solve the problem by making both behaviors available to riggers.
(Also, "None" Y-scaling mode is just "Bone-Original" but without the Y-scale channel multiplication. So the "None" scale mode has the same bugs and will be fixed with the same fixes. So please consider the behavior of "None" Y-scaling mode, too).


Example A


Example B

Alexander Gavrilov has a Rigify rig that uses the "Bone-Original" scaling mode to control a tentacle rig, but he avoids the bug by using a spline that is perfectly straight in its rest pose. Other than my own rigs, I haven't seen this feature used much elsewhere... and I can't even use it in my own rigs because of this bug 😭 !

I'm not the maintainer of Spline IK, only the inverse kinematics solver.

I would expect constraints to be primarily designed to give good results in animation, not giving discontinuous results that are acceptable for applying to the rest pose. I would think that's somewhat of a corner case, and so any option for that should be disabled by default. If having such an option is important or not I'll leave up to riggers to figure out.

Maybe in theory it could automatically find a scale preserving fit based on the rest pose of both the armature and curve, and then still smoothly vary in animation. But the implementation of that may not be practical.

Thanks for your feedback! I'll spend some time this weekend enumerating the pro's and con's of the two possible solutions. In the meantime, I'll investigate a way of preserving the current behavior and solving this bug.

So, currently, Spline IK calculates the *percentage* along the total World-Space length of the bone chain for each joint. This will be from 0-100% regardless of the percentage of the joints *along the curve's length*. I think it should be possible to store these values relative to the curve instead, so that the algorithm doesn't have to adjust the values during evaluation. This is the solution I will try for, for now!