Material Slots: Why they're so weird, and how they could be less weird. #112391

Closed
opened 2023-09-14 19:03:10 +02:00 by Demeter Dzadik · 5 comments
Member

Update

I've been informed that a full re-design of this system is actually on the table, and I posted a proposal for that here: #99996 (comment)

Preface

We all know that material slots are wack. In this task I'd like to make sure that these issues are acknowledged and collected.

This is intended as a discussion starter.

I'll present some issues with the current system, try to reverse engineer why it is the way it is, and then I propose a set of fixes to improve things without re-working from the ground up.

Let's go.

Removing Slots: UI

I think an operator to remove all slots from an object is blatantly missing, which makes this the least problematic aspect of the system.

Removing Slots: PyAPI

The only way to remove individual materials in current PyAPI without bpy.ops is Object.data.materials.pop(index=0).

However, this call behaves with complete insanity when there are material slots present which are linked to the Object, rather than the Data.

  • If all slots are linked to Data, the call works as expected.
  • If the first 3 slots are linked to Object, the last 2 are linked to Data, then we ask to remove index 1, it will go to the material slot index 1, find that it is linked to Object, therefore it will skip it, and go to the first one that is linked to Data, remove that one, and then fail to return the material which was removed from the Mesh, even though that is normally returned.
  • If it doesn't find any slots that are linked to Data, because all slots are linked to Object, then it will fall back to just removing the
    last slot. Even if we passed index=0, it will remove the last of many elements, without warnings or error.

Adding Slots: UI

Plus Button

Adding a slot via the + button will have that slot linked to whatever the previously active slot was linked to, if there was an active one.
This is not normally done when adding new elements in lists in other areas of Blender.
Also, the default value of the link property is 'DATA', and yet, when adding the first material slot, it gets initialized with link='OBJECT'. Based on the knowledge I gained from creating this post, I think material slots should always default to link='DATA'.

ID Selector Drop-down

Slots created this way always have link='DATA'. Why the change of heart, compared to the + button? Well in any case, I prefer this behaviour.

Adding Slots: PyAPI

The only way to add a slot via Py is my_obj.data.materials.append(None). This does not return the added slot, and it is always link='DATA'.

Also, from the caller's perspective, this is a bit weird. We're adding an element to some list, because through some callback, that will create an element in another list that we actually care about. Or well, that's because as someone who hasn't done a deep dive into this system, you THINK you care about Material Slots on Objects, when in reality, they are kind of useless. The materials list on the Mesh is what's actually the fundamental data that you care about. With that mindset, the call makes a lot more sense.

Why Material Slots are Weird: They are Overrides from the 90's.

This wasn't clear to me until today, but I finally see the light. Material Slots are just an ultra-primitive, and poorly implemented Override system.

Their purpose is to let you to have a single Mesh, used by multiple Objects, and change the material assignments per Object. And that's it. If we didn't want to have that functionality, we wouldn't need Material Slots stored on Objects, and we would just have a Materials list on Meshes, and that would be perfectly fine.

Anyways, I'm not saying we should get rid of this, it's obviously useful, but for now, let me show you some of the utterly broken behaviours you can achieve with the current system.

  1. Unintentionally un-assigning materials from polygons on another user of the mesh:

  2. Shuffling around the material-to-polygon assignments on another user of the mesh:

  3. It's even possible to get an object with no material slots to have a material:

Proposal to leave the system as is, but just fix it

  • Remove MaterialSlot.link property.
  • Add a read-only MaterialSlot.original_material, which is actually a function that returns the material that the mesh has in this slot.
  • Add an editable MaterialSlot.override_material, which is what it says.
  • MaterialSlot.material becomes read-only, and returns the override_material unless it is None, in which case it returns the original_material, even if it is None. Add-on devs need to change material assignments to use override_material.
  • The UI should display the original_material and the override_material side by side. If override_material is provided, original_material can be grayed out.
  • Do a better job of keeping the MaterialSlot list in sync with the Material list of the Mesh:
    • When a Material is removed from the Mesh, remove the corresponding slot from all Objects using this Mesh.
    • MaterialSlot entries cannot be added or removed via UI or Py, only their material can be assigned.
    • When an Object is switched to a different Mesh, re-initialize the correct number of empty MaterialSlots.
    • When a Material is added to a Mesh, ALWAYS add a new slot to all Objects using this mesh.
    • When a Material is re-ordered on a Mesh, perform the same re-ordering on the MaterialSlots of all Objects using this mesh.

I believe this set of changes would fix all of the above issues, without re-working the system from scratch.

# Update I've been informed that a full re-design of this system is actually on the table, and I posted a proposal for that here: https://projects.blender.org/blender/blender/issues/99996#issuecomment-1021995 ### Preface We all know that material slots are wack. In this task I'd like to make sure that these issues are acknowledged and collected. This is intended as a discussion starter. I'll present some issues with the current system, try to reverse engineer why it is the way it is, and then I propose a set of fixes to improve things without re-working from the ground up. Let's go. ### Removing Slots: UI I think an operator to remove all slots from an object is blatantly missing, which makes this the least problematic aspect of the system. ### Removing Slots: PyAPI The only way to remove individual materials in current PyAPI without bpy.ops is `Object.data.materials.pop(index=0)`. However, this call behaves with complete insanity when there are material slots present which are linked to the Object, rather than the Data. - If all slots are linked to Data, the call works as expected. - If the first 3 slots are linked to Object, the last 2 are linked to Data, then we ask to remove index 1, it will go to the material slot index 1, find that it is linked to Object, therefore it will skip it, and go to the first one that is linked to Data, remove that one, and then fail to return the material which was removed from the Mesh, even though that is normally returned. - If it doesn't find any slots that are linked to Data, because all slots are linked to Object, then it will fall back to just removing the last slot. **Even if we passed `index=0`, it will remove the last of many elements, without warnings or error.** <video src="/attachments/792e41c4-96c6-4a7e-bda9-2fe8d476922e" title="python_material_removal_is_weird.mp4" controls></video> ### Adding Slots: UI #### Plus Button Adding a slot via the + button will have that slot linked to whatever the previously active slot was linked to, if there was an active one. This is not normally done when adding new elements in lists in other areas of Blender. Also, the default value of the `link` property is `'DATA'`, and yet, when adding the first material slot, it gets initialized with `link='OBJECT'`. Based on the knowledge I gained from creating this post, I think material slots should always default to `link='DATA'`. <video src="/attachments/a73082d2-2622-46ef-a188-dd78f87db8e1" title="ui_uses_link_of_active_slot.mp4" controls></video> #### ID Selector Drop-down Slots created this way always have `link='DATA'`. Why the change of heart, compared to the + button? Well in any case, I prefer this behaviour. <video src="/attachments/c06ee691-f788-4c82-897f-ceeee082aea9" title="ui_dropdown_links_to_data_always.mp4" controls></video> ### Adding Slots: PyAPI The only way to add a slot via Py is `my_obj.data.materials.append(None)`. This does not return the added slot, and it is always `link='DATA'`. <video src="/attachments/86e63c88-a093-47ca-bac9-58cf42a5d230" title="pyapi_always_linked_to_data.mp4" controls></video> Also, from the caller's perspective, this is a bit weird. We're adding an element to some list, because through some callback, that will create an element in another list that we actually care about. Or well, that's because as someone who hasn't done a deep dive into this system, you THINK you care about Material Slots on Objects, when in reality, they are kind of useless. The materials list on the Mesh is what's actually the fundamental data that you care about. With that mindset, the call makes a lot more sense. # Why Material Slots are Weird: They are Overrides from the 90's. This wasn't clear to me until today, but I finally see the light. Material Slots are just an ultra-primitive, and poorly implemented Override system. <video src="/attachments/eed57557-9549-4422-a218-2f4e4b95bf6b" title="multi_user_mesh_behaviours.mp4" controls></video> Their purpose is to let you to have a single Mesh, used by multiple Objects, and change the material assignments per Object. And that's it. If we didn't want to have that functionality, we wouldn't need Material Slots stored on Objects, and we would just have a Materials list on Meshes, and that would be perfectly fine. Anyways, I'm not saying we should get rid of this, it's obviously useful, but for now, let me show you some of the utterly broken behaviours you can achieve with the current system. 1. Unintentionally un-assigning materials from polygons on another user of the mesh: <video src="/attachments/4ce28947-9624-4ad0-abe9-63e826daada6" title="multiuser_unintentional_unassigning_polygons.mp4" controls></video> 2. Shuffling around the material-to-polygon assignments on another user of the mesh: <video src="/attachments/915d7ef7-6b99-4f06-b4c2-d38fdb7d79f6" title="multiuser_unintentional_polygon_assignment_shuffle.mp4" controls></video> 3. It's even possible to get an object with no material slots to have a material: <video src="/attachments/88c40f57-2d63-4ba5-9292-5e9f01932149" title="multiuser_material_no_material.mp4" controls></video> ### Proposal to leave the system as is, but just fix it - Remove `MaterialSlot.link` property. - Add a read-only `MaterialSlot.original_material`, which is actually a function that returns the material that the mesh has in this slot. - Add an editable `MaterialSlot.override_material`, which is what it says. - `MaterialSlot.material` becomes read-only, and returns the override_material unless it is None, in which case it returns the original_material, even if it is None. **Add-on devs need to change material assignments to use override_material**. - The UI should display the original_material and the override_material side by side. If override_material is provided, original_material can be grayed out. - Do a better job of keeping the MaterialSlot list in sync with the Material list of the Mesh: - [x] When a Material is removed from the Mesh, remove the corresponding slot from all Objects using this Mesh. - [x] MaterialSlot entries cannot be added or removed via UI or Py, only their material can be assigned. - [x] When an Object is switched to a different Mesh, re-initialize the correct number of empty MaterialSlots. - When a Material is added to a Mesh, ALWAYS add a new slot to all Objects using this mesh. - When a Material is re-ordered on a Mesh, perform the same re-ordering on the MaterialSlots of all Objects using this mesh. ~~I believe this set of changes would fix all of the above issues, without re-working the system from scratch.~~
Demeter Dzadik added the
Type
Design
label 2023-09-14 19:03:10 +02:00
Member

Remove slots that don't have any assigned polygons

Isn't this Remove Unused Slots in the Material Specials menu?
image

If the first 3 slots are linked to Object, the last 2 are linked to Data, then we ask to remove index 3, it will go to the material slot index 3, find that it is linked to Object, therefore it will skip it, and go to the next one that is linked to Data, and remove that one.

Maybe a typo here because index 3 is the 4th material slot which would be the first slot linked to Data in this example. The issue I see here is that removing either index 0, 1, 2 or 3 all remove the slot at index 3 in this case (removing index 4 removes the slot at index 4 as expected).

I made a simple 3.6.2 .blend with pop(index=0) script ready to run that demonstrates the issue: MaterialPopBug_3.6.2.blend. The issue appears to be present even back in 2.79 and 2.80.

It's even possible to get an object with no material slots to have a material:

I actually have no idea how you achieved this. As far as I was aware, mesh Objects using the same Mesh would always have the same number of material slots, that being the number of materials on the Mesh (including those which are None). If I have multiple mesh Objects using the same Mesh and I add or remove material slots from one of them or from the Mesh directly, all Objects using that mesh update to match the new number of slots (regardless of whether I use the operators or the API functions on Mesh.materials). Similarly, if I change the Mesh of a mesh Object to a different Mesh, the material slots update immediately.


It seems there has been some similar discussion around replacing Object-linked materials last year that may be relevant #99996

> Remove slots that don't have any assigned polygons Isn't this `Remove Unused Slots` in the Material Specials menu? ![image](/attachments/1f7a94ee-3dd3-4205-a447-92e4f551f085) > If the first 3 slots are linked to Object, the last 2 are linked to Data, then we ask to remove index 3, it will go to the material slot index 3, find that it is linked to Object, therefore it will skip it, and go to the next one that is linked to Data, and remove that one. Maybe a typo here because index 3 is the 4th material slot which would be the first slot linked to Data in this example. The issue I see here is that removing either index 0, 1, 2 or 3 all remove the slot at index 3 in this case (removing index 4 removes the slot at index 4 as expected). I made a simple 3.6.2 .blend with `pop(index=0)` script ready to run that demonstrates the issue: [MaterialPopBug_3.6.2.blend](/attachments/70db23b9-77ad-4278-ad05-fa7386c12bf5). The issue appears to be present even back in 2.79 and 2.80. > It's even possible to get an object with no material slots to have a material: I actually have no idea how you achieved this. As far as I was aware, mesh Objects using the same Mesh would always have the same number of material slots, that being the number of materials on the Mesh (including those which are `None`). If I have multiple mesh Objects using the same Mesh and I add or remove material slots from one of them or from the Mesh directly, all Objects using that mesh update to match the new number of slots (regardless of whether I use the operators or the API functions on `Mesh.materials`). Similarly, if I change the Mesh of a mesh Object to a different Mesh, the material slots update immediately. --- It seems there has been some similar discussion around replacing Object-linked materials last year that may be relevant https://projects.blender.org/blender/blender/issues/99996
Author
Member

Thanks a lot for reading through my mad ramblings!

It seems there has been some similar discussion around replacing Object-linked materials last year that may be relevant

I wasn't aware of this, and tbh it kinda makes all of my efforts here irrelevant. But you know what, now I know everything there is to know about this silly deprecated death row system from a user POV that has confused the hell out of me for the past 7 years, and that brings me great satisfaction.

@HooglyBoogly If there's anything I can do to help #99996 move forward, let me know.

This task can proooobably be closed, but I'll respond to your other points still.


Isn't this Remove Unused Slots in the Material Specials menu?

I'm dumb, I thought this was from an add-on that I had installed, LOL. My bad, I removed this from the task now.

Maybe a typo here because index 3 is the 4th material slot

You are correct, thank you, I fixed the text and also replaced the video demonstration for more clarity.

mesh Objects using the same Mesh would always have the same number of material slots

I agree this seems to be the intention, but technically all it takes to break it is to spawn a cube and press the + icon.

  • When using the + button with either no materials yet, or with an active material which has link='OBJECT', the slot will only be created on the active object, and none other. So, the slots fall out of sync as easily as that.
  • Then, just use slot re-ordering to wreak havoc.

So, repro steps look like this:

  • 2 objects, same data, called A and B, no materials.
  • Add 2 slots to A with the + button. (B is already out of sync, that's all it took)
  • Change the first slot to Data. (Slots are still not synced.)
  • Assign material Red (Slots now finally got synced)
  • Make sure the 2nd slot is Active (the one linked to Object, with nothing assigned)
  • Hit + again. Now object A has 3 slots, and B has 2.
  • Select first slot, which we set to Data and assigned Red.
  • Move it down, x2. This gets synced with B actually, except it only has 2 slots, so now it has no materials in the list, but it's still red.
  • Remove first 2 slots from A. This also gets synced with B, leaving it with 0 material slots, but it's still red.
Thanks a lot for reading through my mad ramblings! > It seems there has been some similar discussion around replacing Object-linked materials last year that may be relevant I wasn't aware of this, and tbh it kinda makes all of my efforts here irrelevant. But you know what, now I know everything there is to know about this silly deprecated death row system from a user POV that has confused the hell out of me for the past 7 years, and that brings me great satisfaction. @HooglyBoogly If there's anything I can do to help https://projects.blender.org/blender/blender/issues/99996 move forward, let me know. This task can proooobably be closed, but I'll respond to your other points still. ------------------------------------------- > Isn't this Remove Unused Slots in the Material Specials menu? I'm dumb, I thought this was from an add-on that I had installed, LOL. My bad, I removed this from the task now. > Maybe a typo here because index 3 is the 4th material slot You are correct, thank you, I fixed the text and also replaced the video demonstration for more clarity. > mesh Objects using the same Mesh would always have the same number of material slots I agree this seems to be the intention, but technically all it takes to break it is to spawn a cube and press the + icon. - When using the + button with either no materials yet, or with an active material which has `link='OBJECT'`, the slot will only be created on the active object, and none other. So, the slots fall out of sync as easily as that. - Then, just use slot re-ordering to wreak havoc. So, repro steps look like this: - 2 objects, same data, called A and B, no materials. - Add 2 slots to A with the + button. (B is already out of sync, that's all it took) - Change the first slot to Data. (Slots are still not synced.) - Assign material Red (Slots now finally got synced) - Make sure the 2nd slot is Active (the one linked to Object, with nothing assigned) - Hit + again. Now object A has 3 slots, and B has 2. - Select first slot, which we set to Data and assigned Red. - Move it down, x2. This gets synced with B actually, except it only has 2 slots, so now it has no materials in the list, but it's still red. - Remove first 2 slots from A. This also gets synced with B, leaving it with 0 material slots, but it's still red. <video src="/attachments/fea33de7-1eb7-4b52-916d-dc06e6c560ac" title="plus_button_makes_slots_out_of_sync.mp4" controls></video>
Member

They are Overrides from the 90's.
Their purpose is to let you to have a single Mesh, used by multiple Objects, and change the material assignments per Object

I think this is the essential part of why object-linked materials don't make sense in Blender. They're not just redundant with overrides though, they're also redundant with modifiers. Modifiers are how we change geometry data per object. The simplicity of that core concept is so powerful, and the fact that object-linked materials don't work like that causes so many problems, many of which you've mentioned here. The simplicity of the core design and data structures is broken.

@HooglyBoogly If there's anything I can do to help #99996 move forward, let me know.

It would be awesome it you could help with that. Working with you to turn that into an actionable design and get buy-in would be great. Currently I think the best option is to turn it into a modifier (geometry nodes or a builtin modifier). It seems easy to convince developers, but the UX has to be satisfying too, and it needs to work in the important situations object-linked materials do now.

That design task should be improved. It could be much more persuasive and it could give a clearer proposal for what the replacement(s) should look like for each use case.

>They are Overrides from the 90's. >Their purpose is to let you to have a single Mesh, used by multiple Objects, and change the material assignments per Object I think this is the essential part of why object-linked materials don't make sense in Blender. They're not just redundant with overrides though, they're also redundant with modifiers. Modifiers are how we change geometry data per object. The simplicity of that core concept is so powerful, and the fact that object-linked materials don't work like that causes so many problems, many of which you've mentioned here. The simplicity of the core design and data structures is broken. >@HooglyBoogly If there's anything I can do to help #99996 move forward, let me know. It would be awesome it you could help with that. Working with you to turn that into an actionable design and get buy-in would be great. Currently I think the best option is to turn it into a modifier (geometry nodes or a builtin modifier). It seems easy to convince developers, but the UX has to be satisfying too, and it needs to work in the important situations object-linked materials do now. That design task should be improved. It could be much more persuasive and it could give a clearer proposal for what the replacement(s) should look like for each use case.
Author
Member

I posted a proposal in that thread: #99996 (comment)
I'll close this, let's continue there.

I posted a proposal in that thread: https://projects.blender.org/blender/blender/issues/99996#issuecomment-1021995 I'll close this, let's continue there.
Blender Bot added the
Status
Archived
label 2023-09-15 14:56:58 +02:00
Author
Member

I also just realized that the reason my materials were assigned to Object by default is because of a user preference that I guess I must've changed at some point in the past 8 years, lol. Whoops.

I also just realized that the reason my materials were assigned to Object by default is because of a user preference that I guess I must've changed at some point in the past 8 years, lol. Whoops.
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
3 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#112391
No description provided.