GPv3: Python API for frame, drawing and drawing attributes #124787
@ -37,7 +37,7 @@ ccl_device void svm_vector_math(ccl_private float *value,
|
||||
*vector = reflect(a, b);
|
||||
break;
|
||||
case NODE_VECTOR_MATH_REFRACT:
|
||||
*vector = refract(a, normalize(b), param1);
|
||||
*vector = refract(a, safe_normalize(b), param1);
|
||||
break;
|
||||
case NODE_VECTOR_MATH_FACEFORWARD:
|
||||
*vector = faceforward(a, b, c);
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 4d2e4b4ba301e0a911d94dca83b99b88e5c579fe
|
||||
Subproject commit 53ee259cea0132e8dc141f19d543b0a934ade9ac
|
@ -1 +1 @@
|
||||
Subproject commit 181d15c1c640d826c593b54ce097894a1710977e
|
||||
Subproject commit a0b9f2878577e4d6922e469eaef5e268ffa1f29f
|
@ -245,15 +245,17 @@ class DOPESHEET_HT_editor_buttons:
|
||||
|
||||
layout.template_ID(st, "action", new="action.new", unlink="action.unlink")
|
||||
|
||||
# context.space_data.action comes from the active object.
|
||||
adt = context.object and context.object.animation_data
|
||||
if adt and st.action and st.action.is_action_layered:
|
||||
layout.template_search(
|
||||
adt, "action_slot",
|
||||
adt, "action_slots",
|
||||
new="",
|
||||
unlink="anim.slot_unassign_object",
|
||||
)
|
||||
# Show slot selector.
|
||||
if context.preferences.experimental.use_animation_baklava:
|
||||
# context.space_data.action comes from the active object.
|
||||
adt = context.object and context.object.animation_data
|
||||
if adt and st.action and st.action.is_action_layered:
|
||||
layout.template_search(
|
||||
adt, "action_slot",
|
||||
adt, "action_slots",
|
||||
new="",
|
||||
unlink="anim.slot_unassign_object",
|
||||
)
|
||||
|
||||
# Layer management
|
||||
if st.mode == 'GPENCIL':
|
||||
|
@ -35,6 +35,7 @@ from bl_ui.space_toolsystem_common import (
|
||||
|
||||
from bpy.app.translations import (
|
||||
contexts as i18n_contexts,
|
||||
pgettext_iface as iface_,
|
||||
)
|
||||
|
||||
|
||||
@ -791,10 +792,13 @@ class IMAGE_HT_header(Header):
|
||||
tool_settings = context.tool_settings
|
||||
|
||||
# Snap.
|
||||
snap_uv_element = tool_settings.snap_uv_element
|
||||
try:
|
||||
act_snap_icon = tool_settings.bl_rna.properties["snap_uv_element"].enum_items[snap_uv_element].icon
|
||||
except KeyError:
|
||||
snap_uv_elements = tool_settings.snap_uv_element
|
||||
if len(snap_uv_elements) == 1:
|
||||
text = ""
|
||||
elem = next(iter(snap_uv_elements))
|
||||
act_snap_icon = tool_settings.bl_rna.properties["snap_uv_element"].enum_items[elem].icon
|
||||
else:
|
||||
text = iface_("Mix", i18n_contexts.id_image)
|
||||
act_snap_icon = 'NONE'
|
||||
|
||||
row = layout.row(align=True)
|
||||
@ -804,7 +808,7 @@ class IMAGE_HT_header(Header):
|
||||
sub.popover(
|
||||
panel="IMAGE_PT_snapping",
|
||||
icon=act_snap_icon,
|
||||
text="",
|
||||
text=text,
|
||||
)
|
||||
|
||||
# Proportional Editing
|
||||
@ -1016,10 +1020,10 @@ class IMAGE_PT_snapping(Panel):
|
||||
col.label(text="Snap Target")
|
||||
col.prop(tool_settings, "snap_uv_element", expand=True)
|
||||
|
||||
if tool_settings.snap_uv_element != 'INCREMENT':
|
||||
col.label(text="Snap Base")
|
||||
row = col.row(align=True)
|
||||
row.prop(tool_settings, "snap_target", expand=True)
|
||||
col.label(text="Snap Base")
|
||||
row = col.row(align=True)
|
||||
row.active = bool(tool_settings.snap_uv_element.difference({'INCREMENT', 'GRID'}))
|
||||
row.prop(tool_settings, "snap_target", expand=True)
|
||||
|
||||
col.separator()
|
||||
|
||||
|
@ -6022,6 +6022,10 @@ class VIEW3D_MT_edit_greasepencil_animation(Menu):
|
||||
layout.operator("grease_pencil.insert_blank_frame", text="Insert Blank Keyframe (Active Layer)")
|
||||
layout.operator("grease_pencil.insert_blank_frame", text="Insert Blank Keyframe (All Layers)").all_layers = True
|
||||
|
||||
layout.separator()
|
||||
layout.operator("grease_pencil.frame_duplicate", text="Duplicate Active Keyframe (Active Layer)").all = False
|
||||
layout.operator("grease_pencil.frame_duplicate", text="Duplicate Active Keyframe (All Layer)").all = True
|
||||
|
||||
|
||||
class VIEW3D_MT_edit_gpencil_transform(Menu):
|
||||
bl_label = "Transform"
|
||||
|
@ -9,15 +9,145 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
|
||||
struct AnimationEvalContext;
|
||||
struct PointerRNA;
|
||||
|
||||
namespace blender::animrig {
|
||||
|
||||
/* Identifies the property that an evaluated animation value is for.
|
||||
*
|
||||
* This could be replaced with either `FCurveIdentifier` or `RNAPath`. However,
|
||||
* `FCurveIdentifier` is semantically meant to represent an fcurve itself rather
|
||||
* than the property an fcurve might be for, and moreover not all animation will
|
||||
* necessarily come from fcurves in the future anyway. `RNAPath` would be more
|
||||
* semantically appropriate, but it stores a full copy of the string component
|
||||
* of the path, and here we want to be lighter than that and use a string
|
||||
* reference.
|
||||
*/
|
||||
class PropIdentifier {
|
||||
public:
|
||||
/**
|
||||
* Reference to the RNA path of the property.
|
||||
*
|
||||
* This string is typically owned by the FCurve that animates the property.
|
||||
*/
|
||||
StringRefNull rna_path;
|
||||
int array_index;
|
||||
|
||||
PropIdentifier() = default;
|
||||
|
||||
PropIdentifier(const StringRefNull rna_path, const int array_index)
|
||||
: rna_path(rna_path), array_index(array_index)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const PropIdentifier &other) const
|
||||
{
|
||||
return rna_path == other.rna_path && array_index == other.array_index;
|
||||
}
|
||||
bool operator!=(const PropIdentifier &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(rna_path, array_index);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The evaluated value for an animated property, along with its RNA pointer.
|
||||
*/
|
||||
class AnimatedProperty {
|
||||
public:
|
||||
float value;
|
||||
PathResolvedRNA prop_rna;
|
||||
|
||||
AnimatedProperty(const float value, const PathResolvedRNA &prop_rna)
|
||||
: value(value), prop_rna(prop_rna)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/* Result of FCurve evaluation for an action slot.
|
||||
* Mapping from property identifier to its float value.
|
||||
*
|
||||
* Can be fed to the evaluation of the next layer, mixed with another strip, or
|
||||
* used to modify actual RNA properties.
|
||||
*
|
||||
* TODO: see if this is efficient, and contains enough info, for mixing. For now
|
||||
* this just captures the FCurve evaluation result, but doesn't have any info
|
||||
* about how to do the mixing (LERP, quaternion SLERP, etc.).
|
||||
*/
|
||||
class EvaluationResult {
|
||||
protected:
|
||||
using EvaluationMap = Map<PropIdentifier, AnimatedProperty>;
|
||||
EvaluationMap result_;
|
||||
|
||||
public:
|
||||
EvaluationResult() = default;
|
||||
EvaluationResult(const EvaluationResult &other) = default;
|
||||
~EvaluationResult() = default;
|
||||
|
||||
public:
|
||||
operator bool() const
|
||||
{
|
||||
return !this->is_empty();
|
||||
}
|
||||
bool is_empty() const
|
||||
{
|
||||
return result_.is_empty();
|
||||
}
|
||||
|
||||
void store(const StringRefNull rna_path,
|
||||
const int array_index,
|
||||
const float value,
|
||||
const PathResolvedRNA &prop_rna)
|
||||
{
|
||||
PropIdentifier key(rna_path, array_index);
|
||||
AnimatedProperty anim_prop(value, prop_rna);
|
||||
result_.add_overwrite(key, anim_prop);
|
||||
}
|
||||
|
||||
AnimatedProperty value(const StringRefNull rna_path, const int array_index) const
|
||||
{
|
||||
PropIdentifier key(rna_path, array_index);
|
||||
return result_.lookup(key);
|
||||
}
|
||||
|
||||
const AnimatedProperty *lookup_ptr(const PropIdentifier &key) const
|
||||
{
|
||||
return result_.lookup_ptr(key);
|
||||
}
|
||||
AnimatedProperty *lookup_ptr(const PropIdentifier &key)
|
||||
{
|
||||
return result_.lookup_ptr(key);
|
||||
}
|
||||
|
||||
EvaluationMap::ItemIterator items() const
|
||||
{
|
||||
return result_.items();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate the given action for the given slot and animated ID.
|
||||
*
|
||||
* This does *not* apply the resulting values to the ID. Instead, it returns
|
||||
* the resulting values in an `EvaluationResult`.
|
||||
*/
|
||||
EvaluationResult evaluate_action(PointerRNA &animated_id_ptr,
|
||||
Action &action,
|
||||
slot_handle_t slot_handle,
|
||||
const AnimationEvalContext &anim_eval_context);
|
||||
|
||||
/**
|
||||
* Top level animation evaluation function.
|
||||
*
|
||||
|
@ -189,31 +189,16 @@ bool insert_keyframe_direct(ReportList *reports,
|
||||
* Will perform checks just in case.
|
||||
* \return The number of key-frames deleted.
|
||||
*/
|
||||
int delete_keyframe(Main *bmain,
|
||||
ReportList *reports,
|
||||
ID *id,
|
||||
bAction *act,
|
||||
const char rna_path[],
|
||||
int array_index,
|
||||
float cfra);
|
||||
int delete_keyframe(Main *bmain, ReportList *reports, ID *id, const RNAPath &path, float cfra);
|
||||
|
||||
/**
|
||||
* Main Keyframing API call:
|
||||
* Use this when validation of necessary animation data isn't necessary as it
|
||||
* already exists. It will clear the current buttons fcurve(s).
|
||||
*
|
||||
* The flag argument is used for special settings that alter the behavior of
|
||||
* the keyframe deletion. These include the quick refresh options.
|
||||
*
|
||||
* \return The number of f-curves removed.
|
||||
*/
|
||||
int clear_keyframe(Main *bmain,
|
||||
ReportList *reports,
|
||||
ID *id,
|
||||
bAction *act,
|
||||
const char rna_path[],
|
||||
int array_index,
|
||||
eInsertKeyFlags /*flag*/);
|
||||
int clear_keyframe(Main *bmain, ReportList *reports, ID *id, const RNAPath &rna_path);
|
||||
|
||||
/** Check if a flag is set for keyframing (per scene takes precedence). */
|
||||
bool is_keying_flag(const Scene *scene, eKeying_Flag flag);
|
||||
|
@ -64,6 +64,9 @@ set(LIB
|
||||
PRIVATE bf::intern::clog
|
||||
)
|
||||
|
||||
if(WITH_EXPERIMENTAL_FEATURES)
|
||||
add_definitions(-DWITH_ANIM_BAKLAVA)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_animrig "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_library(bf::animrig ALIAS bf_animrig)
|
||||
|
@ -35,10 +35,10 @@ void apply_evaluation_result(const EvaluationResult &evaluation_result,
|
||||
PointerRNA &animated_id_ptr,
|
||||
bool flush_to_original);
|
||||
|
||||
static EvaluationResult evaluate_action(PointerRNA &animated_id_ptr,
|
||||
Action &action,
|
||||
const slot_handle_t slot_handle,
|
||||
const AnimationEvalContext &anim_eval_context)
|
||||
EvaluationResult evaluate_action(PointerRNA &animated_id_ptr,
|
||||
Action &action,
|
||||
const slot_handle_t slot_handle,
|
||||
const AnimationEvalContext &anim_eval_context)
|
||||
{
|
||||
EvaluationResult last_result;
|
||||
|
||||
|
@ -4,117 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
namespace blender::animrig::internal {
|
||||
|
||||
class PropIdentifier {
|
||||
public:
|
||||
/**
|
||||
* Reference to the RNA path of the property.
|
||||
*
|
||||
* This string is typically owned by the FCurve that animates the property.
|
||||
*/
|
||||
StringRefNull rna_path;
|
||||
int array_index;
|
||||
|
||||
PropIdentifier() = default;
|
||||
|
||||
PropIdentifier(const StringRefNull rna_path, const int array_index)
|
||||
: rna_path(rna_path), array_index(array_index)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const PropIdentifier &other) const
|
||||
{
|
||||
return rna_path == other.rna_path && array_index == other.array_index;
|
||||
}
|
||||
bool operator!=(const PropIdentifier &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(rna_path, array_index);
|
||||
}
|
||||
};
|
||||
|
||||
class AnimatedProperty {
|
||||
public:
|
||||
float value;
|
||||
PathResolvedRNA prop_rna;
|
||||
|
||||
AnimatedProperty(const float value, const PathResolvedRNA &prop_rna)
|
||||
: value(value), prop_rna(prop_rna)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/* Evaluated FCurves for some action slot.
|
||||
* Mapping from property identifier to its float value.
|
||||
*
|
||||
* Can be fed to the evaluation of the next layer, mixed with another strip, or
|
||||
* used to modify actual RNA properties.
|
||||
*
|
||||
* TODO: see if this is efficient, and contains enough info, for mixing. For now
|
||||
* this just captures the FCurve evaluation result, but doesn't have any info
|
||||
* about how to do the mixing (LERP, quaternion SLERP, etc.).
|
||||
*/
|
||||
class EvaluationResult {
|
||||
protected:
|
||||
using EvaluationMap = Map<PropIdentifier, AnimatedProperty>;
|
||||
EvaluationMap result_;
|
||||
|
||||
public:
|
||||
EvaluationResult() = default;
|
||||
EvaluationResult(const EvaluationResult &other) = default;
|
||||
~EvaluationResult() = default;
|
||||
|
||||
public:
|
||||
operator bool() const
|
||||
{
|
||||
return !this->is_empty();
|
||||
}
|
||||
bool is_empty() const
|
||||
{
|
||||
return result_.is_empty();
|
||||
}
|
||||
|
||||
void store(const StringRefNull rna_path,
|
||||
const int array_index,
|
||||
const float value,
|
||||
const PathResolvedRNA &prop_rna)
|
||||
{
|
||||
PropIdentifier key(rna_path, array_index);
|
||||
AnimatedProperty anim_prop(value, prop_rna);
|
||||
result_.add_overwrite(key, anim_prop);
|
||||
}
|
||||
|
||||
AnimatedProperty value(const StringRefNull rna_path, const int array_index) const
|
||||
{
|
||||
PropIdentifier key(rna_path, array_index);
|
||||
return result_.lookup(key);
|
||||
}
|
||||
|
||||
const AnimatedProperty *lookup_ptr(const PropIdentifier &key) const
|
||||
{
|
||||
return result_.lookup_ptr(key);
|
||||
}
|
||||
AnimatedProperty *lookup_ptr(const PropIdentifier &key)
|
||||
{
|
||||
return result_.lookup_ptr(key);
|
||||
}
|
||||
|
||||
EvaluationMap::ItemIterator items() const
|
||||
{
|
||||
return result_.items();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate the animation data on the given layer, for the given slot. This
|
||||
* just returns the evaluation result, without taking any other layers,
|
||||
|
@ -627,15 +627,8 @@ static void deg_tag_after_keyframe_delete(Main *bmain, ID *id, AnimData *adt)
|
||||
}
|
||||
}
|
||||
|
||||
int delete_keyframe(Main *bmain,
|
||||
ReportList *reports,
|
||||
ID *id,
|
||||
bAction *act,
|
||||
const char rna_path[],
|
||||
int array_index,
|
||||
float cfra)
|
||||
int delete_keyframe(Main *bmain, ReportList *reports, ID *id, const RNAPath &rna_path, float cfra)
|
||||
{
|
||||
BLI_assert(rna_path != nullptr);
|
||||
AnimData *adt = BKE_animdata_from_id(id);
|
||||
|
||||
if (ELEM(nullptr, id, adt)) {
|
||||
@ -646,33 +639,27 @@ int delete_keyframe(Main *bmain,
|
||||
PointerRNA ptr;
|
||||
PropertyRNA *prop;
|
||||
PointerRNA id_ptr = RNA_id_pointer_create(id);
|
||||
if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) {
|
||||
if (RNA_path_resolve_property(&id_ptr, rna_path.path.c_str(), &ptr, &prop) == false) {
|
||||
BKE_reportf(
|
||||
reports,
|
||||
RPT_ERROR,
|
||||
"Could not delete keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)",
|
||||
id->name,
|
||||
rna_path);
|
||||
rna_path.path.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (act == nullptr) {
|
||||
if (adt->action) {
|
||||
act = adt->action;
|
||||
cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP);
|
||||
}
|
||||
else {
|
||||
BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name);
|
||||
return 0;
|
||||
}
|
||||
if (!adt->action) {
|
||||
BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bAction *act = adt->action;
|
||||
cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP);
|
||||
int array_index = rna_path.index.value_or(0);
|
||||
int array_index_max = array_index + 1;
|
||||
|
||||
if (array_index == -1) {
|
||||
array_index = 0;
|
||||
if (!rna_path.index.has_value()) {
|
||||
array_index_max = RNA_property_array_length(&ptr, prop);
|
||||
|
||||
/* For single properties, increase max_index so that the property itself gets included,
|
||||
* but don't do this for standard arrays since that can cause corruption issues
|
||||
* (extra unused curves).
|
||||
@ -695,7 +682,7 @@ int delete_keyframe(Main *bmain,
|
||||
* paths. In the future when legacy actions are removed, we can restructure
|
||||
* it to be clearer. */
|
||||
for (; array_index < array_index_max; array_index++) {
|
||||
FCurve *fcurve = fcurve_find(fcurves, {rna_path, array_index});
|
||||
FCurve *fcurve = fcurve_find(fcurves, {rna_path.path, array_index});
|
||||
if (fcurve == nullptr) {
|
||||
continue;
|
||||
}
|
||||
@ -708,7 +695,7 @@ int delete_keyframe(Main *bmain,
|
||||
/* Will only loop once unless the array index was -1. */
|
||||
int key_count = 0;
|
||||
for (; array_index < array_index_max; array_index++) {
|
||||
FCurve *fcu = action_fcurve_find(act, {rna_path, array_index});
|
||||
FCurve *fcu = action_fcurve_find(act, {rna_path.path, array_index});
|
||||
|
||||
if (fcu == nullptr) {
|
||||
continue;
|
||||
@ -736,16 +723,8 @@ int delete_keyframe(Main *bmain,
|
||||
/* ************************************************** */
|
||||
/* KEYFRAME CLEAR */
|
||||
|
||||
int clear_keyframe(Main *bmain,
|
||||
ReportList *reports,
|
||||
ID *id,
|
||||
bAction *act,
|
||||
const char rna_path[],
|
||||
int array_index,
|
||||
eInsertKeyFlags /*flag*/)
|
||||
int clear_keyframe(Main *bmain, ReportList *reports, ID *id, const RNAPath &rna_path)
|
||||
{
|
||||
BLI_assert(rna_path != nullptr);
|
||||
|
||||
AnimData *adt = BKE_animdata_from_id(id);
|
||||
|
||||
if (ELEM(nullptr, id, adt)) {
|
||||
@ -756,29 +735,25 @@ int clear_keyframe(Main *bmain,
|
||||
PointerRNA ptr;
|
||||
PropertyRNA *prop;
|
||||
PointerRNA id_ptr = RNA_id_pointer_create(id);
|
||||
if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) {
|
||||
if (RNA_path_resolve_property(&id_ptr, rna_path.path.c_str(), &ptr, &prop) == false) {
|
||||
BKE_reportf(
|
||||
reports,
|
||||
RPT_ERROR,
|
||||
"Could not clear keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)",
|
||||
id->name,
|
||||
rna_path);
|
||||
rna_path.path.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (act == nullptr) {
|
||||
if (adt->action) {
|
||||
act = adt->action;
|
||||
}
|
||||
else {
|
||||
BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name);
|
||||
return 0;
|
||||
}
|
||||
if (!adt->action) {
|
||||
BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name);
|
||||
return 0;
|
||||
}
|
||||
bAction *act = adt->action;
|
||||
|
||||
int array_index = rna_path.index.value_or(0);
|
||||
int array_index_max = array_index + 1;
|
||||
if (array_index == -1) {
|
||||
array_index = 0;
|
||||
if (!rna_path.index.has_value()) {
|
||||
array_index_max = RNA_property_array_length(&ptr, prop);
|
||||
|
||||
/* For single properties, increase max_index so that the property itself gets included,
|
||||
@ -793,7 +768,7 @@ int clear_keyframe(Main *bmain,
|
||||
int key_count = 0;
|
||||
/* Will only loop once unless the array index was -1. */
|
||||
for (; array_index < array_index_max; array_index++) {
|
||||
FCurve *fcu = action_fcurve_find(act, {rna_path, array_index});
|
||||
FCurve *fcu = action_fcurve_find(act, {rna_path.path, array_index});
|
||||
|
||||
if (fcu == nullptr) {
|
||||
continue;
|
||||
|
@ -1,39 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup asset_system
|
||||
*
|
||||
* \brief Information to uniquely identify and locate an asset.
|
||||
*
|
||||
* https://developer.blender.org/docs/features/asset_system/backend/#asset-identifier
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
struct AssetWeakReference;
|
||||
|
||||
namespace blender::asset_system {
|
||||
|
||||
class AssetIdentifier {
|
||||
std::shared_ptr<std::string> library_root_path_;
|
||||
std::string relative_asset_path_;
|
||||
|
||||
public:
|
||||
AssetIdentifier(std::shared_ptr<std::string> library_root_path, std::string relative_asset_path);
|
||||
AssetIdentifier(AssetIdentifier &&) = default;
|
||||
AssetIdentifier(const AssetIdentifier &) = default;
|
||||
|
||||
StringRefNull library_relative_identifier() const;
|
||||
|
||||
std::string full_path() const;
|
||||
std::string full_library_path() const;
|
||||
};
|
||||
|
||||
} // namespace blender::asset_system
|
@ -8,7 +8,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
@ -30,9 +29,7 @@ class IDRemapper;
|
||||
|
||||
namespace blender::asset_system {
|
||||
|
||||
class AssetIdentifier;
|
||||
class AssetRepresentation;
|
||||
class AssetStorage;
|
||||
|
||||
/**
|
||||
* AssetLibrary provides access to an asset library's data.
|
||||
@ -54,9 +51,9 @@ class AssetLibrary {
|
||||
std::shared_ptr<std::string> root_path_;
|
||||
|
||||
/**
|
||||
* Storage for assets (better said their representations) that are considered to be part of this
|
||||
* library. Assets are not automatically loaded into this when loading an asset library. Assets
|
||||
* have to be loaded externally and added to this storage via #add_external_asset() or
|
||||
* AssetStorage for assets (better said their representations) that are considered to be part of
|
||||
* this library. Assets are not automatically loaded into this when loading an asset library.
|
||||
* Assets have to be loaded externally and added to this storage via #add_external_asset() or
|
||||
* #add_local_id_asset(). So this really is arbitrary storage as far as #AssetLibrary is
|
||||
* concerned (allowing the API user to manage partial library storage and partial loading, so
|
||||
* only relevant parts of a library are kept in memory).
|
||||
@ -67,7 +64,17 @@ class AssetLibrary {
|
||||
* already in memory and which not. Neither do we keep track of how many parts of Blender are
|
||||
* using an asset or an asset library, which is needed to know when assets can be freed.
|
||||
*/
|
||||
std::unique_ptr<AssetStorage> asset_storage_;
|
||||
struct AssetStorage {
|
||||
/* Uses shared pointers so the UI can acquire weak pointers. It can then ensure pointers are
|
||||
* not dangling before accessing. */
|
||||
|
||||
Set<std::shared_ptr<AssetRepresentation>> external_assets;
|
||||
/* Store local ID assets separately for efficient lookups.
|
||||
* TODO(Julian): A [ID *, asset] or even [ID.session_uid, asset] map would be preferable for
|
||||
* faster lookups. Not possible until each asset is only represented once in the storage. */
|
||||
Set<std::shared_ptr<AssetRepresentation>> local_id_assets;
|
||||
};
|
||||
AssetStorage asset_storage_;
|
||||
|
||||
protected:
|
||||
/* Changing this pointer should be protected using #catalog_service_mutex_. Note that changes
|
||||
@ -169,12 +176,6 @@ class AssetLibrary {
|
||||
|
||||
void on_blend_save_post(Main *bmain, PointerRNA **pointers, int num_pointers);
|
||||
|
||||
/**
|
||||
* Create an asset identifier from the root path of this asset library and the given relative
|
||||
* asset path (relative to the asset library root directory).
|
||||
*/
|
||||
AssetIdentifier asset_identifier_from_library(StringRef relative_asset_path);
|
||||
|
||||
std::string resolve_asset_weak_reference_to_full_path(const AssetWeakReference &asset_reference);
|
||||
|
||||
eAssetLibraryType library_type() const;
|
||||
|
@ -23,8 +23,6 @@
|
||||
#include "DNA_ID_enums.h"
|
||||
#include "DNA_asset_types.h"
|
||||
|
||||
#include "AS_asset_identifier.hh"
|
||||
|
||||
struct AssetMetaData;
|
||||
struct ID;
|
||||
|
||||
@ -33,14 +31,18 @@ namespace blender::asset_system {
|
||||
class AssetLibrary;
|
||||
|
||||
class AssetRepresentation : NonCopyable, NonMovable {
|
||||
AssetIdentifier identifier_;
|
||||
/** Pointer back to the asset library that owns this asset representation. */
|
||||
const AssetLibrary &owner_asset_library_;
|
||||
/**
|
||||
* Uniquely identifies the asset within the asset library. Currently this is always a path (path
|
||||
* within the asset library).
|
||||
*/
|
||||
std::string relative_identifier_;
|
||||
/**
|
||||
* Indicate if this is a local or external asset, and as such, which of the union members below
|
||||
* should be used.
|
||||
*/
|
||||
const bool is_local_id_ = false;
|
||||
/** Asset library that owns this asset representation. */
|
||||
const AssetLibrary &owner_asset_library_;
|
||||
|
||||
struct ExternalAsset {
|
||||
std::string name;
|
||||
@ -52,11 +54,11 @@ class AssetRepresentation : NonCopyable, NonMovable {
|
||||
ID *local_asset_id_ = nullptr; /* Non-owning. */
|
||||
};
|
||||
|
||||
friend class AssetStorage;
|
||||
friend class AssetLibrary;
|
||||
|
||||
public:
|
||||
/** Constructs an asset representation for an external ID. The asset will not be editable. */
|
||||
AssetRepresentation(AssetIdentifier &&identifier,
|
||||
AssetRepresentation(StringRef relative_asset_path,
|
||||
StringRef name,
|
||||
int id_type,
|
||||
std::unique_ptr<AssetMetaData> metadata,
|
||||
@ -65,13 +67,11 @@ class AssetRepresentation : NonCopyable, NonMovable {
|
||||
* Constructs an asset representation for an ID stored in the current file. This makes the asset
|
||||
* local and fully editable.
|
||||
*/
|
||||
AssetRepresentation(AssetIdentifier &&identifier,
|
||||
AssetRepresentation(StringRef relative_asset_path,
|
||||
ID &id,
|
||||
const AssetLibrary &owner_asset_library);
|
||||
~AssetRepresentation();
|
||||
|
||||
const AssetIdentifier &get_identifier() const;
|
||||
|
||||
/**
|
||||
* Create a weak reference for this asset that can be written to files, but can break under a
|
||||
* number of conditions.
|
||||
@ -82,6 +82,11 @@ class AssetRepresentation : NonCopyable, NonMovable {
|
||||
StringRefNull get_name() const;
|
||||
ID_Type get_id_type() const;
|
||||
AssetMetaData &get_metadata() const;
|
||||
|
||||
StringRefNull library_relative_identifier() const;
|
||||
std::string full_path() const;
|
||||
std::string full_library_path() const;
|
||||
|
||||
/**
|
||||
* Get the import method to use for this asset. A different one may be used if
|
||||
* #may_override_import_method() returns true, otherwise, the returned value must be used. If
|
||||
|
@ -17,7 +17,6 @@ set(SRC
|
||||
intern/asset_catalog_definition_file.cc
|
||||
intern/asset_catalog_path.cc
|
||||
intern/asset_catalog_tree.cc
|
||||
intern/asset_identifier.cc
|
||||
intern/asset_library.cc
|
||||
intern/asset_library_all.cc
|
||||
intern/asset_library_essentials.cc
|
||||
@ -26,13 +25,11 @@ set(SRC
|
||||
intern/asset_library_runtime.cc
|
||||
intern/asset_library_service.cc
|
||||
intern/asset_representation.cc
|
||||
intern/asset_storage.cc
|
||||
intern/utils.cc
|
||||
|
||||
AS_asset_catalog.hh
|
||||
AS_asset_catalog_path.hh
|
||||
AS_asset_catalog_tree.hh
|
||||
AS_asset_identifier.hh
|
||||
AS_asset_library.hh
|
||||
AS_asset_representation.hh
|
||||
AS_essentials_library.hh
|
||||
@ -44,7 +41,6 @@ set(SRC
|
||||
intern/asset_library_on_disk.hh
|
||||
intern/asset_library_runtime.hh
|
||||
intern/asset_library_service.hh
|
||||
intern/asset_storage.hh
|
||||
intern/utils.hh
|
||||
)
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup asset_system
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BKE_blendfile.hh"
|
||||
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
#include "AS_asset_identifier.hh"
|
||||
|
||||
namespace blender::asset_system {
|
||||
|
||||
AssetIdentifier::AssetIdentifier(std::shared_ptr<std::string> library_root_path,
|
||||
std::string relative_asset_path)
|
||||
: library_root_path_(std::move(library_root_path)),
|
||||
relative_asset_path_(std::move(relative_asset_path))
|
||||
{
|
||||
}
|
||||
|
||||
StringRefNull AssetIdentifier::library_relative_identifier() const
|
||||
{
|
||||
return relative_asset_path_;
|
||||
}
|
||||
|
||||
std::string AssetIdentifier::full_path() const
|
||||
{
|
||||
char filepath[FILE_MAX];
|
||||
BLI_path_join(
|
||||
filepath, sizeof(filepath), library_root_path_->c_str(), relative_asset_path_.c_str());
|
||||
return filepath;
|
||||
}
|
||||
|
||||
std::string AssetIdentifier::full_library_path() const
|
||||
{
|
||||
std::string asset_path = full_path();
|
||||
|
||||
char blend_path[1090 /*FILE_MAX_LIBEXTRA*/];
|
||||
if (!BKE_blendfile_library_path_explode(asset_path.c_str(), blend_path, nullptr, nullptr)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return blend_path;
|
||||
}
|
||||
|
||||
} // namespace blender::asset_system
|
@ -9,7 +9,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include "AS_asset_catalog_tree.hh"
|
||||
#include "AS_asset_identifier.hh"
|
||||
#include "AS_asset_library.hh"
|
||||
#include "AS_asset_representation.hh"
|
||||
|
||||
@ -27,7 +26,6 @@
|
||||
#include "asset_catalog_collection.hh"
|
||||
#include "asset_catalog_definition_file.hh"
|
||||
#include "asset_library_service.hh"
|
||||
#include "asset_storage.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
using namespace blender;
|
||||
@ -168,7 +166,6 @@ AssetLibrary::AssetLibrary(eAssetLibraryType library_type, StringRef name, Strin
|
||||
: library_type_(library_type),
|
||||
name_(name),
|
||||
root_path_(std::make_shared<std::string>(utils::normalize_directory_path(root_path))),
|
||||
asset_storage_(std::make_unique<AssetStorage>()),
|
||||
catalog_service_(std::make_unique<AssetCatalogService>())
|
||||
{
|
||||
}
|
||||
@ -208,26 +205,45 @@ std::weak_ptr<AssetRepresentation> AssetLibrary::add_external_asset(
|
||||
const int id_type,
|
||||
std::unique_ptr<AssetMetaData> metadata)
|
||||
{
|
||||
AssetIdentifier identifier = this->asset_identifier_from_library(relative_asset_path);
|
||||
return asset_storage_->add_external_asset(
|
||||
std::move(identifier), name, id_type, std::move(metadata), *this);
|
||||
return asset_storage_.external_assets.lookup_key_or_add(std::make_shared<AssetRepresentation>(
|
||||
relative_asset_path, name, id_type, std::move(metadata), *this));
|
||||
}
|
||||
|
||||
std::weak_ptr<AssetRepresentation> AssetLibrary::add_local_id_asset(StringRef relative_asset_path,
|
||||
ID &id)
|
||||
{
|
||||
AssetIdentifier identifier = this->asset_identifier_from_library(relative_asset_path);
|
||||
return asset_storage_->add_local_id_asset(std::move(identifier), id, *this);
|
||||
return asset_storage_.local_id_assets.lookup_key_or_add(
|
||||
std::make_shared<AssetRepresentation>(relative_asset_path, id, *this));
|
||||
}
|
||||
|
||||
bool AssetLibrary::remove_asset(AssetRepresentation &asset)
|
||||
{
|
||||
return asset_storage_->remove_asset(asset);
|
||||
if (asset_storage_.local_id_assets.remove_as(&asset)) {
|
||||
return true;
|
||||
}
|
||||
return asset_storage_.external_assets.remove_as(&asset);
|
||||
}
|
||||
|
||||
void AssetLibrary::remap_ids_and_remove_invalid(const bke::id::IDRemapper &mappings)
|
||||
{
|
||||
asset_storage_->remap_ids_and_remove_invalid(mappings);
|
||||
Set<AssetRepresentation *> removed_assets;
|
||||
|
||||
for (auto &asset_ptr : asset_storage_.local_id_assets) {
|
||||
AssetRepresentation &asset = *asset_ptr;
|
||||
BLI_assert(asset.is_local_id());
|
||||
|
||||
const IDRemapperApplyResult result = mappings.apply(&asset.local_asset_id_,
|
||||
ID_REMAP_APPLY_DEFAULT);
|
||||
|
||||
/* Entirely remove assets whose ID is unset. We don't want assets with a null ID pointer. */
|
||||
if (result == ID_REMAP_RESULT_SOURCE_UNASSIGNED) {
|
||||
removed_assets.add(&asset);
|
||||
}
|
||||
}
|
||||
|
||||
for (AssetRepresentation *asset : removed_assets) {
|
||||
this->remove_asset(*asset);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -269,11 +285,6 @@ void AssetLibrary::on_blend_save_post(Main *main,
|
||||
}
|
||||
}
|
||||
|
||||
AssetIdentifier AssetLibrary::asset_identifier_from_library(StringRef relative_asset_path)
|
||||
{
|
||||
return AssetIdentifier(root_path_, relative_asset_path);
|
||||
}
|
||||
|
||||
std::string AssetLibrary::resolve_asset_weak_reference_to_full_path(
|
||||
const AssetWeakReference &asset_reference)
|
||||
{
|
||||
|
@ -8,23 +8,26 @@
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
#include "BKE_blendfile.hh"
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_asset_types.h"
|
||||
|
||||
#include "AS_asset_identifier.hh"
|
||||
#include "AS_asset_library.hh"
|
||||
#include "AS_asset_representation.hh"
|
||||
|
||||
namespace blender::asset_system {
|
||||
|
||||
AssetRepresentation::AssetRepresentation(AssetIdentifier &&identifier,
|
||||
AssetRepresentation::AssetRepresentation(StringRef relative_path,
|
||||
StringRef name,
|
||||
const int id_type,
|
||||
std::unique_ptr<AssetMetaData> metadata,
|
||||
const AssetLibrary &owner_asset_library)
|
||||
: identifier_(std::move(identifier)),
|
||||
: owner_asset_library_(owner_asset_library),
|
||||
relative_identifier_(relative_path),
|
||||
is_local_id_(false),
|
||||
owner_asset_library_(owner_asset_library),
|
||||
external_asset_()
|
||||
{
|
||||
external_asset_.name = name;
|
||||
@ -32,12 +35,12 @@ AssetRepresentation::AssetRepresentation(AssetIdentifier &&identifier,
|
||||
external_asset_.metadata_ = std::move(metadata);
|
||||
}
|
||||
|
||||
AssetRepresentation::AssetRepresentation(AssetIdentifier &&identifier,
|
||||
AssetRepresentation::AssetRepresentation(StringRef relative_path,
|
||||
ID &id,
|
||||
const AssetLibrary &owner_asset_library)
|
||||
: identifier_(std::move(identifier)),
|
||||
: owner_asset_library_(owner_asset_library),
|
||||
relative_identifier_(relative_path),
|
||||
is_local_id_(true),
|
||||
owner_asset_library_(owner_asset_library),
|
||||
local_asset_id_(&id)
|
||||
{
|
||||
if (!id.asset_data) {
|
||||
@ -52,14 +55,9 @@ AssetRepresentation::~AssetRepresentation()
|
||||
}
|
||||
}
|
||||
|
||||
const AssetIdentifier &AssetRepresentation::get_identifier() const
|
||||
{
|
||||
return identifier_;
|
||||
}
|
||||
|
||||
AssetWeakReference AssetRepresentation::make_weak_reference() const
|
||||
{
|
||||
return AssetWeakReference::make_reference(owner_asset_library_, identifier_);
|
||||
return AssetWeakReference::make_reference(owner_asset_library_, relative_identifier_);
|
||||
}
|
||||
|
||||
StringRefNull AssetRepresentation::get_name() const
|
||||
@ -85,6 +83,33 @@ AssetMetaData &AssetRepresentation::get_metadata() const
|
||||
return is_local_id_ ? *local_asset_id_->asset_data : *external_asset_.metadata_;
|
||||
}
|
||||
|
||||
StringRefNull AssetRepresentation::library_relative_identifier() const
|
||||
{
|
||||
return relative_identifier_;
|
||||
}
|
||||
|
||||
std::string AssetRepresentation::full_path() const
|
||||
{
|
||||
char filepath[FILE_MAX];
|
||||
BLI_path_join(filepath,
|
||||
sizeof(filepath),
|
||||
owner_asset_library_.root_path().c_str(),
|
||||
relative_identifier_.c_str());
|
||||
return filepath;
|
||||
}
|
||||
|
||||
std::string AssetRepresentation::full_library_path() const
|
||||
{
|
||||
std::string asset_path = full_path();
|
||||
|
||||
char blend_path[1090 /*FILE_MAX_LIBEXTRA*/];
|
||||
if (!BKE_blendfile_library_path_explode(asset_path.c_str(), blend_path, nullptr, nullptr)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return blend_path;
|
||||
}
|
||||
|
||||
std::optional<eAssetImportMethod> AssetRepresentation::get_import_method() const
|
||||
{
|
||||
return owner_asset_library_.import_method_;
|
||||
|
@ -1,68 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup asset_system
|
||||
*/
|
||||
|
||||
#include "AS_asset_representation.hh"
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_asset_types.h"
|
||||
|
||||
#include "BKE_lib_remap.hh"
|
||||
|
||||
#include "asset_storage.hh"
|
||||
|
||||
namespace blender::asset_system {
|
||||
|
||||
std::weak_ptr<AssetRepresentation> AssetStorage::add_local_id_asset(
|
||||
AssetIdentifier &&identifier, ID &id, const AssetLibrary &owner_asset_library)
|
||||
{
|
||||
return local_id_assets_.lookup_key_or_add(
|
||||
std::make_shared<AssetRepresentation>(std::move(identifier), id, owner_asset_library));
|
||||
}
|
||||
|
||||
std::weak_ptr<AssetRepresentation> AssetStorage::add_external_asset(
|
||||
AssetIdentifier &&identifier,
|
||||
StringRef name,
|
||||
const int id_type,
|
||||
std::unique_ptr<AssetMetaData> metadata,
|
||||
const AssetLibrary &owner_asset_library)
|
||||
{
|
||||
return external_assets_.lookup_key_or_add(std::make_shared<AssetRepresentation>(
|
||||
std::move(identifier), name, id_type, std::move(metadata), owner_asset_library));
|
||||
}
|
||||
|
||||
bool AssetStorage::remove_asset(AssetRepresentation &asset)
|
||||
{
|
||||
if (local_id_assets_.remove_as(&asset)) {
|
||||
return true;
|
||||
}
|
||||
return external_assets_.remove_as(&asset);
|
||||
}
|
||||
|
||||
void AssetStorage::remap_ids_and_remove_invalid(const bke::id::IDRemapper &mappings)
|
||||
{
|
||||
Set<AssetRepresentation *> removed_assets;
|
||||
|
||||
for (auto &asset_ptr : local_id_assets_) {
|
||||
AssetRepresentation &asset = *asset_ptr;
|
||||
BLI_assert(asset.is_local_id());
|
||||
|
||||
const IDRemapperApplyResult result = mappings.apply(&asset.local_asset_id_,
|
||||
ID_REMAP_APPLY_DEFAULT);
|
||||
|
||||
/* Entirely remove assets whose ID is unset. We don't want assets with a null ID pointer. */
|
||||
if (result == ID_REMAP_RESULT_SOURCE_UNASSIGNED) {
|
||||
removed_assets.add(&asset);
|
||||
}
|
||||
}
|
||||
|
||||
for (AssetRepresentation *asset : removed_assets) {
|
||||
this->remove_asset(*asset);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::asset_system
|
@ -1,61 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup asset_system
|
||||
*
|
||||
* The database implementation for asset representations within a library. Not part of the public
|
||||
* API, storage access happens via the #AssetLibrary API.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "BLI_set.hh"
|
||||
|
||||
struct AssetMetaData;
|
||||
struct ID;
|
||||
|
||||
namespace blender::bke::id {
|
||||
class IDRemapper;
|
||||
}
|
||||
|
||||
namespace blender::asset_system {
|
||||
|
||||
class AssetIdentifier;
|
||||
class AssetLibrary;
|
||||
class AssetRepresentation;
|
||||
|
||||
class AssetStorage {
|
||||
/* Uses shared pointers so the UI can acquire weak pointers. It can then ensure pointers are not
|
||||
* dangling before accessing. */
|
||||
using StorageT = Set<std::shared_ptr<AssetRepresentation>>;
|
||||
|
||||
StorageT external_assets_;
|
||||
/* Store local ID assets separately for efficient lookups.
|
||||
* TODO(Julian): A [ID *, asset] or even [ID.session_uid, asset] map would be preferable for
|
||||
* faster lookups. Not possible until each asset is only represented once in the storage. */
|
||||
StorageT local_id_assets_;
|
||||
|
||||
public:
|
||||
/** See #AssetLibrary::add_external_asset(). */
|
||||
std::weak_ptr<AssetRepresentation> add_external_asset(AssetIdentifier &&identifier,
|
||||
StringRef name,
|
||||
int id_type,
|
||||
std::unique_ptr<AssetMetaData> metadata,
|
||||
const AssetLibrary &owner_asset_library);
|
||||
/** See #AssetLibrary::add_external_asset(). */
|
||||
std::weak_ptr<AssetRepresentation> add_local_id_asset(AssetIdentifier &&identifier,
|
||||
ID &id,
|
||||
const AssetLibrary &owner_asset_library);
|
||||
|
||||
/** See #AssetLibrary::remove_asset(). */
|
||||
bool remove_asset(AssetRepresentation &asset);
|
||||
|
||||
/** See #AssetLibrary::remap_ids_and_remove_nulled(). */
|
||||
void remap_ids_and_remove_invalid(const blender::bke::id::IDRemapper &mappings);
|
||||
};
|
||||
|
||||
} // namespace blender::asset_system
|
@ -17,7 +17,6 @@
|
||||
#include "DNA_asset_types.h"
|
||||
#include "DNA_space_types.h"
|
||||