Files
test/source/blender/blenloader/intern/versioning_common.cc
Hans Goudey 89e3ba4e25 Mesh: Replace auto smooth with node group
Design task: #93551

This PR replaces the auto smooth option with a geometry nodes modifier
that sets the sharp edge attribute. This solves a fair number of long-
standing problems related to auto smooth, simplifies the process of
normal computation, and allows Blender to automatically choose between
face, vertex, and face corner normals based on the sharp edge and face
attributes.

Versioning adds a geometry node group to objects with meshes that had
auto-smooth enabled. The modifier can be applied, which also improves
performance.

Auto smooth is now unnecessary to get a combination of sharp and smooth
edges. In general workflows are changed a bit. Separate procedural and
destructive workflows are available. Custom normals can be used
immediately without turning on the removed auto smooth option.

**Procedural**

The node group asset "Smooth by Angle" is the main way to set sharp
normals based on the edge angle. It can be accessed directly in the add
modifier menu. Of course the modifier can be reordered, muted, or
applied like any other, or changed internally like any geometry nodes
modifier.

**Destructive**
Often the sharp edges don't need to be dynamic. This can give better
performance since edge angles don't need to be recalculated. In edit
mode the two operators "Select Sharp Edges" and "Mark Sharp" can be
used. In other modes, the "Shade Smooth by Angle" controls the edge
sharpness directly.

### Breaking API Changes
- `use_auto_smooth` is removed. Face corner normals are now used
  automatically   if there are mixed smooth vs. not smooth tags. Meshes
  now always use custom normals if they exist.
- In Cycles, the lack of the separate auto smooth state makes normals look
  triangulated when all faces are shaded smooth.
- `auto_smooth_angle` is removed. Replaced by a modifier (or operator)
  controlling the sharp edge attribute. This means the mesh itself
  (without an object) doesn't know anything about automatically smoothing
  by angle anymore.
- `create_normals_split`, `calc_normals_split`, and `free_normals_split`
  are removed, and are replaced by the simpler `Mesh.corner_normals`
  collection property. Since it gives access to the normals cache, it
  is automatically updated when relevant data changes.

Addons are updated here: https://projects.blender.org/blender/blender-addons/pulls/104609

### Tests
- `geo_node_curves_test_deform_curves_on_surface` has slightly different
   results because face corner normals are used instead of interpolated
   vertex normals.
- `bf_wavefront_obj_tests` has different export results for one file
  which mixed sharp and smooth faces without turning on auto smooth.
- `cycles_mesh_cpu` has one object which is completely flat shaded.
  Previously every edge was split before rendering, now it looks triangulated.

Pull Request: https://projects.blender.org/blender/blender/pulls/108014
2023-10-20 16:54:08 +02:00

535 lines
18 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
/* allow readfile to use deprecated functionality */
#define DNA_DEPRECATED_ALLOW
#include <cstring>
#include "DNA_node_types.h"
#include "DNA_screen_types.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BKE_animsys.h"
#include "BKE_idprop.h"
#include "BKE_ipo.h"
#include "BKE_lib_id.h"
#include "BKE_lib_override.hh"
#include "BKE_main.h"
#include "BKE_main_namemap.h"
#include "BKE_mesh_legacy_convert.hh"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "SEQ_sequencer.h"
#include "MEM_guardedalloc.h"
#include "BLO_readfile.h"
#include "readfile.hh"
#include "versioning_common.hh"
using blender::Map;
using blender::StringRef;
ARegion *do_versions_add_region_if_not_found(ListBase *regionbase,
int region_type,
const char *allocname,
int link_after_region_type)
{
ARegion *link_after_region = nullptr;
LISTBASE_FOREACH (ARegion *, region, regionbase) {
if (region->regiontype == region_type) {
return nullptr;
}
if (region->regiontype == link_after_region_type) {
link_after_region = region;
}
}
ARegion *new_region = static_cast<ARegion *>(MEM_callocN(sizeof(ARegion), allocname));
new_region->regiontype = region_type;
BLI_insertlinkafter(regionbase, link_after_region, new_region);
return new_region;
}
ARegion *do_versions_ensure_region(ListBase *regionbase,
int region_type,
const char *allocname,
int link_after_region_type)
{
ARegion *link_after_region = nullptr;
LISTBASE_FOREACH (ARegion *, region, regionbase) {
if (region->regiontype == region_type) {
return region;
}
if (region->regiontype == link_after_region_type) {
link_after_region = region;
}
}
ARegion *new_region = MEM_cnew<ARegion>(allocname);
new_region->regiontype = region_type;
BLI_insertlinkafter(regionbase, link_after_region, new_region);
return new_region;
}
ID *do_versions_rename_id(Main *bmain,
const short id_type,
const char *name_src,
const char *name_dst)
{
/* We can ignore libraries */
ListBase *lb = which_libbase(bmain, id_type);
ID *id = nullptr;
LISTBASE_FOREACH (ID *, idtest, lb) {
if (!ID_IS_LINKED(idtest)) {
if (STREQ(idtest->name + 2, name_src)) {
id = idtest;
}
if (STREQ(idtest->name + 2, name_dst)) {
return nullptr;
}
}
}
if (id != nullptr) {
BKE_main_namemap_remove_name(bmain, id, id->name + 2);
BLI_strncpy(id->name + 2, name_dst, sizeof(id->name) - 2);
/* We know it's unique, this just sorts. */
BLI_libblock_ensure_unique_name(bmain, id->name);
}
return id;
}
static void change_node_socket_name(ListBase *sockets, const char *old_name, const char *new_name)
{
LISTBASE_FOREACH (bNodeSocket *, socket, sockets) {
if (STREQ(socket->name, old_name)) {
STRNCPY(socket->name, new_name);
}
if (STREQ(socket->identifier, old_name)) {
STRNCPY(socket->identifier, new_name);
}
}
}
bool version_node_socket_is_used(bNodeSocket *sock)
{
return sock->flag & SOCK_IS_LINKED;
}
void version_node_socket_id_delim(bNodeSocket *socket)
{
StringRef name = socket->name;
StringRef id = socket->identifier;
if (!id.startswith(name)) {
/* We only need to affect the case where the identifier starts with the name. */
return;
}
StringRef id_number = id.drop_known_prefix(name);
if (id_number.is_empty()) {
/* The name was already unique, and didn't need numbers at the end for the id. */
return;
}
if (id_number.startswith(".")) {
socket->identifier[name.size()] = '_';
}
}
void version_node_socket_name(bNodeTree *ntree,
const int node_type,
const char *old_name,
const char *new_name)
{
for (bNode *node : ntree->all_nodes()) {
if (node->type == node_type) {
change_node_socket_name(&node->inputs, old_name, new_name);
change_node_socket_name(&node->outputs, old_name, new_name);
}
}
}
void version_node_input_socket_name(bNodeTree *ntree,
const int node_type,
const char *old_name,
const char *new_name)
{
for (bNode *node : ntree->all_nodes()) {
if (node->type == node_type) {
change_node_socket_name(&node->inputs, old_name, new_name);
}
}
}
void version_node_output_socket_name(bNodeTree *ntree,
const int node_type,
const char *old_name,
const char *new_name)
{
for (bNode *node : ntree->all_nodes()) {
if (node->type == node_type) {
change_node_socket_name(&node->outputs, old_name, new_name);
}
}
}
bNodeSocket *version_node_add_socket_if_not_exist(bNodeTree *ntree,
bNode *node,
int in_out,
int type,
int subtype,
const char *identifier,
const char *name)
{
bNodeSocket *sock = nodeFindSocket(node, eNodeSocketInOut(in_out), identifier);
if (sock != nullptr) {
return sock;
}
return nodeAddStaticSocket(
ntree, node, eNodeSocketInOut(in_out), type, subtype, identifier, name);
}
void version_node_id(bNodeTree *ntree, const int node_type, const char *new_name)
{
for (bNode *node : ntree->all_nodes()) {
if (node->type == node_type) {
if (!STREQ(node->idname, new_name)) {
STRNCPY(node->idname, new_name);
}
}
}
}
void version_node_socket_index_animdata(Main *bmain,
const int node_tree_type,
const int node_type,
const int socket_index_orig,
const int socket_index_offset,
const int total_number_of_sockets)
{
/* The for loop for the input ids is at the top level otherwise we lose the animation
* keyframe data. Not sure what causes that, so I (Sybren) moved the code here from
* versioning_290.cc as-is (structure-wise). */
for (int input_index = total_number_of_sockets - 1; input_index >= socket_index_orig;
input_index--)
{
FOREACH_NODETREE_BEGIN (bmain, ntree, owner_id) {
if (ntree->type != node_tree_type) {
continue;
}
for (bNode *node : ntree->all_nodes()) {
if (node->type != node_type) {
continue;
}
const size_t node_name_length = strlen(node->name);
const size_t node_name_escaped_max_length = (node_name_length * 2);
char *node_name_escaped = (char *)MEM_mallocN(node_name_escaped_max_length + 1,
"escaped name");
BLI_str_escape(node_name_escaped, node->name, node_name_escaped_max_length);
char *rna_path_prefix = BLI_sprintfN("nodes[\"%s\"].inputs", node_name_escaped);
const int new_index = input_index + socket_index_offset;
BKE_animdata_fix_paths_rename_all_ex(
bmain, owner_id, rna_path_prefix, nullptr, nullptr, input_index, new_index, false);
MEM_freeN(rna_path_prefix);
MEM_freeN(node_name_escaped);
}
}
FOREACH_NODETREE_END;
}
}
void version_socket_update_is_used(bNodeTree *ntree)
{
for (bNode *node : ntree->all_nodes()) {
LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
socket->flag &= ~SOCK_IS_LINKED;
}
LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
socket->flag &= ~SOCK_IS_LINKED;
}
}
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
link->fromsock->flag |= SOCK_IS_LINKED;
link->tosock->flag |= SOCK_IS_LINKED;
}
}
ARegion *do_versions_add_region(int regiontype, const char *name)
{
ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), name);
region->regiontype = regiontype;
return region;
}
void node_tree_relink_with_socket_id_map(bNodeTree &ntree,
bNode &old_node,
bNode &new_node,
const Map<std::string, std::string> &map)
{
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) {
if (link->tonode == &old_node) {
bNodeSocket *old_socket = link->tosock;
if (const std::string *new_identifier = map.lookup_ptr_as(old_socket->identifier)) {
bNodeSocket *new_socket = nodeFindSocket(&new_node, SOCK_IN, new_identifier->c_str());
link->tonode = &new_node;
link->tosock = new_socket;
old_socket->link = nullptr;
}
}
if (link->fromnode == &old_node) {
bNodeSocket *old_socket = link->fromsock;
if (const std::string *new_identifier = map.lookup_ptr_as(old_socket->identifier)) {
bNodeSocket *new_socket = nodeFindSocket(&new_node, SOCK_OUT, new_identifier->c_str());
link->fromnode = &new_node;
link->fromsock = new_socket;
old_socket->link = nullptr;
}
}
}
}
static blender::Vector<bNodeLink *> find_connected_links(bNodeTree *ntree, bNodeSocket *in_socket)
{
blender::Vector<bNodeLink *> links;
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
if (link->tosock == in_socket) {
links.append(link);
}
}
return links;
}
void add_realize_instances_before_socket(bNodeTree *ntree,
bNode *node,
bNodeSocket *geometry_socket)
{
BLI_assert(geometry_socket->type == SOCK_GEOMETRY);
blender::Vector<bNodeLink *> links = find_connected_links(ntree, geometry_socket);
for (bNodeLink *link : links) {
/* If the realize instances node is already before this socket, no need to continue. */
if (link->fromnode->type == GEO_NODE_REALIZE_INSTANCES) {
return;
}
bNode *realize_node = nodeAddStaticNode(nullptr, ntree, GEO_NODE_REALIZE_INSTANCES);
realize_node->parent = node->parent;
realize_node->locx = node->locx - 100;
realize_node->locy = node->locy;
nodeAddLink(ntree,
link->fromnode,
link->fromsock,
realize_node,
static_cast<bNodeSocket *>(realize_node->inputs.first));
link->fromnode = realize_node;
link->fromsock = static_cast<bNodeSocket *>(realize_node->outputs.first);
}
}
float *version_cycles_node_socket_float_value(bNodeSocket *socket)
{
bNodeSocketValueFloat *socket_data = static_cast<bNodeSocketValueFloat *>(socket->default_value);
return &socket_data->value;
}
float *version_cycles_node_socket_rgba_value(bNodeSocket *socket)
{
bNodeSocketValueRGBA *socket_data = static_cast<bNodeSocketValueRGBA *>(socket->default_value);
return socket_data->value;
}
float *version_cycles_node_socket_vector_value(bNodeSocket *socket)
{
bNodeSocketValueVector *socket_data = static_cast<bNodeSocketValueVector *>(
socket->default_value);
return socket_data->value;
}
IDProperty *version_cycles_properties_from_ID(ID *id)
{
IDProperty *idprop = IDP_GetProperties(id);
return (idprop) ? IDP_GetPropertyTypeFromGroup(idprop, "cycles", IDP_GROUP) : nullptr;
}
IDProperty *version_cycles_properties_from_view_layer(ViewLayer *view_layer)
{
IDProperty *idprop = view_layer->id_properties;
return (idprop) ? IDP_GetPropertyTypeFromGroup(idprop, "cycles", IDP_GROUP) : nullptr;
}
float version_cycles_property_float(IDProperty *idprop, const char *name, float default_value)
{
IDProperty *prop = IDP_GetPropertyTypeFromGroup(idprop, name, IDP_FLOAT);
return (prop) ? IDP_Float(prop) : default_value;
}
int version_cycles_property_int(IDProperty *idprop, const char *name, int default_value)
{
IDProperty *prop = IDP_GetPropertyTypeFromGroup(idprop, name, IDP_INT);
return (prop) ? IDP_Int(prop) : default_value;
}
void version_cycles_property_int_set(IDProperty *idprop, const char *name, int value)
{
IDProperty *prop = IDP_GetPropertyTypeFromGroup(idprop, name, IDP_INT);
if (prop) {
IDP_Int(prop) = value;
}
else {
IDPropertyTemplate val = {0};
val.i = value;
IDP_AddToGroup(idprop, IDP_New(IDP_INT, &val, name));
}
}
bool version_cycles_property_boolean(IDProperty *idprop, const char *name, bool default_value)
{
return version_cycles_property_int(idprop, name, default_value);
}
void version_cycles_property_boolean_set(IDProperty *idprop, const char *name, bool value)
{
version_cycles_property_int_set(idprop, name, value);
}
IDProperty *version_cycles_visibility_properties_from_ID(ID *id)
{
IDProperty *idprop = IDP_GetProperties(id);
return (idprop) ? IDP_GetPropertyTypeFromGroup(idprop, "cycles_visibility", IDP_GROUP) : nullptr;
}
void version_update_node_input(
bNodeTree *ntree,
FunctionRef<bool(bNode *)> check_node,
const char *socket_identifier,
FunctionRef<void(bNode *, bNodeSocket *)> update_input,
FunctionRef<void(bNode *, bNodeSocket *, bNode *, bNodeSocket *)> update_input_link)
{
bool need_update = false;
/* Iterate backwards from end so we don't encounter newly added links. */
LISTBASE_FOREACH_BACKWARD_MUTABLE (bNodeLink *, link, &ntree->links) {
/* Detect link to replace. */
bNode *fromnode = link->fromnode;
bNodeSocket *fromsock = link->fromsock;
bNode *tonode = link->tonode;
bNodeSocket *tosock = link->tosock;
if (!(tonode != nullptr && check_node(tonode) && STREQ(tosock->identifier, socket_identifier)))
{
continue;
}
/* Replace links with updated equivalent */
nodeRemLink(ntree, link);
update_input_link(fromnode, fromsock, tonode, tosock);
need_update = true;
}
/* Update sockets and/or their default values.
* Do this after the link update in case it changes the identifier. */
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (check_node(node)) {
bNodeSocket *input = nodeFindSocket(node, SOCK_IN, socket_identifier);
if (input != nullptr) {
update_input(node, input);
}
}
}
if (need_update) {
version_socket_update_is_used(ntree);
}
}
static bool blendfile_or_libraries_versions_atleast(Main *bmain,
const short versionfile,
const short subversionfile)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, versionfile, subversionfile)) {
return false;
}
LISTBASE_FOREACH (Library *, library, &bmain->libraries) {
if (!MAIN_VERSION_FILE_ATLEAST(library, versionfile, subversionfile)) {
return false;
}
}
return true;
}
void do_versions_after_setup(Main *new_bmain, BlendFileReadReport *reports)
{
/* WARNING: The code below may add IDs. These IDs _will_ be (by definition) conforming to current
* code's version already, and _must not_ be 'versionned' again.
*
* This means that when adding code here, _extreme_ care must be taken that it will not badly
* affect these 'modern' IDs potentially added by already existing processing.
*
* Adding code here should only be done in exceptional cases.
*
* Some further points to keep in mind:
* - While typically versioning order should be respected in code below (i.e. versioning
* affecting older versions should be done first), _this is not a hard rule_. And it should
* not be assumed older code must not be checked when adding newer code.
* - Do not rely strongly on versioning numbers here. This code may be run on data from
* different Blender versions (through the usage of linked data), and all existing data have
* already been processed through the whole do_version during blendfile reading itself. So
* decision to apply some versioning on some data should mostly rely on the data itself.
* - Unlike the regular do_version code, this one should _not_ be assumed as 'valid forever'.
* It is closer to the Editing or BKE code in that respect, changes to the logic or data
* model of an ID will require a careful update here as well.
*
* Another critical weakness of this code is that it is currently _not_ performed on data linked
* during an editing session, but only on data linked while reading a whole blendfile. This will
* have to be fixed at some point.
*/
/* NOTE: Version number is checked against Main version (i.e. current blend file version), AND
* the versions of all the linked libraries. */
if (!blendfile_or_libraries_versions_atleast(new_bmain, 250, 0)) {
do_versions_ipos_to_animato(new_bmain);
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 250, 0)) {
LISTBASE_FOREACH (Scene *, scene, &new_bmain->scenes) {
if (scene->ed) {
SEQ_doversion_250_sound_proxy_update(new_bmain, scene->ed);
}
}
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 302, 1)) {
BKE_lib_override_library_main_proxy_convert(new_bmain, reports);
/* Currently liboverride code can generate invalid namemap. This is a known issue, requires
* #107847 to be properly fixed. */
BKE_main_namemap_validate_and_fix(new_bmain);
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 302, 3)) {
/* Does not add any new IDs, but needs the full Main data-base. */
BKE_lib_override_library_main_hierarchy_root_ensure(new_bmain);
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 401, 2)) {
BKE_main_mesh_legacy_convert_auto_smooth(*new_bmain);
}
}