Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
namespace blender {
/** \file
* \ingroup blenloader
* \brief Utilities ensuring `.blend` file (i.e. Main)
* is in valid state during write and/or read process.
*/
struct Main;
struct ReportList;
/**
* Check (but do *not* fix) that all linked data-blocks are still valid
* (i.e. pointing to the right library).
*/
bool BLO_main_validate_libraries(Main *bmain, ReportList *reports);
/**
* * Check (and fix if needed) that shape key's 'from' pointer is valid.
*/
bool BLO_main_validate_shapekeys(Main *bmain, ReportList *reports);
/**
* Check that the `ID_FLAG_EMBEDDED_DATA_LIB_OVERRIDE` flag for embedded IDs actually matches
* reality of embedded IDs being used by a liboverride ID.
*
* This is needed because embedded IDs did not get their flag properly cleared when runtime data
* was split in `ID.tag`, which can create crashing situations in some rare cases, see #117795.
*/
void BLO_main_validate_embedded_liboverrides(Main *bmain, ReportList *reports);
/**
* Check that the `ID_FLAG_EMBEDDED_DATA` flag is correctly set for embedded IDs, and not for any
* Main ID.
*
* NOTE: It is unknown why/how this can happen, but there are some files out there that have e.g.
* Objects flagged as embedded data... See e.g. the `(Anim) Hero p23 for 2.blend` file from our
* cloud gallery (https://cloud.blender.org/p/gallery/5b642e25bf419c1042056fc6).
*/
void BLO_main_validate_embedded_flag(Main *bmain, ReportList *reports);
} // namespace blender

View File

@@ -0,0 +1,543 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*
* This file contains an API that allows different parts of Blender to define what data is stored
* in .blend files.
*
* Four callbacks have to be provided to fully implement .blend I/O for a piece of data. One of
* those is related to file writing and three for file reading. Reading requires multiple
* callbacks, due to the way linking between files works.
*
* Brief description of the individual callbacks:
* - Blend Write: Define which structs and memory buffers are saved.
* - Blend Read Data: Loads structs and memory buffers from file and updates pointers them.
* - Blend Read Lib: Updates pointers to ID data blocks.
* - Blend Expand: Defines which other data blocks should be loaded (possibly from other files).
* Note, this is now handled as part of the foreach-id iteration. This needs to be implemented
* for DNA data that has references to data-blocks.
*
* Each of these callbacks uses a different API functions.
*
* Some parts of Blender, e.g. modifiers, don't require you to implement all four callbacks.
* Instead only the first two are necessary. The other two are handled by general ID management. In
* the future, we might want to get rid of those two callbacks entirely, but for now they are
* necessary.
*/
#pragma once
#include <type_traits>
#include "DNA_ID.h"
#include "DNA_listBase.h"
#include "DNA_sdna_type_ids.hh"
#include "BLI_dynamic_stack_buffer.hh"
#include "BLI_function_ref.hh"
#include "BLI_implicit_sharing.hh"
#include "BLI_map.hh"
namespace blender {
class ImplicitSharingInfo;
struct BlendFileReadReport;
struct BlendLibReader;
struct ID;
struct ListBase;
struct Main;
struct WriteData;
struct FileData;
enum eReportType : uint16_t;
struct BlendWriter {
WriteData *wd = nullptr;
void write_struct_by_name(const char *struct_name, const void *data);
void write_struct_by_id(int struct_id, const void *data);
void write_struct_at_address_by_id(int struct_id, const void *address, const void *data);
void write_struct_at_address_by_id_with_filecode(int filecode,
int struct_id,
const void *address,
const void *data);
void write_struct_array_by_name(const char *struct_name, int64_t array_size, const void *data);
void write_struct_array_by_id(int struct_id, int64_t array_size, const void *data);
void write_struct_array_at_address_by_id(int struct_id,
int64_t array_size,
const void *address,
const void *data);
void write_struct_list_by_name(const char *struct_name, ListBase *list);
void write_struct_list_by_id(int struct_id, const ListBase *list);
/**
* Write raw data.
*
* \warning Avoid using this method if possible. There are only a very few cases in current
* code where it is actually needed (e.g. the ShapeKey's data, since its items size varies
* depending on the type of geometry owning it, see #shapekey_blend_write).
*
* \warning Data written with this call have no type information attached to them
* in the blend-file. The main consequence is that there will be no handling of endianness
* conversion for them in readfile code.
* Basic typed array methods (like #write_int8_array etc.) also use this
* internally, but if their matching read function is used to load the data (like
* #BLO_read_array), the read function will take care of endianness conversion.
*/
void write_raw(size_t size_in_bytes, const void *data);
/** Write typed arrays. */
void write_char_array(int64_t num, const char *data);
void write_int8_array(int64_t num, const int8_t *data);
void write_int16_array(int64_t num, const int16_t *data);
void write_uint8_array(int64_t num, const uint8_t *data);
void write_int32_array(int64_t num, const int32_t *data);
void write_uint32_array(int64_t num, const uint32_t *data);
void write_float_array(int64_t num, const float *data);
void write_double_array(int64_t num, const double *data);
void write_float3_array(int64_t num, const float *data);
void write_pointer_array(int64_t num, const void *data);
/** Write a null terminated string. */
void write_string(const char *data);
int struct_id_by_name(const char *struct_name) const;
template<typename T> void write_struct(const T *data)
{
this->write_struct_by_id(dna::sdna_struct_id_get<T>(), data);
}
template<typename T> void write_struct_cast(const void *data)
{
this->write_struct_by_id(dna::sdna_struct_id_get<T>(), data);
}
template<typename T> void write_struct_at_address(const void *address, const T *data)
{
this->write_struct_at_address_by_id(dna::sdna_struct_id_get<T>(), address, data);
}
template<typename T> void write_struct_at_address_cast(const void *address, const void *data)
{
this->write_struct_at_address_by_id(dna::sdna_struct_id_get<T>(), address, data);
}
template<typename T> void write_struct_array(const int64_t array_size, const T *data)
{
this->write_struct_array_by_id(dna::sdna_struct_id_get<T>(), array_size, data);
}
template<typename T> void write_struct_array_cast(const int64_t array_size, const void *data)
{
this->write_struct_array_by_id(dna::sdna_struct_id_get<T>(), array_size, data);
}
template<typename T>
void write_struct_array_at_address(const int64_t array_size, const void *address, const T *data)
{
this->write_struct_array_at_address_by_id(
dna::sdna_struct_id_get<T>(), array_size, address, data);
}
template<typename T> void write_struct_list(const ListBaseT<T> *list)
{
this->write_struct_list_by_id(dna::sdna_struct_id_get<T>(), list);
}
template<typename T> void write_id_struct(const void *id_address, const T *id)
{
this->write_struct_at_address_by_id_with_filecode(
GS(id_cast<const ID *>(id)->name), dna::sdna_struct_id_get<T>(), id_address, id);
}
};
struct BlendDataReader {
/** Pointer to private #FileData in readfile.cc. */
FileData *fd = nullptr;
/**
* The key is the old address id referencing shared data that's written to a file, typically an
* array. The corresponding value is the shared data at run-time.
*/
Map<uint64_t, ImplicitSharingInfoAndData> shared_data_by_stored_address;
};
struct BlendLibReader {
FileData *fd;
Main *main;
};
/* -------------------------------------------------------------------- */
/** \name Blend Write API
*
* Most functions fall into one of two categories. Either they write a DNA struct or a raw memory
* buffer to the .blend file.
*
* It is safe to pass NULL as data_ptr. In this case nothing will be stored.
*
* DNA Struct Writing
* ------------------
*
* Functions dealing with DNA structs begin with `BLO_write_struct_*`.
*
* DNA struct types can be identified in different ways:
* - Run-time Name: The name is provided as `const char *`.
* - Compile-time Name: The name is provided at compile time. This is more efficient.
* - Struct ID: Every DNA struct type has an integer ID that can be queried with
* #BlendWriter::struct_id_by_name. Providing this ID can be a useful optimization when many
* structs of the same type are stored AND if those structs are not in a continuous array.
*
* Often only a single instance of a struct is written at once. However, sometimes it is necessary
* to write arrays or linked lists. Separate functions for that are provided as well.
*
* There is a special macro for writing id structs: #BLO_write_id_struct.
* Those are handled differently from other structs.
*
* Raw Data Writing
* ----------------
*
* At the core there is #BlendWriter::write_raw, which can write arbitrary memory buffers to the
* file. The code that reads this data might have to correct its byte-order. For the common cases
* there are convenience functions that write and read arrays of simple types such as `int32`.
* Those will correct endianness automatically.
* \{ */
/**
* Specific code to prepare IDs to be written.
*
* Required for writing properly embedded IDs currently.
*
* \note Once there is a better generic handling of embedded IDs,
* this may go back to private code in `writefile.cc`.
*/
struct BLO_Write_IDBuffer {
private:
static constexpr int static_size = 8192;
DynamicStackBuffer<static_size> buffer_;
public:
BLO_Write_IDBuffer(ID &id, bool is_undo, bool is_placeholder);
BLO_Write_IDBuffer(ID &id, BlendWriter *writer);
ID *get()
{
return static_cast<ID *>(buffer_.buffer());
};
};
/* Misc. */
/**
* Check if the data can be written more efficiently by making use of implicit-sharing. If yes, the
* user count of the sharing-info is increased making the data immutable. The provided callback
* should serialize the potentially shared data. It is only called when necessary.
*
* \param approximate_size_in_bytes: Used to be able to approximate how large the undo step is in
* total.
* \param write_fn: Use the #BlendWrite to serialize the potentially shared data.
*/
void BLO_write_shared(BlendWriter *writer,
const void *data,
size_t approximate_size_in_bytes,
const ImplicitSharingInfo *sharing_info,
FunctionRef<void()> write_fn);
/**
* Needs to be called for all pointers that _need_ to be 'stabilized' when writing undo steps,
* _before_ any of these pointers are actually written (so typically at the very start of a write
* function)..
*
* Typically required for data dynamically generated as part of the write process, see e.g.
* AttributeStorage::dna_attributes.
*/
void BLO_write_generated_pointer_tag(BlendWriter *writer, const void *data);
/**
* Sometimes different data is written depending on whether the file is saved to disk or used for
* undo. This function returns true when the current file-writing is done for undo.
*/
bool BLO_write_is_undo(BlendWriter *writer);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blend Read Data API
*
* Generally, for every BLO_write_* call there should be a corresponding BLO_read_* call.
*
* Most BLO_read_* functions get a pointer to a pointer as argument. That allows the function to
* update the pointer to its new value.
*
* When the given pointer points to a memory buffer that was not stored in the file, the pointer is
* updated to be NULL. When it was pointing to NULL before, it will stay that way.
*
* Examples of matching calls:
*
* \code{.c}
* writer->write_struct(clmd->sim_parms);
* BLO_read_struct(reader, ClothSimSettings, &clmd->sim_parms);
*
* writer->write_struct_list(&action->markers);
* BLO_read_struct_list(reader, TimeMarker, &action->markers);
*
* writer->write_int32_array(hmd->totindex, hmd->indexar);
* if (!BLO_read_array(reader, &hmd->indexar, hmd->totindex)) {
* hmd->totindex = 0;
* }
* \endcode
*
* Avoid using the generic #BLO_read_raw_address when possible, use the typed functions instead.
* Only data written with #BlendWriter::write_raw should typically be read with
* #BLO_read_raw_address.
* \{ */
void *blo_read_raw_address_impl(BlendDataReader *reader, const void *old_address);
#define BLO_read_raw_address(reader, ptr_p) \
*((void **)ptr_p) = blo_read_raw_address_impl((reader), *(ptr_p))
/**
* Read function for pointers to structs.
*
* NOTE: Currently the usage of the type info is very minimal/basic, it does a loose check on
* the data size and marks the blend file as invalid when it's mismatched.
*/
void *blo_read_struct_impl(BlendDataReader *reader, const void *old_address, size_t expected_size);
#define BLO_read_struct(reader, struct_name, ptr_p) \
(*((void **)ptr_p) = blo_read_struct_impl(reader, *((void **)ptr_p), sizeof(struct_name)))
/**
* Like #BLO_read_struct, but mark the blend file as invalid (with an error report) when the
* pointer was non-null but failed to resolve.
*/
void *blo_read_struct_nonnull_impl(BlendDataReader *reader,
const void *old_address,
size_t expected_size);
#define BLO_read_struct_nonnull(reader, struct_name, ptr_p) \
(*((void **)ptr_p) = blo_read_struct_nonnull_impl( \
reader, *((void **)ptr_p), sizeof(struct_name)))
/**
* Like #BLO_read_struct, but does not consider the read data as 'used'. It will still be freed
* by readfile code at the end of the reading process, if no other 'real' usage was detected.
*
* Typical valid usages include:
* - Restoring pointers to a specific item in an array or list (usually 'active' item e.g.). The
* found item is expected to also be read as part of its array/list storage reading.
* - Doing temporary access to deprecated data as part of some versioning code.
*/
void *blo_read_struct_no_us_impl(BlendDataReader *reader,
const void *old_address,
size_t expected_size);
#define BLO_read_struct_no_us(reader, struct_name, ptr_p) \
(*((void **)ptr_p) = blo_read_struct_no_us_impl(reader, *((void **)ptr_p), sizeof(struct_name)))
#define BLO_read_struct_array_no_us(reader, struct_name, ptr_p, array_size) \
(*((void **)ptr_p) = blo_read_struct_no_us_impl( \
reader, *((void **)ptr_p), sizeof(struct_name) * size_t(array_size)))
/**
* Like #BLO_read_struct_no_us, but with the same nonnull semantics as #BLO_read_struct_nonnull.
*/
void *blo_read_struct_no_us_nonnull_impl(BlendDataReader *reader,
const void *old_address,
size_t expected_size);
#define BLO_read_struct_no_us_nonnull(reader, struct_name, ptr_p) \
(*((void **)ptr_p) = blo_read_struct_no_us_nonnull_impl( \
reader, *((void **)ptr_p), sizeof(struct_name)))
/**
* Similar to #BLO_read_struct, but can use a (DNA) type name instead of the type
* itself to find the expected data size.
*
* Somewhat mirrors #BlendWriter::write_struct_array_by_name.
*/
void *BLO_read_struct_by_name_array(BlendDataReader *reader,
const char *struct_name,
int64_t items_num,
const void *old_address);
/* Read all elements in list
*
* Updates all `->prev` and `->next` pointers of the list elements.
* Updates the `list->first` and `list->last` pointers.
*/
void BLO_read_struct_list_with_size(BlendDataReader *reader,
size_t expected_elem_size,
ListBase *list);
#define BLO_read_struct_list(reader, struct_name, list) \
BLO_read_struct_list_with_size(reader, sizeof(struct_name), list)
/**
* Read an array of typed elements (struct or primitive type) from the file.
*
* With corrupt blend files the size may not match the array memory allocation.
* This must be handled either by using #BLO_read_array_and_validate_size to
* automatically set the size to zero, or checking the return value of
* #BLO_read_array to manually handle invalid data.
*
* Typically #BLO_read_array_and_validate_size should be used for cases where
* a size member is only for the array pointer, while a size member shared
* between multiple array needs particular handling.
*/
[[nodiscard]] bool blo_read_array_impl(
BlendDataReader *reader, int64_t array_size, int elems, size_t elem_size, void **ptr_p);
template<typename T, typename SizeT>
requires(!std::is_void_v<T> && !std::is_pointer_v<T>)
[[nodiscard]] bool BLO_read_array(BlendDataReader *reader,
T **ptr_p,
const SizeT array_size,
const int elems = 1)
{
return blo_read_array_impl(
reader, int64_t(array_size), elems, sizeof(T), reinterpret_cast<void **>(ptr_p));
}
template<typename T, typename SizeT>
requires(!std::is_void_v<T> && !std::is_pointer_v<T>)
void BLO_read_array_and_validate_size(BlendDataReader *reader,
T **ptr_p,
SizeT *array_size,
const int elems = 1)
{
if (!blo_read_array_impl(
reader, int64_t(*array_size), elems, sizeof(T), reinterpret_cast<void **>(ptr_p)))
{
*array_size = 0;
}
}
/**
* Read an array of pointers, converting between 32/64-bit pointer sizes if needed.
* Same size mismatch handling as #BLO_read_array.
*/
[[nodiscard]] bool blo_read_pointer_array_impl(BlendDataReader *reader,
int64_t array_size,
void **ptr_p);
template<typename T, typename SizeT>
requires(std::is_pointer_v<T> || std::is_void_v<T>)
[[nodiscard]] bool BLO_read_pointer_array(BlendDataReader *reader,
T **ptr_p,
const SizeT array_size)
{
return blo_read_pointer_array_impl(reader, array_size, reinterpret_cast<void **>(ptr_p));
}
template<typename T, typename SizeT>
requires(std::is_pointer_v<T> || std::is_void_v<T>)
void BLO_read_pointer_array_and_validate_size(BlendDataReader *reader,
T **ptr_p,
SizeT *array_size)
{
if (!blo_read_pointer_array_impl(reader, *array_size, reinterpret_cast<void **>(ptr_p))) {
*array_size = 0;
}
}
/* Read null terminated string. */
void BLO_read_string(BlendDataReader *reader, char **ptr_p);
void BLO_read_string(BlendDataReader *reader, char *const *ptr_p);
void BLO_read_string(BlendDataReader *reader, const char **ptr_p);
/* Misc. */
ImplicitSharingInfoAndData blo_read_shared_impl(
BlendDataReader *reader,
const void **ptr_p,
FunctionRef<const ImplicitSharingInfo *()> read_fn);
/**
* Check if there is any shared data for the given data pointer. If yes, return the existing
* sharing-info. If not, call the provided function to actually read the data now.
*/
template<typename T>
const ImplicitSharingInfo *BLO_read_shared(BlendDataReader *reader,
T **data_ptr,
FunctionRef<const ImplicitSharingInfo *()> read_fn)
{
ImplicitSharingInfoAndData shared_data = blo_read_shared_impl(
reader, (const void **)data_ptr, read_fn);
/* Need const-cast here, because not all DNA members that reference potentially shared data are
* const yet. */
*data_ptr = const_cast<T *>(static_cast<const T *>(shared_data.data));
return shared_data.sharing_info;
}
int BLO_read_fileversion_get(BlendDataReader *reader);
bool BLO_read_data_is_undo(BlendDataReader *reader);
void BLO_read_data_globmap_add(BlendDataReader *reader, void *oldaddr, void *newaddr);
void BLO_read_glob_list(BlendDataReader *reader, ListBase *list);
BlendFileReadReport *BLO_read_data_reports(BlendDataReader *reader);
struct Library *BLO_read_data_current_library(BlendDataReader *reader);
void BLO_read_data_set_need_preview_render_restart(BlendDataReader *reader);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blend Read Lib API
*
* This API does almost the same as the Blend Read Data API.
* However, now only pointers to ID data blocks are updated.
* \{ */
/**
* Search for the new address of given `id`,
* during library linking part of blend-file reading process.
*
* \param self_id: the ID owner of the given `id` pointer. Note that it may be an embedded ID.
* \param is_linked_only: If `true`, only return found pointer if it is a linked ID. Used to
* prevent linked data to point to local IDs.
* \return the new address of the given ID pointer, or null if not found.
*/
ID *BLO_read_get_new_id_address(BlendLibReader *reader,
ID *self_id,
const bool is_linked_only,
ID *id) ATTR_NONNULL(2);
/**
* Search for the new address of the ID for the given `session_uid`.
*
* Only IDs existing in the newly read Main will be returned. If no matching `session_uid` in new
* main can be found, `nullptr` is returned.
*
* This expected to be used during library-linking and/or 'undo_preserve' processes in undo case
* (i.e. memfile reading), typically to find a valid value (or nullptr) for ID pointers values
* coming from the previous, existing Main data, when it is preserved in newly read Main.
* See e.g. the #scene_undo_preserve code-path.
*/
ID *BLO_read_get_new_id_address_from_session_uid(BlendLibReader *reader, uint session_uid)
ATTR_NONNULL(1);
/* Misc. */
bool BLO_read_lib_is_undo(BlendLibReader *reader);
Main *BLO_read_lib_get_main(BlendLibReader *reader);
BlendFileReadReport *BLO_read_lib_reports(BlendLibReader *reader);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Report API
* \{ */
/**
* This function ensures that reports are printed,
* in the case of library linking errors this is important!
*
* NOTE(@ideasman42) a kludge but better than doubling up on prints,
* we could alternatively have a versions of a report function which forces printing.
*/
void BLO_reportf_wrap(BlendFileReadReport *reports, eReportType type, const char *format, ...)
ATTR_PRINTF_FORMAT(3, 4);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,635 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_ID_enums.h"
#include "DNA_listBase.h"
#include "BLI_compiler_attrs.h"
#include "BLI_enum_flags.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_sys_types.h"
#include "BLI_utility_mixins.hh"
struct BlendHandle;
namespace blender {
/** \file
* \ingroup blenloader
* \brief external readfile function prototypes.
*/
struct AssetMetaData;
struct BHead;
struct BlendfileLinkAppendContext;
struct BlendThumbnail;
struct FileData;
struct FileReader;
struct ID;
struct Library;
struct LinkNode;
struct Main;
struct MemFile;
struct PreviewImage;
struct ReportList;
struct Scene;
struct UserDef;
struct View3D;
struct ViewLayer;
struct WorkSpace;
struct bScreen;
struct wmWindowManager;
struct WorkspaceConfigFileData {
Main *main; /* has to be freed when done reading file data */
ListBaseT<WorkSpace> workspaces;
};
/* -------------------------------------------------------------------- */
/** \name BLO Read File API
*
* \see #BLO_write_file for file writing.
* \{ */
enum eBlenFileType {
BLENFILETYPE_BLEND = 1,
// BLENFILETYPE_PUB = 2, /* UNUSED */
// BLENFILETYPE_RUNTIME = 3, /* UNUSED */
};
struct BlendFileData : NonCopyable, NonMovable {
Main *main = nullptr;
UserDef *user = nullptr;
int fileflags = 0;
int globalf = 0;
/** Typically the actual filepath of the read blend-file, except when recovering
* save-on-exit/autosave files. In the latter case, it will be the path of the file that
* generated the auto-saved one being recovered.
*
* NOTE: Currently expected to be the same path as #BlendFileData.filepath. */
char filepath[/*FILE_MAX*/ 1024] = {};
/** TODO: think this isn't needed anymore? */
bScreen *curscreen = nullptr;
Scene *curscene = nullptr;
/** Layer to activate in workspaces when reading without UI. */
ViewLayer *cur_view_layer = nullptr;
eBlenFileType type = eBlenFileType(0);
};
/**
* Data used by WM readfile code and BKE's setup_app_data to handle the complex preservation logic
* of WindowManager and other UI data-blocks across blend-file reading process.
*/
struct BlendFileReadWMSetupData {
/** The existing WM when file-reading process is started. */
wmWindowManager *old_wm;
/** The startup file is being read. */
bool is_read_homefile;
/** The factory startup file is being read. */
bool is_factory_startup;
};
struct BlendFileReadParams {
uint skip_flags : 3; /* #eBLOReadSkip */
uint is_startup : 1;
uint is_factory_settings : 1;
/** Whether we are reading the memfile for an undo or a redo. */
int undo_direction; /* #eUndoStepDir */
};
struct BlendFileReadReport {
/** General reports handling. */
ReportList *reports;
/** Timing information. */
struct {
double whole;
double libraries;
double lib_overrides;
double lib_overrides_resync;
double lib_overrides_recursive_resync;
} duration;
/** Count information. */
struct {
/**
* Some numbers of IDs that ended up in a specific state, or required some specific process
* during this file read.
*/
int missing_libraries;
int missing_linked_id;
/** Some sub-categories of the above `missing_linked_id` counter. */
int missing_obdata;
int missing_obproxies;
/** Number of root override IDs that were resynced. */
int resynced_lib_overrides;
/** Number of proxies converted to library overrides. */
int proxies_to_lib_overrides_success;
/** Number of proxies that failed to convert to library overrides. */
int proxies_to_lib_overrides_failures;
/** Number of sequencer strips that were not read because were in non-supported channels. */
int sequence_strips_skipped;
} count;
/**
* Number of libraries which had overrides that needed to be resynced,
* and a single linked list of those.
*/
int resynced_lib_overrides_libraries_count;
bool do_resynced_lib_overrides_libraries_list;
LinkNode *resynced_lib_overrides_libraries;
/** Whether a pre-2.50 blend file was loaded, in which case any animation is lost. */
bool pre_animato_file_loaded;
};
/** Skip reading some data-block types (may want to skip screen data too). */
enum eBLOReadSkip {
BLO_READ_SKIP_NONE = 0,
/** Skip #BLO_CODE_USER blocks. */
BLO_READ_SKIP_USERDEF = (1 << 0),
/** Only read #BLO_CODE_USER (and associated data). */
BLO_READ_SKIP_DATA = (1 << 1),
/** Do not attempt to re-use IDs from old bmain for unchanged ones in case of undo. */
BLO_READ_SKIP_UNDO_OLD_MAIN = (1 << 2),
};
ENUM_OPERATORS(eBLOReadSkip)
#define BLO_READ_SKIP_ALL (BLO_READ_SKIP_USERDEF | BLO_READ_SKIP_DATA)
/**
* Open a blender file from a `filepath`. The function returns NULL
* and sets a report in the list if it cannot open the file.
*
* \param filepath: The path of the file to open.
* \param reports: If the return value is NULL, errors indicating the cause of the failure.
* \return The data of the file.
*/
BlendFileData *BLO_read_from_file(const char *filepath,
eBLOReadSkip skip_flags,
BlendFileReadReport *reports);
/**
* Open a blender file from memory. The function returns NULL
* and sets a report in the list if it cannot open the file.
*
* \param mem: The file data.
* \param memsize: The length of \a mem.
* \param reports: If the return value is NULL, errors indicating the cause of the failure.
* \return The data of the file.
*/
BlendFileData *BLO_read_from_memory(const void *mem,
int memsize,
eBLOReadSkip skip_flags,
ReportList *reports);
/**
* Used for undo/redo, skips part of libraries reading
* (assuming their data are already loaded & valid).
*
* \param oldmain: old main,
* from which we will keep libraries and other data-blocks that should not have changed.
* \param filepath: current file, only for retrieving library data.
* Typically `BKE_main_blendfile_path(oldmain)`.
*/
BlendFileData *BLO_read_from_memfile(Main *oldmain,
const char *filepath,
MemFile *memfile,
const BlendFileReadParams *params,
ReportList *reports);
/**
* Frees a BlendFileData structure and *all* the data associated with it
* (the userdef data, and the main libblock data).
*
* \param bfd: The structure to free.
*/
void BLO_blendfiledata_free(BlendFileData *bfd);
/**
* Does versioning code that requires the Main data-base to be fully loaded and valid.
*
* readfile's `do_versions` does not allow to create (or delete) IDs, and only operates on a single
* library at a time.
*
* Called at the end of #setup_add_data from BKE's `blendfile.cc`.
*
* \param new_bmain: the newly read Main data-base.
*/
void BLO_read_do_version_after_setup(Main *new_bmain,
BlendfileLinkAppendContext *lapp_context,
BlendFileReadReport *reports);
/** \} */
/* -------------------------------------------------------------------- */
/** \name BLO Blend File Handle API
* \{ */
struct BLODataBlockInfo {
struct Library {
const char *filepath = nullptr;
LibraryFlag flag = LibraryFlag(0);
};
char name[/*MAX_ID_NAME-2*/ 256] = "";
AssetMetaData *asset_data = nullptr;
/** For Library IDs only: specific info, like the stored blendfile path, flags. */
BLODataBlockInfo::Library library_data = {};
/** Ownership over #asset_data above can be "stolen out" of this struct, for more permanent
* storage. In that case, set this to false to avoid double freeing of the stolen data. */
bool free_asset_data = false;
/**
* Optimization: Tag data-blocks for which we know there is no preview.
* Knowing this can be used to skip the (potentially expensive) preview loading process. If this
* is set to true it means we looked for a preview and couldn't find one. False may mean that
* either no preview was found, or that it wasn't looked for in the first place.
*/
bool no_preview_found = false;
};
/**
* Frees contained data, not \a datablock_info itself.
*/
void BLO_datablock_info_free(BLODataBlockInfo *datablock_info);
/**
* Can be used to free the list returned by #BLO_blendhandle_get_datablock_info().
*/
void BLO_datablock_info_linklist_free(LinkNode * /*BLODataBlockInfo*/ datablock_infos);
/**
* Open a blendhandle from a file path.
*
* \param filepath: The file path to open.
* \param reports: Report errors in opening the file (can be NULL).
* \return A handle on success, or NULL on failure.
*/
BlendHandle *BLO_blendhandle_from_file(const char *filepath, BlendFileReadReport *reports);
/**
* Open a blendhandle from memory.
*
* \param mem: The data to load from.
* \param memsize: The size of the data.
* \return A handle on success, or NULL on failure.
*/
BlendHandle *BLO_blendhandle_from_memory(const void *mem,
int memsize,
BlendFileReadReport *reports);
/** Returns the major and minor version number of Blender used to create the file. */
int3 BLO_blendhandle_get_version(const BlendHandle *bh);
/**
* Gets the names of all the data-blocks in a file of a certain type
* (e.g. all the scene names in a file).
*
* \param bh: The blendhandle to access.
* \param ofblocktype: The type of names to get.
* \param use_assets_only: Only list IDs marked as assets.
* \param r_tot_names: The length of the returned list.
* \return A BLI_linklist of strings. The string links should be freed with #MEM_delete().
*/
LinkNode *BLO_blendhandle_get_datablock_names(BlendHandle *bh,
int ofblocktype,
bool use_assets_only,
int *r_tot_names);
/**
* Gets the names and asset-data (if ID is an asset) of data-blocks in a file of a certain type.
* The data-blocks can be limited to assets.
*
* \param bh: The blendhandle to access.
* \param ofblocktype: The type of names to get.
* \param use_assets_only: Limit the result to assets only.
* \param r_tot_info_items: The length of the returned list.
*
* \return A BLI_linklist of `BLODataBlockInfo *`.
*
* \note The links should be freed using #BLO_datablock_info_free() or the entire list using
* #BLO_datablock_info_linklist_free().
*/
LinkNode * /*BLODataBlockInfo*/ BLO_blendhandle_get_datablock_info(BlendHandle *bh,
int ofblocktype,
bool use_assets_only,
int *r_tot_info_items);
/**
* Get the PreviewImage of a single data block in a file.
* (e.g. all the scene previews in a file).
*
* \param bh: The blendhandle to access.
* \param ofblocktype: The type of names to get.
* \param name: Name of the block without the ID_ prefix, to read the preview image from.
* \return PreviewImage or NULL when no preview Images have been found. Caller owns the returned
*/
PreviewImage *BLO_blendhandle_get_preview_for_id(BlendHandle *bh,
int ofblocktype,
const char *name);
/**
* Gets the names of all the linkable data-block types available in a file.
* (e.g. "Scene", "Mesh", "Light", etc.).
*
* \param bh: The blendhandle to access.
* \return A BLI_linklist of strings. The string links should be freed with #MEM_delete().
*/
LinkNode *BLO_blendhandle_get_linkable_groups(BlendHandle *bh);
/**
* Close and free a blendhandle. The handle becomes invalid after this call.
*
* \param bh: The handle to close.
*/
void BLO_blendhandle_close(BlendHandle *bh) ATTR_NONNULL(1);
/**
* Mark the given Main (and the 'root' local one in case of lib-split Mains) as invalid, and
* generate an error report containing given `message`.
*/
void BLO_read_invalidate_message(BlendHandle *bh, Main *bmain, const char *message);
/**
* BLI_assert-like macro to check a condition, and if `false`, fail the whole .blend reading
* process by marking the Main data-base as invalid, and returning provided `_ret_value`.
*
* NOTE: About usages:
* - #BLI_assert should be used when the error is considered as a bug, but there is some code to
* recover from it and produce a valid Main data-base.
* - #BLO_read_assert_message should be used when the error is not considered as recoverable.
*/
#define BLO_read_assert_message(_check_expr, _ret_value, _bh, _bmain, _message) \
if (_check_expr) { \
BLO_read_invalidate_message((_bh), (_bmain), (_message)); \
return _ret_value; \
} \
(void)0
/** \} */
#define BLO_GROUP_MAX 32
#define BLO_EMBEDDED_STARTUP_BLEND "<startup.blend>"
/* -------------------------------------------------------------------- */
/** \name BLO Blend File Linking API
* \{ */
/**
* Options controlling behavior of append/link code.
* \note merged with 'user-level' options from operators etc. in 16 lower bits
* (see #eFileSel_Params_Flag in DNA_space_types.h).
*/
enum eBLOLibLinkFlags {
/** Generate a placeholder (empty ID) if not found in current lib file. */
BLO_LIBLINK_USE_PLACEHOLDERS = 1 << 16,
/** Force loaded ID to be tagged as #ID_TAG_INDIRECT (used in reload context only). */
BLO_LIBLINK_FORCE_INDIRECT = 1 << 17,
/**
* Set the object active when #OB_FLAG_ACTIVE_CLIPBOARD is set.
* Used for copy & paste so the active object is preserved.
*/
BLO_LIBLINK_APPEND_SET_OB_ACTIVE_CLIPBOARD = 1 << 18,
/** Set fake user on appended IDs. */
BLO_LIBLINK_APPEND_SET_FAKEUSER = 1 << 19,
/**
* Append (make local) also indirect dependencies of appended IDs coming from other libraries.
* NOTE: All IDs (including indirectly linked ones) coming from the same initial library are
* always made local.
*/
BLO_LIBLINK_APPEND_RECURSIVE = 1 << 20,
/** Try to re-use previously appended matching ID on new append. */
BLO_LIBLINK_APPEND_LOCAL_ID_REUSE = 1 << 21,
/** Clear the asset data. */
BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR = 1 << 22,
/** Instantiate object data IDs (i.e. create objects for them if needed). */
BLO_LIBLINK_OBDATA_INSTANCE = 1 << 24,
/** Instantiate collections as empties, instead of linking them into current view layer. */
BLO_LIBLINK_COLLECTION_INSTANCE = 1 << 25,
/**
* Do not rebuild collections hierarchy runtime data (mainly the parents info)
* as part of #BLO_library_link_end.
* Needed when some IDs have been temporarily removed from Main,
* see e.g. #BKE_blendfile_library_relocate.
*/
BLO_LIBLINK_COLLECTION_NO_HIERARCHY_REBUILD = 1 << 26,
/**
* Pack the linked data-blocks to keep them working even if the source file is not available.
*/
BLO_LIBLINK_PACK = 1 << 27,
};
/**
* Struct for passing arguments to
* #BLO_library_link_begin, #BLO_library_link_named_part & #BLO_library_link_end.
* Wrap these in parameters since it's important both functions receive matching values.
*/
struct LibraryLink_Params {
/** The current main database, e.g. #G_MAIN or `CTX_data_main(C)`. */
Main *bmain;
/** Options for linking, used for instantiating. */
int flag;
/** Additional tag for #ID.tag. */
int id_tag_extra;
/** Context for instancing objects (optional, no instantiation will be performed when NULL). */
struct {
/** The scene in which to instantiate objects/collections. */
Scene *scene;
/** The scene layer in which to instantiate objects/collections. */
ViewLayer *view_layer;
/** The active 3D viewport (only used to define local-view). */
const View3D *v3d;
} context;
};
void BLO_library_link_params_init(LibraryLink_Params *params,
Main *bmain,
int flag,
int id_tag_extra);
void BLO_library_link_params_init_with_context(LibraryLink_Params *params,
Main *bmain,
int flag,
int id_tag_extra,
Scene *scene,
ViewLayer *view_layer,
const View3D *v3d);
/**
* Initialize the #BlendHandle for linking library data.
*
* \param bh: A blender file handle as returned by
* #BLO_blendhandle_from_file or #BLO_blendhandle_from_memory.
* \param filepath: Used for relative linking, copied to the `lib->filepath`.
* \param params: Settings for linking that don't change from beginning to end of linking.
* \return the library #Main, to be passed to #BLO_library_link_named_part as \a mainl.
*/
Main *BLO_library_link_begin(BlendHandle **bh,
const char *filepath,
const LibraryLink_Params *params);
/**
* Link a named data-block from an external blend file.
*
* \param mainl: The main database to link from (not the active one).
* \param bh: The blender file handle.
* \param idcode: The kind of data-block to link.
* \param name: The name of the data-block (without the 2 char ID prefix).
* \return the linked ID when found.
*/
ID *BLO_library_link_named_part(Main *mainl,
BlendHandle **bh,
short idcode,
const char *name,
const LibraryLink_Params *params);
/**
* Finalize linking from a given .blend file (library).
* Optionally instance the indirect object/collection in the scene when the flags are set.
* \note Do not use \a bh after calling this function, it may frees it.
*
* \param mainl: The main database to link from (not the active one).
* \param bh: The blender file handle (WARNING! may be freed by this function!).
* \param params: Settings for linking that don't change from beginning to end of linking.
*/
void BLO_library_link_end(Main *mainl,
BlendHandle **bh,
const LibraryLink_Params *params,
ReportList *reports);
/**
* Struct for temporarily loading datablocks from a blend file.
*/
struct TempLibraryContext {
/** Temporary main used to load data into (currently initialized from `real_main`). */
Main *bmain_base;
BlendFileReadReport bf_reports;
/** The ID datablock that was loaded. Is NULL if loading failed. */
ID *temp_id;
};
TempLibraryContext *BLO_library_temp_load_id(Main *real_main,
const char *blend_file_path,
short idcode,
const char *idname,
ReportList *reports);
void BLO_library_temp_free(TempLibraryContext *temp_lib_ctx);
/** \} */
void *BLO_library_read_struct(FileData *fd, BHead *bh, const char *blockname);
/**
* Update defaults in startup.blend, without having to save and embed it.
* \note defaults for preferences are stored in `userdef_default.c` and can be updated there.
*/
/**
* Update defaults in startup.blend, without having to save and embed the file.
* This function can be emptied each time the startup.blend is updated.
*
* \note Screen data may be cleared at this point, this will happen in the case
* an app-template's data needs to be versioned when read-file is called with "Load UI" disabled.
* Versioning the screen data can be safely skipped without "Load UI" since the screen data
* will have been versioned when it was first loaded.
*/
void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template);
void BLO_update_defaults_workspace(WorkSpace *workspace, const char *app_template);
/** Disable unwanted experimental feature settings on startup. */
void BLO_sanitize_experimental_features_userpref_blend(UserDef *userdef);
/**
* Does a very light reading of given .blend file to extract its stored thumbnail.
*
* \param filepath: The path of the file to extract thumbnail from.
* \return The raw thumbnail
* (MEM-allocated, as stored in file, use #BKE_main_thumbnail_to_imbuf()
* to convert it to ImBuf image).
*/
BlendThumbnail *BLO_thumbnail_from_file(const char *filepath);
/**
* Does a very light reading of given .blend file to extract its version.
*
* \param filepath: The path of the blend file to extract version from.
* \return The file version
*/
short BLO_version_from_file(const char *filepath);
/**
* Runtime structure on `ID.runtime.readfile_data` that is available during the readfile process.
*
* This is intended for short-lived data, for example for things that are detected in an early
* phase of versioning that should be used in a later stage of versioning.
*
* \note This is NOT allocated when 'reading' an undo step, as that doesn't have to deal with
* versioning, linking, and the other stuff that this struct was meant for.
*/
struct ID_Readfile_Data {
struct Tags {
/* General ID reading related tags. */
/**
* Mark ID placeholders for linked data-blocks needing to be read from their library
* blend-files.
*/
bool is_link_placeholder : 1;
/**
* Mark IDs needing to be expanded (only done once). See #expand_main.
*/
bool needs_expanding : 1;
/**
* Mark IDs needing to be 'lib-linked', i.e. to get their pointers to other data-blocks
* updated from the 'UID' values stored in `.blend` files to the new, actual pointers.
*/
bool needs_linking : 1;
/**
* Memfile undo only: mark IDs used by 'no undo' IDs (e.g. brush dependencies).
*
* This is currently used to ensure that all linked 'no undo' IDs are preserved and remain
* fully valid across undo steps (also used to tag libraries containing such no-undo linked
* IDs).
*/
bool used_by_no_undo_id : 1;
/* Specific ID-type reading/versioning related tags. */
/**
* Set when this ID used a legacy Action, in which case it also should pick
* an appropriate slot.
*
* \see ANIM_versioning.hh
*/
bool action_assignment_needs_slot : 1;
} tags;
};
/**
* Return `id->runtime->readfile_data->tags` if the `readfile_data` is allocated,
* otherwise return an all-zero set of tags.
*/
ID_Readfile_Data::Tags BLO_readfile_id_runtime_tags(const ID &id);
/**
* Create the `readfile_data` if needed, and return `id->runtime->readfile_data->tags`.
*
* Use it instead of #BLO_readfile_id_runtime_tags when tags need to be set.
*/
ID_Readfile_Data::Tags &BLO_readfile_id_runtime_tags_for_write(ID &id);
/**
* Free the #ID_Readfile_Data of all IDs in this bmain and all their embedded IDs.
*
* This is typically called at the end of the versioning process, as after that
* `ID.runtime.readfile_data` should no longer be needed.
*/
void BLO_readfile_id_runtime_data_free_all(Main &bmain);
/**
* Free the #ID_Readfile_Data of this ID. Does _not_ deal with embedded IDs.
*/
void BLO_readfile_id_runtime_data_free(ID &id);
#define BLEN_THUMB_MEMSIZE_FILE(_x, _y) (sizeof(int) * (2 + size_t(_x) * size_t(_y)))
} // namespace blender

View File

@@ -0,0 +1,121 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup blenloader
* External write-file function prototypes.
*/
#include "BLI_filereader.h"
#include "BLI_implicit_sharing.hh"
#include "BLI_map.hh"
#include "DNA_listBase.h"
namespace blender {
struct Main;
struct Scene;
struct WriteData;
struct WriteDataStableAddressIDs;
struct MemFileSharedStorage {
/**
* Maps the address id to the shared data and corresponding sharing info..
*/
Map<uint64_t, ImplicitSharingInfoAndData> sharing_info_by_address_id;
~MemFileSharedStorage();
};
struct MemFileChunk {
void *next, *prev;
const char *buf;
/** Size in bytes. */
size_t size;
/** When true, this chunk doesn't own the memory, it's shared with a previous #MemFileChunk */
bool is_identical;
/** When true, this chunk is also identical to the one in the next step (used by undo code to
* detect unchanged IDs).
* Defined when writing the next step (i.e. last undo step has those always false). */
bool is_identical_future;
/** Session UID of the ID being currently written (MAIN_ID_SESSION_UID_UNSET when not writing
* ID-related data). Used to find matching chunks in previous memundo step. */
uint id_session_uid;
};
struct MemFile {
ListBaseT<MemFileChunk> chunks;
size_t size;
/**
* Some data is not serialized into a new buffer because the undo-step can take ownership of it
* without making a copy. This is faster and requires less memory.
*/
MemFileSharedStorage *shared_storage;
};
struct MemFileWriteData {
MemFile *written_memfile;
MemFile *reference_memfile;
uint current_id_session_uid;
MemFileChunk *reference_current_chunk;
/** Maps an ID session uid to its first reference MemFileChunk, if existing. */
Map<uint, MemFileChunk *> id_session_uid_mapping;
};
struct MemFileUndoData {
char filepath[/*FILE_MAX*/ 1024];
MemFile memfile;
size_t undo_size;
};
/* FileReader-compatible wrapper for reading MemFiles */
struct UndoReader {
FileReader reader;
MemFile *memfile;
int undo_direction;
bool memchunk_identical;
};
/* Actually only used `writefile.cc`. */
void BLO_memfile_write_init(WriteData *wd,
MemFileWriteData *mem_data,
MemFile *written_memfile,
MemFile *reference_memfile);
void BLO_memfile_write_finalize(WriteData *wd, MemFileWriteData *mem_data);
void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size);
/* exports */
/**
* Not memfile itself.
*/
/* **************** support for memory-write, for undo buffers *************** */
void BLO_memfile_free(MemFile *memfile);
/**
* Result is that 'first' is being freed.
* To keep the #MemFile linked list of consistent, `first` is always first in list.
*/
void BLO_memfile_merge(MemFile *first, MemFile *second);
/**
* Clear is_identical_future before adding next memfile.
*/
void BLO_memfile_clear_future(MemFile *memfile);
/* Utilities. */
Main *BLO_memfile_main_get(MemFile *memfile, Main *bmain, Scene **r_scene);
FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction);
} // namespace blender

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_userdef_types.h"
namespace blender {
#ifdef __cplusplus
extern "C" {
#endif
/** Default theme, see: `release/datafiles/userdef/userdef_default_theme.c`. */
extern const bTheme U_theme_default;
#ifdef __cplusplus
}
#endif
} // namespace blender

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup blenloader
* \brief external `writefile.cc` function prototypes.
*/
#include "BLI_sys_types.h"
namespace blender {
struct BlendThumbnail;
struct Main;
struct MemFile;
struct ReportList;
/* -------------------------------------------------------------------- */
/** \name BLO Write File API
*
* \see #BLO_read_from_file for file reading.
* \{ */
/**
* Adjust paths when saving (kept unless #BlendFileWriteParams.use_save_as_copy is set).
*/
enum eBLO_WritePathRemap {
/** No path manipulation. */
BLO_WRITE_PATH_REMAP_NONE = 0,
/** Remap existing relative paths (default). */
BLO_WRITE_PATH_REMAP_RELATIVE = 1,
/** Remap paths making all paths relative to the new location. */
BLO_WRITE_PATH_REMAP_RELATIVE_ALL = 2,
/** Make all paths absolute. */
BLO_WRITE_PATH_REMAP_ABSOLUTE = 3,
};
/** Similar to #BlendFileReadParams. */
struct BlendFileWriteParams {
eBLO_WritePathRemap remap_mode = {};
/** Save `.blend1`, `.blend2`... etc. */
uint use_save_versions : 1 = false;
/** On write, restore paths after editing them (see #BLO_WRITE_PATH_REMAP_RELATIVE). */
uint use_save_as_copy : 1 = false;
uint use_userdef : 1 = false;
/** This is writing a copy/paste buffer, not a regular blendfile. */
uint is_copypaste_buffer : 1 = false;
const BlendThumbnail *thumb = nullptr;
};
/**
* \return Success.
*/
extern bool BLO_write_file(Main *mainvar,
const char *filepath,
int write_flags,
const BlendFileWriteParams *params,
ReportList *reports);
/**
* \return Success.
*/
extern bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, int write_flags);
using BLO_WriteFileCallback = bool (*)(const void *data, size_t size, void *user_data);
/**
* Write a regular, non-undo blend file through a caller-provided byte sink.
*
* This keeps the same ID filtering and serialization semantics as #BLO_write_file without
* requiring a temporary filesystem path.
*
* \return Success.
*/
extern bool BLO_write_file_to_callback(Main *mainvar,
int write_flags,
BLO_WriteFileCallback callback,
void *user_data);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,150 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
PUBLIC .
../editors/include
../makesrna
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
${CMAKE_SOURCE_DIR}/release/datafiles/userdef/userdef_default_theme.c
intern/blend_validate.cc
intern/readblenentry.cc
intern/readfile.cc
intern/readfile_tempload.cc
intern/undofile.cc
intern/versioning_250.cc
intern/versioning_260.cc
intern/versioning_270.cc
intern/versioning_280.cc
intern/versioning_290.cc
intern/versioning_300.cc
intern/versioning_400.cc
intern/versioning_410.cc
intern/versioning_420.cc
intern/versioning_430.cc
intern/versioning_440.cc
intern/versioning_450.cc
intern/versioning_500.cc
intern/versioning_510.cc
intern/versioning_520.cc
intern/versioning_common.cc
intern/versioning_defaults.cc
intern/versioning_dna.cc
intern/versioning_legacy.cc
intern/versioning_userdef.cc
intern/writefile.cc
BLO_blend_validate.hh
BLO_read_write.hh
BLO_readfile.hh
BLO_undofile.hh
BLO_userdef_default.h
BLO_writefile.hh
versioning_common.hh
intern/readfile.hh
intern/writefile.hh
)
set(LIB
PRIVATE bf::animrig
PRIVATE bf::asset_system
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PUBLIC bf::blenloader_core
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::draw
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::imbuf::movie
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::memutil
PRIVATE bf::nodes
PRIVATE bf::render
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
PRIVATE bf::extern::xxhash
PRIVATE bf::dependencies::zstd
)
if(WITH_BUILDINFO)
add_definitions(-DWITH_BUILDINFO)
endif()
if(WITH_CODEC_FFMPEG)
add_definitions(-DWITH_FFMPEG)
endif()
if(WITH_ALEMBIC)
list(APPEND INC
../io/alembic
)
add_definitions(-DWITH_ALEMBIC)
endif()
blender_add_lib(bf_blenloader "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_library(bf::blenloader ALIAS bf_blenloader)
# RNA_prototypes.hh
add_dependencies(bf_blenloader bf_rna)
if(WITH_GTESTS)
# Utility functions for test also used by other tests.
set(TEST_UTIL_SRC
tests/blendfile_loading_base_test.cc
tests/blendfile_loading_base_test.h
)
set(TEST_UTIL_INC
${INC}
../../../tests/gtests
../../../intern/ghost
)
set(TEST_UTIL_INC_SYS
${INC_SYS}
${CMAKE_SOURCE_DIR}/extern/gtest/include
)
set(TEST_UTIL_LIB
${LIB}
PRIVATE bf::blenfont
bf_blenloader
PRIVATE bf::dependencies::gflags
PRIVATE bf::dependencies::glog
)
blender_add_lib(bf_blenloader_test_util "${TEST_UTIL_SRC}" "${TEST_UTIL_INC}" "${TEST_UTIL_INC_SYS}" "${TEST_UTIL_LIB}")
# Actual `blenloader` tests.
set(TEST_SRC
tests/blendfile_load_test.cc
)
set(TEST_LIB
${LIB}
bf_blenloader
bf_blenloader_test_util
)
blender_add_test_suite_lib(blenloader "${TEST_SRC}" "${INC}" "${INC_SYS}" "${TEST_LIB}")
endif()
if(WITH_EXPERIMENTAL_FEATURES)
add_definitions(-DWITH_EXPERIMENTAL_FEATURES)
endif()
if(WITH_WEB)
# Web files are produced by the current Blender schema. Historical
# migration units are editor/sequencer-heavy and are not part of the
# headless evaluator; unsupported old file versions are rejected by the
# Web API before entering this reader.
list(FILTER SRC EXCLUDE REGEX "intern/versioning_.*\\.cc$")
list(APPEND SRC intern/versioning_dna.cc)
endif()

View File

@@ -0,0 +1,278 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*
* Utils to check/validate a Main is in sane state,
* only checks relations between data-blocks and libraries for now.
*
* \note Does not *fix* anything, only reports found errors.
*/
#include "CLG_log.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "BLI_linklist.h"
#include "DNA_collection_types.h"
#include "DNA_key_types.h"
#include "DNA_node_types.h"
#include "DNA_windowmanager_types.h"
#include "BKE_key.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_remap.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_report.hh"
#include "BLO_blend_validate.hh"
#include "BLO_readfile.hh"
#include "readfile.hh"
namespace blender {
static CLG_LogRef LOG = {"blend.validate"};
bool BLO_main_validate_libraries(Main *bmain, ReportList *reports)
{
blo_split_main(bmain);
BLI_assert(bmain->split_mains);
VectorSet<Main *> &split_mains = *bmain->split_mains;
BLI_assert(split_mains[0] == bmain);
bool is_valid = true;
BKE_main_lock(bmain);
MainListsArray lbarray = BKE_main_lists_get(*bmain);
int i = lbarray.size();
while (i--) {
for (ID *id = static_cast<ID *>(lbarray[i]->first); id != nullptr;
id = static_cast<ID *>(id->next))
{
if (ID_IS_LINKED(id)) {
is_valid = false;
BKE_reportf(reports,
RPT_ERROR,
"ID %s is in local database while being linked from library %s!",
id->name,
id->lib->filepath);
}
}
}
for (Main *curmain : split_mains) {
if (curmain == bmain) {
continue;
}
Library *curlib = curmain->curlib;
if (curlib == nullptr) {
BKE_report(reports, RPT_ERROR, "Library database with null library data-block pointer!");
continue;
}
BKE_library_filepath_set(bmain, curlib, curlib->filepath);
BlendFileReadReport bf_reports{};
bf_reports.reports = reports;
BlendHandle *bh = BLO_blendhandle_from_file(curlib->runtime->filepath_abs, &bf_reports);
if (bh == nullptr) {
BKE_reportf(reports,
RPT_ERROR,
"Library ID %s not found at expected path %s!",
curlib->id.name,
curlib->runtime->filepath_abs);
continue;
}
lbarray = BKE_main_lists_get(*curmain);
i = lbarray.size();
while (i--) {
ID *id = static_cast<ID *>(lbarray[i]->first);
if (id == nullptr) {
continue;
}
if (GS(id->name) == ID_LI) {
is_valid = false;
BKE_reportf(reports,
RPT_ERROR,
"Library ID %s in library %s, this should not happen!",
id->name,
curlib->filepath);
continue;
}
int totnames = 0;
LinkNode *names = BLO_blendhandle_get_datablock_names(bh, GS(id->name), false, &totnames);
for (; id != nullptr; id = static_cast<ID *>(id->next)) {
if (!ID_IS_LINKED(id)) {
is_valid = false;
BKE_reportf(reports,
RPT_ERROR,
"ID %s has null lib pointer while being in library %s!",
id->name,
curlib->filepath);
continue;
}
if (id->lib != curlib) {
is_valid = false;
BKE_reportf(reports, RPT_ERROR, "ID %s has mismatched lib pointer!", id->name);
continue;
}
LinkNode *name = names;
for (; name; name = name->next) {
const char *str_name = static_cast<const char *>(name->link);
if (id->name[2] == str_name[0] && STREQ(str_name, id->name + 2)) {
break;
}
}
if (name == nullptr) {
is_valid = false;
BKE_reportf(reports,
RPT_ERROR,
"ID %s not found in library %s anymore!",
id->name,
id->lib->filepath);
continue;
}
}
BLI_linklist_freeN(names);
}
BLO_blendhandle_close(bh);
}
blo_join_main(bmain);
BLI_assert(!bmain->split_mains);
BKE_main_unlock(bmain);
return is_valid;
}
bool BLO_main_validate_shapekeys(Main *bmain, ReportList *reports)
{
ListBaseT<ID> *lb;
ID *id;
bool is_valid = true;
BKE_main_lock(bmain);
FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
if (!BKE_key_idtype_support(GS(id->name))) {
break;
}
if (!ID_IS_LINKED(id)) {
/* We assume lib data is valid... */
Key *shapekey = BKE_key_from_id(id);
if (shapekey != nullptr && shapekey->from != id) {
is_valid = false;
BKE_reportf(reports,
RPT_ERROR,
"ID %s uses shapekey %s, but its 'from' pointer is invalid (%p), fixing...",
id->name,
shapekey->id.name,
shapekey->from);
shapekey->from = id;
}
}
}
FOREACH_MAIN_LISTBASE_ID_END;
}
FOREACH_MAIN_LISTBASE_END;
BKE_main_unlock(bmain);
/* NOTE: #BKE_id_delete also locks `bmain`, so we need to do this loop outside of the lock here.
*/
for (Key &shapekey : bmain->shapekeys.items_mutable()) {
if (shapekey.from != nullptr) {
continue;
}
BKE_reportf(reports,
RPT_ERROR,
"ShapeKey %s has an invalid 'from' pointer (%p), it will be deleted",
shapekey.id.name,
shapekey.from);
/* NOTE: also need to remap UI data ID pointers here, since `bmain` is not the current
* `G_MAIN`, default UI-handling remapping callback (defined by call to
* `BKE_library_callback_remap_editor_id_reference_set`) won't work on expected data here. */
BKE_id_delete(bmain, &shapekey, {.extra_remapping_flags = ID_REMAP_FORCE_UI_POINTERS});
}
return is_valid;
}
void BLO_main_validate_embedded_liboverrides(Main *bmain, ReportList * /*reports*/)
{
ID *id_iter;
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
bNodeTree *node_tree = bke::node_tree_from_id(id_iter);
if (node_tree) {
if (node_tree->id.flag & ID_FLAG_EMBEDDED_DATA_LIB_OVERRIDE) {
if (!ID_IS_OVERRIDE_LIBRARY(id_iter)) {
node_tree->id.flag &= ~ID_FLAG_EMBEDDED_DATA_LIB_OVERRIDE;
}
}
}
if (GS(id_iter->name) == ID_SCE) {
Scene *scene = reinterpret_cast<Scene *>(id_iter);
if (scene->master_collection &&
(scene->master_collection->id.flag & ID_FLAG_EMBEDDED_DATA_LIB_OVERRIDE))
{
scene->master_collection->id.flag &= ~ID_FLAG_EMBEDDED_DATA_LIB_OVERRIDE;
}
}
}
FOREACH_MAIN_ID_END;
}
void BLO_main_validate_embedded_flag(Main *bmain, ReportList * /*reports*/)
{
ID *id_iter;
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
if (id_iter->flag & ID_FLAG_EMBEDDED_DATA) {
CLOG_ERROR(
&LOG, "ID %s is flagged as embedded, while existing in Main data-base", id_iter->name);
id_iter->flag &= ~ID_FLAG_EMBEDDED_DATA;
}
bNodeTree *node_tree = bke::node_tree_from_id(id_iter);
if (node_tree) {
if ((node_tree->id.flag & ID_FLAG_EMBEDDED_DATA) == 0) {
CLOG_ERROR(&LOG,
"ID %s has an embedded nodetree which is not flagged as embedded",
id_iter->name);
node_tree->id.flag |= ID_FLAG_EMBEDDED_DATA;
}
}
if (GS(id_iter->name) == ID_SCE) {
Scene *scene = reinterpret_cast<Scene *>(id_iter);
if (scene->master_collection &&
(scene->master_collection->id.flag & ID_FLAG_EMBEDDED_DATA) == 0)
{
CLOG_ERROR(&LOG,
"ID %s has an embedded Collection which is not flagged as embedded",
id_iter->name);
scene->master_collection->id.flag |= ID_FLAG_EMBEDDED_DATA;
}
}
}
FOREACH_MAIN_ID_END;
}
} // namespace blender

View File

@@ -0,0 +1,470 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
* `.blend` file reading entry point.
*/
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_ghash.h"
#include "BLI_linklist.h"
#include "BLI_path_utils.hh" /* Only for assertions. */
#include "BLI_set.hh"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_genfile.h"
#include "BKE_asset.hh"
#include "BKE_idtype.hh"
#include "BKE_main.hh"
#include "BKE_preview_image.hh"
#include "BLO_readfile.hh"
#include "readfile.hh"
#include "BLI_sys_types.h" /* Needed for `intptr_t`. */
namespace blender {
#ifdef WIN32
# include "BLI_winstuff.h"
#endif
/* Access routines used by file-selector. */
void BLO_datablock_info_free(BLODataBlockInfo *datablock_info)
{
if (datablock_info->free_asset_data) {
BKE_asset_metadata_free(&datablock_info->asset_data);
datablock_info->free_asset_data = false;
}
}
void BLO_datablock_info_linklist_free(LinkNode *datablock_infos)
{
BLI_linklist_free(datablock_infos, [](void *link) {
BLODataBlockInfo *datablock_info = static_cast<BLODataBlockInfo *>(link);
BLO_datablock_info_free(datablock_info);
MEM_delete(datablock_info);
});
}
BlendHandle *BLO_blendhandle_from_file(const char *filepath, BlendFileReadReport *reports)
{
BlendHandle *bh;
bh = reinterpret_cast<BlendHandle *>(blo_filedata_from_file(filepath, reports));
return bh;
}
BlendHandle *BLO_blendhandle_from_memory(const void *mem,
int memsize,
BlendFileReadReport *reports)
{
BlendHandle *bh;
bh = reinterpret_cast<BlendHandle *>(blo_filedata_from_memory(mem, memsize, reports));
return bh;
}
int3 BLO_blendhandle_get_version(const BlendHandle *bh)
{
const FileData *fd = reinterpret_cast<const FileData *>(bh);
return int3(fd->fileversion / 100, fd->fileversion % 100, fd->filesubversion);
}
/* Return `false` if the block should be skipped because it is either an invalid block, or it does
* not meet to required conditions. */
static bool blendhandle_load_id_data_and_validate(FileData *fd,
BHead *bhead,
bool use_assets_only,
const char *&r_idname,
short &r_idflag,
AssetMetaData *&r_asset_meta_data,
BLODataBlockInfo::Library *r_library_data)
{
r_idname = blo_bhead_id_name(fd, bhead);
if (!r_idname || r_idname[0] == '\0') {
return false;
}
r_idflag = blo_bhead_id_flag(fd, bhead);
if (r_library_data) {
r_library_data->filepath = blo_bhead_library_filepath(fd, bhead);
r_library_data->flag = blo_bhead_library_flag(fd, bhead);
}
/* Do not list (and therefore allow direct linking of) packed data.
* While supporting this is conceptually possible, it would require significant changes in
* the UI (file browser) and UX (link operation) to convey this concept and handle it
* correctly. */
if (r_idflag & ID_FLAG_LINKED_AND_PACKED) {
return false;
}
r_asset_meta_data = blo_bhead_id_asset_data_address(fd, bhead);
if (use_assets_only && r_asset_meta_data == nullptr) {
return false;
}
return true;
}
LinkNode *BLO_blendhandle_get_datablock_names(BlendHandle *bh,
int ofblocktype,
const bool use_assets_only,
int *r_tot_names)
{
FileData *fd = reinterpret_cast<FileData *>(bh);
LinkNode *names = nullptr;
BHead *bhead;
int tot = 0;
for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
if (bhead->code == ofblocktype) {
const char *idname;
short idflag;
AssetMetaData *asset_meta_data;
if (!blendhandle_load_id_data_and_validate(
fd, bhead, use_assets_only, idname, idflag, asset_meta_data, nullptr))
{
continue;
}
BLI_linklist_prepend(&names, BLI_strdup(idname + 2));
tot++;
}
else if (bhead->code == BLO_CODE_ENDB) {
break;
}
}
*r_tot_names = tot;
return names;
}
LinkNode *BLO_blendhandle_get_datablock_info(BlendHandle *bh,
int ofblocktype,
const bool use_assets_only,
int *r_tot_info_items)
{
FileData *fd = reinterpret_cast<FileData *>(bh);
LinkNode *infos = nullptr;
BHead *bhead;
int tot = 0;
const bool is_library = (ofblocktype == ID_LI);
const int sdna_nr_preview_image = DNA_struct_find_with_alias(fd->filesdna, "PreviewImage");
for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
if (bhead->code == BLO_CODE_ENDB) {
break;
}
if (bhead->code == ofblocktype) {
BHead *id_bhead = bhead;
const char *idname;
short idflag;
AssetMetaData *asset_meta_data;
BLODataBlockInfo::Library library_data;
if (!blendhandle_load_id_data_and_validate(fd,
id_bhead,
use_assets_only,
idname,
idflag,
asset_meta_data,
is_library ? &library_data : nullptr))
{
continue;
}
const char *name = idname + 2;
BLODataBlockInfo *info = MEM_new<BLODataBlockInfo>(__func__);
if (is_library) {
info->library_data = library_data;
}
/* Lastly, read asset data from the following blocks. */
if (asset_meta_data) {
bhead = blo_read_asset_data_block(fd, bhead, &asset_meta_data);
/* blo_read_asset_data_block() reads all DATA heads and already advances bhead to the
* next non-DATA one. Go back, so the loop doesn't skip the non-DATA head. */
bhead = blo_bhead_prev(fd, bhead);
}
STRNCPY(info->name, name);
info->asset_data = asset_meta_data;
info->free_asset_data = true;
bool has_preview = false;
/* See if we can find a preview in the data of this ID. */
for (BHead *data_bhead = blo_bhead_next(fd, id_bhead); data_bhead->code == BLO_CODE_DATA;
data_bhead = blo_bhead_next(fd, data_bhead))
{
if (data_bhead->SDNAnr == sdna_nr_preview_image) {
has_preview = true;
break;
}
}
info->no_preview_found = !has_preview;
BLI_linklist_prepend(&infos, info);
tot++;
}
}
*r_tot_info_items = tot;
return infos;
}
/**
* Read the preview rects and store in `result`.
*
* `bhead` should point to the block that sourced the `preview_from_file`
* parameter.
* `bhead` parameter is consumed. The correct bhead pointing to the next bhead in the file after
* the preview rects is returned by this function.
* \param fd: The filedata to read the data from.
* \param bhead: should point to the block that sourced the `preview_from_file parameter`.
* bhead is consumed. the new bhead is returned by this function.
* \param result: the Preview Image where the preview rect will be stored.
* \param preview_from_file: The read PreviewImage where the bhead points to. The rects of this
* \return PreviewImage or nullptr when no preview Images have been found. Caller owns the returned
*/
static BHead *blo_blendhandle_read_preview_rects(FileData *fd,
BHead *bhead,
PreviewImage *result,
const PreviewImage *preview_from_file)
{
for (int preview_index = 0; preview_index < NUM_ICON_SIZES; preview_index++) {
if (preview_from_file->rect[preview_index] && preview_from_file->w[preview_index] &&
preview_from_file->h[preview_index])
{
bhead = blo_bhead_next(fd, bhead);
BLI_assert((preview_from_file->w[preview_index] * preview_from_file->h[preview_index] *
sizeof(uint)) == bhead->len);
result->rect[preview_index] = static_cast<uint *>(
BLO_library_read_struct(fd, bhead, "PreviewImage Icon Rect"));
}
else {
/* This should not be needed, but can happen in 'broken' .blend files,
* better handle this gracefully than crashing. */
BLI_assert(preview_from_file->rect[preview_index] == nullptr &&
preview_from_file->w[preview_index] == 0 &&
preview_from_file->h[preview_index] == 0);
result->rect[preview_index] = nullptr;
result->w[preview_index] = result->h[preview_index] = 0;
}
result->flag[preview_index] &= ~PRV_RENDERING;
}
return bhead;
}
PreviewImage *BLO_blendhandle_get_preview_for_id(BlendHandle *bh,
int ofblocktype,
const char *name)
{
FileData *fd = reinterpret_cast<FileData *>(bh);
bool looking = false;
const int sdna_preview_image = DNA_struct_find_with_alias(fd->filesdna, "PreviewImage");
for (BHead *bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
if (bhead->code == BLO_CODE_DATA) {
if (looking && bhead->SDNAnr == sdna_preview_image) {
PreviewImage *preview_from_file = static_cast<PreviewImage *>(
BLO_library_read_struct(fd, bhead, "PreviewImage"));
if (preview_from_file == nullptr) {
break;
}
PreviewImage *result = MEM_dupalloc(preview_from_file);
result->runtime = MEM_new<bke::PreviewImageRuntime>(__func__);
bhead = blo_blendhandle_read_preview_rects(fd, bhead, result, preview_from_file);
MEM_delete(preview_from_file);
return result;
}
}
else if (looking || bhead->code == BLO_CODE_ENDB) {
/* We were looking for a preview image, but didn't find any belonging to block. So it doesn't
* exist. */
break;
}
else if (bhead->code == ofblocktype) {
const char *idname = blo_bhead_id_name(fd, bhead);
if (idname && STREQ(&idname[2], name)) {
looking = true;
}
}
}
return nullptr;
}
LinkNode *BLO_blendhandle_get_linkable_groups(BlendHandle *bh)
{
FileData *fd = reinterpret_cast<FileData *>(bh);
Set<const char *> gathered;
LinkNode *names = nullptr;
BHead *bhead;
for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
if (bhead->code == BLO_CODE_ENDB) {
break;
}
if (BKE_idtype_idcode_is_valid(bhead->code)) {
if (BKE_idtype_idcode_is_linkable(bhead->code)) {
const char *str = BKE_idtype_idcode_to_name(bhead->code);
if (gathered.add(str)) {
BLI_linklist_prepend(&names, BLI_strdup(str));
}
}
}
}
return names;
}
void BLO_blendhandle_close(BlendHandle *bh)
{
FileData *fd = reinterpret_cast<FileData *>(bh);
blo_filedata_free(fd);
}
void BLO_read_invalidate_message(BlendHandle *bh, Main *bmain, const char *message)
{
FileData *fd = reinterpret_cast<FileData *>(bh);
blo_readfile_invalidate(fd, bmain, message);
}
/**********/
BlendFileData *BLO_read_from_file(const char *filepath,
eBLOReadSkip skip_flags,
BlendFileReadReport *reports)
{
BLI_assert(!BLI_path_is_rel(filepath));
BLI_assert(BLI_path_is_abs_from_cwd(filepath));
BlendFileData *bfd = nullptr;
FileData *fd;
fd = blo_filedata_from_file(filepath, reports);
if (fd) {
fd->skip_flags = skip_flags;
bfd = blo_read_file_internal(fd, filepath);
blo_filedata_free(fd);
}
return bfd;
}
BlendFileData *BLO_read_from_memory(const void *mem,
int memsize,
eBLOReadSkip skip_flags,
ReportList *reports)
{
BlendFileData *bfd = nullptr;
FileData *fd;
BlendFileReadReport bf_reports{};
bf_reports.reports = reports;
fd = blo_filedata_from_memory(mem, memsize, &bf_reports);
if (fd) {
fd->skip_flags = skip_flags;
bfd = blo_read_file_internal(fd, "");
blo_filedata_free(fd);
}
return bfd;
}
BlendFileData *BLO_read_from_memfile(Main *oldmain,
const char *filepath,
MemFile *memfile,
const BlendFileReadParams *params,
ReportList *reports)
{
BlendFileData *bfd = nullptr;
FileData *fd;
BlendFileReadReport bf_reports{};
bf_reports.reports = reports;
fd = blo_filedata_from_memfile(memfile, params, &bf_reports);
if (fd) {
fd->skip_flags = eBLOReadSkip(params->skip_flags);
STRNCPY(fd->relabase, filepath);
/* Build old ID map for all old IDs. */
blo_make_old_idmap_from_main(fd, oldmain);
/* Separate linked data from old main.
* WARNING: Do not split out packed IDs here, as these are handled similarly as local IDs in
* undo context. */
blo_split_main(oldmain, false);
fd->old_bmain = oldmain;
/* Removed packed data from this trick - it's internal data that needs saves. */
/* Store all existing ID caches pointers into a mapping, to allow restoring them into newly
* read IDs whenever possible.
*
* Note that this is only required for local data, since linked data are always re-used
* 'as-is'. */
blo_cache_storage_init(fd, oldmain);
bfd = blo_read_file_internal(fd, filepath);
/* Ensure relinked caches are not freed together with their old IDs. */
blo_cache_storage_old_bmain_clear(fd, oldmain);
/* Still in-use libraries have already been moved from oldmain to new main
* (fd->bmain->split_mains), but oldmain itself shall *never* be 'transferred' to the new
* split_mains!
*/
BLI_assert(oldmain->split_mains && (*oldmain->split_mains)[0] == oldmain);
/* That way, libraries (aka mains) we did not reuse in new undone/redone state
* will be cleared together with `oldmain`. */
blo_join_main(oldmain);
blo_filedata_free(fd);
}
return bfd;
}
void BLO_blendfiledata_free(BlendFileData *bfd)
{
if (bfd->main) {
BKE_main_free(bfd->main);
}
if (bfd->user) {
MEM_delete(bfd->user);
}
MEM_delete(bfd);
}
void BLO_read_do_version_after_setup(Main *new_bmain,
BlendfileLinkAppendContext *lapp_context,
BlendFileReadReport *reports)
{
do_versions_after_setup(new_bmain, lapp_context, reports);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,381 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
* blenloader readfile private function prototypes.
*/
#pragma once
#include <cstdio> /* IWYU pragma: keep. Include header using off_t before poisoning it below. */
#include <optional>
#ifdef WIN32
# include "BLI_winstuff.h"
#endif
#include "BLI_enum_flags.hh"
#include "BLI_fileops.h"
#include "BLI_filereader.h"
#include "BLI_map.hh"
#include "DNA_sdna_types.h"
#include "DNA_space_types.h"
#include "BLO_core_bhead.hh"
#include "BLO_core_blend_header.hh"
#include "BLO_readfile.hh"
namespace blender {
struct BlendFileData;
struct BlendfileLinkAppendContext;
struct BlendFileReadParams;
struct BlendFileReadReport;
struct BLOCacheStorage;
struct BHeadN;
struct BHeadSort;
struct DNA_ReconstructInfo;
struct IDNameLib_Map;
struct Key;
struct Main;
struct MemFile;
struct Object;
struct OldNewMap;
struct UserDef;
/**
* Store some critical information about the read blend-file.
*/
enum eFileDataFlag {
FD_FLAGS_SWITCH_ENDIAN = 1 << 0,
FD_FLAGS_FILE_POINTSIZE_IS_4 = 1 << 1,
FD_FLAGS_POINTSIZE_DIFFERS = 1 << 2,
FD_FLAGS_FILE_OK = 1 << 3,
FD_FLAGS_IS_MEMFILE = 1 << 4,
/**
* The Blender file is not compatible with current code, but is still likely a blender file
* 'from the future'. Improves report to the user.
*/
FD_FLAGS_FILE_FUTURE = 1 << 5,
/**
* The blend-file has IDs with invalid names (either using the 5.0+ new 'long names', or
* corrupted). I.e. their names have no null char in their first 66 bytes.
*/
FD_FLAGS_HAS_INVALID_ID_NAMES = 1 << 6,
};
ENUM_OPERATORS(eFileDataFlag)
/* Disallow since it's 32bit on ms-windows. */
#ifdef __GNUC__
# pragma GCC poison off_t
#endif
/**
* General data used during a blend-file reading.
*
* Note that this data (and its accesses) are absolutely not thread-safe currently. It should never
* be accessed concurrently.
*/
struct FileData {
/** Linked list of BHeadN's. */
ListBaseT<BHeadN> bhead_list = {};
enum eFileDataFlag flags = eFileDataFlag(0);
bool is_eof = false;
BlenderHeader blender_header = {};
FileReader *file = nullptr;
std::optional<BLI_stat_t> file_stat;
/**
* Whether we are undoing (< 0) or redoing (> 0), used to choose which 'unchanged' flag to use
* to detect unchanged data from memfile.
* #eUndoStepDir.
*/
int undo_direction = 0;
/** Used for relative paths handling.
*
* Typically the actual filepath of the read blend-file, except when recovering
* save-on-exit/autosave files. In the latter case, it will be the path of the file that
* generated the auto-saved one being recovered.
*
* NOTE: Currently expected to be the same path as #BlendFileData.filepath. */
char relabase[FILE_MAX] = {};
/** General reading variables. */
SDNA *filesdna = nullptr;
const SDNA *memsdna = nullptr;
/** Array of #eSDNA_StructCompare. */
const char *compflags = nullptr;
DNA_ReconstructInfo *reconstruct_info = nullptr;
int fileversion = 0;
/**
* Unlike the `fileversion` which is read from the header,
* this is initialized from #read_file_dna.
*/
int filesubversion = 0;
/** Used to retrieve ID names from (bhead+1). */
int id_name_offset = 0;
/**
* Used to retrieve asset data from (bhead+1). NOTE: This may not be available in old files,
* will be -1 then!
*/
int id_asset_data_offset = 0;
int id_flag_offset = 0;
int id_deep_hash_offset = 0;
/**
* Gives access to libraries' filepath and flag, useful for introspection of blendfiles'
* dependencies _without_ having to fully read them.
*/
int library_filepath_offset = 0;
int library_flag_offset = 0;
/** For do_versions patching. */
int globalf = 0;
int fileflags = 0;
/** Optionally skip some data-blocks when they're not needed. */
eBLOReadSkip skip_flags = BLO_READ_SKIP_NONE;
/**
* Tag to apply to all loaded ID data-blocks.
*
* \note This is initialized from #LibraryLink_Params.id_tag_extra since passing it as an
* argument would need an additional argument to be passed around when expanding library data.
*/
int id_tag_extra = 0;
OldNewMap *datamap = nullptr;
OldNewMap *globmap = nullptr;
/** Used to keep track of already loaded packed IDs to avoid loading them multiple times. */
std::shared_ptr<Map<IDHash, ID *>> id_by_deep_hash;
/**
* Store mapping from old ID pointers (the values they have in the .blend file) to new ones,
* typically from value in `bhead->old` to address in memory where the ID was read.
* Used during library-linking process (see #lib_link_all).
*/
OldNewMap *libmap = nullptr;
BLOCacheStorage *cache_storage = nullptr;
BHeadSort *bheadmap = nullptr;
int tot_bheadmap = 0;
std::optional<Map<StringRefNull, BHead *>> bhead_idname_map;
/**
* The root (main, local) Main.
* The Main that will own Library IDs.
*
* When reading libraries, this is typically _not_ the same Main as the one being populated from
* the content of this filedata, see #fd_bmain.
*/
Main *bmain = nullptr;
/** The existing root (main, local) Main, used for undo. */
Main *old_bmain = nullptr;
/**
* The main for the (local) data loaded from this filedata.
*
* This is the same as #bmain when opening a blend-file, but not when reading/loading from
* libraries blend-files.
*/
Main *fd_bmain = nullptr;
/**
* IDMap using UID's as keys of all the old IDs in the old bmain. Used during undo to find a
* matching old data when reading a new ID. */
IDNameLib_Map *old_idmap_uid = nullptr;
/**
* IDMap using uids as keys of the IDs read (or moved) in the new main(s).
*
* Used during undo to ensure that the ID pointers from the 'no undo' IDs remain valid (these
* IDs are re-used from old main even if their content is not the same as in the memfile undo
* step, so they could point e.g. to an ID that does not exist in the newly read undo step).
*
* Also used to find current valid pointers (or none) of these 'no undo' IDs existing in
* read memfile. */
IDNameLib_Map *new_idmap_uid = nullptr;
BlendFileReadReport *reports = nullptr;
/** Opaque handle to the storage system used for non-static allocation strings. */
void *storage_handle = nullptr;
/**
* Set when reading a file from undo with incomplete preview, to trigger restart of preview jobs.
*/
bool need_preview_render_restart = false;
};
/**
* Split a single main into a vector of Mains, each containing only IDs from a given library.
*
* The vector is accessible in all of the split mains through the shared pointer
* #Main::split_mains.
*
* The first Main of the vector is the same as the given `main`, and contains local IDs.
*
* If `do_split_packed_ids` is `false`, packed linked IDs remain in the local (first) main as well.
*/
void blo_split_main(Main *bmain, bool do_split_packed_ids = true);
/**
* Join the set of split mains (found in given `main` #Main::split_mains vector shared pointer)
* back into that 'main' main.
*/
void blo_join_main(Main *bmain);
BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) ATTR_NONNULL(1, 2);
/**
* On each new library added, it now checks for the current #FileData and expands relativeness
*
* cannot be called with relative paths anymore!
*/
FileData *blo_filedata_from_file(const char *filepath, BlendFileReadReport *reports);
FileData *blo_filedata_from_memory(const void *mem, int memsize, BlendFileReadReport *reports);
FileData *blo_filedata_from_memfile(MemFile *memfile,
const BlendFileReadParams *params,
BlendFileReadReport *reports);
/**
* Build a #IDNameLib_Map of old main (we only care about local data here,
* so we can do that after #blo_split_main() call).
*/
void blo_make_old_idmap_from_main(FileData *fd, Main *bmain) ATTR_NONNULL(1, 2);
BHead *blo_read_asset_data_block(FileData *fd, BHead *bhead, AssetMetaData **r_asset_data)
ATTR_NONNULL(1, 2);
void blo_cache_storage_init(FileData *fd, Main *bmain) ATTR_NONNULL(1, 2);
void blo_cache_storage_old_bmain_clear(FileData *fd, Main *bmain_old) ATTR_NONNULL(1, 2);
void blo_cache_storage_end(FileData *fd) ATTR_NONNULL(1);
void blo_filedata_free(FileData *fd) ATTR_NONNULL(1);
BHead *blo_bhead_first(FileData *fd) ATTR_NONNULL(1);
BHead *blo_bhead_next(FileData *fd, BHead *thisblock) ATTR_NONNULL(1);
BHead *blo_bhead_prev(FileData *fd, BHead *thisblock) ATTR_NONNULL(1, 2);
/**
* Warning! Caller's responsibility to ensure given bhead **is** an ID one!
*
* Will return `nullptr` if the name is not valid (e.g. because it has no null-char terminator, if
* it was saved in a version of Blender with higher MAX_ID_NAME value).
*/
const char *blo_bhead_id_name(FileData *fd, const BHead *bhead);
/**
* Warning! It's the caller's responsibility to ensure that the given bhead **is** an ID one!
*
* Returns the ID flag value (or `0` if the blendfile is too old and the offset of the ID::flag
* member could not be computed).
*/
short blo_bhead_id_flag(const FileData *fd, const BHead *bhead);
/**
* Warning! Caller's responsibility to ensure given bhead **is** an ID one!
*/
AssetMetaData *blo_bhead_id_asset_data_address(const FileData *fd, const BHead *bhead);
/**
* Return the stored filepath (may be relative) of a library ID.
*
* Warning! Caller's responsibility to ensure that the given bhead **is** a Library ID one!
*/
const char *blo_bhead_library_filepath(const FileData *fd, const BHead *bhead);
/**
* Return the stored flags of a library ID.
*
* Warning! Caller's responsibility to ensure that the given bhead **is** a Library ID one!
*/
LibraryFlag blo_bhead_library_flag(const FileData *fd, const BHead *bhead);
/* do versions stuff */
/**
* Manipulates SDNA before calling #DNA_struct_get_compareflags,
* allowing us to rename structs and struct members.
*
* - This means older versions of Blender won't have access to this data **USE WITH CARE**.
* - These changes are applied on file load (run-time), similar to versioning for compatibility.
*
* \attention ONLY USE THIS KIND OF VERSIONING WHEN `dna_rename_defs.h` ISN'T SUFFICIENT.
*/
void blo_do_versions_dna(SDNA *sdna, int versionfile, int subversionfile);
void blo_do_versions_oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void *newaddr, int nr);
/**
* Only library data.
*/
void *blo_do_versions_newlibadr(FileData *fd,
ID *self_id,
const bool is_linked_only,
const void *adr);
/**
* \note this version patch is intended for versions < 2.52.2,
* but was initially introduced in 2.27 already.
*/
void blo_do_version_old_trackto_to_constraints(Object *ob);
void blo_do_versions_key_uidgen(Key *key);
/**
* Patching #UserDef struct and Themes.
*/
void blo_do_versions_userdef(UserDef *userdef);
void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_260(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_270(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_280(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_290(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_300(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_400(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_410(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_420(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_430(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_440(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_450(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_500(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_510(FileData *fd, Library *lib, Main *bmain);
void blo_do_versions_520(FileData *fd, Library *lib, Main *bmain);
void do_versions_after_linking_250(Main *bmain);
void do_versions_after_linking_260(Main *bmain);
void do_versions_after_linking_270(Main *bmain);
void do_versions_after_linking_280(FileData *fd, Main *bmain);
void do_versions_after_linking_290(FileData *fd, Main *bmain);
void do_versions_after_linking_300(FileData *fd, Main *bmain);
void do_versions_after_linking_400(FileData *fd, Main *bmain);
void do_versions_after_linking_410(FileData *fd, Main *bmain);
void do_versions_after_linking_420(FileData *fd, Main *bmain);
void do_versions_after_linking_430(FileData *fd, Main *bmain);
void do_versions_after_linking_440(FileData *fd, Main *bmain);
void do_versions_after_linking_450(FileData *fd, Main *bmain);
void do_versions_after_linking_500(FileData *fd, Main *bmain);
void do_versions_after_linking_510(FileData *fd, Main *bmain);
void do_versions_after_linking_520(FileData *fd, Main *bmain);
void do_versions_after_setup(Main *new_bmain,
BlendfileLinkAppendContext *lapp_context,
BlendFileReadReport *reports);
/**
* Direct data-blocks with global linking.
*
* \note This is rather unfortunate to have to expose this here,
* but better use that nasty hack in do_version than readfile itself.
*/
void *blo_read_get_new_globaldata_address(FileData *fd, const void *adr) ATTR_NONNULL(1);
/**
* Mark the Main data as invalid (.blend file reading should be aborted ASAP, and the already read
* data should be discarded). Also add an error report to `fd` including given `message`.
*/
void blo_readfile_invalidate(FileData *fd, Main *bmain, const char *message) ATTR_NONNULL(1, 2, 3);
} // namespace blender

View File

@@ -0,0 +1,55 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
#include "BLO_readfile.hh"
#include "MEM_guardedalloc.h"
#include "BLI_string.h"
#include "BKE_main.hh"
#include "DNA_ID.h"
namespace blender {
TempLibraryContext *BLO_library_temp_load_id(Main *real_main,
const char *blend_file_path,
const short idcode,
const char *idname,
ReportList *reports)
{
TempLibraryContext *temp_lib_ctx = MEM_new_zeroed<TempLibraryContext>(__func__);
temp_lib_ctx->bmain_base = BKE_main_new();
temp_lib_ctx->bf_reports.reports = reports;
/* Copy the file path so any path remapping is performed properly. */
STRNCPY(temp_lib_ctx->bmain_base->filepath, real_main->filepath);
BlendHandle *blendhandle = BLO_blendhandle_from_file(blend_file_path, &temp_lib_ctx->bf_reports);
LibraryLink_Params lib_link_params;
BLO_library_link_params_init(&lib_link_params, temp_lib_ctx->bmain_base, 0, ID_TAG_TEMP_MAIN);
Main *bmain_lib = BLO_library_link_begin(&blendhandle, blend_file_path, &lib_link_params);
temp_lib_ctx->temp_id = BLO_library_link_named_part(
bmain_lib, &blendhandle, idcode, idname, &lib_link_params);
BLO_library_link_end(bmain_lib, &blendhandle, &lib_link_params, reports);
BLO_blendhandle_close(blendhandle);
return temp_lib_ctx;
}
void BLO_library_temp_free(TempLibraryContext *temp_lib_ctx)
{
BKE_main_free(temp_lib_ctx->bmain_base);
MEM_delete(temp_lib_ctx);
}
} // namespace blender

View File

@@ -0,0 +1,286 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
/* open/close */
#ifndef _WIN32
# include <unistd.h>
#else
# include <io.h>
#endif
#include "MEM_guardedalloc.h"
#include "DNA_listBase.h"
#include "BLI_implicit_sharing.hh"
#include "BLI_listbase.h"
#include "BLO_readfile.hh"
#include "BLO_undofile.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_undo_system.hh"
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
#include "writefile.hh"
namespace blender {
/* **************** support for memory-write, for undo buffers *************** */
void BLO_memfile_free(MemFile *memfile)
{
while (MemFileChunk *chunk = static_cast<MemFileChunk *>(BLI_pophead(&memfile->chunks))) {
if (chunk->is_identical == false) {
MEM_delete(chunk->buf);
}
MEM_delete(chunk);
}
MEM_SAFE_DELETE(memfile->shared_storage);
memfile->size = 0;
}
MemFileSharedStorage::~MemFileSharedStorage()
{
for (const ImplicitSharingInfoAndData &data : sharing_info_by_address_id.values()) {
/* Removing the user makes sure shared data is freed when the undo step was its last owner. */
data.sharing_info->remove_user_and_delete_if_last();
}
}
void BLO_memfile_merge(MemFile *first, MemFile *second)
{
/* We use this mapping to store the memory buffers from second memfile chunks which are not owned
* by it (i.e. shared with some previous memory steps). */
Map<const char *, MemFileChunk *> buffer_to_second_memchunk;
/* First, detect all memchunks in second memfile that are not owned by it. */
for (MemFileChunk &sc : second->chunks) {
if (sc.is_identical) {
buffer_to_second_memchunk.add(sc.buf, &sc);
}
}
/* Now, check all chunks from first memfile (the one we are removing), and if a memchunk owned by
* it is also used by the second memfile, transfer the ownership. */
for (MemFileChunk &fc : first->chunks) {
if (!fc.is_identical) {
if (MemFileChunk *sc = buffer_to_second_memchunk.lookup_default(fc.buf, nullptr)) {
BLI_assert(sc->is_identical);
sc->is_identical = false;
fc.is_identical = true;
}
/* Note that if the second memfile does not use that chunk, we assume that the first one
* fully owns it without sharing it with any other memfile, and hence it should be freed with
* it. */
}
}
BLO_memfile_free(first);
}
void BLO_memfile_clear_future(MemFile *memfile)
{
for (MemFileChunk &chunk : memfile->chunks) {
chunk.is_identical_future = false;
}
}
void BLO_memfile_write_init(WriteData *wd,
MemFileWriteData *mem_data,
MemFile *written_memfile,
MemFile *reference_memfile)
{
wd->use_memfile = true;
mem_data->written_memfile = written_memfile;
mem_data->reference_memfile = reference_memfile;
mem_data->reference_current_chunk = reference_memfile ? static_cast<MemFileChunk *>(
reference_memfile->chunks.first) :
nullptr;
/* If we have a reference memfile, we generate a mapping between the session_uid's of the
* IDs stored in that previous undo step, and its first matching memchunk. This will allow
* us to easily find the existing undo memory storage of IDs even when some re-ordering in
* current Main data-base broke the order matching with the memchunks from previous step.
*/
if (reference_memfile != nullptr) {
uint current_session_uid = MAIN_ID_SESSION_UID_UNSET;
for (MemFileChunk &mem_chunk : reference_memfile->chunks) {
if (!ELEM(mem_chunk.id_session_uid, MAIN_ID_SESSION_UID_UNSET, current_session_uid)) {
current_session_uid = mem_chunk.id_session_uid;
mem_data->id_session_uid_mapping.add_new(current_session_uid, &mem_chunk);
}
}
}
}
void BLO_memfile_write_finalize(WriteData * /*wd*/, MemFileWriteData *mem_data)
{
mem_data->id_session_uid_mapping.clear();
}
void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
{
MemFile *memfile = mem_data->written_memfile;
MemFileChunk **compchunk_step = &mem_data->reference_current_chunk;
MemFileChunk *curchunk = MEM_new_uninitialized<MemFileChunk>("MemFileChunk");
curchunk->size = size;
curchunk->buf = nullptr;
curchunk->is_identical = false;
/* This is unsafe in the sense that an app handler or other code that does not
* perform an undo push may make changes after the last undo push that
* will then not be undo. Though it's not entirely clear that is wrong behavior. */
curchunk->is_identical_future = true;
curchunk->id_session_uid = mem_data->current_id_session_uid;
BLI_addtail(&memfile->chunks, curchunk);
/* we compare compchunk with buf */
if (*compchunk_step != nullptr) {
MemFileChunk *compchunk = *compchunk_step;
if (compchunk->size == curchunk->size) {
if (memcmp(compchunk->buf, buf, size) == 0) {
curchunk->buf = compchunk->buf;
curchunk->is_identical = true;
compchunk->is_identical_future = true;
}
}
*compchunk_step = static_cast<MemFileChunk *>(compchunk->next);
}
/* not equal... */
if (curchunk->buf == nullptr) {
char *buf_new = MEM_new_array_uninitialized<char>(size, "Chunk buffer");
memcpy(buf_new, buf, size);
curchunk->buf = buf_new;
memfile->size += size;
}
}
Main *BLO_memfile_main_get(MemFile *memfile, Main *bmain, Scene **r_scene)
{
Main *bmain_undo = nullptr;
BlendFileReadParams read_params{};
BlendFileData *bfd = BLO_read_from_memfile(
bmain, BKE_main_blendfile_path(bmain), memfile, &read_params, nullptr);
if (bfd) {
bmain_undo = bfd->main;
if (r_scene) {
*r_scene = bfd->curscene;
}
MEM_delete(bfd);
}
return bmain_undo;
}
static int64_t undo_read(FileReader *reader, void *buffer, size_t size)
{
UndoReader *undo = reinterpret_cast<UndoReader *>(reader);
static size_t seek = SIZE_MAX; /* The current position. */
static size_t offset = 0; /* Size of previous chunks. */
static MemFileChunk *chunk = nullptr;
size_t chunkoffset, readsize, totread;
undo->memchunk_identical = true;
if (size == 0) {
return 0;
}
if (seek != size_t(undo->reader.offset)) {
chunk = static_cast<MemFileChunk *>(undo->memfile->chunks.first);
seek = 0;
while (chunk) {
if (seek + chunk->size > size_t(undo->reader.offset)) {
break;
}
seek += chunk->size;
chunk = static_cast<MemFileChunk *>(chunk->next);
}
offset = seek;
seek = size_t(undo->reader.offset);
}
if (chunk) {
totread = 0;
do {
/* First check if it's on the end if current chunk. */
if (seek - offset == chunk->size) {
offset += chunk->size;
chunk = static_cast<MemFileChunk *>(chunk->next);
}
/* Debug, should never happen. */
if (chunk == nullptr) {
printf("illegal read, chunk zero\n");
return 0;
}
chunkoffset = seek - offset;
readsize = size - totread;
/* Data can be spread over multiple chunks, so clamp size
* to within this chunk, and then it will read further in
* the next chunk. */
if (chunkoffset + readsize > chunk->size) {
readsize = chunk->size - chunkoffset;
}
memcpy(POINTER_OFFSET(buffer, totread), chunk->buf + chunkoffset, readsize);
totread += readsize;
undo->reader.offset += off64_t(readsize);
seek += readsize;
/* `is_identical` of current chunk represents whether it changed compared to previous undo
* step. this is fine in redo case, but not in undo case, where we need an extra flag
* defined when saving the next (future) step after the one we want to restore, as we are
* supposed to 'come from' that future undo step, and not the one before current one. */
undo->memchunk_identical &= undo->undo_direction == STEP_REDO ? chunk->is_identical :
chunk->is_identical_future;
} while (totread < size);
return int64_t(totread);
}
return 0;
}
static void undo_close(FileReader *reader)
{
MEM_delete(reader);
}
FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction)
{
UndoReader *undo = MEM_new_zeroed<UndoReader>(__func__);
undo->memfile = memfile;
undo->undo_direction = undo_direction;
undo->reader.read = undo_read;
undo->reader.seek = nullptr;
undo->reader.close = undo_close;
return reinterpret_cast<FileReader *>(undo);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,521 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
#define DNA_DEPRECATED_ALLOW
#include "DNA_brush_types.h"
#include "DNA_camera_types.h"
#include "DNA_collection_types.h"
#include "DNA_curves_types.h"
#include "DNA_modifier_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_workspace_types.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_string_utf8.h"
#include "BKE_collection.hh"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_file_handler.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_image_format.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_paint.hh"
#include "BKE_screen.hh"
#include "SEQ_sequencer.hh"
#include "BLT_translation.hh"
#include "readfile.hh"
#include "versioning_common.hh"
namespace blender {
void do_versions_after_linking_430(FileData * /*fd*/, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 6)) {
/* Shift animation data to accommodate the new Diffuse Roughness input. */
version_node_socket_index_animdata(bmain, NTREE_SHADER, SH_NODE_BSDF_PRINCIPLED, 7, 1, 30);
}
}
static void update_paint_modes_for_brush_assets(Main &bmain)
{
/* Replace paint brushes with a reference to the default brush asset for that mode. */
for (Scene &scene : bmain.scenes) {
BKE_paint_brushes_set_default_references(scene.toolsettings);
}
/* Replace persistent tool references with the new single builtin brush tool. */
for (WorkSpace &workspace : bmain.workspaces) {
for (bToolRef &tref : workspace.tools) {
if (tref.space_type == SPACE_IMAGE && tref.mode == SI_MODE_PAINT) {
STRNCPY_UTF8(tref.idname, "builtin.brush");
continue;
}
if (tref.space_type != SPACE_VIEW3D) {
continue;
}
if (!ELEM(tref.mode,
CTX_MODE_SCULPT,
CTX_MODE_PAINT_VERTEX,
CTX_MODE_PAINT_WEIGHT,
CTX_MODE_PAINT_TEXTURE,
CTX_MODE_PAINT_GPENCIL_LEGACY,
CTX_MODE_PAINT_GREASE_PENCIL,
CTX_MODE_SCULPT_GPENCIL_LEGACY,
CTX_MODE_SCULPT_GREASE_PENCIL,
CTX_MODE_WEIGHT_GPENCIL_LEGACY,
CTX_MODE_WEIGHT_GREASE_PENCIL,
CTX_MODE_VERTEX_GREASE_PENCIL,
CTX_MODE_VERTEX_GPENCIL_LEGACY,
CTX_MODE_SCULPT_CURVES))
{
continue;
}
STRNCPY_UTF8(tref.idname, "builtin.brush");
}
}
}
/**
* It was possible that curve attributes were initialized to 0 even if that is not allowed for some
* attributes.
*/
static void fix_built_in_curve_attribute_defaults(Main *bmain)
{
for (Curves &curves : bmain->hair_curves) {
const int curves_num = curves.geometry.curve_num;
if (int *resolutions = static_cast<int *>(CustomData_get_layer_named_for_write(
&curves.geometry.curve_data_legacy, CD_PROP_INT32, "resolution", curves_num)))
{
for (int &resolution : MutableSpan{resolutions, curves_num}) {
resolution = std::max(resolution, 1);
}
}
if (int8_t *nurb_orders = static_cast<int8_t *>(CustomData_get_layer_named_for_write(
&curves.geometry.curve_data_legacy, CD_PROP_INT8, "nurbs_order", curves_num)))
{
for (int8_t &nurbs_order : MutableSpan{nurb_orders, curves_num}) {
nurbs_order = std::max<int8_t>(nurbs_order, 1);
}
}
}
}
static void node_reroute_add_storage(bNodeTree &tree)
{
for (bNode *node : tree.all_nodes()) {
if (node->is_reroute()) {
if (node->storage != nullptr) {
continue;
}
bNodeSocket &input = *static_cast<bNodeSocket *>(node->inputs.first);
bNodeSocket &output = *static_cast<bNodeSocket *>(node->outputs.first);
/* Use uniform identifier for sockets. In old Blender versions (<=2021, up to af0b7925), the
* identifiers were sometimes all lower case. Fixing those wrong socket identifiers is
* important because otherwise they loose links now that the reroute node also uses node
* declarations. */
version_node_socket_identifier_set(input, "Input");
version_node_socket_identifier_set(output, "Output");
NodeReroute *data = MEM_new<NodeReroute>(__func__);
STRNCPY_UTF8(data->type_idname, input.idname);
node->storage = data;
}
}
}
static void add_bevel_modifier_attribute_name_defaults(Main &bmain)
{
for (Object &ob : bmain.objects) {
if (ob.type != OB_MESH) {
continue;
}
for (ModifierData &md : ob.modifiers) {
if (md.type == eModifierType_Bevel) {
BevelModifierData *bmd = reinterpret_cast<BevelModifierData *>(&md);
if (bmd->vertex_weight_name[0] == '\0') {
STRNCPY(bmd->vertex_weight_name, "bevel_weight_vert");
}
if (bmd->edge_weight_name[0] == '\0') {
STRNCPY(bmd->edge_weight_name, "bevel_weight_edge");
}
}
}
}
}
static void hide_simulation_node_skip_socket_value(Main &bmain)
{
for (bNodeTree &tree : bmain.nodetrees) {
for (bNode &node : tree.nodes) {
if (node.type_legacy != GEO_NODE_SIMULATION_OUTPUT) {
continue;
}
bNodeSocket *skip_input = static_cast<bNodeSocket *>(node.inputs.first);
if (!skip_input || !STREQ(skip_input->identifier, "Skip")) {
continue;
}
auto *default_value = static_cast<bNodeSocketValueBoolean *>(skip_input->default_value);
if (!default_value->value) {
continue;
}
bool is_linked = false;
for (bNodeLink &link : tree.links) {
if (link.tosock == skip_input) {
is_linked = true;
}
}
if (is_linked) {
continue;
}
bNode &input_node = version_node_add_empty(tree, "FunctionNodeInputBool");
input_node.parent = node.parent;
input_node.locx_legacy = node.locx_legacy - 25;
input_node.locy_legacy = node.locy_legacy;
NodeInputBool *input_node_storage = MEM_new<NodeInputBool>(__func__);
input_node.storage = input_node_storage;
input_node_storage->boolean = true;
bNodeSocket &input_node_socket = version_node_add_socket(
tree, input_node, SOCK_OUT, "NodeSocketBool", "Boolean");
version_node_add_link(tree, input_node, input_node_socket, node, *skip_input);
/* Change the old socket value so that the versioning code is not run again. */
default_value->value = false;
}
}
}
void blo_do_versions_430(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 2)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &space_link : area.spacedata) {
if (space_link.spacetype == SPACE_NODE) {
SpaceNode *space_node = reinterpret_cast<SpaceNode *>(&space_link);
space_node->flag &= ~SNODE_FLAG_UNUSED_5;
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 3)) {
for (Brush &brush : bmain->brushes) {
if (BrushGpencilSettings *settings = brush.gpencil_settings) {
/* Copy the `draw_strength` value to the `alpha` value. */
brush.alpha = settings->draw_strength;
/* We approximate the simplify pixel threshold by taking the previous threshold (world
* space) and dividing by the legacy radius conversion factor. This should generally give
* reasonable "pixel" threshold values, at least for previous GPv2 defaults. */
settings->simplify_px = settings->simplify_f /
bke::greasepencil::LEGACY_RADIUS_CONVERSION_FACTOR * 0.1f;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 4)) {
for (Scene &scene : bmain->scenes) {
scene.view_settings.temperature = 6500.0f;
scene.view_settings.tint = 10.0f;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 7)) {
for (Scene &scene : bmain->scenes) {
SequencerToolSettings *sequencer_tool_settings = seq::tool_settings_ensure(&scene);
sequencer_tool_settings->snap_mode |= SEQ_SNAP_TO_PREVIEW_BORDERS |
SEQ_SNAP_TO_PREVIEW_CENTER |
SEQ_SNAP_TO_STRIPS_PREVIEW;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 8)) {
update_paint_modes_for_brush_assets(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 9)) {
fix_built_in_curve_attribute_defaults(bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 10)) {
/* Initialize Color Balance node white point settings. */
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type != NTREE_CUSTOM) {
for (bNode &node : ntree->nodes) {
if (node.type_legacy == CMP_NODE_COLORBALANCE) {
if (version_node_ensure_storage_or_invalidate(node)) {
NodeColorBalance *n = static_cast<NodeColorBalance *>(node.storage);
n->input_temperature = n->output_temperature = 6500.0f;
n->input_tint = n->output_tint = 10.0f;
}
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 11)) {
for (Curves &curves : bmain->hair_curves) {
curves.geometry.attributes_active_index = curves.attributes_active_index_legacy;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 13)) {
Camera default_cam;
for (Camera &camera : bmain->cameras) {
camera.central_cylindrical_range_u_min = default_cam.central_cylindrical_range_u_min;
camera.central_cylindrical_range_u_max = default_cam.central_cylindrical_range_u_max;
camera.central_cylindrical_range_v_min = default_cam.central_cylindrical_range_v_min;
camera.central_cylindrical_range_v_max = default_cam.central_cylindrical_range_v_max;
camera.central_cylindrical_radius = default_cam.central_cylindrical_radius;
}
}
/* The File Output node now uses the linear color space setting of its stored image formats. So
* we need to ensure the color space value is initialized to some sane default based on the image
* type. Furthermore, the node now gained a new Save As Render option that is global to the node,
* which will be used if Use Node Format is enabled for each input, so we potentially need to
* disable Use Node Format in case inputs had different Save As render options. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 14)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type != NTREE_COMPOSIT) {
continue;
}
for (bNode &node : ntree->nodes) {
if (node.type_legacy != CMP_NODE_OUTPUT_FILE) {
continue;
}
if (!version_node_ensure_storage_or_invalidate(node)) {
continue;
}
/* Initialize node format color space if it is not set. */
NodeCompositorFileOutput *storage = static_cast<NodeCompositorFileOutput *>(node.storage);
if (storage->format.linear_colorspace_settings.name[0] == '\0') {
BKE_image_format_update_color_space_for_type(&storage->format);
}
if (node.inputs.is_empty()) {
continue;
}
/* Initialize input formats color space if it is not set. */
for (const bNodeSocket &input : node.inputs) {
NodeImageMultiFileSocket *input_storage = static_cast<NodeImageMultiFileSocket *>(
input.storage);
if (input_storage->format.linear_colorspace_settings.name[0] == '\0') {
BKE_image_format_update_color_space_for_type(&input_storage->format);
}
}
/* EXR images don't use Save As Render. */
if (ELEM(storage->format.imtype, R_IMF_IMTYPE_OPENEXR, R_IMF_IMTYPE_MULTILAYER)) {
continue;
}
/* Find out if all inputs have the same Save As Render option. */
const bNodeSocket *first_input = static_cast<bNodeSocket *>(node.inputs.first);
const NodeImageMultiFileSocket *first_input_storage =
static_cast<NodeImageMultiFileSocket *>(first_input->storage);
const bool first_save_as_render = first_input_storage->save_as_render;
bool all_inputs_have_same_save_as_render = true;
for (const bNodeSocket &input : node.inputs) {
const NodeImageMultiFileSocket *input_storage = static_cast<NodeImageMultiFileSocket *>(
input.storage);
if (bool(input_storage->save_as_render) != first_save_as_render) {
all_inputs_have_same_save_as_render = false;
break;
}
}
/* All inputs have the same save as render option, so we set the node Save As Render option
* to that value, and we leave inputs as is. */
if (all_inputs_have_same_save_as_render) {
storage->save_as_render = first_save_as_render;
continue;
}
/* For inputs that have Use Node Format enabled, we need to disabled it because otherwise
* they will use the node's Save As Render option. It follows that we need to copy the
* node's format to the input format. */
for (const bNodeSocket &input : node.inputs) {
NodeImageMultiFileSocket *input_storage = static_cast<NodeImageMultiFileSocket *>(
input.storage);
if (!input_storage->use_node_format) {
continue;
}
input_storage->use_node_format = false;
input_storage->format = storage->format;
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 15)) {
for (Collection &collection : bmain->collections) {
const ListBaseT<CollectionExport> *exporters = &collection.exporters;
for (CollectionExport &data : *exporters) {
/* The name field should be empty at this point. */
BLI_assert(data.name[0] == '\0');
bke::FileHandlerType *fh = bke::file_handler_find(data.fh_idname);
BKE_collection_exporter_name_set(exporters, &data, fh ? fh->label : DATA_("Undefined"));
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 16)) {
for (Scene &scene : bmain->scenes) {
scene.eevee.flag |= SCE_EEVEE_FAST_GI_ENABLED;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 17)) {
FOREACH_NODETREE_BEGIN (bmain, tree, id) {
if (tree->default_group_node_width == 0) {
tree->default_group_node_width = bke::NodeWidth::Default;
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 20)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_SEQ) {
ARegion *region = BKE_area_find_region_type(&area, RGN_TYPE_TOOLS);
if (region != nullptr) {
region->flag &= ~RGN_FLAG_HIDDEN;
}
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 21)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_CLIP) {
ARegion *region = BKE_area_find_region_type(&area, RGN_TYPE_WINDOW);
if (region != nullptr) {
View2D *v2d = &region->v2d;
v2d->flag &= ~V2D_VIEWSYNC_SCREEN_TIME;
}
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 22)) {
add_bevel_modifier_attribute_name_defaults(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 23)) {
for (Object &object : bmain->objects) {
for (ModifierData &md : object.modifiers) {
if (md.type != eModifierType_Nodes) {
continue;
}
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(&md);
if (nmd.bake_target == NODES_MODIFIER_BAKE_TARGET_INHERIT) {
/* Use disk target for existing modifiers to avoid changing behavior. */
nmd.bake_target = NODES_MODIFIER_BAKE_TARGET_DISK;
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 24)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
node_reroute_add_storage(*ntree);
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 26)) {
hide_simulation_node_skip_socket_value(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 28)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_VIEW3D) {
View3D *v3d = reinterpret_cast<View3D *>(&sl);
copy_v3_fl(v3d->overlay.gpencil_grid_color, 0.5f);
copy_v2_fl(v3d->overlay.gpencil_grid_scale, 1.0f);
copy_v2_fl(v3d->overlay.gpencil_grid_offset, 0.0f);
v3d->overlay.gpencil_grid_subdivisions = 4;
}
}
}
}
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type != NTREE_COMPOSIT) {
continue;
}
for (bNode &node : ntree->nodes.items_mutable()) {
if (ELEM(node.type_legacy, CMP_NODE_VIEWER, CMP_NODE_COMPOSITE_DEPRECATED)) {
node.flag &= ~NODE_PREVIEW;
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 29)) {
/* Open warnings panel by default. */
for (Object &object : bmain->objects) {
for (ModifierData &md : object.modifiers) {
if (md.type == eModifierType_Nodes) {
md.layout_panel_open_flag |= 1 << NODES_MODIFIER_PANEL_WARNINGS;
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 31)) {
for (WorkSpace &workspace : bmain->workspaces) {
for (bToolRef &tref : workspace.tools) {
if (tref.space_type != SPACE_SEQ) {
continue;
}
STRNCPY_UTF8(tref.idname, "builtin.select_box");
}
}
}
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,934 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
#define DNA_DEPRECATED_ALLOW
#include "DNA_ID.h"
#include "DNA_brush_enums.h"
#include "DNA_brush_types.h"
#include "DNA_light_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_node_types.h"
#include "DNA_screen_types.h"
#include "DNA_sequence_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_workspace_types.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BLI_sys_types.h"
#include "BKE_asset.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_customdata.hh"
#include "BKE_grease_pencil_legacy_convert.hh"
#include "BKE_idprop.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_tracking.hh"
#include "SEQ_iterator.hh"
#include "SEQ_sequencer.hh"
#include "readfile.hh"
#include "versioning_common.hh"
// #include "CLG_log.h"
namespace blender {
// static CLG_LogRef LOG = {"blend.doversion"};
/* The Mix mode of the Mix node previously assumed the alpha of the first input as opposed to
* mixing the alpha as well. So we add a separate color node to get the alpha of the first input
* and set it to the result using a set alpha node. */
static void do_version_mix_node_mix_mode_compositor(bNodeTree &node_tree, bNode &node)
{
if (!version_node_ensure_storage_or_invalidate(node)) {
return;
}
const NodeShaderMix *data = reinterpret_cast<NodeShaderMix *>(node.storage);
if (data->data_type != SOCK_RGBA) {
return;
}
if (data->blend_type != MA_RAMP_BLEND) {
return;
}
bNodeSocket *first_input = bke::node_find_socket(node, SOCK_IN, "A_Color"_ustr);
bNodeSocket *output = bke::node_find_socket(node, SOCK_OUT, "Result_Color"_ustr);
/* Find the link going into the inputs of the node. */
bNodeLink *first_link = nullptr;
for (bNodeLink &link : node_tree.links) {
if (link.tosock == first_input) {
first_link = &link;
}
}
bNode &separate_node = version_node_add_empty(node_tree, "CompositorNodeSeparateColor");
/* Preserve the muted state on the new node so restoring all nodes later behaves the same way. */
SET_FLAG_FROM_TEST(separate_node.flag, node.flag & NODE_MUTED, NODE_MUTED);
separate_node.parent = node.parent;
separate_node.location[0] = node.location[0] - 10.0f;
separate_node.location[1] = node.location[1];
NodeCMPCombSepColor *storage = MEM_new<NodeCMPCombSepColor>(__func__);
storage->mode = CMP_NODE_COMBSEP_COLOR_RGB;
separate_node.storage = storage;
bNodeSocket &separate_input = version_node_add_socket(
node_tree, separate_node, SOCK_IN, "NodeSocketColor", "Image");
bNodeSocket &separate_alpha_output = version_node_add_socket(
node_tree, separate_node, SOCK_OUT, "NodeSocketFloat", "Alpha");
copy_v4_v4(separate_input.default_value_typed<bNodeSocketValueRGBA>()->value,
first_input->default_value_typed<bNodeSocketValueRGBA>()->value);
if (first_link) {
version_node_add_link(
node_tree, *first_link->fromnode, *first_link->fromsock, separate_node, separate_input);
}
bNode &set_alpha_node = version_node_add_empty(node_tree, "CompositorNodeSetAlpha");
SET_FLAG_FROM_TEST(set_alpha_node.flag, node.flag & NODE_MUTED, NODE_MUTED);
set_alpha_node.parent = node.parent;
set_alpha_node.location[0] = node.location[0] - 10.0f;
set_alpha_node.location[1] = node.location[1];
set_alpha_node.storage = MEM_new<NodeCMPCombSepColor>(__func__);
bNodeSocket &set_alpha_image_input = version_node_add_socket(
node_tree, set_alpha_node, SOCK_IN, "NodeSocketColor", "Image");
bNodeSocket &set_alpha_alpha_input = version_node_add_socket(
node_tree, set_alpha_node, SOCK_IN, "NodeSocketFloat", "Alpha");
bNodeSocket &set_alpha_type_input = version_node_add_socket(
node_tree, set_alpha_node, SOCK_IN, "NodeSocketMenu", "Type");
bNodeSocket &set_alpha_output = version_node_add_socket(
node_tree, set_alpha_node, SOCK_OUT, "NodeSocketColor", "Image");
set_alpha_type_input.default_value_typed<bNodeSocketValueMenu>()->value =
CMP_NODE_SETALPHA_MODE_REPLACE_ALPHA;
version_node_add_link(node_tree, node, *output, set_alpha_node, set_alpha_image_input);
version_node_add_link(
node_tree, separate_node, separate_alpha_output, set_alpha_node, set_alpha_alpha_input);
for (bNodeLink &link : node_tree.links.items_reversed_mutable()) {
if (link.fromsock == output && link.tonode != &set_alpha_node) {
version_node_add_link(
node_tree, set_alpha_node, set_alpha_output, *link.tonode, *link.tosock);
bke::node_remove_link(&node_tree, link);
}
}
}
/* The Mix mode of the Mix node previously assumed the alpha of the first input as opposed to
* mixing the alpha as well. So we add a separate color node to get the alpha of the first input
* and set it to the result using a pair of separate and combine color nodes. */
static void do_version_mix_node_mix_mode_geometry(bNodeTree &node_tree, bNode &node)
{
if (!version_node_ensure_storage_or_invalidate(node)) {
return;
}
const NodeShaderMix *data = reinterpret_cast<NodeShaderMix *>(node.storage);
if (data->data_type != SOCK_RGBA) {
return;
}
if (data->blend_type != MA_RAMP_BLEND) {
return;
}
bNodeSocket *first_input = bke::node_find_socket(node, SOCK_IN, "A_Color"_ustr);
bNodeSocket *output = bke::node_find_socket(node, SOCK_OUT, "Result_Color"_ustr);
/* Find the link going into the inputs of the node. */
bNodeLink *first_link = nullptr;
for (bNodeLink &link : node_tree.links) {
if (link.tosock == first_input) {
first_link = &link;
}
}
bNode &separate_alpha_node = version_node_add_empty(node_tree, "FunctionNodeSeparateColor");
separate_alpha_node.parent = node.parent;
separate_alpha_node.location[0] = node.location[0] - 10.0f;
separate_alpha_node.location[1] = node.location[1];
NodeCombSepColor *separate_alpha_storage = MEM_new<NodeCombSepColor>(__func__);
separate_alpha_storage->mode = NODE_COMBSEP_COLOR_RGB;
separate_alpha_node.storage = separate_alpha_storage;
bNodeSocket &separate_alpha_input = version_node_add_socket(
node_tree, separate_alpha_node, SOCK_IN, "NodeSocketColor", "Color");
bNodeSocket &separate_alpha_output = version_node_add_socket(
node_tree, separate_alpha_node, SOCK_OUT, "NodeSocketFloat", "Alpha");
copy_v4_v4(separate_alpha_input.default_value_typed<bNodeSocketValueRGBA>()->value,
first_input->default_value_typed<bNodeSocketValueRGBA>()->value);
if (first_link) {
version_node_add_link(node_tree,
*first_link->fromnode,
*first_link->fromsock,
separate_alpha_node,
separate_alpha_input);
}
bNode &separate_color_node = version_node_add_empty(node_tree, "FunctionNodeSeparateColor");
separate_color_node.parent = node.parent;
separate_color_node.location[0] = node.location[0] - 10.0f;
separate_color_node.location[1] = node.location[1];
NodeCombSepColor *separate_color_storage = MEM_new<NodeCombSepColor>(__func__);
separate_color_storage->mode = NODE_COMBSEP_COLOR_RGB;
separate_color_node.storage = separate_color_storage;
bNodeSocket &separate_color_input = version_node_add_socket(
node_tree, separate_color_node, SOCK_IN, "NodeSocketColor", "Color");
bNodeSocket &separate_color_red_output = version_node_add_socket(
node_tree, separate_color_node, SOCK_OUT, "NodeSocketFloat", "Red");
bNodeSocket &separate_color_green_output = version_node_add_socket(
node_tree, separate_color_node, SOCK_OUT, "NodeSocketFloat", "Green");
bNodeSocket &separate_color_blue_output = version_node_add_socket(
node_tree, separate_color_node, SOCK_OUT, "NodeSocketFloat", "Blue");
version_node_add_link(node_tree, node, *output, separate_color_node, separate_color_input);
bNode &combine_color_node = version_node_add_empty(node_tree, "FunctionNodeCombineColor");
combine_color_node.parent = node.parent;
combine_color_node.location[0] = node.location[0] - 10.0f;
combine_color_node.location[1] = node.location[1];
NodeCombSepColor *combine_color_storage = MEM_new<NodeCombSepColor>(__func__);
combine_color_storage->mode = NODE_COMBSEP_COLOR_RGB;
combine_color_node.storage = combine_color_storage;
bNodeSocket &combine_color_red_input = version_node_add_socket(
node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Red");
bNodeSocket &combine_color_green_input = version_node_add_socket(
node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Green");
bNodeSocket &combine_color_blue_input = version_node_add_socket(
node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Blue");
bNodeSocket &combine_color_alpha_input = version_node_add_socket(
node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Alpha");
bNodeSocket &combine_color_output = version_node_add_socket(
node_tree, combine_color_node, SOCK_OUT, "NodeSocketColor", "Color");
version_node_add_link(node_tree,
separate_color_node,
separate_color_red_output,
combine_color_node,
combine_color_red_input);
version_node_add_link(node_tree,
separate_color_node,
separate_color_green_output,
combine_color_node,
combine_color_green_input);
version_node_add_link(node_tree,
separate_color_node,
separate_color_blue_output,
combine_color_node,
combine_color_blue_input);
version_node_add_link(node_tree,
separate_alpha_node,
separate_alpha_output,
combine_color_node,
combine_color_alpha_input);
for (bNodeLink &link : node_tree.links.items_reversed_mutable()) {
if (link.fromsock == output && link.tonode != &separate_color_node) {
version_node_add_link(
node_tree, combine_color_node, combine_color_output, *link.tonode, *link.tosock);
bke::node_remove_link(&node_tree, link);
}
}
}
static void init_node_tool_operator_idnames(Main &bmain)
{
for (bNodeTree &group : bmain.nodetrees) {
if (group.type != NTREE_GEOMETRY) {
continue;
}
if (!group.geometry_node_asset_traits) {
continue;
}
if (group.geometry_node_asset_traits->node_tool_idname) {
continue;
}
std::string name_str = "geometry.";
for (char c : StringRef(BKE_id_name(group.id))) {
c = tolower(c);
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
name_str.push_back(c);
}
else {
const bool last_is_underscore = name_str[name_str.size() - 1] == '_';
if (!last_is_underscore) {
name_str.push_back('_');
}
}
}
group.geometry_node_asset_traits->node_tool_idname = BLI_strdupn(name_str.c_str(),
name_str.size());
if (group.id.asset_data) {
auto property = bke::idprop::create(
"node_tool_idname", StringRefNull(group.geometry_node_asset_traits->node_tool_idname));
BKE_asset_metadata_idprop_ensure(group.id.asset_data, property.release());
}
}
}
static void version_realize_instances_to_curve_domain(Main &bmain)
{
for (bNodeTree &node_tree : bmain.nodetrees) {
if (node_tree.type != NTREE_GEOMETRY) {
continue;
}
for (bNode &node : node_tree.nodes) {
if (node.type_legacy != GEO_NODE_REALIZE_INSTANCES) {
continue;
}
node.custom1 |= GEO_NODE_REALIZE_TO_POINT_DOMAIN;
}
}
}
static void version_mesh_uv_map_strings(Main &bmain)
{
for (Mesh &mesh : bmain.meshes) {
const CustomData *data = &mesh.corner_data;
if (!mesh.active_uv_map_attribute) {
if (const char *name = CustomData_get_active_layer_name(data, CD_PROP_FLOAT2)) {
mesh.active_uv_map_attribute = BLI_strdup(name);
}
}
if (!mesh.default_uv_map_attribute) {
if (const char *name = CustomData_get_render_layer_name(data, CD_PROP_FLOAT2)) {
mesh.default_uv_map_attribute = BLI_strdup(name);
}
}
}
}
static void version_clear_unused_strip_flags(Main &bmain)
{
for (Scene &scene : bmain.scenes) {
Editing *ed = seq::editing_get(&scene);
if (ed != nullptr) {
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
constexpr int flag_overlap = 1 << 3;
constexpr int flag_ipo_frame_locked = 1 << 8;
constexpr int flag_effect_not_loaded = 1 << 9;
constexpr int flag_delete = 1 << 10;
constexpr int flag_ignore_channel_lock = 1 << 16;
constexpr int flag_show_offsets = 1 << 20;
strip->flag &= ~eStripFlag(flag_overlap | flag_ipo_frame_locked | flag_effect_not_loaded |
flag_delete | flag_ignore_channel_lock | flag_show_offsets);
return true;
});
}
}
}
static void version_string_to_curves_node_inputs(bNodeTree &tree, bNode &node)
{
if (!node.storage) {
return;
}
auto &storage = *reinterpret_cast<NodeGeometryStringToCurves *>(node.storage);
if (!blender::bke::node_find_socket(node, SOCK_IN, "Font"_ustr)) {
bNodeSocket &socket = version_node_add_socket(tree, node, SOCK_IN, "NodeSocketFont", "Font");
socket.default_value_typed<bNodeSocketValueFont>()->value = reinterpret_cast<VFont *>(node.id);
node.id = nullptr;
}
if (!blender::bke::node_find_socket(node, SOCK_IN, "Overflow"_ustr)) {
bNodeSocket &socket = version_node_add_socket(
tree, node, SOCK_IN, "NodeSocketMenu", "Overflow");
socket.default_value_typed<bNodeSocketValueMenu>()->value = storage.overflow;
}
if (!blender::bke::node_find_socket(node, SOCK_IN, "Align X"_ustr)) {
bNodeSocket &socket = version_node_add_socket(
tree, node, SOCK_IN, "NodeSocketMenu", "Align X");
socket.default_value_typed<bNodeSocketValueMenu>()->value = storage.align_x;
}
if (!blender::bke::node_find_socket(node, SOCK_IN, "Align Y"_ustr)) {
bNodeSocket &socket = version_node_add_socket(
tree, node, SOCK_IN, "NodeSocketMenu", "Align Y");
socket.default_value_typed<bNodeSocketValueMenu>()->value = storage.align_y;
}
if (!blender::bke::node_find_socket(node, SOCK_IN, "Pivot Point"_ustr)) {
bNodeSocket &socket = version_node_add_socket(
tree, node, SOCK_IN, "NodeSocketMenu", "Pivot Point");
socket.default_value_typed<bNodeSocketValueMenu>()->value = storage.pivot_mode;
}
}
static const char *legacy_pass_name_to_new_name(const char *name)
{
if (STREQ(name, "DiffDir")) {
return "Diffuse Direct";
}
if (STREQ(name, "DiffInd")) {
return "Diffuse Indirect";
}
if (STREQ(name, "DiffCol")) {
return "Diffuse Color";
}
if (STREQ(name, "GlossDir")) {
return "Glossy Direct";
}
if (STREQ(name, "GlossInd")) {
return "Glossy Indirect";
}
if (STREQ(name, "GlossCol")) {
return "Glossy Color";
}
if (STREQ(name, "TransDir")) {
return "Transmission Direct";
}
if (STREQ(name, "TransInd")) {
return "Transmission Indirect";
}
if (STREQ(name, "TransCol")) {
return "Transmission Color";
}
if (STREQ(name, "VolumeDir")) {
return "Volume Direct";
}
if (STREQ(name, "VolumeInd")) {
return "Volume Indirect";
}
if (STREQ(name, "VolumeCol")) {
return "Volume Color";
}
if (STREQ(name, "AO")) {
return "Ambient Occlusion";
}
if (STREQ(name, "Env")) {
return "Environment";
}
if (STREQ(name, "IndexMA")) {
return "Material Index";
}
if (STREQ(name, "IndexOB")) {
return "Object Index";
}
if (STREQ(name, "GreasePencil")) {
return "Grease Pencil";
}
if (STREQ(name, "Emit")) {
return "Emission";
}
if (STREQ(name, "Z")) {
return "Depth";
}
if (STREQ(name, "Speed")) {
return "Vector";
}
return name;
}
static void do_version_light_remove_use_nodes(Main *bmain, Light *light)
{
if (light->use_nodes) {
return;
}
/* Users defined a light node tree, but deactivated it by disabling "Use Nodes". So we
* simulate the same effect by creating a new Light Output node and setting it to active. */
bNodeTree *ntree = light->nodetree;
if (ntree == nullptr) {
/* In case the light was defined through Python API it might have been missing a node tree.
*/
ntree = bke::node_tree_add_tree_embedded(
bmain, &light->id, "Light Node Tree Versioning", "ShaderNodeTree");
}
bNode *old_output = nullptr;
for (bNode &node : ntree->nodes) {
if (STREQ(node.idname, "ShaderNodeOutputLight") && (node.flag & NODE_DO_OUTPUT)) {
old_output = &node;
old_output->flag &= ~NODE_DO_OUTPUT;
}
}
bNode &new_output = version_node_add_empty(*ntree, "ShaderNodeOutputLight");
bNodeSocket &output_surface_input = version_node_add_socket(
*ntree, new_output, SOCK_IN, "NodeSocketShader", "Surface");
new_output.flag |= NODE_DO_OUTPUT;
bNode &emission = version_node_add_empty(*ntree, "ShaderNodeEmission");
bNodeSocket &emission_color_input = version_node_add_socket(
*ntree, emission, SOCK_IN, "NodeSocketColor", "Color");
bNodeSocket &emission_strength_input = version_node_add_socket(
*ntree, emission, SOCK_IN, "NodeSocketFloat", "Strength");
bNodeSocket &emission_output = version_node_add_socket(
*ntree, emission, SOCK_OUT, "NodeSocketShader", "Emission");
version_node_add_link(*ntree, emission, emission_output, new_output, output_surface_input);
bNodeSocketValueRGBA *rgba = emission_color_input.default_value_typed<bNodeSocketValueRGBA>();
rgba->value[0] = 1.0f;
rgba->value[1] = 1.0f;
rgba->value[2] = 1.0f;
rgba->value[3] = 1.0f;
emission_strength_input.default_value_typed<bNodeSocketValueFloat>()->value = 1.0f;
if (old_output != nullptr) {
/* Position the newly created node after the old output. Assume the old output node is at
* the far right of the node tree. */
emission.location[0] = old_output->location[0] + 1.5f * old_output->width;
emission.location[1] = old_output->location[1];
}
else {
/* Use default position, see #node_tree_shader_default() */
emission.location[0] = -200.0f;
emission.location[1] = 100.0f;
}
new_output.location[0] = emission.location[0] + 2.0f * emission.width;
new_output.location[1] = emission.location[1];
}
/* For cycles, the Denoising Albedo render pass is now registered after the Denoising Normal pass
* to match the compositor Denoise node. So we swap the order of Denoising Albedo and Denoising
* Normal sockets in the Render Layers node that has been saved with the old order. */
static void do_version_render_layers_node_albedo_normal_swap(bNode &node)
{
bNodeSocket *socket_denoise_normal = nullptr;
bNodeSocket *socket_denoise_albedo = nullptr;
for (bNodeSocket &socket : node.outputs) {
if (STREQ(socket.identifier, "Denoising Normal")) {
socket_denoise_normal = &socket;
}
if (STREQ(socket.identifier, "Denoising Albedo")) {
socket_denoise_albedo = &socket;
}
}
if (socket_denoise_albedo && socket_denoise_normal) {
BLI_listbase_swaplinks(&node.outputs, socket_denoise_normal, socket_denoise_albedo);
}
}
/* Some nodes no longer have storage but their storage is still allocated at write time for
* forward compatibility. This only happens during writes from 4.5, so we need to free this
* storage again when loading any file from 4.5. But before this versioning was done, it was
* possible to save a file from 4.5 in 5.0 or 5.1 and it would still have the storage, so we also
* need to include versions up to the current 5.1 subversion. */
static void free_compositor_forward_compatibility_storage(bNode &node)
{
if (!node.storage) {
return;
}
switch (node.type_legacy) {
case CMP_NODE_BOKEHIMAGE:
MEM_delete(static_cast<NodeBokehImage *>(node.storage));
break;
case CMP_NODE_MASK:
MEM_delete(static_cast<NodeMask *>(node.storage));
break;
case CMP_NODE_ANTIALIASING:
MEM_delete(static_cast<NodeAntiAliasingData *>(node.storage));
break;
case CMP_NODE_VECBLUR:
MEM_delete(static_cast<NodeBlurData *>(node.storage));
break;
case CMP_NODE_CHROMA_MATTE:
case CMP_NODE_COLOR_MATTE:
case CMP_NODE_DIFF_MATTE:
case CMP_NODE_LUMA_MATTE:
MEM_delete(static_cast<NodeChroma *>(node.storage));
break;
case CMP_NODE_COLORCORRECTION:
MEM_delete(static_cast<NodeColorCorrection *>(node.storage));
break;
case CMP_NODE_MASK_BOX:
MEM_delete(static_cast<NodeBoxMask *>(node.storage));
break;
case CMP_NODE_MASK_ELLIPSE:
MEM_delete(static_cast<NodeEllipseMask *>(node.storage));
break;
case CMP_NODE_SUNBEAMS_DEPRECATED:
MEM_delete(static_cast<NodeSunBeams *>(node.storage));
break;
case CMP_NODE_DBLUR:
MEM_delete(static_cast<NodeDBlurData *>(node.storage));
break;
case CMP_NODE_BILATERALBLUR:
MEM_delete(static_cast<NodeBilateralBlurData *>(node.storage));
break;
case CMP_NODE_CROP:
MEM_delete(static_cast<NodeTwoXYs *>(node.storage));
break;
case CMP_NODE_COLORBALANCE:
MEM_delete(static_cast<NodeColorBalance *>(node.storage));
break;
default:
return;
}
node.storage = nullptr;
}
static void convert_brush_flags_to_type(Brush &brush)
{
if (brush.flag & BRUSH_UNUSED_1) {
brush.flag &= ~BRUSH_UNUSED_1;
brush.stroke_method = BRUSH_STROKE_AIRBRUSH;
}
else if (brush.flag & BRUSH_UNUSED_2) {
brush.flag &= ~BRUSH_UNUSED_2;
brush.stroke_method = BRUSH_STROKE_ANCHORED;
}
else if (brush.flag & BRUSH_UNUSED_3) {
brush.flag &= ~BRUSH_UNUSED_3;
brush.stroke_method = BRUSH_STROKE_SPACE;
}
else if (brush.flag & BRUSH_UNUSED_4) {
brush.flag &= ~BRUSH_UNUSED_4;
brush.stroke_method = BRUSH_STROKE_DRAG_DOT;
}
else if (brush.flag & BRUSH_UNUSED_5) {
brush.flag &= ~BRUSH_UNUSED_5;
brush.stroke_method = BRUSH_STROKE_LINE;
}
else if (brush.flag & BRUSH_UNUSED_6) {
brush.flag &= ~BRUSH_UNUSED_6;
brush.stroke_method = BRUSH_STROKE_CURVE;
}
else {
brush.stroke_method = BRUSH_STROKE_DOTS;
}
}
void do_versions_after_linking_510(FileData *fd, Main *bmain)
{
/* Some blend files were saved with an invalid active viewer key, possibly due to a bug that
* was fixed already in c8cb24121f, but blend files were never updated. So starting in 5.1, we
* fix those files by essentially doing what ED_node_set_active_viewer_key is supposed to do at
* load time during versioning. Note that the invalid active viewer will just cause a harmless
* assert, so this does not need to exist in previous releases. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 0)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &space : area.spacedata) {
if (space.spacetype == SPACE_NODE) {
SpaceNode *space_node = reinterpret_cast<SpaceNode *>(&space);
bNodeTreePath *path = static_cast<bNodeTreePath *>(space_node->treepath.last);
if (space_node->nodetree && path) {
space_node->nodetree->active_viewer_key = path->parent_key;
}
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 0)) {
version_clear_unused_strip_flags(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 24)) {
/* Note: For legacy Grease Pencil objects (#OB_GPENCIL_LEGACY) this is handled as part of
* bke::greasepencil::convert::legacy_main. */
bke::greasepencil::convert::material_stroke_fill_toggles_to_attributes(
*bmain, {}, *fd->reports);
/* Set the stroke mode for all brushes. */
for (Brush &brush : bmain->brushes) {
if (BrushGpencilSettings *settings = brush.gpencil_settings) {
if (Material *material = settings->material) {
BLI_assert(material->gp_style != nullptr);
SET_FLAG_FROM_TEST(settings->flag2,
(material->gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0,
GP_BRUSH_USE_STROKE);
SET_FLAG_FROM_TEST(settings->flag2,
(material->gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0,
GP_BRUSH_USE_FILL);
}
else {
settings->flag2 |= GP_BRUSH_USE_STROKE;
settings->flag2 &= ~GP_BRUSH_USE_FILL;
}
}
}
/* Set the color to transparent for when the stroke/fill is disabled. */
for (Material &material : bmain->materials) {
if (material.gp_style == nullptr) {
continue;
}
MaterialGPencilStyle &gp_style = *material.gp_style;
if ((gp_style.flag & GP_MATERIAL_STROKE_SHOW) == 0) {
gp_style.stroke_rgba[3] = 0.0f;
}
if ((gp_style.flag & GP_MATERIAL_FILL_SHOW) == 0) {
gp_style.fill_rgba[3] = 0.0f;
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
}
void blo_do_versions_510(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 1)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == SH_NODE_MIX) {
do_version_mix_node_mix_mode_compositor(*node_tree, node);
}
}
}
else if (node_tree->type == NTREE_GEOMETRY) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == SH_NODE_MIX) {
do_version_mix_node_mix_mode_geometry(*node_tree, node);
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 5)) {
version_realize_instances_to_curve_domain(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 7)) {
version_mesh_uv_map_strings(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 8)) {
for (Object &obj : bmain->objects) {
if (!obj.pose) {
continue;
}
for (bPoseChannel &pose_bone : obj.pose->chanbase) {
/* Those flags were previously unused, so to be safe we clear them. */
pose_bone.flag &= ~(POSE_SELECTED_ROOT | POSE_SELECTED_TIP);
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 9)) {
init_node_tool_operator_idnames(*bmain);
for (Scene &scene : bmain->scenes) {
scene.r.ffcodecdata.custom_constant_rate_factor = 23;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 10)) {
for (wmWindowManager &wm : bmain->wm) {
wm.xr.session_settings.view_scale = 1.0f;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 12)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
version_node_input_socket_name(node_tree, CMP_NODE_CRYPTOMATTE_LEGACY, "image", "Image");
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 13)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == CMP_NODE_R_LAYERS) {
for (bNodeSocket &socket : node.outputs) {
const char *new_pass_name = legacy_pass_name_to_new_name(socket.name);
STRNCPY(socket.name, new_pass_name);
const char *new_pass_identifier = legacy_pass_name_to_new_name(socket.identifier);
version_node_socket_identifier_set(socket, new_pass_identifier);
}
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 14)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_IMAGE) {
SpaceImage *sima = reinterpret_cast<SpaceImage *>(&sl);
sima->uv_edge_opacity = sima->uv_opacity;
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 16)) {
for (Scene &scene : bmain->scenes) {
if (scene.toolsettings) {
scene.toolsettings->anim_mirror_object = nullptr;
scene.toolsettings->anim_relative_object = nullptr;
scene.toolsettings->anim_mirror_bone[0] = '\0';
}
}
}
/* This has no version check and always runs for all versions because there is forward
* compatibility code at write time that reallocates the storage, so we need to free it
* regardless of the version. */
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
for (bNode &node : node_tree->nodes) {
if (ELEM(node.type_legacy, CMP_NODE_IMAGE, CMP_NODE_R_LAYERS)) {
for (bNodeSocket &socket : node.outputs) {
if (socket.storage) {
MEM_delete_void(socket.storage);
socket.storage = nullptr;
}
}
}
}
}
}
FOREACH_NODETREE_END;
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 15)) {
for (Light &light : bmain->lights) {
do_version_light_remove_use_nodes(bmain, &light);
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 17)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == CMP_NODE_MOVIEDISTORTION) {
if (node.storage) {
BKE_tracking_distortion_free(static_cast<MovieDistortion *>(node.storage));
}
node.storage = nullptr;
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 18)) {
FOREACH_NODETREE_BEGIN (bmain, tree, id) {
if (tree->type == NTREE_GEOMETRY) {
for (bNode &node : tree->nodes) {
if (node.type_legacy == GEO_NODE_STRING_TO_CURVES) {
version_string_to_curves_node_inputs(*tree, node);
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 19)) {
for (Mesh &mesh : bmain->meshes) {
bke::mesh_convert_customdata_to_storage(mesh);
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 20)) {
for (Scene &scene : bmain->scenes) {
SequencerToolSettings *seq_ts = seq::tool_settings_ensure(&scene);
constexpr eSequencerSnapMode SEQ_SNAP_TO_FRAME_RANGE_OLD = eSequencerSnapMode(1 << 8);
/* Snap to frame range was bit 8, now bit 9, to make room for snap to increment in bit 8.
*/
if (seq_ts->snap_mode & SEQ_SNAP_TO_FRAME_RANGE_OLD) {
seq_ts->snap_mode &= ~SEQ_SNAP_TO_FRAME_RANGE_OLD;
seq_ts->snap_mode |= SEQ_SNAP_TO_FRAME_RANGE;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 21)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == CMP_NODE_R_LAYERS) {
do_version_render_layers_node_albedo_normal_swap(node);
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 22)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
for (bNode &node : node_tree->nodes) {
free_compositor_forward_compatibility_storage(node);
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 23)) {
for (Brush &brush : bmain->brushes) {
convert_brush_flags_to_type(brush);
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 24)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
/* The 'Viewer Region' option was removed from the UI. */
node_tree->flag &= ~NTREE_VIEWER_BORDER;
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 25)) {
for (Scene &scene : bmain->scenes) {
scene.eevee.direct_light_intensity = 1.0f;
scene.eevee.indirect_light_intensity = 1.0f;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 27)) {
for (Scene &scene : bmain->scenes) {
if (scene.toolsettings) {
const short snap_geom_old = SCE_SNAP_TO_VERTEX | SCE_SNAP_TO_EDGE | SCE_SNAP_TO_FACE |
SCE_SNAP_TO_EDGE_MIDPOINT | SCE_SNAP_TO_EDGE_PERPENDICULAR;
static_assert(snap_geom_old == 63);
if (scene.toolsettings->snap_mode_tools == snap_geom_old) {
scene.toolsettings->snap_mode_tools = SCE_SNAP_TO_GEOM;
}
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
}
} // namespace blender

View File

@@ -0,0 +1,924 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
#define DNA_DEPRECATED_ALLOW
#include "NOD_geometry_nodes_srna.hh"
#include "DNA_ID.h"
#include "DNA_brush_types.h"
#include "DNA_camera_types.h"
#include "DNA_curve_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_tree_interface_types.h"
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_xr_types.h"
#include "BLI_listbase_iterator.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_sys_types.h"
#include "BKE_anim_visualization.h"
#include "BKE_animsys.h"
#include "BKE_attribute.hh"
#include "BKE_colortools.hh"
#include "BKE_curves.hh"
#include "BKE_idprop.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_override.hh"
#include "BKE_main.hh"
#include "BKE_mesh_legacy_convert.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_report.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_sequencer.hh"
#include "BLO_read_write.hh"
#include "readfile.hh"
#include "versioning_common.hh"
// #include "CLG_log.h"
namespace blender {
// static CLG_LogRef LOG = {"blend.doversion"};
static void version_geometry_nodes_properties(FileData &fd,
Main &bmain,
Object &object,
NodesModifierData &nmd)
{
const IDProperty *old_props = nmd.settings_legacy.properties;
if (!old_props) {
/* Versioning has already been done, this check makes the function idempotent. */
return;
}
if (!nmd.node_group) {
IDP_FreeProperty(nmd.settings_legacy.properties);
nmd.settings_legacy.properties = nullptr;
BLO_reportf_wrap(fd.reports,
RPT_WARNING,
"Modifier '%s' from Object '%s' is missing its Geometry Node Group, its "
"settings will be lost (reset to default).",
nmd.modifier.name,
BKE_id_name(object.id));
return;
}
if (ID_MISSING(&nmd.node_group->id)) {
/* Keeping the old idproperties is not an option, and not really useful, since if the
* blend-file is saved in this current state, it won't be re-versioned here later anyway.
*
* Furthermore, the whole remaining part of the code expects this to be nullptr, and keeping it
* at runtime actually causes weird issues in depsgraph nodes building phase.
*
* So all in all, it's simpler and safer to also just lose these values here - if file is not
* saved in this state, next loading will do the versioning if the node-group is available
* again, otherwise that data is lost.
*/
IDP_FreeProperty(nmd.settings_legacy.properties);
nmd.settings_legacy.properties = nullptr;
BLO_reportf_wrap(
fd.reports,
RPT_WARNING,
"Modifier '%s' from Object '%s' is using a missing linked Geometry Node Group, its "
"settings will be lost (reset to default) if the file is saved in this state.",
nmd.modifier.name,
BKE_id_name(object.id));
return;
}
const bNodeTree &ntree = *nmd.node_group;
ntree.ensure_interface_cache();
IDProperty *system_props = bke::idprop::create_group("NodesModifierProperties").release();
IDProperty *inputs = bke::idprop::create_group("inputs").release();
IDP_AddToGroup(system_props, inputs);
const std::string inputs_path_prefix = fmt::format("modifiers[\"{}\"]", nmd.modifier.name);
for (const bNodeTreeInterfaceSocket *input : ntree.interface_inputs()) {
const StringRefNull identifier = input->identifier;
IDProperty *old_value_prop = IDP_GetPropertyFromGroup(old_props, identifier);
if (!old_value_prop) {
continue;
}
IDProperty *group = bke::idprop::create_group(identifier).release();
IDP_AddToGroup(inputs, group);
if (input->flag & NODE_INTERFACE_SOCKET_LAYER_SELECTION) {
IDP_AddToGroup(
group, bke::idprop::create("type", int(nodes::GeometryNodesInputType::Layer)).release());
const StringRefNull layer_name = [&]() {
const IDProperty *layer_name = IDP_GetPropertyTypeFromGroup(
old_props, identifier, IDP_STRING);
if (layer_name) {
return StringRefNull(IDP_string_get(layer_name));
}
return StringRefNull();
}();
IDP_AddToGroup(group, bke::idprop::create("layer_name", layer_name).release());
continue;
}
IDProperty *new_value_prop = IDP_CopyProperty(old_value_prop);
STRNCPY(new_value_prop->name, "value");
IDP_AddToGroup(group, new_value_prop);
const std::string old_value_path = fmt::format("[\"{}\"]", identifier);
const std::string new_value_path = fmt::format(".properties.inputs.{}.value", identifier);
BKE_animdata_fix_paths_rename_all_ex(&bmain,
&object.id,
inputs_path_prefix.c_str(),
old_value_path.c_str(),
new_value_path.c_str(),
0,
0,
false,
false);
if (IDOverrideLibrary *override_library = object.id.override_library) {
for (IDOverrideLibraryProperty &prop : override_library->properties) {
const StringRef path = prop.rna_path;
const int64_t i = path.find(inputs_path_prefix);
if (i == StringRef::not_found) {
continue;
}
if (path.drop_known_prefix(inputs_path_prefix) != old_value_path) {
continue;
}
MEM_delete(prop.rna_path);
prop.rna_path = BLI_sprintfN("%s%s", inputs_path_prefix.c_str(), new_value_path.c_str());
}
}
bool use_attribute = false;
if (const IDProperty *use_attribute_prop = IDP_GetPropertyFromGroup(
old_props, identifier + "_use_attribute"))
{
/* This property changed to an enum property and animation is not versioned. */
if (use_attribute_prop->type == IDP_INT) {
use_attribute = bool(IDP_int_get(use_attribute_prop));
}
else if (use_attribute_prop->type == IDP_BOOLEAN) {
use_attribute = bool(IDP_bool_get(use_attribute_prop));
}
}
const auto input_type = use_attribute ? nodes::GeometryNodesInputType::Attribute :
nodes::GeometryNodesInputType::Value;
IDP_AddToGroup(group, bke::idprop::create("type", int(input_type)).release());
const StringRefNull attribute_name = [&]() {
const IDProperty *attribute_name = IDP_GetPropertyTypeFromGroup(
old_props, identifier + "_attribute_name", IDP_STRING);
if (attribute_name) {
return StringRefNull(IDP_string_get(attribute_name));
}
return StringRefNull();
}();
IDP_AddToGroup(group, bke::idprop::create("attribute_name", attribute_name).release());
}
IDProperty *outputs = bke::idprop::create_group("outputs").release();
IDP_AddToGroup(system_props, outputs);
for (const bNodeTreeInterfaceSocket *output : ntree.interface_outputs()) {
const StringRef identifier = output->identifier;
IDProperty *old_name_prop = IDP_GetPropertyTypeFromGroup(
old_props, identifier + "_attribute_name", IDP_STRING);
if (!old_name_prop) {
continue;
}
IDProperty *group = bke::idprop::create_group(identifier).release();
IDP_AddToGroup(outputs, group);
IDProperty *new_value_prop = IDP_CopyProperty(old_name_prop);
STRNCPY(new_value_prop->name, "attribute_name");
IDP_AddToGroup(group, new_value_prop);
}
if (nmd.modifier.system_properties) {
IDP_FreeProperty(nmd.modifier.system_properties);
}
nmd.modifier.system_properties = system_props;
IDP_FreeProperty(nmd.settings_legacy.properties);
nmd.settings_legacy.properties = nullptr;
}
static void sanitize_node_tree_interface_socket_identifiers(bNodeTree &node_tree)
{
node_tree.ensure_interface_cache();
Set<StringRef> all_identifiers;
Map<std::string, StringRefNull> identifier_map;
for (bNodeTreeInterfaceItem *item : node_tree.interface_items()) {
if (item->item_type == NodeTreeInterfaceItemType::Panel) {
continue;
}
auto &socket = *bke::node_interface::get_item_as<bNodeTreeInterfaceSocket>(item);
/* Socket identifiers are required to be valid RNA identifiers and unique. */
if (!RNA_validate_identifier(socket.identifier, true)) {
std::string prev_identifier(socket.identifier);
RNA_identifier_sanitize(socket.identifier, true);
if (all_identifiers.contains(socket.identifier)) {
std::string new_identifier = BLI_uniquename_cb(
[&](StringRef name) { return all_identifiers.contains(name); },
'_',
socket.identifier);
MEM_SAFE_DELETE(socket.identifier);
socket.identifier = BLI_strdup(new_identifier.c_str());
}
identifier_map.add(std::move(prev_identifier), socket.identifier);
}
all_identifiers.add(socket.identifier);
}
/* Rename all the node socket identifiers that got changed in the interface. */
if (!identifier_map.is_empty()) {
for (bNode &node : node_tree.nodes) {
if (!(node.is_group_input() || node.is_group_output())) {
continue;
}
ListBaseT<bNodeSocket> sockets = node.is_group_output() ? node.inputs : node.outputs;
for (bNodeSocket &socket : sockets) {
if (identifier_map.contains(socket.identifier)) {
version_node_socket_identifier_set(socket, identifier_map.lookup(socket.identifier));
}
}
}
}
}
/* Saving file extension is now a property of the File Output node. So inherit this
* setting from the active scene to restore the old behavior.
* Note: One limitation is that node groups containing file outputs that are not part of any
* scene are not affected by versioning. */
static void do_version_file_output_use_file_extension_recursive(bNodeTree &node_tree,
const Scene &scene)
{
for (bNode &node : node_tree.nodes) {
if (node.type_legacy == CMP_NODE_OUTPUT_FILE) {
NodeCompositorFileOutput *data = static_cast<NodeCompositorFileOutput *>(node.storage);
data->use_file_extension = (scene.r.scemode & R_EXTENSION) != 0;
}
else if (node.type_legacy == NODE_GROUP) {
bNodeTree *ngroup = id_cast<bNodeTree *>(node.id);
if (ngroup) {
do_version_file_output_use_file_extension_recursive(*ngroup, scene);
}
}
}
}
static void version_clear_strip_linear_modifier_flag(Main &bmain)
{
for (Scene &scene : bmain.scenes) {
Editing *ed = seq::editing_get(&scene);
if (ed != nullptr) {
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
constexpr eStripFlag flag_linear_modifiers = eStripFlag(1 << 23);
strip->flag &= ~flag_linear_modifiers;
return true;
});
}
}
}
static void version_text_strip_space_line(Main &bmain)
{
for (Scene &scene : bmain.scenes) {
Editing *ed = seq::editing_get(&scene);
if (ed == nullptr) {
continue;
}
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
if (strip->type == STRIP_TYPE_TEXT && strip->effectdata != nullptr) {
TextVars *data = static_cast<TextVars *>(strip->effectdata);
data->space_line = 1.0f;
}
return true;
});
}
}
static void version_compositor_effect_initialized(Main &bmain)
{
/* A file with compositor effects that was saved, opened in
* previous version and saved there, would have lost the
* compositor effect data since earlier versions would not
* write it. Ensure the effect data is not null. */
for (Scene &scene : bmain.scenes) {
if (scene.ed) {
seq::foreach_strip(&scene.ed->seqbase, [&](Strip *strip) {
if (strip->type == STRIP_TYPE_COMPOSITOR) {
seq::effect_ensure_initialized(strip);
}
return true;
});
}
}
}
static void version_text_strip_abs_space_line(Main &bmain)
{
for (Scene &scene : bmain.scenes) {
Editing *ed = seq::editing_get(&scene);
if (ed == nullptr) {
continue;
}
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
if (strip->type == STRIP_TYPE_TEXT && strip->effectdata != nullptr) {
TextVars *data = static_cast<TextVars *>(strip->effectdata);
data->abs_space_line = 60.0f;
data->flag &= ~SEQ_TEXT_USE_ABSOLUTE_LINE_SPACING;
}
return true;
});
}
}
static void fix_single_point_curves_custom_knots(Main *bmain)
{
/* Fix corrupted flagu/flagv values created by older versions of the Curve Pen tool.
* The tool could create loose vertices with invalid flag values (e.g. -2), where
* CU_NURB_CUSTOM was set alongside other flags and knotsu/knotsv was left null,
* causing a crash when opening these files in newer versions. */
for (Curve &cu : bmain->curves) {
for (Nurb *nu = static_cast<Nurb *>(cu.nurb.first); nu != nullptr; nu = nu->next) {
if (nu->knotsu == nullptr && (nu->flagu & CU_NURB_CUSTOM)) {
nu->flagu &= (CU_NURB_CYCLIC | CU_NURB_BEZIER | CU_NURB_ENDPOINT);
}
if (nu->knotsv == nullptr && (nu->flagv & CU_NURB_CUSTOM)) {
nu->flagv &= (CU_NURB_CYCLIC | CU_NURB_BEZIER | CU_NURB_ENDPOINT);
}
}
}
}
static void version_strip_modifier_show_preview_flag(Main &bmain)
{
for (Scene &scene : bmain.scenes) {
Editing *ed = seq::editing_get(&scene);
if (ed == nullptr) {
continue;
}
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
for (StripModifierData &smd : strip->modifiers) {
if ((smd.flag & STRIP_MODIFIER_FLAG_MUTE) == 0) {
smd.flag |= STRIP_MODIFIER_FLAG_SHOW_PREVIEW;
}
}
return true;
});
}
}
static void version_scene_strip_view_layer_name(Main &bmain)
{
for (const Scene &scene : bmain.scenes) {
Editing *ed = seq::editing_get(&scene);
if (ed == nullptr) {
continue;
}
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
if (strip->type != STRIP_TYPE_SCENE || strip->scene == nullptr) {
return true;
}
strip->scene_view_layer_name = BLI_strdup(BKE_view_layer_default_render(strip->scene)->name);
return true;
});
}
}
/* Compositor node trees with an image input and an image output can likely be used as strip
* modifiers. */
static void enable_compositor_nodes_is_strip_modifier(Main &bmain)
{
for (bNodeTree &group : bmain.nodetrees) {
if (group.type != NTREE_COMPOSIT) {
continue;
}
bool has_image_input = false;
bool has_image_output = false;
group.tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) {
if (item.item_type != NodeTreeInterfaceItemType::Socket) {
/* Continue. */
return true;
}
const auto &socket = reinterpret_cast<const bNodeTreeInterfaceSocket &>(item);
if (socket.flag & NODE_INTERFACE_SOCKET_INPUT) {
has_image_input = has_image_input || STREQ(socket.socket_type, "NodeSocketColor");
/* Continue. */
return true;
}
if (socket.flag & NODE_INTERFACE_SOCKET_OUTPUT) {
has_image_output = has_image_output || STREQ(socket.socket_type, "NodeSocketColor");
/* Continue. */
return true;
}
/* Break. */
return false;
});
if (has_image_input && has_image_output) {
if (!group.compositor_node_asset_traits) {
group.compositor_node_asset_traits = MEM_new<CompositorNodeAssetTraits>(__func__);
}
group.compositor_node_asset_traits->flag |= COMPOSIT_NODE_ASSET_STRIP_MODIFIER;
bke::node_update_asset_metadata(group);
}
}
}
static void versioning_replace_legacy_compositor_switch_node(bNodeTree *node_tree)
{
version_node_input_socket_name(node_tree, CMP_NODE_SWITCH, "On", "True");
version_node_input_socket_name(node_tree, CMP_NODE_SWITCH, "Off", "False");
version_node_output_socket_name(node_tree, CMP_NODE_SWITCH, "Image", "Output");
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == CMP_NODE_SWITCH) {
node.type_legacy = GEO_NODE_SWITCH;
NodeSwitch *storage = MEM_new<NodeSwitch>(__func__);
storage->input_type = SOCK_RGBA;
STRNCPY_UTF8(node.idname, "GeometryNodeSwitch");
node.storage = storage;
}
}
}
void do_versions_after_linking_520(FileData *fd, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 2)) {
for (Scene &scene : bmain->scenes) {
bNodeTree *node_tree = version_get_scene_compositor_node_tree(bmain, &scene);
if (node_tree == nullptr) {
continue;
}
do_version_file_output_use_file_extension_recursive(*node_tree, scene);
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 16)) {
for (Object &object : bmain->objects) {
for (ModifierData &md : object.modifiers) {
if (md.type == eModifierType_Nodes) {
version_geometry_nodes_properties(
*fd, *bmain, object, reinterpret_cast<NodesModifierData &>(md));
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 27)) {
version_scene_strip_view_layer_name(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 36)) {
/* Shift animation data to accommodate the new thin wall input. */
version_node_socket_index_animdata(bmain, NTREE_SHADER, SH_NODE_BSDF_PRINCIPLED, 5, 1, 31);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 44)) {
/* We have to remove the invalid motion paths. Re-baking into clip space on file load would be
* very expensive. */
for (Object &object : bmain->objects) {
if (object.mpath && (object.avs.path_bakeflag & MOTIONPATH_BAKE_CAMERA_SPACE)) {
animviz_free_motionpath(object.mpath);
object.mpath = nullptr;
object.avs.path_bakeflag &= ~MOTIONPATH_BAKE_HAS_PATHS;
}
if (object.pose && (object.pose->avs.path_bakeflag & MOTIONPATH_BAKE_CAMERA_SPACE)) {
for (bPoseChannel &pose_bone : object.pose->chanbase) {
if (pose_bone.mpath) {
animviz_free_motionpath(pose_bone.mpath);
pose_bone.mpath = nullptr;
}
}
object.pose->avs.path_bakeflag &= ~MOTIONPATH_BAKE_HAS_PATHS;
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
}
static void version_solid_color_width_height_defaults(Main &bmain)
{
for (Scene &scene : bmain.scenes) {
Editing *ed = seq::editing_get(&scene);
if (ed == nullptr) {
continue;
}
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
if (strip->type == STRIP_TYPE_COLOR && strip->effectdata != nullptr) {
SolidColorVars *data = static_cast<SolidColorVars *>(strip->effectdata);
data->width = scene.r.xsch;
data->height = scene.r.ysch;
}
return true;
});
}
}
void blo_do_versions_520(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 1)) {
for (Scene &scene : bmain->scenes) {
scene.r.mode |= R_SAVE_OUTPUT;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 4)) {
for (Brush &brush : bmain->brushes) {
if (brush.gpencil_settings != nullptr) {
brush.blend = 0;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 5)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id_owner) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == FN_NODE_INPUT_VECTOR) {
auto &data = *static_cast<NodeInputVector *>(node.storage);
data.vector[3] = 0.0f;
data.dimensions = 3;
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 6)) {
for (Scene &scene : bmain->scenes) {
SequencerToolSettings *sequencer_tool_settings = seq::tool_settings_ensure(&scene);
sequencer_tool_settings->snap_flag |= SEQ_SNAP_TO_ALL_CHANNEL_STRIPS;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 7)) {
for (Scene &scene : bmain->scenes) {
scene.r.anisotropic_filter = 2;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 9)) {
for (Mesh &mesh : bmain->meshes) {
bke::mesh_freestyle_marks_to_generic(mesh);
}
}
/* Convert H.264 codec value for older files (2.79), see #155775. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 10)) {
for (Scene &scene : bmain->scenes) {
if (scene.r.ffcodecdata.codec == 28) {
scene.r.ffcodecdata.codec = 27;
}
}
}
/* Disable "unified" flags for Grease Pencil Draw mode. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 11)) {
for (Scene &scene : bmain->scenes) {
if (scene.toolsettings->gp_paint) {
UnifiedPaintSettings &settings =
scene.toolsettings->gp_paint->paint.unified_paint_settings;
settings.flag &= ~(UNIFIED_PAINT_SIZE | UNIFIED_PAINT_ALPHA | UNIFIED_PAINT_COLOR);
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 12)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &space : area.spacedata) {
if (space.spacetype == SPACE_NODE) {
SpaceNode *space_node = reinterpret_cast<SpaceNode *>(&space);
space_node->overlay.flag |= SN_OVERLAY_SHOW_RENDER_REGION;
space_node->overlay.passepartout_alpha = 0.5f;
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 13)) {
version_clear_strip_linear_modifier_flag(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 14)) {
fix_single_point_curves_custom_knots(bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 15)) {
for (Scene &scene : bmain->scenes) {
scene.r.scemode |= R_USE_TEXTURE_CACHE;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 16)) {
for (Brush &brush : bmain->brushes) {
if (brush.gpencil_settings != nullptr) {
brush.gpencil_settings->curve_type = CURVE_TYPE_POLY;
brush.gpencil_settings->conversion_threshold = 0.001f;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 17)) {
for (Material &materials : bmain->materials) {
if (materials.gp_style != nullptr) {
materials.gp_style->placement_mode = GP_MATERIAL_PLACEMENT_COUNT;
materials.gp_style->placement_count = 1;
materials.gp_style->placement_density = 10.0f;
materials.gp_style->placement_radius_spacing = 100.0f;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 18)) {
for (Scene &scene : bmain->scenes) {
if (scene.toolsettings->sculpt) {
Sculpt &sculpt = *scene.toolsettings->sculpt;
MeshAutomaskingSettings *settings = MEM_new<MeshAutomaskingSettings>(__func__);
settings->flags = sculpt.automasking_flags;
settings->boundary_edges_propagation_steps =
sculpt.automasking_boundary_edges_propagation_steps;
settings->cavity_blur_steps = sculpt.automasking_cavity_blur_steps;
settings->cavity_factor = sculpt.automasking_cavity_factor;
settings->start_normal_limit = sculpt.automasking_start_normal_limit;
settings->start_normal_falloff = sculpt.automasking_start_normal_falloff;
settings->view_normal_limit = sculpt.automasking_view_normal_limit;
settings->view_normal_falloff = sculpt.automasking_view_normal_falloff;
settings->cavity_curve = BKE_curvemapping_copy(sculpt.automasking_cavity_curve);
settings->cavity_curve_op = BKE_curvemapping_copy(sculpt.automasking_cavity_curve_op);
scene.toolsettings->sculpt->paint.mesh_automasking_settings = settings;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 19)) {
for (bNodeTree &tree : bmain->nodetrees) {
sanitize_node_tree_interface_socket_identifiers(tree);
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 20)) {
for (Brush &brush : bmain->brushes) {
if (brush.ob_mode != OB_MODE_SCULPT) {
continue;
}
brush.mesh_automasking_settings = MEM_new<MeshAutomaskingSettings>(__func__);
brush.mesh_automasking_settings->flags = brush.automasking_flags;
brush.mesh_automasking_settings->boundary_edges_propagation_steps =
brush.automasking_boundary_edges_propagation_steps;
brush.mesh_automasking_settings->cavity_blur_steps = brush.automasking_cavity_blur_steps;
brush.mesh_automasking_settings->cavity_factor = brush.automasking_cavity_factor;
brush.mesh_automasking_settings->start_normal_falloff =
brush.automasking_start_normal_falloff;
brush.mesh_automasking_settings->start_normal_limit = brush.automasking_start_normal_limit;
brush.mesh_automasking_settings->view_normal_falloff = brush.automasking_view_normal_falloff;
brush.mesh_automasking_settings->view_normal_limit = brush.automasking_view_normal_limit;
brush.mesh_automasking_settings->cavity_curve = BKE_curvemapping_copy(
brush.automasking_cavity_curve);
brush.mesh_automasking_settings->cavity_curve_op = nullptr;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 21)) {
for (Material &materials : bmain->materials) {
if (materials.gp_style != nullptr) {
materials.gp_style->random_size_factor = 0.0f;
materials.gp_style->random_strength_factor = 0.0f;
materials.gp_style->random_rotation_factor = 0.0f;
materials.gp_style->random_hue_factor = 0.0f;
materials.gp_style->random_saturation_factor = 0.0f;
materials.gp_style->random_value_factor = 0.0f;
materials.gp_style->random_noise_scale = 1.0f;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 22)) {
version_strip_modifier_show_preview_flag(*bmain);
}
/* The ID member of the Viewer node is no longer initialized to the Viewer Image, so clear that
* member. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 23)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == CMP_NODE_VIEWER) {
node.id = nullptr;
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 24)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_SHADER) {
for (bNode &node : node_tree->nodes) {
if (node.type_legacy == SH_NODE_RAYCAST && node.storage == nullptr) {
node.storage = MEM_new<NodeShaderRaycast>(__func__);
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 25)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &space : area.spacedata) {
if (space.spacetype == SPACE_OUTLINER) {
SpaceOutliner *space_outliner = reinterpret_cast<SpaceOutliner *>(&space);
space_outliner->flag |= SO_SCROLL_TO_ACTIVE;
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 26)) {
FOREACH_NODETREE_BEGIN (bmain, tree, id) {
if (tree->type != NTREE_GEOMETRY) {
continue;
}
for (bNode &node : tree->nodes) {
switch (node.type_legacy) {
case FN_NODE_COMPARE:
case FN_NODE_RANDOM_VALUE: {
version_socket_identifier_suffixes_for_dynamic_types(node.inputs, "_");
version_socket_identifier_suffixes_for_dynamic_types(node.outputs, "_");
break;
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 28)) {
version_text_strip_space_line(*bmain);
version_compositor_effect_initialized(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 29)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype != SPACE_SEQ) {
continue;
}
ListBaseT<ARegion> *regionbase = (&sl == area.spacedata.first) ? &area.regionbase :
&sl.regionbase;
ARegion *scrubbing_region = do_versions_add_region_if_not_found(
regionbase, RGN_TYPE_SCRUBBING, "Scrubbing Region", RGN_TYPE_FOOTER);
if (scrubbing_region) {
scrubbing_region->alignment = RGN_ALIGN_BOTTOM | RGN_STACK_ON_PREV |
RGN_ALIGN_HIDE_WITH_PREV;
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 30)) {
enable_compositor_nodes_is_strip_modifier(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 31)) {
for (Mesh &mesh : bmain->meshes) {
if (mesh.attributes().contains(".uv_seam")) {
mesh.attributes_for_write().rename(".uv_seam", "uv_seam");
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 34)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_COMPOSIT) {
versioning_replace_legacy_compositor_switch_node(ntree);
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 35)) {
for (Object &object : bmain->objects) {
object.parent_bone_head_tail_factor = 1.0;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 37)) {
version_text_strip_abs_space_line(*bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 38)) {
for (Brush &brush : bmain->brushes) {
if (brush.gpencil_settings != nullptr) {
brush.gpencil_settings->fill_gap_factor = 0.4f;
brush.gpencil_settings->flag |= GP_BRUSH_FILL_INTERNAL_GAPS;
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 39)) {
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_SEQ) {
SpaceSeq *sseq = reinterpret_cast<SpaceSeq *>(&sl);
sseq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_COMPOSITION_GUIDES;
float default_col[4] = {0.5f, 0.5f, 0.5f, 1.0f};
copy_v4_v4(sseq->preview_overlay.composition_guide_color, default_col);
}
}
}
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 40)) {
for (wmWindowManager &wm : bmain->wm) {
wm.xr.session_settings.viewfinder_enabled = false;
wm.xr.session_settings.viewfinder_crosshair_enabled = true;
wm.xr.session_settings.viewfinder_hand = XR_VIEWFINDER_HAND_RIGHT;
wm.xr.session_settings.viewfinder_scale = 1.0f;
wm.xr.session_settings.viewfinder_passepartout_overscan = 0.5f;
wm.xr.session_settings.viewfinder_passepartout_opacity = 0.5f;
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 41)) {
version_solid_color_width_height_defaults(*bmain);
}
/* Fix the fact that previously, making a linked data local and/or clearing a liboverride would
* not properly flag some sub-data like modifiers or constraints as local. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 43)) {
for (ID &id : MainAllIDsIterator{*bmain}) {
if (!ID_IS_LINKED(&id) && !ID_IS_OVERRIDE_LIBRARY(&id)) {
BKE_lib_override_flag_subdata_local(id);
}
}
}
/* The compositor previously did not support default inputs for group nodes, but some built-in
* nodes had the position field default type for some inputs, so node groups would gain it as a
* default type through some operators. Later, the default inputs were supported for group nodes,
* though position field were not supported in the compositor, so it would assert. To fix this,
* we reset any position field default input to the default value. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 502, 44)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
node_tree->ensure_interface_cache();
for (bNodeTreeInterfaceSocket *input : node_tree->interface_inputs()) {
if (input->default_input == NODE_DEFAULT_INPUT_POSITION_FIELD) {
input->default_input = NODE_DEFAULT_INPUT_VALUE;
}
}
}
}
FOREACH_NODETREE_END;
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
}
} // namespace blender

View File

@@ -0,0 +1,884 @@
/* 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_layer_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_screen_types.h"
#include "DNA_sequence_types.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_string_utf8.h"
#include "BKE_animsys.h"
#include "BKE_grease_pencil_legacy_convert.hh"
#include "BKE_idprop.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_override.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_main_namemap.hh"
#include "BKE_mesh_legacy_convert.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "ANIM_versioning.hh"
#include "NOD_socket.hh"
#include "BLT_translation.hh"
#include "SEQ_sequencer.hh"
#include "MEM_guardedalloc.h"
#include "BLO_readfile.hh"
#include "readfile.hh"
#include "versioning_common.hh"
namespace blender {
short do_versions_new_to_old_idcode_get(const short id_code_new)
{
switch (id_code_new) {
case ID_GP:
/* ID_GD_LEGACY (Grease Pencil v2) is now converted to ID_GP (Grease Pencil v3). */
return ID_GD_LEGACY;
default:
return ID_LINK_PLACEHOLDER;
}
}
ARegion *do_versions_add_region_if_not_found(ListBaseT<ARegion> *regionbase,
int region_type,
const char * /*allocname*/,
int link_after_region_type)
{
ARegion *link_after_region = nullptr;
for (ARegion &region : *regionbase) {
if (region.regiontype == region_type) {
return nullptr;
}
if (region.regiontype == link_after_region_type) {
link_after_region = &region;
}
}
ARegion *new_region = BKE_area_region_new();
new_region->regiontype = eRegion_Type(region_type);
BLI_insertlinkafter(regionbase, link_after_region, new_region);
return new_region;
}
ARegion *do_versions_ensure_region(ListBaseT<ARegion> *regionbase,
int region_type,
const char * /*allocname*/,
int link_after_region_type)
{
ARegion *link_after_region = nullptr;
for (ARegion &region : *regionbase) {
if (region.regiontype == region_type) {
return &region;
}
if (region.regiontype == link_after_region_type) {
link_after_region = &region;
}
}
ARegion *new_region = BKE_area_region_new();
new_region->regiontype = eRegion_Type(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 */
ListBaseT<ID> *lb = which_libbase(bmain, id_type);
ID *id = nullptr;
for (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_libblock_rename(*bmain, *id, name_dst);
}
return id;
}
static void change_node_socket_name(ListBaseT<bNodeSocket> *sockets,
const char *old_name,
const char *new_name)
{
for (bNodeSocket &socket : *sockets) {
if (STREQ(socket.name, old_name)) {
STRNCPY_UTF8(socket.name, new_name);
}
if (STREQ(socket.identifier, old_name)) {
version_node_socket_identifier_set(socket, new_name);
}
}
}
bool version_node_socket_is_used(bNodeSocket *sock)
{
BLI_assert(sock != nullptr);
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()] = '_';
socket->runtime->identifier_ustr = UString(socket->identifier);
}
}
void version_node_socket_identifier_set(bNodeSocket &socket, const StringRefNull identifier)
{
STRNCPY_UTF8(socket.identifier, identifier.c_str());
socket.runtime->identifier_ustr = UString(socket.identifier);
}
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_legacy == 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_legacy == 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_legacy == node_type) {
change_node_socket_name(&node->outputs, old_name, new_name);
}
}
}
StringRef legacy_socket_idname_to_socket_type(StringRef idname)
{
using string_pair = std::pair<const char *, const char *>;
static const string_pair subtypes_map[] = {{"NodeSocketFloatUnsigned", "NodeSocketFloat"},
{"NodeSocketFloatPercentage", "NodeSocketFloat"},
{"NodeSocketFloatFactor", "NodeSocketFloat"},
{"NodeSocketFloatAngle", "NodeSocketFloat"},
{"NodeSocketFloatTime", "NodeSocketFloat"},
{"NodeSocketFloatTimeAbsolute", "NodeSocketFloat"},
{"NodeSocketFloatDistance", "NodeSocketFloat"},
{"NodeSocketIntUnsigned", "NodeSocketInt"},
{"NodeSocketIntPercentage", "NodeSocketInt"},
{"NodeSocketIntFactor", "NodeSocketInt"},
{"NodeSocketVectorTranslation", "NodeSocketVector"},
{"NodeSocketVectorDirection", "NodeSocketVector"},
{"NodeSocketVectorVelocity", "NodeSocketVector"},
{"NodeSocketVectorAcceleration", "NodeSocketVector"},
{"NodeSocketVectorEuler", "NodeSocketVector"},
{"NodeSocketVectorXYZ", "NodeSocketVector"}};
for (const string_pair &pair : subtypes_map) {
if (pair.first == idname) {
return pair.second;
}
}
/* Unchanged socket idname. */
return idname;
}
bNode &version_node_add_empty(bNodeTree &ntree, const char *idname)
{
bke::bNodeType *ntype = bke::node_type_find(UString(idname));
bNode *node = MEM_new<bNode>(__func__);
node->runtime = MEM_new<bke::bNodeRuntime>(__func__);
BLI_addtail(&ntree.nodes, node);
bke::node_unique_id(ntree, *node);
STRNCPY(node->idname, idname);
DATA_(ntype->ui_name).copy_utf8_truncated(node->name);
bke::node_unique_name(ntree, *node);
node->flag = NODE_SELECT | NODE_OPTIONS | NODE_INIT;
node->width = ntype->default_width;
node->height = ntype->height;
node->color[0] = node->color[1] = node->color[2] = 0.608;
node->type_legacy = ntype->type_legacy;
BKE_ntree_update_tag_node_new(&ntree, node);
return *node;
}
bNode &version_node_add_unknown(bNodeTree &ntree,
bke::bNodeType &ntype,
const char *idname,
const int16_t legacy_type,
const std::string &ui_name,
const std::string &ui_description,
const std::string &enum_name_legacy,
const short nclass,
const float width,
const float height,
const bool no_muting)
{
using namespace blender::bke;
ntype.idname = UString(idname);
ntype.type_legacy = legacy_type;
ntype.height = height;
ntype.default_width = width;
ntype.minheight = 30.0f;
ntype.maxheight = FLT_MAX;
ntype.ui_name = ui_name;
ntype.ui_description = ui_description;
ntype.enum_name_legacy = enum_name_legacy.c_str();
ntype.nclass = nclass;
ntype.no_muting = no_muting;
ntype.ui_name = ui_name;
bNode *node = MEM_new<bNode>(__func__);
node->runtime = MEM_new<bNodeRuntime>(__func__);
BLI_addtail(&ntree.nodes, node);
node_unique_id(ntree, *node);
node->typeinfo = &ntype;
STRNCPY(node->idname, idname);
DATA_(ntype.ui_name).copy_utf8_truncated(node->name);
node_unique_name(ntree, *node);
node->flag = NODE_SELECT | NODE_OPTIONS | NODE_INIT;
node->width = ntype.default_width;
node->height = ntype.height;
node->color[0] = node->color[1] = node->color[2] = 0.608f;
node->type_legacy = ntype.type_legacy;
BKE_ntree_update_tag_node_new(&ntree, node);
return *node;
}
void version_node_remove(bNodeTree &ntree, bNode &node)
{
bke::node_unlink_node(ntree, node);
bke::node_unlink_attached(&ntree, &node);
bke::node_free_node(&ntree, node);
bke::node_rebuild_id_vector(ntree);
}
bNodeSocket &version_node_add_socket(bNodeTree &ntree,
bNode &node,
const eNodeSocketInOut in_out,
const char *idname,
const char *identifier)
{
bke::bNodeSocketType *stype = bke::node_socket_type_find(idname);
BLI_assert(stype != nullptr);
bNodeSocket *socket = MEM_new<bNodeSocket>(__func__);
socket->runtime = MEM_new<bke::bNodeSocketRuntime>(__func__);
socket->in_out = in_out;
socket->limit = (in_out == SOCK_IN ? 1 : 0xFFF);
socket->type = stype->type;
STRNCPY_UTF8(socket->idname, idname);
STRNCPY_UTF8(socket->identifier, identifier);
socket->runtime->identifier_ustr = UString(socket->identifier);
STRNCPY_UTF8(socket->name, identifier);
if (in_out == SOCK_IN) {
BLI_addtail(&node.inputs, socket);
}
else {
BLI_addtail(&node.outputs, socket);
}
node_socket_init_default_value_data(stype->type, stype->subtype, &socket->default_value);
BKE_ntree_update_tag_socket_new(&ntree, socket);
return *socket;
}
bNodeLink &version_node_add_link(
bNodeTree &ntree, bNode &node_a, bNodeSocket &socket_a, bNode &node_b, bNodeSocket &socket_b)
{
BLI_assert(socket_a.in_out != socket_b.in_out);
if (socket_a.in_out == SOCK_IN) {
return version_node_add_link(ntree, node_b, socket_b, node_a, socket_a);
}
bNode &node_from = node_a;
bNodeSocket &socket_from = socket_a;
bNode &node_to = node_b;
bNodeSocket &socket_to = socket_b;
bNodeLink *link = MEM_new<bNodeLink>(__func__);
link->fromnode = &node_from;
link->fromsock = &socket_from;
link->tonode = &node_to;
link->tosock = &socket_to;
BLI_addtail(&ntree.links, link);
BKE_ntree_update_tag_link_added(&ntree, link);
return *link;
}
bool version_node_ensure_storage_or_invalidate(bNode &node)
{
/* Accept node if storage is valid. */
if (node.storage != nullptr) {
return true;
}
/* Invalidate the type identifiers to prevent invalid access where storage data is expected
* (#154086). */
bke::node_set_undefined_type(node);
return false;
}
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 = bke::node_find_socket(*node, eNodeSocketInOut(in_out), UString(identifier));
if (sock != nullptr) {
return sock;
}
return bke::node_add_static_socket(
*ntree, *node, eNodeSocketInOut(in_out), type, subtype, identifier, name);
}
void version_node_tree_clear_interface(bNodeTree &ntree)
{
ntree.tree_interface.clear_items();
}
void version_node_id(bNodeTree *ntree, const int node_type, const char *new_name)
{
for (bNode *node : ntree->all_nodes()) {
if (node->type_legacy == 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_legacy != node_type) {
continue;
}
char node_name_escaped[sizeof(node->name) * 2];
BLI_str_escape(node_name_escaped, node->name, sizeof(node_name_escaped));
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,
/*verify_paths=*/false,
/*infix_is_name=*/true);
MEM_delete(rna_path_prefix);
}
}
FOREACH_NODETREE_END;
}
}
void version_socket_update_is_used(bNodeTree *ntree)
{
for (bNode *node : ntree->all_nodes()) {
for (bNodeSocket &socket : node->inputs) {
socket.flag &= ~SOCK_IS_LINKED;
}
for (bNodeSocket &socket : node->outputs) {
socket.flag &= ~SOCK_IS_LINKED;
}
}
for (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 = BKE_area_region_new();
region->regiontype = eRegion_Type(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)
{
for (bNodeLink &link : ntree.links.items_mutable()) {
if (link.tonode == &old_node) {
bNodeSocket *old_socket = link.tosock;
if (old_socket->is_available()) {
if (const std::string *new_identifier = map.lookup_ptr_as(old_socket->identifier)) {
bNodeSocket *new_socket = bke::node_find_socket(
*&new_node, SOCK_IN, UString(*new_identifier));
link.tonode = &new_node;
link.tosock = new_socket;
old_socket->link = nullptr;
}
}
}
if (link.fromnode == &old_node) {
bNodeSocket *old_socket = link.fromsock;
if (old_socket->is_available()) {
if (const std::string *new_identifier = map.lookup_ptr_as(old_socket->identifier)) {
bNodeSocket *new_socket = bke::node_find_socket(
*&new_node, SOCK_OUT, UString(*new_identifier));
link.fromnode = &new_node;
link.fromsock = new_socket;
old_socket->link = nullptr;
}
}
}
}
}
static Vector<bNodeLink *> find_connected_links(bNodeTree *ntree, bNodeSocket *in_socket)
{
Vector<bNodeLink *> links;
for (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);
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_legacy == GEO_NODE_REALIZE_INSTANCES) {
return;
}
bNode *realize_node = bke::node_add_static_node(nullptr, *ntree, GEO_NODE_REALIZE_INSTANCES);
realize_node->parent = node->parent;
realize_node->locx_legacy = node->locx_legacy - 100;
realize_node->locy_legacy = node->locy_legacy;
bke::node_add_link(*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_ID_system_properties_get(id);
return (idprop) ? IDP_GetPropertyTypeFromGroup(idprop, "cycles", IDP_GROUP) : nullptr;
}
IDProperty *version_cycles_properties_from_view_layer(ViewLayer *view_layer)
{
IDProperty *idprop = view_layer->system_properties;
return (idprop) ? IDP_GetPropertyTypeFromGroup(idprop, "cycles", IDP_GROUP) : nullptr;
}
IDProperty *version_cycles_properties_from_render_layer(SceneRenderLayer *render_layer)
{
IDProperty *idprop = render_layer->prop;
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_get(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_get(prop) : default_value;
}
void version_cycles_property_int_set(IDProperty *idprop, const char *name, int value)
{
if (IDProperty *prop = IDP_GetPropertyTypeFromGroup(idprop, name, IDP_INT)) {
IDP_int_set(prop, value);
}
else {
IDP_AddToGroup(idprop, bke::idprop::create(name, value).release());
}
}
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_ID_system_properties_get(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. */
for (bNodeLink &link : ntree->links.items_reversed_mutable()) {
/* 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 */
bke::node_remove_link(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. */
for (bNode &node : ntree->nodes) {
if (check_node(&node)) {
bNodeSocket *input = bke::node_find_socket(node, SOCK_IN, UString(socket_identifier));
if (input != nullptr) {
update_input(&node, input);
}
}
}
if (need_update) {
version_socket_update_is_used(ntree);
}
}
bNode *version_eevee_output_node_get(bNodeTree *ntree, int16_t node_type)
{
bNode *output_node = nullptr;
/* NOTE: duplicated from `ntreeShaderOutputNode` with small adjustments so it can be called
* during versioning. */
for (bNode &node : ntree->nodes) {
if (node.type_legacy != node_type) {
continue;
}
if (node.custom1 == SHD_OUTPUT_ALL) {
if (output_node == nullptr) {
output_node = &node;
}
else if (output_node->custom1 == SHD_OUTPUT_ALL) {
if ((node.flag & NODE_DO_OUTPUT) && !(output_node->flag & NODE_DO_OUTPUT)) {
output_node = &node;
}
}
}
else if (node.custom1 == SHD_OUTPUT_EEVEE) {
if (output_node == nullptr) {
output_node = &node;
}
else if ((node.flag & NODE_DO_OUTPUT) && !(output_node->flag & NODE_DO_OUTPUT)) {
output_node = &node;
}
}
}
return output_node;
}
bool all_scenes_use(Main *bmain, const Span<const char *> engines)
{
if (!bmain->scenes.first) {
return false;
}
for (Scene &scene : bmain->scenes) {
bool match = false;
for (const char *engine : engines) {
if (STREQ(scene.r.engine, engine)) {
match = true;
}
}
if (!match) {
return false;
}
}
return true;
}
bNodeTree *version_get_scene_compositor_node_tree(Main *bmain, Scene *scene)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 4)) {
return scene->nodetree;
}
return scene->compositing_node_group;
}
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;
}
for (Library &library : bmain->libraries) {
if (!LIBRARY_VERSION_FILE_ATLEAST(&library, versionfile, subversionfile)) {
return false;
}
}
return true;
}
void do_versions_after_setup(Main *new_bmain,
BlendfileLinkAppendContext *lapp_context,
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 *versioned* 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)) {
/* This happens here, because at this point in the versioning code there's
* 'reports' available. */
reports->pre_animato_file_loaded = true;
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 250, 0)) {
for (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, 402, 22)) {
/* Initial auto smooth versioning started at (401, 2), but a bug caused the legacy flag to not
* be cleared, so it is re-run in a later version when the bug is fixed and the versioning has
* been made idempotent. */
BKE_main_mesh_legacy_convert_auto_smooth(*new_bmain);
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 404, 2)) {
/* Version all the action assignments of just-versioned datablocks. This MUST happen before the
* GreasePencil conversion, as that assumes the Action Slots have already been assigned. */
animrig::versioning::convert_legacy_action_assignments(*new_bmain, reports->reports);
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 403, 3)) {
/* Convert all the legacy grease pencil objects. This does not touch annotations. */
bke::greasepencil::convert::legacy_main(*new_bmain, lapp_context, *reports);
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 500, 4)) {
for (Scene &scene : new_bmain->scenes) {
bNodeTree *ntree = scene.nodetree;
if (!ntree) {
continue;
}
ntree->id.flag &= ~ID_FLAG_EMBEDDED_DATA;
ntree->owner_id = nullptr;
ntree->id.tag |= ID_TAG_NO_MAIN;
scene.compositing_node_group = ntree;
scene.nodetree = nullptr;
BKE_libblock_management_main_add(new_bmain, ntree);
/* NOTE: The user count remains zero at this point. It will get automatically updated after
* blend file reading is done. */
}
}
if (!blendfile_or_libraries_versions_atleast(new_bmain, 501, 29)) {
/* Clear modifier node trees if the tree type is undefined.
* This can happen to generated auto-smooth node groups for unknown reasons (#152810). */
for (Object &object : new_bmain->objects) {
for (ModifierData &md : object.modifiers) {
if (md.type != eModifierType_Nodes) {
continue;
}
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(&md);
if (nmd.node_group && !ID_MISSING(nmd.node_group) &&
!STREQ(nmd.node_group->idname, "GeometryNodeTree"))
{
id_us_min(&nmd.node_group->id);
nmd.node_group = nullptr;
}
}
}
}
}
} // namespace blender

View File

@@ -0,0 +1,906 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*
* This file handles updating the `startup.blend`, this is used when reading old files.
*
* Unlike regular versioning this makes changes that ensure the startup file
* has brushes and other presets setup to take advantage of newer features.
*
* To update preference defaults see `userdef_default.c`.
*/
#define DNA_DEPRECATED_ALLOW
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_mempool.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "DNA_camera_types.h"
#include "DNA_curveprofile_types.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_light_types.h"
#include "DNA_mask_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_sequence_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_workspace_types.h"
#include "DNA_world_types.h"
#include "BKE_appdir.hh"
#include "BKE_attribute.hh"
#include "BKE_brush.hh"
#include "BKE_colortools.hh"
#include "BKE_curveprofile.h"
#include "BKE_customdata.hh"
#include "BKE_gpencil_legacy.h"
#include "BKE_idprop.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_main_namemap.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "BLO_readfile.hh"
#include "BLT_translation.hh"
#include "versioning_common.hh"
namespace blender {
/* Make preferences read-only, use `versioning_userdef.cc`. */
#define U (*((const UserDef *)&U))
static bool blo_is_builtin_template(const char *app_template)
{
/* For all builtin templates shipped with Blender. */
return (!app_template || STR_ELEM(app_template,
N_("2D_Animation"),
N_("Storyboarding"),
N_("Sculpting"),
N_("VFX"),
N_("Video_Editing")));
}
static void blo_update_defaults_screen(bScreen *screen,
const char *app_template,
const char *workspace_name)
{
/* For all app templates. */
for (ScrArea &area : screen->areabase) {
for (ARegion &region : area.regionbase) {
/* Some toolbars have been saved as initialized,
* we don't want them to have odd zoom-level or scrolling set, see: #47047 */
if (ELEM(region.regiontype, RGN_TYPE_UI, RGN_TYPE_TOOLS, RGN_TYPE_TOOL_PROPS)) {
region.v2d.flag &= ~V2D_IS_INIT;
}
}
/* Set default folder. */
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_FILE) {
SpaceFile *sfile = reinterpret_cast<SpaceFile *>(&sl);
if (sfile->params) {
const char *dir_default = BKE_appdir_folder_default();
if (dir_default) {
STRNCPY(sfile->params->dir, dir_default);
sfile->params->file[0] = '\0';
}
}
}
}
}
/* For builtin templates only. */
if (!blo_is_builtin_template(app_template)) {
return;
}
for (ScrArea &area : screen->areabase) {
for (ARegion &region : area.regionbase) {
/* Remove all stored panels, we want to use defaults
* (order, open/closed) as defined by UI code here! */
BKE_area_region_panels_free(&region.panels);
region.panels_category_active.free_no_destruct();
/* Reset size so it uses consistent defaults from the region types. */
region.sizex = 0;
region.sizey = 0;
}
if (area.spacetype == SPACE_IMAGE) {
if (STREQ(workspace_name, "UV Editing")) {
SpaceImage *sima = static_cast<SpaceImage *>(area.spacedata.first);
if (sima->mode == SI_MODE_VIEW) {
sima->mode = SI_MODE_UV;
}
sima->uv_face_opacity = 1.0f;
sima->uv_edge_opacity = 1.0f;
}
else if (STR_ELEM(workspace_name, "Texture Paint", "Shading")) {
SpaceImage *sima = static_cast<SpaceImage *>(area.spacedata.first);
/* Face opacity is set to 0 to not interfere with visualization while painting */
sima->uv_face_opacity = 0.0f;
sima->uv_edge_opacity = 1.0f;
}
else if (BLI_str_startswith(workspace_name, "Compositing")) {
SpaceImage *sima = static_cast<SpaceImage *>(area.spacedata.first);
sima->overlay.flag &= ~SI_OVERLAY_DRAW_TEXT_INFO;
}
}
else if (area.spacetype == SPACE_ACTION) {
/* Show markers region, hide channels and collapse summary in timelines. */
SpaceAction *saction = static_cast<SpaceAction *>(area.spacedata.first);
saction->flag |= SACTION_SHOW_MARKERS;
if (saction->mode == SACTCONT_TIMELINE) {
saction->ads.flag |= ADS_FLAG_SUMMARY_COLLAPSED;
for (ARegion &region : area.regionbase) {
if (region.regiontype == RGN_TYPE_CHANNELS) {
region.flag |= RGN_FLAG_HIDDEN;
}
}
}
else {
/* Open properties panel by default. */
for (ARegion &region : area.regionbase) {
if (region.regiontype == RGN_TYPE_UI) {
region.flag &= ~RGN_FLAG_HIDDEN;
}
}
}
}
else if (area.spacetype == SPACE_GRAPH) {
SpaceGraph *sipo = static_cast<SpaceGraph *>(area.spacedata.first);
sipo->flag |= SIPO_SHOW_MARKERS;
}
else if (area.spacetype == SPACE_NLA) {
SpaceNla *snla = static_cast<SpaceNla *>(area.spacedata.first);
snla->flag |= SNLA_SHOW_MARKERS;
}
else if (area.spacetype == SPACE_SEQ) {
SpaceSeq *seq = static_cast<SpaceSeq *>(area.spacedata.first);
seq->flag |= SEQ_SHOW_MARKERS | SEQ_ZOOM_TO_FIT | SEQ_USE_PROXIES | SEQ_SHOW_OVERLAY;
seq->render_size = SEQ_RENDER_SIZE_PROXY_100;
seq->timeline_overlay.flag |= SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_NAME |
SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID |
SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG |
SEQ_TIMELINE_SHOW_STRIP_RETIMING |
SEQ_TIMELINE_WAVEFORMS_HALF |
SEQ_TIMELINE_STRIP_END_THUMBNAILS;
seq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED;
seq->cache_overlay.flag = SEQ_CACHE_SHOW | SEQ_CACHE_SHOW_FINAL_OUT;
seq->draw_flag |= SEQ_DRAW_TRANSFORM_PREVIEW;
}
else if (area.spacetype == SPACE_TEXT) {
/* Show syntax and line numbers in Script workspace text editor. */
SpaceText *stext = static_cast<SpaceText *>(area.spacedata.first);
stext->showsyntax = true;
stext->showlinenrs = true;
stext->flags |= ST_FIND_WRAP;
}
else if (area.spacetype == SPACE_VIEW3D) {
View3D *v3d = static_cast<View3D *>(area.spacedata.first);
/* Screen space cavity by default for faster performance. */
v3d->shading.cavity_type = V3D_SHADING_CAVITY_CURVATURE;
v3d->shading.flag |= V3D_SHADING_SPECULAR_HIGHLIGHT;
v3d->overlay.texture_paint_mode_opacity = 1.0f;
v3d->overlay.weight_paint_mode_opacity = 1.0f;
v3d->overlay.vertex_paint_mode_opacity = 1.0f;
/* Update default Z bias for retopology overlay. */
v3d->overlay.retopology_offset = 0.01f;
/* Clear this deprecated bit for later reuse. */
v3d->overlay.edit_flag &= ~V3D_OVERLAY_EDIT_EDGES_DEPRECATED;
/* grease pencil settings */
v3d->vertex_opacity = 1.0f;
v3d->gp_flag |= V3D_GP_SHOW_EDIT_LINES;
/* Remove dither pattern in wireframe mode. */
v3d->shading.xray_alpha_wire = 0.0f;
v3d->clip_start = 0.01f;
/* Skip startups that use the viewport color by default. */
if (v3d->shading.background_type != V3D_SHADING_BACKGROUND_VIEWPORT) {
copy_v3_fl(v3d->shading.background_color, 0.05f);
}
/* Disable Curve Normals. */
v3d->overlay.edit_flag &= ~V3D_OVERLAY_EDIT_CU_NORMALS;
v3d->overlay.normals_constant_screen_size = 7.0f;
/* Always enable Grease Pencil vertex color overlay by default. */
v3d->overlay.gpencil_vertex_paint_opacity = 1.0f;
/* Always use theme color for wireframe by default. */
v3d->shading.wire_color_type = V3D_SHADING_SINGLE_COLOR;
/* Level out the 3D Viewport camera rotation, see: #113751. */
constexpr float viewports_to_level[][4] = {
/* Animation, Modeling, Scripting, Texture Paint, UV Editing. */
{0x1.6e7cb8p-1, -0x1.c1747p-2, -0x1.2997dap-2, -0x1.d5d806p-2},
/* Layout. */
{0x1.6e7cb8p-1, -0x1.c17478p-2, -0x1.2997dcp-2, -0x1.d5d80cp-2},
/* Geometry Nodes. */
{0x1.6e7cb6p-1, -0x1.c17476p-2, -0x1.2997dep-2, -0x1.d5d80cp-2},
};
constexpr float viewports_to_clear_ofs[][4] = {
/* Geometry Nodes. */
{0x1.6e7cb6p-1, -0x1.c17476p-2, -0x1.2997dep-2, -0x1.d5d80cp-2},
/* Sculpting. */
{0x1.885b28p-1, -0x1.2d10cp-1, -0x1.42ae54p-3, -0x1.a486a2p-3},
};
constexpr float unified_viewquat[4] = {
0x1.6cbc88p-1, -0x1.c3a5c8p-2, -0x1.26413ep-2, -0x1.db430ap-2};
for (ARegion &region : area.regionbase) {
if (region.regiontype == RGN_TYPE_WINDOW) {
RegionView3D *rv3d = static_cast<RegionView3D *>(region.regiondata);
for (int i = 0; i < ARRAY_SIZE(viewports_to_clear_ofs); i++) {
if (equals_v4v4(rv3d->viewquat, viewports_to_clear_ofs[i])) {
zero_v3(rv3d->ofs);
}
}
for (int i = 0; i < ARRAY_SIZE(viewports_to_level); i++) {
if (equals_v4v4(rv3d->viewquat, viewports_to_level[i])) {
copy_qt_qt(rv3d->viewquat, unified_viewquat);
}
}
}
}
}
else if (area.spacetype == SPACE_CLIP) {
SpaceClip *sclip = static_cast<SpaceClip *>(area.spacedata.first);
sclip->around = V3D_AROUND_CENTER_MEDIAN;
sclip->mask_info.blend_factor = 0.7f;
sclip->mask_info.draw_flag = MASK_DRAWFLAG_SPLINE;
}
}
/* Show tool-header by default (for most cases at least, hide for others). */
const bool hide_image_tool_header = STR_ELEM(workspace_name, "Rendering", "Compositing");
for (ScrArea &area : screen->areabase) {
for (SpaceLink &sl : area.spacedata) {
ListBaseT<ARegion> *regionbase = (&sl == static_cast<SpaceLink *>(area.spacedata.first)) ?
&area.regionbase :
&sl.regionbase;
for (ARegion &region : *regionbase) {
if (region.regiontype == RGN_TYPE_TOOL_HEADER) {
if (((sl.spacetype == SPACE_IMAGE) && hide_image_tool_header) ||
sl.spacetype == SPACE_SEQ)
{
region.flag |= RGN_FLAG_HIDDEN;
}
else {
region.flag &= ~(RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER);
}
}
}
}
}
/* 2D animation template. */
if (app_template && STREQ(app_template, "2D_Animation")) {
for (ScrArea &area : screen->areabase) {
if (area.spacetype == SPACE_ACTION) {
SpaceAction *saction = static_cast<SpaceAction *>(area.spacedata.first);
/* Enable Sliders. */
saction->flag |= SACTION_SLIDERS;
}
else if (area.spacetype == SPACE_VIEW3D) {
View3D *v3d = static_cast<View3D *>(area.spacedata.first);
/* Set Material Color by default. */
v3d->shading.color_type = V3D_SHADING_MATERIAL_COLOR;
/* Enable Annotations. */
v3d->flag2 |= V3D_SHOW_ANNOTATION;
}
}
}
}
void BLO_update_defaults_workspace(WorkSpace *workspace, const char *app_template)
{
for (WorkSpaceLayout &layout : workspace->layouts) {
if (layout.screen) {
blo_update_defaults_screen(layout.screen, app_template, workspace->id.name + 2);
}
}
if (blo_is_builtin_template(app_template)) {
/* Clear all tools to use default options instead, ignore the tool saved in the file. */
while (!workspace->tools.is_empty()) {
BKE_workspace_tool_remove(workspace, static_cast<bToolRef *>(workspace->tools.first));
}
/* For 2D animation template. */
if (STREQ(workspace->id.name + 2, "Drawing")) {
workspace->object_mode = OB_MODE_PAINT_GREASE_PENCIL;
}
/* For Sculpting template. */
if (STREQ(workspace->id.name + 2, "Sculpting")) {
for (WorkSpaceLayout &layout : workspace->layouts) {
bScreen *screen = layout.screen;
if (screen) {
for (ScrArea &area : screen->areabase) {
if (area.spacetype == SPACE_VIEW3D) {
View3D *v3d = static_cast<View3D *>(area.spacedata.first);
v3d->shading.flag &= ~V3D_SHADING_CAVITY;
copy_v3_fl(v3d->shading.single_color, 1.0f);
STRNCPY(v3d->shading.matcap, "basic_1");
}
}
}
}
}
}
/* For Video Editing template. */
if (STRPREFIX(workspace->id.name + 2, "Video Editing")) {
for (WorkSpaceLayout &layout : workspace->layouts) {
bScreen *screen = layout.screen;
if (screen) {
for (ScrArea &area : screen->areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_SEQ) {
if ((reinterpret_cast<SpaceSeq *>(&sl))->view == SEQ_VIEW_PREVIEW) {
continue;
}
ListBaseT<ARegion> *regionbase = (&sl == area.spacedata.first) ? &area.regionbase :
&sl.regionbase;
ARegion *sidebar = BKE_region_find_in_listbase_by_type(regionbase, RGN_TYPE_UI);
sidebar->flag |= RGN_FLAG_HIDDEN;
}
if (sl.spacetype == SPACE_PROPERTIES) {
SpaceProperties *properties = reinterpret_cast<SpaceProperties *>(&sl);
properties->mainb = properties->mainbo = properties->mainbuser = BCONTEXT_STRIP;
}
}
}
}
}
}
}
static void blo_update_defaults_paint(Paint *paint)
{
if (!paint) {
return;
}
/* Ensure input_samples has a correct default value of 1. */
if (paint->unified_paint_settings.input_samples == 0) {
paint->unified_paint_settings.input_samples = 1;
}
const UnifiedPaintSettings &default_ups = UnifiedPaintSettings();
paint->unified_paint_settings.size = default_ups.size;
paint->unified_paint_settings.input_samples = default_ups.input_samples;
paint->unified_paint_settings.unprojected_size = default_ups.unprojected_size;
paint->unified_paint_settings.alpha = default_ups.alpha;
paint->unified_paint_settings.weight = default_ups.weight;
paint->unified_paint_settings.flag = default_ups.flag;
copy_v3_v3(paint->unified_paint_settings.color, default_ups.color);
copy_v3_v3(paint->unified_paint_settings.secondary_color, default_ups.secondary_color);
if (paint->unified_paint_settings.curve_rand_hue == nullptr) {
paint->unified_paint_settings.curve_rand_hue = BKE_paint_default_curve();
}
if (paint->unified_paint_settings.curve_rand_saturation == nullptr) {
paint->unified_paint_settings.curve_rand_saturation = BKE_paint_default_curve();
}
if (paint->unified_paint_settings.curve_rand_value == nullptr) {
paint->unified_paint_settings.curve_rand_value = BKE_paint_default_curve();
}
}
static void blo_update_defaults_windowmanager(wmWindowManager *wm)
{
wm->xr.session_settings.fly_speed = 3.0f;
wm->xr.session_settings.view_scale = 1.0f;
}
static void blo_update_defaults_scene(Main *bmain, Scene *scene)
{
ToolSettings *ts = scene->toolsettings;
STRNCPY_UTF8(scene->r.engine, RE_engine_id_BLENDER_EEVEE);
scene->r.cfra = 1.0f;
scene->r.im_format.exr_flag |= R_IMF_EXR_FLAG_MULTIPART;
scene->r.bake.im_format.exr_flag |= R_IMF_EXR_FLAG_MULTIPART;
scene->r.compositor_device = SCE_COMPOSITOR_DEVICE_GPU;
/* Don't enable compositing nodes. */
if (scene->nodetree) {
bke::node_tree_free_embedded_tree(scene->nodetree);
MEM_delete(scene->nodetree);
scene->nodetree = nullptr;
scene->use_nodes = false;
}
/* Rename render layers. */
BKE_view_layer_rename(
bmain, scene, static_cast<ViewLayer *>(scene->view_layers.first), "ViewLayer");
/* Disable Z pass by default. */
for (ViewLayer &view_layer : scene->view_layers) {
view_layer.passflag &= ~SCE_PASS_DEPTH;
view_layer.eevee.ambient_occlusion_distance = 10.0f;
}
if (scene->ed) {
/* Display missing media by default. */
scene->ed->show_missing_media_flag |= SEQ_EDIT_SHOW_MISSING_MEDIA;
/* Turn on frame pre-fetching per default. */
scene->ed->cache_flag |= SEQ_CACHE_PREFETCH_ENABLE;
}
/* New EEVEE defaults. */
scene->eevee.motion_blur_shutter_deprecated = 0.5f;
scene->eevee.flag &= ~SCE_EEVEE_VOLUME_CUSTOM_RANGE;
scene->eevee.clamp_volume_indirect = 0.0f; /* Default from versioning is not 0. */
scene->eevee.ray_tracing_options = {};
scene->eevee.fast_gi_thickness_near = 0.1f; /* Default from versioning is not 0.1f. */
copy_v3_v3(scene->display.light_direction, float3(M_SQRT1_3));
copy_v2_fl2(scene->safe_areas.title, 0.1f, 0.05f);
copy_v2_fl2(scene->safe_areas.action, 0.035f, 0.035f);
ts->uv_flag |= UV_FLAG_SELECT_SYNC;
/* Default Rotate Increment. */
const float default_snap_angle_increment = DEG2RADF(5.0f);
ts->snap_angle_increment_2d = default_snap_angle_increment;
ts->snap_angle_increment_3d = default_snap_angle_increment;
const float default_snap_angle_increment_precision = DEG2RADF(1.0f);
ts->snap_angle_increment_2d_precision = default_snap_angle_increment_precision;
ts->snap_angle_increment_3d_precision = default_snap_angle_increment_precision;
/* Be sure `curfalloff` and primitive are initialized. */
if (ts->gp_sculpt.cur_falloff == nullptr) {
ts->gp_sculpt.cur_falloff = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
CurveMapping *gp_falloff_curve = ts->gp_sculpt.cur_falloff;
BKE_curvemapping_init(gp_falloff_curve);
BKE_curvemap_reset(gp_falloff_curve->cm,
&gp_falloff_curve->clipr,
CURVE_PRESET_GAUSS,
CurveMapSlopeType::Positive);
}
if (ts->gp_sculpt.cur_primitive == nullptr) {
ts->gp_sculpt.cur_primitive = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
CurveMapping *gp_primitive_curve = ts->gp_sculpt.cur_primitive;
BKE_curvemapping_init(gp_primitive_curve);
BKE_curvemap_reset(gp_primitive_curve->cm,
&gp_primitive_curve->clipr,
CURVE_PRESET_BELL,
CurveMapSlopeType::Positive);
}
if (ts->sculpt) {
ts->sculpt->flags = Sculpt().flags;
}
/* Correct default startup UVs. */
Mesh *mesh = static_cast<Mesh *>(BLI_findstring(&bmain->meshes, "Cube", offsetof(ID, name) + 2));
if (mesh && (mesh->corners_num == 24) &&
CustomData_has_layer(&mesh->corner_data, CD_PROP_FLOAT2))
{
const float uv_values[24][2] = {
{0.625, 0.50}, {0.875, 0.50}, {0.875, 0.75}, {0.625, 0.75}, {0.375, 0.75}, {0.625, 0.75},
{0.625, 1.00}, {0.375, 1.00}, {0.375, 0.00}, {0.625, 0.00}, {0.625, 0.25}, {0.375, 0.25},
{0.125, 0.50}, {0.375, 0.50}, {0.375, 0.75}, {0.125, 0.75}, {0.375, 0.50}, {0.625, 0.50},
{0.625, 0.75}, {0.375, 0.75}, {0.375, 0.25}, {0.625, 0.25}, {0.625, 0.50}, {0.375, 0.50},
};
float (*uv_map)[2] = static_cast<float (*)[2]>(
CustomData_get_layer_for_write(&mesh->corner_data, CD_PROP_FLOAT2, mesh->corners_num));
memcpy(uv_map, uv_values, sizeof(float[2]) * mesh->corners_num);
}
/* Make sure that the curve profile is initialized */
if (ts->custom_bevel_profile_preset == nullptr) {
ts->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE);
}
/* Clear ID properties so Cycles gets defaults. */
IDProperty *idprop = IDP_GetProperties(&scene->id);
if (idprop) {
IDP_ClearProperty(idprop);
}
if (ts->sculpt) {
ts->sculpt->automasking_boundary_edges_propagation_steps = 1;
}
/* Ensure input_samples has a correct default value of 1. */
if (ts->unified_paint_settings.input_samples == 0) {
ts->unified_paint_settings.input_samples = 1;
}
const UnifiedPaintSettings default_ups = {};
ts->unified_paint_settings.flag = default_ups.flag;
copy_v3_v3(ts->unified_paint_settings.color, default_ups.color);
copy_v3_v3(ts->unified_paint_settings.secondary_color, default_ups.secondary_color);
if (ts->unified_paint_settings.curve_rand_hue == nullptr) {
ts->unified_paint_settings.curve_rand_hue = BKE_paint_default_curve();
}
if (ts->unified_paint_settings.curve_rand_saturation == nullptr) {
ts->unified_paint_settings.curve_rand_saturation = BKE_paint_default_curve();
}
if (ts->unified_paint_settings.curve_rand_value == nullptr) {
ts->unified_paint_settings.curve_rand_value = BKE_paint_default_curve();
}
blo_update_defaults_paint(reinterpret_cast<Paint *>(ts->vpaint));
blo_update_defaults_paint(reinterpret_cast<Paint *>(ts->wpaint));
blo_update_defaults_paint(reinterpret_cast<Paint *>(ts->sculpt));
blo_update_defaults_paint(reinterpret_cast<Paint *>(ts->gp_paint));
blo_update_defaults_paint(reinterpret_cast<Paint *>(ts->gp_vertexpaint));
blo_update_defaults_paint(reinterpret_cast<Paint *>(ts->gp_sculptpaint));
blo_update_defaults_paint(reinterpret_cast<Paint *>(ts->curves_sculpt));
blo_update_defaults_paint(reinterpret_cast<Paint *>(&ts->imapaint));
/* Weight Paint settings */
ts->weightuser = OB_DRAW_GROUPUSER_ACTIVE;
/* Cycles settings. */
IDProperty *cscene = version_cycles_properties_from_ID(&scene->id);
if (cscene) {
/* Set the default sampling pattern to AUTOMATIC. */
version_cycles_property_int_set(cscene, "sampling_pattern", 5);
}
}
void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
{
/* For all app templates. */
for (WorkSpace &workspace : bmain->workspaces) {
BLO_update_defaults_workspace(&workspace, app_template);
}
/* Grease pencil materials and paint modes setup. */
{
/* Rename and fix materials and enable default object lights on. */
if (app_template && STREQ(app_template, "2D_Animation")) {
Material *ma = nullptr;
do_versions_rename_id(bmain, ID_MA, "Black", "Solid Stroke");
do_versions_rename_id(bmain, ID_MA, "Red", "Squares Stroke");
do_versions_rename_id(bmain, ID_MA, "Grey", "Solid Fill");
do_versions_rename_id(bmain, ID_MA, "Black Dots", "Dots Stroke");
/* Dots Stroke. */
ma = static_cast<Material *>(
BLI_findstring(&bmain->materials, "Dots Stroke", offsetof(ID, name) + 2));
if (ma == nullptr) {
ma = BKE_gpencil_material_add(bmain, "Dots Stroke");
}
ma->gp_style->mode = GP_MATERIAL_MODE_DOT;
/* Squares Stroke. */
ma = static_cast<Material *>(
BLI_findstring(&bmain->materials, "Squares Stroke", offsetof(ID, name) + 2));
if (ma == nullptr) {
ma = BKE_gpencil_material_add(bmain, "Squares Stroke");
}
ma->gp_style->mode = GP_MATERIAL_MODE_SQUARE;
/* Change Solid Stroke settings. */
ma = static_cast<Material *>(
BLI_findstring(&bmain->materials, "Solid Stroke", offsetof(ID, name) + 2));
if (ma != nullptr) {
ma->gp_style->mix_rgba[3] = 1.0f;
ma->gp_style->texture_offset[0] = -0.5f;
ma->gp_style->mix_factor = 0.5f;
}
/* Change Solid Fill settings. */
ma = static_cast<Material *>(
BLI_findstring(&bmain->materials, "Solid Fill", offsetof(ID, name) + 2));
if (ma != nullptr) {
ma->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
ma->gp_style->mix_rgba[3] = 1.0f;
ma->gp_style->texture_offset[0] = -0.5f;
ma->gp_style->mix_factor = 0.5f;
}
Object *ob = static_cast<Object *>(
BLI_findstring(&bmain->objects, "Stroke", offsetof(ID, name) + 2));
if (ob && ob->type == OB_GPENCIL_LEGACY) {
ob->dtx |= OB_USE_GPENCIL_LIGHTS;
}
}
/* Add library weak references to avoid duplicating materials from essentials. */
const std::optional<std::string> assets_path = BKE_appdir_folder_id(BLENDER_SYSTEM_DATAFILES,
"assets/brushes");
if (assets_path.has_value()) {
const std::string assets_blend_path = *assets_path + "/essentials_brushes-gp_draw.blend";
for (Material &material : bmain->materials) {
BKE_main_library_weak_reference_add(
&material.id, assets_blend_path.c_str(), material.id.name);
}
}
/* Reset grease pencil paint modes. */
for (Scene &scene : bmain->scenes) {
ToolSettings *ts = scene.toolsettings;
/* Ensure new Paint modes. */
BKE_paint_ensure_from_paintmode(&scene, PaintMode::VertexGPencil);
BKE_paint_ensure_from_paintmode(&scene, PaintMode::SculptGPencil);
BKE_paint_ensure_from_paintmode(&scene, PaintMode::WeightGPencil);
/* Enable cursor. */
if (ts->gp_paint) {
ts->gp_paint->paint.flags |= PAINT_SHOW_BRUSH;
}
/* Ensure Palette by default. */
if (ts->gp_paint) {
BKE_gpencil_palette_ensure(bmain, &scene);
}
}
if (app_template && (STR_ELEM(app_template, "2D_Animation", "Storyboarding"))) {
/* Since !153036, the base colors for stroke & fill were getting versioned to have 0% opacity
* if the stroke/fill was disabled. This meant that in a new file using the following App
* Templates, the "Solid Stroke" material wouldn't show anything when trying to draw a fill.
* This sets the fill to a mid grey to make sure users don't run into this issue. */
/* Change Solid Stroke settings. */
Material *ma = static_cast<Material *>(
BLI_findstring(&bmain->materials, "Solid Stroke", offsetof(ID, name) + 2));
if (ma != nullptr) {
/* Black Stroke and Grey Fill. */
copy_v4_fl4(ma->gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f);
copy_v4_fl4(ma->gp_style->fill_rgba, 0.5f, 0.5f, 0.5f, 1.0f);
}
}
}
/* For builtin templates only. */
if (!blo_is_builtin_template(app_template)) {
return;
}
/* Work-spaces. */
for (wmWindowManager &wm : bmain->wm) {
blo_update_defaults_windowmanager(&wm);
for (wmWindow &win : wm.windows) {
for (WorkSpace &workspace : bmain->workspaces) {
WorkSpaceLayout *layout = BKE_workspace_active_layout_for_workspace_get(win.workspace_hook,
&workspace);
/* Name all screens by their workspaces (avoids 'Default.###' names). */
/* Default only has one window. */
if (layout->screen) {
bScreen *screen = layout->screen;
if (!STREQ(screen->id.name + 2, workspace.id.name + 2)) {
BKE_libblock_rename(*bmain, screen->id, workspace.id.name + 2);
}
}
/* For some reason we have unused screens, needed until re-saving.
* Clear unused layouts because they're visible in the outliner & Python API. */
for (WorkSpaceLayout &layout_iter : workspace.layouts.items_mutable()) {
if (layout != &layout_iter) {
BKE_workspace_layout_remove(bmain, &workspace, &layout_iter);
}
}
}
}
}
/* Scenes */
for (Scene &scene : bmain->scenes) {
blo_update_defaults_scene(bmain, &scene);
if (app_template && STR_ELEM(app_template, "Video_Editing", "2D_Animation")) {
/* Filmic is too slow, use standard until it is optimized. */
STRNCPY_UTF8(scene.view_settings.view_transform, "Standard");
STRNCPY_UTF8(scene.view_settings.look, "None");
}
else {
/* Default to AgX view transform. */
STRNCPY_UTF8(scene.view_settings.view_transform, "AgX");
}
if (app_template && STREQ(app_template, "Video_Editing")) {
/* Pass: no extra tweaks needed. Keep the view settings configured above, and rely on the
* default state of enabled AV sync. */
}
else {
/* AV Sync break physics sim caching, disable until that is fixed. */
scene.audio.flag &= ~AUDIO_SYNC;
scene.flag &= ~SCE_FRAME_DROP;
}
/* Change default selection mode for Grease Pencil. */
if (app_template && STREQ(app_template, "2D_Animation")) {
ToolSettings *ts = scene.toolsettings;
ts->gpencil_selectmode_edit = GP_SELECTMODE_STROKE;
}
}
/* Objects */
do_versions_rename_id(bmain, ID_OB, "Lamp", "Light");
do_versions_rename_id(bmain, ID_LA, "Lamp", "Light");
if (app_template && STREQ(app_template, "2D_Animation")) {
for (Object &object : bmain->objects) {
if (object.type == OB_GPENCIL_LEGACY) {
/* Set grease pencil object in drawing mode */
bGPdata *gpd = id_cast<bGPdata *>(object.data);
object.mode = OB_MODE_PAINT_GREASE_PENCIL;
gpd->flag |= GP_DATA_STROKE_PAINTMODE;
break;
}
}
}
for (Object &object : bmain->objects) {
const Object dob;
/* Set default for shadow terminator bias. */
object.shadow_terminator_normal_offset = dob.shadow_terminator_normal_offset;
object.shadow_terminator_geometry_offset = dob.shadow_terminator_geometry_offset;
object.shadow_terminator_shading_offset = dob.shadow_terminator_shading_offset;
}
for (Mesh &mesh : bmain->meshes) {
/* Match default for new meshes. */
mesh.smoothresh_legacy = DEG2RADF(30);
/* Match voxel remesher options for all existing meshes in templates. */
mesh.flag |= ME_REMESH_REPROJECT_VOLUME | ME_REMESH_REPROJECT_ATTRIBUTES;
/* For Sculpting template. */
if (app_template && STREQ(app_template, "Sculpting")) {
mesh.remesh_voxel_size = 0.035f;
bke::mesh_smooth_set(mesh, false);
}
else {
/* Remove sculpt-mask data in default mesh objects for all non-sculpt templates. */
CustomData_free_layers(&mesh.vert_data, CD_PAINT_MASK);
CustomData_free_layers(&mesh.corner_data, CD_GRID_PAINT_MASK);
}
mesh.attributes_for_write().remove(".sculpt_face_set");
}
for (Camera &camera : bmain->cameras) {
/* Initialize to a useful value. */
camera.dof.focus_distance = 10.0f;
camera.dof.aperture_fstop = 2.8f;
}
for (Light &light : bmain->lights) {
/* Fix lights defaults. */
light.clipsta = 0.05f;
light.att_dist = 40.0f;
}
/* Materials */
for (Material &ma : bmain->materials) {
/* Update default material to be a bit more rough. */
ma.roughness = 0.5f;
/* Enable transparent shadows. */
ma.blend_flag |= MA_BL_TRANSPARENT_SHADOW;
if (ma.nodetree) {
for (bNode *node : ma.nodetree->all_nodes()) {
if (node->type_legacy == SH_NODE_BSDF_PRINCIPLED) {
bNodeSocket *roughness_socket = bke::node_find_socket(*node, SOCK_IN, "Roughness"_ustr);
*version_cycles_node_socket_float_value(roughness_socket) = 0.5f;
bNodeSocket *emission = bke::node_find_socket(*node, SOCK_IN, "Emission Color"_ustr);
copy_v4_fl(version_cycles_node_socket_rgba_value(emission), 1.0f);
bNodeSocket *emission_strength = bke::node_find_socket(
*node, SOCK_IN, "Emission Strength"_ustr);
*version_cycles_node_socket_float_value(emission_strength) = 0.0f;
bNodeSocket *ior = bke::node_find_socket(*node, SOCK_IN, "IOR"_ustr);
*version_cycles_node_socket_float_value(ior) = 1.5f;
bNodeSocket *subsurface_scale = bke::node_find_socket(
*node, SOCK_IN, "Subsurface Scale"_ustr);
*version_cycles_node_socket_float_value(subsurface_scale) = 0.005f;
node->custom1 = SHD_GLOSSY_MULTI_GGX;
node->custom2 = SHD_SUBSURFACE_RANDOM_WALK;
node->location[0] = -200.0f;
node->location[1] = 100.0f;
BKE_ntree_update_tag_node_property(ma.nodetree, node);
}
else if (node->type_legacy == SH_NODE_SUBSURFACE_SCATTERING) {
node->custom1 = SHD_SUBSURFACE_RANDOM_WALK;
BKE_ntree_update_tag_node_property(ma.nodetree, node);
}
else if (node->type_legacy == SH_NODE_OUTPUT_MATERIAL) {
node->location[0] = 200.0f;
node->location[1] = 100.0f;
}
}
}
}
/* Brushes */
{
/* Remove default brushes replaced by assets. Also remove outliner `treestore` that may point
* to brushes. Normally the treestore is updated properly but it doesn't seem to update during
* versioning code. It's not helpful anyway. */
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (SpaceLink &space_link : area.spacedata) {
if (space_link.spacetype == SPACE_OUTLINER) {
SpaceOutliner *space_outliner = reinterpret_cast<SpaceOutliner *>(&space_link);
if (space_outliner->treestore) {
BLI_mempool_destroy(space_outliner->treestore);
space_outliner->treestore = nullptr;
}
}
}
}
}
for (Brush &brush : bmain->brushes.items_mutable()) {
BKE_id_delete(bmain, &brush);
}
}
{
for (Light &light : bmain->lights) {
light.shadow_maximum_resolution = 0.001f;
light.transmission_fac = 1.0f;
SET_FLAG_FROM_TEST(light.mode, false, LA_SHAD_RES_ABSOLUTE);
}
}
{
for (World &world : bmain->worlds) {
SET_FLAG_FROM_TEST(world.flag, true, WO_USE_SUN_SHADOW);
if (world.nodetree) {
for (bNode *node : world.nodetree->all_nodes()) {
if (node->type_legacy == SH_NODE_OUTPUT_WORLD) {
node->location[0] = 200.0f;
node->location[1] = 100.0f;
}
else if (node->type_legacy == SH_NODE_BACKGROUND) {
node->location[0] = -200.0f;
node->location[1] = 100.0f;
}
}
}
}
}
/* Grease Pencil Anti-Aliasing. */
{
for (Scene &scene : bmain->scenes) {
scene.grease_pencil_settings.smaa_threshold = 1.0f;
scene.grease_pencil_settings.smaa_threshold_render = 0.25f;
scene.grease_pencil_settings.aa_samples = 8;
scene.grease_pencil_settings.motion_blur_steps = 8;
}
}
}
} // namespace blender

View File

@@ -0,0 +1,67 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*
* Apply edits to DNA at load time to behave as if old files were written with new names.
*/
#include "DNA_genfile.h"
#include "readfile.hh"
namespace blender {
void blo_do_versions_dna(SDNA *sdna, const int versionfile, const int subversionfile)
{
#define DNA_VERSION_ATLEAST(ver, subver) \
(versionfile > (ver) || (versionfile == (ver) && (subversionfile >= (subver))))
if (!DNA_VERSION_ATLEAST(280, 2)) {
/* Version files created in the 'blender2.8' branch
* between October 2016, and November 2017 (>=280.0 and < 280.2). */
if (versionfile >= 280) {
DNA_sdna_patch_struct_by_name(sdna, "SceneLayer", "ViewLayer");
DNA_sdna_patch_struct_member_by_name(
sdna, "FileGlobal", "cur_render_layer", "cur_view_layer");
DNA_sdna_patch_struct_member_by_name(
sdna, "ParticleEditSettings", "scene_layer", "view_layer");
DNA_sdna_patch_struct_member_by_name(sdna, "Scene", "active_layer", "active_view_layer");
DNA_sdna_patch_struct_member_by_name(sdna, "Scene", "render_layers", "view_layers");
DNA_sdna_patch_struct_member_by_name(sdna, "WorkSpace", "render_layer", "view_layer");
}
}
if (!DNA_VERSION_ATLEAST(500, 51)) {
/* These old struct names were only used by an experimental feature. They were renamed before
* the feature became official. This versioning just allows to read old files but does not
* provide forward compatibility. */
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryClosureInput", "NodeClosureInput");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryClosureInputItem", "NodeClosureInputItem");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryClosureOutputItem", "NodeClosureOutputItem");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryClosureInputItems", "NodeClosureInputItems");
DNA_sdna_patch_struct_by_name(
sdna, "NodeGeometryClosureOutputItems", "NodeClosureOutputItems");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryClosureOutput", "NodeClosureOutput");
DNA_sdna_patch_struct_by_name(
sdna, "NodeGeometryEvaluateClosureInputItem", "NodeEvaluateClosureInputItem");
DNA_sdna_patch_struct_by_name(
sdna, "NodeGeometryEvaluateClosureOutputItem", "NodeEvaluateClosureOutputItem");
DNA_sdna_patch_struct_by_name(
sdna, "NodeGeometryEvaluateClosureInputItems", "NodeEvaluateClosureInputItems");
DNA_sdna_patch_struct_by_name(
sdna, "NodeGeometryEvaluateClosureOutputItems", "NodeEvaluateClosureOutputItems");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryEvaluateClosure", "NodeEvaluateClosure");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryCombineBundleItem", "NodeCombineBundleItem");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometryCombineBundle", "NodeCombineBundle");
DNA_sdna_patch_struct_by_name(
sdna, "NodeGeometrySeparateBundleItem", "NodeSeparateBundleItem");
DNA_sdna_patch_struct_by_name(sdna, "NodeGeometrySeparateBundle", "NodeSeparateBundle");
}
#undef DNA_VERSION_ATLEAST
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
/**
* This file is a template to use as base when switching to a new version of Blender.
*
* DO NOT add this file to CMakeList.txt.
*
* When initializing a new version of Blender in main, after branching out the current one into
* its release branch:
* - Copy that file and rename it to the proper new version number (e.g. `versioning_510.cc`).
* - Rename the two functions below by replacing the `xxx` with the matching new version number.
* - Add the new file to CMakeList.txt
* - Add matching calls in #do_versions_after_linking and #do_versions, in `readfile.cc` and update
* declarations in`readfile.hh`.
*/
#define DNA_DEPRECATED_ALLOW
#include "DNA_ID.h"
#include "BLI_sys_types.h"
#include "BKE_main.hh"
#include "readfile.hh"
#include "versioning_common.hh"
// #include "CLG_log.h"
namespace blender {
// static CLG_LogRef LOG = {"blend.doversion"};
void do_versions_after_linking_xxx(FileData * /*fd*/, Main * /*bmain*/)
{
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
}
void blo_do_versions_xxx(FileData * /*fd*/, Library * /*lib*/, Main * /*bmain*/)
{
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
* blenloader readfile private function prototypes.
*/
#pragma once
#include <ostream>
#include "DNA_sdna_pointers.hh"
#include "DNA_sdna_types.h"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLO_undofile.hh"
namespace blender {
class WriteWrap;
struct WriteDataStableAddressIDs {
/**
* Knows which DNA members are pointers. Those members are overridden when serializing the
* .blend file to get more stable pointer identifiers.
*/
std::shared_ptr<dna::pointers::PointersInDNA> sdna_pointers;
/**
* Maps each runtime-pointer to a unique identifier that's written in the .blend file.
*
* Currently, for regular blendfile writing, no pointers are ever removed from this map during
* writing of a single file.
* Correctness wise, this is fine. However, when some data-blocks write temporary addresses,
* those may be reused across IDs while actually pointing to different data. This can break
* address id stability in some situations. In the future this could be improved by clearing
* such temporary pointers before writing the next data-block.
*
* In undo case, only a very small sub-set of 'generated' addresses (temp data generated for
* writefile only) is added to this mapping, most pointers are written as-is (since their
* addresses are always stable in undo context: if unchanged, address remains the same).
* The mapping is also cleared after every ID writing, to allow temp allocated data to re-use the
* same source address across different IDs. The generated address ids will always be unique
* though (see also #used_ids description below).
*/
Map<const void *, uint64_t> pointer_map;
/**
* Contains all the #pointer_map.values() ever generated during a writefile operation.
*
* This is used to make sure that the same id is never reused for a different pointer. While this
* is technically allowed in .blend files (when the pointers are local data of different
* objects), we currently don't always know what type a pointer points to when writing it. So we
* can't determine if a pointer is local or not.
*
* Note that in undo (memfile) case, this set is _not_ reset after every ID writing, to ensure
* that all generated address ids remain unique across a whole blend-file writing operation.
*/
Set<uint64_t> used_ids;
/**
* The next stable address id is derived from this. This is modified in
* two cases:
* - A new stable address is needed, in which case this is just incremented.
* - A new "section" of the .blend file starts. In this case, this should be reinitialized with
* some hash of an identifier of the next section. This makes sure that if the number of
* pointers in the previous section is modified, the pointers in the new section are not
* affected. A "section" can be anything, but currently a section simply starts when a new
* data-block starts. In the future, an API could be added that allows sections to start
* within a data-block which could isolate stable pointer ids even more.
*
* When creating the new address id, keep in mind that this may be 0 and it may collide with
* previous hints.
*/
uint64_t next_id_hint = 0;
};
struct WriteData {
const SDNA *sdna;
std::ostream *debug_dst = nullptr;
struct {
/** Use for file and memory writing (size stored in max_size). */
uchar *buf;
/** Number of bytes used in #WriteData.buf (flushed when exceeded). */
size_t used_len;
/** Maximum size of the buffer. */
size_t max_size;
/** Threshold above which writes get their own chunk. */
size_t chunk_size;
} buffer;
#ifdef USE_WRITE_DATA_LEN
/** Total number of bytes written. */
size_t write_len;
#endif
/** Whether writefile code is currently writing an ID. */
bool is_writing_id;
/** Some validation and error handling data. */
struct {
/**
* Set on unlikely case of an error (ignores further file writing). Only used for very
* low-level errors (like if the actual write on file fails).
*/
bool critical_error;
/**
* A set of all 'old' addresses used as UID of written blocks for the current ID. Allows
* detecting invalid re-uses of the same address multiple times.
*/
Set<const void *> per_id_addresses_set;
} validation_data;
/**
* Data to generate stable fake pointer values in written blendfile.
*
* \note For undo steps, a partial copy of this data is stored in the written MemFile at the end
* of the writing, and used to initialize this data on the next undo step writing (see
* #BLO_memfile_write_init and #BLO_memfile_write_finalize).
*/
WriteDataStableAddressIDs stable_address_ids;
/**
* Keeps track of which shared data has been written for the current ID. This is necessary to
* avoid writing the same data more than once.
*/
Set<const void *> per_id_written_shared_addresses;
/** #MemFile writing (used for undo). */
MemFileWriteData mem;
/** When true, write to #WriteData.current, could also call 'is_undo'. */
bool use_memfile;
/**
* Wrap writing, so we can use ZSTD or
* other compression types later, see: #G_FILE_COMPRESS.
* Will be nullptr for UNDO.
*/
WriteWrap *ww;
/**
* Timestamp info defined when creating the new WriteData. Used for performance logging.
*/
double timestamp_init;
};
} // namespace blender

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "blendfile_loading_base_test.h"
#include "BLI_path_utils.hh"
namespace blender {
class BlendfileLoadingTest : public BlendfileLoadingBaseTest {};
TEST_F(BlendfileLoadingTest, CanaryTest)
{
/* Load the smallest blend file we have in the tests/files directory. */
if (!blendfile_load("modifier_stack" SEP_STR "array_test.blend")) {
return;
}
depsgraph_create(DAG_EVAL_RENDER);
EXPECT_NE(nullptr, this->depsgraph);
}
} // namespace blender

View File

@@ -0,0 +1,175 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "blendfile_loading_base_test.h"
#include "MEM_guardedalloc.h"
#include "BKE_appdir.hh"
#include "BKE_blender.hh"
#include "BKE_callbacks.hh"
#include "BKE_context.hh"
#include "BKE_cpp_types.hh"
#include "BKE_global.hh"
#include "BKE_idtype.hh"
#include "BKE_image.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_mball_tessellate.hh"
#include "BKE_modifier.hh"
#include "BKE_node.hh"
#include "BKE_scene.hh"
#include "BKE_vfont.hh"
#include "BLF_api.hh"
#include "BLI_listbase.h"
#include "BLI_path_utils.hh"
#include "BLI_threads.h"
#include "BLO_readfile.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DNA_genfile.h" /* for DNA_sdna_current_init() */
#include "DNA_windowmanager_types.h"
#include "IMB_imbuf.hh"
#include "ED_datafiles.h"
#include "RNA_define.hh"
#include "SEQ_modifier.hh"
#include "WM_api.hh"
#include "wm.hh"
#include "CLG_log.h"
namespace blender {
void BlendfileLoadingBaseTest::SetUpTestCase()
{
testing::Test::SetUpTestCase();
/* Minimal code to make loading a blendfile and constructing a depsgraph not crash, copied from
* main() in creator.c. */
CLG_init();
BLI_threadapi_init();
DNA_sdna_current_init();
BKE_blender_globals_init();
BKE_idtype_init();
BKE_cpp_types_init();
BKE_appdir_init();
IMB_init();
BKE_modifier_init();
seq::modifiers_init();
DEG_register_node_types();
RNA_init();
bke::node_system_init();
BKE_callback_global_init();
BKE_vfont_builtin_register(datatoc_bfont_pfb, datatoc_bfont_pfb_size);
BLF_init();
BKE_blender_globals_main_replace(BKE_main_new());
G.background = true;
G.factory_startup = true;
/* Allocate a dummy window manager. The real window manager will try and load Python scripts from
* the release directory, which it won't be able to find. */
ASSERT_EQ(G.main->wm.first, nullptr);
wmWindowManager *wm = BKE_id_new<wmWindowManager>(G.main, "WMdummy");
wm->runtime = MEM_new<bke::WindowManagerRuntime>(__func__);
}
void BlendfileLoadingBaseTest::TearDownTestCase()
{
/* Copied from WM_exit_ex() in wm_init_exit.cc, and cherry-picked those lines that match the
* allocation/initialization done in SetUpTestCase(). */
BKE_blender_free();
RNA_exit();
BLF_exit();
DEG_free_node_types();
DNA_sdna_current_free();
BLI_threadapi_exit();
BKE_blender_atexit();
BKE_tempdir_session_purge();
BKE_appdir_exit();
CLG_exit();
testing::Test::TearDownTestCase();
}
void BlendfileLoadingBaseTest::TearDown()
{
BKE_mball_cubeTable_free();
blendfile_free();
depsgraph_free();
testing::Test::TearDown();
}
bool BlendfileLoadingBaseTest::blendfile_load(const char *filepath)
{
const std::string &test_assets_dir = tests::flags_test_asset_dir();
if (test_assets_dir.empty()) {
return false;
}
char abspath[FILE_MAX];
BLI_path_join(abspath, sizeof(abspath), test_assets_dir.c_str(), filepath);
BlendFileReadReport bf_reports = {};
bfile = BLO_read_from_file(abspath, BLO_READ_SKIP_NONE, &bf_reports);
if (bfile == nullptr) {
ADD_FAILURE() << "Unable to load file '" << filepath << "' from test assets dir '"
<< test_assets_dir << "'";
return false;
}
/* Make sure that all view_layers in the file are synced. Depsgraph can make a copy of the whole
* scene, which will fail when one view layer isn't synced. */
for (ViewLayer &view_layer : bfile->curscene->view_layers) {
BKE_view_layer_synced_ensure(*bfile->main, bfile->curscene, &view_layer);
}
return true;
}
void BlendfileLoadingBaseTest::blendfile_free()
{
if (bfile == nullptr) {
return;
}
BLO_blendfiledata_free(bfile);
bfile = nullptr;
}
void BlendfileLoadingBaseTest::depsgraph_create(eEvaluationMode depsgraph_evaluation_mode)
{
depsgraph = DEG_graph_new(
bfile->main, bfile->curscene, bfile->cur_view_layer, depsgraph_evaluation_mode);
DEG_graph_build_from_view_layer(depsgraph);
BKE_scene_graph_update_tagged(depsgraph, bfile->main);
}
void BlendfileLoadingBaseTest::depsgraph_free()
{
if (depsgraph == nullptr) {
return;
}
DEG_graph_free(depsgraph);
depsgraph = nullptr;
}
} // namespace blender

View File

@@ -0,0 +1,50 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DEG_depsgraph.hh"
#include "testing/testing.h"
namespace blender {
struct Depsgraph;
struct BlendFileData;
class BlendfileLoadingBaseTest : public testing::Test {
protected:
struct BlendFileData *bfile = nullptr;
struct Depsgraph *depsgraph = nullptr;
public:
/* Sets up Blender just enough to not crash on loading
* a blendfile and constructing a depsgraph. */
static void SetUpTestCase();
static void TearDownTestCase();
protected:
/* Frees the depsgraph & blendfile. */
void TearDown() override;
/* Loads a blend file from the tests/files directory from SVN.
* Returns 'ok' flag (true=good, false=bad) and sets `this->bfile`.
* Fails the test if the file cannot be loaded (still returns though).
* Requires the CLI argument `--test-asset-dir` to point to `../tests/files`.
*
* WARNING: only files saved with Blender 2.80+ can be loaded. Since Blender
* is only partially initialized (most importantly, without window manager),
* the space types are not registered, so any versioning code that handles
* those will SEGFAULT.
*/
bool blendfile_load(const char *filepath);
/* Free bfile if it is not nullptr. */
void blendfile_free();
/* Create a depsgraph. Assumes a blend file has been loaded to this->bfile. */
virtual void depsgraph_create(eEvaluationMode depsgraph_evaluation_mode);
/* Free the depsgraph if it's not nullptr. */
virtual void depsgraph_free();
};
} // namespace blender

View File

@@ -0,0 +1,319 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*/
#pragma once
#include "BLI_function_ref.hh"
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BKE_fcurve.hh"
#include "RNA_define.hh"
#include "DNA_anim_types.h"
#include "DNA_listBase.h"
#include "DNA_node_types.h"
namespace blender {
struct ARegion;
struct bNode;
struct bNodeSocket;
struct bNodeTree;
struct ID;
struct IDProperty;
struct Main;
struct ViewLayer;
struct SceneRenderLayer;
/**
* Mapping from new ID types to old converted ID types.
*
* This is used by linking code, when a directly linked data comes from a library where it exists
* as an older, different type of ID.
*
* Since the current blend-file will convert that older ID type to the new one as part of its
* versioning process, when saved, it will store a reference to that linked ID using the _new_ ID
* type, which will not be found on next blend-file opening/reloading when searching for that ID in
* the library blend-file.
*
* \returns The old, deprecated ID type code if any matches the given `id_code_new` one, otherwise
* `ID_LINK_PLACEHOLDER`.
*/
short do_versions_new_to_old_idcode_get(short id_code_new);
/**
* Check if a region of type \a region_type exists in \a regionbase. Otherwise add it after the
* first region of type \a link_after_region_type.
* \returns null if a region of the given type already existed, otherwise the newly added region.
*/
ARegion *do_versions_add_region_if_not_found(ListBaseT<ARegion> *regionbase,
int region_type,
const char *allocname,
int link_after_region_type);
/**
* Check if a region of type \a region_type exists in \a regionbase. Otherwise add it after the
* first region of type \a link_after_region_type.
* \returns either a new, or already existing region.
*/
ARegion *do_versions_ensure_region(ListBaseT<ARegion> *regionbase,
int region_type,
const char *allocname,
int link_after_region_type);
/**
* Rename if the ID doesn't exist.
*
* \return the ID (if found).
*/
ID *do_versions_rename_id(Main *bmain, short id_type, const char *name_src, const char *name_dst);
bool version_node_socket_is_used(bNodeSocket *sock);
void version_node_socket_name(bNodeTree *ntree,
int node_type,
const char *old_name,
const char *new_name);
void version_node_input_socket_name(bNodeTree *ntree,
int node_type,
const char *old_name,
const char *new_name);
void version_node_output_socket_name(bNodeTree *ntree,
int node_type,
const char *old_name,
const char *new_name);
/**
* Find the base socket name for an idname that may include a subtype.
*/
StringRef legacy_socket_idname_to_socket_type(StringRef idname);
/**
* Adds a new node for versioning purposes. This is intended to be used to create raw DNA that
* might have been read from a file. The created node does not have storage or sockets. Both have
* to be added manually afterwards.
*
* This may seem redundant because the set of sockets is already part of the node declaration.
* However, the declaration should not be used here, because it changes over time. The versioning
* code generally expects to get the sockets that the node had at the time of writing the
* versioning code. Changing the declaration later can break the versioning code in ways that are
* hard to detect.
*
* When adding new nodes in versioning code that replace or belong to existing nodes, they should
* be positioned so that it overlaps the existing node with just a slight offset. This is better
* than putting them next to each other they way one would do it manually, because it messes up
* more complex node trees significantly. In simple tests, putting the nodes next to each other
* looks better, but in actual user-files it looks way worse and makes it less obvious what was
* changed by versioning code.
*/
bNode &version_node_add_empty(bNodeTree &ntree, const char *idname);
/**
* Similar to #version_node_add_empty but doesn't require a valid #idname. This is typically
* needed to write a node in a blend file for forward compatibility reasons, where the node was
* removed and has no RNA definition anymore.
*
* The same rules defined in #version_node_add_empty apply here as well (node placement, separate
* socket and storage definition etc..).
*
* The parameters are needed to create a valid #bNodeType to set it as node->typeinfo.
* See also #bNodeType for more details.
*/
bNode &version_node_add_unknown(bNodeTree &ntree,
bke::bNodeType &node_type,
const char *idname,
const int16_t legacy_type,
const std::string &ui_name,
const std::string &ui_description,
const std::string &enum_name_legacy,
const short nclass,
const float width = 140.0f,
const float height = 100.0f,
const bool no_muting = false);
/**
* Removes a node for versioning purposes:
* - Animation data (#AnimData) are not removed, because they might be using #bAction.id which
* is not be available before linking.
* - User count is not updated. This is ensured after blend file reading is done.
*/
void version_node_remove(bNodeTree &ntree, bNode &node);
bNodeSocket &version_node_add_socket(bNodeTree &ntree,
bNode &node,
eNodeSocketInOut in_out,
const char *idname,
const char *identifier);
bNodeLink &version_node_add_link(
bNodeTree &ntree, bNode &node_a, bNodeSocket &socket_a, bNode &node_b, bNodeSocket &socket_b);
/**
* Returns true if the node has valid storage data.
* If the node does not have storage data then the node type is set to "Undefined" to prevent
* further access and the function returns false.
*
* Storage can get lost when saving nodes in older versions and then loading such files may contain
* nodes where storage is expected but does not exist (#154086).
*/
bool version_node_ensure_storage_or_invalidate(bNode &node);
/**
* Adjust animation data for newly added node sockets.
*
* Node sockets are addressed by their index (in their RNA path, and thus FCurves/drivers), and
* thus when a new node is added in the middle of the list, existing animation data needs to be
* adjusted.
*
* Since this is about animation data, it only concerns input sockets.
*
* \param node_tree_type: Node tree type that has these nodes, for example #NTREE_SHADER.
* \param node_type: Node type to adjust, for example #SH_NODE_BSDF_PRINCIPLED.
* \param socket_index_orig: The original index of the moved socket; when socket 4 moved to 6,
* pass 4 here.
* \param socket_index_offset: The offset of the nodes, so when socket 4 moved to 6,
* pass 2 here.
* \param total_number_of_sockets: The total number of sockets in the node.
*/
void version_node_socket_index_animdata(
Main *bmain,
int node_tree_type, /* NTREE_....., e.g. NTREE_SHADER */
int node_type, /* SH_NODE_..., e.g. SH_NODE_BSDF_PRINCIPLED */
int socket_index_orig,
int socket_index_offset,
int total_number_of_sockets);
/**
* Replace the ID name of all nodes in the tree with the given type with the new name.
*/
void version_node_id(bNodeTree *ntree, int node_type, const char *new_name);
/**
* Convert `SocketName.001` unique name format to `SocketName_001`. Previously both were used.
*/
void version_node_socket_id_delim(bNodeSocket *socket);
void version_node_socket_identifier_set(bNodeSocket &socket, StringRefNull identifier);
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);
void version_node_tree_clear_interface(bNodeTree &ntree);
/**
* Change socket identifiers so that everything after the separator is removed for available
* sockets.
*/
void version_socket_identifier_suffixes_for_dynamic_types(
const ListBaseT<bNodeSocket> &sockets,
const char *separator,
const std::optional<int> total = std::nullopt);
/**
* The versioning code generally expects `SOCK_IS_LINKED` to be set correctly. This function
* updates the flag on all sockets after changes to the node tree.
*/
void version_socket_update_is_used(bNodeTree *ntree);
ARegion *do_versions_add_region(int regiontype, const char *name);
void sequencer_init_preview_region(ARegion *region);
void add_realize_instances_before_socket(bNodeTree *ntree,
bNode *node,
bNodeSocket *geometry_socket);
float *version_cycles_node_socket_float_value(bNodeSocket *socket);
float *version_cycles_node_socket_rgba_value(bNodeSocket *socket);
float *version_cycles_node_socket_vector_value(bNodeSocket *socket);
IDProperty *version_cycles_properties_from_ID(ID *id);
IDProperty *version_cycles_properties_from_view_layer(ViewLayer *view_layer);
IDProperty *version_cycles_properties_from_render_layer(SceneRenderLayer *render_layer);
IDProperty *version_cycles_visibility_properties_from_ID(ID *id);
float version_cycles_property_float(IDProperty *idprop, const char *name, float default_value);
int version_cycles_property_int(IDProperty *idprop, const char *name, int default_value);
void version_cycles_property_int_set(IDProperty *idprop, const char *name, int value);
bool version_cycles_property_boolean(IDProperty *idprop, const char *name, bool default_value);
void version_cycles_property_boolean_set(IDProperty *idprop, const char *name, bool value);
void node_tree_relink_with_socket_id_map(bNodeTree &ntree,
bNode &old_node,
bNode &new_node,
const Map<std::string, std::string> &map);
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);
bNode *version_eevee_output_node_get(bNodeTree *ntree, int16_t node_type);
/**
* Allow 5.0+ to 'convert' older blend-files' system properties storage.
*/
void version_system_idprops_generate(Main *bmain);
void version_system_idprops_nodes_generate(Main *bmain);
void version_system_idprops_children_bones_generate(Main *bmain);
bool all_scenes_use(Main *bmain, const Span<const char *> engines);
/**
* Adjust the values of the given FCurve key frames by applying the given function. The function is
* expected to get and return a float representing the value of the key frame. The FCurve is
* potentially changed to have the given property type, if not already the case.
*/
template<typename Function>
static void adjust_fcurve_key_frame_values(FCurve *fcurve,
const PropertyType property_type,
const Function &function)
{
/* Adjust key frames. */
if (fcurve->bezt) {
for (int i = 0; i < fcurve->totvert; i++) {
fcurve->bezt[i].vec[0][1] = function(fcurve->bezt[i].vec[0][1]);
fcurve->bezt[i].vec[1][1] = function(fcurve->bezt[i].vec[1][1]);
fcurve->bezt[i].vec[2][1] = function(fcurve->bezt[i].vec[2][1]);
}
}
/* Adjust baked key frames. */
if (fcurve->fpt) {
for (int i = 0; i < fcurve->totvert; i++) {
fcurve->fpt[i].vec[1] = function(fcurve->fpt[i].vec[1]);
}
}
/* Setup the flags based on the property type. */
fcurve->flag &= ~(FCURVE_INT_VALUES | FCURVE_DISCRETE_VALUES);
switch (property_type) {
case PROP_FLOAT:
break;
case PROP_INT:
fcurve->flag |= FCURVE_INT_VALUES;
break;
default:
fcurve->flag |= (FCURVE_DISCRETE_VALUES | FCURVE_INT_VALUES);
break;
}
/* Recalculate the automatic handles of the FCurve after adjustments. */
BKE_fcurve_handles_recalc(*fcurve);
}
/* Gets the compositing node tree of the given scene. The deprecated node-tree member is returned
* for older versions before reusable node trees were introduced in bd61e69be5, while the new
* compositing_node_group is returned otherwise. */
bNodeTree *version_get_scene_compositor_node_tree(Main *bmain, Scene *scene);
} // namespace blender