Changeset View
Standalone View
source/blender/blenkernel/intern/attribute.cc
| Show First 20 Lines • Show All 135 Lines • ▼ Show 20 Lines | bool BKE_id_attributes_supported(const ID *id) | ||||
| return false; | return false; | ||||
| } | } | ||||
| bool BKE_attribute_allow_procedural_access(const char *attribute_name) | bool BKE_attribute_allow_procedural_access(const char *attribute_name) | ||||
| { | { | ||||
| return blender::bke::allow_procedural_attribute_access(attribute_name); | return blender::bke::allow_procedural_attribute_access(attribute_name); | ||||
| } | } | ||||
| static bool bke_id_attribute_rename_if_exists(ID *id, | |||||
HooglyBoogly: Looks like this `BKE_id_attribute_rename_all` function is just to recover from the state where… | |||||
| const char *old_name, | |||||
| const char *new_name, | |||||
| ReportList *reports) | |||||
| { | |||||
| CustomDataLayer *layer = BKE_id_attribute_search( | |||||
| id, old_name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); | |||||
| if (layer == nullptr) { | |||||
| return false; | |||||
| } | |||||
| return BKE_id_attribute_rename(id, old_name, new_name, reports); | |||||
| } | |||||
| bool BKE_id_attribute_rename(ID *id, | bool BKE_id_attribute_rename(ID *id, | ||||
| const char *old_name, | const char *old_name, | ||||
| const char *new_name, | const char *new_name, | ||||
| ReportList *reports) | ReportList *reports) | ||||
| { | { | ||||
| using namespace blender; | using namespace blender; | ||||
| if (BKE_id_attribute_required(id, old_name)) { | if (BKE_id_attribute_required(id, old_name)) { | ||||
| BLI_assert_msg(0, "Required attribute name is not editable"); | BLI_assert_msg(0, "Required attribute name is not editable"); | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (STREQ(new_name, "")) { | if (STREQ(new_name, "")) { | ||||
| BKE_report(reports, RPT_ERROR, "Attribute name can not be empty"); | BKE_report(reports, RPT_ERROR, "Attribute name can not be empty"); | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (STREQ(old_name, new_name)) { | |||||
| /* NOTE: Checking if the new name matches the old name only makes sense when the name | |||||
| * is clamped to it's maximum length, otherwise assigning an over-long name multiple times | |||||
| * will add `.001` suffix unnecessarily. */ | |||||
| { | |||||
| const int maxlength = CustomData_name_max_length_calc(new_name); | |||||
| /* NOTE: A function that performs a clamped comparison without copying would be handy here. */ | |||||
| char new_name_clamped[MAX_CUSTOMDATA_LAYER_NAME]; | |||||
| BLI_strncpy_utf8(new_name_clamped, new_name, maxlength); | |||||
| if (STREQ(old_name, new_name_clamped)) { | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | |||||
| CustomDataLayer *layer = BKE_id_attribute_search( | CustomDataLayer *layer = BKE_id_attribute_search( | ||||
| id, old_name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); | id, old_name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); | ||||
| if (layer == nullptr) { | if (layer == nullptr) { | ||||
| BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry"); | BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry"); | ||||
| return false; | return false; | ||||
| } | } | ||||
| char result_name[MAX_CUSTOMDATA_LAYER_NAME]; | char result_name[MAX_CUSTOMDATA_LAYER_NAME]; | ||||
| BKE_id_attribute_calc_unique_name(id, new_name, result_name); | BKE_id_attribute_calc_unique_name(id, new_name, result_name); | ||||
Done Inline ActionsThis "first character is ." test isn't really testing for "dependent layers" (see allow_procedural_attribute_access) Either way, we shouldn't disallow this at such a low level. For example, this will prevent users of the Python API from renaming these layers, even if they know what they're doing. HooglyBoogly: This "first character is `.`" test isn't really testing for "dependent layers" (see… | |||||
Done Inline ActionsSo maybe it shoud be disallowed on a gui level... Or maybe just not disallowed at all, now that on-demand layer creation is in deleting a layer doesn't crash anymore so... (edit) decided to just not disallow it at all. Baardaap: So maybe it shoud be disallowed on a gui level...
Or maybe just not disallowed at all, now… | |||||
Done Inline ActionsThe layers should be hidden from the attribute list with the allow_procedural_attribute_access function, that should take care of the UI level HooglyBoogly: The layers should be hidden from the attribute list with the… | |||||
Done Inline Actionsright now that function disallows anything starting with .selection. should I add all suffixes I use there (so .vs, .es and .pn) ? Or should be just hide everything starting with a .? I'd personally like the second thing better. it seems rather random if only certain prefixes get hidden. Also we'd probably need some warning on (manual) attribute creation from the ui, because right now I can create hidden attributes , which is rather counterintuitive. Baardaap: right now that function disallows anything starting with .selection. should I add all suffixes… | |||||
Done Inline ActionsI think I'd recommend testing suffixes. For now I'm not sure we should completely prevent users from using names that start with a period, there may be some reason to do that in the future (or other software does it, hard to know!) HooglyBoogly: I think I'd recommend testing suffixes. For now I'm not sure we should completely prevent users… | |||||
Done Inline Actionscurrently it test a prefix, so I'll change my sublayer naming scheme to use a prefix as well. and then test multiple prefixes. I had chosen suffixes because of searching, but if they're hidden that doesn't matter anyway. I had made my prefixes as short as possible because of the limited layer name length, but maybe I should make them a little bit longer to prevent accidental clashes... Whatever system we use, there needs to be something to prevent creating a hidden layer from the gui, because
Maybe it's best if I try to come up with a separate patch which unifies the hiding and the prevention of creating from the gui? Baardaap: currently it test a prefix, so I'll change my sublayer naming scheme to use a prefix as well. | |||||
| if (layer->type == CD_PROP_FLOAT2 && GS(id->name) == ID_ME) { | |||||
Done Inline ActionsComment formatting. HooglyBoogly: Comment formatting.
Also, putting comments on a separate line is more standard code style in… | |||||
| /* Rename UV sub-attributes. */ | |||||
| char buffer_src[MAX_CUSTOMDATA_LAYER_NAME]; | |||||
| char buffer_dst[MAX_CUSTOMDATA_LAYER_NAME]; | |||||
| bke_id_attribute_rename_if_exists(id, | |||||
| BKE_uv_map_vert_select_name_get(layer->name, buffer_src), | |||||
| BKE_uv_map_vert_select_name_get(result_name, buffer_dst), | |||||
| reports); | |||||
| bke_id_attribute_rename_if_exists(id, | |||||
| BKE_uv_map_edge_select_name_get(layer->name, buffer_src), | |||||
| BKE_uv_map_edge_select_name_get(result_name, buffer_dst), | |||||
| reports); | |||||
| bke_id_attribute_rename_if_exists(id, | |||||
| BKE_uv_map_pin_name_get(layer->name, buffer_src), | |||||
| BKE_uv_map_pin_name_get(result_name, buffer_dst), | |||||
| reports); | |||||
| } | |||||
| if (StringRef(old_name) == BKE_id_attributes_active_color_name(id)) { | if (StringRef(old_name) == BKE_id_attributes_active_color_name(id)) { | ||||
| BKE_id_attributes_active_color_set(id, result_name); | BKE_id_attributes_active_color_set(id, result_name); | ||||
| } | } | ||||
| if (StringRef(old_name) == BKE_id_attributes_default_color_name(id)) { | if (StringRef(old_name) == BKE_id_attributes_default_color_name(id)) { | ||||
| BKE_id_attributes_default_color_set(id, result_name); | BKE_id_attributes_default_color_set(id, result_name); | ||||
| } | } | ||||
| BLI_strncpy_utf8(layer->name, result_name, sizeof(layer->name)); | BLI_strncpy_utf8(layer->name, result_name, sizeof(layer->name)); | ||||
| Show All 28 Lines | static bool unique_name_cb(void *arg, const char *name) | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| bool BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname) | bool BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname) | ||||
| { | { | ||||
| AttrUniqueData data{id}; | AttrUniqueData data{id}; | ||||
| const int maxlength = CustomData_name_max_length_calc(name); | |||||
| /* Set default name if none specified. | /* Set default name if none specified. | ||||
| * NOTE: We only call IFACE_() if needed to avoid locale lookup overhead. */ | * NOTE: We only call IFACE_() if needed to avoid locale lookup overhead. */ | ||||
Done Inline ActionsSTRPREFIX macro could be used instead of STREQLEN, avoids assumptions about length of defines. campbellbarton: `STRPREFIX` macro could be used instead of `STREQLEN`, avoids assumptions about length of… | |||||
| if (!name || name[0] == '\0') { | if (!name || name[0] == '\0') { | ||||
| BLI_strncpy(outname, IFACE_("Attribute"), MAX_CUSTOMDATA_LAYER_NAME); | BLI_strncpy(outname, IFACE_("Attribute"), maxlength); | ||||
| } | } | ||||
| else { | else { | ||||
| BLI_strncpy_utf8(outname, name, MAX_CUSTOMDATA_LAYER_NAME); | BLI_strncpy_utf8(outname, name, maxlength); | ||||
| } | } | ||||
| return BLI_uniquename_cb( | return BLI_uniquename_cb(unique_name_cb, &data, nullptr, '.', outname, maxlength); | ||||
| unique_name_cb, &data, nullptr, '.', outname, MAX_CUSTOMDATA_LAYER_NAME); | |||||
| } | } | ||||
| CustomDataLayer *BKE_id_attribute_new( | CustomDataLayer *BKE_id_attribute_new( | ||||
| ID *id, const char *name, const int type, const eAttrDomain domain, ReportList *reports) | ID *id, const char *name, const int type, const eAttrDomain domain, ReportList *reports) | ||||
| { | { | ||||
| using namespace blender::bke; | using namespace blender::bke; | ||||
| DomainInfo info[ATTR_DOMAIN_NUM]; | DomainInfo info[ATTR_DOMAIN_NUM]; | ||||
| get_domains(id, info); | get_domains(id, info); | ||||
| Show All 22 Lines | CustomDataLayer *BKE_id_attribute_new( | ||||
| } | } | ||||
| attributes->add(uniquename, domain, eCustomDataType(type), AttributeInitDefaultValue()); | attributes->add(uniquename, domain, eCustomDataType(type), AttributeInitDefaultValue()); | ||||
| const int index = CustomData_get_named_layer_index(customdata, type, uniquename); | const int index = CustomData_get_named_layer_index(customdata, type, uniquename); | ||||
| return (index == -1) ? nullptr : &(customdata->layers[index]); | return (index == -1) ? nullptr : &(customdata->layers[index]); | ||||
| } | } | ||||
| static void bke_id_attribute_copy_if_exists(ID *id, const char *srcname, const char *dstname) | |||||
| { | |||||
| using namespace blender::bke; | |||||
| std::optional<MutableAttributeAccessor> attributes = get_attribute_accessor_for_write(*id); | |||||
| if (!attributes) { | |||||
| return; | |||||
| } | |||||
| GAttributeReader src = attributes->lookup(srcname); | |||||
| if (!src) { | |||||
| return; | |||||
| } | |||||
| const eCustomDataType type = cpp_type_to_custom_data_type(src.varray.type()); | |||||
| attributes->add(dstname, src.domain, type, AttributeInitVArray(src.varray)); | |||||
| } | |||||
| CustomDataLayer *BKE_id_attribute_duplicate(ID *id, const char *name, ReportList *reports) | CustomDataLayer *BKE_id_attribute_duplicate(ID *id, const char *name, ReportList *reports) | ||||
| { | { | ||||
| using namespace blender::bke; | using namespace blender::bke; | ||||
| char uniquename[MAX_CUSTOMDATA_LAYER_NAME]; | char uniquename[MAX_CUSTOMDATA_LAYER_NAME]; | ||||
| BKE_id_attribute_calc_unique_name(id, name, uniquename); | BKE_id_attribute_calc_unique_name(id, name, uniquename); | ||||
| if (GS(id->name) == ID_ME) { | if (GS(id->name) == ID_ME) { | ||||
| Mesh *mesh = reinterpret_cast<Mesh *>(id); | Mesh *mesh = reinterpret_cast<Mesh *>(id); | ||||
| Show All 13 Lines | CustomDataLayer *BKE_id_attribute_duplicate(ID *id, const char *name, ReportList *reports) | ||||
| if (!src) { | if (!src) { | ||||
| BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry"); | BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry"); | ||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| const eCustomDataType type = cpp_type_to_custom_data_type(src.varray.type()); | const eCustomDataType type = cpp_type_to_custom_data_type(src.varray.type()); | ||||
| attributes->add(uniquename, src.domain, type, AttributeInitVArray(src.varray)); | attributes->add(uniquename, src.domain, type, AttributeInitVArray(src.varray)); | ||||
| if (GS(id->name) == ID_ME && type == CD_PROP_FLOAT2) { | |||||
| /* Duplicate UV sub-attributes. */ | |||||
| char buffer_src[MAX_CUSTOMDATA_LAYER_NAME]; | |||||
| char buffer_dst[MAX_CUSTOMDATA_LAYER_NAME]; | |||||
| bke_id_attribute_copy_if_exists(id, | |||||
| BKE_uv_map_vert_select_name_get(name, buffer_src), | |||||
| BKE_uv_map_vert_select_name_get(uniquename, buffer_dst)); | |||||
| bke_id_attribute_copy_if_exists(id, | |||||
| BKE_uv_map_edge_select_name_get(name, buffer_src), | |||||
| BKE_uv_map_edge_select_name_get(uniquename, buffer_dst)); | |||||
| bke_id_attribute_copy_if_exists(id, | |||||
| BKE_uv_map_pin_name_get(name, buffer_src), | |||||
| BKE_uv_map_pin_name_get(uniquename, buffer_dst)); | |||||
| } | |||||
| return BKE_id_attribute_search(id, uniquename, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); | return BKE_id_attribute_search(id, uniquename, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); | ||||
| } | } | ||||
| bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports) | bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports) | ||||
| { | { | ||||
| using namespace blender; | using namespace blender; | ||||
| using namespace blender::bke; | using namespace blender::bke; | ||||
| if (!name || name[0] == '\0') { | if (!name || name[0] == '\0') { | ||||
| BKE_report(reports, RPT_ERROR, "The attribute name must not be empty"); | BKE_report(reports, RPT_ERROR, "The attribute name must not be empty"); | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (BKE_id_attribute_required(id, name)) { | if (BKE_id_attribute_required(id, name)) { | ||||
| BKE_report(reports, RPT_ERROR, "Attribute is required and can't be removed"); | BKE_report(reports, RPT_ERROR, "Attribute is required and can't be removed"); | ||||
| return false; | return false; | ||||
| } | } | ||||
| DomainInfo info[ATTR_DOMAIN_NUM]; | DomainInfo info[ATTR_DOMAIN_NUM]; | ||||
| get_domains(id, info); | get_domains(id, info); | ||||
| if (GS(id->name) == ID_ME) { | if (GS(id->name) == ID_ME) { | ||||
| Mesh *mesh = reinterpret_cast<Mesh *>(id); | Mesh *mesh = reinterpret_cast<Mesh *>(id); | ||||
| if (BMEditMesh *em = mesh->edit_mesh) { | if (BMEditMesh *em = mesh->edit_mesh) { | ||||
| for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) { | for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) { | ||||
| if (CustomData *data = info[domain].customdata) { | if (CustomData *data = info[domain].customdata) { | ||||
| int layer_index = CustomData_get_named_layer_index_notype(data, name); | |||||
| if (layer_index >= 0) { | |||||
| if (data->layers[layer_index].type == CD_PROP_FLOAT2) { | |||||
| /* free associated UV map bool layers */ | |||||
| char buffer_src[MAX_CUSTOMDATA_LAYER_NAME]; | |||||
| BM_data_layer_free_named( | |||||
| em->bm, data, BKE_uv_map_vert_select_name_get(name, buffer_src)); | |||||
| BM_data_layer_free_named( | |||||
| em->bm, data, BKE_uv_map_edge_select_name_get(name, buffer_src)); | |||||
| BM_data_layer_free_named(em->bm, data, BKE_uv_map_pin_name_get(name, buffer_src)); | |||||
| } | |||||
| } | |||||
| /* Because it's possible that name is owned by the layer and will be freed | |||||
| * when freeing the layer, do these checks before freeing. */ | |||||
| const bool is_active_color_attribute = name == StringRef(mesh->active_color_attribute); | |||||
| const bool is_default_color_attribute = name == StringRef(mesh->default_color_attribute); | |||||
| if (BM_data_layer_free_named(em->bm, data, name)) { | if (BM_data_layer_free_named(em->bm, data, name)) { | ||||
| if (name == StringRef(mesh->active_color_attribute)) { | if (is_active_color_attribute) { | ||||
| MEM_SAFE_FREE(mesh->active_color_attribute); | MEM_SAFE_FREE(mesh->active_color_attribute); | ||||
| } | } | ||||
| else if (name == StringRef(mesh->default_color_attribute)) { | else if (is_default_color_attribute) { | ||||
| MEM_SAFE_FREE(mesh->default_color_attribute); | MEM_SAFE_FREE(mesh->default_color_attribute); | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| std::optional<MutableAttributeAccessor> attributes = get_attribute_accessor_for_write(*id); | std::optional<MutableAttributeAccessor> attributes = get_attribute_accessor_for_write(*id); | ||||
| if (!attributes) { | if (!attributes) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (GS(id->name) == ID_ME) { | |||||
| std::optional<blender::bke::AttributeMetaData> metadata = attributes->lookup_meta_data(name); | |||||
| if (metadata->data_type == CD_PROP_FLOAT2) { | |||||
| /* remove UV sub-attributes. */ | |||||
| char buffer_src[MAX_CUSTOMDATA_LAYER_NAME]; | |||||
| BKE_id_attribute_remove(id, BKE_uv_map_vert_select_name_get(name, buffer_src), reports); | |||||
| BKE_id_attribute_remove(id, BKE_uv_map_edge_select_name_get(name, buffer_src), reports); | |||||
| BKE_id_attribute_remove(id, BKE_uv_map_pin_name_get(name, buffer_src), reports); | |||||
| } | |||||
| } | |||||
| return attributes->remove(name); | return attributes->remove(name); | ||||
| } | } | ||||
| CustomDataLayer *BKE_id_attribute_find(const ID *id, | CustomDataLayer *BKE_id_attribute_find(const ID *id, | ||||
| const char *name, | const char *name, | ||||
| const int type, | const int type, | ||||
| const eAttrDomain domain) | const eAttrDomain domain) | ||||
| { | { | ||||
| ▲ Show 20 Lines • Show All 432 Lines • ▼ Show 20 Lines | case ID_CV: { | ||||
| break; | break; | ||||
| } | } | ||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| *((short *)r_id->name) = id_type; | *((short *)r_id->name) = id_type; | ||||
| } | } | ||||
| const char *BKE_uv_map_vert_select_name_get(const char *uv_map_name, char *buffer) | |||||
Done Inline ActionsWhy do you need both create and ensure implementations, why not just ensure? brecht: Why do you need both create and ensure implementations, why not just ensure? | |||||
Done Inline ActionsI could change it. Currently the ensure functions are only used when the UV layer itself already exists and the create function is used to create all layers in one go. But it could be merged I guess. Baardaap: I could change it. Currently the ensure functions are only used when the UV layer itself… | |||||
| { | |||||
| BLI_assert(strlen(UV_VERTSEL_NAME) == 2); | |||||
| BLI_assert(strlen(uv_map_name) < MAX_CUSTOMDATA_LAYER_NAME - 4); | |||||
Done Inline ActionsShould be MAX_CUSTOMDATA_LAYER_NAME - 4, because 63 was the previous supported maximum length (excluding the null byte). Or, MAX_CUSTOMDATA_LAYER_NAME may need to be increased. Otherwise the UI and Python both assert: e.g. blender -b --python-expr "setattr(__import__('bpy').context.object.data.uv_layers[0], 'name', 'x' * 63)"campbellbarton: Should be `MAX_CUSTOMDATA_LAYER_NAME - 4`, because 63 was the previous supported maximum length… | |||||
Done Inline ActionsOops, probably written at night when I couldn't decide between <= -5 or < -4 Baardaap: Oops, probably written at night when I couldn't decide between <= -5 or < -4 | |||||
| BLI_snprintf(buffer, MAX_CUSTOMDATA_LAYER_NAME, ".%s.%s", UV_VERTSEL_NAME, uv_map_name); | |||||
| return buffer; | |||||
| } | |||||
| const char *BKE_uv_map_edge_select_name_get(const char *uv_map_name, char *buffer) | |||||
| { | |||||
| BLI_assert(strlen(UV_EDGESEL_NAME) == 2); | |||||
| BLI_assert(strlen(uv_map_name) < MAX_CUSTOMDATA_LAYER_NAME - 4); | |||||
| BLI_snprintf(buffer, MAX_CUSTOMDATA_LAYER_NAME, ".%s.%s", UV_EDGESEL_NAME, uv_map_name); | |||||
| return buffer; | |||||
| } | |||||
| const char *BKE_uv_map_pin_name_get(const char *uv_map_name, char *buffer) | |||||
| { | |||||
| BLI_assert(strlen(UV_PINNED_NAME) == 2); | |||||
| BLI_assert(strlen(uv_map_name) < MAX_CUSTOMDATA_LAYER_NAME - 4); | |||||
| BLI_snprintf(buffer, MAX_CUSTOMDATA_LAYER_NAME, ".%s.%s", UV_PINNED_NAME, uv_map_name); | |||||
| return buffer; | |||||
| } | |||||
Looks like this BKE_id_attribute_rename_all function is just to recover from the state where there are duplicate names?
I think calling BKE_id_attribute_rename instead should be fine.
Either way, we shouldn't have to account for the case of matching names, since that's already an invalid state.