Multi-Object mode support (design overview)
Open, NormalPublic

Description

This task is to outline how multi-object mode support might be achieved.

This design task focuses on mesh edit-mode, I think this is one of the more complicated cases,
so supporting this should lead us to tackle most of the more tricky problems.

Motivation

Maybe obvious, including this for completeness - in case I miss something.

Certain operations are useful to apply on multiple objects at once.

Examples are:

  • Transform

    Ability to move vertices from many objects at one for eg. Change positions in relation to each other, snapping multiple vertices from different meshes to one location.

    This avoids the hassles of enter/exit modes for otherwise simple tasks.

    Note that IMHO this reason alone makes it worth adding support for this, even if not all tools have support for it.
  • Basic Tools

    Calculate normals, triangulate faces, cleanup operations - it's useful to support this without users having to write batch processing scripts that iterate over selected objects.
  • UV unwrapping.

    It's common for objects to share a texture, especially for real-time models.

    Note that this alone is not a compelling reason to add multi-mode support IMHO, we could support this with an operation in object mode, it would be clumsy but functional.

Design Challenges

  • How to handle tools which join data? - select 2x vertices and making an edge for eg.

    I think attempting to make this work at all is likely to cause more confusion than it's worth (move geometry between meshes, or create the missing geometry to the last selected vertices object).

    Suggest to warn that geometry from different meshes can't be joined.
  • Active Object

    Currently the editmode object is always the active object.

    Is this something we would expose (draw differently for eg).

    Since we will have UV/vgroup/shapes from the active object in the UI we can't avoid the fact that the user still needs to be aware of the active object.

    Should we make it that selecting a vertex from another mesh makes this become the active object (think this would be reasonable, just noting it in case there is a down-side).
  • How to handle linked duplicates?

    Two or more objects can share the same data, in some cases we could operate on both (border select for eg), other times - transform for eg. We probably have to pick one and not use the others.
  • How to handle data layers? (UV's, vertex groups, shape-keys).

    AFAICS there are no elegant solutions here. If someone changes a uv/vgroup... etc, all we can do is change all other objects to the same name or index.
  • Accidental/Slow Mode Switching

    Even if this is working well, there is the possibility of the user accidentally entering edit-mode on 100's of objects.

    (A little like the problem we used to have pressing P by accident entering the BGE).

    Since changing modes for many objects could be slow, I think the best we can do here is to check for Escape key being pressed after each object is converted. That should at least avoid a user locking up their Blender instance by accident.

    Another option is to have a popup asking the user if they _really_ want to enter editmode on a huge number of objects, but think this is weak.
  • Selecting a single object

    With multi-object editing users may want to select a single mesh.

    not essential, but users will likely want this.
  • Toggling individual objects

    Users will likely want to add/remove objects from the current edit-mode.

    more of a nice-to-have, mentioning for completeness.

Technical Challenges

  • Partial Rollback

    When one operation fails, we will want need a reliable way to roll-back an edit for all other objects.

    The undo stack is capable of this in principle, we just need a good general API than handles this.

    Note that this isn't necessarily always the case, we can:
    • In some cases these checks can be performed before making any destructive edits.
    • Print a message "Operations on ... objects failed", without rolling back changes, in some cases this is preferable as long as it's not leaving user data in a broken state.
  • Faking 'One Big Mesh'

    Some operations will need to *pretend* that all objects are one bug mesh (uv unwrapping for eg). This can be done on a case-by-case basis.

    Examples of this are:
    • Transform

      Don't think this will be too hard to support, although there will be some complications regarding local/global transform spaces. The way transform supports a matrix per transform element means we can support this. How to handle local-space operations is not well defined, We could use the active objects local matrix, or each objects local matrix - however I think this may give confusing results in most cases.
    • UV unwrap

      Only requires extracting faces from all meshes.
    • Knife tool

      This is much more complicated, it may be an exception where we don't support multi-object initially, There might be a trick to support it more easily (have an array of knife-mode data with is aligned to each object), but wouldn't count on this since we most likely want objects occluding eachother for eg.
    • Intersect

      This is one of the few cases we might need to join the mesh then split it apart again after. That - or we don't support multi-object modes for this tool. Joining has the big disadvantage that it can loose precision and it's likely to cause errors with custom-data layers (some objects have uv's/vgroups/multires, some not).
  • Undo Stack

    Getting this working usefully may be entire project on it's own, From checking on the code, seems we can group UndoElem into UndoElemGroup so many undo operations can be performed in a single step.

    Note that the current code relies on undo running on the active object. (undo doesn't store pointers to the object). If we have objects which are not the active object being included in undo, we must have a fool-proof way to get the list of objects back.

    This might not be so simple - relying on visibility for eg, depends on view layers, so calling undo from a different window will give a different set of objects.

    Edit-mesh undo already stores data pointers in undo structure, so we might be able to use these instead of the contexts objects.

    Need some investigation to ensure this is going to work reliably.

    Another option is to force the same context when running undo, so it's not possible to execute undo in a different view layer (it could be done internally, the user need not be aware of it).

Initial Steps

This isn't complete, it's just some ideas on how to start out developing this in a branch.

  • All selected objects in a matching mode are grouped (even if not active)
  • Active object is an exception, it's included even when not selected.

    This is just a simple rule to start with, we might up using a different rule.
  • At first we don't handle undo at all (since this might rely on larger refactor to the undo stack).

Task Breakdown

  • Drawing Code

    Use mode drawing for all selected objects with the appropriate data, type.

    Currently the convention is to only show the active-object w/ mode drawing, this convention shouldn't be hard to break.
  • Tools

    Basic design for making it possible to operate on many objects at once.

    Possible API's:
Object **ob_array = ED_object_mode_get_all_ex(window, scene, OB_MESH, OB_MODE_EDIT, &ob_array_len);
Object **ob_array = ED_object_mode_get_all(C, OB_MESH, OB_MODE_EDIT, &ob_array_len);

/* we could use an iterator too */
FOREACH_OBJECT_IN_MODE(view_layer, OB_MODE) {
    BMEditMesh *em = ...;
}
Have `*_single()` and `*_multi` versions of functions, to avoid confusion.

Details

Campbell Barton (campbellbarton) triaged this task as Normal priority.
Campbell Barton (campbellbarton) updated the task description. (Show Details)
Campbell Barton (campbellbarton) renamed this task from Multi-Mode Support (design overview) to Multi-Object mode support (design overview).

Hi, I hope I'm understanding this correctly. Is this an attempt to make things work similar to Modo?

In Modo, you have Mesh Layers. You could have multiple meshes on a layer; 3 spheres on 1, 2 cubes on another, etc. These layers can be selected via Object Selection Mode—similar to Vert, Edge, or Face Selection modes. Once you've selected a layer there's no need to enter an "Edit Mode", simply enable Vert/Edge/Face selection, and you're good to go.

Meshes can be copied and pasted from layer to layer.

A very popular script is "Select That Mesh" by Seneca Menard, which will select whatever mesh is under the mouse cursor, regardless of which layer it's on. Allowing you to very quickly go from layer to layer, or mesh to mesh, to make edits.

As it stands, I use Heavypoly's custom menus, one of which allows you you add meshes in Edit mode. So unless I need something more than a cube, cylinder, sphere, or plane, I never have leave Edit Mode. Unless I want to make another Object for organizational reasons.

I personally believe doing away with Edit Mode and Object Mode is the way to go.

@Mike (zimlorog) I don't use modo, but it seems like this isn't an exact equivalent (this ability has been discussed before modo was around).

The ability to select an single mesh, with multiple objects in edit-mode would be handy, so it's worth keeping that in mind.

As for having extra selection modes without an explicit edit-mode. This is not the plan being proposed, I realize this is how some applications handle it though.

Further, I'd rather comments here don't use other 3D applications as a short-hand for explaining how something should work, instead detail the design you think can work well for Blender.

Hmm... lots of tricky issues here. Here are some rambling thoughts about how we could tackle these.

From a technical standpoint, IMO, one of the biggest issues concerns how most tools assume that there is a single "active object", and that all geometry comes from that object (e.g. most of the Pose Mode tools). Perhaps one of the simplest workarounds for that is that as a first pass, we simply take the existing operators, push the existing single-active-object code into a subroutine, a wrap that subroutine with a loop over the set of selected objects, operating on each as if it were the "active object" of the day. So, from a user perspective, we basically only have the concept of "selected objects" now, while the underlying code sees "selected objects set" --> operate on this "active object [being processed]". That approach probably solves most of the tools.

As for the other issues raised:

  • Cross Object Joining - Simply warning/complaining will probably be the simplest option. Especially since things like modifiers could be involved and/or you need to keep some objects separated (e.g. for mechanical models). But, if you just had simple geometry without modifiers, several options include: 1) use order in which vertices were selected - i.e. move the initial object's geometry into the second one, or 2) merge the smaller objects into the larger ones.
  • Linked Duplicates - It depends on the operation I guess. For example, consider selecting two regions on a pair of linked duplicate spheres, and trying to extrude those; as long as there are no vertices in common between those selections, it would be nice from a user standpoint to be able to do just extrude them as if they're separate meshes. However, on second thought, that probably wouldn't be possible, as selection data would be shared between objects, and thus the selected vertices from both would get operated on. Unless we do something crazy (and invasive), like having per-instance selection buffers, etc. it's probably another case where it's just easier to error out, to keep the project manageable in the short-term; then we can just leave that as an issue for the future, when users have actually been playing with multi-object editing for a while, and we have a better idea of what they need/don't need.
  • Data Layers, etc. - Probably it won't be an issue if we do the multi-object thing I described above. Then, most of the code can still keep working as-is
  • Accidental/Slow Mode Switching - Perhaps we could cheat this? If more than a sane number of objects (or more than a certain amount of geometry - sum(totvert), sum(totfaces), etc.) are in the selection, maybe we don't need to create edit structures for all of them until an editing operator is actually invoked. That is, maybe we can just enable the relevant draw engine overlays, causing the screen to suddenly get cluttered with vertex dots drowning out anything else, the user realises their mistake, backs out, problem averted. On the flip side, if they really *do* want to go ahead with all this, they probably will expect the operations to be slow, so the first selection can take longer as it builds up the relevant edit meshes.

    Thinking about it more, another idea could be that things like selection work in a two-stage way: first, we have a coarse object-level thing (like we're selecting objects in object mode), and then, only when we've identified which of the objects in the selected set may be affected, do we go in and convert those to edit structures (or filter those out for editing). Then, we can feed this filtered set to our selection tools as we do now.
  • Toggling Objects - Adding/Removing Objects from the "Edit Set" while in EditMode - Blegh! Let's not go there! IMO, one of the strengths of Blender's Edit/Sculpt/Pose/etc. modes is the fact that we explicitly select the object(s) for which we want to be working on exclusively *before* entering the mode to work on the sub-geometry of those objects. In particular, this avoids many annoying problems that you encounter in things like Inkscape or MS Office.

    Specifically, let's say you've got this path with heaps of vertices that you're trying to select + tweak. It takes you a very long time to carefully select the vertices you want to modify. And then, you click slightly off from one of the next verts you were going to select (and let's face it, we're human, so this will and does happen a lot). In one of the aforementioned apps, what often happens is that when that happens, a series of things happen: 1) You lose selection on the object you've been carefully working on, along with all the carefully selected points, 2) The closest neighbouring object gets selected instead and made the active one, and 3) The nearest point(s) on that neighbouring object get selected instead. Lo and behold, you may have just lost 5-10 minutes of work, and now have to also get out of editmode, fix up the object-level selection (which may not be easy with lots of tightly interlocking parts that sit almost planar to each other), before getting back into edit mode to try and get the same selection again.

    (Note, simply using collection visibility to mask out things you don't want to accidentally isn't a complete solution; sometimes you still want to see those things in the background, just not edit them. Also, setting up a dedicated collection to do edit masking for such a case is probably too much overhead. So, I think it's still important that users are able to quickly isolate/specify the exclusive set of geometry objects that they want to edit in a particular mode by simply selecting them in Object Mode and entering an editing mode to edit those aspects of the selected objects)
  • Selecting a single mesh - If you've got a single mesh selected, I don't really see why it wouldn't behave like it does now? i.e. You've got a single object selected, so it's just like having the single active object now. Extending this a bit, if you've got a two linked duplicates selected, use the "active" one like now (i.e. the last selected one only). Finally, if there is more than one object selected (of the same type), but they don't use the same datablock, you've got the multi-mesh/object editing scenario we've been discussing.

    I don't think we really need to worry about the case where you've got a whole bunch of objects selected but only want to edit the last selected - for most users, it's more a bug that only the last selected gets edited... for the others (e.g. blender power users), if we really wanted, it'd just be a case of some kind of option somewhere (workspace/tool/editmode-operator settings) to restrict it to taking a single object for the "editing set".
  • Mode Drawing - I always just assumed that all selected objects would end up getting drawn as if they're in the mode. So, for editmode, all vertex points are shown + edges. Perhaps the only ways we'd try getting fancy is in fading the intensity of shading on these points based on the relative order in which the objects were selected (i.e. first selected lightest, last selected darkest). I would consider leaving stuff like that for later down the track once artists have started playing with it for a while, and we start having a better feel for the pressure points.

    That said, I imagine that artists would likely have some kind of thing where there's a "primary" mesh they're concentrating on, which will likely be in the middle of the viewport at a middle-ish distance, and they would like everything else to be faded out a bit, so that they can concentrate on this primary mesh more. The tricky thing in such situations is knowing which one that mesh is - unless of course, we just give them the mental model that the last one they select is (as now) the "primary" one they want to work on; the others can be accessed if it is convenient to do so.
  • Undo System - This is where it gets nasty. I'm not too familiar with this area, so I'll mostly refrain from commenting too much. My first thoughts on how to architect this though are that:
    • If we keep the concept of a single "Global Undo step" for all operations inside editmode, then we'd have to make the each "Edit Undo step" (i.e. each global undo has several of these) consist of snapshots of all the selected objects in editmode. To keep memory costs down, maybe only diffs for objects that actually change get stored per edit-step?
    • Otherwise, this may be a good opportunity to axe the Global/Edit split, and just include edits to all selected objects in a single step. But, that depends a bit on whether that's feasible to support with Mesh vs BMesh stuff?

Hmm... lots of tricky issues here. Here are some rambling thoughts about how we could tackle these.

From a technical standpoint, IMO, one of the biggest issues concerns how most tools assume that there is a single "active object", and that all geometry comes from that object (e.g. most of the Pose Mode tools). Perhaps one of the simplest workarounds for that is that as a first pass, we simply take the existing operators, push the existing single-active-object code into a subroutine, a wrap that subroutine with a loop over the set of selected objects, operating on each as if it were the "active object" of the day. So, from a user perspective, we basically only have the concept of "selected objects" now, while the underlying code sees "selected objects set" --> operate on this "active object [being processed]". That approach probably solves most of the tools.

Right, that's how I've started it works in maybe half of all operators - for others however this does break-down badly (will write more on this later - example UV unwrap you want it not to overlap all resulting UV's).

As for the other issues raised:

  • Cross Object Joining - Simply warning/complaining will probably be the simplest option. Especially since things like modifiers could be involved and/or you need to keep some objects separated (e.g. for mechanical models). But, if you just had simple geometry without modifiers, several options include: 1) use order in which vertices were selected - i.e. move the initial object's geometry into the second one, or 2) merge the smaller objects into the larger ones.

Was considering moving geometry between objects but think its a bad convention - in simple case of two vertices it's obvious, but this ability opens a can of worms.

  • Do you tell users they loose data if vertex groups in one mesh are not in another.
  • Do you duplicate data if the vertex is connected to other geometry.
  • Do we attempt this with face creation, bridge tool... etc.

The effort to get this working even on a basic level, only to create confusing situations for users IMHO makes it not worthwhile.

There might be some rare exceptions - eg: Convex-hull could operate on all selected meshes, although resulting mesh would use the active.
Further IMHO this causes more trouble than it solves.

We're probably better off handling this use-case with an explicit operation to move selected geometry to the active object.

  • Linked Duplicates - It depends on the operation I guess. For example, consider selecting two regions on a pair of linked duplicate spheres, and trying to extrude those; as long as there are no vertices in common between those selections, it would be nice from a user standpoint to be able to do just extrude them as if they're separate meshes. However, on second thought, that probably wouldn't be possible, as selection data would be shared between objects, and thus the selected vertices from both would get operated on. Unless we do something crazy (and invasive), like having per-instance selection buffers, etc. it's probably another case where it's just easier to error out, to keep the project manageable in the short-te.rm; then we can just leave that as an issue for the future, when users have actually been playing with multi-object editing for a while, and we have a better idea of what they need/don't need.

Agree with last part, it think we may end up having to accept limitations, eg:

  • Destructive operators use an iterator that ensures data is only stepped over once.
  • Operators such as border/lasso/pick select can loop over data multiple times.
  • In the case of transforming vertices from two objects that share data, pick the first one.
  • Data Layers, etc. - Probably it won't be an issue if we do the multi-object thing I described above. Then, most of the code can still keep working as-is

Right, so far have found this to be the case.

  • Accidental/Slow Mode Switching - Perhaps we could cheat this? If more than a sane number of objects (or more than a certain amount of geometry - sum(totvert), sum(totfaces), etc.) are in the selection, maybe we don't need to create edit structures for all of them until an editing operator is actually invoked. That is, maybe we can just enable the relevant draw engine overlays, causing the screen to suddenly get cluttered with vertex dots drowning out anything else, the user realises their mistake, backs out, problem averted. On the flip side, if they really *do* want to go ahead with all this, they probably will expect the operations to be slow, so the first selection can take longer as it builds up the relevant edit meshes.

    Thinking about it more, another idea could be that things like selection work in a two-stage way: first, we have a coarse object-level thing (like we're selecting objects in object mode), and then, only when we've identified which of the objects in the selected set may be affected, do we go in and convert those to edit structures (or filter those out for editing). Then, we can feed this filtered set to our selection tools as we do now.

Are you suggesting to lazy-initialize edit-mesh structure only when it's needed?

This would work fine in certain cases but am concerned it complicates code too much.
causing different code-paths for the same operations.

Eg, if you have selection, you would want to first know if the modifier options are different in edit/object mode, in that case you can't guess when selection would fail or not.

This option can be applied only in simple cases when there are no modifiers, so we could just allow for exceptions to the rule.

Think we could keep this in mind but no need to do it initially (as you say, summing geometry with warning might be enough), we could allow user to press Esc too .

  • Toggling Objects - Adding/Removing Objects from the "Edit Set" while in EditMode - Blegh! Let's not go there! IMO, one of the strengths of Blender's Edit/Sculpt/Pose/etc. modes is the fact that we explicitly select the object(s) for which we want to be working on exclusively *before* entering the mode to work on the sub-geometry of those objects. In particular, this avoids many annoying problems that you encounter in things like Inkscape or MS Office.

I was thinking mouse-selection would be used to add objects to edit mode. Agree that would be terrible.

I assumed users would be asking for this early on, it's at least worth considering if we should support it or not.

Specifically, let's say you've got this path with heaps of vertices that you're trying to select + tweak. It takes you a very long time to carefully select the vertices you want to modify. And then, you click slightly off from one of the next verts you were going to select (and let's face it, we're human, so this will and does happen a lot).  In one of the aforementioned apps, what often happens is that when that happens, a series of things happen: 1) You lose selection on the object you've been carefully working on, along with all the carefully selected points, 2) The closest neighbouring object gets selected instead and made the active one, and 3) The nearest point(s) on that neighbouring object get selected instead.  Lo and behold, you may have just lost 5-10 minutes of work, and now have to also get out of editmode, fix up the object-level selection (which may not be easy with lots of tightly interlocking parts that sit almost planar to each other), before getting back into edit mode to try and get the same selection again.

An example where *roughly* similar functionality is useful is the ability to send objects out of local-view.
I recall it was very useful to quickly select a bunch of dense objects (a building and it's surroundings for eg).
Enter local-view, then notice some foliage around the building (select objects in same group). MKey to move out of local-view.

We could support this the same way, objects can be moved out of edit-mode, but not added back in.

Or we could support adding objects in with explicit actions:

  • existing Ctrl-RMB which selects objects while in edit-mode.
  • via the outliner.
(Note, simply using collection visibility to mask out things you don't want to accidentally isn't a complete solution;  sometimes you still want to see those things in the background, just not edit them. Also, setting up a dedicated collection to do edit masking for such a case is probably too much overhead. So, I think it's still important that users are able to quickly isolate/specify the exclusive set of geometry objects that they want to edit in a particular mode by simply selecting them in Object Mode and entering an editing mode to edit those aspects of the selected objects)

Agree.

  • Selecting a single mesh - If you've got a single mesh selected, I don't really see why it wouldn't behave like it does now? i.e. You've got a single object selected, so it's just like having the single active object now. Extending this a bit, if you've got a two linked duplicates selected, use the "active" one like now (i.e. the last selected one only). Finally, if there is more than one object selected (of the same type), but they don't use the same datablock, you've got the multi-mesh/object editing scenario we've been discussing.

Yes, single selected meshes don't change their behavior.
Having said that - multi-object editing is not a different mode either. Operators just act on other objects too.

I don't think we really need to worry about the case where you've got a whole bunch of objects selected but only want to edit the last selected - for most users, it's more a bug that only the last selected gets edited... for the others (e.g. blender power users), if we really wanted, it'd just be a case of some kind of option somewhere (workspace/tool/editmode-operator settings) to restrict it to taking a single object for the "editing set".

Ah, see what you're getting at. I didnt consider this a problem - but see your point that it's possible _some_ users want the ability to enter edit-mode on a single object even when others are selected.

If we want this it's not hard to support it (option for operator, extra flag on objects for eg).
Think we can add it later - only if really needed.

  • Mode Drawing - I always just assumed that all selected objects would end up getting drawn as if they're in the mode. So, for editmode, all vertex points are shown + edges. Perhaps the only ways we'd try getting fancy is in fading the intensity of shading on these points based on the relative order in which the objects were selected (i.e. first selected lightest, last selected darkest). I would consider leaving stuff like that for later down the track once artists have started playing with it for a while, and we start having a better feel for the pressure points.

These things crossed my mind too, although not sure why we would fade colors based on selection order?

That said, I imagine that artists would likely have some kind of thing where there's a "primary" mesh they're concentrating on, which will likely be in the middle of the viewport at a middle-ish distance, and they would like everything else to be faded out a bit, so that they can concentrate on this primary mesh more. The tricky thing in such situations is knowing which one that mesh is - unless of course, we just give them the mental model that the last one they select is (as now) the "primary" one they want to work on; the others can be accessed if it is convenient to do so.

Yes, the active object is still an important concept since it's used for all UI drawing. So we might want to draw non-active mesh data slightly differently.

  • Undo System - This is where it gets nasty. I'm not too familiar with this area, so I'll mostly refrain from commenting too much. My first thoughts on how to architect this though are that:
    • If we keep the concept of a single "Global Undo step" for all operations inside editmode, then we'd have to make the each "Edit Undo step" (i.e. each global undo has several of these) consist of snapshots of all the selected objects in editmode. To keep memory costs down, maybe only diffs for objects that actually change get stored per edit-step?
    • Otherwise, this may be a good opportunity to axe the Global/Edit split, and just include edits to all selected objects in a single step. But, that depends a bit on whether that's feasible to support with Mesh vs BMesh stuff?

Looked into this and I'm not all that concerned about the undo system. Mainly because AFAICS this is mostly a matter of putting existing undo steps into a container (UndoStepGroup). then applying/rolling back all at once.