I've found a workaround with drivers:
Math
First, an array of $m$ segments with base segment length $l_s$ and an incremental per-segment scale of $s$ is $$\sum_{n=0}^{m-1}l_ss^n$$ We can move the $l_s$ to the outside and ask Wolfram Alpha to simplify the sum: $$l_s\left(\frac{s^m-1}{s-1} \right)$$ Then we can find $m$ for a target length $l_t$ as $$\log_s(\frac{l_t}{l_s}(s-1)+1)$$ And we can simplify the $\log_s$ by dividing it out: $$\frac{\log(\frac{l_t}{l_s}(s-1)+1)}{\log s}$$
So written in Python form, log(target_len/segment_len*(scale - 1) + 1)/log(scale)
and since that could be a fractional count, we can decide if we want to round up or down with ceil()
or floor()
.
Implementation
Now the tricky part: How to get the inputs to this equation with drivers:
Target Length
Finding target_len
aka $l_t$: This must be computed as the sum of the calculated lengths of the splines in the curve. It has to be split into two parts, one part is the "input variable" which accesses the data
path of the curve object:
then your expression needs sum([s.calc_length() for s in trunk.splines])
for target_len
(of course this assumes all scales applied)
Segment Length
This is ugly for multiple reasons:
- It depends on how you configured your curve modifier (not taken into account here) deform axis. This example assumes Z.
- It assumes your base object's origin is at the base of the mesh.
- It assumes all of your scales are applied.
- It can't use
dimensions.z
on the object because the dimensions will change with the application of array and cancel out all of the math, it must look into the mesh directly.
- Meshes don't know their own bounds, so we have to compute them from the vertices!
- Again we must split into two parts, accessing the
vertices
property of the mesh (note the type changed to mesh and the mesh selected, not the object):
Then the expression needs (max([v.co[2] for v in base_vertices])-min([v.co[2] for v in base_vertices]))
to find the Z extents of the mesh.
Scale
This one is easy! It's just the Z scale of the empty used in the "object offset" of the array modifier. (Again, Z because it's the deform axis of the curve modifier on the array object):
Expression
Finally your expression just combines the above as ceil(log(sum([s.calc_length() for s in trunk.splines])/(max([v.co[2] for v in base_vertices])-min([v.co[2] for v in base_vertices]))*(scale - 1) + 1)/log(scale))
(here with ceil()
because I want at least enough to cover).
Note that this equation can explode, because if $\sum s^n$ converges, then its limit sets the maximum possible length of the array. This can happen if scale is too small, the segments are too short, or the curve gets too long. If any of those happen, the array will quit applying (stuck at 1
) until you edit the driver and edit the expression field (just clicking on it and clicking out will fix it). It should be possible to fix this with even more math in the expression box, but that is left as an exercise for the reader.
Action Shot