2023-08-16 00:20:26 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
2023-05-31 16:19:06 +02:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
2020-12-02 13:25:25 +01:00
|
|
|
|
|
|
|
|
/** \file
|
|
|
|
|
* \ingroup modifiers
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <cstring>
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
#include <fmt/format.h>
|
Cleanup: fewer iostreams related includes from BLI/BKE headers
Including <iostream> or similar headers is quite expensive, since it
also pulls in things like <locale> and so on. In many BLI headers,
iostreams are only used to implement some sort of "debug print",
or an operator<< for ostream.
Change some of the commonly used places to instead include <iosfwd>,
which is the standard way of forward-declaring iostreams related
classes, and move the actual debug-print / operator<< implementations
into .cc files.
This is not done for templated classes though (it would be possible
to provide explicit operator<< instantiations somewhere in the
source file, but that would lead to hard-to-figure-out linker error
whenever someone would add a different template type). There, where
possible, I changed from full <iostream> include to only the needed
<ostream> part.
For Span<T>, I just removed print_as_lines since it's not used by
anything. It could be moved into a .cc file using a similar approach
as above if needed.
Doing full blender build changes include counts this way:
- <iostream> 1986 -> 978
- <sstream> 2880 -> 925
It does not affect the total build time much though, mostly because
towards the end of it there's just several CPU cores finishing
compiling OpenVDB related source files.
Pull Request: https://projects.blender.org/blender/blender/pulls/111046
2023-08-11 12:27:56 +03:00
|
|
|
#include <sstream>
|
2020-12-02 13:25:25 +01:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
|
|
|
|
#include "BLI_listbase.h"
|
2021-04-15 08:57:10 +02:00
|
|
|
#include "BLI_multi_value_map.hh"
|
2020-12-02 13:25:25 +01:00
|
|
|
#include "BLI_set.hh"
|
|
|
|
|
#include "BLI_string.h"
|
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
|
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
#include "DNA_array_utils.hh"
|
2020-12-15 10:47:58 +11:00
|
|
|
#include "DNA_collection_types.h"
|
2022-07-08 14:45:48 +02:00
|
|
|
#include "DNA_curves_types.h"
|
2020-12-02 13:25:25 +01:00
|
|
|
#include "DNA_defaults.h"
|
2021-05-27 11:06:08 -04:00
|
|
|
#include "DNA_material_types.h"
|
2020-12-02 13:25:25 +01:00
|
|
|
#include "DNA_mesh_types.h"
|
|
|
|
|
#include "DNA_modifier_types.h"
|
|
|
|
|
#include "DNA_node_types.h"
|
|
|
|
|
#include "DNA_object_types.h"
|
|
|
|
|
#include "DNA_pointcloud_types.h"
|
|
|
|
|
#include "DNA_scene_types.h"
|
|
|
|
|
#include "DNA_screen_types.h"
|
2021-04-08 17:35:06 +02:00
|
|
|
#include "DNA_space_types.h"
|
Geometry Nodes: viewport preview
This adds support for showing geometry passed to the Viewer in the 3d
viewport (instead of just in the spreadsheet). The "viewer geometry"
bypasses the group output. So it is not necessary to change the final
output of the node group to be able to see the intermediate geometry.
**Activation and deactivation of a viewer node**
* A viewer node is activated by clicking on it.
* Ctrl+shift+click on any node/socket connects it to the viewer and
makes it active.
* Ctrl+shift+click in empty space deactivates the active viewer.
* When the active viewer is not visible anymore (e.g. another object
is selected, or the current node group is exit), it is deactivated.
* Clicking on the icon in the header of the Viewer node toggles whether
its active or not.
**Pinning**
* The spreadsheet still allows pinning the active viewer as before.
When pinned, the spreadsheet still references the viewer node even
when it becomes inactive.
* The viewport does not support pinning at the moment. It always shows
the active viewer.
**Attribute**
* When a field is linked to the second input of the viewer node it is
displayed as an overlay in the viewport.
* When possible the correct domain for the attribute is determined
automatically. This does not work in all cases. It falls back to the
face corner domain on meshes and the point domain on curves. When
necessary, the domain can be picked manually.
* The spreadsheet now only shows the "Viewer" column for the domain
that is selected in the Viewer node.
* Instance attributes are visualized as a constant color per instance.
**Viewport Options**
* The attribute overlay opacity can be controlled with the "Viewer Node"
setting in the overlays popover.
* A viewport can be configured not to show intermediate viewer-geometry
by disabling the "Viewer Node" option in the "View" menu.
**Implementation Details**
* The "spreadsheet context path" was generalized to a "viewer path" that
is used in more places now.
* The viewer node itself determines the attribute domain, evaluates the
field and stores the result in a `.viewer` attribute.
* A new "viewer attribute' overlay displays the data from the `.viewer`
attribute.
* The ground truth for the active viewer node is stored in the workspace
now. Node editors, spreadsheets and viewports retrieve the active
viewer from there unless they are pinned.
* The depsgraph object iterator has a new "viewer path" setting. When set,
the viewed geometry of the corresponding object is part of the iterator
instead of the final evaluated geometry.
* To support the instance attribute overlay `DupliObject` was extended
to contain the information necessary for drawing the overlay.
* The ctrl+shift+click operator has been refactored so that it can make
existing links to viewers active again.
* The auto-domain-detection in the Viewer node works by checking the
"preferred domain" for every field input. If there is not exactly one
preferred domain, the fallback is used.
Known limitations:
* Loose edges of meshes don't have the attribute overlay. This could be
added separately if necessary.
* Some attributes are hard to visualize as a color directly. For example,
the values might have to be normalized or some should be drawn as arrays.
For now, we encourage users to build node groups that generate appropriate
viewer-geometry. We might include some of that functionality in future versions.
Support for displaying attribute values as text in the viewport is planned as well.
* There seems to be an issue with the attribute overlay for pointclouds on
nvidia gpus, to be investigated.
Differential Revision: https://developer.blender.org/D15954
2022-09-28 17:54:59 +02:00
|
|
|
#include "DNA_view3d_types.h"
|
2021-04-08 17:35:06 +02:00
|
|
|
#include "DNA_windowmanager_types.h"
|
2020-12-02 13:25:25 +01:00
|
|
|
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
#include "BKE_bake_data_block_map.hh"
|
2023-09-03 16:14:11 +02:00
|
|
|
#include "BKE_bake_geometry_nodes_modifier.hh"
|
2025-04-14 17:47:56 +02:00
|
|
|
#include "BKE_compute_context_cache.hh"
|
2022-09-13 08:44:26 +02:00
|
|
|
#include "BKE_compute_contexts.hh"
|
2023-11-16 11:41:55 +01:00
|
|
|
#include "BKE_customdata.hh"
|
2024-02-10 18:25:14 +01:00
|
|
|
#include "BKE_global.hh"
|
2024-03-26 12:57:30 -04:00
|
|
|
#include "BKE_idprop.hh"
|
2024-01-15 12:44:04 -05:00
|
|
|
#include "BKE_lib_id.hh"
|
2024-01-18 12:20:42 +01:00
|
|
|
#include "BKE_lib_query.hh"
|
2023-12-01 19:43:16 +01:00
|
|
|
#include "BKE_main.hh"
|
2023-03-12 22:29:15 +01:00
|
|
|
#include "BKE_mesh.hh"
|
2023-11-14 09:30:40 +01:00
|
|
|
#include "BKE_modifier.hh"
|
2025-01-09 20:03:08 +01:00
|
|
|
#include "BKE_node_legacy_types.hh"
|
2022-05-30 12:54:07 +02:00
|
|
|
#include "BKE_node_runtime.hh"
|
2023-11-16 11:41:55 +01:00
|
|
|
#include "BKE_node_tree_update.hh"
|
2023-10-09 23:41:53 +02:00
|
|
|
#include "BKE_object.hh"
|
2024-09-20 16:18:12 +02:00
|
|
|
#include "BKE_packedFile.hh"
|
2024-01-11 10:54:47 +01:00
|
|
|
#include "BKE_pointcloud.hh"
|
2023-09-25 17:48:21 -04:00
|
|
|
#include "BKE_screen.hh"
|
2024-04-12 17:03:18 -04:00
|
|
|
#include "BKE_workspace.hh"
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2023-08-28 15:01:05 +02:00
|
|
|
#include "BLO_read_write.hh"
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2021-10-21 13:54:48 -05:00
|
|
|
#include "UI_interface.hh"
|
2023-08-05 02:57:52 +02:00
|
|
|
#include "UI_resources.hh"
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2024-02-09 18:59:42 +01:00
|
|
|
#include "BLT_translation.hh"
|
2021-09-21 17:17:40 -05:00
|
|
|
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
#include "WM_api.hh"
|
2023-08-04 23:11:22 +02:00
|
|
|
#include "WM_types.hh"
|
2021-09-16 20:49:10 -05:00
|
|
|
|
2023-08-10 22:40:27 +02:00
|
|
|
#include "RNA_access.hh"
|
|
|
|
|
#include "RNA_enum_types.hh"
|
2024-07-10 18:30:02 +02:00
|
|
|
#include "RNA_prototypes.hh"
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2023-09-22 03:18:17 +02:00
|
|
|
#include "DEG_depsgraph_build.hh"
|
|
|
|
|
#include "DEG_depsgraph_query.hh"
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
#include "DEG_depsgraph_writeback_sync.hh"
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2023-05-04 18:35:37 +02:00
|
|
|
#include "MOD_modifiertypes.hh"
|
2023-07-10 13:14:15 +02:00
|
|
|
#include "MOD_nodes.hh"
|
2023-05-04 18:35:37 +02:00
|
|
|
#include "MOD_ui_common.hh"
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2025-04-15 15:24:29 +02:00
|
|
|
#include "ED_node.hh"
|
2023-08-05 02:57:52 +02:00
|
|
|
#include "ED_object.hh"
|
2023-08-04 23:11:22 +02:00
|
|
|
#include "ED_screen.hh"
|
2023-08-05 02:57:52 +02:00
|
|
|
#include "ED_undo.hh"
|
Geometry Nodes: viewport preview
This adds support for showing geometry passed to the Viewer in the 3d
viewport (instead of just in the spreadsheet). The "viewer geometry"
bypasses the group output. So it is not necessary to change the final
output of the node group to be able to see the intermediate geometry.
**Activation and deactivation of a viewer node**
* A viewer node is activated by clicking on it.
* Ctrl+shift+click on any node/socket connects it to the viewer and
makes it active.
* Ctrl+shift+click in empty space deactivates the active viewer.
* When the active viewer is not visible anymore (e.g. another object
is selected, or the current node group is exit), it is deactivated.
* Clicking on the icon in the header of the Viewer node toggles whether
its active or not.
**Pinning**
* The spreadsheet still allows pinning the active viewer as before.
When pinned, the spreadsheet still references the viewer node even
when it becomes inactive.
* The viewport does not support pinning at the moment. It always shows
the active viewer.
**Attribute**
* When a field is linked to the second input of the viewer node it is
displayed as an overlay in the viewport.
* When possible the correct domain for the attribute is determined
automatically. This does not work in all cases. It falls back to the
face corner domain on meshes and the point domain on curves. When
necessary, the domain can be picked manually.
* The spreadsheet now only shows the "Viewer" column for the domain
that is selected in the Viewer node.
* Instance attributes are visualized as a constant color per instance.
**Viewport Options**
* The attribute overlay opacity can be controlled with the "Viewer Node"
setting in the overlays popover.
* A viewport can be configured not to show intermediate viewer-geometry
by disabling the "Viewer Node" option in the "View" menu.
**Implementation Details**
* The "spreadsheet context path" was generalized to a "viewer path" that
is used in more places now.
* The viewer node itself determines the attribute domain, evaluates the
field and stores the result in a `.viewer` attribute.
* A new "viewer attribute' overlay displays the data from the `.viewer`
attribute.
* The ground truth for the active viewer node is stored in the workspace
now. Node editors, spreadsheets and viewports retrieve the active
viewer from there unless they are pinned.
* The depsgraph object iterator has a new "viewer path" setting. When set,
the viewed geometry of the corresponding object is part of the iterator
instead of the final evaluated geometry.
* To support the instance attribute overlay `DupliObject` was extended
to contain the information necessary for drawing the overlay.
* The ctrl+shift+click operator has been refactored so that it can make
existing links to viewers active again.
* The auto-domain-detection in the Viewer node works by checking the
"preferred domain" for every field input. If there is not exactly one
preferred domain, the fallback is used.
Known limitations:
* Loose edges of meshes don't have the attribute overlay. This could be
added separately if necessary.
* Some attributes are hard to visualize as a color directly. For example,
the values might have to be normalized or some should be drawn as arrays.
For now, we encourage users to build node groups that generate appropriate
viewer-geometry. We might include some of that functionality in future versions.
Support for displaying attribute values as text in the viewport is planned as well.
* There seems to be an issue with the attribute overlay for pointclouds on
nvidia gpus, to be investigated.
Differential Revision: https://developer.blender.org/D15954
2022-09-28 17:54:59 +02:00
|
|
|
#include "ED_viewer_path.hh"
|
2021-04-15 08:57:10 +02:00
|
|
|
|
2023-06-23 22:15:42 +02:00
|
|
|
#include "NOD_geometry.hh"
|
2024-12-05 18:02:14 +01:00
|
|
|
#include "NOD_geometry_nodes_dependencies.hh"
|
2023-06-12 13:31:09 +02:00
|
|
|
#include "NOD_geometry_nodes_execute.hh"
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
#include "NOD_geometry_nodes_gizmos.hh"
|
2022-09-13 08:44:26 +02:00
|
|
|
#include "NOD_geometry_nodes_lazy_function.hh"
|
2021-09-28 12:05:42 -05:00
|
|
|
#include "NOD_node_declaration.hh"
|
2025-01-21 12:53:24 +01:00
|
|
|
#include "NOD_socket_usage_inference.hh"
|
2021-08-20 13:14:39 +02:00
|
|
|
|
2022-09-13 08:44:26 +02:00
|
|
|
namespace lf = blender::fn::lazy_function;
|
2023-03-31 13:19:37 -04:00
|
|
|
namespace geo_log = blender::nodes::geo_eval_log;
|
2023-09-03 16:14:11 +02:00
|
|
|
namespace bake = blender::bke::bake;
|
2022-09-13 08:44:26 +02:00
|
|
|
|
2023-03-31 13:19:37 -04:00
|
|
|
namespace blender {
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void init_data(ModifierData *md)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
NodesModifierData *nmd = (NodesModifierData *)md;
|
|
|
|
|
|
|
|
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(nmd, modifier));
|
2024-10-01 15:31:50 +02:00
|
|
|
nmd->modifier.layout_panel_open_flag |= 1 << NODES_MODIFIER_PANEL_WARNINGS;
|
2020-12-02 13:25:25 +01:00
|
|
|
|
|
|
|
|
MEMCPY_STRUCT_AFTER(nmd, DNA_struct_default_get(NodesModifierData), modifier);
|
2023-07-11 12:55:57 +02:00
|
|
|
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
2023-09-03 16:14:11 +02:00
|
|
|
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-05 18:02:14 +01:00
|
|
|
static void find_dependencies_from_settings(const NodesModifierSettings &settings,
|
|
|
|
|
nodes::GeometryNodesEvalDependencies &deps)
|
2021-01-13 08:13:57 -06:00
|
|
|
{
|
2024-03-26 13:36:30 -04:00
|
|
|
IDP_foreach_property(settings.properties, IDP_TYPE_FILTER_ID, [&](IDProperty *property) {
|
|
|
|
|
if (ID *id = IDP_Id(property)) {
|
2024-12-05 18:02:14 +01:00
|
|
|
deps.add_generic_id_full(id);
|
2024-03-26 13:36:30 -04:00
|
|
|
}
|
|
|
|
|
});
|
2021-01-13 08:13:57 -06:00
|
|
|
}
|
|
|
|
|
|
2021-04-26 16:35:22 +02:00
|
|
|
/* We don't know exactly what attributes from the other object we will need. */
|
|
|
|
|
static const CustomData_MeshMasks dependency_data_mask{CD_MASK_PROP_ALL | CD_MASK_MDEFORMVERT,
|
|
|
|
|
CD_MASK_PROP_ALL,
|
|
|
|
|
CD_MASK_PROP_ALL,
|
|
|
|
|
CD_MASK_PROP_ALL,
|
|
|
|
|
CD_MASK_PROP_ALL};
|
|
|
|
|
|
|
|
|
|
static void add_collection_relation(const ModifierUpdateDepsgraphContext *ctx,
|
|
|
|
|
Collection &collection)
|
|
|
|
|
{
|
|
|
|
|
DEG_add_collection_geometry_relation(ctx->node, &collection, "Nodes Modifier");
|
|
|
|
|
DEG_add_collection_geometry_customdata_mask(ctx->node, &collection, &dependency_data_mask);
|
|
|
|
|
}
|
2021-02-22 20:26:51 +01:00
|
|
|
|
2024-12-05 18:02:14 +01:00
|
|
|
static void add_object_relation(
|
|
|
|
|
const ModifierUpdateDepsgraphContext *ctx,
|
|
|
|
|
Object &object,
|
|
|
|
|
const nodes::GeometryNodesEvalDependencies::ObjectDependencyInfo &info)
|
2021-02-12 12:03:38 -06:00
|
|
|
{
|
2024-12-05 18:02:14 +01:00
|
|
|
if (info.transform) {
|
|
|
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
|
|
|
|
|
}
|
|
|
|
|
if (&(ID &)object == &ctx->object->id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (info.geometry) {
|
2021-04-26 16:35:22 +02:00
|
|
|
if (object.type == OB_EMPTY && object.instance_collection != nullptr) {
|
|
|
|
|
add_collection_relation(ctx, *object.instance_collection);
|
2021-02-22 20:26:51 +01:00
|
|
|
}
|
2021-05-26 16:06:01 +02:00
|
|
|
else if (DEG_object_has_geometry_component(&object)) {
|
2021-02-12 12:03:38 -06:00
|
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier");
|
2021-04-26 16:35:22 +02:00
|
|
|
DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask);
|
2021-02-12 12:03:38 -06:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-28 22:54:13 +01:00
|
|
|
if (object.type == OB_CAMERA) {
|
|
|
|
|
if (info.camera_parameters) {
|
|
|
|
|
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_PARAMETERS, "Nodes Modifier");
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-12 12:03:38 -06:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
2022-01-31 15:27:35 -06:00
|
|
|
if (nmd->node_group == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-12-09 13:42:07 +01:00
|
|
|
if (ID_MISSING(nmd->node_group)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-01-31 15:27:35 -06:00
|
|
|
|
|
|
|
|
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Nodes Modifier");
|
|
|
|
|
|
2024-12-05 18:02:14 +01:00
|
|
|
nodes::GeometryNodesEvalDependencies eval_deps =
|
2024-12-10 17:52:44 +01:00
|
|
|
nodes::gather_geometry_nodes_eval_dependencies_recursive(*nmd->node_group);
|
2024-12-05 18:02:14 +01:00
|
|
|
|
|
|
|
|
/* Create dependencies to data-blocks referenced by the settings in the modifier. */
|
|
|
|
|
find_dependencies_from_settings(nmd->settings, eval_deps);
|
2022-07-08 14:45:48 +02:00
|
|
|
|
|
|
|
|
if (ctx->object->type == OB_CURVES) {
|
|
|
|
|
Curves *curves_id = static_cast<Curves *>(ctx->object->data);
|
|
|
|
|
if (curves_id->surface != nullptr) {
|
2024-12-05 18:02:14 +01:00
|
|
|
eval_deps.add_object(curves_id->surface);
|
2022-07-08 14:45:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
|
|
|
|
|
for (const NodesModifierDataBlock &data_block : Span(bake.data_blocks, bake.data_blocks_num)) {
|
|
|
|
|
if (data_block.id) {
|
2024-12-05 18:02:14 +01:00
|
|
|
eval_deps.add_generic_id_full(data_block.id);
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-05 18:02:14 +01:00
|
|
|
for (ID *id : eval_deps.ids.values()) {
|
2022-01-31 15:27:35 -06:00
|
|
|
switch ((ID_Type)GS(id->name)) {
|
|
|
|
|
case ID_OB: {
|
|
|
|
|
Object *object = reinterpret_cast<Object *>(id);
|
2024-12-05 18:02:14 +01:00
|
|
|
add_object_relation(
|
|
|
|
|
ctx, *object, eval_deps.objects_info.lookup_default(object->id.session_uid, {}));
|
2022-01-31 15:27:35 -06:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ID_GR: {
|
|
|
|
|
Collection *collection = reinterpret_cast<Collection *>(id);
|
|
|
|
|
add_collection_relation(ctx, *collection);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ID_IM:
|
|
|
|
|
case ID_TE: {
|
|
|
|
|
DEG_add_generic_id_relation(ctx->node, id, "Nodes Modifier");
|
2022-08-31 15:58:33 +10:00
|
|
|
break;
|
2022-01-31 15:27:35 -06:00
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
/* Purposefully don't add relations for materials. While there are material sockets,
|
|
|
|
|
* the pointers are only passed around as handles rather than dereferenced. */
|
|
|
|
|
break;
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-01 16:27:29 -06:00
|
|
|
|
2024-12-05 18:02:14 +01:00
|
|
|
if (eval_deps.needs_own_transform) {
|
2022-08-04 12:11:31 +02:00
|
|
|
DEG_add_depends_on_transform_relation(ctx->node, "Nodes Modifier");
|
2022-02-01 16:27:29 -06:00
|
|
|
}
|
2024-12-05 18:02:14 +01:00
|
|
|
if (eval_deps.needs_active_camera) {
|
2023-12-12 19:11:06 +01:00
|
|
|
DEG_add_scene_camera_relation(ctx->node, ctx->scene, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
|
2025-03-28 22:54:13 +01:00
|
|
|
}
|
|
|
|
|
/* Active camera is a scene parameter that can change, so we need a relation for that, too. */
|
|
|
|
|
if (eval_deps.needs_active_camera || eval_deps.needs_scene_render_params) {
|
2023-12-12 19:11:06 +01:00
|
|
|
DEG_add_scene_relation(ctx->node, ctx->scene, DEG_SCENE_COMP_PARAMETERS, "Nodes Modifier");
|
|
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static bool depends_on_time(Scene * /*scene*/, ModifierData *md)
|
2021-12-09 11:50:25 -06:00
|
|
|
{
|
2021-12-15 14:51:58 -06:00
|
|
|
const NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
|
const bNodeTree *tree = nmd->node_group;
|
2021-12-09 11:50:25 -06:00
|
|
|
if (tree == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-12-09 13:42:07 +01:00
|
|
|
if (ID_MISSING(tree)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-01-09 11:07:52 +01:00
|
|
|
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
|
|
|
|
|
if (bake.bake_mode == NODES_MODIFIER_BAKE_MODE_ANIMATION) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-12-10 17:52:44 +01:00
|
|
|
nodes::GeometryNodesEvalDependencies eval_deps =
|
|
|
|
|
nodes::gather_geometry_nodes_eval_dependencies_recursive(*nmd->node_group);
|
|
|
|
|
return eval_deps.time_dependent;
|
2021-12-09 11:50:25 -06:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
2023-07-27 12:04:18 +10:00
|
|
|
walk(user_data, ob, (ID **)&nmd->node_group, IDWALK_CB_USER);
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2024-03-26 13:36:30 -04:00
|
|
|
IDP_foreach_property(nmd->settings.properties, IDP_TYPE_FILTER_ID, [&](IDProperty *id_prop) {
|
|
|
|
|
walk(user_data, ob, (ID **)&id_prop->data.pointer, IDWALK_CB_USER);
|
|
|
|
|
});
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
|
|
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
|
|
|
|
for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num))
|
|
|
|
|
{
|
|
|
|
|
walk(user_data, ob, &data_block.id, IDWALK_CB_USER);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data)
|
2021-02-03 14:39:24 +01:00
|
|
|
{
|
2025-01-24 16:45:32 +01:00
|
|
|
PointerRNA ptr = RNA_pointer_create_discrete(&ob->id, &RNA_Modifier, md);
|
2024-08-29 12:08:50 +02:00
|
|
|
PropertyRNA *prop = RNA_struct_find_property(&ptr, "texture");
|
|
|
|
|
walk(user_data, ob, md, &ptr, prop);
|
2021-02-03 14:39:24 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
|
|
|
|
|
|
if (nmd->node_group == nullptr) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-22 12:48:59 +02:00
|
|
|
static bool logging_enabled(const ModifierEvalContext *ctx)
|
|
|
|
|
{
|
|
|
|
|
if (!DEG_is_active(ctx->depsgraph)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ((ctx->flag & MOD_APPLY_ORCO) != 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:30:46 +02:00
|
|
|
static void update_id_properties_from_node_group(NodesModifierData *nmd)
|
2023-03-30 18:51:25 -04:00
|
|
|
{
|
|
|
|
|
if (nmd->node_group == nullptr) {
|
|
|
|
|
if (nmd->settings.properties) {
|
|
|
|
|
IDP_FreeProperty(nmd->settings.properties);
|
|
|
|
|
nmd->settings.properties = nullptr;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IDProperty *old_properties = nmd->settings.properties;
|
2024-03-26 15:39:34 -04:00
|
|
|
nmd->settings.properties = bke::idprop::create_group("Nodes Modifier Settings").release();
|
2023-03-30 18:51:25 -04:00
|
|
|
IDProperty *new_properties = nmd->settings.properties;
|
|
|
|
|
|
2024-03-08 17:49:09 +01:00
|
|
|
nodes::update_input_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
|
2023-06-12 13:31:09 +02:00
|
|
|
nodes::update_output_properties_from_node_tree(
|
|
|
|
|
*nmd->node_group, old_properties, *new_properties);
|
2021-09-27 15:33:48 +02:00
|
|
|
|
2020-12-02 13:25:25 +01:00
|
|
|
if (old_properties != nullptr) {
|
|
|
|
|
IDP_FreeProperty(old_properties);
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-29 11:42:34 +01:00
|
|
|
static void remove_outdated_bake_caches(NodesModifierData &nmd)
|
2023-09-26 20:30:46 +02:00
|
|
|
{
|
|
|
|
|
if (!nmd.runtime->cache) {
|
|
|
|
|
if (nmd.bakes_num == 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
nmd.runtime->cache = std::make_shared<bake::ModifierCache>();
|
|
|
|
|
}
|
|
|
|
|
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
|
|
|
|
|
std::lock_guard lock{modifier_cache.mutex};
|
|
|
|
|
|
2024-01-29 11:42:34 +01:00
|
|
|
Set<int> existing_bake_ids;
|
|
|
|
|
for (const NodesModifierBake &bake : Span{nmd.bakes, nmd.bakes_num}) {
|
|
|
|
|
existing_bake_ids.add(bake.id);
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
2024-01-09 11:01:12 +01:00
|
|
|
|
2024-01-29 11:42:34 +01:00
|
|
|
auto remove_predicate = [&](auto item) { return !existing_bake_ids.contains(item.key); };
|
|
|
|
|
|
|
|
|
|
modifier_cache.bake_cache_by_id.remove_if(remove_predicate);
|
|
|
|
|
modifier_cache.simulation_cache_by_id.remove_if(remove_predicate);
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_bakes_from_node_group(NodesModifierData &nmd)
|
|
|
|
|
{
|
|
|
|
|
Map<int, NodesModifierBake *> old_bake_by_id;
|
|
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
|
|
|
|
|
old_bake_by_id.add(bake.id, &bake);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<int> new_bake_ids;
|
2023-09-26 16:51:00 -04:00
|
|
|
if (nmd.node_group) {
|
|
|
|
|
for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) {
|
|
|
|
|
const bNode *node = nmd.node_group->find_nested_node(ref.id);
|
|
|
|
|
if (node) {
|
2025-01-09 15:28:57 +01:00
|
|
|
if (ELEM(node->type_legacy, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_BAKE)) {
|
2023-09-26 16:51:00 -04:00
|
|
|
new_bake_ids.append(ref.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (old_bake_by_id.contains(ref.id)) {
|
|
|
|
|
/* Keep baked data in case linked data is missing so that it still exists when the linked
|
|
|
|
|
* data has been found. */
|
2023-09-26 20:30:46 +02:00
|
|
|
new_bake_ids.append(ref.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:35:09 +01:00
|
|
|
NodesModifierBake *new_bake_data = MEM_calloc_arrayN<NodesModifierBake>(new_bake_ids.size(),
|
|
|
|
|
__func__);
|
2023-09-26 20:30:46 +02:00
|
|
|
for (const int i : new_bake_ids.index_range()) {
|
|
|
|
|
const int id = new_bake_ids[i];
|
|
|
|
|
NodesModifierBake *old_bake = old_bake_by_id.lookup_default(id, nullptr);
|
|
|
|
|
NodesModifierBake &new_bake = new_bake_data[i];
|
|
|
|
|
if (old_bake) {
|
|
|
|
|
new_bake = *old_bake;
|
2025-01-09 18:19:53 +01:00
|
|
|
/* The ownership of this data was moved to `new_bake`. */
|
2023-09-26 20:30:46 +02:00
|
|
|
old_bake->directory = nullptr;
|
2025-01-09 18:19:53 +01:00
|
|
|
old_bake->data_blocks = nullptr;
|
|
|
|
|
old_bake->data_blocks_num = 0;
|
|
|
|
|
old_bake->packed = nullptr;
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
new_bake.id = id;
|
|
|
|
|
new_bake.frame_start = 1;
|
|
|
|
|
new_bake.frame_end = 100;
|
2024-02-08 17:04:38 +01:00
|
|
|
new_bake.bake_mode = NODES_MODIFIER_BAKE_MODE_STILL;
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (NodesModifierBake &old_bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
|
2025-01-09 18:19:53 +01:00
|
|
|
nodes_modifier_bake_destruct(&old_bake, true);
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(nmd.bakes);
|
|
|
|
|
|
|
|
|
|
nmd.bakes = new_bake_data;
|
|
|
|
|
nmd.bakes_num = new_bake_ids.size();
|
|
|
|
|
|
2024-01-29 11:42:34 +01:00
|
|
|
remove_outdated_bake_caches(nmd);
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-23 16:33:14 +01:00
|
|
|
static void update_panels_from_node_group(NodesModifierData &nmd)
|
|
|
|
|
{
|
|
|
|
|
Map<int, NodesModifierPanel *> old_panel_by_id;
|
|
|
|
|
for (NodesModifierPanel &panel : MutableSpan(nmd.panels, nmd.panels_num)) {
|
|
|
|
|
old_panel_by_id.add(panel.id, &panel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<const bNodeTreeInterfacePanel *> interface_panels;
|
|
|
|
|
if (nmd.node_group) {
|
|
|
|
|
nmd.node_group->ensure_interface_cache();
|
|
|
|
|
nmd.node_group->tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) {
|
|
|
|
|
if (item.item_type != NODE_INTERFACE_PANEL) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
interface_panels.append(reinterpret_cast<const bNodeTreeInterfacePanel *>(&item));
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:35:09 +01:00
|
|
|
NodesModifierPanel *new_panels = MEM_calloc_arrayN<NodesModifierPanel>(interface_panels.size(),
|
|
|
|
|
__func__);
|
2023-12-23 16:33:14 +01:00
|
|
|
|
|
|
|
|
for (const int i : interface_panels.index_range()) {
|
|
|
|
|
const bNodeTreeInterfacePanel &interface_panel = *interface_panels[i];
|
|
|
|
|
const int id = interface_panel.identifier;
|
|
|
|
|
NodesModifierPanel *old_panel = old_panel_by_id.lookup_default(id, nullptr);
|
|
|
|
|
NodesModifierPanel &new_panel = new_panels[i];
|
|
|
|
|
if (old_panel) {
|
|
|
|
|
new_panel = *old_panel;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
new_panel.id = id;
|
|
|
|
|
const bool default_closed = interface_panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED;
|
|
|
|
|
SET_FLAG_FROM_TEST(new_panel.flag, !default_closed, NODES_MODIFIER_PANEL_OPEN);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MEM_SAFE_FREE(nmd.panels);
|
|
|
|
|
|
|
|
|
|
nmd.panels = new_panels;
|
|
|
|
|
nmd.panels_num = interface_panels.size();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:30:46 +02:00
|
|
|
} // namespace blender
|
|
|
|
|
|
|
|
|
|
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
|
|
|
|
|
{
|
|
|
|
|
using namespace blender;
|
|
|
|
|
update_id_properties_from_node_group(nmd);
|
|
|
|
|
update_bakes_from_node_group(*nmd);
|
2023-12-23 16:33:14 +01:00
|
|
|
update_panels_from_node_group(*nmd);
|
2020-12-02 13:25:25 +01:00
|
|
|
|
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:30:46 +02:00
|
|
|
NodesModifierBake *NodesModifierData::find_bake(const int id)
|
|
|
|
|
{
|
|
|
|
|
return const_cast<NodesModifierBake *>(std::as_const(*this).find_bake(id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NodesModifierBake *NodesModifierData::find_bake(const int id) const
|
|
|
|
|
{
|
|
|
|
|
for (const NodesModifierBake &bake : blender::Span{this->bakes, this->bakes_num}) {
|
|
|
|
|
if (bake.id == id) {
|
|
|
|
|
return &bake;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-31 13:19:37 -04:00
|
|
|
namespace blender {
|
|
|
|
|
|
2023-10-03 14:12:12 +02:00
|
|
|
/**
|
|
|
|
|
* Setup side effects nodes so that the given node in the given compute context will be executed.
|
|
|
|
|
* To make sure that it is executed, all parent group nodes and zones have to be set to have side
|
|
|
|
|
* effects as well.
|
|
|
|
|
*/
|
2025-04-16 23:35:59 +02:00
|
|
|
static void try_add_side_effect_node(const ModifierEvalContext &ctx,
|
|
|
|
|
const ComputeContext &final_compute_context,
|
2023-10-03 14:12:12 +02:00
|
|
|
const int final_node_id,
|
|
|
|
|
const NodesModifierData &nmd,
|
|
|
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
2021-04-08 17:35:06 +02:00
|
|
|
{
|
2023-10-03 14:12:12 +02:00
|
|
|
if (nmd.node_group == nullptr) {
|
2021-10-21 15:49:29 +02:00
|
|
|
return;
|
2021-04-15 08:57:10 +02:00
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
|
|
|
|
|
Vector<const ComputeContext *> compute_context_vec;
|
|
|
|
|
for (const ComputeContext *c = &final_compute_context; c; c = c->parent()) {
|
|
|
|
|
compute_context_vec.append(c);
|
|
|
|
|
}
|
|
|
|
|
std::reverse(compute_context_vec.begin(), compute_context_vec.end());
|
|
|
|
|
|
|
|
|
|
const auto *modifier_compute_context = dynamic_cast<const bke::ModifierComputeContext *>(
|
|
|
|
|
compute_context_vec[0]);
|
|
|
|
|
if (modifier_compute_context == nullptr) {
|
2021-10-21 15:49:29 +02:00
|
|
|
return;
|
2021-04-15 08:57:10 +02:00
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
if (modifier_compute_context->modifier_name() != nmd.modifier.name) {
|
2021-10-21 15:49:29 +02:00
|
|
|
return;
|
2021-04-15 08:57:10 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-03 14:12:12 +02:00
|
|
|
const bNodeTree *current_tree = nmd.node_group;
|
|
|
|
|
const bke::bNodeTreeZone *current_zone = nullptr;
|
2021-04-15 08:57:10 +02:00
|
|
|
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
/* Write side effect nodes to a new map and only if everything succeeds, move the nodes to the
|
|
|
|
|
* caller. This is easier than changing r_side_effect_nodes directly and then undoing changes in
|
|
|
|
|
* case of errors. */
|
2023-09-16 18:57:04 +02:00
|
|
|
nodes::GeoNodesSideEffectNodes local_side_effect_nodes;
|
2023-10-03 14:12:12 +02:00
|
|
|
for (const ComputeContext *compute_context_generic : compute_context_vec.as_span().drop_front(1))
|
|
|
|
|
{
|
|
|
|
|
const bke::bNodeTreeZones *current_zones = current_tree->zones();
|
|
|
|
|
if (current_zones == nullptr) {
|
2021-10-21 15:49:29 +02:00
|
|
|
return;
|
2021-04-15 08:57:10 +02:00
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
if (lf_graph_info == nullptr) {
|
2021-10-21 15:49:29 +02:00
|
|
|
return;
|
2021-04-15 08:57:10 +02:00
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
const ComputeContextHash &parent_compute_context_hash =
|
|
|
|
|
compute_context_generic->parent()->hash();
|
|
|
|
|
if (const auto *compute_context = dynamic_cast<const bke::SimulationZoneComputeContext *>(
|
|
|
|
|
compute_context_generic))
|
|
|
|
|
{
|
|
|
|
|
const bke::bNodeTreeZone *simulation_zone = current_zones->get_zone_by_node(
|
|
|
|
|
compute_context->output_node_id());
|
|
|
|
|
if (simulation_zone == nullptr) {
|
|
|
|
|
return;
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
if (simulation_zone->parent_zone != current_zone) {
|
|
|
|
|
return;
|
2023-07-11 22:36:10 +02:00
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
|
|
|
simulation_zone, nullptr);
|
|
|
|
|
if (lf_zone_node == nullptr) {
|
|
|
|
|
return;
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
}
|
2024-02-27 20:37:52 +01:00
|
|
|
const lf::FunctionNode *lf_simulation_output_node =
|
|
|
|
|
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(
|
|
|
|
|
simulation_zone->output_node, nullptr);
|
|
|
|
|
if (lf_simulation_output_node == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
2024-02-27 20:37:52 +01:00
|
|
|
/* By making the simulation output node a side-effect-node, we can ensure that the simulation
|
|
|
|
|
* runs when it contains an active viewer. */
|
|
|
|
|
local_side_effect_nodes.nodes_by_context.add(compute_context_generic->hash(),
|
|
|
|
|
lf_simulation_output_node);
|
|
|
|
|
|
2023-10-03 14:12:12 +02:00
|
|
|
current_zone = simulation_zone;
|
|
|
|
|
}
|
|
|
|
|
else if (const auto *compute_context = dynamic_cast<const bke::RepeatZoneComputeContext *>(
|
|
|
|
|
compute_context_generic))
|
|
|
|
|
{
|
|
|
|
|
const bke::bNodeTreeZone *repeat_zone = current_zones->get_zone_by_node(
|
|
|
|
|
compute_context->output_node_id());
|
|
|
|
|
if (repeat_zone == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (repeat_zone->parent_zone != current_zone) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
|
|
|
repeat_zone, nullptr);
|
|
|
|
|
if (lf_zone_node == nullptr) {
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
2024-09-24 11:52:02 +02:00
|
|
|
local_side_effect_nodes.iterations_by_iteration_zone.add(
|
2023-10-03 14:12:12 +02:00
|
|
|
{parent_compute_context_hash, compute_context->output_node_id()},
|
|
|
|
|
compute_context->iteration());
|
|
|
|
|
current_zone = repeat_zone;
|
|
|
|
|
}
|
2024-09-24 11:52:02 +02:00
|
|
|
else if (const auto *compute_context =
|
|
|
|
|
dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(
|
|
|
|
|
compute_context_generic))
|
|
|
|
|
{
|
|
|
|
|
const bke::bNodeTreeZone *foreach_zone = current_zones->get_zone_by_node(
|
|
|
|
|
compute_context->output_node_id());
|
|
|
|
|
if (foreach_zone == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (foreach_zone->parent_zone != current_zone) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
|
|
|
|
|
foreach_zone, nullptr);
|
|
|
|
|
if (lf_zone_node == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
|
|
|
|
|
local_side_effect_nodes.iterations_by_iteration_zone.add(
|
|
|
|
|
{parent_compute_context_hash, compute_context->output_node_id()},
|
|
|
|
|
compute_context->index());
|
|
|
|
|
current_zone = foreach_zone;
|
|
|
|
|
}
|
2023-11-21 14:12:07 +01:00
|
|
|
else if (const auto *compute_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
|
2023-10-03 14:12:12 +02:00
|
|
|
compute_context_generic))
|
|
|
|
|
{
|
|
|
|
|
const bNode *group_node = current_tree->node_by_id(compute_context->node_id());
|
|
|
|
|
if (group_node == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (group_node->id == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (group_node->is_muted()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (current_zone != current_zones->get_zone_by_node(group_node->identifier)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const lf::FunctionNode *lf_group_node = lf_graph_info->mapping.group_node_map.lookup_default(
|
|
|
|
|
group_node, nullptr);
|
|
|
|
|
if (lf_group_node == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_group_node);
|
|
|
|
|
current_tree = reinterpret_cast<const bNodeTree *>(group_node->id);
|
|
|
|
|
current_zone = nullptr;
|
|
|
|
|
}
|
2025-04-16 23:35:59 +02:00
|
|
|
else if (const auto *compute_context =
|
|
|
|
|
dynamic_cast<const bke::EvaluateClosureComputeContext *>(compute_context_generic))
|
|
|
|
|
{
|
|
|
|
|
const bNode *evaluate_node = current_tree->node_by_id(compute_context->node_id());
|
|
|
|
|
if (!evaluate_node) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (evaluate_node->is_muted()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (current_zone != current_zones->get_zone_by_node(evaluate_node->identifier)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const std::optional<nodes::ClosureSourceLocation> &source_location =
|
|
|
|
|
compute_context->closure_source_location();
|
|
|
|
|
if (!source_location) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!source_location->tree->zones()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const lf::FunctionNode *lf_evaluate_node =
|
|
|
|
|
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(evaluate_node,
|
|
|
|
|
nullptr);
|
|
|
|
|
if (!lf_evaluate_node) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* The tree may sometimes be original and sometimes evaluated, depending on the source of the
|
|
|
|
|
* compute context. */
|
2025-05-02 15:08:29 +02:00
|
|
|
const bNodeTree *eval_closure_tree = DEG_is_evaluated(source_location->tree) ?
|
2025-04-16 23:35:59 +02:00
|
|
|
source_location->tree :
|
|
|
|
|
reinterpret_cast<const bNodeTree *>(
|
|
|
|
|
DEG_get_evaluated_id(
|
|
|
|
|
ctx.depsgraph, &source_location->tree->id));
|
|
|
|
|
const bNode *closure_output_node = eval_closure_tree->node_by_id(
|
|
|
|
|
source_location->closure_output_node_id);
|
|
|
|
|
if (!closure_output_node) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_evaluate_node);
|
|
|
|
|
current_tree = eval_closure_tree;
|
|
|
|
|
current_zone = eval_closure_tree->zones()->get_zone_by_node(closure_output_node->identifier);
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
else {
|
|
|
|
|
return;
|
2022-11-06 15:39:57 +01:00
|
|
|
}
|
2021-04-15 08:57:10 +02:00
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
const bNode *final_node = current_tree->node_by_id(final_node_id);
|
|
|
|
|
if (final_node == nullptr) {
|
2022-09-13 08:44:26 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
if (lf_graph_info == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
const bke::bNodeTreeZones *tree_zones = current_tree->zones();
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
if (tree_zones == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
if (tree_zones->get_zone_by_node(final_node_id) != current_zone) {
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
const lf::FunctionNode *lf_node =
|
|
|
|
|
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(final_node, nullptr);
|
|
|
|
|
if (lf_node == nullptr) {
|
2023-02-22 18:42:15 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2023-10-03 14:12:12 +02:00
|
|
|
local_side_effect_nodes.nodes_by_context.add(final_compute_context.hash(), lf_node);
|
2022-09-13 08:44:26 +02:00
|
|
|
|
2023-09-16 18:57:04 +02:00
|
|
|
/* Successfully found all side effect nodes for the viewer path. */
|
|
|
|
|
for (const auto item : local_side_effect_nodes.nodes_by_context.items()) {
|
|
|
|
|
r_side_effect_nodes.nodes_by_context.add_multiple(item.key, item.value);
|
2022-09-13 08:44:26 +02:00
|
|
|
}
|
2024-09-24 11:52:02 +02:00
|
|
|
for (const auto item : local_side_effect_nodes.iterations_by_iteration_zone.items()) {
|
|
|
|
|
r_side_effect_nodes.iterations_by_iteration_zone.add_multiple(item.key, item.value);
|
2023-09-22 08:58:16 +02:00
|
|
|
}
|
2021-04-08 17:35:06 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-03 14:12:12 +02:00
|
|
|
static void find_side_effect_nodes_for_viewer_path(
|
|
|
|
|
const ViewerPath &viewer_path,
|
|
|
|
|
const NodesModifierData &nmd,
|
|
|
|
|
const ModifierEvalContext &ctx,
|
|
|
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
|
|
|
{
|
|
|
|
|
const std::optional<ed::viewer_path::ViewerPathForGeometryNodesViewer> parsed_path =
|
|
|
|
|
ed::viewer_path::parse_geometry_nodes_viewer(viewer_path);
|
|
|
|
|
if (!parsed_path.has_value()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-04-17 13:09:20 +02:00
|
|
|
if (parsed_path->object != DEG_get_original(ctx.object)) {
|
2023-10-03 14:12:12 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (parsed_path->modifier_name != nmd.modifier.name) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 17:47:56 +02:00
|
|
|
bke::ComputeContextCache compute_context_cache;
|
|
|
|
|
const ComputeContext *current = &compute_context_cache.for_modifier(nullptr, nmd);
|
2023-10-03 14:12:12 +02:00
|
|
|
for (const ViewerPathElem *elem : parsed_path->node_path) {
|
2025-04-14 17:47:56 +02:00
|
|
|
current = ed::viewer_path::compute_context_for_viewer_path_elem(
|
|
|
|
|
*elem, compute_context_cache, current);
|
|
|
|
|
if (!current) {
|
2023-10-03 14:12:12 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-16 23:35:59 +02:00
|
|
|
try_add_side_effect_node(ctx, *current, parsed_path->viewer_node_id, nmd, r_side_effect_nodes);
|
2023-10-03 14:12:12 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
static void find_side_effect_nodes_for_nested_node(
|
2025-04-16 23:35:59 +02:00
|
|
|
const ModifierEvalContext &ctx,
|
2023-12-18 13:01:06 +01:00
|
|
|
const NodesModifierData &nmd,
|
|
|
|
|
const int root_nested_node_id,
|
|
|
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
|
|
|
{
|
2025-04-14 17:47:56 +02:00
|
|
|
bke::ComputeContextCache compute_context_cache;
|
|
|
|
|
const ComputeContext *compute_context = &compute_context_cache.for_modifier(nullptr, nmd);
|
2023-12-18 13:01:06 +01:00
|
|
|
|
|
|
|
|
int nested_node_id = root_nested_node_id;
|
|
|
|
|
const bNodeTree *tree = nmd.node_group;
|
|
|
|
|
while (true) {
|
|
|
|
|
const bNestedNodeRef *ref = tree->find_nested_node_ref(nested_node_id);
|
|
|
|
|
if (!ref) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const bNode *node = tree->node_by_id(ref->path.node_id);
|
|
|
|
|
if (!node) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const bke::bNodeTreeZones *zones = tree->zones();
|
|
|
|
|
if (!zones) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (zones->get_zone_by_node(node->identifier) != nullptr) {
|
|
|
|
|
/* Only top level nodes are allowed here. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (node->is_group()) {
|
|
|
|
|
if (!node->id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-04-14 17:47:56 +02:00
|
|
|
compute_context = &compute_context_cache.for_group_node(compute_context, *node, *tree);
|
2023-12-18 13:01:06 +01:00
|
|
|
tree = reinterpret_cast<const bNodeTree *>(node->id);
|
|
|
|
|
nested_node_id = ref->path.id_in_node;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-04-16 23:35:59 +02:00
|
|
|
try_add_side_effect_node(ctx, *compute_context, ref->path.node_id, nmd, r_side_effect_nodes);
|
2023-12-18 13:01:06 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This ensures that nodes that the user wants to bake are actually evaluated. Otherwise they might
|
|
|
|
|
* not be if they are not connected to the output.
|
|
|
|
|
*/
|
|
|
|
|
static void find_side_effect_nodes_for_baking(const NodesModifierData &nmd,
|
|
|
|
|
const ModifierEvalContext &ctx,
|
|
|
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
|
|
|
|
|
{
|
|
|
|
|
if (!nmd.runtime->cache) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!DEG_is_active(ctx.depsgraph)) {
|
|
|
|
|
/* Only the active depsgraph can bake. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
|
|
|
|
|
for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) {
|
|
|
|
|
if (!modifier_cache.requested_bakes.contains(ref.id)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-04-16 23:35:59 +02:00
|
|
|
find_side_effect_nodes_for_nested_node(ctx, nmd, ref.id, r_side_effect_nodes);
|
2023-12-18 13:01:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
static void find_side_effect_nodes_for_active_gizmos(
|
|
|
|
|
const NodesModifierData &nmd,
|
|
|
|
|
const ModifierEvalContext &ctx,
|
|
|
|
|
const wmWindowManager &wm,
|
|
|
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes,
|
|
|
|
|
Set<ComputeContextHash> &r_socket_log_contexts)
|
|
|
|
|
{
|
2025-04-17 13:09:20 +02:00
|
|
|
Object *object_orig = DEG_get_original(ctx.object);
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
const NodesModifierData &nmd_orig = *reinterpret_cast<const NodesModifierData *>(
|
|
|
|
|
BKE_modifier_get_original(ctx.object, const_cast<ModifierData *>(&nmd.modifier)));
|
2025-04-14 17:47:56 +02:00
|
|
|
bke::ComputeContextCache compute_context_cache;
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
nodes::gizmos::foreach_active_gizmo_in_modifier(
|
|
|
|
|
*object_orig,
|
|
|
|
|
nmd_orig,
|
|
|
|
|
wm,
|
2025-04-14 17:47:56 +02:00
|
|
|
compute_context_cache,
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
[&](const ComputeContext &compute_context,
|
|
|
|
|
const bNode &gizmo_node,
|
|
|
|
|
const bNodeSocket &gizmo_socket) {
|
2025-04-16 23:35:59 +02:00
|
|
|
try_add_side_effect_node(
|
|
|
|
|
ctx, compute_context, gizmo_node.identifier, nmd, r_side_effect_nodes);
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
r_socket_log_contexts.add(compute_context.hash());
|
|
|
|
|
|
|
|
|
|
nodes::gizmos::foreach_compute_context_on_gizmo_path(
|
|
|
|
|
compute_context, gizmo_node, gizmo_socket, [&](const ComputeContext &node_context) {
|
|
|
|
|
/* Make sure that all intermediate sockets are logged. This is necessary to be able
|
|
|
|
|
* to evaluate the nodes in reverse for the gizmo. */
|
|
|
|
|
r_socket_log_contexts.add(node_context.hash());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-16 18:57:04 +02:00
|
|
|
static void find_side_effect_nodes(const NodesModifierData &nmd,
|
|
|
|
|
const ModifierEvalContext &ctx,
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes,
|
|
|
|
|
Set<ComputeContextHash> &r_socket_log_contexts)
|
2021-04-08 17:35:06 +02:00
|
|
|
{
|
2022-09-13 08:44:26 +02:00
|
|
|
Main *bmain = DEG_get_bmain(ctx.depsgraph);
|
Geometry Nodes: viewport preview
This adds support for showing geometry passed to the Viewer in the 3d
viewport (instead of just in the spreadsheet). The "viewer geometry"
bypasses the group output. So it is not necessary to change the final
output of the node group to be able to see the intermediate geometry.
**Activation and deactivation of a viewer node**
* A viewer node is activated by clicking on it.
* Ctrl+shift+click on any node/socket connects it to the viewer and
makes it active.
* Ctrl+shift+click in empty space deactivates the active viewer.
* When the active viewer is not visible anymore (e.g. another object
is selected, or the current node group is exit), it is deactivated.
* Clicking on the icon in the header of the Viewer node toggles whether
its active or not.
**Pinning**
* The spreadsheet still allows pinning the active viewer as before.
When pinned, the spreadsheet still references the viewer node even
when it becomes inactive.
* The viewport does not support pinning at the moment. It always shows
the active viewer.
**Attribute**
* When a field is linked to the second input of the viewer node it is
displayed as an overlay in the viewport.
* When possible the correct domain for the attribute is determined
automatically. This does not work in all cases. It falls back to the
face corner domain on meshes and the point domain on curves. When
necessary, the domain can be picked manually.
* The spreadsheet now only shows the "Viewer" column for the domain
that is selected in the Viewer node.
* Instance attributes are visualized as a constant color per instance.
**Viewport Options**
* The attribute overlay opacity can be controlled with the "Viewer Node"
setting in the overlays popover.
* A viewport can be configured not to show intermediate viewer-geometry
by disabling the "Viewer Node" option in the "View" menu.
**Implementation Details**
* The "spreadsheet context path" was generalized to a "viewer path" that
is used in more places now.
* The viewer node itself determines the attribute domain, evaluates the
field and stores the result in a `.viewer` attribute.
* A new "viewer attribute' overlay displays the data from the `.viewer`
attribute.
* The ground truth for the active viewer node is stored in the workspace
now. Node editors, spreadsheets and viewports retrieve the active
viewer from there unless they are pinned.
* The depsgraph object iterator has a new "viewer path" setting. When set,
the viewed geometry of the corresponding object is part of the iterator
instead of the final evaluated geometry.
* To support the instance attribute overlay `DupliObject` was extended
to contain the information necessary for drawing the overlay.
* The ctrl+shift+click operator has been refactored so that it can make
existing links to viewers active again.
* The auto-domain-detection in the Viewer node works by checking the
"preferred domain" for every field input. If there is not exactly one
preferred domain, the fallback is used.
Known limitations:
* Loose edges of meshes don't have the attribute overlay. This could be
added separately if necessary.
* Some attributes are hard to visualize as a color directly. For example,
the values might have to be normalized or some should be drawn as arrays.
For now, we encourage users to build node groups that generate appropriate
viewer-geometry. We might include some of that functionality in future versions.
Support for displaying attribute values as text in the viewport is planned as well.
* There seems to be an issue with the attribute overlay for pointclouds on
nvidia gpus, to be investigated.
Differential Revision: https://developer.blender.org/D15954
2022-09-28 17:54:59 +02:00
|
|
|
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
|
|
|
|
|
if (wm == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
|
|
|
|
|
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
|
|
|
|
|
const WorkSpace *workspace = BKE_workspace_active_get(window->workspace_hook);
|
|
|
|
|
find_side_effect_nodes_for_viewer_path(workspace->viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
|
|
|
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
|
|
|
|
|
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
|
|
|
|
|
if (sl->spacetype == SPACE_SPREADSHEET) {
|
|
|
|
|
const SpaceSpreadsheet &sspreadsheet = *reinterpret_cast<const SpaceSpreadsheet *>(sl);
|
|
|
|
|
find_side_effect_nodes_for_viewer_path(
|
|
|
|
|
sspreadsheet.viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
|
|
|
}
|
|
|
|
|
if (sl->spacetype == SPACE_VIEW3D) {
|
|
|
|
|
const View3D &v3d = *reinterpret_cast<const View3D *>(sl);
|
|
|
|
|
find_side_effect_nodes_for_viewer_path(v3d.viewer_path, nmd, ctx, r_side_effect_nodes);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-08 17:35:06 +02:00
|
|
|
}
|
2023-12-18 13:01:06 +01:00
|
|
|
|
|
|
|
|
find_side_effect_nodes_for_baking(nmd, ctx, r_side_effect_nodes);
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
find_side_effect_nodes_for_active_gizmos(
|
|
|
|
|
nmd, ctx, *wm, r_side_effect_nodes, r_socket_log_contexts);
|
2021-04-08 17:35:06 +02:00
|
|
|
}
|
|
|
|
|
|
2022-12-29 19:36:15 +01:00
|
|
|
static void find_socket_log_contexts(const NodesModifierData &nmd,
|
|
|
|
|
const ModifierEvalContext &ctx,
|
2023-03-31 13:19:37 -04:00
|
|
|
Set<ComputeContextHash> &r_socket_log_contexts)
|
2022-12-29 19:36:15 +01:00
|
|
|
{
|
|
|
|
|
Main *bmain = DEG_get_bmain(ctx.depsgraph);
|
|
|
|
|
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
|
|
|
|
|
if (wm == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-04-15 15:24:29 +02:00
|
|
|
bke::ComputeContextCache compute_context_cache;
|
2022-12-29 19:36:15 +01:00
|
|
|
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
|
|
|
|
|
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
|
|
|
|
|
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
|
|
|
|
|
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
|
|
|
|
|
if (sl->spacetype == SPACE_NODE) {
|
|
|
|
|
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
|
2025-01-23 15:34:12 +01:00
|
|
|
if (snode.edittree == nullptr || snode.edittree->type != NTREE_GEOMETRY) {
|
2023-09-25 10:25:48 +10:00
|
|
|
continue;
|
|
|
|
|
}
|
2025-04-15 15:24:29 +02:00
|
|
|
if (!ed::space_node::node_editor_is_for_geometry_nodes_modifier(snode, *ctx.object, nmd)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-06-20 10:25:41 +02:00
|
|
|
const Map<const bke::bNodeTreeZone *, ComputeContextHash> hash_by_zone =
|
2025-04-15 15:24:29 +02:00
|
|
|
geo_log::GeoModifierLog::get_context_hash_by_zone_for_node_editor(
|
|
|
|
|
snode, compute_context_cache);
|
Geometry Nodes: make evaluation and logging system aware of zones
This refactors how a geometry nodes node tree is converted to a lazy-function
graph. Previously, all nodes were inserted into a single graph. This was fine
because every node was evaluated at most once per node group evaluation.
However, loops (#108896) break this assumption since now nodes may be
evaluated multiple times and thus a single flat graph does not work anymore.
Now, a separate lazy-function is build for every zone which gives us much
more flexibility for what can happen in a zone. Right now, the change only
applies to simulation zones since that's the only kind of zone we have.
Technically, those zones could be inlined, but turning them into a separate
lazy-function also does not hurt and makes it possible to test this refactor
without implementing loops first. Also, having them as separate functions
might help in the future if we integrate a substep loop directly into the
simulation zone.
The most tricky part here is to just link everything up correctly, especially
with respect to deterministic anonymous attribute lifetimes. Fortunately,
correctness can be checked visually by looking at the generated graphs.
The logging/viewer system also had to be refactored a bit, because now there
can be multiple different `ComputeContext` in a single node tree. Each zone
is in a separate `ComputeContext`. To make it work, the `ViewerPath` system
now explicitly supports zones and drawing code will look up the right logger
for showing inspection data.
No functional changes are expected, except that the spreadsheet now shows
"Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
|
|
|
for (const ComputeContextHash &hash : hash_by_zone.values()) {
|
|
|
|
|
r_socket_log_contexts.add(hash);
|
2022-12-29 19:36:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-02 10:54:54 +01:00
|
|
|
/**
|
2023-06-12 13:31:09 +02:00
|
|
|
* \note This could be done in #initialize_group_input, though that would require adding the
|
|
|
|
|
* the object as a parameter, so it's likely better to this check as a separate step.
|
2022-02-02 10:54:54 +01:00
|
|
|
*/
|
2025-03-25 15:40:32 +01:00
|
|
|
static void check_property_socket_sync(const Object *ob,
|
|
|
|
|
const nodes::PropertiesVectorSet &properties,
|
|
|
|
|
ModifierData *md)
|
2021-09-27 15:33:48 +02:00
|
|
|
{
|
2023-06-12 13:31:09 +02:00
|
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
2022-02-02 10:54:54 +01:00
|
|
|
|
2023-06-12 13:31:09 +02:00
|
|
|
int geometry_socket_count = 0;
|
2021-09-27 15:33:48 +02:00
|
|
|
|
2023-09-14 14:13:07 +02:00
|
|
|
nmd->node_group->ensure_interface_cache();
|
2023-08-30 12:37:21 +02:00
|
|
|
for (const int i : nmd->node_group->interface_inputs().index_range()) {
|
|
|
|
|
const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[i];
|
2024-05-13 16:07:12 +02:00
|
|
|
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
|
2023-08-30 12:37:21 +02:00
|
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
2025-04-03 15:44:06 +02:00
|
|
|
if (type == SOCK_GEOMETRY) {
|
|
|
|
|
geometry_socket_count++;
|
|
|
|
|
}
|
2023-06-12 13:31:09 +02:00
|
|
|
/* The first socket is the special geometry socket for the modifier object. */
|
2023-08-30 12:37:21 +02:00
|
|
|
if (i == 0 && type == SOCK_GEOMETRY) {
|
2022-02-02 10:54:54 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
2023-06-12 13:31:09 +02:00
|
|
|
|
2025-03-25 15:40:32 +01:00
|
|
|
IDProperty *property = properties.lookup_key_default_as(socket->identifier, nullptr);
|
2023-06-12 13:31:09 +02:00
|
|
|
if (property == nullptr) {
|
2025-04-03 15:44:06 +02:00
|
|
|
if (!ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)) {
|
2023-10-19 11:48:08 +02:00
|
|
|
BKE_modifier_set_error(
|
|
|
|
|
ob, md, "Missing property for input socket \"%s\"", socket->name ? socket->name : "");
|
2022-02-02 10:54:54 +01:00
|
|
|
}
|
2023-06-12 13:31:09 +02:00
|
|
|
continue;
|
2022-02-02 10:54:54 +01:00
|
|
|
}
|
2022-04-21 14:05:16 -05:00
|
|
|
|
2023-06-12 13:31:09 +02:00
|
|
|
if (!nodes::id_property_type_matches_socket(*socket, *property)) {
|
2023-10-19 11:48:08 +02:00
|
|
|
BKE_modifier_set_error(ob,
|
|
|
|
|
md,
|
|
|
|
|
"Property type does not match input socket \"(%s)\"",
|
|
|
|
|
socket->name ? socket->name : "");
|
2022-04-21 14:05:16 -05:00
|
|
|
continue;
|
|
|
|
|
}
|
2023-06-12 13:31:09 +02:00
|
|
|
}
|
2022-04-21 14:05:16 -05:00
|
|
|
|
2023-06-12 13:31:09 +02:00
|
|
|
if (geometry_socket_count == 1) {
|
2023-08-30 12:37:21 +02:00
|
|
|
const bNodeTreeInterfaceSocket *first_socket = nmd->node_group->interface_inputs()[0];
|
2024-05-13 16:07:12 +02:00
|
|
|
const bke::bNodeSocketType *typeinfo = first_socket->socket_typeinfo();
|
2023-08-30 12:37:21 +02:00
|
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
|
|
|
if (type != SOCK_GEOMETRY) {
|
2023-06-12 13:31:09 +02:00
|
|
|
BKE_modifier_set_error(ob, md, "Node group's geometry input must be the first");
|
2022-02-02 10:54:54 +01:00
|
|
|
}
|
2021-10-26 12:50:39 -05:00
|
|
|
}
|
2021-09-27 15:33:48 +02:00
|
|
|
}
|
|
|
|
|
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
class NodesModifierBakeDataBlockMap : public bake::BakeDataBlockMap {
|
|
|
|
|
/** Protects access to `new_mappings` which may be added to from multiple threads. */
|
2025-05-07 04:53:16 +02:00
|
|
|
Mutex mutex_;
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
Map<bake::BakeDataBlockID, ID *> old_mappings;
|
|
|
|
|
Map<bake::BakeDataBlockID, ID *> new_mappings;
|
|
|
|
|
|
|
|
|
|
ID *lookup_or_remember_missing(const bake::BakeDataBlockID &key) override
|
|
|
|
|
{
|
|
|
|
|
if (ID *id = this->old_mappings.lookup_default(key, nullptr)) {
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
if (this->old_mappings.contains(key)) {
|
|
|
|
|
/* Don't allow overwriting old mappings. */
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
std::lock_guard lock{mutex_};
|
|
|
|
|
return this->new_mappings.lookup_or_add(key, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void try_add(ID &id) override
|
|
|
|
|
{
|
|
|
|
|
bake::BakeDataBlockID key{id};
|
|
|
|
|
if (this->old_mappings.contains(key)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::lock_guard lock{mutex_};
|
|
|
|
|
this->new_mappings.add_overwrite(std::move(key), &id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
ID *lookup_in_map(Map<bake::BakeDataBlockID, ID *> &map,
|
|
|
|
|
const bake::BakeDataBlockID &key,
|
|
|
|
|
const std::optional<ID_Type> &type)
|
|
|
|
|
{
|
|
|
|
|
ID *id = map.lookup_default(key, nullptr);
|
|
|
|
|
if (!id) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (type && GS(id->name) != *type) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
namespace sim_input = nodes::sim_input;
|
|
|
|
|
namespace sim_output = nodes::sim_output;
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
struct BakeFrameIndices {
|
|
|
|
|
std::optional<int> prev;
|
|
|
|
|
std::optional<int> current;
|
|
|
|
|
std::optional<int> next;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static BakeFrameIndices get_bake_frame_indices(
|
2024-02-27 23:09:54 -05:00
|
|
|
const Span<std::unique_ptr<bake::FrameCache>> frame_caches, const SubFrame frame)
|
2023-12-18 13:01:06 +01:00
|
|
|
{
|
|
|
|
|
BakeFrameIndices frame_indices;
|
|
|
|
|
if (!frame_caches.is_empty()) {
|
2024-11-19 11:05:57 +01:00
|
|
|
const int first_future_frame_index = binary_search::first_if(
|
2023-12-18 13:01:06 +01:00
|
|
|
frame_caches,
|
|
|
|
|
[&](const std::unique_ptr<bake::FrameCache> &value) { return value->frame > frame; });
|
|
|
|
|
frame_indices.next = (first_future_frame_index == frame_caches.size()) ?
|
|
|
|
|
std::nullopt :
|
|
|
|
|
std::optional<int>(first_future_frame_index);
|
|
|
|
|
if (first_future_frame_index > 0) {
|
|
|
|
|
const int index = first_future_frame_index - 1;
|
|
|
|
|
if (frame_caches[index]->frame < frame) {
|
|
|
|
|
frame_indices.prev = index;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_assert(frame_caches[index]->frame == frame);
|
|
|
|
|
frame_indices.current = index;
|
|
|
|
|
if (index > 0) {
|
|
|
|
|
frame_indices.prev = index - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return frame_indices;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ensure_bake_loaded(bake::NodeBakeCache &bake_cache, bake::FrameCache &frame_cache)
|
|
|
|
|
{
|
|
|
|
|
if (!frame_cache.state.items_by_id.is_empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
if (!frame_cache.meta_data_source.has_value()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (bake_cache.memory_blob_reader) {
|
|
|
|
|
if (const auto *meta_buffer = std::get_if<Span<std::byte>>(&*frame_cache.meta_data_source)) {
|
|
|
|
|
const std::string meta_str{reinterpret_cast<const char *>(meta_buffer->data()),
|
|
|
|
|
size_t(meta_buffer->size())};
|
|
|
|
|
std::istringstream meta_stream{meta_str};
|
|
|
|
|
std::optional<bake::BakeState> bake_state = bake::deserialize_bake(
|
|
|
|
|
meta_stream, *bake_cache.memory_blob_reader, *bake_cache.blob_sharing);
|
|
|
|
|
if (!bake_state.has_value()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
frame_cache.state = std::move(*bake_state);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-18 13:01:06 +01:00
|
|
|
if (!bake_cache.blobs_dir) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
const auto *meta_path = std::get_if<std::string>(&*frame_cache.meta_data_source);
|
|
|
|
|
if (!meta_path) {
|
2023-12-18 13:01:06 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
bake::DiskBlobReader blob_reader{*bake_cache.blobs_dir};
|
|
|
|
|
fstream meta_file{*meta_path};
|
|
|
|
|
std::optional<bake::BakeState> bake_state = bake::deserialize_bake(
|
2023-12-18 13:01:06 +01:00
|
|
|
meta_file, blob_reader, *bake_cache.blob_sharing);
|
|
|
|
|
if (!bake_state.has_value()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
frame_cache.state = std::move(*bake_state);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 16:18:12 +02:00
|
|
|
static bool try_find_baked_data(const NodesModifierBake &bake,
|
|
|
|
|
bake::NodeBakeCache &bake_cache,
|
2023-12-18 13:01:06 +01:00
|
|
|
const Main &bmain,
|
|
|
|
|
const Object &object,
|
|
|
|
|
const NodesModifierData &nmd,
|
|
|
|
|
const int id)
|
|
|
|
|
{
|
2024-09-20 16:18:12 +02:00
|
|
|
if (bake.packed) {
|
|
|
|
|
if (bake.packed->meta_files_num == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
bake_cache.reset();
|
|
|
|
|
Map<SubFrame, const NodesModifierBakeFile *> file_by_frame;
|
|
|
|
|
for (const NodesModifierBakeFile &meta_file :
|
|
|
|
|
Span{bake.packed->meta_files, bake.packed->meta_files_num})
|
|
|
|
|
{
|
|
|
|
|
const std::optional<SubFrame> frame = bake::file_name_to_frame(meta_file.name);
|
|
|
|
|
if (!frame) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!file_by_frame.add(*frame, &meta_file)) {
|
|
|
|
|
/* Can only have on file per (sub)frame. */
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Make sure frames processed in the right order. */
|
|
|
|
|
Vector<SubFrame> frames;
|
|
|
|
|
frames.extend(file_by_frame.keys().begin(), file_by_frame.keys().end());
|
|
|
|
|
|
|
|
|
|
for (const SubFrame &frame : frames) {
|
|
|
|
|
const NodesModifierBakeFile &meta_file = *file_by_frame.lookup(frame);
|
|
|
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
|
|
|
frame_cache->frame = frame;
|
|
|
|
|
frame_cache->meta_data_source = meta_file.data();
|
|
|
|
|
bake_cache.frames.append(std::move(frame_cache));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bake_cache.memory_blob_reader = std::make_unique<bake::MemoryBlobReader>();
|
|
|
|
|
for (const NodesModifierBakeFile &blob_file :
|
|
|
|
|
Span{bake.packed->blob_files, bake.packed->blob_files_num})
|
|
|
|
|
{
|
|
|
|
|
bake_cache.memory_blob_reader->add(blob_file.name, blob_file.data());
|
|
|
|
|
}
|
|
|
|
|
bake_cache.blob_sharing = std::make_unique<bake::BlobReadSharing>();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
std::optional<bake::BakePath> bake_path = bake::get_node_bake_path(bmain, object, nmd, id);
|
|
|
|
|
if (!bake_path) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
Vector<bake::MetaFile> meta_files = bake::find_sorted_meta_files(bake_path->meta_dir);
|
|
|
|
|
if (meta_files.is_empty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
bake_cache.reset();
|
2023-12-18 13:01:06 +01:00
|
|
|
for (const bake::MetaFile &meta_file : meta_files) {
|
|
|
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
|
|
|
frame_cache->frame = meta_file.frame;
|
2024-09-20 16:18:12 +02:00
|
|
|
frame_cache->meta_data_source = meta_file.path;
|
|
|
|
|
bake_cache.frames.append(std::move(frame_cache));
|
2023-12-18 13:01:06 +01:00
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
bake_cache.blobs_dir = bake_path->blobs_dir;
|
|
|
|
|
bake_cache.blob_sharing = std::make_unique<bake::BlobReadSharing>();
|
2023-12-18 13:01:06 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams {
|
|
|
|
|
private:
|
|
|
|
|
static constexpr float max_delta_frames = 1.0f;
|
|
|
|
|
|
|
|
|
|
const NodesModifierData &nmd_;
|
|
|
|
|
const ModifierEvalContext &ctx_;
|
|
|
|
|
const Main *bmain_;
|
2023-09-26 20:30:46 +02:00
|
|
|
const Scene *scene_;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
SubFrame current_frame_;
|
|
|
|
|
bool use_frame_cache_;
|
|
|
|
|
bool depsgraph_is_active_;
|
2023-09-03 16:14:11 +02:00
|
|
|
bake::ModifierCache *modifier_cache_;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
float fps_;
|
2023-09-26 20:30:46 +02:00
|
|
|
bool has_invalid_simulation_ = false;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
|
|
|
|
|
public:
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
struct DataPerZone {
|
|
|
|
|
nodes::SimulationZoneBehavior behavior;
|
|
|
|
|
NodesModifierBakeDataBlockMap data_block_map;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mutable Map<int, std::unique_ptr<DataPerZone>> data_by_zone_id;
|
|
|
|
|
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
|
|
|
|
|
: nmd_(nmd), ctx_(ctx)
|
2023-06-14 14:23:13 +02:00
|
|
|
{
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
const Depsgraph *depsgraph = ctx_.depsgraph;
|
|
|
|
|
bmain_ = DEG_get_bmain(depsgraph);
|
|
|
|
|
current_frame_ = DEG_get_ctime(depsgraph);
|
|
|
|
|
const Scene *scene = DEG_get_input_scene(depsgraph);
|
2023-09-26 20:30:46 +02:00
|
|
|
scene_ = scene;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
use_frame_cache_ = ctx_.object->flag & OB_FLAG_USE_SIMULATION_CACHE;
|
|
|
|
|
depsgraph_is_active_ = DEG_is_active(depsgraph);
|
2023-09-03 16:14:11 +02:00
|
|
|
modifier_cache_ = nmd.runtime->cache.get();
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
fps_ = FPS;
|
|
|
|
|
|
2023-09-03 16:14:11 +02:00
|
|
|
if (!modifier_cache_) {
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-09-03 16:14:11 +02:00
|
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
if (depsgraph_is_active_) {
|
|
|
|
|
/* Invalidate data on user edits. */
|
|
|
|
|
if (nmd.modifier.flag & eModifierFlag_UserModified) {
|
2023-12-01 17:08:00 +01:00
|
|
|
for (std::unique_ptr<bake::SimulationNodeCache> &node_cache :
|
|
|
|
|
modifier_cache_->simulation_cache_by_id.values())
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
{
|
2023-09-03 16:14:11 +02:00
|
|
|
if (node_cache->cache_status != bake::CacheStatus::Baked) {
|
|
|
|
|
node_cache->cache_status = bake::CacheStatus::Invalid;
|
2024-06-06 20:47:17 +02:00
|
|
|
if (!node_cache->bake.frames.is_empty()) {
|
|
|
|
|
if (node_cache->bake.frames.last()->frame == current_frame_) {
|
|
|
|
|
/* Remove the last (which is the current) cached frame so that it is simulated
|
|
|
|
|
* again. */
|
|
|
|
|
node_cache->bake.frames.pop_last();
|
|
|
|
|
}
|
|
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
this->reset_invalid_node_bakes();
|
|
|
|
|
}
|
2023-12-01 17:08:00 +01:00
|
|
|
for (const std::unique_ptr<bake::SimulationNodeCache> &node_cache_ptr :
|
|
|
|
|
modifier_cache_->simulation_cache_by_id.values())
|
2023-09-26 20:30:46 +02:00
|
|
|
{
|
2023-12-01 17:08:00 +01:00
|
|
|
const bake::SimulationNodeCache &node_cache = *node_cache_ptr;
|
2023-09-26 20:30:46 +02:00
|
|
|
if (node_cache.cache_status == bake::CacheStatus::Invalid) {
|
|
|
|
|
has_invalid_simulation_ = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void reset_invalid_node_bakes()
|
|
|
|
|
{
|
2023-12-01 17:08:00 +01:00
|
|
|
for (auto item : modifier_cache_->simulation_cache_by_id.items()) {
|
2023-09-26 20:30:46 +02:00
|
|
|
const int id = item.key;
|
2023-12-01 17:08:00 +01:00
|
|
|
bake::SimulationNodeCache &node_cache = *item.value;
|
2023-09-26 20:30:46 +02:00
|
|
|
if (node_cache.cache_status != bake::CacheStatus::Invalid) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const std::optional<IndexRange> sim_frame_range = bake::get_node_bake_frame_range(
|
|
|
|
|
*scene_, *ctx_.object, nmd_, id);
|
|
|
|
|
if (!sim_frame_range.has_value()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const SubFrame start_frame{int(sim_frame_range->start())};
|
|
|
|
|
if (current_frame_ <= start_frame) {
|
|
|
|
|
node_cache.reset();
|
|
|
|
|
}
|
2023-12-18 13:01:06 +01:00
|
|
|
if (!node_cache.bake.frames.is_empty() &&
|
2024-01-02 18:12:54 +01:00
|
|
|
current_frame_ < node_cache.bake.frames.first()->frame)
|
|
|
|
|
{
|
2023-09-26 20:30:46 +02:00
|
|
|
node_cache.reset();
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-06-14 14:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
nodes::SimulationZoneBehavior *get(const int zone_id) const override
|
|
|
|
|
{
|
2023-09-03 16:14:11 +02:00
|
|
|
if (!modifier_cache_) {
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2023-09-03 16:14:11 +02:00
|
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
return &this->data_by_zone_id
|
|
|
|
|
.lookup_or_add_cb(zone_id,
|
|
|
|
|
[&]() {
|
|
|
|
|
auto data = std::make_unique<DataPerZone>();
|
|
|
|
|
data->behavior.data_block_map = &data->data_block_map;
|
|
|
|
|
this->init_simulation_info(
|
|
|
|
|
zone_id, data->behavior, data->data_block_map);
|
|
|
|
|
return data;
|
|
|
|
|
})
|
|
|
|
|
->behavior;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void init_simulation_info(const int zone_id,
|
|
|
|
|
nodes::SimulationZoneBehavior &zone_behavior,
|
|
|
|
|
NodesModifierBakeDataBlockMap &data_block_map) const
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
{
|
2024-01-29 11:42:34 +01:00
|
|
|
bake::SimulationNodeCache &node_cache =
|
|
|
|
|
*modifier_cache_->simulation_cache_by_id.lookup_or_add_cb(
|
|
|
|
|
zone_id, []() { return std::make_unique<bake::SimulationNodeCache>(); });
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
const NodesModifierBake &bake = *nmd_.find_bake(zone_id);
|
2023-09-26 20:30:46 +02:00
|
|
|
const IndexRange sim_frame_range = *bake::get_node_bake_frame_range(
|
|
|
|
|
*scene_, *ctx_.object, nmd_, zone_id);
|
|
|
|
|
const SubFrame sim_start_frame{int(sim_frame_range.first())};
|
|
|
|
|
const SubFrame sim_end_frame{int(sim_frame_range.last())};
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
|
2024-06-03 18:46:35 +02:00
|
|
|
if (!modifier_cache_->requested_bakes.contains(zone_id)) {
|
|
|
|
|
/* Try load baked data. */
|
|
|
|
|
if (!node_cache.bake.failed_finding_bake) {
|
|
|
|
|
if (node_cache.cache_status != bake::CacheStatus::Baked) {
|
2024-09-20 16:18:12 +02:00
|
|
|
if (try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, zone_id)) {
|
2024-06-03 18:46:35 +02:00
|
|
|
node_cache.cache_status = bake::CacheStatus::Baked;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
node_cache.bake.failed_finding_bake = true;
|
|
|
|
|
}
|
2023-07-10 16:02:31 +02:00
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
}
|
2023-07-10 16:02:31 +02:00
|
|
|
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
/* If there are no baked frames, we don't need keep track of the data-blocks. */
|
2024-11-07 14:25:57 +01:00
|
|
|
if (!node_cache.bake.frames.is_empty() || node_cache.prev_cache.has_value()) {
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num})
|
|
|
|
|
{
|
|
|
|
|
data_block_map.old_mappings.add(data_block, data_block.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames,
|
|
|
|
|
current_frame_);
|
2023-09-03 16:14:11 +02:00
|
|
|
if (node_cache.cache_status == bake::CacheStatus::Baked) {
|
|
|
|
|
this->read_from_cache(frame_indices, node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
2023-07-10 16:02:31 +02:00
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
if (use_frame_cache_) {
|
|
|
|
|
/* If the depsgraph is active, we allow creating new simulation states. Otherwise, the access
|
|
|
|
|
* is read-only. */
|
|
|
|
|
if (depsgraph_is_active_) {
|
2023-12-18 13:01:06 +01:00
|
|
|
if (node_cache.bake.frames.is_empty()) {
|
2023-09-26 20:30:46 +02:00
|
|
|
if (current_frame_ < sim_start_frame || current_frame_ > sim_end_frame) {
|
|
|
|
|
/* Outside of simulation frame range, so ignore the simulation if there is no cache. */
|
|
|
|
|
this->input_pass_through(zone_behavior);
|
|
|
|
|
this->output_pass_through(zone_behavior);
|
|
|
|
|
return;
|
|
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
/* Initialize the simulation. */
|
2023-09-26 20:30:46 +02:00
|
|
|
if (current_frame_ > sim_start_frame || has_invalid_simulation_) {
|
2023-09-03 16:14:11 +02:00
|
|
|
node_cache.cache_status = bake::CacheStatus::Invalid;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
this->input_pass_through(zone_behavior);
|
|
|
|
|
this->output_store_frame_cache(node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
if (frame_indices.prev && !frame_indices.current && !frame_indices.next &&
|
|
|
|
|
current_frame_ <= sim_end_frame)
|
|
|
|
|
{
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
/* Read the previous frame's data and store the newly computed simulation state. */
|
|
|
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
2023-12-18 13:01:06 +01:00
|
|
|
const bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[*frame_indices.prev];
|
2023-09-26 20:30:46 +02:00
|
|
|
const float real_delta_frames = float(current_frame_) - float(prev_frame_cache.frame);
|
|
|
|
|
if (real_delta_frames != 1) {
|
2023-09-03 16:14:11 +02:00
|
|
|
node_cache.cache_status = bake::CacheStatus::Invalid;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
const float delta_frames = std::min(max_delta_frames, real_delta_frames);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
output_copy_info.delta_time = delta_frames / fps_;
|
2023-09-01 11:23:53 +02:00
|
|
|
output_copy_info.state = prev_frame_cache.state;
|
2023-09-03 16:14:11 +02:00
|
|
|
this->output_store_frame_cache(node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
|
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
}
|
2023-09-03 16:14:11 +02:00
|
|
|
this->read_from_cache(frame_indices, node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
}
|
2023-07-10 16:02:31 +02:00
|
|
|
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
/* When there is no per-frame cache, check if there is a previous state. */
|
2023-09-03 16:14:11 +02:00
|
|
|
if (node_cache.prev_cache) {
|
|
|
|
|
if (node_cache.prev_cache->frame < current_frame_) {
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
/* Do a simulation step. */
|
|
|
|
|
const float delta_frames = std::min(
|
2023-09-14 12:03:24 +02:00
|
|
|
max_delta_frames, float(current_frame_) - float(node_cache.prev_cache->frame));
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
auto &output_move_info = zone_behavior.input.emplace<sim_input::OutputMove>();
|
|
|
|
|
output_move_info.delta_time = delta_frames / fps_;
|
2023-09-03 16:14:11 +02:00
|
|
|
output_move_info.state = std::move(node_cache.prev_cache->state);
|
|
|
|
|
this->store_as_prev_items(node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
2023-07-10 16:02:31 +02:00
|
|
|
}
|
2023-09-03 16:14:11 +02:00
|
|
|
if (node_cache.prev_cache->frame == current_frame_) {
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
/* Just read from the previous state if the frame has not changed. */
|
|
|
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
|
|
|
|
output_copy_info.delta_time = 0.0f;
|
2023-09-03 16:14:11 +02:00
|
|
|
output_copy_info.state = node_cache.prev_cache->state;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
|
2023-09-03 16:14:11 +02:00
|
|
|
read_single_info.state = node_cache.prev_cache->state;
|
2023-08-04 16:59:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
if (!depsgraph_is_active_) {
|
|
|
|
|
/* There is no previous state, and it's not possible to initialize the simulation because
|
|
|
|
|
* the depsgraph is not active. */
|
|
|
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
|
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* Reset the simulation when the scene time moved backwards. */
|
2023-09-03 16:14:11 +02:00
|
|
|
node_cache.prev_cache.reset();
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
|
|
|
if (depsgraph_is_active_) {
|
|
|
|
|
/* Initialize the simulation. */
|
2023-09-03 16:14:11 +02:00
|
|
|
this->store_as_prev_items(node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
|
|
|
}
|
|
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
void input_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
|
|
|
|
|
{
|
|
|
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
|
|
|
}
|
2023-07-10 16:02:31 +02:00
|
|
|
|
2023-09-14 12:02:13 +02:00
|
|
|
void output_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
|
|
|
|
|
{
|
|
|
|
|
zone_behavior.output.emplace<sim_output::PassThrough>();
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-01 17:08:00 +01:00
|
|
|
void output_store_frame_cache(bake::SimulationNodeCache &node_cache,
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
|
|
|
{
|
2023-09-09 09:53:01 +02:00
|
|
|
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
|
|
|
|
|
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
|
|
|
|
|
node_cache = &node_cache,
|
|
|
|
|
current_frame = current_frame_](bke::bake::BakeState state) {
|
|
|
|
|
std::lock_guard lock{simulation_cache->mutex};
|
|
|
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
|
|
|
frame_cache->frame = current_frame;
|
|
|
|
|
frame_cache->state = std::move(state);
|
2023-12-18 13:01:06 +01:00
|
|
|
node_cache->bake.frames.append(std::move(frame_cache));
|
2023-09-09 09:53:01 +02:00
|
|
|
};
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-01 17:08:00 +01:00
|
|
|
void store_as_prev_items(bake::SimulationNodeCache &node_cache,
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
|
|
|
{
|
2023-09-09 09:53:01 +02:00
|
|
|
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
|
|
|
|
|
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
|
|
|
|
|
node_cache = &node_cache,
|
|
|
|
|
current_frame = current_frame_](bke::bake::BakeState state) {
|
|
|
|
|
std::lock_guard lock{simulation_cache->mutex};
|
|
|
|
|
if (!node_cache->prev_cache) {
|
|
|
|
|
node_cache->prev_cache.emplace();
|
|
|
|
|
}
|
|
|
|
|
node_cache->prev_cache->state = std::move(state);
|
|
|
|
|
node_cache->prev_cache->frame = current_frame;
|
|
|
|
|
};
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
void read_from_cache(const BakeFrameIndices &frame_indices,
|
2023-12-01 17:08:00 +01:00
|
|
|
bake::SimulationNodeCache &node_cache,
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
|
|
|
{
|
|
|
|
|
if (frame_indices.prev) {
|
|
|
|
|
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
|
2023-12-18 13:01:06 +01:00
|
|
|
bake::FrameCache &frame_cache = *node_cache.bake.frames[*frame_indices.prev];
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
const float delta_frames = std::min(max_delta_frames,
|
|
|
|
|
float(current_frame_) - float(frame_cache.frame));
|
|
|
|
|
output_copy_info.delta_time = delta_frames / fps_;
|
2023-09-01 11:23:53 +02:00
|
|
|
output_copy_info.state = frame_cache.state;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
zone_behavior.input.emplace<sim_input::PassThrough>();
|
|
|
|
|
}
|
|
|
|
|
if (frame_indices.current) {
|
2023-09-03 16:14:11 +02:00
|
|
|
this->read_single(*frame_indices.current, node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
else if (frame_indices.next) {
|
|
|
|
|
if (frame_indices.prev) {
|
|
|
|
|
this->read_interpolated(
|
2023-09-03 16:14:11 +02:00
|
|
|
*frame_indices.prev, *frame_indices.next, node_cache, zone_behavior);
|
2023-07-10 16:02:31 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2023-09-14 12:02:13 +02:00
|
|
|
this->output_pass_through(zone_behavior);
|
2023-07-10 16:02:31 +02:00
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
else if (frame_indices.prev) {
|
2023-09-03 16:14:11 +02:00
|
|
|
this->read_single(*frame_indices.prev, node_cache, zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2023-09-14 12:02:13 +02:00
|
|
|
this->output_pass_through(zone_behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
|
|
|
|
|
void read_single(const int frame_index,
|
2023-12-01 17:08:00 +01:00
|
|
|
bake::SimulationNodeCache &node_cache,
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
|
|
|
{
|
2023-12-18 13:01:06 +01:00
|
|
|
bake::FrameCache &frame_cache = *node_cache.bake.frames[frame_index];
|
|
|
|
|
ensure_bake_loaded(node_cache.bake, frame_cache);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
|
2023-09-01 11:23:53 +02:00
|
|
|
read_single_info.state = frame_cache.state;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void read_interpolated(const int prev_frame_index,
|
|
|
|
|
const int next_frame_index,
|
2023-12-01 17:08:00 +01:00
|
|
|
bake::SimulationNodeCache &node_cache,
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
nodes::SimulationZoneBehavior &zone_behavior) const
|
|
|
|
|
{
|
2023-12-18 13:01:06 +01:00
|
|
|
bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[prev_frame_index];
|
|
|
|
|
bake::FrameCache &next_frame_cache = *node_cache.bake.frames[next_frame_index];
|
|
|
|
|
ensure_bake_loaded(node_cache.bake, prev_frame_cache);
|
|
|
|
|
ensure_bake_loaded(node_cache.bake, next_frame_cache);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
auto &read_interpolated_info = zone_behavior.output.emplace<sim_output::ReadInterpolated>();
|
|
|
|
|
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
|
|
|
|
|
(float(next_frame_cache.frame) -
|
|
|
|
|
float(prev_frame_cache.frame));
|
2023-09-01 11:23:53 +02:00
|
|
|
read_interpolated_info.prev_state = prev_frame_cache.state;
|
|
|
|
|
read_interpolated_info.next_state = next_frame_cache.state;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
2023-12-18 13:01:06 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class NodesModifierBakeParams : public nodes::GeoNodesBakeParams {
|
|
|
|
|
private:
|
|
|
|
|
const NodesModifierData &nmd_;
|
|
|
|
|
const ModifierEvalContext &ctx_;
|
|
|
|
|
Main *bmain_;
|
|
|
|
|
SubFrame current_frame_;
|
|
|
|
|
bake::ModifierCache *modifier_cache_;
|
|
|
|
|
bool depsgraph_is_active_;
|
|
|
|
|
|
|
|
|
|
public:
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
struct DataPerNode {
|
|
|
|
|
nodes::BakeNodeBehavior behavior;
|
|
|
|
|
NodesModifierBakeDataBlockMap data_block_map;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mutable Map<int, std::unique_ptr<DataPerNode>> data_by_node_id;
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
NodesModifierBakeParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
|
|
|
|
|
: nmd_(nmd), ctx_(ctx)
|
|
|
|
|
{
|
|
|
|
|
const Depsgraph *depsgraph = ctx_.depsgraph;
|
|
|
|
|
current_frame_ = DEG_get_ctime(depsgraph);
|
|
|
|
|
modifier_cache_ = nmd.runtime->cache.get();
|
|
|
|
|
depsgraph_is_active_ = DEG_is_active(depsgraph);
|
|
|
|
|
bmain_ = DEG_get_bmain(depsgraph);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-26 20:07:57 +01:00
|
|
|
nodes::BakeNodeBehavior *get(const int id) const override
|
2023-12-18 13:01:06 +01:00
|
|
|
{
|
|
|
|
|
if (!modifier_cache_) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
std::lock_guard lock{modifier_cache_->mutex};
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
return &this->data_by_node_id
|
|
|
|
|
.lookup_or_add_cb(id,
|
|
|
|
|
[&]() {
|
|
|
|
|
auto data = std::make_unique<DataPerNode>();
|
|
|
|
|
data->behavior.data_block_map = &data->data_block_map;
|
|
|
|
|
this->init_bake_behavior(
|
|
|
|
|
id, data->behavior, data->data_block_map);
|
|
|
|
|
return data;
|
|
|
|
|
})
|
|
|
|
|
->behavior;
|
2023-12-18 13:01:06 +01:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
private:
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
void init_bake_behavior(const int id,
|
|
|
|
|
nodes::BakeNodeBehavior &behavior,
|
|
|
|
|
NodesModifierBakeDataBlockMap &data_block_map) const
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
{
|
2024-01-29 11:42:34 +01:00
|
|
|
bake::BakeNodeCache &node_cache = *modifier_cache_->bake_cache_by_id.lookup_or_add_cb(
|
|
|
|
|
id, []() { return std::make_unique<bake::BakeNodeCache>(); });
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
const NodesModifierBake &bake = *nmd_.find_bake(id);
|
|
|
|
|
|
|
|
|
|
for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num}) {
|
|
|
|
|
data_block_map.old_mappings.add(data_block, data_block.id);
|
|
|
|
|
}
|
2023-12-18 13:01:06 +01:00
|
|
|
|
|
|
|
|
if (depsgraph_is_active_) {
|
|
|
|
|
if (modifier_cache_->requested_bakes.contains(id)) {
|
|
|
|
|
/* This node is baked during the current evaluation. */
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
auto &store_info = behavior.behavior.emplace<sim_output::StoreNewState>();
|
2023-12-18 13:01:06 +01:00
|
|
|
store_info.store_fn = [modifier_cache = modifier_cache_,
|
|
|
|
|
node_cache = &node_cache,
|
|
|
|
|
current_frame = current_frame_](bake::BakeState state) {
|
|
|
|
|
std::lock_guard lock{modifier_cache->mutex};
|
|
|
|
|
auto frame_cache = std::make_unique<bake::FrameCache>();
|
|
|
|
|
frame_cache->frame = current_frame;
|
|
|
|
|
frame_cache->state = std::move(state);
|
|
|
|
|
auto &frames = node_cache->bake.frames;
|
2024-11-19 11:05:57 +01:00
|
|
|
const int insert_index = binary_search::first_if(
|
2023-12-18 13:01:06 +01:00
|
|
|
frames, [&](const std::unique_ptr<bake::FrameCache> &frame_cache) {
|
|
|
|
|
return frame_cache->frame > current_frame;
|
|
|
|
|
});
|
|
|
|
|
frames.insert(insert_index, std::move(frame_cache));
|
|
|
|
|
};
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Try load baked data. */
|
|
|
|
|
if (node_cache.bake.frames.is_empty()) {
|
|
|
|
|
if (!node_cache.bake.failed_finding_bake) {
|
2024-09-20 16:18:12 +02:00
|
|
|
if (!try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, id)) {
|
2023-12-18 13:01:06 +01:00
|
|
|
node_cache.bake.failed_finding_bake = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node_cache.bake.frames.is_empty()) {
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
behavior.behavior.emplace<sim_output::PassThrough>();
|
2023-12-18 13:01:06 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames,
|
|
|
|
|
current_frame_);
|
|
|
|
|
if (frame_indices.current) {
|
|
|
|
|
this->read_single(*frame_indices.current, node_cache, behavior);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (frame_indices.prev && frame_indices.next) {
|
|
|
|
|
this->read_interpolated(*frame_indices.prev, *frame_indices.next, node_cache, behavior);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (frame_indices.prev) {
|
|
|
|
|
this->read_single(*frame_indices.prev, node_cache, behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-12-18 13:01:06 +01:00
|
|
|
if (frame_indices.next) {
|
|
|
|
|
this->read_single(*frame_indices.next, node_cache, behavior);
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-12-18 13:01:06 +01:00
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void read_single(const int frame_index,
|
|
|
|
|
bake::BakeNodeCache &node_cache,
|
|
|
|
|
nodes::BakeNodeBehavior &behavior) const
|
|
|
|
|
{
|
|
|
|
|
bake::FrameCache &frame_cache = *node_cache.bake.frames[frame_index];
|
|
|
|
|
ensure_bake_loaded(node_cache.bake, frame_cache);
|
|
|
|
|
if (this->check_read_error(frame_cache, behavior)) {
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
return;
|
|
|
|
|
}
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
auto &read_single_info = behavior.behavior.emplace<sim_output::ReadSingle>();
|
2023-12-18 13:01:06 +01:00
|
|
|
read_single_info.state = frame_cache.state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void read_interpolated(const int prev_frame_index,
|
|
|
|
|
const int next_frame_index,
|
|
|
|
|
bake::BakeNodeCache &node_cache,
|
|
|
|
|
nodes::BakeNodeBehavior &behavior) const
|
|
|
|
|
{
|
|
|
|
|
bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[prev_frame_index];
|
|
|
|
|
bake::FrameCache &next_frame_cache = *node_cache.bake.frames[next_frame_index];
|
|
|
|
|
ensure_bake_loaded(node_cache.bake, prev_frame_cache);
|
|
|
|
|
ensure_bake_loaded(node_cache.bake, next_frame_cache);
|
|
|
|
|
if (this->check_read_error(prev_frame_cache, behavior) ||
|
|
|
|
|
this->check_read_error(next_frame_cache, behavior))
|
|
|
|
|
{
|
2023-09-01 10:59:00 +02:00
|
|
|
return;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
auto &read_interpolated_info = behavior.behavior.emplace<sim_output::ReadInterpolated>();
|
2023-12-18 13:01:06 +01:00
|
|
|
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
|
|
|
|
|
(float(next_frame_cache.frame) -
|
|
|
|
|
float(prev_frame_cache.frame));
|
|
|
|
|
read_interpolated_info.prev_state = prev_frame_cache.state;
|
|
|
|
|
read_interpolated_info.next_state = next_frame_cache.state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] bool check_read_error(const bake::FrameCache &frame_cache,
|
|
|
|
|
nodes::BakeNodeBehavior &behavior) const
|
|
|
|
|
{
|
2024-09-20 16:18:12 +02:00
|
|
|
if (frame_cache.meta_data_source && frame_cache.state.items_by_id.is_empty()) {
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
auto &read_error_info = behavior.behavior.emplace<sim_output::ReadError>();
|
2024-02-05 17:08:17 +01:00
|
|
|
read_error_info.message = RPT_("Cannot load the baked data");
|
2023-12-18 13:01:06 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
};
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
static void add_missing_data_block_mappings(
|
|
|
|
|
NodesModifierBake &bake,
|
|
|
|
|
const Span<bake::BakeDataBlockID> missing,
|
|
|
|
|
FunctionRef<ID *(const bake::BakeDataBlockID &)> get_data_block)
|
|
|
|
|
{
|
|
|
|
|
const int old_num = bake.data_blocks_num;
|
|
|
|
|
const int new_num = old_num + missing.size();
|
|
|
|
|
bake.data_blocks = reinterpret_cast<NodesModifierDataBlock *>(
|
|
|
|
|
MEM_recallocN(bake.data_blocks, sizeof(NodesModifierDataBlock) * new_num));
|
|
|
|
|
for (const int i : missing.index_range()) {
|
|
|
|
|
NodesModifierDataBlock &data_block = bake.data_blocks[old_num + i];
|
|
|
|
|
const blender::bke::bake::BakeDataBlockID &key = missing[i];
|
|
|
|
|
|
|
|
|
|
data_block.id_name = BLI_strdup(key.id_name.c_str());
|
|
|
|
|
if (!key.lib_name.empty()) {
|
|
|
|
|
data_block.lib_name = BLI_strdup(key.lib_name.c_str());
|
|
|
|
|
}
|
|
|
|
|
data_block.id_type = int(key.type);
|
|
|
|
|
ID *id = get_data_block(key);
|
|
|
|
|
if (id) {
|
|
|
|
|
data_block.id = id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bake.data_blocks_num = new_num;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nodes_modifier_data_block_destruct(NodesModifierDataBlock *data_block, const bool do_id_user)
|
|
|
|
|
{
|
|
|
|
|
MEM_SAFE_FREE(data_block->id_name);
|
|
|
|
|
MEM_SAFE_FREE(data_block->lib_name);
|
|
|
|
|
if (do_id_user) {
|
|
|
|
|
id_us_min(data_block->id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* During evaluation we might have baked geometry that contains references to other data-blocks
|
|
|
|
|
* (such as materials). We need to make sure that those data-blocks stay dependencies of the
|
|
|
|
|
* modifier. Otherwise, the data-block references might not work when the baked data is loaded
|
|
|
|
|
* again. Therefor, the dependencies are written back to the original modifier.
|
|
|
|
|
*/
|
|
|
|
|
static void add_data_block_items_writeback(const ModifierEvalContext &ctx,
|
|
|
|
|
NodesModifierData &nmd_eval,
|
|
|
|
|
NodesModifierData &nmd_orig,
|
|
|
|
|
NodesModifierSimulationParams &simulation_params,
|
|
|
|
|
NodesModifierBakeParams &bake_params)
|
|
|
|
|
{
|
|
|
|
|
Depsgraph *depsgraph = ctx.depsgraph;
|
|
|
|
|
Main *bmain = DEG_get_bmain(depsgraph);
|
|
|
|
|
|
|
|
|
|
struct DataPerBake {
|
|
|
|
|
bool reset_first = false;
|
|
|
|
|
Map<bake::BakeDataBlockID, ID *> new_mappings;
|
|
|
|
|
};
|
|
|
|
|
Map<int, DataPerBake> writeback_data;
|
|
|
|
|
for (auto item : simulation_params.data_by_zone_id.items()) {
|
|
|
|
|
DataPerBake data;
|
|
|
|
|
NodesModifierBake &bake = *nmd_eval.find_bake(item.key);
|
|
|
|
|
if (item.value->data_block_map.old_mappings.size() < bake.data_blocks_num) {
|
|
|
|
|
data.reset_first = true;
|
|
|
|
|
}
|
|
|
|
|
if (bake::SimulationNodeCache *node_cache = nmd_eval.runtime->cache->get_simulation_node_cache(
|
|
|
|
|
item.key))
|
|
|
|
|
{
|
|
|
|
|
/* Only writeback if the bake node has actually baked anything. */
|
2024-11-07 14:25:57 +01:00
|
|
|
if (!node_cache->bake.frames.is_empty() || node_cache->prev_cache.has_value()) {
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
data.new_mappings = std::move(item.value->data_block_map.new_mappings);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (data.reset_first || !data.new_mappings.is_empty()) {
|
|
|
|
|
writeback_data.add(item.key, std::move(data));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (auto item : bake_params.data_by_node_id.items()) {
|
|
|
|
|
if (bake::BakeNodeCache *node_cache = nmd_eval.runtime->cache->get_bake_node_cache(item.key)) {
|
|
|
|
|
/* Only writeback if the bake node has actually baked anything. */
|
|
|
|
|
if (!node_cache->bake.frames.is_empty()) {
|
|
|
|
|
DataPerBake data;
|
|
|
|
|
data.new_mappings = std::move(item.value->data_block_map.new_mappings);
|
|
|
|
|
writeback_data.add(item.key, std::move(data));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (writeback_data.is_empty()) {
|
|
|
|
|
/* Nothing to do. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deg::sync_writeback::add(
|
|
|
|
|
*depsgraph,
|
2024-02-02 12:18:32 +01:00
|
|
|
[object_eval = ctx.object,
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
bmain,
|
|
|
|
|
&nmd_orig,
|
|
|
|
|
&nmd_eval,
|
|
|
|
|
writeback_data = std::move(writeback_data)]() {
|
|
|
|
|
for (auto item : writeback_data.items()) {
|
|
|
|
|
const int bake_id = item.key;
|
|
|
|
|
DataPerBake data = item.value;
|
|
|
|
|
|
|
|
|
|
NodesModifierBake &bake_orig = *nmd_orig.find_bake(bake_id);
|
|
|
|
|
NodesModifierBake &bake_eval = *nmd_eval.find_bake(bake_id);
|
|
|
|
|
|
|
|
|
|
if (data.reset_first) {
|
|
|
|
|
/* Reset data-block list on original data. */
|
|
|
|
|
dna::array::clear<NodesModifierDataBlock>(&bake_orig.data_blocks,
|
|
|
|
|
&bake_orig.data_blocks_num,
|
|
|
|
|
&bake_orig.active_data_block,
|
|
|
|
|
[](NodesModifierDataBlock *data_block) {
|
|
|
|
|
nodes_modifier_data_block_destruct(
|
|
|
|
|
data_block, true);
|
|
|
|
|
});
|
|
|
|
|
/* Reset data-block list on evaluated data. */
|
|
|
|
|
dna::array::clear<NodesModifierDataBlock>(&bake_eval.data_blocks,
|
|
|
|
|
&bake_eval.data_blocks_num,
|
|
|
|
|
&bake_eval.active_data_block,
|
|
|
|
|
[](NodesModifierDataBlock *data_block) {
|
|
|
|
|
nodes_modifier_data_block_destruct(
|
|
|
|
|
data_block, false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<bake::BakeDataBlockID> sorted_new_mappings;
|
|
|
|
|
sorted_new_mappings.extend(data.new_mappings.keys().begin(),
|
|
|
|
|
data.new_mappings.keys().end());
|
|
|
|
|
bool needs_reevaluation = false;
|
|
|
|
|
/* Add new data block mappings to the original modifier. This may do a name lookup in
|
|
|
|
|
* bmain to find the data block if there is not faster way to get it. */
|
|
|
|
|
add_missing_data_block_mappings(
|
|
|
|
|
bake_orig, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * {
|
|
|
|
|
ID *id_orig = nullptr;
|
|
|
|
|
if (ID *id_eval = data.new_mappings.lookup_default(key, nullptr)) {
|
2025-04-17 13:09:20 +02:00
|
|
|
id_orig = DEG_get_original(id_eval);
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
needs_reevaluation = true;
|
|
|
|
|
id_orig = BKE_libblock_find_name_and_library(
|
|
|
|
|
bmain, short(key.type), key.id_name.c_str(), key.lib_name.c_str());
|
|
|
|
|
}
|
|
|
|
|
if (id_orig) {
|
|
|
|
|
id_us_plus(id_orig);
|
|
|
|
|
}
|
|
|
|
|
return id_orig;
|
|
|
|
|
});
|
|
|
|
|
/* Add new data block mappings to the evaluated modifier. In most cases this makes it so
|
|
|
|
|
* the evaluated modifier is in the same state as if it were copied from the updated
|
|
|
|
|
* original again. The exception is when a missing data block was found that is not in
|
|
|
|
|
* the depsgraph currently. */
|
|
|
|
|
add_missing_data_block_mappings(
|
|
|
|
|
bake_eval, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * {
|
|
|
|
|
return data.new_mappings.lookup_default(key, nullptr);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (needs_reevaluation) {
|
2025-04-17 13:09:20 +02:00
|
|
|
Object *object_orig = DEG_get_original(object_eval);
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
DEG_id_tag_update(&object_orig->id, ID_RECALC_GEOMETRY);
|
|
|
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 13:25:25 +01:00
|
|
|
static void modifyGeometry(ModifierData *md,
|
|
|
|
|
const ModifierEvalContext *ctx,
|
2023-06-15 22:18:28 +02:00
|
|
|
bke::GeometrySet &geometry_set)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
2023-03-31 13:19:37 -04:00
|
|
|
using namespace blender;
|
2020-12-02 13:25:25 +01:00
|
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
|
if (nmd->node_group == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-06-12 13:31:09 +02:00
|
|
|
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
|
|
|
|
|
BKE_modifier_get_original(ctx->object, &nmd->modifier));
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2025-03-25 15:40:32 +01:00
|
|
|
nodes::PropertiesVectorSet properties = nodes::build_properties_vector_set(
|
|
|
|
|
nmd->settings.properties);
|
|
|
|
|
|
2022-09-13 08:44:26 +02:00
|
|
|
const bNodeTree &tree = *nmd->node_group;
|
2025-03-25 15:40:32 +01:00
|
|
|
check_property_socket_sync(ctx->object, properties, md);
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2023-09-14 14:13:07 +02:00
|
|
|
tree.ensure_topology_cache();
|
2022-09-13 08:44:26 +02:00
|
|
|
const bNode *output_node = tree.group_output_node();
|
|
|
|
|
if (output_node == nullptr) {
|
|
|
|
|
BKE_modifier_set_error(ctx->object, md, "Node group must have a group output node");
|
2021-11-10 10:52:07 -06:00
|
|
|
geometry_set.clear();
|
2020-12-02 13:25:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-13 08:44:26 +02:00
|
|
|
Span<const bNodeSocket *> group_outputs = output_node->input_sockets().drop_back(1);
|
2021-09-27 15:33:48 +02:00
|
|
|
if (group_outputs.is_empty()) {
|
2021-11-10 10:52:07 -06:00
|
|
|
BKE_modifier_set_error(ctx->object, md, "Node group must have an output socket");
|
|
|
|
|
geometry_set.clear();
|
2020-12-02 13:25:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
const bNodeSocket *first_output_socket = group_outputs[0];
|
|
|
|
|
if (!STREQ(first_output_socket->idname, "NodeSocketGeometry")) {
|
2021-11-10 10:52:07 -06:00
|
|
|
BKE_modifier_set_error(ctx->object, md, "Node group's first output must be a geometry");
|
|
|
|
|
geometry_set.clear();
|
2020-12-02 13:25:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-31 13:19:37 -04:00
|
|
|
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
|
|
|
|
|
nodes::ensure_geometry_nodes_lazy_function_graph(tree);
|
2022-09-13 08:44:26 +02:00
|
|
|
if (lf_graph_info == nullptr) {
|
|
|
|
|
BKE_modifier_set_error(ctx->object, md, "Cannot evaluate node group");
|
|
|
|
|
geometry_set.clear();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-02 16:10:42 +01:00
|
|
|
bool use_orig_index_verts = false;
|
|
|
|
|
bool use_orig_index_edges = false;
|
2023-07-24 22:06:55 +02:00
|
|
|
bool use_orig_index_faces = false;
|
2023-08-03 17:09:18 +02:00
|
|
|
if (const Mesh *mesh = geometry_set.get_mesh()) {
|
2023-07-25 21:15:52 +02:00
|
|
|
use_orig_index_verts = CustomData_has_layer(&mesh->vert_data, CD_ORIGINDEX);
|
|
|
|
|
use_orig_index_edges = CustomData_has_layer(&mesh->edge_data, CD_ORIGINDEX);
|
|
|
|
|
use_orig_index_faces = CustomData_has_layer(&mesh->face_data, CD_ORIGINDEX);
|
2022-03-02 16:10:42 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-29 13:22:20 +01:00
|
|
|
nodes::GeoNodesCallData call_data;
|
|
|
|
|
|
2023-06-12 13:31:09 +02:00
|
|
|
nodes::GeoNodesModifierData modifier_eval_data{};
|
|
|
|
|
modifier_eval_data.depsgraph = ctx->depsgraph;
|
|
|
|
|
modifier_eval_data.self_object = ctx->object;
|
|
|
|
|
auto eval_log = std::make_unique<geo_log::GeoModifierLog>();
|
2023-11-29 13:22:20 +01:00
|
|
|
call_data.modifier_data = &modifier_eval_data;
|
2023-06-12 13:31:09 +02:00
|
|
|
|
Geometry Nodes: refactor simulation storage and how simulation nodes access it
Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
should be more obvious where data is modified. A similar approach should also
work for the Bake node.
Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.
The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.
Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.
Pull Request: https://projects.blender.org/blender/blender/pulls/111623
2023-08-31 16:28:03 +02:00
|
|
|
NodesModifierSimulationParams simulation_params(*nmd, *ctx);
|
2023-11-29 13:22:20 +01:00
|
|
|
call_data.simulation_params = &simulation_params;
|
2023-12-18 13:01:06 +01:00
|
|
|
NodesModifierBakeParams bake_params{*nmd, *ctx};
|
|
|
|
|
call_data.bake_params = &bake_params;
|
2023-06-12 13:31:09 +02:00
|
|
|
|
|
|
|
|
Set<ComputeContextHash> socket_log_contexts;
|
|
|
|
|
if (logging_enabled(ctx)) {
|
2023-11-29 13:22:20 +01:00
|
|
|
call_data.eval_log = eval_log.get();
|
2023-06-12 13:31:09 +02:00
|
|
|
|
|
|
|
|
find_socket_log_contexts(*nmd, *ctx, socket_log_contexts);
|
2023-11-29 13:22:20 +01:00
|
|
|
call_data.socket_log_contexts = &socket_log_contexts;
|
2023-06-12 13:31:09 +02:00
|
|
|
}
|
2023-09-16 18:57:04 +02:00
|
|
|
|
|
|
|
|
nodes::GeoNodesSideEffectNodes side_effect_nodes;
|
Geometry Nodes: support attaching gizmos to input values
This adds support for attaching gizmos for input values. The goal is to make it
easier for users to set input values intuitively in the 3D viewport.
We went through multiple different possible designs until we settled on the one
implemented here. We picked it for it's flexibility and ease of use when using
geometry node assets. The core principle in the design is that **gizmos are
attached to existing input values instead of being the input value themselves**.
This actually fits the existing concept of gizmos in Blender well, but may be a
bit unintutitive in a node setup at first. The attachment is done using links in
the node editor.
The most basic usage of the node is to link a Value node to the new Linear Gizmo
node. This attaches the gizmo to the input value and allows you to change it
from the 3D view. The attachment is indicated by the gizmo icon in the sockets
which are controlled by a gizmo as well as the back-link (notice the double
link) when the gizmo is active.
The core principle makes it straight forward to control the same node setup from
the 3D view with gizmos, or by manually changing input values, or by driving the
input values procedurally.
If the input value is controlled indirectly by other inputs, it's often possible
to **automatically propagate** the gizmo to the actual input.
Backpropagation does not work for all nodes, although more nodes can be
supported over time.
This patch adds the first three gizmo nodes which cover common use cases:
* **Linear Gizmo**: Creates a gizmo that controls a float or integer value using
a linear movement of e.g. an arrow in the 3D viewport.
* **Dial Gizmo**: Creates a circular gizmo in the 3D viewport that can be
rotated to change the attached angle input.
* **Transform Gizmo**: Creates a simple gizmo for location, rotation and scale.
In the future, more built-in gizmos and potentially the ability for custom
gizmos could be added.
All gizmo nodes have a **Transform** geometry output. Using it is optional but
it is recommended when the gizmo is used to control inputs that affect a
geometry. When it is used, Blender will automatically transform the gizmos
together with the geometry that they control. To achieve this, the output should
be merged with the generated geometry using the *Join Geometry* node. The data
contained in *Transform* output is not visible geometry, but just internal
information that helps Blender to give a better user experience when using
gizmos.
The gizmo nodes have a multi-input socket. This allows **controlling multiple
values** with the same gizmo.
Only a small set of **gizmo shapes** is supported initially. It might be
extended in the future but one goal is to give the gizmos used by different node
group assets a familiar look and feel. A similar constraint exists for
**colors**. Currently, one can choose from a fixed set of colors which can be
modified in the theme settings.
The set of **visible gizmos** is determined by a multiple factors because it's
not really feasible to show all possible gizmos at all times. To see any of the
geometry nodes gizmos, the "Active Modifier" option has to be enabled in the
"Viewport Gizmos" popover. Then all gizmos are drawn for which at least one of
the following is true:
* The gizmo controls an input of the active modifier of the active object.
* The gizmo controls a value in a selected node in an open node editor.
* The gizmo controls a pinned value in an open node editor. Pinning works by
clicking the gizmo icon next to the value.
Pull Request: https://projects.blender.org/blender/blender/pulls/112677
2024-07-10 16:18:47 +02:00
|
|
|
find_side_effect_nodes(*nmd, *ctx, side_effect_nodes, socket_log_contexts);
|
2023-11-29 13:22:20 +01:00
|
|
|
call_data.side_effect_nodes = &side_effect_nodes;
|
2023-06-12 13:31:09 +02:00
|
|
|
|
2025-04-10 08:56:02 +02:00
|
|
|
bke::ModifierComputeContext modifier_compute_context{nullptr, *nmd};
|
2023-06-12 13:31:09 +02:00
|
|
|
|
2025-03-25 15:40:32 +01:00
|
|
|
geometry_set = nodes::execute_geometry_nodes_on_geometry(
|
|
|
|
|
tree, properties, modifier_compute_context, call_data, std::move(geometry_set));
|
2023-06-12 13:31:09 +02:00
|
|
|
|
|
|
|
|
if (logging_enabled(ctx)) {
|
2023-07-11 12:55:57 +02:00
|
|
|
nmd_orig->runtime->eval_log = std::move(eval_log);
|
2023-06-12 13:31:09 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-01 13:36:43 +02:00
|
|
|
if (DEG_is_active(ctx->depsgraph) && !(ctx->flag & MOD_APPLY_TO_ORIGINAL)) {
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
add_data_block_items_writeback(*ctx, *nmd, *nmd_orig, simulation_params, bake_params);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-24 22:06:55 +02:00
|
|
|
if (use_orig_index_verts || use_orig_index_edges || use_orig_index_faces) {
|
2022-12-19 14:24:49 -06:00
|
|
|
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
|
|
|
|
|
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
|
|
|
|
|
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
|
|
|
|
|
* assumed that the output mesh does not have a mapping to the original mesh. */
|
|
|
|
|
if (use_orig_index_verts) {
|
2023-12-20 02:21:48 +01:00
|
|
|
CustomData_add_layer(&mesh->vert_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->verts_num);
|
2022-12-19 14:24:49 -06:00
|
|
|
}
|
|
|
|
|
if (use_orig_index_edges) {
|
2023-12-20 02:21:48 +01:00
|
|
|
CustomData_add_layer(&mesh->edge_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->edges_num);
|
2022-12-19 14:24:49 -06:00
|
|
|
}
|
2023-07-24 22:06:55 +02:00
|
|
|
if (use_orig_index_faces) {
|
2023-07-25 21:15:52 +02:00
|
|
|
CustomData_add_layer(&mesh->face_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->faces_num);
|
2022-12-19 14:24:49 -06:00
|
|
|
}
|
2022-03-02 16:10:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
|
2022-12-17 14:46:15 +01:00
|
|
|
{
|
2023-08-03 17:09:18 +02:00
|
|
|
bke::GeometrySet geometry_set = bke::GeometrySet::from_mesh(
|
2023-06-15 22:18:28 +02:00
|
|
|
mesh, bke::GeometryOwnershipType::Editable);
|
2022-12-17 14:46:15 +01:00
|
|
|
|
|
|
|
|
modifyGeometry(md, ctx, geometry_set);
|
|
|
|
|
|
2025-04-08 16:11:52 +02:00
|
|
|
bke::MeshComponent &mesh_component = geometry_set.get_component_for_write<bke::MeshComponent>();
|
|
|
|
|
if (mesh_component.get() != mesh) {
|
|
|
|
|
/* If this is the same as the input mesh, it's not necessary to make a copy of it even if it's
|
|
|
|
|
* not owned by the geometry set. That's because we know that the caller manages the ownership
|
|
|
|
|
* of the mesh. */
|
|
|
|
|
mesh_component.ensure_owns_direct_data();
|
|
|
|
|
}
|
|
|
|
|
Mesh *new_mesh = mesh_component.release();
|
2022-12-17 14:46:15 +01:00
|
|
|
if (new_mesh == nullptr) {
|
2023-02-27 11:09:26 -05:00
|
|
|
return BKE_mesh_new_nomain(0, 0, 0, 0);
|
2022-12-17 14:46:15 +01:00
|
|
|
}
|
|
|
|
|
return new_mesh;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void modify_geometry_set(ModifierData *md,
|
|
|
|
|
const ModifierEvalContext *ctx,
|
|
|
|
|
bke::GeometrySet *geometry_set)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
modifyGeometry(md, ctx, *geometry_set);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 12:35:49 +02:00
|
|
|
struct SocketSearchData {
|
2021-11-05 11:19:12 -05:00
|
|
|
uint32_t object_session_uid;
|
|
|
|
|
char modifier_name[MAX_NAME];
|
|
|
|
|
char socket_identifier[MAX_NAME];
|
2021-10-21 13:54:48 -05:00
|
|
|
bool is_output;
|
|
|
|
|
};
|
|
|
|
|
/* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */
|
2025-04-18 12:35:49 +02:00
|
|
|
BLI_STATIC_ASSERT(std::is_trivially_destructible_v<SocketSearchData>, "");
|
|
|
|
|
|
|
|
|
|
struct DrawGroupInputsContext {
|
|
|
|
|
const bContext &C;
|
|
|
|
|
NodesModifierData &nmd;
|
|
|
|
|
nodes::PropertiesVectorSet properties;
|
|
|
|
|
PointerRNA *md_ptr;
|
|
|
|
|
PointerRNA *bmain_ptr;
|
|
|
|
|
Array<bool> input_usages;
|
|
|
|
|
};
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2021-11-10 15:43:18 -06:00
|
|
|
static NodesModifierData *get_modifier_data(Main &bmain,
|
|
|
|
|
const wmWindowManager &wm,
|
2025-04-18 12:35:49 +02:00
|
|
|
const SocketSearchData &data)
|
2021-10-21 13:54:48 -05:00
|
|
|
{
|
2021-11-10 15:43:18 -06:00
|
|
|
if (ED_screen_animation_playing(&wm)) {
|
|
|
|
|
/* Work around an issue where the attribute search exec function has stale pointers when data
|
|
|
|
|
* is reallocated when evaluating the node tree, causing a crash. This would be solved by
|
|
|
|
|
* allowing the UI search data to own arbitrary memory rather than just referencing it. */
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-22 13:47:13 +01:00
|
|
|
const Object *object = (Object *)BKE_libblock_find_session_uid(
|
2021-11-05 11:19:12 -05:00
|
|
|
&bmain, ID_OB, data.object_session_uid);
|
|
|
|
|
if (object == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
ModifierData *md = BKE_modifiers_findby_name(object, data.modifier_name);
|
|
|
|
|
if (md == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
BLI_assert(md->type == eModifierType_Nodes);
|
|
|
|
|
return reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
|
}
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2023-03-31 13:19:37 -04:00
|
|
|
static geo_log::GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
|
2022-09-13 08:44:26 +02:00
|
|
|
{
|
2023-07-11 12:55:57 +02:00
|
|
|
if (!nmd.runtime->eval_log) {
|
2022-09-13 08:44:26 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2025-04-10 08:56:02 +02:00
|
|
|
bke::ModifierComputeContext compute_context{nullptr, nmd};
|
2023-07-11 12:55:57 +02:00
|
|
|
return &nmd.runtime->eval_log->get_tree_log(compute_context.hash());
|
2022-09-13 08:44:26 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-05 11:19:12 -05:00
|
|
|
static void attribute_search_update_fn(
|
|
|
|
|
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
|
|
|
|
{
|
2025-04-18 12:35:49 +02:00
|
|
|
SocketSearchData &data = *static_cast<SocketSearchData *>(arg);
|
2021-11-10 15:43:18 -06:00
|
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
2021-11-05 11:19:12 -05:00
|
|
|
if (nmd == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-09-13 08:44:26 +02:00
|
|
|
if (nmd->node_group == nullptr) {
|
2021-11-05 11:19:12 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2023-03-31 13:19:37 -04:00
|
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
2022-09-13 08:44:26 +02:00
|
|
|
if (tree_log == nullptr) {
|
2021-10-21 13:54:48 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2022-09-13 08:44:26 +02:00
|
|
|
tree_log->ensure_existing_attributes();
|
|
|
|
|
nmd->node_group->ensure_topology_cache();
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2022-09-13 08:44:26 +02:00
|
|
|
Vector<const bNodeSocket *> sockets_to_check;
|
|
|
|
|
if (data.is_output) {
|
|
|
|
|
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupOutput")) {
|
|
|
|
|
for (const bNodeSocket *socket : node->input_sockets()) {
|
|
|
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
|
|
|
sockets_to_check.append(socket);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
2022-12-13 17:25:37 +01:00
|
|
|
for (const bNode *node : nmd->node_group->group_input_nodes()) {
|
2022-09-13 08:44:26 +02:00
|
|
|
for (const bNodeSocket *socket : node->output_sockets()) {
|
|
|
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
|
|
|
sockets_to_check.append(socket);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Set<StringRef> names;
|
2023-03-31 13:19:37 -04:00
|
|
|
Vector<const geo_log::GeometryAttributeInfo *> attributes;
|
2022-09-13 08:44:26 +02:00
|
|
|
for (const bNodeSocket *socket : sockets_to_check) {
|
2023-03-31 13:19:37 -04:00
|
|
|
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
|
2022-09-13 08:44:26 +02:00
|
|
|
if (value_log == nullptr) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-03-31 13:19:37 -04:00
|
|
|
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
|
|
|
|
|
for (const geo_log::GeometryAttributeInfo &attribute : geo_log->attributes) {
|
2022-09-13 08:44:26 +02:00
|
|
|
if (names.add(attribute.name)) {
|
|
|
|
|
attributes.append(&attribute);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-21 13:54:48 -05:00
|
|
|
}
|
2023-03-31 13:19:37 -04:00
|
|
|
ui::attribute_search_add_items(str, data.is_output, attributes.as_span(), items, is_first);
|
2021-10-21 13:54:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
|
|
|
|
|
{
|
|
|
|
|
if (item_v == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-04-18 12:35:49 +02:00
|
|
|
SocketSearchData &data = *static_cast<SocketSearchData *>(data_v);
|
2023-03-31 13:19:37 -04:00
|
|
|
const auto &item = *static_cast<const geo_log::GeometryAttributeInfo *>(item_v);
|
2021-11-10 15:43:18 -06:00
|
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
2021-11-05 11:19:12 -05:00
|
|
|
if (nmd == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2023-06-12 13:31:09 +02:00
|
|
|
const std::string attribute_prop_name = data.socket_identifier +
|
2024-12-04 12:12:29 -05:00
|
|
|
nodes::input_attribute_name_suffix;
|
2021-11-05 11:19:12 -05:00
|
|
|
IDProperty &name_property = *IDP_GetPropertyFromGroup(nmd->settings.properties,
|
2024-12-04 12:12:29 -05:00
|
|
|
attribute_prop_name);
|
2023-05-02 15:07:55 +10:00
|
|
|
IDP_AssignString(&name_property, item.name.c_str());
|
2021-10-21 13:54:48 -05:00
|
|
|
|
|
|
|
|
ED_undo_push(C, "Assign Attribute Name");
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-23 12:49:56 +01:00
|
|
|
static void add_attribute_search_button(DrawGroupInputsContext &ctx,
|
2021-11-05 11:19:12 -05:00
|
|
|
uiLayout *layout,
|
2021-10-21 13:54:48 -05:00
|
|
|
const StringRefNull rna_path_attribute_name,
|
2023-08-30 12:37:21 +02:00
|
|
|
const bNodeTreeInterfaceSocket &socket,
|
2021-10-21 13:54:48 -05:00
|
|
|
const bool is_output)
|
|
|
|
|
{
|
2024-12-23 12:49:56 +01:00
|
|
|
if (!ctx.nmd.runtime->eval_log) {
|
2025-05-08 20:45:37 +02:00
|
|
|
layout->prop(ctx.md_ptr, rna_path_attribute_name, UI_ITEM_NONE, "", ICON_NONE);
|
2021-10-21 13:54:48 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
|
|
|
|
uiBut *but = uiDefIconTextButR(block,
|
|
|
|
|
UI_BTYPE_SEARCH_MENU,
|
|
|
|
|
0,
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
"",
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
|
|
|
|
|
UI_UNIT_Y,
|
2024-12-23 12:49:56 +01:00
|
|
|
ctx.md_ptr,
|
2024-12-06 14:08:10 +01:00
|
|
|
rna_path_attribute_name,
|
2021-10-21 13:54:48 -05:00
|
|
|
0,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
2025-02-14 15:12:48 -05:00
|
|
|
StringRef(socket.description));
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2024-12-23 12:49:56 +01:00
|
|
|
const Object *object = ed::object::context_object(&ctx.C);
|
2021-11-05 11:19:12 -05:00
|
|
|
BLI_assert(object != nullptr);
|
|
|
|
|
if (object == nullptr) {
|
2021-10-21 13:54:48 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 12:35:49 +02:00
|
|
|
SocketSearchData *data = MEM_callocN<SocketSearchData>(__func__);
|
2024-01-22 13:47:13 +01:00
|
|
|
data->object_session_uid = object->id.session_uid;
|
2024-12-23 12:49:56 +01:00
|
|
|
STRNCPY(data->modifier_name, ctx.nmd.modifier.name);
|
2021-11-05 11:19:12 -05:00
|
|
|
STRNCPY(data->socket_identifier, socket.identifier);
|
|
|
|
|
data->is_output = is_output;
|
2021-10-21 13:54:48 -05:00
|
|
|
|
|
|
|
|
UI_but_func_search_set_results_are_suggestions(but, true);
|
|
|
|
|
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
|
|
|
|
|
UI_but_func_search_set(but,
|
|
|
|
|
nullptr,
|
|
|
|
|
attribute_search_update_fn,
|
|
|
|
|
static_cast<void *>(data),
|
|
|
|
|
true,
|
|
|
|
|
nullptr,
|
|
|
|
|
attribute_search_exec_fn,
|
|
|
|
|
nullptr);
|
2022-05-31 18:52:27 +02:00
|
|
|
|
|
|
|
|
char *attribute_name = RNA_string_get_alloc(
|
2024-12-23 12:49:56 +01:00
|
|
|
ctx.md_ptr, rna_path_attribute_name.c_str(), nullptr, 0, nullptr);
|
2023-03-31 13:19:37 -04:00
|
|
|
const bool access_allowed = bke::allow_procedural_attribute_access(attribute_name);
|
2022-05-31 18:52:27 +02:00
|
|
|
MEM_freeN(attribute_name);
|
|
|
|
|
if (!access_allowed) {
|
|
|
|
|
UI_but_flag_enable(but, UI_BUT_REDALERT);
|
|
|
|
|
}
|
2021-10-21 13:54:48 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-23 12:49:56 +01:00
|
|
|
static void add_attribute_search_or_value_buttons(DrawGroupInputsContext &ctx,
|
2021-11-05 11:19:12 -05:00
|
|
|
uiLayout *layout,
|
2024-12-04 12:37:54 -05:00
|
|
|
const StringRef socket_id_esc,
|
|
|
|
|
const StringRefNull rna_path,
|
2023-08-30 12:37:21 +02:00
|
|
|
const bNodeTreeInterfaceSocket &socket)
|
2021-10-21 13:54:48 -05:00
|
|
|
{
|
2024-05-13 16:07:12 +02:00
|
|
|
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
|
2023-08-30 12:37:21 +02:00
|
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
2024-12-04 12:37:54 -05:00
|
|
|
const std::string rna_path_attribute_name = fmt::format(
|
|
|
|
|
"[\"{}{}\"]", socket_id_esc, nodes::input_attribute_name_suffix);
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2022-11-08 19:09:28 +01:00
|
|
|
/* We're handling this manually in this case. */
|
|
|
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
|
|
2025-05-03 20:51:42 +02:00
|
|
|
uiLayout *split = &layout->split(0.4f, false);
|
2025-04-25 19:45:25 +02:00
|
|
|
uiLayout *name_row = &split->row(false);
|
2021-10-21 13:54:48 -05:00
|
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
2023-01-20 17:41:34 -06:00
|
|
|
|
2025-01-07 15:48:43 +01:00
|
|
|
uiLayout *prop_row = nullptr;
|
|
|
|
|
|
2025-03-25 15:40:32 +01:00
|
|
|
const std::optional<StringRef> attribute_name = nodes::input_attribute_name_get(ctx.properties,
|
|
|
|
|
socket);
|
2023-08-30 12:37:21 +02:00
|
|
|
if (type == SOCK_BOOLEAN && !attribute_name) {
|
2025-05-08 17:21:08 +02:00
|
|
|
name_row->label("", ICON_NONE);
|
2025-04-25 19:45:25 +02:00
|
|
|
prop_row = &split->row(true);
|
2023-01-20 17:36:07 -06:00
|
|
|
}
|
|
|
|
|
else {
|
2025-04-25 19:45:25 +02:00
|
|
|
prop_row = &layout->row(true);
|
2023-01-20 17:36:07 -06:00
|
|
|
}
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2023-08-30 12:37:21 +02:00
|
|
|
if (type == SOCK_BOOLEAN) {
|
2023-01-20 17:41:34 -06:00
|
|
|
uiLayoutSetPropSep(prop_row, false);
|
|
|
|
|
uiLayoutSetAlignment(prop_row, UI_LAYOUT_ALIGN_EXPAND);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 13:36:25 +02:00
|
|
|
if (attribute_name) {
|
2025-05-08 17:21:08 +02:00
|
|
|
name_row->label(socket.name ? IFACE_(socket.name) : "", ICON_NONE);
|
2025-04-25 19:45:25 +02:00
|
|
|
prop_row = &split->row(true);
|
2024-12-23 12:49:56 +01:00
|
|
|
add_attribute_search_button(ctx, prop_row, rna_path_attribute_name, socket, false);
|
2025-05-08 17:21:08 +02:00
|
|
|
layout->label("", ICON_BLANK1);
|
2023-01-20 17:41:34 -06:00
|
|
|
}
|
|
|
|
|
else {
|
2025-01-07 15:48:43 +01:00
|
|
|
const char *name = socket.name ? IFACE_(socket.name) : "";
|
2025-05-08 20:45:37 +02:00
|
|
|
prop_row->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
2024-12-23 12:49:56 +01:00
|
|
|
uiItemDecoratorR(layout, ctx.md_ptr, rna_path.c_str(), -1);
|
2023-01-20 17:36:07 -06:00
|
|
|
}
|
2021-10-21 13:54:48 -05:00
|
|
|
|
|
|
|
|
PointerRNA props;
|
2022-11-08 19:09:28 +01:00
|
|
|
uiItemFullO(prop_row,
|
2021-10-21 13:54:48 -05:00
|
|
|
"object.geometry_nodes_input_attribute_toggle",
|
|
|
|
|
"",
|
|
|
|
|
ICON_SPREADSHEET,
|
|
|
|
|
nullptr,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
2023-07-29 15:06:33 +10:00
|
|
|
UI_ITEM_NONE,
|
2021-10-21 13:54:48 -05:00
|
|
|
&props);
|
2024-12-23 12:49:56 +01:00
|
|
|
RNA_string_set(&props, "modifier_name", ctx.nmd.modifier.name);
|
2023-08-24 13:36:25 +02:00
|
|
|
RNA_string_set(&props, "input_name", socket.identifier);
|
2021-10-21 13:54:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-04-18 12:35:49 +02:00
|
|
|
static void layer_name_search_update_fn(
|
|
|
|
|
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
|
|
|
|
{
|
|
|
|
|
const SocketSearchData &data = *static_cast<SocketSearchData *>(arg);
|
|
|
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
|
|
|
if (nmd == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (nmd->node_group == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
|
|
|
|
if (tree_log == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
tree_log->ensure_layer_names();
|
|
|
|
|
nmd->node_group->ensure_topology_cache();
|
|
|
|
|
|
|
|
|
|
Vector<const bNodeSocket *> sockets_to_check;
|
|
|
|
|
for (const bNode *node : nmd->node_group->group_input_nodes()) {
|
|
|
|
|
for (const bNodeSocket *socket : node->output_sockets()) {
|
|
|
|
|
if (socket->type == SOCK_GEOMETRY) {
|
|
|
|
|
sockets_to_check.append(socket);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Set<StringRef> names;
|
|
|
|
|
Vector<const std::string *> layer_names;
|
|
|
|
|
for (const bNodeSocket *socket : sockets_to_check) {
|
|
|
|
|
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
|
|
|
|
|
if (value_log == nullptr) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
|
|
|
|
|
if (const std::optional<geo_log::GeometryInfoLog::GreasePencilInfo> &grease_pencil_info =
|
|
|
|
|
geo_log->grease_pencil_info)
|
|
|
|
|
{
|
|
|
|
|
for (const std::string &name : grease_pencil_info->layer_names) {
|
|
|
|
|
if (names.add(name)) {
|
|
|
|
|
layer_names.append(&name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BLI_assert(items);
|
|
|
|
|
ui::grease_pencil_layer_search_add_items(str, layer_names.as_span(), *items, is_first);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void layer_name_search_exec_fn(bContext *C, void *data_v, void *item_v)
|
|
|
|
|
{
|
|
|
|
|
const SocketSearchData &data = *static_cast<SocketSearchData *>(data_v);
|
|
|
|
|
const std::string *item = static_cast<std::string *>(item_v);
|
|
|
|
|
if (item == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
|
|
|
|
|
if (nmd == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IDProperty &name_property = *IDP_GetPropertyFromGroup(nmd->settings.properties,
|
|
|
|
|
data.socket_identifier);
|
|
|
|
|
IDP_AssignString(&name_property, item->c_str());
|
|
|
|
|
|
|
|
|
|
ED_undo_push(C, "Assign Layer Name");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void add_layer_name_search_button(DrawGroupInputsContext &ctx,
|
|
|
|
|
uiLayout *layout,
|
|
|
|
|
const StringRefNull socket_id_esc,
|
|
|
|
|
const bNodeTreeInterfaceSocket &socket)
|
|
|
|
|
{
|
|
|
|
|
const std::string rna_path = fmt::format("[\"{}\"]", socket_id_esc);
|
|
|
|
|
if (!ctx.nmd.runtime->eval_log) {
|
2025-05-08 20:45:37 +02:00
|
|
|
layout->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, "", ICON_NONE);
|
2025-04-18 12:35:49 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
|
|
2025-05-03 20:51:42 +02:00
|
|
|
uiLayout *split = &layout->split(0.4f, false);
|
2025-04-25 19:45:25 +02:00
|
|
|
uiLayout *name_row = &split->row(false);
|
2025-04-18 12:35:49 +02:00
|
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
|
|
|
|
|
2025-05-08 17:21:08 +02:00
|
|
|
name_row->label(socket.name ? IFACE_(socket.name) : "", ICON_NONE);
|
2025-04-25 19:45:25 +02:00
|
|
|
uiLayout *prop_row = &split->row(true);
|
2025-04-18 12:35:49 +02:00
|
|
|
|
|
|
|
|
uiBlock *block = uiLayoutGetBlock(prop_row);
|
|
|
|
|
uiBut *but = uiDefIconTextButR(block,
|
|
|
|
|
UI_BTYPE_SEARCH_MENU,
|
|
|
|
|
0,
|
|
|
|
|
ICON_OUTLINER_DATA_GP_LAYER,
|
|
|
|
|
"",
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
|
|
|
|
|
UI_UNIT_Y,
|
|
|
|
|
ctx.md_ptr,
|
|
|
|
|
rna_path,
|
|
|
|
|
0,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
StringRef(socket.description));
|
|
|
|
|
UI_but_placeholder_set(but, "Layer");
|
2025-05-08 17:21:08 +02:00
|
|
|
layout->label("", ICON_BLANK1);
|
2025-04-18 12:35:49 +02:00
|
|
|
|
|
|
|
|
const Object *object = ed::object::context_object(&ctx.C);
|
|
|
|
|
BLI_assert(object != nullptr);
|
|
|
|
|
if (object == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SocketSearchData *data = MEM_callocN<SocketSearchData>(__func__);
|
|
|
|
|
data->object_session_uid = object->id.session_uid;
|
|
|
|
|
STRNCPY(data->modifier_name, ctx.nmd.modifier.name);
|
|
|
|
|
STRNCPY(data->socket_identifier, socket.identifier);
|
|
|
|
|
|
|
|
|
|
UI_but_func_search_set_results_are_suggestions(but, true);
|
|
|
|
|
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
|
|
|
|
|
UI_but_func_search_set(but,
|
|
|
|
|
nullptr,
|
|
|
|
|
layer_name_search_update_fn,
|
|
|
|
|
static_cast<void *>(data),
|
|
|
|
|
true,
|
|
|
|
|
nullptr,
|
|
|
|
|
layer_name_search_exec_fn,
|
|
|
|
|
nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-08 20:45:37 +02:00
|
|
|
/* Drawing the properties manually with #uiLayout::prop instead of #uiDefAutoButsRNA allows using
|
2020-12-02 13:25:25 +01:00
|
|
|
* the node socket identifier for the property names, since they are unique, but also having
|
2021-06-24 15:56:58 +10:00
|
|
|
* the correct label displayed in the UI. */
|
2024-12-23 12:49:56 +01:00
|
|
|
static void draw_property_for_socket(DrawGroupInputsContext &ctx,
|
2021-11-05 11:19:12 -05:00
|
|
|
uiLayout *layout,
|
2024-01-09 11:01:12 +01:00
|
|
|
const bNodeTreeInterfaceSocket &socket)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
2023-08-30 12:37:21 +02:00
|
|
|
const StringRefNull identifier = socket.identifier;
|
2020-12-02 13:25:25 +01:00
|
|
|
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
|
2025-03-25 15:40:32 +01:00
|
|
|
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
|
2020-12-02 13:25:25 +01:00
|
|
|
|
|
|
|
|
/* IDProperties can be removed with python, so there could be a situation where
|
|
|
|
|
* there isn't a property for a socket or it doesn't have the correct type. */
|
2023-06-12 13:31:09 +02:00
|
|
|
if (property == nullptr || !nodes::id_property_type_matches_socket(socket, *property)) {
|
2021-04-29 10:46:55 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2020-12-14 18:44:04 +11:00
|
|
|
|
2025-05-13 05:30:54 +02:00
|
|
|
const std::string socket_id_esc = BLI_str_escape(identifier.c_str());
|
|
|
|
|
const std::string rna_path = fmt::format("[\"{}\"]", socket_id_esc);
|
2021-01-13 08:13:57 -06:00
|
|
|
|
2025-01-21 12:53:24 +01:00
|
|
|
const int input_index = ctx.nmd.node_group->interface_input_index(socket);
|
|
|
|
|
|
2025-04-25 19:45:25 +02:00
|
|
|
uiLayout *row = &layout->row(true);
|
2022-11-08 19:09:28 +01:00
|
|
|
uiLayoutSetPropDecorate(row, true);
|
2025-01-21 12:53:24 +01:00
|
|
|
uiLayoutSetActive(row, ctx.input_usages[input_index]);
|
2024-01-09 11:01:12 +01:00
|
|
|
|
2025-05-08 20:45:37 +02:00
|
|
|
/* Use #uiItemPointerR to draw pointer properties because #uiLayout::prop would not have enough
|
2021-04-29 10:46:55 -05:00
|
|
|
* information about what type of ID to select for editing the values. This is because
|
|
|
|
|
* pointer IDProperties contain no information about their type. */
|
2024-05-13 16:07:12 +02:00
|
|
|
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
|
2023-08-30 12:37:21 +02:00
|
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
2023-10-23 00:14:18 +02:00
|
|
|
const char *name = socket.name ? IFACE_(socket.name) : "";
|
2023-08-30 12:37:21 +02:00
|
|
|
switch (type) {
|
2021-04-29 10:46:55 -05:00
|
|
|
case SOCK_OBJECT: {
|
2024-12-23 12:49:56 +01:00
|
|
|
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "objects", name, ICON_OBJECT_DATA);
|
2021-04-29 10:46:55 -05:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SOCK_COLLECTION: {
|
2022-11-08 19:09:28 +01:00
|
|
|
uiItemPointerR(
|
2024-12-23 12:49:56 +01:00
|
|
|
row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "collections", name, ICON_OUTLINER_COLLECTION);
|
2021-04-29 10:46:55 -05:00
|
|
|
break;
|
2021-01-13 08:13:57 -06:00
|
|
|
}
|
2021-05-27 11:06:08 -04:00
|
|
|
case SOCK_MATERIAL: {
|
2024-12-23 12:49:56 +01:00
|
|
|
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "materials", name, ICON_MATERIAL);
|
2021-05-27 11:06:08 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SOCK_TEXTURE: {
|
2024-12-23 12:49:56 +01:00
|
|
|
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "textures", name, ICON_TEXTURE);
|
2021-05-27 11:06:08 -04:00
|
|
|
break;
|
|
|
|
|
}
|
2021-10-14 14:18:24 +01:00
|
|
|
case SOCK_IMAGE: {
|
2025-03-04 23:59:22 +01:00
|
|
|
uiTemplateID(row,
|
|
|
|
|
&ctx.C,
|
|
|
|
|
ctx.md_ptr,
|
|
|
|
|
rna_path,
|
|
|
|
|
"image.new",
|
|
|
|
|
"image.open",
|
|
|
|
|
nullptr,
|
|
|
|
|
UI_TEMPLATE_ID_FILTER_ALL,
|
|
|
|
|
false,
|
|
|
|
|
name);
|
2021-10-14 14:18:24 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2025-05-08 04:31:09 +02:00
|
|
|
case SOCK_MENU: {
|
|
|
|
|
if (socket.flag & NODE_INTERFACE_SOCKET_MENU_EXPANDED) {
|
2025-05-14 14:04:11 +02:00
|
|
|
/* Use a single space when the name is empty to work around a bug with expanded enums. Also
|
|
|
|
|
* see #ui_item_enum_expand_exec. */
|
|
|
|
|
row->prop(ctx.md_ptr,
|
|
|
|
|
rna_path,
|
|
|
|
|
UI_ITEM_R_EXPAND,
|
|
|
|
|
StringRef(name).is_empty() ? " " : name,
|
|
|
|
|
ICON_NONE);
|
2025-05-08 04:31:09 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-08 20:45:37 +02:00
|
|
|
row->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
2025-05-08 04:31:09 +02:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-10-20 14:51:30 +02:00
|
|
|
case SOCK_BOOLEAN: {
|
|
|
|
|
if (is_layer_selection_field(socket)) {
|
2025-04-18 12:35:49 +02:00
|
|
|
add_layer_name_search_button(ctx, row, socket_id_esc, socket);
|
|
|
|
|
/* Adds a spacing at the end of the row. */
|
2025-05-08 17:21:08 +02:00
|
|
|
row->label("", ICON_BLANK1);
|
2023-10-20 14:51:30 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
ATTR_FALLTHROUGH;
|
|
|
|
|
}
|
2021-09-16 20:49:10 -05:00
|
|
|
default: {
|
2024-12-23 12:49:56 +01:00
|
|
|
if (nodes::input_has_attribute_toggle(*ctx.nmd.node_group, input_index)) {
|
|
|
|
|
add_attribute_search_or_value_buttons(ctx, row, socket_id_esc, rna_path, socket);
|
2021-09-16 20:49:10 -05:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-08 20:45:37 +02:00
|
|
|
row->prop(ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
|
2021-09-16 20:49:10 -05:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
2024-12-23 12:49:56 +01:00
|
|
|
if (!nodes::input_has_attribute_toggle(*ctx.nmd.node_group, input_index)) {
|
2025-05-08 17:21:08 +02:00
|
|
|
row->label("", ICON_BLANK1);
|
2023-01-20 17:41:34 -06:00
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-23 12:49:56 +01:00
|
|
|
static void draw_property_for_output_socket(DrawGroupInputsContext &ctx,
|
2021-11-05 11:19:12 -05:00
|
|
|
uiLayout *layout,
|
2023-08-30 12:37:21 +02:00
|
|
|
const bNodeTreeInterfaceSocket &socket)
|
2021-09-27 15:33:48 +02:00
|
|
|
{
|
2023-08-30 12:37:21 +02:00
|
|
|
const StringRefNull identifier = socket.identifier;
|
|
|
|
|
char socket_id_esc[MAX_NAME * 2];
|
|
|
|
|
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
2024-12-04 12:37:54 -05:00
|
|
|
const std::string rna_path_attribute_name = fmt::format(
|
|
|
|
|
"[\"{}{}\"]", socket_id_esc, nodes::input_attribute_name_suffix);
|
2021-09-27 15:33:48 +02:00
|
|
|
|
2025-05-03 20:51:42 +02:00
|
|
|
uiLayout *split = &layout->split(0.4f, false);
|
2025-04-25 19:45:25 +02:00
|
|
|
uiLayout *name_row = &split->row(false);
|
2021-10-21 13:54:48 -05:00
|
|
|
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
|
2025-05-08 17:21:08 +02:00
|
|
|
name_row->label(socket.name ? socket.name : "", ICON_NONE);
|
2021-10-21 13:54:48 -05:00
|
|
|
|
2025-04-25 19:45:25 +02:00
|
|
|
uiLayout *row = &split->row(true);
|
2024-12-23 12:49:56 +01:00
|
|
|
add_attribute_search_button(ctx, row, rna_path_attribute_name, socket, true);
|
2021-09-27 15:33:48 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-23 16:33:14 +01:00
|
|
|
static NodesModifierPanel *find_panel_by_id(NodesModifierData &nmd, const int id)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
2023-12-23 16:33:14 +01:00
|
|
|
for (const int i : IndexRange(nmd.panels_num)) {
|
|
|
|
|
if (nmd.panels[i].id == id) {
|
|
|
|
|
return &nmd.panels[i];
|
|
|
|
|
}
|
2023-09-07 14:47:40 +02:00
|
|
|
}
|
2023-12-23 16:33:14 +01:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
|
2024-12-23 12:21:00 +01:00
|
|
|
static bool interface_panel_has_socket(const bNodeTreeInterfacePanel &interface_panel)
|
2024-10-21 15:47:16 +02:00
|
|
|
{
|
|
|
|
|
for (const bNodeTreeInterfaceItem *item : interface_panel.items()) {
|
|
|
|
|
if (item->item_type == NODE_INTERFACE_SOCKET) {
|
2024-12-01 07:37:06 +01:00
|
|
|
const bNodeTreeInterfaceSocket &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(
|
|
|
|
|
item);
|
|
|
|
|
if ((socket.flag &
|
|
|
|
|
(NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER | NODE_INTERFACE_SOCKET_OUTPUT)) == 0)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-10-21 15:47:16 +02:00
|
|
|
}
|
|
|
|
|
if (item->item_type == NODE_INTERFACE_PANEL) {
|
2024-12-23 12:21:00 +01:00
|
|
|
if (interface_panel_has_socket(*reinterpret_cast<const bNodeTreeInterfacePanel *>(item))) {
|
2024-10-21 15:47:16 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-21 12:53:24 +01:00
|
|
|
static bool interface_panel_affects_output(DrawGroupInputsContext &ctx,
|
|
|
|
|
const bNodeTreeInterfacePanel &panel)
|
|
|
|
|
{
|
|
|
|
|
for (const bNodeTreeInterfaceItem *item : panel.items()) {
|
|
|
|
|
if (item->item_type == NODE_INTERFACE_SOCKET) {
|
|
|
|
|
const auto &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
|
2025-03-07 18:46:53 +01:00
|
|
|
if (socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!(socket.flag & NODE_INTERFACE_SOCKET_INPUT)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-01-21 12:53:24 +01:00
|
|
|
const int input_index = ctx.nmd.node_group->interface_input_index(socket);
|
|
|
|
|
if (ctx.input_usages[input_index]) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (item->item_type == NODE_INTERFACE_PANEL) {
|
|
|
|
|
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
|
|
|
|
|
if (interface_panel_affects_output(ctx, sub_interface_panel)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-23 12:49:56 +01:00
|
|
|
static void draw_interface_panel_content(DrawGroupInputsContext &ctx,
|
2023-12-23 16:33:14 +01:00
|
|
|
uiLayout *layout,
|
2025-02-28 19:07:02 +01:00
|
|
|
const bNodeTreeInterfacePanel &interface_panel,
|
|
|
|
|
const bool skip_first = false)
|
2023-12-23 16:33:14 +01:00
|
|
|
{
|
2025-02-28 19:07:02 +01:00
|
|
|
for (const bNodeTreeInterfaceItem *item : interface_panel.items().drop_front(skip_first ? 1 : 0))
|
|
|
|
|
{
|
2023-12-23 16:33:14 +01:00
|
|
|
if (item->item_type == NODE_INTERFACE_PANEL) {
|
|
|
|
|
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
|
2024-12-23 12:21:00 +01:00
|
|
|
if (!interface_panel_has_socket(sub_interface_panel)) {
|
2024-10-21 15:47:16 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2024-12-23 12:49:56 +01:00
|
|
|
NodesModifierPanel *panel = find_panel_by_id(ctx.nmd, sub_interface_panel.identifier);
|
2025-01-24 16:45:32 +01:00
|
|
|
PointerRNA panel_ptr = RNA_pointer_create_discrete(
|
2024-12-23 12:49:56 +01:00
|
|
|
ctx.md_ptr->owner_id, &RNA_NodesModifierPanel, panel);
|
2025-02-28 19:07:02 +01:00
|
|
|
PanelLayout panel_layout;
|
|
|
|
|
bool skip_first = false;
|
|
|
|
|
/* Check if the panel should have a toggle in the header. */
|
|
|
|
|
const bNodeTreeInterfaceSocket *toggle_socket = sub_interface_panel.header_toggle_socket();
|
|
|
|
|
if (toggle_socket && !(toggle_socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
|
|
|
|
|
const StringRefNull identifier = toggle_socket->identifier;
|
2025-03-25 15:40:32 +01:00
|
|
|
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
|
2025-02-28 19:07:02 +01:00
|
|
|
/* IDProperties can be removed with python, so there could be a situation where
|
|
|
|
|
* there isn't a property for a socket or it doesn't have the correct type. */
|
|
|
|
|
if (property == nullptr ||
|
|
|
|
|
!nodes::id_property_type_matches_socket(*toggle_socket, *property))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
char socket_id_esc[MAX_NAME * 2];
|
|
|
|
|
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
|
|
|
|
|
|
|
|
|
char rna_path[sizeof(socket_id_esc) + 4];
|
|
|
|
|
SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc);
|
|
|
|
|
|
2025-05-07 02:55:25 +02:00
|
|
|
panel_layout = layout->panel_prop_with_bool_header(
|
|
|
|
|
&ctx.C, &panel_ptr, "is_open", ctx.md_ptr, rna_path, IFACE_(sub_interface_panel.name));
|
2025-02-28 19:07:02 +01:00
|
|
|
skip_first = true;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-05-06 17:13:30 +02:00
|
|
|
panel_layout = layout->panel_prop(&ctx.C, &panel_ptr, "is_open");
|
2025-05-08 17:21:08 +02:00
|
|
|
panel_layout.header->label(IFACE_(sub_interface_panel.name), ICON_NONE);
|
2025-02-28 19:07:02 +01:00
|
|
|
}
|
2025-01-21 12:53:24 +01:00
|
|
|
if (!interface_panel_affects_output(ctx, sub_interface_panel)) {
|
|
|
|
|
uiLayoutSetActive(panel_layout.header, false);
|
|
|
|
|
}
|
2024-09-20 16:24:09 +02:00
|
|
|
uiLayoutSetTooltipFunc(
|
|
|
|
|
panel_layout.header,
|
2025-02-14 15:12:48 -05:00
|
|
|
[](bContext * /*C*/, void *panel_arg, const StringRef /*tip*/) -> std::string {
|
2024-09-20 16:24:09 +02:00
|
|
|
const auto *panel = static_cast<bNodeTreeInterfacePanel *>(panel_arg);
|
|
|
|
|
return StringRef(panel->description);
|
|
|
|
|
},
|
|
|
|
|
const_cast<bNodeTreeInterfacePanel *>(&sub_interface_panel),
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr);
|
|
|
|
|
if (panel_layout.body) {
|
2025-02-28 19:07:02 +01:00
|
|
|
draw_interface_panel_content(ctx, panel_layout.body, sub_interface_panel, skip_first);
|
2023-02-11 16:11:10 +01:00
|
|
|
}
|
2021-09-27 15:33:48 +02:00
|
|
|
}
|
2023-12-23 16:33:14 +01:00
|
|
|
else {
|
|
|
|
|
const auto &interface_socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
|
|
|
|
|
if (interface_socket.flag & NODE_INTERFACE_SOCKET_INPUT) {
|
|
|
|
|
if (!(interface_socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
|
2024-12-23 12:49:56 +01:00
|
|
|
draw_property_for_socket(ctx, layout, interface_socket);
|
2023-12-23 16:33:14 +01:00
|
|
|
}
|
2021-07-22 17:53:35 -04:00
|
|
|
}
|
2022-09-13 08:44:26 +02:00
|
|
|
}
|
2021-07-22 17:53:35 -04:00
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-17 13:40:30 +01:00
|
|
|
static bool has_output_attribute(const NodesModifierData &nmd)
|
|
|
|
|
{
|
|
|
|
|
if (!nmd.node_group) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (const bNodeTreeInterfaceSocket *interface_socket : nmd.node_group->interface_outputs()) {
|
2024-05-13 16:07:12 +02:00
|
|
|
const bke::bNodeSocketType *typeinfo = interface_socket->socket_typeinfo();
|
2024-01-17 13:40:30 +01:00
|
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
|
|
|
|
|
if (nodes::socket_type_has_attribute_toggle(type)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-23 12:49:56 +01:00
|
|
|
static void draw_output_attributes_panel(DrawGroupInputsContext &ctx, uiLayout *layout)
|
2021-09-27 13:04:58 -05:00
|
|
|
{
|
2024-12-23 12:49:56 +01:00
|
|
|
if (ctx.nmd.node_group != nullptr && ctx.nmd.settings.properties != nullptr) {
|
|
|
|
|
for (const bNodeTreeInterfaceSocket *socket : ctx.nmd.node_group->interface_outputs()) {
|
2024-05-13 16:07:12 +02:00
|
|
|
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
|
2023-08-30 12:37:21 +02:00
|
|
|
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) :
|
|
|
|
|
SOCK_CUSTOM;
|
|
|
|
|
if (nodes::socket_type_has_attribute_toggle(type)) {
|
2024-12-23 12:49:56 +01:00
|
|
|
draw_property_for_output_socket(ctx, layout, *socket);
|
2021-09-27 13:04:58 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 13:40:30 +01:00
|
|
|
static void draw_bake_panel(uiLayout *layout, PointerRNA *modifier_ptr)
|
2022-04-21 15:10:07 +02:00
|
|
|
{
|
2025-04-26 21:07:34 +02:00
|
|
|
uiLayout *col = &layout->column(false);
|
2023-06-12 12:06:14 -04:00
|
|
|
uiLayoutSetPropSep(col, true);
|
|
|
|
|
uiLayoutSetPropDecorate(col, false);
|
2025-05-08 20:45:37 +02:00
|
|
|
col->prop(modifier_ptr, "bake_target", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
|
|
|
col->prop(modifier_ptr, "bake_directory", UI_ITEM_NONE, IFACE_("Bake Path"), ICON_NONE);
|
2024-01-17 13:40:30 +01:00
|
|
|
}
|
2023-05-25 18:56:00 +02:00
|
|
|
|
2024-01-17 13:40:30 +01:00
|
|
|
static void draw_named_attributes_panel(uiLayout *layout, NodesModifierData &nmd)
|
|
|
|
|
{
|
2024-12-04 20:00:27 +01:00
|
|
|
if (G.is_rendering) {
|
|
|
|
|
/* Avoid accessing this data while baking in a separate thread. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-12-23 16:33:14 +01:00
|
|
|
geo_log::GeoTreeLog *tree_log = get_root_tree_log(nmd);
|
2022-09-13 08:44:26 +02:00
|
|
|
if (tree_log == nullptr) {
|
2022-04-21 15:10:07 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2022-09-13 08:44:26 +02:00
|
|
|
|
|
|
|
|
tree_log->ensure_used_named_attributes();
|
2023-03-31 13:19:37 -04:00
|
|
|
const Map<StringRefNull, geo_log::NamedAttributeUsage> &usage_by_attribute =
|
2022-09-13 08:44:26 +02:00
|
|
|
tree_log->used_named_attributes;
|
2022-04-21 15:10:07 +02:00
|
|
|
|
|
|
|
|
if (usage_by_attribute.is_empty()) {
|
2025-05-08 17:21:08 +02:00
|
|
|
layout->label(RPT_("No named attributes used"), ICON_INFO);
|
2022-04-21 15:10:07 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 16:00:43 +02:00
|
|
|
struct NameWithUsage {
|
|
|
|
|
StringRefNull name;
|
2023-03-31 13:19:37 -04:00
|
|
|
geo_log::NamedAttributeUsage usage;
|
2022-04-25 16:00:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Vector<NameWithUsage> sorted_used_attribute;
|
2022-04-21 15:10:07 +02:00
|
|
|
for (auto &&item : usage_by_attribute.items()) {
|
|
|
|
|
sorted_used_attribute.append({item.key, item.value});
|
|
|
|
|
}
|
2022-04-25 16:00:43 +02:00
|
|
|
std::sort(sorted_used_attribute.begin(),
|
|
|
|
|
sorted_used_attribute.end(),
|
|
|
|
|
[](const NameWithUsage &a, const NameWithUsage &b) {
|
2024-03-12 17:31:02 +01:00
|
|
|
return BLI_strcasecmp_natural(a.name.c_str(), b.name.c_str()) < 0;
|
2022-04-25 16:00:43 +02:00
|
|
|
});
|
2022-04-21 15:10:07 +02:00
|
|
|
|
2022-04-25 16:00:43 +02:00
|
|
|
for (const NameWithUsage &attribute : sorted_used_attribute) {
|
2024-12-06 14:08:10 +01:00
|
|
|
const StringRef attribute_name = attribute.name;
|
2023-03-31 13:19:37 -04:00
|
|
|
const geo_log::NamedAttributeUsage usage = attribute.usage;
|
2022-04-21 15:10:07 +02:00
|
|
|
|
|
|
|
|
/* #uiLayoutRowWithHeading doesn't seem to work in this case. */
|
2025-05-03 20:51:42 +02:00
|
|
|
uiLayout *split = &layout->split(0.4f, false);
|
2022-04-21 15:10:07 +02:00
|
|
|
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
Vector<std::string> usages;
|
2023-03-31 13:19:37 -04:00
|
|
|
if ((usage & geo_log::NamedAttributeUsage::Read) != geo_log::NamedAttributeUsage::None) {
|
2024-01-11 19:49:03 +01:00
|
|
|
usages.append(IFACE_("Read"));
|
2022-04-21 15:10:07 +02:00
|
|
|
}
|
2023-03-31 13:19:37 -04:00
|
|
|
if ((usage & geo_log::NamedAttributeUsage::Write) != geo_log::NamedAttributeUsage::None) {
|
2024-01-11 19:49:03 +01:00
|
|
|
usages.append(IFACE_("Write"));
|
2022-04-21 15:10:07 +02:00
|
|
|
}
|
2023-03-31 13:19:37 -04:00
|
|
|
if ((usage & geo_log::NamedAttributeUsage::Remove) != geo_log::NamedAttributeUsage::None) {
|
2024-06-13 12:15:55 +02:00
|
|
|
usages.append(CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove"));
|
2022-04-21 15:10:07 +02:00
|
|
|
}
|
|
|
|
|
for (const int i : usages.index_range()) {
|
|
|
|
|
ss << usages[i];
|
|
|
|
|
if (i < usages.size() - 1) {
|
|
|
|
|
ss << ", ";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-25 19:45:25 +02:00
|
|
|
uiLayout *row = &split->row(false);
|
2022-04-21 15:10:07 +02:00
|
|
|
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT);
|
|
|
|
|
uiLayoutSetActive(row, false);
|
2025-05-08 17:21:08 +02:00
|
|
|
row->label(ss.str(), ICON_NONE);
|
2022-04-21 15:10:07 +02:00
|
|
|
|
2025-04-25 19:45:25 +02:00
|
|
|
row = &split->row(false);
|
2025-05-08 17:21:08 +02:00
|
|
|
row->label(attribute_name, ICON_NONE);
|
2022-04-21 15:10:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 13:40:30 +01:00
|
|
|
static void draw_manage_panel(const bContext *C,
|
|
|
|
|
uiLayout *layout,
|
|
|
|
|
PointerRNA *modifier_ptr,
|
|
|
|
|
NodesModifierData &nmd)
|
|
|
|
|
{
|
2025-05-06 17:13:30 +02:00
|
|
|
if (uiLayout *panel_layout = layout->panel_prop(
|
|
|
|
|
C, modifier_ptr, "open_bake_panel", IFACE_("Bake")))
|
2024-01-17 13:40:30 +01:00
|
|
|
{
|
|
|
|
|
draw_bake_panel(panel_layout, modifier_ptr);
|
|
|
|
|
}
|
2025-05-06 17:13:30 +02:00
|
|
|
if (uiLayout *panel_layout = layout->panel_prop(
|
|
|
|
|
C, modifier_ptr, "open_named_attributes_panel", IFACE_("Named Attributes")))
|
2024-01-17 13:40:30 +01:00
|
|
|
{
|
|
|
|
|
draw_named_attributes_panel(panel_layout, nmd);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-01 15:31:50 +02:00
|
|
|
static void draw_warnings(const bContext *C,
|
|
|
|
|
const NodesModifierData &nmd,
|
|
|
|
|
uiLayout *layout,
|
|
|
|
|
PointerRNA *md_ptr)
|
|
|
|
|
{
|
2024-12-04 20:00:27 +01:00
|
|
|
if (G.is_rendering) {
|
|
|
|
|
/* Avoid accessing this data while baking in a separate thread. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-10-01 15:31:50 +02:00
|
|
|
using namespace geo_log;
|
|
|
|
|
GeoTreeLog *tree_log = get_root_tree_log(nmd);
|
|
|
|
|
if (!tree_log) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-04-10 08:56:02 +02:00
|
|
|
tree_log->ensure_node_warnings(*CTX_data_main(C));
|
2024-10-01 15:31:50 +02:00
|
|
|
const int warnings_num = tree_log->all_warnings.size();
|
|
|
|
|
if (warnings_num == 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-05-06 17:13:30 +02:00
|
|
|
PanelLayout panel = layout->panel_prop(C, md_ptr, "open_warnings_panel");
|
2025-05-08 17:21:08 +02:00
|
|
|
panel.header->label(fmt::format(fmt::runtime(IFACE_("Warnings ({})")), warnings_num).c_str(),
|
|
|
|
|
ICON_NONE);
|
2024-10-01 15:31:50 +02:00
|
|
|
if (!panel.body) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Vector<const NodeWarning *> warnings(tree_log->all_warnings.size());
|
|
|
|
|
for (const int i : warnings.index_range()) {
|
|
|
|
|
warnings[i] = &tree_log->all_warnings[i];
|
|
|
|
|
}
|
|
|
|
|
std::sort(warnings.begin(), warnings.end(), [](const NodeWarning *a, const NodeWarning *b) {
|
|
|
|
|
const int severity_a = node_warning_type_severity(a->type);
|
|
|
|
|
const int severity_b = node_warning_type_severity(b->type);
|
|
|
|
|
if (severity_a > severity_b) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (severity_a < severity_b) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return BLI_strcasecmp_natural(a->message.c_str(), b->message.c_str()) < 0;
|
|
|
|
|
});
|
|
|
|
|
|
2025-04-26 21:07:34 +02:00
|
|
|
uiLayout *col = &panel.body->column(false);
|
2024-10-01 15:31:50 +02:00
|
|
|
for (const NodeWarning *warning : warnings) {
|
|
|
|
|
const int icon = node_warning_type_icon(warning->type);
|
2025-05-08 17:21:08 +02:00
|
|
|
col->label(warning->message, icon);
|
2024-10-01 15:31:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-23 16:33:14 +01:00
|
|
|
static void panel_draw(const bContext *C, Panel *panel)
|
|
|
|
|
{
|
|
|
|
|
uiLayout *layout = panel->layout;
|
|
|
|
|
|
|
|
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
|
|
|
|
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
|
|
|
|
|
|
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
|
/* Decorators are added manually for supported properties because the
|
|
|
|
|
* attribute/value toggle requires a manually built layout anyway. */
|
|
|
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
|
|
|
|
|
|
if (!(nmd->flag & NODES_MODIFIER_HIDE_DATABLOCK_SELECTOR)) {
|
2024-02-20 18:43:40 +01:00
|
|
|
const char *newop = (nmd->node_group == nullptr) ? "node.new_geometry_node_group_assign" :
|
|
|
|
|
"object.geometry_node_tree_copy_assign";
|
2024-09-17 12:22:18 +02:00
|
|
|
uiTemplateID(layout, C, ptr, "node_group", newop, nullptr, nullptr);
|
2023-12-23 16:33:14 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-23 12:49:56 +01:00
|
|
|
Main *bmain = CTX_data_main(C);
|
|
|
|
|
PointerRNA bmain_ptr = RNA_main_pointer_create(bmain);
|
2025-03-25 15:40:32 +01:00
|
|
|
DrawGroupInputsContext ctx{
|
|
|
|
|
*C, *nmd, nodes::build_properties_vector_set(nmd->settings.properties), ptr, &bmain_ptr};
|
2024-12-23 12:49:56 +01:00
|
|
|
|
2023-12-23 16:33:14 +01:00
|
|
|
if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) {
|
|
|
|
|
nmd->node_group->ensure_interface_cache();
|
2025-01-21 12:53:24 +01:00
|
|
|
ctx.input_usages.reinitialize(nmd->node_group->interface_inputs().size());
|
|
|
|
|
nodes::socket_usage_inference::infer_group_interface_inputs_usage(
|
2025-03-25 15:40:32 +01:00
|
|
|
*nmd->node_group, ctx.properties, ctx.input_usages);
|
2024-12-23 12:49:56 +01:00
|
|
|
draw_interface_panel_content(ctx, layout, nmd->node_group->tree_interface.root_panel);
|
2023-12-23 16:33:14 +01:00
|
|
|
}
|
|
|
|
|
|
2025-05-13 17:27:30 +02:00
|
|
|
modifier_error_message_draw(layout, ptr);
|
2023-12-23 16:33:14 +01:00
|
|
|
|
2024-10-01 15:31:50 +02:00
|
|
|
draw_warnings(C, *nmd, layout, ptr);
|
|
|
|
|
|
2024-01-17 13:40:30 +01:00
|
|
|
if (has_output_attribute(*nmd)) {
|
2025-05-06 17:13:30 +02:00
|
|
|
if (uiLayout *panel_layout = layout->panel_prop(
|
|
|
|
|
C, ptr, "open_output_attributes_panel", IFACE_("Output Attributes")))
|
2024-01-17 13:40:30 +01:00
|
|
|
{
|
2024-12-23 12:49:56 +01:00
|
|
|
draw_output_attributes_panel(ctx, panel_layout);
|
2024-01-17 13:40:30 +01:00
|
|
|
}
|
2023-12-23 16:33:14 +01:00
|
|
|
}
|
2025-05-06 17:13:30 +02:00
|
|
|
if (uiLayout *panel_layout = layout->panel_prop(C, ptr, "open_manage_panel", IFACE_("Manage"))) {
|
2024-01-17 13:40:30 +01:00
|
|
|
draw_manage_panel(C, panel_layout, ptr, *nmd);
|
2023-12-23 16:33:14 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void panel_register(ARegionType *region_type)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
2023-03-31 13:19:37 -04:00
|
|
|
using namespace blender;
|
2023-12-23 16:33:14 +01:00
|
|
|
modifier_panel_register(region_type, eModifierType_Nodes, panel_draw);
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
2022-05-16 16:00:00 +02:00
|
|
|
|
|
|
|
|
BLO_write_struct(writer, NodesModifierData, nmd);
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
BLO_write_string(writer, nmd->bake_directory);
|
2023-05-24 08:45:31 +02:00
|
|
|
|
2020-12-02 13:25:25 +01:00
|
|
|
if (nmd->settings.properties != nullptr) {
|
2023-01-20 17:36:07 -06:00
|
|
|
Map<IDProperty *, IDPropertyUIDataBool *> boolean_props;
|
2023-01-23 15:06:55 -06:00
|
|
|
if (!BLO_write_is_undo(writer)) {
|
|
|
|
|
/* Boolean properties are added automatically for boolean node group inputs. Integer
|
|
|
|
|
* properties are automatically converted to boolean sockets where applicable as well.
|
|
|
|
|
* However, boolean properties will crash old versions of Blender, so convert them to integer
|
|
|
|
|
* properties for writing. The actual value is stored in the same variable for both types */
|
|
|
|
|
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
|
|
|
|
|
if (prop->type == IDP_BOOLEAN) {
|
|
|
|
|
boolean_props.add_new(prop, reinterpret_cast<IDPropertyUIDataBool *>(prop->ui_data));
|
|
|
|
|
prop->type = IDP_INT;
|
|
|
|
|
prop->ui_data = nullptr;
|
|
|
|
|
}
|
2023-01-20 17:36:07 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 13:25:25 +01:00
|
|
|
/* Note that the property settings are based on the socket type info
|
|
|
|
|
* and don't necessarily need to be written, but we can't just free them. */
|
|
|
|
|
IDP_BlendWrite(writer, nmd->settings.properties);
|
2023-01-20 17:36:07 -06:00
|
|
|
|
2023-09-26 20:30:46 +02:00
|
|
|
BLO_write_struct_array(writer, NodesModifierBake, nmd->bakes_num, nmd->bakes);
|
|
|
|
|
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
|
|
|
|
|
BLO_write_string(writer, bake.directory);
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
|
|
|
|
|
BLO_write_struct_array(
|
|
|
|
|
writer, NodesModifierDataBlock, bake.data_blocks_num, bake.data_blocks);
|
|
|
|
|
for (const NodesModifierDataBlock &item : Span(bake.data_blocks, bake.data_blocks_num)) {
|
|
|
|
|
BLO_write_string(writer, item.id_name);
|
|
|
|
|
BLO_write_string(writer, item.lib_name);
|
|
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
if (bake.packed) {
|
|
|
|
|
BLO_write_struct(writer, NodesModifierPackedBake, bake.packed);
|
|
|
|
|
BLO_write_struct_array(
|
|
|
|
|
writer, NodesModifierBakeFile, bake.packed->meta_files_num, bake.packed->meta_files);
|
|
|
|
|
BLO_write_struct_array(
|
|
|
|
|
writer, NodesModifierBakeFile, bake.packed->blob_files_num, bake.packed->blob_files);
|
|
|
|
|
const auto write_bake_file = [&](const NodesModifierBakeFile &bake_file) {
|
|
|
|
|
BLO_write_string(writer, bake_file.name);
|
|
|
|
|
if (bake_file.packed_file) {
|
|
|
|
|
BKE_packedfile_blend_write(writer, bake_file.packed_file);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
for (const NodesModifierBakeFile &meta_file :
|
|
|
|
|
Span{bake.packed->meta_files, bake.packed->meta_files_num})
|
|
|
|
|
{
|
|
|
|
|
write_bake_file(meta_file);
|
|
|
|
|
}
|
|
|
|
|
for (const NodesModifierBakeFile &blob_file :
|
|
|
|
|
Span{bake.packed->blob_files, bake.packed->blob_files_num})
|
|
|
|
|
{
|
|
|
|
|
write_bake_file(blob_file);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
2023-12-23 16:33:14 +01:00
|
|
|
BLO_write_struct_array(writer, NodesModifierPanel, nmd->panels_num, nmd->panels);
|
2023-09-26 20:30:46 +02:00
|
|
|
|
2023-01-23 15:06:55 -06:00
|
|
|
if (!BLO_write_is_undo(writer)) {
|
|
|
|
|
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
|
|
|
|
|
if (prop->type == IDP_INT) {
|
|
|
|
|
if (IDPropertyUIDataBool **ui_data = boolean_props.lookup_ptr(prop)) {
|
|
|
|
|
prop->type = IDP_BOOLEAN;
|
|
|
|
|
if (ui_data) {
|
|
|
|
|
prop->ui_data = reinterpret_cast<IDPropertyUIData *>(*ui_data);
|
|
|
|
|
}
|
2023-01-20 17:36:07 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void blend_read(BlendDataReader *reader, ModifierData *md)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
2024-04-24 17:01:22 +02:00
|
|
|
BLO_read_string(reader, &nmd->bake_directory);
|
2022-04-28 08:39:30 -05:00
|
|
|
if (nmd->node_group == nullptr) {
|
|
|
|
|
nmd->settings.properties = nullptr;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2024-04-24 17:01:22 +02:00
|
|
|
BLO_read_struct(reader, IDProperty, &nmd->settings.properties);
|
2022-04-28 08:39:30 -05:00
|
|
|
IDP_BlendDataRead(reader, &nmd->settings.properties);
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
|
2024-04-24 17:01:22 +02:00
|
|
|
BLO_read_struct_array(reader, NodesModifierBake, nmd->bakes_num, &nmd->bakes);
|
2024-07-11 17:25:29 +02:00
|
|
|
|
|
|
|
|
if (nmd->bakes_num > 0 && nmd->bakes == nullptr) {
|
|
|
|
|
/* This case generally shouldn't be allowed to happen. However, there is a bug report with a
|
|
|
|
|
* corrupted .blend file (#123974) that triggers this case. Unfortunately, it's not clear how
|
|
|
|
|
* that could have happened. For now, handle this case more gracefully in release builds, while
|
|
|
|
|
* still crashing in debug builds. */
|
|
|
|
|
nmd->bakes_num = 0;
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:30:46 +02:00
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
2024-04-24 17:01:22 +02:00
|
|
|
BLO_read_string(reader, &bake.directory);
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
|
2024-04-24 17:01:22 +02:00
|
|
|
BLO_read_struct_array(reader, NodesModifierDataBlock, bake.data_blocks_num, &bake.data_blocks);
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num))
|
|
|
|
|
{
|
2024-04-24 17:01:22 +02:00
|
|
|
BLO_read_string(reader, &data_block.id_name);
|
|
|
|
|
BLO_read_string(reader, &data_block.lib_name);
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
|
|
|
|
|
BLO_read_struct(reader, NodesModifierPackedBake, &bake.packed);
|
|
|
|
|
if (bake.packed) {
|
|
|
|
|
BLO_read_struct_array(
|
|
|
|
|
reader, NodesModifierBakeFile, bake.packed->meta_files_num, &bake.packed->meta_files);
|
|
|
|
|
BLO_read_struct_array(
|
|
|
|
|
reader, NodesModifierBakeFile, bake.packed->blob_files_num, &bake.packed->blob_files);
|
|
|
|
|
const auto read_bake_file = [&](NodesModifierBakeFile &bake_file) {
|
|
|
|
|
BLO_read_string(reader, &bake_file.name);
|
|
|
|
|
if (bake_file.packed_file) {
|
|
|
|
|
BKE_packedfile_blend_read(reader, &bake_file.packed_file, "");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
for (NodesModifierBakeFile &meta_file :
|
|
|
|
|
MutableSpan{bake.packed->meta_files, bake.packed->meta_files_num})
|
|
|
|
|
{
|
|
|
|
|
read_bake_file(meta_file);
|
|
|
|
|
}
|
|
|
|
|
for (NodesModifierBakeFile &blob_file :
|
|
|
|
|
MutableSpan{bake.packed->blob_files, bake.packed->blob_files_num})
|
|
|
|
|
{
|
|
|
|
|
read_bake_file(blob_file);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
2024-04-24 17:01:22 +02:00
|
|
|
BLO_read_struct_array(reader, NodesModifierPanel, nmd->panels_num, &nmd->panels);
|
2023-09-26 20:30:46 +02:00
|
|
|
|
2023-07-11 12:55:57 +02:00
|
|
|
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
2023-09-03 16:14:11 +02:00
|
|
|
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
|
|
|
|
NodesModifierData *tnmd = reinterpret_cast<NodesModifierData *>(target);
|
|
|
|
|
|
|
|
|
|
BKE_modifier_copydata_generic(md, target, flag);
|
|
|
|
|
|
2023-09-26 20:30:46 +02:00
|
|
|
if (nmd->bakes) {
|
|
|
|
|
tnmd->bakes = static_cast<NodesModifierBake *>(MEM_dupallocN(nmd->bakes));
|
|
|
|
|
for (const int i : IndexRange(nmd->bakes_num)) {
|
|
|
|
|
NodesModifierBake &bake = tnmd->bakes[i];
|
|
|
|
|
if (bake.directory) {
|
|
|
|
|
bake.directory = BLI_strdup(bake.directory);
|
|
|
|
|
}
|
Geometry Nodes: support baking data block references
With this patch, materials are kept intact in simulation zones and bake nodes
without any additional user action.
This implements the design proposed in #108410 to support referencing
data-blocks (only materials for now) in the baked data. The task also describes
why this is not a trivial issue. A previous attempt was implemented in #109703
but it didn't work well-enough.
The solution is to have an explicit `name (+ library name) -> data-block`
mapping that is stored in the modifier for each bake node and simulation zone.
The `library name` is necessary for it to be unique within a .blend file. Note
that this refers to the name of the `Library` data-block and not a file path.
The baked data only contains the names of the used data-blocks. When the baked
data is loaded, the correct material data-block is looked up from the mapping.
### Automatic Mapping Generation
The most tricky aspect of this approach is to make it feel mostly automatic.
From the user point-of-view, it should just work. Therefore, we don't want the
user to have to create the mapping manually in the majority of cases. Creating
the mapping automatically is difficult because the data-blocks that should
become part of the mapping are only known during depsgraph evaluation. So we
somehow have to gather the missing data blocks during evaluation and then write
the new mappings back to the original data.
While writing back to original data is something we do in some cases already,
the situation here is different, because we are actually creating new relations
between data-blocks. This also means that we'll have to do user-counting. Since
user counts in data-blocks are *not* atomic, we can't do that from multiple
threads at the same time. Also, under some circumstances, it may be necessary to
trigger depsgraph evaluation again after the write-back because it actually
affects the result.
To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It
allows gathering tasks which write back to original data in a synchronous way
which may also require a reevaluation.
### Accessing the Mapping
A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier.
This map allows getting the `ID` pointer that should be used for a specific
data-block name that is stored in baked data. It's also used to gather all the
missing data mappings during evaluation.
### Weak ID References
The baked/cached geometries may have references to other data-blocks (currently
only materials, but in the future also e.g. instanced objects/collections).
However, the pointers of these data-blocks are not stable over time. That is
especially true when storing/loading the data from disk, but also just when
playing back the animation. Therefore, the used data-blocks have to referenced
in a different way at run-time.
This is solved by adding `std::unique_ptr<bake::BakeMaterialsList>` to the
run-time data of various geometry data-blocks. If the data-block is cached over
a longer period of time (such that material pointers can't be used directly), it
stores the material name (+ library name) used by each material slot. When the
geometry is used again, the material pointers are restored using these weak name
references and the `BakeDataBlockMap`.
### Manual Mapping Management
There is a new `Data-Blocks` panel in the bake settings in the node editor
sidebar that allows inspecting and modifying the data-blocks that are used when
baking. The user can change what data-block a specific name is mapped to.
Pull Request: https://projects.blender.org/blender/blender/pulls/117043
2024-02-01 09:21:55 +01:00
|
|
|
if (bake.data_blocks) {
|
|
|
|
|
bake.data_blocks = static_cast<NodesModifierDataBlock *>(MEM_dupallocN(bake.data_blocks));
|
|
|
|
|
for (const int i : IndexRange(bake.data_blocks_num)) {
|
|
|
|
|
NodesModifierDataBlock &data_block = bake.data_blocks[i];
|
|
|
|
|
if (data_block.id_name) {
|
|
|
|
|
data_block.id_name = BLI_strdup(data_block.id_name);
|
|
|
|
|
}
|
|
|
|
|
if (data_block.lib_name) {
|
|
|
|
|
data_block.lib_name = BLI_strdup(data_block.lib_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-20 16:18:12 +02:00
|
|
|
if (bake.packed) {
|
|
|
|
|
bake.packed = static_cast<NodesModifierPackedBake *>(MEM_dupallocN(bake.packed));
|
|
|
|
|
const auto copy_bake_files_inplace = [](NodesModifierBakeFile **bake_files,
|
|
|
|
|
const int bake_files_num) {
|
|
|
|
|
if (!*bake_files) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
*bake_files = static_cast<NodesModifierBakeFile *>(MEM_dupallocN(*bake_files));
|
|
|
|
|
for (NodesModifierBakeFile &bake_file : MutableSpan{*bake_files, bake_files_num}) {
|
|
|
|
|
bake_file.name = BLI_strdup_null(bake_file.name);
|
|
|
|
|
if (bake_file.packed_file) {
|
|
|
|
|
bake_file.packed_file = BKE_packedfile_duplicate(bake_file.packed_file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
copy_bake_files_inplace(&bake.packed->meta_files, bake.packed->meta_files_num);
|
|
|
|
|
copy_bake_files_inplace(&bake.packed->blob_files, bake.packed->blob_files_num);
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-23 16:33:14 +01:00
|
|
|
if (nmd->panels) {
|
|
|
|
|
tnmd->panels = static_cast<NodesModifierPanel *>(MEM_dupallocN(nmd->panels));
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-11 12:55:57 +02:00
|
|
|
tnmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
|
|
|
|
|
|
2023-06-14 14:04:02 +02:00
|
|
|
if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
|
|
|
|
|
/* Share the simulation cache between the original and evaluated modifier. */
|
2023-09-03 16:14:11 +02:00
|
|
|
tnmd->runtime->cache = nmd->runtime->cache;
|
2023-06-15 17:40:38 +02:00
|
|
|
/* Keep bake path in the evaluated modifier. */
|
2023-12-18 13:01:06 +01:00
|
|
|
tnmd->bake_directory = nmd->bake_directory ? BLI_strdup(nmd->bake_directory) : nullptr;
|
2023-06-14 14:04:02 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2023-09-03 16:14:11 +02:00
|
|
|
tnmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
2023-06-15 17:40:38 +02:00
|
|
|
/* Clear the bake path when duplicating. */
|
2023-12-18 13:01:06 +01:00
|
|
|
tnmd->bake_directory = nullptr;
|
2023-06-14 14:04:02 +02:00
|
|
|
}
|
2021-07-07 11:20:19 +02:00
|
|
|
|
2020-12-02 13:25:25 +01:00
|
|
|
if (nmd->settings.properties != nullptr) {
|
|
|
|
|
tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 16:18:12 +02:00
|
|
|
void nodes_modifier_packed_bake_free(NodesModifierPackedBake *packed_bake)
|
|
|
|
|
{
|
|
|
|
|
const auto free_packed_files = [](NodesModifierBakeFile *files, const int files_num) {
|
|
|
|
|
for (NodesModifierBakeFile &file : MutableSpan{files, files_num}) {
|
|
|
|
|
MEM_SAFE_FREE(file.name);
|
|
|
|
|
if (file.packed_file) {
|
|
|
|
|
BKE_packedfile_free(file.packed_file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(files);
|
|
|
|
|
};
|
|
|
|
|
free_packed_files(packed_bake->meta_files, packed_bake->meta_files_num);
|
|
|
|
|
free_packed_files(packed_bake->blob_files, packed_bake->blob_files_num);
|
|
|
|
|
MEM_SAFE_FREE(packed_bake);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-09 18:19:53 +01:00
|
|
|
void nodes_modifier_bake_destruct(NodesModifierBake *bake, const bool do_id_user)
|
|
|
|
|
{
|
|
|
|
|
MEM_SAFE_FREE(bake->directory);
|
|
|
|
|
|
|
|
|
|
for (NodesModifierDataBlock &data_block : MutableSpan(bake->data_blocks, bake->data_blocks_num))
|
|
|
|
|
{
|
|
|
|
|
nodes_modifier_data_block_destruct(&data_block, do_id_user);
|
|
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(bake->data_blocks);
|
|
|
|
|
|
|
|
|
|
if (bake->packed) {
|
|
|
|
|
nodes_modifier_packed_bake_free(bake->packed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void free_data(ModifierData *md)
|
2020-12-02 13:25:25 +01:00
|
|
|
{
|
|
|
|
|
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
|
|
|
|
if (nmd->settings.properties != nullptr) {
|
|
|
|
|
IDP_FreeProperty_ex(nmd->settings.properties, false);
|
|
|
|
|
nmd->settings.properties = nullptr;
|
|
|
|
|
}
|
2021-07-07 11:20:19 +02:00
|
|
|
|
2023-09-26 20:30:46 +02:00
|
|
|
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
|
2025-01-09 18:19:53 +01:00
|
|
|
nodes_modifier_bake_destruct(&bake, false);
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(nmd->bakes);
|
|
|
|
|
|
2023-12-23 16:33:14 +01:00
|
|
|
MEM_SAFE_FREE(nmd->panels);
|
|
|
|
|
|
2023-12-18 13:01:06 +01:00
|
|
|
MEM_SAFE_FREE(nmd->bake_directory);
|
2023-07-11 12:55:57 +02:00
|
|
|
MEM_delete(nmd->runtime);
|
2020-12-02 13:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
static void required_data_mask(ModifierData * /*md*/, CustomData_MeshMasks *r_cddata_masks)
|
2020-12-14 15:28:24 +01:00
|
|
|
{
|
|
|
|
|
/* We don't know what the node tree will need. If there are vertex groups, it is likely that the
|
|
|
|
|
* node tree wants to access them. */
|
|
|
|
|
r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT;
|
2021-03-31 16:25:23 +02:00
|
|
|
r_cddata_masks->vmask |= CD_MASK_PROP_ALL;
|
2020-12-14 15:28:24 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-31 13:19:37 -04:00
|
|
|
} // namespace blender
|
|
|
|
|
|
2020-12-02 13:25:25 +01:00
|
|
|
ModifierTypeInfo modifierType_Nodes = {
|
2023-07-26 17:08:14 +02:00
|
|
|
/*idname*/ "GeometryNodes",
|
2023-01-16 12:41:11 +11:00
|
|
|
/*name*/ N_("GeometryNodes"),
|
2023-07-27 12:04:18 +10:00
|
|
|
/*struct_name*/ "NodesModifierData",
|
|
|
|
|
/*struct_size*/ sizeof(NodesModifierData),
|
2023-01-16 12:41:11 +11:00
|
|
|
/*srna*/ &RNA_NodesModifier,
|
2023-11-14 10:03:56 +01:00
|
|
|
/*type*/ ModifierTypeType::Constructive,
|
2023-01-16 12:41:11 +11:00
|
|
|
/*flags*/
|
2025-01-26 20:07:57 +01:00
|
|
|
(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs |
|
|
|
|
|
eModifierTypeFlag_SupportsEditmode | eModifierTypeFlag_EnableInEditmode |
|
|
|
|
|
eModifierTypeFlag_SupportsMapping | eModifierTypeFlag_AcceptsGreasePencil),
|
2023-01-16 12:41:11 +11:00
|
|
|
/*icon*/ ICON_GEOMETRY_NODES,
|
|
|
|
|
|
2023-07-27 12:04:18 +10:00
|
|
|
/*copy_data*/ blender::copy_data,
|
|
|
|
|
|
|
|
|
|
/*deform_verts*/ nullptr,
|
|
|
|
|
/*deform_matrices*/ nullptr,
|
|
|
|
|
/*deform_verts_EM*/ nullptr,
|
|
|
|
|
/*deform_matrices_EM*/ nullptr,
|
|
|
|
|
/*modify_mesh*/ blender::modify_mesh,
|
|
|
|
|
/*modify_geometry_set*/ blender::modify_geometry_set,
|
|
|
|
|
|
|
|
|
|
/*init_data*/ blender::init_data,
|
|
|
|
|
/*required_data_mask*/ blender::required_data_mask,
|
|
|
|
|
/*free_data*/ blender::free_data,
|
|
|
|
|
/*is_disabled*/ blender::is_disabled,
|
|
|
|
|
/*update_depsgraph*/ blender::update_depsgraph,
|
|
|
|
|
/*depends_on_time*/ blender::depends_on_time,
|
|
|
|
|
/*depends_on_normals*/ nullptr,
|
|
|
|
|
/*foreach_ID_link*/ blender::foreach_ID_link,
|
|
|
|
|
/*foreach_tex_link*/ blender::foreach_tex_link,
|
|
|
|
|
/*free_runtime_data*/ nullptr,
|
|
|
|
|
/*panel_register*/ blender::panel_register,
|
|
|
|
|
/*blend_write*/ blender::blend_write,
|
|
|
|
|
/*blend_read*/ blender::blend_read,
|
2024-01-18 22:51:30 +01:00
|
|
|
/*foreach_cache*/ nullptr,
|
2020-12-02 13:25:25 +01:00
|
|
|
};
|