Files
test/source/blender/makesrna/intern/rna_path.cc
Julian Eisel 67b92418ee Outliner: Use UI names and grouping for library overrides properties
Part of T95802.

Showing properties with an RNA path in the UI isn't very user friendly.
Instead, represent the RNA path as a tree, merging together parts of the
RNA path that are shared by multiple properties. Properties and "groups"
(RNA structs/pointers) are now shown with their UI name and an icon if
any. The actually overridden properties still show the Library Overrides
icon. See the patch for screenshots.

Also: When a RNA collection item, like a modifier or constraint was
added via a library override, indicate that item and show all collection
items in the list, since the complete list of items and their orders may
be important context.

Differential Revision: https://developer.blender.org/D15606
2022-08-04 15:57:08 +02:00

1360 lines
36 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup RNA
*/
#include <cstdlib>
#include <stdlib.h>
#include <string.h>
#include "BLI_alloca.h"
#include "BLI_dynstr.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BKE_idprop.h"
#include "BKE_idtype.h"
#include "DNA_ID.h" /* For ID properties. */
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_path.h"
#include "RNA_prototypes.h"
#include "rna_access_internal.h"
#include "rna_internal.h"
/**
* Extract the first token from `path`.
*
* \param path: Extract the token from path, step the pointer to the beginning of the next token
* \return The nil terminated token.
*/
static char *rna_path_token(const char **path, char *fixedbuf, int fixedlen)
{
int len = 0;
/* Get data until `.` or `[`. */
const char *p = *path;
while (*p && !ELEM(*p, '.', '[')) {
len++;
p++;
}
/* Empty, return. */
if (UNLIKELY(len == 0)) {
return NULL;
}
/* Try to use fixed buffer if possible. */
char *buf = (len + 1 < fixedlen) ? fixedbuf :
(char *)MEM_mallocN(sizeof(char) * (len + 1), __func__);
memcpy(buf, *path, sizeof(char) * len);
buf[len] = '\0';
if (*p == '.') {
p++;
}
*path = p;
return buf;
}
/**
* Extract the first token in brackets from `path` (with quoted text support).
*
* - `[0]` -> `0`
* - `["Some\"Quote"]` -> `Some"Quote`
*
* \param path: Extract the token from path, step the pointer to the beginning of the next token
* (past quoted text and brackets).
* \return The nil terminated token.
*/
static char *rna_path_token_in_brackets(const char **path,
char *fixedbuf,
int fixedlen,
bool *r_quoted)
{
int len = 0;
bool quoted = false;
BLI_assert(r_quoted != NULL);
/* Get data between `[]`, check escaping quotes and back-slashes with #BLI_str_unescape. */
if (UNLIKELY(**path != '[')) {
return NULL;
}
(*path)++;
const char *p = *path;
/* 2 kinds of look-ups now, quoted or unquoted. */
if (*p == '"') {
/* Find the matching quote. */
(*path)++;
p = *path;
const char *p_end = BLI_str_escape_find_quote(p);
if (p_end == NULL) {
/* No Matching quote. */
return NULL;
}
/* Exclude the last quote from the length. */
len += (p_end - p);
/* Skip the last quoted char to get the `]`. */
p_end += 1;
p = p_end;
quoted = true;
}
else {
/* Find the matching bracket. */
while (*p && (*p != ']')) {
len++;
p++;
}
}
if (UNLIKELY(*p != ']')) {
return NULL;
}
/* Empty, return. */
if (UNLIKELY(len == 0)) {
return NULL;
}
/* Try to use fixed buffer if possible. */
char *buf = (len + 1 < fixedlen) ? fixedbuf :
(char *)MEM_mallocN(sizeof(char) * (len + 1), __func__);
/* Copy string, taking into account escaped ']' */
if (quoted) {
BLI_str_unescape(buf, *path, len);
/* +1 to step over the last quote. */
BLI_assert((*path)[len] == '"');
p = (*path) + len + 1;
}
else {
memcpy(buf, *path, sizeof(char) * len);
buf[len] = '\0';
}
/* Set path to start of next token. */
if (*p == ']') {
p++;
}
if (*p == '.') {
p++;
}
*path = p;
*r_quoted = quoted;
return buf;
}
/**
* \return true when the key in the path is correctly parsed and found in the collection
* or when the path is empty.
*/
static bool rna_path_parse_collection_key(const char **path,
PointerRNA *ptr,
PropertyRNA *prop,
PointerRNA *r_nextptr)
{
char fixedbuf[256];
int intkey;
*r_nextptr = *ptr;
/* end of path, ok */
if (!(**path)) {
return true;
}
bool found = false;
if (**path == '[') {
bool quoted;
char *token;
/* resolve the lookup with [] brackets */
token = rna_path_token_in_brackets(path, fixedbuf, sizeof(fixedbuf), &quoted);
if (!token) {
return false;
}
/* check for "" to see if it is a string */
if (quoted) {
if (RNA_property_collection_lookup_string(ptr, prop, token, r_nextptr)) {
found = true;
}
else {
r_nextptr->data = NULL;
}
}
else {
/* otherwise do int lookup */
intkey = atoi(token);
if (intkey == 0 && (token[0] != '0' || token[1] != '\0')) {
return false; /* we can be sure the fixedbuf was used in this case */
}
if (RNA_property_collection_lookup_int(ptr, prop, intkey, r_nextptr)) {
found = true;
}
else {
r_nextptr->data = NULL;
}
}
if (token != fixedbuf) {
MEM_freeN(token);
}
}
else {
if (RNA_property_collection_type_get(ptr, prop, r_nextptr)) {
found = true;
}
else {
/* ensure we quit on invalid values */
r_nextptr->data = NULL;
}
}
return found;
}
static bool rna_path_parse_array_index(const char **path,
PointerRNA *ptr,
PropertyRNA *prop,
int *r_index)
{
char fixedbuf[256];
int index_arr[RNA_MAX_ARRAY_DIMENSION] = {0};
int len[RNA_MAX_ARRAY_DIMENSION];
const int dim = RNA_property_array_dimension(ptr, prop, len);
int i;
*r_index = -1;
/* end of path, ok */
if (!(**path)) {
return true;
}
for (i = 0; i < dim; i++) {
int temp_index = -1;
char *token;
/* multi index resolve */
if (**path == '[') {
bool quoted;
token = rna_path_token_in_brackets(path, fixedbuf, sizeof(fixedbuf), &quoted);
if (token == NULL) {
/* invalid syntax blah[] */
return false;
}
/* check for "" to see if it is a string */
if (quoted) {
temp_index = RNA_property_array_item_index(prop, *token);
}
else {
/* otherwise do int lookup */
temp_index = atoi(token);
if (temp_index == 0 && (token[0] != '0' || token[1] != '\0')) {
if (token != fixedbuf) {
MEM_freeN(token);
}
return false;
}
}
}
else if (dim == 1) {
/* location.x || scale.X, single dimension arrays only */
token = rna_path_token(path, fixedbuf, sizeof(fixedbuf));
if (token == NULL) {
/* invalid syntax blah. */
return false;
}
temp_index = RNA_property_array_item_index(prop, *token);
}
else {
/* just to avoid uninitialized pointer use */
token = fixedbuf;
}
if (token != fixedbuf) {
MEM_freeN(token);
}
/* out of range */
if (temp_index < 0 || temp_index >= len[i]) {
return false;
}
index_arr[i] = temp_index;
/* end multi index resolve */
}
/* arrays always contain numbers so further values are not valid */
if (**path) {
return false;
}
/* flatten index over all dimensions */
{
int totdim = 1;
int flat_index = 0;
for (i = dim - 1; i >= 0; i--) {
flat_index += index_arr[i] * totdim;
totdim *= len[i];
}
*r_index = flat_index;
}
return true;
}
/**
* Generic rna path parser.
*
* \note All parameters besides \a ptr and \a path are optional.
*
* \param ptr: The root of given RNA path.
* \param path: The RNA path.
* \param r_ptr: The final RNA data holding the last property in \a path.
* \param r_prop: The final property of \a r_ptr, from \a path.
* \param r_index: The final index in the \a r_prop, if defined by \a path.
* \param r_item_ptr: Only valid for Pointer and Collection, return the actual value of the
* pointer, or of the collection item.
* Mutually exclusive with \a eval_pointer option.
* \param r_elements: A list of \a PropertyElemRNA items(pairs of \a PointerRNA, \a PropertyRNA
* that represent the whole given \a path).
* \param eval_pointer: If \a true, and \a path leads to a Pointer property, or an item in a
* Collection property, \a r_ptr will be set to the value of that property,
* and \a r_prop will be NULL.
* Mutually exclusive with \a r_item_ptr.
*
* \return \a true on success, \a false if the path is somehow invalid.
*/
static bool rna_path_parse(const PointerRNA *ptr,
const char *path,
PointerRNA *r_ptr,
PropertyRNA **r_prop,
int *r_index,
PointerRNA *r_item_ptr,
ListBase *r_elements,
const bool eval_pointer)
{
BLI_assert(r_item_ptr == NULL || !eval_pointer);
PropertyRNA *prop;
PointerRNA curptr, nextptr;
PropertyElemRNA *prop_elem = NULL;
int index = -1;
char fixedbuf[256];
int type;
const bool do_item_ptr = r_item_ptr != NULL && !eval_pointer;
if (do_item_ptr) {
RNA_POINTER_INVALIDATE(&nextptr);
}
prop = NULL;
curptr = *ptr;
if (path == NULL || *path == '\0') {
return false;
}
while (*path) {
if (do_item_ptr) {
RNA_POINTER_INVALIDATE(&nextptr);
}
const bool use_id_prop = (*path == '[');
/* Custom property lookup: e.g. `C.object["someprop"]`. */
if (!curptr.data) {
return false;
}
/* look up property name in current struct */
bool quoted = false;
char *token = use_id_prop ?
rna_path_token_in_brackets(&path, fixedbuf, sizeof(fixedbuf), &quoted) :
rna_path_token(&path, fixedbuf, sizeof(fixedbuf));
if (!token) {
return false;
}
prop = NULL;
if (use_id_prop) { /* look up property name in current struct */
IDProperty *group = RNA_struct_idprops(&curptr, 0);
if (group && quoted) {
prop = (PropertyRNA *)IDP_GetPropertyFromGroup(group, token);
}
}
else {
prop = RNA_struct_find_property(&curptr, token);
}
if (token != fixedbuf) {
MEM_freeN(token);
}
if (!prop) {
return false;
}
if (r_elements) {
prop_elem = MEM_cnew<PropertyElemRNA>(__func__);
prop_elem->ptr = curptr;
prop_elem->prop = prop;
prop_elem->index = -1; /* index will be added later, if needed. */
BLI_addtail(r_elements, prop_elem);
}
type = RNA_property_type(prop);
/* now look up the value of this property if it is a pointer or
* collection, otherwise return the property rna so that the
* caller can read the value of the property itself */
switch (type) {
case PROP_POINTER: {
/* resolve pointer if further path elements follow
* or explicitly requested
*/
if (do_item_ptr || eval_pointer || *path != '\0') {
nextptr = RNA_property_pointer_get(&curptr, prop);
}
if (eval_pointer || *path != '\0') {
curptr = nextptr;
prop = NULL; /* now we have a PointerRNA, the prop is our parent so forget it */
index = -1;
}
break;
}
case PROP_COLLECTION: {
/* Resolve pointer if further path elements follow.
* Note that if path is empty, rna_path_parse_collection_key will do nothing anyway,
* so do_item_ptr is of no use in that case.
*/
if (*path) {
if (!rna_path_parse_collection_key(&path, &curptr, prop, &nextptr)) {
return false;
}
if (eval_pointer || *path != '\0') {
curptr = nextptr;
prop = NULL; /* now we have a PointerRNA, the prop is our parent so forget it */
index = -1;
}
}
break;
}
default:
if (r_index || prop_elem) {
if (!rna_path_parse_array_index(&path, &curptr, prop, &index)) {
return false;
}
if (prop_elem) {
prop_elem->index = index;
}
}
break;
}
}
if (r_ptr) {
*r_ptr = curptr;
}
if (r_prop) {
*r_prop = prop;
}
if (r_index) {
*r_index = index;
}
if (r_item_ptr && do_item_ptr) {
*r_item_ptr = nextptr;
}
if (prop_elem && (prop_elem->ptr.data != curptr.data || prop_elem->prop != prop ||
prop_elem->index != index)) {
prop_elem = MEM_cnew<PropertyElemRNA>(__func__);
prop_elem->ptr = curptr;
prop_elem->prop = prop;
prop_elem->index = index;
BLI_addtail(r_elements, prop_elem);
}
return true;
}
bool RNA_path_resolve(const PointerRNA *ptr,
const char *path,
PointerRNA *r_ptr,
PropertyRNA **r_prop)
{
if (!rna_path_parse(ptr, path, r_ptr, r_prop, NULL, NULL, NULL, true)) {
return false;
}
return r_ptr->data != NULL;
}
bool RNA_path_resolve_full(
const PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
{
if (!rna_path_parse(ptr, path, r_ptr, r_prop, r_index, NULL, NULL, true)) {
return false;
}
return r_ptr->data != NULL;
}
bool RNA_path_resolve_full_maybe_null(
const PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
{
return rna_path_parse(ptr, path, r_ptr, r_prop, r_index, NULL, NULL, true);
}
bool RNA_path_resolve_property(const PointerRNA *ptr,
const char *path,
PointerRNA *r_ptr,
PropertyRNA **r_prop)
{
if (!rna_path_parse(ptr, path, r_ptr, r_prop, NULL, NULL, NULL, false)) {
return false;
}
return r_ptr->data != NULL && *r_prop != NULL;
}
bool RNA_path_resolve_property_full(
const PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
{
if (!rna_path_parse(ptr, path, r_ptr, r_prop, r_index, NULL, NULL, false)) {
return false;
}
return r_ptr->data != NULL && *r_prop != NULL;
}
bool RNA_path_resolve_property_and_item_pointer(const PointerRNA *ptr,
const char *path,
PointerRNA *r_ptr,
PropertyRNA **r_prop,
PointerRNA *r_item_ptr)
{
if (!rna_path_parse(ptr, path, r_ptr, r_prop, NULL, r_item_ptr, NULL, false)) {
return false;
}
return r_ptr->data != NULL && *r_prop != NULL;
}
bool RNA_path_resolve_property_and_item_pointer_full(const PointerRNA *ptr,
const char *path,
PointerRNA *r_ptr,
PropertyRNA **r_prop,
int *r_index,
PointerRNA *r_item_ptr)
{
if (!rna_path_parse(ptr, path, r_ptr, r_prop, r_index, r_item_ptr, NULL, false)) {
return false;
}
return r_ptr->data != NULL && *r_prop != NULL;
}
bool RNA_path_resolve_elements(PointerRNA *ptr, const char *path, ListBase *r_elements)
{
return rna_path_parse(ptr, path, NULL, NULL, NULL, NULL, r_elements, false);
}
char *RNA_path_append(const char *path,
const PointerRNA *UNUSED(ptr),
PropertyRNA *prop,
int intkey,
const char *strkey)
{
DynStr *dynstr;
char *result;
dynstr = BLI_dynstr_new();
/* add .identifier */
if (path) {
BLI_dynstr_append(dynstr, path);
if (*path) {
BLI_dynstr_append(dynstr, ".");
}
}
BLI_dynstr_append(dynstr, RNA_property_identifier(prop));
const bool has_key = (intkey > -1) || (strkey != nullptr);
if (has_key && (RNA_property_type(prop) == PROP_COLLECTION)) {
/* add ["strkey"] or [intkey] */
BLI_dynstr_append(dynstr, "[");
if (strkey) {
const int strkey_esc_max_size = (strlen(strkey) * 2) + 1;
char *strkey_esc = static_cast<char *>(BLI_array_alloca(strkey_esc, strkey_esc_max_size));
BLI_str_escape(strkey_esc, strkey, strkey_esc_max_size);
BLI_dynstr_append(dynstr, "\"");
BLI_dynstr_append(dynstr, strkey_esc);
BLI_dynstr_append(dynstr, "\"");
}
else {
char appendstr[128];
BLI_snprintf(appendstr, sizeof(appendstr), "%d", intkey);
BLI_dynstr_append(dynstr, appendstr);
}
BLI_dynstr_append(dynstr, "]");
}
result = BLI_dynstr_get_cstring(dynstr);
BLI_dynstr_free(dynstr);
return result;
}
/* Having both path append & back seems like it could be useful,
* this function isn't used at the moment. */
static UNUSED_FUNCTION_WITH_RETURN_TYPE(char *, RNA_path_back)(const char *path)
{
char fixedbuf[256];
const char *previous, *current;
char *result;
int i;
if (!path) {
return NULL;
}
previous = NULL;
current = path;
/* parse token by token until the end, then we back up to the previous
* position and strip of the next token to get the path one step back */
while (*current) {
char *token;
token = rna_path_token(&current, fixedbuf, sizeof(fixedbuf));
if (!token) {
return NULL;
}
if (token != fixedbuf) {
MEM_freeN(token);
}
/* in case of collection we also need to strip off [] */
bool quoted;
token = rna_path_token_in_brackets(&current, fixedbuf, sizeof(fixedbuf), &quoted);
if (token && token != fixedbuf) {
MEM_freeN(token);
}
if (!*current) {
break;
}
previous = current;
}
if (!previous) {
return NULL;
}
/* copy and strip off last token */
i = previous - path;
result = BLI_strdup(path);
if (i > 0 && result[i - 1] == '.') {
i--;
}
result[i] = 0;
return result;
}
const char *RNA_path_array_index_token_find(const char *rna_path, const PropertyRNA *array_prop)
{
if (array_prop != NULL) {
if (!ELEM(array_prop->type, PROP_BOOLEAN, PROP_INT, PROP_FLOAT)) {
BLI_assert(array_prop->arraydimension == 0);
return NULL;
}
if (array_prop->arraydimension == 0) {
return NULL;
}
}
/* Valid 'array part' of a rna path can only have '[', ']' and digit characters.
* It may have more than one of those (e.g. `[12][1]`) in case of multi-dimensional arrays. */
int64_t rna_path_len = (int64_t)strlen(rna_path);
if (rna_path[rna_path_len] != ']') {
return NULL;
}
const char *last_valid_index_token_start = NULL;
for (rna_path_len--; rna_path_len >= 0; rna_path_len--) {
switch (rna_path[rna_path_len]) {
case '[':
if (rna_path_len <= 0 || rna_path[rna_path_len - 1] != ']') {
return &rna_path[rna_path_len];
}
last_valid_index_token_start = &rna_path[rna_path_len];
rna_path_len--;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
default:
return last_valid_index_token_start;
}
}
return last_valid_index_token_start;
}
/* generic path search func
* if its needed this could also reference the IDProperty direct */
typedef struct IDP_Chain {
struct IDP_Chain *up; /* parent member, reverse and set to child for path conversion. */
const char *name;
int index;
} IDP_Chain;
static char *rna_idp_path_create(IDP_Chain *child_link)
{
DynStr *dynstr = BLI_dynstr_new();
char *path;
bool is_first = true;
int tot = 0;
IDP_Chain *link = child_link;
/* reverse the list */
IDP_Chain *link_prev;
link_prev = NULL;
while (link) {
IDP_Chain *link_next = link->up;
link->up = link_prev;
link_prev = link;
link = link_next;
tot++;
}
for (link = link_prev; link; link = link->up) {
/* pass */
if (link->index >= 0) {
BLI_dynstr_appendf(dynstr, is_first ? "%s[%d]" : ".%s[%d]", link->name, link->index);
}
else {
BLI_dynstr_appendf(dynstr, is_first ? "%s" : ".%s", link->name);
}
is_first = false;
}
path = BLI_dynstr_get_cstring(dynstr);
BLI_dynstr_free(dynstr);
if (*path == '\0') {
MEM_freeN(path);
path = NULL;
}
return path;
}
static char *rna_idp_path(PointerRNA *ptr,
IDProperty *haystack,
IDProperty *needle,
IDP_Chain *parent_link)
{
char *path = NULL;
IDP_Chain link;
IDProperty *iter;
int i;
BLI_assert(haystack->type == IDP_GROUP);
link.up = parent_link;
/* Always set both name and index, else a stale value might get used. */
link.name = NULL;
link.index = -1;
for (i = 0, iter = static_cast<IDProperty *>(haystack->data.group.first); iter;
iter = iter->next, i++) {
if (needle == iter) { /* found! */
link.name = iter->name;
link.index = -1;
path = rna_idp_path_create(&link);
break;
}
/* Early out in case the IDProperty type cannot contain RNA properties. */
if (!ELEM(iter->type, IDP_GROUP, IDP_IDPARRAY)) {
continue;
}
/* Ensure this is RNA. */
/* NOTE: `iter` might be a fully user-defined IDProperty (a.k.a. custom data), which name
* collides with an actual fully static RNA property of the same struct (which would then not
* be flagged with `PROP_IDPROPERTY`).
*
* That case must be ignored here, we only want to deal with runtime RNA properties stored in
* IDProps.
*
* See T84091. */
PropertyRNA *prop = RNA_struct_find_property(ptr, iter->name);
if (prop == NULL || (prop->flag & PROP_IDPROPERTY) == 0) {
continue;
}
if (iter->type == IDP_GROUP) {
if (prop->type == PROP_POINTER) {
PointerRNA child_ptr = RNA_property_pointer_get(ptr, prop);
if (RNA_pointer_is_null(&child_ptr)) {
/* Pointer ID prop might be a 'leaf' in the IDProp group hierarchy, in which case a NULL
* value is perfectly valid. Just means it won't match the searched needle. */
continue;
}
link.name = iter->name;
link.index = -1;
if ((path = rna_idp_path(&child_ptr, iter, needle, &link))) {
break;
}
}
}
else if (iter->type == IDP_IDPARRAY) {
if (prop->type == PROP_COLLECTION) {
IDProperty *array = IDP_IDPArray(iter);
if (needle >= array && needle < (iter->len + array)) { /* found! */
link.name = iter->name;
link.index = (int)(needle - array);
path = rna_idp_path_create(&link);
break;
}
int j;
link.name = iter->name;
for (j = 0; j < iter->len; j++, array++) {
PointerRNA child_ptr;
if (RNA_property_collection_lookup_int(ptr, prop, j, &child_ptr)) {
if (RNA_pointer_is_null(&child_ptr)) {
/* Array item ID prop might be a 'leaf' in the IDProp group hierarchy, in which case
* a NULL value is perfectly valid. Just means it won't match the searched needle. */
continue;
}
link.index = j;
if ((path = rna_idp_path(&child_ptr, array, needle, &link))) {
break;
}
}
}
if (path) {
break;
}
}
}
}
return path;
}
char *RNA_path_from_struct_to_idproperty(PointerRNA *ptr, IDProperty *needle)
{
IDProperty *haystack = RNA_struct_idprops(ptr, false);
if (haystack) { /* can fail when called on bones */
return rna_idp_path(ptr, haystack, needle, NULL);
}
return NULL;
}
static char *rna_path_from_ID_to_idpgroup(const PointerRNA *ptr)
{
PointerRNA id_ptr;
BLI_assert(ptr->owner_id != NULL);
/* TODO: Support Bones/PoseBones. no pointers stored to the bones from here, only the ID.
* See example in T25746.
* Unless this is added only way to find this is to also search
* all bones and pose bones of an armature or object.
*/
RNA_id_pointer_create(ptr->owner_id, &id_ptr);
return RNA_path_from_struct_to_idproperty(&id_ptr, static_cast<IDProperty *>(ptr->data));
}
ID *RNA_find_real_ID_and_path(Main *bmain, ID *id, const char **r_path)
{
if (r_path) {
*r_path = "";
}
if ((id == NULL) || (id->flag & LIB_EMBEDDED_DATA) == 0) {
return id;
}
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
if (r_path) {
switch (GS(id->name)) {
case ID_NT:
*r_path = "node_tree";
break;
case ID_GR:
*r_path = "collection";
break;
default:
BLI_assert_msg(0, "Missing handling of embedded id type.");
}
}
if (id_type->owner_get == NULL) {
BLI_assert_msg(0, "Missing handling of embedded id type.");
return id;
}
return id_type->owner_get(bmain, id);
}
static char *rna_prepend_real_ID_path(Main *bmain, ID *id, char *path, ID **r_real_id)
{
if (r_real_id != NULL) {
*r_real_id = NULL;
}
const char *prefix;
ID *real_id = RNA_find_real_ID_and_path(bmain, id, &prefix);
if (r_real_id != NULL) {
*r_real_id = real_id;
}
if (path != NULL) {
char *new_path = NULL;
if (real_id) {
if (prefix[0]) {
new_path = BLI_sprintfN("%s%s%s", prefix, path[0] == '[' ? "" : ".", path);
}
else {
return path;
}
}
MEM_freeN(path);
return new_path;
}
return prefix[0] != '\0' ? BLI_strdup(prefix) : NULL;
}
char *RNA_path_from_ID_to_struct(const PointerRNA *ptr)
{
char *ptrpath = NULL;
if (!ptr->owner_id || !ptr->data) {
return NULL;
}
if (!RNA_struct_is_ID(ptr->type)) {
if (ptr->type->path) {
/* if type has a path to some ID, use it */
ptrpath = ptr->type->path((PointerRNA *)ptr);
}
else if (ptr->type->nested && RNA_struct_is_ID(ptr->type->nested)) {
PointerRNA parentptr;
PropertyRNA *userprop;
/* find the property in the struct we're nested in that references this struct, and
* use its identifier as the first part of the path used...
*/
RNA_id_pointer_create(ptr->owner_id, &parentptr);
userprop = rna_struct_find_nested(&parentptr, ptr->type);
if (userprop) {
ptrpath = BLI_strdup(RNA_property_identifier(userprop));
}
else {
return NULL; /* can't do anything about this case yet... */
}
}
else if (RNA_struct_is_a(ptr->type, &RNA_PropertyGroup)) {
/* special case, easier to deal with here than in ptr->type->path() */
return rna_path_from_ID_to_idpgroup(ptr);
}
else {
return NULL;
}
}
return ptrpath;
}
char *RNA_path_from_real_ID_to_struct(Main *bmain, const PointerRNA *ptr, struct ID **r_real)
{
char *path = RNA_path_from_ID_to_struct(ptr);
/* NULL path is valid in that case, when given struct is an ID one... */
return rna_prepend_real_ID_path(bmain, ptr->owner_id, path, r_real);
}
static void rna_path_array_multi_from_flat_index(const int dimsize[RNA_MAX_ARRAY_LENGTH],
const int totdims,
const int index_dim,
int index,
int r_index_multi[RNA_MAX_ARRAY_LENGTH])
{
int dimsize_step[RNA_MAX_ARRAY_LENGTH + 1];
int i = totdims - 1;
dimsize_step[i + 1] = 1;
dimsize_step[i] = dimsize[i];
while (--i != -1) {
dimsize_step[i] = dimsize[i] * dimsize_step[i + 1];
}
while (++i != index_dim) {
int index_round = index / dimsize_step[i + 1];
r_index_multi[i] = index_round;
index -= (index_round * dimsize_step[i + 1]);
}
BLI_assert(index == 0);
}
static void rna_path_array_multi_string_from_flat_index(const PointerRNA *ptr,
PropertyRNA *prop,
int index_dim,
int index,
char *index_str,
int index_str_len)
{
int dimsize[RNA_MAX_ARRAY_LENGTH];
int totdims = RNA_property_array_dimension(ptr, prop, dimsize);
int index_multi[RNA_MAX_ARRAY_LENGTH];
rna_path_array_multi_from_flat_index(dimsize, totdims, index_dim, index, index_multi);
for (int i = 0, offset = 0; (i < index_dim) && (offset < index_str_len); i++) {
offset += BLI_snprintf_rlen(
&index_str[offset], index_str_len - offset, "[%d]", index_multi[i]);
}
}
char *RNA_path_from_ID_to_property_index(const PointerRNA *ptr,
PropertyRNA *prop,
int index_dim,
int index)
{
const bool is_rna = (prop->magic == RNA_MAGIC);
const char *propname;
char *ptrpath, *path;
if (!ptr->owner_id || !ptr->data) {
return NULL;
}
/* path from ID to the struct holding this property */
ptrpath = RNA_path_from_ID_to_struct(ptr);
propname = RNA_property_identifier(prop);
/* support indexing w/ multi-dimensional arrays */
char index_str[RNA_MAX_ARRAY_LENGTH * 12 + 1];
if (index_dim == 0) {
index_str[0] = '\0';
}
else {
rna_path_array_multi_string_from_flat_index(
ptr, prop, index_dim, index, index_str, sizeof(index_str));
}
if (ptrpath) {
if (is_rna) {
path = BLI_sprintfN("%s.%s%s", ptrpath, propname, index_str);
}
else {
char propname_esc[MAX_IDPROP_NAME * 2];
BLI_str_escape(propname_esc, propname, sizeof(propname_esc));
path = BLI_sprintfN("%s[\"%s\"]%s", ptrpath, propname_esc, index_str);
}
MEM_freeN(ptrpath);
}
else if (RNA_struct_is_ID(ptr->type)) {
if (is_rna) {
path = BLI_sprintfN("%s%s", propname, index_str);
}
else {
char propname_esc[MAX_IDPROP_NAME * 2];
BLI_str_escape(propname_esc, propname, sizeof(propname_esc));
path = BLI_sprintfN("[\"%s\"]%s", propname_esc, index_str);
}
}
else {
path = NULL;
}
return path;
}
char *RNA_path_from_ID_to_property(const PointerRNA *ptr, PropertyRNA *prop)
{
return RNA_path_from_ID_to_property_index(ptr, prop, 0, -1);
}
char *RNA_path_from_real_ID_to_property_index(Main *bmain,
const PointerRNA *ptr,
PropertyRNA *prop,
int index_dim,
int index,
ID **r_real_id)
{
char *path = RNA_path_from_ID_to_property_index(ptr, prop, index_dim, index);
/* NULL path is always an error here, in that case do not return the 'fake ID from real ID' part
* of the path either. */
return path != NULL ? rna_prepend_real_ID_path(bmain, ptr->owner_id, path, r_real_id) : NULL;
}
char *RNA_path_resolve_from_type_to_property(const PointerRNA *ptr,
PropertyRNA *prop,
const StructRNA *type)
{
/* Try to recursively find an "type"'d ancestor,
* to handle situations where path from ID is not enough. */
PointerRNA idptr;
ListBase path_elems = {NULL};
char *path = NULL;
char *full_path = RNA_path_from_ID_to_property(ptr, prop);
if (full_path == NULL) {
return NULL;
}
RNA_id_pointer_create(ptr->owner_id, &idptr);
if (RNA_path_resolve_elements(&idptr, full_path, &path_elems)) {
LISTBASE_FOREACH_BACKWARD (PropertyElemRNA *, prop_elem, &path_elems) {
if (RNA_struct_is_a(prop_elem->ptr.type, type)) {
char *ref_path = RNA_path_from_ID_to_struct(&prop_elem->ptr);
if (ref_path) {
path = BLI_strdup(full_path + strlen(ref_path) + 1); /* +1 for the linking '.' */
MEM_freeN(ref_path);
}
break;
}
}
BLI_freelistN(&path_elems);
}
MEM_freeN(full_path);
return path;
}
char *RNA_path_full_ID_py(Main *bmain, ID *id)
{
const char *path;
ID *id_real = RNA_find_real_ID_and_path(bmain, id, &path);
if (id_real) {
id = id_real;
}
else {
path = "";
}
char lib_filepath_esc[(sizeof(id->lib->filepath) * 2) + 4];
if (ID_IS_LINKED(id)) {
int ofs = 0;
memcpy(lib_filepath_esc, ", \"", 3);
ofs += 3;
ofs += BLI_str_escape(lib_filepath_esc + ofs, id->lib->filepath, sizeof(lib_filepath_esc));
memcpy(lib_filepath_esc + ofs, "\"", 2);
}
else {
lib_filepath_esc[0] = '\0';
}
char id_esc[(sizeof(id->name) - 2) * 2];
BLI_str_escape(id_esc, id->name + 2, sizeof(id_esc));
return BLI_sprintfN("bpy.data.%s[\"%s\"%s]%s%s",
BKE_idtype_idcode_to_name_plural(GS(id->name)),
id_esc,
lib_filepath_esc,
path[0] ? "." : "",
path);
}
char *RNA_path_full_struct_py(Main *bmain, const PointerRNA *ptr)
{
char *id_path;
char *data_path;
char *ret;
if (!ptr->owner_id) {
return NULL;
}
/* never fails */
id_path = RNA_path_full_ID_py(bmain, ptr->owner_id);
data_path = RNA_path_from_ID_to_struct(ptr);
/* XXX data_path may be NULL (see T36788),
* do we want to get the 'bpy.data.foo["bar"].(null)' stuff? */
ret = BLI_sprintfN("%s.%s", id_path, data_path);
if (data_path) {
MEM_freeN(data_path);
}
MEM_freeN(id_path);
return ret;
}
char *RNA_path_full_property_py_ex(
Main *bmain, const PointerRNA *ptr, PropertyRNA *prop, int index, bool use_fallback)
{
char *id_path;
const char *data_delim;
const char *data_path;
bool data_path_free;
char *ret;
if (!ptr->owner_id) {
return NULL;
}
/* never fails */
id_path = RNA_path_full_ID_py(bmain, ptr->owner_id);
data_path = RNA_path_from_ID_to_property(ptr, prop);
if (data_path) {
data_delim = (data_path[0] == '[') ? "" : ".";
data_path_free = true;
}
else {
if (use_fallback) {
/* Fuzzy fallback. Be explicit in our ignorance. */
data_path = RNA_property_identifier(prop);
data_delim = " ... ";
}
else {
data_delim = ".";
}
data_path_free = false;
}
if ((index == -1) || (RNA_property_array_check(prop) == false)) {
ret = BLI_sprintfN("%s%s%s", id_path, data_delim, data_path);
}
else {
ret = BLI_sprintfN("%s%s%s[%d]", id_path, data_delim, data_path, index);
}
MEM_freeN(id_path);
if (data_path_free) {
MEM_freeN((void *)data_path);
}
return ret;
}
char *RNA_path_full_property_py(Main *bmain, const PointerRNA *ptr, PropertyRNA *prop, int index)
{
return RNA_path_full_property_py_ex(bmain, ptr, prop, index, false);
}
char *RNA_path_struct_property_py(PointerRNA *ptr, PropertyRNA *prop, int index)
{
char *data_path;
char *ret;
if (!ptr->owner_id) {
return NULL;
}
data_path = RNA_path_from_ID_to_property(ptr, prop);
if (data_path == NULL) {
/* This may not be an ID at all, check for simple when pointer owns property.
* TODO: more complex nested case. */
if (!RNA_struct_is_ID(ptr->type)) {
const char *prop_identifier = RNA_property_identifier(prop);
if (RNA_struct_find_property(ptr, prop_identifier) == prop) {
data_path = BLI_strdup(prop_identifier);
}
}
}
if ((index == -1) || (RNA_property_array_check(prop) == false)) {
ret = BLI_strdup(data_path);
}
else {
ret = BLI_sprintfN("%s[%d]", data_path, index);
}
if (data_path) {
MEM_freeN(data_path);
}
return ret;
}
char *RNA_path_property_py(const PointerRNA *UNUSED(ptr), PropertyRNA *prop, int index)
{
const bool is_rna = (prop->magic == RNA_MAGIC);
const char *propname = RNA_property_identifier(prop);
char *ret;
if ((index == -1) || (RNA_property_array_check(prop) == false)) {
if (is_rna) {
ret = BLI_strdup(propname);
}
else {
char propname_esc[MAX_IDPROP_NAME * 2];
BLI_str_escape(propname_esc, propname, sizeof(propname_esc));
ret = BLI_sprintfN("[\"%s\"]", propname_esc);
}
}
else {
if (is_rna) {
ret = BLI_sprintfN("%s[%d]", propname, index);
}
else {
char propname_esc[MAX_IDPROP_NAME * 2];
BLI_str_escape(propname_esc, propname, sizeof(propname_esc));
ret = BLI_sprintfN("[\"%s\"][%d]", propname_esc, index);
}
}
return ret;
}