Page MenuHome

Add rotation value to array modifier
Needs ReviewPublic

Authored by Henrik Dick (weasel) on Apr 24 2020, 8:00 PM.
Tokens
"Like" token, awarded by Fracture128."Love" token, awarded by andruxa696."Like" token, awarded by Kdaf."Love" token, awarded by daniellapalme."Love" token, awarded by eversimo."Love" token, awarded by ogierm."Love" token, awarded by ate1."Love" token, awarded by Jaydead."The World Burns" token, awarded by Frozen_Death_Knight."Love" token, awarded by brilliant_ape."Like" token, awarded by YAFU."Love" token, awarded by Snaki94."Love" token, awarded by Zuorion."Love" token, awarded by Schiette."Love" token, awarded by MrJomo."Love" token, awarded by HooglyBoogly."Love" token, awarded by jorsh."Love" token, awarded by duarteframos.

Details

Reviewers
None
Group Reviewers
Modifiers
Summary

This is a very simple implementation of a rotation input for the array modifier.
It has options for selecting the rotation mode and an option for using the rotation as the end point rotation,
so you can make it 360° and just increase the count and it will stay a full circle.

It also avoids a performance penalty like the one in T76102 that you usually get with an object offset.

Note it is different from using object offset as the rotation is always done around the object origin before translation.
That way you can have a linear array with differently rotated objects, or a circular array with not rotated objects (can be seen in the demo file and in some images in the comments).

How to use it?

The Rotation subpanel in the Array Modifier contains 3 properties.

  1. Rotation (in the mode choosen by 2.)
  2. Rotation Mode (exactly like in the object transform panel)
  3. Fill Angle (disabled for quaternions)

If Fill Angle is turned off, then the rotation is an offset between the elements. The next object is always rotated from the prior by the angle chosen.
If Fill Angle is turned on, then the chosen rotation is the end rotation (total rotation of the last element). This option will try to be smart about full rotations to avoid duplicates.
The rotation is applied around the origin of the duplicated object. That means that without translation (offset subanels disabled, no object offset) the object is always rotated around the object origin. If a translation offset is in place, it will always rotate the object around their new moved origin.

Here are some examples:
Using it with rotation only for circular arrays


Using it with translation and rotation (demo to grasp the concept)

The current UI looks like this:

Here is a demo file which shows a few things where this is useful:

Here is also a demo file where I played around with it for testing:

Discussion

  • Why could this not work with a duplicator system? Why the array modifier? Instancing is faster!

While it is true that instancing is faster and there are better ways to duplicate your geometry, a better duplicator system would not serve the niche of this modifier. For example rotational symmetric boolean cutouts or rotational symmetry with a continuous surface and subsurface following.

  • Why is object offset not good enough? It is relativly fast to setup and addons can speed the process up.

There are multiple reasons why people (myself included) don't like using the object offset for circular arrays.

  1. A lot of setup. You need to parent additional emptys to the object. Usually one, but if you want a different rotation center even two. This only really works well/fast if you are at (0,0,0) and have no rotation and scale.
  2. Hard to control as you only choose the rotation of one step and you need to setup drivers to make it work with multiple instance counts. Also translation and rotation at the same time is really non intuitive. Also you need to go outside the array modifier to another object to change the rotation and then go back to change the count. A lot of people use an external calculator to get the offset angle (not everyone knows about the freedom in the value text input). This is really important as changing the count is something that is done quite frequently.
  3. Unnecessary clutter as there are more objects to name and manage visibility. There is always at least one more object needed per array. That can accumulate very fast. Especially if you have objects with multiple arrays for example to create easy icosaeder symmetry.
  4. Less performance if you move your object, as the modifier stack needs to be reevaluated due to the dependencies. (This can become really bad for either high poly objects, which shouldn't use array anyway, or just large modifier stacks)
  • Further Discussion

One thing has been left out of the Manual part, and that is the bit about how it tries to be smart about avoiding duplicates. I would like to get a discussion going on that. Currently for Quaternion the fill angle option is completely disabled as it seems not very useful since you can never do a full rotation. Quaternion should really be used for motion trails or similar effects. For Euler Angle, Fill Angle makes the specified rotation the rotation of the last element. In the case that the rotation is a full rotation and the last element would be in the place of the first one, it acts as if there would be one more element. This results in the expected behaviour, where choosing a count of 8 and a angle of 360° will result in 8 equally spaced meshes. For Axis Angle, I decided to make that the default behaviour to avoid the jump when it hits 360°. Thats what made the gif animation at the top possible.
One could reduce the smartness if unwanted by introducing yet another option for "omit last", that would switch between the described behaviours.

Diff Detail

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

Event Timeline

Henrik Dick (weasel) requested review of this revision.Apr 24 2020, 8:00 PM
Henrik Dick (weasel) created this revision.

The UI of the modifier is currently lacking and I'm thinking of redesigning it completely to be more clean.

Agreed! But don't bother, that's already done in D7498:


It's very tall there but normally most of the subpanels are closed. Rotation could be another subpanel there.

Actually looking at it now it looks like object offset doesn't need to be a subpanel.

Even though I added this like this, I dont think this is the best approach for rotational symetry in general. I would rather like to have rotational symmetry in the mirror modifier with clipping and bisect to the symmetry planes etc (mirror modifier would then by the symmetry modifier), but I can still add that even if the array modifier can do similar stuff already right?

For this patch, should I add a checkbox to activate the rotation panel like for the translation for consistency? Also can I remove the checkbox for object offset (and do versioning code for that).? It doesn't seem to be neccesary as it is simply disabled when no object is selected.

  • added rotation checkbox for consistency with the other offset transforms
  • changed the layout a little bit so its at least fine until D7498 updates it

I looked into the patch a bit more, and I'm not sure the parameters you've exposed are the most intuitive way to control this behavior. Here's an example with 15 degrees of rotation.


It's not so clear what the 15 degrees is relative too. And the size of the circle is controlled by a combination of the offset and the angle. Or with "fill angle," just the offset. I think it's more intuitive if the user input a center to rotate around as well as the rotation.

It also looks like the each cube isn't exactly aligned at the ends or pointing into the center.

For this patch, should I add a checkbox to activate the rotation panel like for the translation for consistency? Also can I remove the checkbox for object offset (and do versioning code for that).? It doesn't seem to be neccesary as it is simply disabled when no object is selected.

It's not yet possible to add subpanels to modifier layouts, but I agree that makes sense.

The checkbox is helpful because it can be animated, to turn on and off the object offset. It's also useful to give an indication of what information matters and what doesn't.

@Hans Goudey (HooglyBoogly) I get your point. It would be quite simple to make a circle if you would just have to turn up the rotation and rotation origin value. But let me discuss that a little further.

Making a circle array is very simple already with this patch, you just have to offset the cube in editmode, disable relative offset (since you only want rotation) and set rotation to 360° on one axis with fill angle enabled. In my opinion using rotation and offset together is not very desirable anyway as you dont get easy control over the radius of the resulting circle (just as you described). That way of doing it (editmode offset etc) is working really nice and controllable and thats the way I intended this feature to be used. For staircases you can additionally use offset on the rotation axis and that will work perfectly as well. The advantage of this method over any other usage method (like with rotation origin offset) is that your object origin is in the center of your created circle.

Also I would like to point out that the array modifier has multiple offset options (like object offset) that would interfere with the offset rotation origin just like they are interfering with rotation now (in the image from your comment). In a sense adding a rotation origin option will only offset your model before the array happens and offset it back after the array is applied leaving you off centered. If that is often neccessary I am convinced to add it, but you can do it with object offset already (just like the whole rotation thing anyways).

Thats not to say that I dont like the idea of having rotational symmetry around an arbitrary point. The array modifier can do more than symmetry though (like rotations in 3d along a curve).
For the usecase of a 2-dimensional symmetry configuration that most people want I would rather add it to the mirror modifier at some point (or create a new symmetry modifier) as that would give much more flexibility when working on it (clipping, bisect, mirroring, easy configuration of integer rotational symmetry around a custom origin). Also it could have support for some more obscure symmertries like icosaeder symmetry.

I did not yet agree to add a rotation center to the array modifier, but I did actually agree that translation and rotation together seems very non intuitive. I now changed the transformation order, so the geometry will first get rotated and then get translated in object space. This makes the rotation value different from what the same rotation with an object offset would look like, but adds a lot of flexibility for the user. It also doesn't break my intended usage method and it's more intuitive for more complex arrays.

  • changed the behaviour in 3d to interpolate the euler angles and use them instead of matrices

I figured it would be good to show a image of it working.
In the image below you can a circular array with fill angle and a linear array with fill angle (rotation=[0°,90°,90°])

I'm not so keen on this functionality.

For full control we basically need offset, scale, rotation - which is quite difficult to control only with numeric input.
This makes the UI quite heavy too.

I'd like to check with others UI/modeling module team members on this one,
but my sense is that having full control of all transform channels in the array modifier is cumbersome and it ends up being simpler for users to use an object to control the transform.


We could try solve this in a more general way.

  • Objects are currently used for transform controls, however users might not want to add objects in each instance.
  • We could create a generic struct that stores transformation (can be optionally used by all modifiers that currently use an object for this purpose).
  • We could have a template for this which doesn't clutter the UI, perhaps a popup or a collapse-able UI (something a little like the curve widget).
  • This means adding numeric transform controls isn't something everyone does themselves (each a little differently), as well as 9+ buttons.
  • We could have a gizmo to adjust these, so users can manipulate them in the 3D view.

I also think a more general transform struct with a ui template would be fantastic, but with the rotation here, there is a little bit more than just controlling the custom object offset.

The new rotation parameter uses euler angles for interpolation while object offset doesn't (it does successive matrix multiplication). Also the rotation is applied first and then the other offsets.
That way it is possible to create arrays like these with both rotation and object offset:

To make this possible with a transform template it would need to have direct euler angle access and a way to restrict it to the usage of euler angles in the UI.
Why? Because the transform matrix does not contain information on how far you rotated. It only contains the information on where you ended up (so 90° and 450° or 360° and 720° is the same).
Also a new checkbox would be neccessary to choose between one of the behaviours (current behaviour of object offset / improved rotation behaviour with rotation parameter from this patch),
not only so old files keep working, but also because it is kind of confusing if rotation does work different from object offset if it is supposed to be a object offset replacement.

I think it is worth cluttering the UI in this case instead of having it in a full transform property.
The UI clutter will be reduced by D7498 by using collapsable subsections so that is not really a big problem.

I'd like to check with others UI/modeling module team members on this one,
but my sense is that having full control of all transform channels in the array modifier is cumbersome and it ends up being simpler for users to use an object to control the transform.

Speaking strictly from a user point of view this is a very welcome and useful addition. Relying on another object for array rotation is a frequent source of frustration and a workflow hiccup we encounter often.

  1. When moving the object with an Array using Object Offset one must remember to also bring the "offset object" .
  2. The offset object can be parented to the "master", but when copying/duplicating one still has to remember to specifically include it in selections.
  3. This creates additional objects in the scene to keep track of, select, micro-manage.
  4. It also makes it impossible to easily copy modifier stacks across identical objects with similar setups.

When duplicating any object with an array using Object Offset along with its controller, if afterwards one changes some parameter and then try to Ctrl + L > Modifiers it will break the desired solution, since copying the stack would also make the offset object match, which is often not the desired outcome.

Whether this matches your global vision for Blender or expectations from a UI or code quality point of view as a developer I leave to you, but it would definitely be a desirable feature for users.

I have to agree with Duarte.

On the technical side, I'm not sure what are the design implications of this change. But from the user perspective this is a very welcome addition.
There has been many times I had to create a circular array but was limited or slowed down by the limitations of the modifier. Having the ability to not only rotate but also set the origin offset would be very helpful.

There are of course workarounds for these limitations - like using other external objects - but those are more complicated and can easily become problematic if you want to duplicate or re-use your object.

If this implementation is problematic with Blender's design, alternatively we could have a new "Transform" modifier. This could be used in combination with many other modifiers to create procedural arrays, deformations, etc.... I often use the displace modifier set to one direction only to achieve effects like this, but I don't have control over rotation, only position.

Thanks for great work regardless and I look forward to whatever comes out of this.

I do agree with Campbell's points.

Surely, flexibility is a nice addition for artists, but here they come with added complexity and are limited to a single modifier. The interface indeed becomes too clumsy and unclear as well.

The best long-term solution is to have this functionality implemented via modifier nodes. This is the project we all want and are working towards to, and for until it's done I'd suggest not add unnecessary complexity and technical debt to the old system.

@Sergey Sharybin (sergey)

  1. this patch does not clutter the UI in my opinion especially with D7498 coming. (maybe some more input by @Hans Goudey (HooglyBoogly) on that?)
  2. this patch does add a new rotation option that works completely different to what you would be able to do with object offset, so its nothing that would go into every modifier over time. I didn't want to just clutter the UI without adding useful functionality that hasn't been there, that's why I did it this way.
Henrik Dick (weasel) edited the summary of this revision. (Show Details)Apr 29 2020, 12:07 PM

I do agree with @Henrik Dick (weasel) about the interface side of this actually. With the upcoming modifier UI rework, the only new UI by default would be a collapsed "Rotation" panel. As for clumsy, I'm not sure, it's just 4 options.

I'd rather put this patch on hold.

In general modifiers are not good for creating instances, the result is heavy inefficient meshes.

There are a handful of cases where it's OK (where the duplicates form a continuous surface for example), however in general, this modifier isn't very efficient and is something I'd rather leave as-is.

And instead focus on a better duplicator system which doesn't have these drawbacks.

@Campbell Barton (campbellbarton) Ok I see, you don't want to touch the array modifier because you think it is the wrong aproach to do an
efficient array and don't want to spent time improving it since it isn't the right way anyway in your opinion (is that right?).

I want to point out that, yes I am aware that it is slower than instanced duplication or other similar methods, but it is absolutly crucial for
anything that needs to be 3D-printed as it can merge vertices and create manifold meshes. Also the "heavy" geometry is needed when
it is used with the boolean modifier, which a lot of people want it for. For that boolean usecase it is especially annoying to setup an object
offset with an empty, because it is often somehow rotated in space and not at the world origin.

Here is an example file with a few cases where the rotation value as it is currently (in this patch) implemented is really useful. I tried to
only include examples where it must be a modifier and not just duplication. I tried to also pick two practical cases where the old object
offset alone wouldn't work (or at least not great), those are marked in red in the file. I also tried to show how one of the red cases could
be achived prior to this patch.

There are a handful of cases where it's OK

And those cases will still be vaild when there will be better instancer system.
I do use array with intended use cases (desire of having one single mesh) and having rotation parameter would for sure be useful, current object offset have its drawbacks.

Possibility of using array to generate instances is a temporary bonus before object notes will be implemented - an it will take quite a while for sure.
(Current workaround-ish method of using arrayed instances is via creating array of planes and using face instance).

Unless mesh/modifiers nodes are coming soon, i think that this patch will be good feature and event then it would be valuable addition.

Just implementing the operator "spin" as a modifier would be very sufficient in most use cases. By doing so we would not have the issue of weighing down the UI of the array modifier further. Also the modifier would use code that already exists in blenders codebase

@Daniel Bystedt (dbystedt) As far as I know using the Spin Operator's code in a modifier would require the modifier to create a bmesh instance. The array modifier is written without bmesh and therefore much more efficient. Also adding a whole new modifier for something that can already be accomplished by the array modifier seems very redundant.
And then there is also the point that this patch is more than it looks like. It is not like the spin operator, but more like first rotation in 3D and then array offset. Try the arrayexamples.blend from one of the previous comments to see why this patch can do more than the naive approach for circular arrays from the spin operator. Also this patch is extendible e.g. there could be options for Quaternion and Axis Angle rotation representation, which are not there yet in this patch (there is only euler rotation currently).

  • Rebase

Now thanks to D7498 the UI looks very nice. Thanks @Hans Goudey (HooglyBoogly) for that awesome awesome patch btw.

@Campbell Barton (campbellbarton) I know the eventual goal is to have a better duplicator system, but to me it seems like this patch is a bit orthogonal to that, lots of times you want a contiguous mesh.

Also, at this point the only UI "cost" to this is another subpanel.

Given these reasons and the relatively small size of the patch, it seems worth it to me. Just my take on it though.

source/blender/makesrna/intern/rna_modifier.c
2759

I'm not sure the descriptions add anything here, I would remove them.

@Campbell Barton (campbellbarton)
Please don't let another one of these languish in favour of a better system that's far off in the future. Your concerns are valid, but I know a lot of fellow modelers that have immediate use for this.
As I understand it, this wouldn't be worse than the current workflow with an empty, performance-wise, but it would be a real improvement in terms of workflow.

I'd understand it being a separate modifier if that solves the UI concerns, I just really want this in modifier form.

  • rebase
  • add options for quaternions and axis angle

The updated example file, since the DNA names changed.

Henrik Dick (weasel) edited the summary of this revision. (Show Details)Thu, Jul 30, 11:04 PM
  • change axis angle stepsize
  • cleanup

There is a little problem with the display of the rotation values.
It is very visible if you compare the object transform rotation panel
and the array rotation panel.
@Hans Goudey (HooglyBoogly) I would really appreciate if you would take a look at it

Great job with the patch description.

@Hans Goudey (HooglyBoogly) I would really appreciate if you would take a look at it

I think I see what you're talking about. With this diff you can get the panel looking exactly like the panel in object properties:

diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c
index 37b28b3b3d6..e0df598a200 100644
--- a/source/blender/modifiers/intern/MOD_array.c
+++ b/source/blender/modifiers/intern/MOD_array.c
@@ -1034,20 +1034,22 @@ static void rotation_draw(const bContext *C, Panel *panel)
 
   uiLayoutSetPropSep(layout, true);
 
-  uiLayout *col = uiLayoutColumn(layout, false);
+  int rotmode = RNA_enum_get(&ptr, "rotation_mode");
 
+  uiLayout *col = uiLayoutColumn(layout, false);
   uiLayoutSetActive(col, RNA_boolean_get(&ptr, "use_rotation"));
-  int rotmode = RNA_enum_get(&ptr, "rotation_mode");
+
+  const char *rotation_label = IFACE_("Rotation");
   if (rotmode == ROT_MODE_QUAT) {
-    uiItemR(col, &ptr, "rotation_quaternion", 0, NULL, ICON_NONE);
+    uiItemR(col, &ptr, "rotation_quaternion", 0, rotation_label, ICON_NONE);
   }
   else if (rotmode == ROT_MODE_AXISANGLE) {
-    uiItemR(col, &ptr, "rotation_axis_angle", 0, NULL, ICON_NONE);
+    uiItemR(col, &ptr, "rotation_axis_angle", 0, rotation_label, ICON_NONE);
   }
   else {
-    uiItemR(col, &ptr, "rotation_euler", 0, NULL, ICON_NONE);
+    uiItemR(col, &ptr, "rotation_euler", 0, rotation_label, ICON_NONE);
   }
-  uiItemR(col, &ptr, "rotation_mode", 0, NULL, ICON_NONE);
+  uiItemR(col, &ptr, "rotation_mode", 0, IFACE_("Mode"), ICON_NONE);
 
   uiLayout *sub = uiLayoutColumn(col, false);
   uiLayoutSetActive(sub, rotmode != ROT_MODE_QUAT);

Personally though, I would drop the "Rotation" label completely, because the panel is already labeled "Rotation". Yes, that's different than the other two panels, but their property labels are to make it clear what type they are-- factor or distance.

It also looks like the lower precision of the axis angle rotation compared to the object data panel is because of the ui_range in rna_modifier.c.

source/blender/makesrna/intern/rna_modifier.c
2858
/home/hans/Blender-Git/blender/source/blender/makesrna/intern/rna_modifier.c: In function ‘rna_def_modifier_array’:
/home/hans/Blender-Git/blender/source/blender/makesrna/intern/rna_modifier.c:2858:33: warning: declaration of ‘rna_enum_object_rotation_mode_items’ shadows a global declaration [-Wshadow]
 2858 |   static const EnumPropertyItem rna_enum_object_rotation_mode_items[] = {
      |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /home/hans/Blender-Git/blender/source/blender/makesrna/intern/rna_modifier.c:53:
/home/hans/Blender-Git/blender/source/blender/makesrna/RNA_enum_types.h:155:31: note: shadowed declaration is here
  155 | extern const EnumPropertyItem rna_enum_object_rotation_mode_items[];
      |

You can just remove this redefinition in this file and it will use the one from RNA_enum_types.h. Best not to duplicate it anyway.

@Campbell Barton (campbellbarton) I know the eventual goal is to have a better duplicator system, but to me it seems like this patch is a bit orthogonal to that, lots of times you want a contiguous mesh.

In this case it's typically tiling in one direction, you rarely use rotation for continuous meshes. For the times you do, I don't see having to rely on an object to do it as a significant down-side.

Kdaf (Kdaf) added a subscriber: Kdaf (Kdaf).
  • cleanup ui ranges, tooltips and names
  • remove unnecessarily duplicated code

The UI steps for Axis-Angle are kind of hard to choose, because if
you want to keep the easy animation usage, then you need to have them
in one single property, which sadly shares the same UI range and precision.
I balanced it, so it matches the amount of zeros from the object panel
and the steps are degree steps for the angle (which is not the case in the
object panel, there it is 0.1 radians which is 5.73 degree per step)

My goal with this patch is to improve non-destructive workflow. Currently its kind of difficult in some cases.
I want to throw in examples of people explaining the current array modifier for circular arrays to people.
https://youtu.be/HhYom1n0qZs
https://youtu.be/bLGYsd4lEjY
It's really visible that blender is lacking there, from the length of the explanation for such a simple thing in concept.
Both example videos can be shortened by this patch to roughly half the time or less. Having the rotation as an option
in the array modifier options is especially useful when you try to snap one or more of your array objects on to a surface.

@Campbell Barton (campbellbarton) I would also say that circular patterns are quite common in all types of technical
modelling tasks, because they are useful (mechanically) and beautiful. Just think of how often you
see a wheel or a gear (with intricate detail) <- very common (not rare) continuous examples.

The non continuous instances only really come up to be used to carve into a continuous mesh, which
is also common practice and gets a speed boost here. Most of the other non continuous examples are
instancing examples where the array modifier is not the way to go anyway.
Its not my intention to change the usage in that non continuous case. They should use blenders instancing.

I will put together a much better example blend file to really show off, why this patch is definitly needed

Henrik Dick (weasel) edited the summary of this revision. (Show Details)Fri, Aug 7, 1:40 PM

I have now included a second demo file, but I'm not sure I actually nailed it, so maybe some more user feedback might be necessary.

I would like to mention right here, that there are other ways of creating circular arrays, that lots of people are not aware of. You can

  1. use Linear Array and then Simple Deform with Bend if you figure out how that works, but objects need to be placed in a very special way and the center isn't at the origin.
  2. use Linear Array and then Curve Deform with a Circle Curve. Again you need to figure out your special placement and scale it to length manually.

This is especially useful when it comes to making round continuous surfaces like weels, but not very useful to make polygonal stuff like a lot of gears and such things, where angles are important.