Depsgraph: handle camera switching via markers in context drivers.

Blender allows animating the active camera selection (i.e. scene.camera)
by binding cameras to markers in the timeline. The dependency graph was
completely ignoring this by not building nodes for these cameras (it is
possible to reference a camera not directly included in the scene), and
not taking this into account in driver relations.

This change ensures that all cameras are included in the dependency
graph, and any drivers referencing scene.camera get dependencies on
all cameras of the timeline, and also time itself to ensure switches
are processed.

Pull Request #110139
This commit is contained in:
Alexander Gavrilov 2023-07-15 18:38:24 +03:00
parent 406f601c4b
commit b248295530
8 changed files with 166 additions and 51 deletions

View File

@ -148,6 +148,27 @@ bool DepsgraphBuilder::check_pchan_has_bbone_segments(const Object *object, cons
return check_pchan_has_bbone_segments(object, pchan);
}
const char *DepsgraphBuilder::get_rna_path_relative_to_scene_camera(const Scene *scene,
const PointerRNA &target_prop,
const char *rna_path)
{
if (rna_path == nullptr || target_prop.data != scene || target_prop.type != &RNA_Scene ||
!BLI_str_startswith(rna_path, "camera"))
{
return nullptr;
}
/* Return the part of the path relative to the camera. */
switch (rna_path[6]) {
case '.':
return rna_path + 7;
case '[':
return rna_path + 6;
default:
return nullptr;
}
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -13,6 +13,8 @@ struct ID;
struct Main;
struct ModifierData;
struct Object;
struct PointerRNA;
struct Scene;
struct bPoseChannel;
namespace blender::deg {
@ -33,6 +35,12 @@ class DepsgraphBuilder {
virtual bool check_pchan_has_bbone_segments(const Object *object, const bPoseChannel *pchan);
virtual bool check_pchan_has_bbone_segments(const Object *object, const char *bone_name);
/** If `target_prop` + `rna_path` uses indirection via the `scene.camera` pointer, returns
* the substring of `rna_path` relative to the camera; otherwise returns nullptr. */
static const char *get_rna_path_relative_to_scene_camera(const Scene *scene,
const PointerRNA &target_prop,
const char *rna_path);
protected:
/* NOTE: The builder does NOT take ownership over any of those resources. */
DepsgraphBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuilderCache *cache);

View File

@ -1300,11 +1300,34 @@ void DepsgraphNodeBuilder::build_driver_variables(ID *id, FCurve *fcurve)
build_id(target_id);
build_driver_id_property(target_prop, dtar->rna_path);
/* For rna_path based variables: */
if ((dtar->flag & DTAR_FLAG_STRUCT_REF) == 0) {
/* Handle all other cameras used by the scene timeline if applicable. */
if (const char *camera_path = get_rna_path_relative_to_scene_camera(
scene_, target_prop, dtar->rna_path))
{
build_driver_scene_camera_variable(scene_, camera_path);
}
}
}
DRIVER_TARGETS_LOOPER_END;
}
}
void DepsgraphNodeBuilder::build_driver_scene_camera_variable(Scene *scene,
const char *camera_path)
{
/* This skips scene->camera, which was already handled by the caller. */
LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
if (!ELEM(marker->camera, nullptr, scene->camera)) {
PointerRNA camera_ptr;
RNA_id_pointer_create(&marker->camera->id, &camera_ptr);
build_driver_id_property(camera_ptr, camera_path);
}
}
}
void DepsgraphNodeBuilder::build_driver_id_property(const PointerRNA &target_prop,
const char *rna_path_from_target_prop)
{

View File

@ -164,6 +164,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
virtual void build_idproperties(IDProperty *id_property);
virtual void build_scene_render(Scene *scene, ViewLayer *view_layer);
virtual void build_scene_camera(Scene *scene);
virtual void build_scene_parameters(Scene *scene);
virtual void build_scene_compositor(Scene *scene);
@ -223,6 +224,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
virtual void build_driver(ID *id, FCurve *fcurve, int driver_index);
virtual void build_driver_variables(ID *id, FCurve *fcurve);
virtual void build_driver_scene_camera_variable(Scene *scene, const char *camera_path);
/* Build operations of a property value from which is read by a driver target.
*

View File

@ -31,8 +31,18 @@ void DepsgraphNodeBuilder::build_scene_render(Scene *scene, ViewLayer *view_laye
build_scene_sequencer(scene);
build_scene_speakers(scene, view_layer);
}
build_scene_camera(scene);
}
void DepsgraphNodeBuilder::build_scene_camera(Scene *scene)
{
if (scene->camera != nullptr) {
build_object(-1, scene->camera, DEG_ID_LINKED_DIRECTLY, true);
build_object(-1, scene->camera, DEG_ID_LINKED_INDIRECTLY, true);
}
LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
if (!ELEM(marker->camera, nullptr, scene->camera)) {
build_object(-1, marker->camera, DEG_ID_LINKED_INDIRECTLY, true);
}
}
}

View File

@ -114,9 +114,7 @@ void DepsgraphNodeBuilder::build_view_layer(Scene *scene,
}
}
build_layer_collections(&view_layer->layer_collections);
if (scene->camera != nullptr) {
build_object(-1, scene->camera, DEG_ID_LINKED_INDIRECTLY, true);
}
build_scene_camera(scene);
/* Rigidbody. */
if (scene->rigidbody_world != nullptr) {
build_rigidbody(scene);

View File

@ -1930,54 +1930,14 @@ void DepsgraphRelationBuilder::build_driver_variables(ID *id, FCurve *fcu)
add_relation(target_key, driver_key, "Target -> Driver");
}
else if (dtar->rna_path != nullptr && dtar->rna_path[0] != '\0') {
RNAPathKey variable_exit_key(target_prop, dtar->rna_path, RNAPointerSource::EXIT);
if (RNA_pointer_is_null(&variable_exit_key.ptr)) {
continue;
}
if (is_same_bone_dependency(variable_exit_key, self_key) ||
is_same_nodetree_node_dependency(variable_exit_key, self_key))
{
continue;
}
add_relation(variable_exit_key, driver_key, "RNA Target -> Driver");
build_driver_rna_path_variable(
driver_key, self_key, target_id, target_prop, dtar->rna_path);
/* It is possible that RNA path points to a property of a different ID than the target_id:
* for example, paths like "data" on Object, "camera" on Scene.
*
* For the demonstration purposes lets consider a driver variable uses Scene ID as target
* and "camera.location.x" as its RNA path. If the scene has 2 different cameras at
* 2 different locations changing the active scene camera is expected to immediately be
* reflected in the variable value. In order to achieve this behavior we create a relation
* from the target ID to the driver so that if the ID property of the target ID changes the
* driver is re-evaluated.
*
* The most straightforward (at the moment of writing this comment) way of figuring out
* such relation is to use copy-on-write operation of the target ID. There are two down
* sides of this approach which are considered a design limitation as there is a belief
* that they are not common in practice or are not reliable due to other issues:
*
* - IDs which are not covered with the copy-on-write mechanism.
*
* Such IDs are either do not have ID properties, or are not part of the dependency
* graph.
*
* - Modifications of evaluated IDs from a Python handler.
* Such modifications are not fully integrated in the dependency graph evaluation as it
* has issues with copy-on-write tagging and the fact that relations are defined by the
* original main database status.
*
* The original report for this is #98618.
*
* The not-so-obvious part is that we don't do such relation for the context properties.
* They are resolved at the graph build time and do not change at runtime (#107081).
* Thus scene has to be excluded as a special case; this is OK because changes to
* scene.camera not caused by animation should actually force a dependency graph rebuild.
*/
if (target_id != variable_exit_key.ptr.owner_id && GS(target_id->name) != ID_SCE) {
if (deg_copy_on_write_is_needed(GS(target_id->name))) {
ComponentKey target_id_key(target_id, NodeType::COPY_ON_WRITE);
add_relation(target_id_key, driver_key, "Target ID -> Driver");
}
/* Add relations to all other cameras used by the scene timeline if applicable. */
if (const char *camera_path = get_rna_path_relative_to_scene_camera(
scene_, target_prop, dtar->rna_path))
{
build_driver_scene_camera_variable(driver_key, self_key, scene_, camera_path);
}
/* The RNA getter for `object.data` can write to the mesh datablock due
@ -2005,6 +1965,89 @@ void DepsgraphRelationBuilder::build_driver_variables(ID *id, FCurve *fcu)
}
}
void DepsgraphRelationBuilder::build_driver_scene_camera_variable(const OperationKey &driver_key,
const RNAPathKey &self_key,
Scene *scene,
const char *rna_path)
{
/* First, add relations to all cameras used in the timeline,
* excluding scene->camera which was already handled by the caller. */
bool animated = false;
LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
if (!ELEM(marker->camera, nullptr, scene->camera)) {
PointerRNA camera_ptr;
RNA_id_pointer_create(&marker->camera->id, &camera_ptr);
build_driver_id_property(camera_ptr, rna_path);
build_driver_rna_path_variable(driver_key, self_key, &scene->id, camera_ptr, rna_path);
animated = true;
}
}
/* If timeline indeed switches the camera, this variable also implicitly depends on time. */
if (animated) {
TimeSourceKey time_src_key;
add_relation(time_src_key, driver_key, "TimeSrc -> Driver Camera Ref");
}
}
void DepsgraphRelationBuilder::build_driver_rna_path_variable(const OperationKey &driver_key,
const RNAPathKey &self_key,
ID *target_id,
const PointerRNA &target_prop,
const char *rna_path)
{
RNAPathKey variable_exit_key(target_prop, rna_path, RNAPointerSource::EXIT);
if (RNA_pointer_is_null(&variable_exit_key.ptr)) {
return;
}
if (is_same_bone_dependency(variable_exit_key, self_key) ||
is_same_nodetree_node_dependency(variable_exit_key, self_key))
{
return;
}
add_relation(variable_exit_key, driver_key, "RNA Target -> Driver");
/* It is possible that RNA path points to a property of a different ID than the target_id:
* for example, paths like "data" on Object, "camera" on Scene.
*
* For the demonstration purposes lets consider a driver variable uses Scene ID as target
* and "camera.location.x" as its RNA path. If the scene has 2 different cameras at
* 2 different locations changing the active scene camera is expected to immediately be
* reflected in the variable value. In order to achieve this behavior we create a relation
* from the target ID to the driver so that if the ID property of the target ID changes the
* driver is re-evaluated.
*
* The most straightforward (at the moment of writing this comment) way of figuring out
* such relation is to use copy-on-write operation of the target ID. There are two down
* sides of this approach which are considered a design limitation as there is a belief
* that they are not common in practice or are not reliable due to other issues:
*
* - IDs which are not covered with the copy-on-write mechanism.
*
* Such IDs are either do not have ID properties, or are not part of the dependency
* graph.
*
* - Modifications of evaluated IDs from a Python handler.
* Such modifications are not fully integrated in the dependency graph evaluation as it
* has issues with copy-on-write tagging and the fact that relations are defined by the
* original main database status.
*
* The original report for this is #98618.
*
* The not-so-obvious part is that we don't do such relation for the context properties.
* They are resolved at the graph build time and do not change at runtime (#107081).
* Thus scene has to be excluded as a special case; this is OK because changes to
* scene.camera not caused by animation should actually force a dependency graph rebuild.
*/
if (target_id != variable_exit_key.ptr.owner_id && GS(target_id->name) != ID_SCE) {
if (deg_copy_on_write_is_needed(GS(target_id->name))) {
ComponentKey target_id_key(target_id, NodeType::COPY_ON_WRITE);
add_relation(target_id_key, driver_key, "Target ID -> Driver");
}
}
}
void DepsgraphRelationBuilder::build_driver_id_property(const PointerRNA &target_prop,
const char *rna_path_from_target_prop)
{

View File

@ -177,6 +177,16 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder {
virtual void build_driver_data(ID *id, FCurve *fcurve);
virtual void build_driver_variables(ID *id, FCurve *fcurve);
virtual void build_driver_scene_camera_variable(const OperationKey &driver_key,
const RNAPathKey &self_key,
Scene *scene,
const char *rna_path);
virtual void build_driver_rna_path_variable(const OperationKey &driver_key,
const RNAPathKey &self_key,
ID *target_id,
const PointerRNA &target_prop,
const char *rna_path);
/* Build operations of a property value from which is read by a driver target.
*
* The driver target points to a data-block (or a sub-data-block like View Layer).