diff options
Diffstat (limited to 'source')
694 files changed, 19061 insertions, 4830 deletions
diff --git a/source/blender/blendthumb/src/Dll.cpp b/source/blender/blendthumb/src/Dll.cpp index 6516540034e..7f10777f884 100644 --- a/source/blender/blendthumb/src/Dll.cpp +++ b/source/blender/blendthumb/src/Dll.cpp @@ -14,11 +14,17 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/** \file + * \ingroup blendthumb + * + * Thumbnail from Blend file extraction for MS-Windows (DLL). + */ + #include <new> #include <objbase.h> -#include <shlobj.h> // For SHChangeNotify +#include <shlobj.h> /* For #SHChangeNotify */ #include <shlwapi.h> -#include <thumbcache.h> // For IThumbnailProvider. +#include <thumbcache.h> /* For IThumbnailProvider */ extern HRESULT CBlendThumb_CreateInstance(REFIID riid, void **ppv); @@ -33,16 +39,16 @@ struct CLASS_OBJECT_INIT { PFNCREATEINSTANCE pfnCreate; }; -// add classes supported by this module here +/* Add classes supported by this module here. */ const CLASS_OBJECT_INIT c_rgClassObjectInit[] = { {&CLSID_BlendThumbHandler, CBlendThumb_CreateInstance}}; long g_cRefModule = 0; -// Handle the DLL's module -HINSTANCE g_hInst = NULL; +/** Handle the DLL's module */ +HINSTANCE g_hInst = nullptr; -// Standard DLL functions +/** Standard DLL functions. */ STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *) { if (dwReason == DLL_PROCESS_ATTACH) { @@ -54,7 +60,7 @@ STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *) STDAPI DllCanUnloadNow() { - // Only allow the DLL to be unloaded after all outstanding references have been released + /* Only allow the DLL to be unloaded after all outstanding references have been released. */ return (g_cRefModule == 0) ? S_OK : S_FALSE; } @@ -76,7 +82,7 @@ class CClassFactory : public IClassFactory { REFIID riid, void **ppv) { - *ppv = NULL; + *ppv = nullptr; HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; for (size_t i = 0; i < cClassObjectInits; i++) { if (clsid == *pClassObjectInits[i].pClsid) { @@ -87,7 +93,8 @@ class CClassFactory : public IClassFactory { hr = pClassFactory->QueryInterface(riid, ppv); pClassFactory->Release(); } - break; // match found + /* Match found. */ + break; } } return hr; @@ -98,7 +105,7 @@ class CClassFactory : public IClassFactory { DllAddRef(); } - // IUnknown + /** #IUnknown */ IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = {QITABENT(CClassFactory, IClassFactory), {0}}; @@ -119,7 +126,7 @@ class CClassFactory : public IClassFactory { return cRef; } - // IClassFactory + /** #IClassFactory */ IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv); @@ -152,33 +159,37 @@ STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv) clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv); } -// A struct to hold the information required for a registry entry - +/** + * A struct to hold the information required for a registry entry. + */ struct REGISTRY_ENTRY { HKEY hkeyRoot; PCWSTR pszKeyName; PCWSTR pszValueName; DWORD dwValueType; - PCWSTR pszData; // These two fields could/should have been a union, but C++ - DWORD dwData; // only lets you initialize the first field in a union. + /** These two fields could/should have been a union, but C++ */ + PCWSTR pszData; + /** Only lets you initialize the first field in a union. */ + DWORD dwData; }; -// Creates a registry key (if needed) and sets the default value of the key - +/** + * Creates a registry key (if needed) and sets the default value of the key. + */ HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry) { HKEY hKey; HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot, pRegistryEntry->pszKeyName, 0, - NULL, + nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, - NULL, + nullptr, &hKey, - NULL)); + nullptr)); if (SUCCEEDED(hr)) { - // All this just to support REG_DWORD... + /* All this just to support #REG_DWORD. */ DWORD size; DWORD data; BYTE *lpData = (LPBYTE)pRegistryEntry->pszData; @@ -202,9 +213,9 @@ HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry) return hr; } -// -// Registers this COM server -// +/** + * Registers this COM server. + */ STDAPI DllRegisterServer() { HRESULT hr; @@ -216,15 +227,15 @@ STDAPI DllRegisterServer() } else { const REGISTRY_ENTRY rgRegistryEntries[] = { - // RootKey KeyName ValueName ValueType Data + /* `RootKey KeyName ValueName ValueType Data` */ {HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER, - NULL, + nullptr, REG_SZ, SZ_BLENDTHUMBHANDLER}, {HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER L"\\InProcServer32", - NULL, + nullptr, REG_SZ, szModuleName}, {HKEY_CURRENT_USER, @@ -237,10 +248,10 @@ STDAPI DllRegisterServer() L"Treatment", REG_DWORD, 0, - 0}, // doesn't appear to do anything... + 0}, /* This doesn't appear to do anything. */ {HKEY_CURRENT_USER, L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", - NULL, + nullptr, REG_SZ, SZ_CLSID_BLENDTHUMBHANDLER}, }; @@ -251,17 +262,17 @@ STDAPI DllRegisterServer() } } if (SUCCEEDED(hr)) { - // This tells the shell to invalidate the thumbnail cache. This is important because any - // .blend files viewed before registering this handler would otherwise show cached blank - // thumbnails. - SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + /* This tells the shell to invalidate the thumbnail cache. + * This is important because any `.blend` files viewed before registering this handler + * would otherwise show cached blank thumbnails. */ + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); } return hr; } -// -// Unregisters this COM server -// +/** + * Unregisters this COM server + */ STDAPI DllUnregisterServer() { HRESULT hr = S_OK; @@ -270,11 +281,11 @@ STDAPI DllUnregisterServer() L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER, L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"}; - // Delete the registry entries + /* Delete the registry entries. */ for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++) { hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i])); if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { - // If the registry entry has already been deleted, say S_OK. + /* If the registry entry has already been deleted, say S_OK. */ hr = S_OK; } } diff --git a/source/blender/blenkernel/BKE_asset.h b/source/blender/blenkernel/BKE_asset.h index 50eb2859279..42eea41b7a7 100644 --- a/source/blender/blenkernel/BKE_asset.h +++ b/source/blender/blenkernel/BKE_asset.h @@ -22,6 +22,8 @@ #include "BLI_utildefines.h" +#include "DNA_asset_types.h" + #ifdef __cplusplus extern "C" { #endif @@ -46,6 +48,12 @@ struct AssetTagEnsureResult BKE_asset_metadata_tag_ensure(struct AssetMetaData * const char *name); void BKE_asset_metadata_tag_remove(struct AssetMetaData *asset_data, struct AssetTag *tag); +/** Clean up the catalog ID (white-spaces removed, length reduced, etc.) and assign it. */ +void BKE_asset_metadata_catalog_id_clear(struct AssetMetaData *asset_data); +void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data, + bUUID catalog_id, + const char *catalog_simple_name); + void BKE_asset_library_reference_init_default(struct AssetLibraryReference *library_ref); struct PreviewImage *BKE_asset_metadata_preview_get_from_id(const struct AssetMetaData *asset_data, diff --git a/source/blender/blenkernel/BKE_asset_catalog.hh b/source/blender/blenkernel/BKE_asset_catalog.hh new file mode 100644 index 00000000000..8afc4fe2ad2 --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_catalog.hh @@ -0,0 +1,360 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#pragma once + +#ifndef __cplusplus +# error This is a C++ header. The C interface is yet to be implemented/designed. +#endif + +#include "BLI_function_ref.hh" +#include "BLI_map.hh" +#include "BLI_set.hh" +#include "BLI_string_ref.hh" +#include "BLI_uuid.h" +#include "BLI_vector.hh" + +#include "BKE_asset_catalog_path.hh" + +#include <map> +#include <memory> +#include <set> +#include <string> + +namespace blender::bke { + +using CatalogID = bUUID; +using CatalogPathComponent = std::string; +/* Would be nice to be able to use `std::filesystem::path` for this, but it's currently not + * available on the minimum macOS target version. */ +using CatalogFilePath = std::string; + +class AssetCatalog; +class AssetCatalogDefinitionFile; +class AssetCatalogTree; +class AssetCatalogFilter; + +/* Manages the asset catalogs of a single asset library (i.e. of catalogs defined in a single + * directory hierarchy). */ +class AssetCatalogService { + public: + static const CatalogFilePath DEFAULT_CATALOG_FILENAME; + + public: + AssetCatalogService() = default; + explicit AssetCatalogService(const CatalogFilePath &asset_library_root); + + /** Load asset catalog definitions from the files found in the asset library. */ + void load_from_disk(); + /** Load asset catalog definitions from the given file or directory. */ + void load_from_disk(const CatalogFilePath &file_or_directory_path); + + /** + * Write the catalog definitions to disk in response to the blend file being saved. + * + * The location where the catalogs are saved is variable, and depends on the location of the + * blend file. The first matching rule wins: + * + * - Already loaded a CDF from disk? + * -> Always write to that file. + * - The directory containing the blend file has a blender_assets.cats.txt file? + * -> Merge with & write to that file. + * - The directory containing the blend file is part of an asset library, as per + * the user's preferences? + * -> Merge with & write to ${ASSET_LIBRARY_ROOT}/blender_assets.cats.txt + * - Create a new file blender_assets.cats.txt next to the blend file. + * + * Return true on success, which either means there were no in-memory categories to save, + * or the save was successful. */ + bool write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path); + + /** + * Merge on-disk changes into the in-memory asset catalogs. + * This should be called before writing the asset catalogs to disk. + * + * - New on-disk catalogs are loaded into memory. + * - Already-known on-disk catalogs are ignored (so will be overwritten with our in-memory + * data). This includes in-memory marked-as-deleted catalogs. + */ + void merge_from_disk_before_writing(); + + /** Return catalog with the given ID. Return nullptr if not found. */ + AssetCatalog *find_catalog(CatalogID catalog_id) const; + + /** Return first catalog with the given path. Return nullptr if not found. This is not an + * efficient call as it's just a linear search over the catalogs. */ + AssetCatalog *find_catalog_by_path(const AssetCatalogPath &path) const; + + /** + * Create a filter object that can be used to determine whether an asset belongs to the given + * catalog, or any of the catalogs in the sub-tree rooted at the given catalog. + * + * \see #AssetCatalogFilter + */ + AssetCatalogFilter create_catalog_filter(CatalogID active_catalog_id) const; + + /** Create a catalog with some sensible auto-generated catalog ID. + * The catalog will be saved to the default catalog file.*/ + AssetCatalog *create_catalog(const AssetCatalogPath &catalog_path); + + /** + * Soft-delete the catalog, ensuring it actually gets deleted when the catalog definition file is + * written. */ + void delete_catalog(CatalogID catalog_id); + + /** + * Update the catalog path, also updating the catalog path of all sub-catalogs. + */ + void update_catalog_path(CatalogID catalog_id, const AssetCatalogPath &new_catalog_path); + + AssetCatalogTree *get_catalog_tree(); + + /** Return true only if there are no catalogs known. */ + bool is_empty() const; + + protected: + /* These pointers are owned by this AssetCatalogService. */ + Map<CatalogID, std::unique_ptr<AssetCatalog>> catalogs_; + Map<CatalogID, std::unique_ptr<AssetCatalog>> deleted_catalogs_; + std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_; + std::unique_ptr<AssetCatalogTree> catalog_tree_ = std::make_unique<AssetCatalogTree>(); + CatalogFilePath asset_library_root_; + + void load_directory_recursive(const CatalogFilePath &directory_path); + void load_single_file(const CatalogFilePath &catalog_definition_file_path); + + std::unique_ptr<AssetCatalogDefinitionFile> parse_catalog_file( + const CatalogFilePath &catalog_definition_file_path); + + /** + * Construct an in-memory catalog definition file (CDF) from the currently known catalogs. + * This object can then be processed further before saving to disk. */ + std::unique_ptr<AssetCatalogDefinitionFile> construct_cdf_in_memory( + const CatalogFilePath &file_path); + + /** + * Find a suitable path to write a CDF to. + * + * This depends on the location of the blend file, and on whether a CDF already exists next to it + * or whether the blend file is saved inside an asset library. + */ + static CatalogFilePath find_suitable_cdf_path_for_writing( + const CatalogFilePath &blend_file_path); + + std::unique_ptr<AssetCatalogTree> read_into_tree(); + void rebuild_tree(); + + /** + * For every catalog, ensure that its parent path also has a known catalog. + */ + void create_missing_catalogs(); +}; + +/** + * Representation of a catalog path in the #AssetCatalogTree. + */ +class AssetCatalogTreeItem { + friend class AssetCatalogTree; + + public: + /** Container for child items. Uses a #std::map to keep items ordered by their name (i.e. their + * last catalog component). */ + using ChildMap = std::map<std::string, AssetCatalogTreeItem>; + using ItemIterFn = FunctionRef<void(AssetCatalogTreeItem &)>; + + AssetCatalogTreeItem(StringRef name, + CatalogID catalog_id, + const AssetCatalogTreeItem *parent = nullptr); + + CatalogID get_catalog_id() const; + StringRef get_name() const; + /** Return the full catalog path, defined as the name of this catalog prefixed by the full + * catalog path of its parent and a separator. */ + AssetCatalogPath catalog_path() const; + int count_parents() const; + bool has_children() const; + + /** Iterate over children calling \a callback for each of them, but do not recurse into their + * children. */ + void foreach_child(const ItemIterFn callback); + + protected: + /** Child tree items, ordered by their names. */ + ChildMap children_; + /** The user visible name of this component. */ + CatalogPathComponent name_; + CatalogID catalog_id_; + + /** Pointer back to the parent item. Used to reconstruct the hierarchy from an item (e.g. to + * build a path). */ + const AssetCatalogTreeItem *parent_ = nullptr; + + private: + static void foreach_item_recursive(ChildMap &children_, ItemIterFn callback); +}; + +/** + * A representation of the catalog paths as tree structure. Each component of the catalog tree is + * represented by an #AssetCatalogTreeItem. The last path component of an item is used as its name, + * which may also be shown to the user. + * An item can not have multiple children with the same name. That means the name uniquely + * identifies an item within its parent. + * + * There is no single root tree element, the #AssetCatalogTree instance itself represents the root. + */ +class AssetCatalogTree { + using ChildMap = AssetCatalogTreeItem::ChildMap; + using ItemIterFn = AssetCatalogTreeItem::ItemIterFn; + + public: + /** Ensure an item representing \a path is in the tree, adding it if necessary. */ + void insert_item(const AssetCatalog &catalog); + + void foreach_item(const AssetCatalogTreeItem::ItemIterFn callback); + /** Iterate over root items calling \a callback for each of them, but do not recurse into their + * children. */ + void foreach_root_item(const ItemIterFn callback); + + protected: + /** Child tree items, ordered by their names. */ + ChildMap root_items_; +}; + +/** Keeps track of which catalogs are defined in a certain file on disk. + * Only contains non-owning pointers to the #AssetCatalog instances, so ensure the lifetime of this + * class is shorter than that of the #`AssetCatalog`s themselves. */ +class AssetCatalogDefinitionFile { + public: + /* For now this is the only version of the catalog definition files that is supported. + * Later versioning code may be added to handle older files. */ + const static int SUPPORTED_VERSION; + const static std::string VERSION_MARKER; + const static std::string HEADER; + + CatalogFilePath file_path; + + AssetCatalogDefinitionFile() = default; + + /** + * Write the catalog definitions to the same file they were read from. + * Return true when the file was written correctly, false when there was a problem. + */ + bool write_to_disk() const; + /** + * Write the catalog definitions to an arbitrary file path. + * + * Any existing file is backed up to "filename~". Any previously existing backup is overwritten. + * + * Return true when the file was written correctly, false when there was a problem. + */ + bool write_to_disk(const CatalogFilePath &dest_file_path) const; + + bool contains(CatalogID catalog_id) const; + /* Add a new catalog. Undefined behavior if a catalog with the same ID was already added. */ + void add_new(AssetCatalog *catalog); + + using AssetCatalogParsedFn = FunctionRef<bool(std::unique_ptr<AssetCatalog>)>; + void parse_catalog_file(const CatalogFilePath &catalog_definition_file_path, + AssetCatalogParsedFn callback); + + protected: + /* Catalogs stored in this file. They are mapped by ID to make it possible to query whether a + * catalog is already known, without having to find the corresponding `AssetCatalog*`. */ + Map<CatalogID, AssetCatalog *> catalogs_; + + bool parse_version_line(StringRef line); + std::unique_ptr<AssetCatalog> parse_catalog_line(StringRef line); + + /** + * Write the catalog definitions to the given file path. + * Return true when the file was written correctly, false when there was a problem. + */ + bool write_to_disk_unsafe(const CatalogFilePath &dest_file_path) const; + bool ensure_directory_exists(const CatalogFilePath directory_path) const; +}; + +/** Asset Catalog definition, containing a symbolic ID and a path that points to a node in the + * catalog hierarchy. */ +class AssetCatalog { + public: + AssetCatalog() = default; + AssetCatalog(CatalogID catalog_id, const AssetCatalogPath &path, const std::string &simple_name); + + CatalogID catalog_id; + AssetCatalogPath path; + /** + * Simple, human-readable name for the asset catalog. This is stored on assets alongside the + * catalog ID; the catalog ID is a UUID that is not human-readable, + * so to avoid complete data-loss when the catalog definition file gets lost, + * we also store a human-readable simple name for the catalog. */ + std::string simple_name; + + struct Flags { + /* Treat this catalog as deleted. Keeping deleted catalogs around is necessary to support + * merging of on-disk changes with in-memory changes. */ + bool is_deleted = false; + } flags; + + /** + * Create a new Catalog with the given path, auto-generating a sensible catalog simple-name. + * + * NOTE: the given path will be cleaned up (trailing spaces removed, etc.), so the returned + * `AssetCatalog`'s path differ from the given one. + */ + static std::unique_ptr<AssetCatalog> from_path(const AssetCatalogPath &path); + + protected: + /** Generate a sensible catalog ID for the given path. */ + static std::string sensible_simple_name_for_path(const AssetCatalogPath &path); +}; + +/** Comparator for asset catalogs, ordering by (path, UUID). */ +struct AssetCatalogPathCmp { + bool operator()(const AssetCatalog *lhs, const AssetCatalog *rhs) const + { + if (lhs->path == rhs->path) { + return lhs->catalog_id < rhs->catalog_id; + } + return lhs->path < rhs->path; + } +}; + +/** + * Set that stores catalogs ordered by (path, UUID). + * Being a set, duplicates are removed. The catalog's simple name is ignored in this. */ +using AssetCatalogOrderedSet = std::set<const AssetCatalog *, AssetCatalogPathCmp>; + +/** + * Filter that can determine whether an asset should be visible or not, based on its catalog ID. + * + * \see AssetCatalogService::create_filter() + */ +class AssetCatalogFilter { + public: + bool contains(CatalogID asset_catalog_id) const; + + protected: + friend AssetCatalogService; + const Set<CatalogID> matching_catalog_ids; + + explicit AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids); +}; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_asset_catalog_path.hh b/source/blender/blenkernel/BKE_asset_catalog_path.hh new file mode 100644 index 00000000000..328625b1bfa --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_catalog_path.hh @@ -0,0 +1,143 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#pragma once + +#ifndef __cplusplus +# error This is a C++ header. +#endif + +#include "BLI_function_ref.hh" +#include "BLI_string_ref.hh" +#include "BLI_sys_types.h" + +#include <string> + +namespace blender::bke { + +/** + * Location of an Asset Catalog in the catalog tree, denoted by slash-separated path components. + * + * Each path component is a string that is not allowed to have slashes or colons. The latter is to + * make things easy to save in the colon-delimited Catalog Definition File format. + * + * The path of a catalog determines where in the catalog hierarchy the catalog is shown. Examples + * are "Characters/Ellie/Poses/Hand" or "Kit_bash/City/Skyscrapers". The path looks like a + * file-system path, with a few differences: + * + * - Only slashes are used as path component separators. + * - All paths are absolute, so there is no need for a leading slash. + * + * See https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Catalogs + * + * Paths are stored as byte sequences, and assumed to be UTF-8. + */ +class AssetCatalogPath { + friend std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append); + + private: + /** + * The path itself, such as "Agents/Secret/327". + */ + std::string path_; + + public: + static const char SEPARATOR; + + AssetCatalogPath() = delete; + AssetCatalogPath(StringRef path); + AssetCatalogPath(const std::string &path); + AssetCatalogPath(const char *path); + AssetCatalogPath(const AssetCatalogPath &other_path) = default; + AssetCatalogPath(AssetCatalogPath &&other_path) noexcept; + ~AssetCatalogPath() = default; + + uint64_t hash() const; + uint64_t length() const; /* Length of the path in bytes. */ + + /** C-string representation of the path. */ + const char *c_str() const; + const std::string &str() const; + + /* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor. + * Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */ + bool operator==(const AssetCatalogPath &other_path) const; + bool operator!=(const AssetCatalogPath &other_path) const; + bool operator<(const AssetCatalogPath &other_path) const; + AssetCatalogPath &operator=(const AssetCatalogPath &other_path) = default; + AssetCatalogPath &operator=(AssetCatalogPath &&other_path) = default; + + /** Concatenate two paths, returning the new path. */ + AssetCatalogPath operator/(const AssetCatalogPath &path_to_append) const; + + /* False when the path is empty, true otherwise. */ + operator bool() const; + + /** + * Clean up the path. This ensures: + * - Every path component is stripped of its leading/trailing spaces. + * - Empty components (caused by double slashes or leading/trailing slashes) are removed. + * - Invalid characters are replaced with valid ones. + */ + [[nodiscard]] AssetCatalogPath cleanup() const; + + /** + * \return true only if the given path is a parent of this catalog's path. + * When this catalog's path is equal to the given path, return true as well. + * In other words, this defines a weak subset. + * + * True: "some/path/there" is contained in "some/path" and "some". + * False: "path/there" is not contained in "some/path/there". + * + * Note that non-cleaned-up paths (so for example starting or ending with a + * slash) are not supported, and result in undefined behavior. + */ + bool is_contained_in(const AssetCatalogPath &other_path) const; + + /** + * \return the parent path, or an empty path if there is no parent. + */ + AssetCatalogPath parent() const; + + /** + * Change the initial part of the path from `from_path` to `to_path`. + * If this path does not start with `from_path`, return an empty path as result. + * + * Example: + * + * AssetCatalogPath path("some/path/to/some/catalog"); + * path.rebase("some/path", "new/base") -> "new/base/to/some/catalog" + */ + AssetCatalogPath rebase(const AssetCatalogPath &from_path, + const AssetCatalogPath &to_path) const; + + /** Call the callback function for each path component, in left-to-right order. */ + using ComponentIteratorFn = FunctionRef<void(StringRef component_name, bool is_last_component)>; + void iterate_components(ComponentIteratorFn callback) const; + + protected: + /** Strip leading/trailing spaces and replace disallowed characters. */ + static std::string cleanup_component(StringRef component_name); +}; + +/** Output the path as string. */ +std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append); + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_asset_library.h b/source/blender/blenkernel/BKE_asset_library.h new file mode 100644 index 00000000000..3ddfbb1415e --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_library.h @@ -0,0 +1,74 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#pragma once + +struct Main; +// + +#ifdef __cplusplus +extern "C" { +#endif + +/** Forward declaration, defined in intern/asset_library.hh */ +typedef struct AssetLibrary AssetLibrary; + +/** TODO(@sybren): properly have a think/discussion about the API for this. */ +struct AssetLibrary *BKE_asset_library_load(const char *library_path); +void BKE_asset_library_free(struct AssetLibrary *asset_library); + +/** + * Try to find an appropriate location for an asset library root from a file or directory path. + * Does not check if \a input_path exists. + * + * The design is made to find an appropriate asset library path from a .blend file path, but + * technically works with any file or directory as \a input_path. + * Design is: + * * If \a input_path lies within a known asset library path (i.e. an asset library registered in + * the Preferences), return the asset library path. + * * Otherwise, if \a input_path has a parent path, return the parent path (e.g. to use the + * directory a .blend file is in as asset library root). + * * If \a input_path is empty or doesn't have a parent path (e.g. because a .blend wasn't saved + * yet), there is no suitable path. The caller has to decide how to handle this case. + * + * \param r_library_path: The returned asset library path with a trailing slash, or an empty string + * if no suitable path is found. Assumed to be a buffer of at least + * #FILE_MAXDIR bytes. + * + * \return True if the function could find a valid, that is, a non-empty path to return in \a + * r_library_path. + */ +bool BKE_asset_library_find_suitable_root_path_from_path( + const char *input_path, char r_library_path[768 /* FILE_MAXDIR */]); +/** + * Uses the current location on disk of the file represented by \a bmain as input to + * #BKE_asset_library_find_suitable_root_path_from_path(). Refer to it for a design + * description. + * + * \return True if the function could find a valid, that is, a non-empty path to return in \a + * r_library_path. If \a bmain wasn't saved into a file yet, the return value will be + * false. + */ +bool BKE_asset_library_find_suitable_root_path_from_main( + const struct Main *bmain, char r_library_path[768 /* FILE_MAXDIR */]); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh new file mode 100644 index 00000000000..1dc02f7aa9b --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -0,0 +1,54 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#pragma once + +#ifndef __cplusplus +# error This is a C++-only header file. Use BKE_asset_library.h instead. +#endif + +#include "BKE_asset_library.h" + +#include "BKE_asset_catalog.hh" +#include "BKE_callbacks.h" + +#include <memory> + +namespace blender::bke { + +struct AssetLibrary { + std::unique_ptr<AssetCatalogService> catalog_service; + + void load(StringRefNull library_root_directory); + + void on_save_handler_register(); + void on_save_handler_unregister(); + + void on_save_post(struct Main *, struct PointerRNA **pointers, const int num_pointers); + + private: + bCallbackFuncStore on_save_callback_store_; +}; + +} // namespace blender::bke + +blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( + const ::AssetLibrary *library); +blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library); diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index cf54e7efa0d..da3de2f08bd 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -101,6 +101,8 @@ class AttributeIDRef { BLI_assert(this->is_anonymous()); return *anonymous_id_; } + + friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id); }; } // namespace blender::bke @@ -120,6 +122,11 @@ struct AttributeMetaData { } }; +struct AttributeKind { + AttributeDomain domain; + CustomDataType data_type; +}; + /** * Base class for the attribute initializer types described below. */ diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 1f25106404a..31ce1b124e9 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,13 +39,13 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 25 +#define BLENDER_FILE_SUBVERSION 32 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file * was written with too new a version. */ #define BLENDER_FILE_MIN_VERSION 300 -#define BLENDER_FILE_MIN_SUBVERSION 11 +#define BLENDER_FILE_MIN_SUBVERSION 26 /** User readable version string. */ const char *BKE_blender_version_string(void); diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h index ef2a0ed34a0..7c518f33c89 100644 --- a/source/blender/blenkernel/BKE_callbacks.h +++ b/source/blender/blenkernel/BKE_callbacks.h @@ -130,6 +130,7 @@ void BKE_callback_exec_id_depsgraph(struct Main *bmain, struct Depsgraph *depsgraph, eCbEvent evt); void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt); +void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt); void BKE_callback_global_init(void); void BKE_callback_global_finalize(void); diff --git a/source/blender/blenkernel/BKE_colortools.h b/source/blender/blenkernel/BKE_colortools.h index ec2262d4f60..109947cece4 100644 --- a/source/blender/blenkernel/BKE_colortools.h +++ b/source/blender/blenkernel/BKE_colortools.h @@ -97,6 +97,7 @@ void BKE_curvemapping_evaluate_premulRGBF(const struct CurveMapping *cumap, float vecout[3], const float vecin[3]); bool BKE_curvemapping_RGBA_does_something(const struct CurveMapping *cumap); +void BKE_curvemapping_table_F(const struct CurveMapping *cumap, float **array, int *size); void BKE_curvemapping_table_RGBA(const struct CurveMapping *cumap, float **array, int *size); /* non-const, these modify the curve */ diff --git a/source/blender/blenkernel/BKE_displist.h b/source/blender/blenkernel/BKE_displist.h index db5663fcc94..8fb596a8096 100644 --- a/source/blender/blenkernel/BKE_displist.h +++ b/source/blender/blenkernel/BKE_displist.h @@ -87,11 +87,6 @@ void BKE_displist_make_curveTypes(struct Depsgraph *depsgraph, const struct Scene *scene, struct Object *ob, const bool for_render); -void BKE_displist_make_curveTypes_forRender(struct Depsgraph *depsgraph, - const struct Scene *scene, - struct Object *ob, - struct ListBase *dispbase, - struct Mesh **r_final); void BKE_displist_make_mball(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); void BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, diff --git a/source/blender/blenkernel/BKE_font.h b/source/blender/blenkernel/BKE_font.h index 522d3843bb2..827ae1b6a0f 100644 --- a/source/blender/blenkernel/BKE_font.h +++ b/source/blender/blenkernel/BKE_font.h @@ -85,6 +85,15 @@ bool BKE_vfont_to_curve_ex(struct Object *ob, struct CharTrans **r_chartransdata); bool BKE_vfont_to_curve_nubase(struct Object *ob, int mode, struct ListBase *r_nubase); bool BKE_vfont_to_curve(struct Object *ob, int mode); +void BKE_vfont_build_char(struct Curve *cu, + struct ListBase *nubase, + unsigned int character, + struct CharInfo *info, + float ofsx, + float ofsy, + float rot, + int charidx, + const float fsize); int BKE_vfont_select_get(struct Object *ob, int *r_start, int *r_end); void BKE_vfont_select_clamp(struct Object *ob); diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 317d513dae6..f182fb527e1 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -25,6 +25,7 @@ #include "BLI_float3.hh" #include "BLI_float4x4.hh" +#include "BLI_function_ref.hh" #include "BLI_hash.hh" #include "BLI_map.hh" #include "BLI_set.hh" @@ -280,6 +281,8 @@ struct GeometrySet { return this->remove(Component::static_type); } + void keep_only(const blender::Span<GeometryComponentType> component_types); + void add(const GeometryComponent &component); blender::Vector<const GeometryComponent *> get_components_for_read() const; @@ -293,6 +296,25 @@ struct GeometrySet { bool owns_direct_data() const; void ensure_owns_direct_data(); + using AttributeForeachCallback = + blender::FunctionRef<void(const blender::bke::AttributeIDRef &attribute_id, + const AttributeMetaData &meta_data, + const GeometryComponent &component)>; + + void attribute_foreach(blender::Span<GeometryComponentType> component_types, + bool include_instances, + AttributeForeachCallback callback) const; + + void gather_attributes_for_propagation( + blender::Span<GeometryComponentType> component_types, + GeometryComponentType dst_component_type, + bool include_instances, + blender::Map<blender::bke::AttributeIDRef, AttributeKind> &r_attributes) const; + + using ForeachSubGeometryCallback = blender::FunctionRef<void(GeometrySet &geometry_set)>; + + void modify_geometry_sets(ForeachSubGeometryCallback callback); + /* Utility methods for creation. */ static GeometrySet create_with_mesh( Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); @@ -307,6 +329,8 @@ struct GeometrySet { bool has_instances() const; bool has_volume() const; bool has_curve() const; + bool has_realized_data() const; + bool is_empty() const; const Mesh *get_mesh_for_read() const; const PointCloud *get_pointcloud_for_read() const; @@ -481,11 +505,38 @@ class InstanceReference { { } - InstanceReference(const InstanceReference &src) : type_(src.type_), data_(src.data_) + InstanceReference(const InstanceReference &other) : type_(other.type_), data_(other.data_) + { + if (other.geometry_set_) { + geometry_set_ = std::make_unique<GeometrySet>(*other.geometry_set_); + } + } + + InstanceReference(InstanceReference &&other) + : type_(other.type_), data_(other.data_), geometry_set_(std::move(other.geometry_set_)) + { + other.type_ = Type::None; + other.data_ = nullptr; + } + + InstanceReference &operator=(const InstanceReference &other) + { + if (this == &other) { + return *this; + } + this->~InstanceReference(); + new (this) InstanceReference(other); + return *this; + } + + InstanceReference &operator=(InstanceReference &&other) { - if (src.type_ == Type::GeometrySet) { - geometry_set_ = std::make_unique<GeometrySet>(*src.geometry_set_); + if (this == &other) { + return *this; } + this->~InstanceReference(); + new (this) InstanceReference(std::move(other)); + return *this; } Type type() const @@ -579,6 +630,7 @@ class InstancesComponent : public GeometryComponent { void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); blender::Span<InstanceReference> references() const; + void remove_unused_references(); void ensure_geometry_instances(); GeometrySet &geometry_set_from_reference(const int reference_index); @@ -597,6 +649,9 @@ class InstancesComponent : public GeometryComponent { int attribute_domain_size(const AttributeDomain domain) const final; + void foreach_referenced_geometry( + blender::FunctionRef<void(const GeometrySet &geometry_set)> callback) const; + bool is_empty() const final; bool owns_direct_data() const override; @@ -667,6 +722,13 @@ class AttributeFieldInput : public fn::FieldInput { { } + template<typename T> static fn::Field<T> Create(std::string name) + { + const CPPType &type = CPPType::get<T>(); + auto field_input = std::make_shared<AttributeFieldInput>(std::move(name), type); + return fn::Field<T>{field_input}; + } + StringRefNull attribute_name() const { return name_; @@ -696,6 +758,14 @@ class AnonymousAttributeFieldInput : public fn::FieldInput { { } + template<typename T> static fn::Field<T> Create(StrongAnonymousAttributeID anonymous_id) + { + const CPPType &type = CPPType::get<T>(); + auto field_input = std::make_shared<AnonymousAttributeFieldInput>(std::move(anonymous_id), + type); + return fn::Field<T>{field_input}; + } + const GVArray *get_varray_for_context(const fn::FieldContext &context, IndexMask mask, ResourceScope &scope) const override; diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh index 44a0ee30c4c..653450c7d8e 100644 --- a/source/blender/blenkernel/BKE_geometry_set_instances.hh +++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh @@ -39,21 +39,12 @@ struct GeometryInstanceGroup { Vector<float4x4> transforms; }; -void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit); - void geometry_set_gather_instances(const GeometrySet &geometry_set, Vector<GeometryInstanceGroup> &r_instance_groups); GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set); GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set); -struct AttributeKind { - CustomDataType data_type; - AttributeDomain domain; -}; - /** * Add information about all the attributes on every component of the type. The resulting info * will contain the highest complexity data type and the highest priority domain among every diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index 36f57209e33..d2a8ec2e332 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -133,6 +133,9 @@ enum { LIB_ID_COPY_SHAPEKEY = 1 << 26, /** EXCEPTION! Specific deep-copy of node trees used e.g. for rendering purposes. */ LIB_ID_COPY_NODETREE_LOCALIZE = 1 << 27, + /** EXCEPTION! Specific handling of RB objects regarding collections differs depending whether we + duplicate scene/collections, or objects. */ + LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING = 1 << 28, /* *** Helper 'defines' gathering most common flag sets. *** */ /** Shapekeys are not real ID's, more like local data to geometry IDs... */ @@ -261,7 +264,8 @@ struct ID *BKE_id_copy_ex(struct Main *bmain, const int flag); struct ID *BKE_id_copy_for_duplicate(struct Main *bmain, struct ID *id, - const uint duplicate_flags); + const uint duplicate_flags, + const int copy_flags); void BKE_lib_id_swap(struct Main *bmain, struct ID *id_a, struct ID *id_b); void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b); diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h index c6658ff424a..b94a1b33606 100644 --- a/source/blender/blenkernel/BKE_lib_override.h +++ b/source/blender/blenkernel/BKE_lib_override.h @@ -84,6 +84,8 @@ bool BKE_lib_override_library_proxy_convert(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct Object *ob_proxy); +void BKE_lib_override_library_main_proxy_convert(struct Main *bmain, + struct BlendFileReadReport *reports); bool BKE_lib_override_library_resync(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, @@ -171,6 +173,8 @@ void BKE_lib_override_library_main_unused_cleanup(struct Main *bmain); void BKE_lib_override_library_update(struct Main *bmain, struct ID *local); void BKE_lib_override_library_main_update(struct Main *bmain); +bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID *id); + /* Storage (.blend file writing) part. */ /* For now, we just use a temp main list. */ diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h index 69e2d52e1dd..b1eaf7207fa 100644 --- a/source/blender/blenkernel/BKE_material.h +++ b/source/blender/blenkernel/BKE_material.h @@ -90,7 +90,7 @@ void BKE_object_material_array_assign(struct Main *bmain, short BKE_object_material_slot_find_index(struct Object *ob, struct Material *ma); bool BKE_object_material_slot_add(struct Main *bmain, struct Object *ob); bool BKE_object_material_slot_remove(struct Main *bmain, struct Object *ob); -bool BKE_object_material_slot_used(struct ID *id, short actcol); +bool BKE_object_material_slot_used(struct Object *object, short actcol); struct Material *BKE_gpencil_material(struct Object *ob, short act); struct MaterialGPencilStyle *BKE_gpencil_material_settings(struct Object *ob, short act); diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index b0a8fee1178..f44d35c62a3 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -651,9 +651,8 @@ extern void (*BKE_mesh_batch_cache_free_cb)(struct Mesh *me); /* Inlines */ -/* Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h, - * but I don't want to force every user of BKE_mesh.h to also include that file. - * ~~ Sybren */ +/* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h, + * but I don't want to force every user of BKE_mesh.h to also include that file. */ BLI_INLINE int BKE_mesh_origindex_mface_mpoly(const int *index_mf_to_mpoly, const int *index_mp_to_orig, const int i) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 21ca65baf00..a81a620ab12 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -417,7 +417,7 @@ typedef struct bNodeTreeType { void (*local_sync)(struct bNodeTree *localtree, struct bNodeTree *ntree); void (*local_merge)(struct Main *bmain, struct bNodeTree *localtree, struct bNodeTree *ntree); - /* Tree update. Overrides nodetype->updatetreefunc! */ + /* Tree update. Overrides `nodetype->updatetreefunc` ! */ void (*update)(struct bNodeTree *ntree); bool (*validate_link)(struct bNodeTree *ntree, struct bNodeLink *link); @@ -443,7 +443,7 @@ void ntreeTypeFreeLink(const struct bNodeTreeType *nt); bool ntreeIsRegistered(struct bNodeTree *ntree); struct GHashIterator *ntreeTypeGetIterator(void); -/* helper macros for iterating over tree types */ +/* Helper macros for iterating over tree types. */ #define NODE_TREE_TYPES_BEGIN(ntype) \ { \ GHashIterator *__node_tree_type_iter__ = ntreeTypeGetIterator(); \ @@ -480,7 +480,7 @@ bool ntreeHasType(const struct bNodeTree *ntree, int type); bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup); void ntreeUpdateTree(struct Main *main, struct bNodeTree *ntree); void ntreeUpdateAllNew(struct Main *main); -void ntreeUpdateAllUsers(struct Main *main, struct ID *id); +void ntreeUpdateAllUsers(struct Main *main, struct ID *id, int tree_update_flag); void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, @@ -548,7 +548,7 @@ void nodeUnregisterType(struct bNodeType *ntype); bool nodeTypeUndefined(struct bNode *node); struct GHashIterator *nodeTypeGetIterator(void); -/* helper macros for iterating over node types */ +/* Helper macros for iterating over node types. */ #define NODE_TYPES_BEGIN(ntype) \ { \ GHashIterator *__node_type_iter__ = nodeTypeGetIterator(); \ @@ -574,7 +574,7 @@ const char *nodeStaticSocketType(int type, int subtype); const char *nodeStaticSocketInterfaceType(int type, int subtype); const char *nodeStaticSocketLabel(int type, int subtype); -/* helper macros for iterating over node types */ +/* Helper macros for iterating over node types. */ #define NODE_SOCKET_TYPES_BEGIN(stype) \ { \ GHashIterator *__node_socket_type_iter__ = nodeSocketTypeGetIterator(); \ @@ -746,7 +746,8 @@ int BKE_node_clipboard_get_type(void); /* Node Instance Hash */ typedef struct bNodeInstanceHash { - GHash *ghash; /* XXX should be made a direct member, GHash allocation needs to support it */ + /** XXX should be made a direct member, #GHash allocation needs to support it */ + GHash *ghash; } bNodeInstanceHash; typedef void (*bNodeInstanceValueFP)(void *value); @@ -1102,6 +1103,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, #define SH_NODE_VERTEX_COLOR 706 #define SH_NODE_OUTPUT_AOV 707 #define SH_NODE_VECTOR_ROTATE 708 +#define SH_NODE_CURVE_FLOAT 709 /* custom defines options for Material node */ // #define SH_NODE_MAT_DIFF 1 @@ -1346,7 +1348,7 @@ void ntreeCompositCryptomatteLayerPrefix(const Scene *scene, const bNode *node, char *r_prefix, size_t prefix_len); -/* Update the runtime layer names with the cryptomatte layer names of the references +/* Update the runtime layer names with the crypto-matte layer names of the references * render layer or image. */ void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node); struct CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node); @@ -1411,12 +1413,12 @@ int ntreeTexExecTree(struct bNodeTree *ntree, * \{ */ #define GEO_NODE_TRIANGULATE 1000 -#define GEO_NODE_EDGE_SPLIT 1001 +#define GEO_NODE_LEGACY_EDGE_SPLIT 1001 #define GEO_NODE_TRANSFORM 1002 #define GEO_NODE_BOOLEAN 1003 #define GEO_NODE_LEGACY_POINT_DISTRIBUTE 1004 #define GEO_NODE_LEGACY_POINT_INSTANCE 1005 -#define GEO_NODE_SUBDIVISION_SURFACE 1006 +#define GEO_NODE_LEGACY_SUBDIVISION_SURFACE 1006 #define GEO_NODE_OBJECT_INFO 1007 #define GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE 1008 #define GEO_NODE_LEGACY_ATTRIBUTE_MATH 1009 @@ -1451,14 +1453,14 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_MESH_PRIMITIVE_LINE 1038 #define GEO_NODE_MESH_PRIMITIVE_GRID 1039 #define GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE 1040 -#define GEO_NODE_LECAGY_ATTRIBUTE_CLAMP 1041 +#define GEO_NODE_LEGACY_ATTRIBUTE_CLAMP 1041 #define GEO_NODE_BOUNDING_BOX 1042 #define GEO_NODE_SWITCH 1043 #define GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER 1044 #define GEO_NODE_CURVE_TO_MESH 1045 #define GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP 1046 #define GEO_NODE_CURVE_RESAMPLE 1047 -#define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048 +#define GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE 1048 #define GEO_NODE_LEGACY_MATERIAL_ASSIGN 1049 #define GEO_NODE_INPUT_MATERIAL 1050 #define GEO_NODE_MATERIAL_REPLACE 1051 @@ -1467,7 +1469,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_LENGTH 1054 #define GEO_NODE_LEGACY_SELECT_BY_MATERIAL 1055 #define GEO_NODE_CONVEX_HULL 1056 -#define GEO_NODE_CURVE_TO_POINTS 1057 +#define GEO_NODE_LEGACY_CURVE_TO_POINTS 1057 #define GEO_NODE_LEGACY_CURVE_REVERSE 1058 #define GEO_NODE_SEPARATE_COMPONENTS 1059 #define GEO_NODE_LEGACY_CURVE_SUBDIVIDE 1060 @@ -1479,7 +1481,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_PRIMITIVE_CIRCLE 1066 #define GEO_NODE_VIEWER 1067 #define GEO_NODE_CURVE_PRIMITIVE_LINE 1068 -#define GEO_NODE_CURVE_ENDPOINTS 1069 +#define GEO_NODE_LEGACY_CURVE_ENDPOINTS 1069 #define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070 #define GEO_NODE_CURVE_TRIM 1071 #define GEO_NODE_LEGACY_CURVE_SET_HANDLES 1072 @@ -1500,7 +1502,17 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_STRING_JOIN 1087 #define GEO_NODE_CURVE_PARAMETER 1088 #define GEO_NODE_CURVE_FILLET 1089 - +#define GEO_NODE_DISTRIBUTE_POINTS_ON_FACES 1090 +#define GEO_NODE_STRING_TO_CURVES 1091 +#define GEO_NODE_INSTANCE_ON_POINTS 1092 +#define GEO_NODE_MESH_TO_POINTS 1093 +#define GEO_NODE_POINTS_TO_VERTICES 1094 +#define GEO_NODE_CURVE_REVERSE 1095 +#define GEO_NODE_PROXIMITY 1096 +#define GEO_NODE_CURVE_SUBDIVIDE 1097 +#define GEO_NODE_INPUT_SPLINE_LENGTH 1098 +#define GEO_NODE_CURVE_SPLINE_TYPE 1099 +#define GEO_NODE_CURVE_SET_HANDLES 1100 /** \} */ /* -------------------------------------------------------------------- */ @@ -1509,13 +1521,16 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define FN_NODE_BOOLEAN_MATH 1200 #define FN_NODE_FLOAT_COMPARE 1202 -#define FN_NODE_RANDOM_FLOAT 1206 +#define FN_NODE_LEGACY_RANDOM_FLOAT 1206 #define FN_NODE_INPUT_VECTOR 1207 #define FN_NODE_INPUT_STRING 1208 #define FN_NODE_FLOAT_TO_INT 1209 #define FN_NODE_VALUE_TO_STRING 1210 #define FN_NODE_STRING_LENGTH 1211 #define FN_NODE_STRING_SUBSTRING 1212 +#define FN_NODE_INPUT_SPECIAL_CHARACTERS 1213 +#define FN_NODE_RANDOM_VALUE 1214 +#define FN_NODE_ROTATE_EULER 1215 /** \} */ diff --git a/source/blender/blenkernel/BKE_preferences.h b/source/blender/blenkernel/BKE_preferences.h index 04a41d425bb..bd887c1ea0d 100644 --- a/source/blender/blenkernel/BKE_preferences.h +++ b/source/blender/blenkernel/BKE_preferences.h @@ -29,22 +29,32 @@ extern "C" { struct UserDef; struct bUserAssetLibrary; -void BKE_preferences_asset_library_free(struct bUserAssetLibrary *library) ATTR_NONNULL(); - struct bUserAssetLibrary *BKE_preferences_asset_library_add(struct UserDef *userdef, const char *name, const char *path) ATTR_NONNULL(1); +void BKE_preferences_asset_library_remove(struct UserDef *userdef, + struct bUserAssetLibrary *library) ATTR_NONNULL(); + void BKE_preferences_asset_library_name_set(struct UserDef *userdef, struct bUserAssetLibrary *library, const char *name) ATTR_NONNULL(); -void BKE_preferences_asset_library_remove(struct UserDef *userdef, - struct bUserAssetLibrary *library) ATTR_NONNULL(); - struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_index( const struct UserDef *userdef, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_name( const struct UserDef *userdef, const char *name) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; + +/** + * Return the bUserAssetLibrary that contains the given file/directory path. The given path can be + * the library's top-level directory, or any path inside that directory. + * + * When more than one asset libraries match, the first matching one is returned (no smartness when + * there nested asset libraries). + * + * Return NULL when no such asset library is found. */ +struct bUserAssetLibrary *BKE_preferences_asset_library_containing_path( + const struct UserDef *userdef, const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; + int BKE_preferences_asset_library_get_index(const struct UserDef *userdef, const struct bUserAssetLibrary *library) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index 541ff19c1cd..97e0d8415a5 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -316,6 +316,9 @@ class BezierSpline final : public Spline { void translate(const blender::float3 &translation) override; void transform(const blender::float4x4 &matrix) override; + void set_handle_position_right(const int index, const blender::float3 &value); + void set_handle_position_left(const int index, const blender::float3 &value); + bool point_is_sharp(const int index) const; void mark_cache_invalid() final; diff --git a/source/blender/blenkernel/BKE_subdiv.h b/source/blender/blenkernel/BKE_subdiv.h index 3d47c8d3bc5..2fb27fad30d 100644 --- a/source/blender/blenkernel/BKE_subdiv.h +++ b/source/blender/blenkernel/BKE_subdiv.h @@ -56,7 +56,7 @@ typedef enum eSubdivFVarLinearInterpolation { } eSubdivFVarLinearInterpolation; typedef struct SubdivSettings { - /* Simple subdivision corresponds to "Simple" option in the interface. When its enabled the + /* Simple subdivision corresponds to "Simple" option in the interface. When it's enabled the * subdivided mesh is not "smoothed": new vertices are added uniformly on the existing surface. * * On an OpenSubdiv implementation level this translates to a subdivision scheme: diff --git a/source/blender/blenkernel/BKE_subdiv_foreach.h b/source/blender/blenkernel/BKE_subdiv_foreach.h index a351b9a3d11..3f74299455d 100644 --- a/source/blender/blenkernel/BKE_subdiv_foreach.h +++ b/source/blender/blenkernel/BKE_subdiv_foreach.h @@ -127,7 +127,7 @@ typedef struct SubdivForeachContext { SubdivForeachVertexFromEdgeCb vertex_edge; /* Called exactly once, always corresponds to a single ptex face. */ SubdivForeachVertexInnerCb vertex_inner; - /* Called once for each loose vertex. One loose coarse vertexcorresponds + /* Called once for each loose vertex. One loose coarse vertex corresponds * to a single subdivision vertex. */ SubdivForeachLooseCb vertex_loose; @@ -144,7 +144,7 @@ typedef struct SubdivForeachContext { SubdivForeachPolygonCb poly; /* User-defined pointer, to allow callbacks know something about context the - * traversal is happening for, + * traversal is happening for. */ void *user_data; @@ -163,7 +163,7 @@ typedef struct SubdivForeachContext { * indices (for vertices, edges, loops, polygons) in the same way as subdivision * modifier will do for a dense mesh. * - * Returns truth if the whole topology was traversed, without any early exits. + * Returns true if the whole topology was traversed, without any early exits. * * TODO(sergey): Need to either get rid of subdiv or of coarse_mesh. * The main point here is to be able to get base level topology, which can be diff --git a/source/blender/blenkernel/BKE_volume.h b/source/blender/blenkernel/BKE_volume.h index d9333996632..5fe0d54c2cf 100644 --- a/source/blender/blenkernel/BKE_volume.h +++ b/source/blender/blenkernel/BKE_volume.h @@ -18,7 +18,7 @@ /** \file * \ingroup bke - * \brief Volume datablock. + * \brief Volume data-block. */ #ifdef __cplusplus extern "C" { @@ -37,7 +37,7 @@ struct VolumeGridVector; void BKE_volumes_init(void); -/* Datablock Management */ +/* Data-block Management */ void BKE_volume_init_grids(struct Volume *volume); void *BKE_volume_add(struct Main *bmain, const char *name); @@ -122,13 +122,13 @@ void BKE_volume_grid_transform_matrix(const struct VolumeGrid *grid, float mat[4 /* Volume Editing * - * These are intended for modifiers to use on evaluated datablocks. + * These are intended for modifiers to use on evaluated data-blocks. * - * new_for_eval creates a volume datablock with no grids or file path, but + * new_for_eval creates a volume data-block with no grids or file path, but * preserves other settings such as viewport display options. * - * copy_for_eval creates a volume datablock preserving everything except the - * file path. Grids are shared with the source datablock, not copied. */ + * copy_for_eval creates a volume data-block preserving everything except the + * file path. Grids are shared with the source data-block, not copied. */ struct Volume *BKE_volume_new_for_eval(const struct Volume *volume_src); struct Volume *BKE_volume_copy_for_eval(struct Volume *volume_src, bool reference); diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index de7864ef36a..fb7fdd1ac21 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -84,6 +84,9 @@ set(SRC intern/armature_selection.cc intern/armature_update.c intern/asset.cc + intern/asset_catalog.cc + intern/asset_catalog_path.cc + intern/asset_library.cc intern/attribute.c intern/attribute_access.cc intern/attribute_math.cc @@ -303,6 +306,10 @@ set(SRC BKE_armature.h BKE_armature.hh BKE_asset.h + BKE_asset_catalog.hh + BKE_asset_catalog_path.hh + BKE_asset_library.h + BKE_asset_library.hh BKE_attribute.h BKE_attribute_access.hh BKE_attribute_math.hh @@ -783,6 +790,10 @@ if(WITH_GTESTS) set(TEST_SRC intern/action_test.cc intern/armature_test.cc + intern/asset_catalog_test.cc + intern/asset_catalog_path_test.cc + intern/asset_library_test.cc + intern/asset_test.cc intern/cryptomatte_test.cc intern/fcurve_test.cc intern/lattice_deform_test.cc diff --git a/source/blender/blenkernel/intern/asset.cc b/source/blender/blenkernel/intern/asset.cc index f74018b20c5..ae9ded3c754 100644 --- a/source/blender/blenkernel/intern/asset.cc +++ b/source/blender/blenkernel/intern/asset.cc @@ -26,8 +26,10 @@ #include "BLI_listbase.h" #include "BLI_string.h" +#include "BLI_string_ref.hh" #include "BLI_string_utils.h" #include "BLI_utildefines.h" +#include "BLI_uuid.h" #include "BKE_asset.h" #include "BKE_icons.h" @@ -37,6 +39,8 @@ #include "MEM_guardedalloc.h" +using namespace blender; + AssetMetaData *BKE_asset_metadata_create(void) { AssetMetaData *asset_data = (AssetMetaData *)MEM_callocN(sizeof(*asset_data), __func__); @@ -115,6 +119,27 @@ void BKE_asset_library_reference_init_default(AssetLibraryReference *library_ref memcpy(library_ref, DNA_struct_default_get(AssetLibraryReference), sizeof(*library_ref)); } +void BKE_asset_metadata_catalog_id_clear(struct AssetMetaData *asset_data) +{ + asset_data->catalog_id = BLI_uuid_nil(); + asset_data->catalog_simple_name[0] = '\0'; +} + +void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data, + const ::bUUID catalog_id, + const char *catalog_simple_name) +{ + asset_data->catalog_id = catalog_id; + + constexpr size_t max_simple_name_length = sizeof(asset_data->catalog_simple_name); + + /* The substr() call is necessary to make copy() copy the first N characters (instead of refusing + * to copy and producing an empty string). */ + StringRef trimmed_id = + StringRef(catalog_simple_name).trim().substr(0, max_simple_name_length - 1); + trimmed_id.copy(asset_data->catalog_simple_name, max_simple_name_length); +} + /* Queries -------------------------------------------- */ PreviewImage *BKE_asset_metadata_preview_get_from_id(const AssetMetaData *UNUSED(asset_data), diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc new file mode 100644 index 00000000000..2c7cf28d60d --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -0,0 +1,791 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.h" +#include "BKE_preferences.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string_ref.hh" + +#include "DNA_userdef_types.h" + +/* For S_ISREG() and S_ISDIR() on Windows. */ +#ifdef WIN32 +# include "BLI_winstuff.h" +#endif + +#include <fstream> +#include <set> + +namespace blender::bke { + +const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt"; + +/* For now this is the only version of the catalog definition files that is supported. + * Later versioning code may be added to handle older files. */ +const int AssetCatalogDefinitionFile::SUPPORTED_VERSION = 1; +/* String that's matched in the catalog definition file to know that the line is the version + * declaration. It has to start with a space to ensure it won't match any hypothetical future field + * that starts with "VERSION". */ +const std::string AssetCatalogDefinitionFile::VERSION_MARKER = "VERSION "; + +const std::string AssetCatalogDefinitionFile::HEADER = + "# This is an Asset Catalog Definition file for Blender.\n" + "#\n" + "# Empty lines and lines starting with `#` will be ignored.\n" + "# The first non-ignored line should be the version indicator.\n" + "# Other lines are of the format \"UUID:catalog/path/for/assets:simple catalog name\"\n"; + +AssetCatalogService::AssetCatalogService(const CatalogFilePath &asset_library_root) + : asset_library_root_(asset_library_root) +{ +} + +bool AssetCatalogService::is_empty() const +{ + return catalogs_.is_empty(); +} + +AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) const +{ + const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id); + if (catalog_uptr_ptr == nullptr) { + return nullptr; + } + return catalog_uptr_ptr->get(); +} + +AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath &path) const +{ + for (const auto &catalog : catalogs_.values()) { + if (catalog->path == path) { + return catalog.get(); + } + } + + return nullptr; +} + +AssetCatalogFilter AssetCatalogService::create_catalog_filter( + const CatalogID active_catalog_id) const +{ + Set<CatalogID> matching_catalog_ids; + matching_catalog_ids.add(active_catalog_id); + + const AssetCatalog *active_catalog = find_catalog(active_catalog_id); + if (!active_catalog) { + /* If the UUID is unknown (i.e. not mapped to an actual Catalog), it is impossible to determine + * its children. The filter can still work on the given UUID. */ + return AssetCatalogFilter(std::move(matching_catalog_ids)); + } + + /* This cannot just iterate over tree items to get all the required data, because tree items only + * represent single UUIDs. It could be used to get the main UUIDs of the children, though, and + * then only do an exact match on the path (instead of the more complex `is_contained_in()` + * call). Without an extra indexed-by-path acceleration structure, this is still going to require + * a linear search, though. */ + for (const auto &catalog_uptr : this->catalogs_.values()) { + if (catalog_uptr->path.is_contained_in(active_catalog->path)) { + matching_catalog_ids.add(catalog_uptr->catalog_id); + } + } + + return AssetCatalogFilter(std::move(matching_catalog_ids)); +} + +void AssetCatalogService::delete_catalog(CatalogID catalog_id) +{ + std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id); + if (catalog_uptr_ptr == nullptr) { + /* Catalog cannot be found, which is fine. */ + return; + } + + /* Mark the catalog as deleted. */ + AssetCatalog *catalog = catalog_uptr_ptr->get(); + catalog->flags.is_deleted = true; + + /* Move ownership from this->catalogs_ to this->deleted_catalogs_. */ + this->deleted_catalogs_.add(catalog_id, std::move(*catalog_uptr_ptr)); + + /* The catalog can now be removed from the map without freeing the actual AssetCatalog. */ + this->catalogs_.remove(catalog_id); + + this->rebuild_tree(); +} + +void AssetCatalogService::update_catalog_path(CatalogID catalog_id, + const AssetCatalogPath &new_catalog_path) +{ + AssetCatalog *renamed_cat = this->find_catalog(catalog_id); + const AssetCatalogPath old_cat_path = renamed_cat->path; + + for (auto &catalog_uptr : catalogs_.values()) { + AssetCatalog *cat = catalog_uptr.get(); + + const AssetCatalogPath new_path = cat->path.rebase(old_cat_path, new_catalog_path); + if (!new_path) { + continue; + } + cat->path = new_path; + } + + this->rebuild_tree(); +} + +AssetCatalog *AssetCatalogService::create_catalog(const AssetCatalogPath &catalog_path) +{ + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path(catalog_path); + + /* So we can std::move(catalog) and still use the non-owning pointer: */ + AssetCatalog *const catalog_ptr = catalog.get(); + + /* TODO(@sybren): move the `AssetCatalog::from_path()` function to another place, that can reuse + * catalogs when a catalog with the given path is already known, and avoid duplicate catalog IDs. + */ + BLI_assert_msg(!catalogs_.contains(catalog->catalog_id), "duplicate catalog ID not supported"); + catalogs_.add_new(catalog->catalog_id, std::move(catalog)); + + if (catalog_definition_file_) { + /* Ensure the new catalog gets written to disk at some point. If there is no CDF in memory yet, + * it's enough to have the catalog known to the service as it'll be saved to a new file. */ + catalog_definition_file_->add_new(catalog_ptr); + } + + BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist."); + catalog_tree_->insert_item(*catalog_ptr); + + return catalog_ptr; +} + +static std::string asset_definition_default_file_path_from_dir(StringRef asset_library_root) +{ + char file_path[PATH_MAX]; + BLI_join_dirfile(file_path, + sizeof(file_path), + asset_library_root.data(), + AssetCatalogService::DEFAULT_CATALOG_FILENAME.data()); + return file_path; +} + +void AssetCatalogService::load_from_disk() +{ + load_from_disk(asset_library_root_); +} + +void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_directory_path) +{ + BLI_stat_t status; + if (BLI_stat(file_or_directory_path.data(), &status) == -1) { + // TODO(@sybren): throw an appropriate exception. + return; + } + + if (S_ISREG(status.st_mode)) { + load_single_file(file_or_directory_path); + } + else if (S_ISDIR(status.st_mode)) { + load_directory_recursive(file_or_directory_path); + } + else { + // TODO(@sybren): throw an appropriate exception. + } + + /* TODO: Should there be a sanitize step? E.g. to remove catalogs with identical paths? */ + + rebuild_tree(); +} + +void AssetCatalogService::load_directory_recursive(const CatalogFilePath &directory_path) +{ + // TODO(@sybren): implement proper multi-file support. For now, just load + // the default file if it is there. + CatalogFilePath file_path = asset_definition_default_file_path_from_dir(directory_path); + + if (!BLI_exists(file_path.data())) { + /* No file to be loaded is perfectly fine. */ + return; + } + + this->load_single_file(file_path); +} + +void AssetCatalogService::load_single_file(const CatalogFilePath &catalog_definition_file_path) +{ + /* TODO(@sybren): check that #catalog_definition_file_path is contained in #asset_library_root_, + * otherwise some assumptions may fail. */ + std::unique_ptr<AssetCatalogDefinitionFile> cdf = parse_catalog_file( + catalog_definition_file_path); + + BLI_assert_msg(!this->catalog_definition_file_, + "Only loading of a single catalog definition file is supported."); + this->catalog_definition_file_ = std::move(cdf); +} + +std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_file( + const CatalogFilePath &catalog_definition_file_path) +{ + auto cdf = std::make_unique<AssetCatalogDefinitionFile>(); + cdf->file_path = catalog_definition_file_path; + + auto catalog_parsed_callback = [this, catalog_definition_file_path]( + std::unique_ptr<AssetCatalog> catalog) { + if (this->catalogs_.contains(catalog->catalog_id)) { + // TODO(@sybren): apparently another CDF was already loaded. This is not supported yet. + std::cerr << catalog_definition_file_path << ": multiple definitions of catalog " + << catalog->catalog_id << " in multiple files, ignoring this one." << std::endl; + /* Don't store 'catalog'; unique_ptr will free its memory. */ + return false; + } + + /* The AssetCatalog pointer is now owned by the AssetCatalogService. */ + this->catalogs_.add_new(catalog->catalog_id, std::move(catalog)); + return true; + }; + + cdf->parse_catalog_file(cdf->file_path, catalog_parsed_callback); + + return cdf; +} + +void AssetCatalogService::merge_from_disk_before_writing() +{ + /* TODO(Sybren): expand to support multiple CDFs. */ + + if (!catalog_definition_file_ || catalog_definition_file_->file_path.empty() || + !BLI_is_file(catalog_definition_file_->file_path.c_str())) { + return; + } + + auto catalog_parsed_callback = [this](std::unique_ptr<AssetCatalog> catalog) { + const bUUID catalog_id = catalog->catalog_id; + + /* The following two conditions could be or'ed together. Keeping them separated helps when + * adding debug prints, breakpoints, etc. */ + if (this->catalogs_.contains(catalog_id)) { + /* This catalog was already seen, so just ignore it. */ + return false; + } + if (this->deleted_catalogs_.contains(catalog_id)) { + /* This catalog was already seen and subsequently deleted, so just ignore it. */ + return false; + } + + /* This is a new catalog, so let's keep it around. */ + this->catalogs_.add_new(catalog_id, std::move(catalog)); + return true; + }; + + catalog_definition_file_->parse_catalog_file(catalog_definition_file_->file_path, + catalog_parsed_callback); +} + +bool AssetCatalogService::write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path) +{ + /* TODO(Sybren): expand to support multiple CDFs. */ + + /* - Already loaded a CDF from disk? -> Always write to that file. */ + if (this->catalog_definition_file_) { + merge_from_disk_before_writing(); + return catalog_definition_file_->write_to_disk(); + } + + if (catalogs_.is_empty() && deleted_catalogs_.is_empty()) { + /* Avoid saving anything, when there is nothing to save. */ + return true; /* Writing nothing when there is nothing to write is still a success. */ + } + + const CatalogFilePath cdf_path_to_write = find_suitable_cdf_path_for_writing(blend_file_path); + this->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write); + merge_from_disk_before_writing(); + return catalog_definition_file_->write_to_disk(); +} + +CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing( + const CatalogFilePath &blend_file_path) +{ + BLI_assert_msg(!blend_file_path.empty(), + "A non-empty .blend file path is required to be able to determine where the " + "catalog definition file should be put"); + + /* Determine the default CDF path in the same directory of the blend file. */ + char blend_dir_path[PATH_MAX]; + BLI_split_dir_part(blend_file_path.c_str(), blend_dir_path, sizeof(blend_dir_path)); + const CatalogFilePath cdf_path_next_to_blend = asset_definition_default_file_path_from_dir( + blend_dir_path); + + if (BLI_exists(cdf_path_next_to_blend.c_str())) { + /* - The directory containing the blend file has a blender_assets.cats.txt file? + * -> Merge with & write to that file. */ + return cdf_path_next_to_blend; + } + + /* - There's no definition file next to the .blend file. + * -> Ask the asset library API for an appropriate location. */ + char suitable_root_path[PATH_MAX]; + BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(), suitable_root_path); + char asset_lib_cdf_path[PATH_MAX]; + BLI_path_join(asset_lib_cdf_path, + sizeof(asset_lib_cdf_path), + suitable_root_path, + DEFAULT_CATALOG_FILENAME.c_str(), + NULL); + + return asset_lib_cdf_path; +} + +std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_in_memory( + const CatalogFilePath &file_path) +{ + auto cdf = std::make_unique<AssetCatalogDefinitionFile>(); + cdf->file_path = file_path; + + for (auto &catalog : catalogs_.values()) { + cdf->add_new(catalog.get()); + } + + return cdf; +} + +std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree() +{ + auto tree = std::make_unique<AssetCatalogTree>(); + + /* Go through the catalogs, insert each path component into the tree where needed. */ + for (auto &catalog : catalogs_.values()) { + tree->insert_item(*catalog); + } + + return tree; +} + +void AssetCatalogService::rebuild_tree() +{ + create_missing_catalogs(); + this->catalog_tree_ = read_into_tree(); +} + +void AssetCatalogService::create_missing_catalogs() +{ + /* Construct an ordered set of paths to check, so that parents are ordered before children. */ + std::set<AssetCatalogPath> paths_to_check; + for (auto &catalog : catalogs_.values()) { + paths_to_check.insert(catalog->path); + } + + std::set<AssetCatalogPath> seen_paths; + /* The empty parent should never be created, so always be considered "seen". */ + seen_paths.insert(AssetCatalogPath("")); + + /* Find and create missing direct parents (so ignoring parents-of-parents). */ + while (!paths_to_check.empty()) { + /* Pop the first path of the queue. */ + const AssetCatalogPath path = *paths_to_check.begin(); + paths_to_check.erase(paths_to_check.begin()); + + if (seen_paths.find(path) != seen_paths.end()) { + /* This path has been seen already, so it can be ignored. */ + continue; + } + seen_paths.insert(path); + + const AssetCatalogPath parent_path = path.parent(); + if (seen_paths.find(parent_path) != seen_paths.end()) { + /* The parent exists, continue to the next path. */ + continue; + } + + /* The parent doesn't exist, so create it and queue it up for checking its parent. */ + create_catalog(parent_path); + paths_to_check.insert(parent_path); + } + + /* TODO(Sybren): bind the newly created catalogs to a CDF, if we know about it. */ +} + +/* ---------------------------------------------------------------------- */ + +AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name, + CatalogID catalog_id, + const AssetCatalogTreeItem *parent) + : name_(name), catalog_id_(catalog_id), parent_(parent) +{ +} + +CatalogID AssetCatalogTreeItem::get_catalog_id() const +{ + return catalog_id_; +} + +StringRef AssetCatalogTreeItem::get_name() const +{ + return name_; +} + +AssetCatalogPath AssetCatalogTreeItem::catalog_path() const +{ + AssetCatalogPath current_path = name_; + for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) { + current_path = AssetCatalogPath(parent->name_) / current_path; + } + return current_path; +} + +int AssetCatalogTreeItem::count_parents() const +{ + int i = 0; + for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) { + i++; + } + return i; +} + +bool AssetCatalogTreeItem::has_children() const +{ + return !children_.empty(); +} + +/* ---------------------------------------------------------------------- */ + +void AssetCatalogTree::insert_item(const AssetCatalog &catalog) +{ + const AssetCatalogTreeItem *parent = nullptr; + /* The children for the currently iterated component, where the following component should be + * added to (if not there yet). */ + AssetCatalogTreeItem::ChildMap *current_item_children = &root_items_; + + BLI_assert_msg(!ELEM(catalog.path.str()[0], '/', '\\'), + "Malformed catalog path; should not start with a separator"); + + const CatalogID nil_id{}; + + catalog.path.iterate_components([&](StringRef component_name, const bool is_last_component) { + /* Insert new tree element - if no matching one is there yet! */ + auto [key_and_item, was_inserted] = current_item_children->emplace( + component_name, + AssetCatalogTreeItem( + component_name, is_last_component ? catalog.catalog_id : nil_id, parent)); + AssetCatalogTreeItem &item = key_and_item->second; + + /* If full path of this catalog already exists as parent path of a previously read catalog, + * we can ensure this tree item's UUID is set here. */ + if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) { + item.catalog_id_ = catalog.catalog_id; + } + + /* Walk further into the path (no matter if a new item was created or not). */ + parent = &item; + current_item_children = &item.children_; + }); +} + +void AssetCatalogTree::foreach_item(AssetCatalogTreeItem::ItemIterFn callback) +{ + AssetCatalogTreeItem::foreach_item_recursive(root_items_, callback); +} + +void AssetCatalogTreeItem::foreach_item_recursive(AssetCatalogTreeItem::ChildMap &children, + const ItemIterFn callback) +{ + for (auto &[key, item] : children) { + callback(item); + foreach_item_recursive(item.children_, callback); + } +} + +void AssetCatalogTree::foreach_root_item(const ItemIterFn callback) +{ + for (auto &[key, item] : root_items_) { + callback(item); + } +} + +void AssetCatalogTreeItem::foreach_child(const ItemIterFn callback) +{ + for (auto &[key, item] : children_) { + callback(item); + } +} + +AssetCatalogTree *AssetCatalogService::get_catalog_tree() +{ + return catalog_tree_.get(); +} + +bool AssetCatalogDefinitionFile::contains(const CatalogID catalog_id) const +{ + return catalogs_.contains(catalog_id); +} + +void AssetCatalogDefinitionFile::add_new(AssetCatalog *catalog) +{ + catalogs_.add_new(catalog->catalog_id, catalog); +} + +void AssetCatalogDefinitionFile::parse_catalog_file( + const CatalogFilePath &catalog_definition_file_path, + AssetCatalogParsedFn catalog_loaded_callback) +{ + std::fstream infile(catalog_definition_file_path); + + bool seen_version_number = false; + std::string line; + while (std::getline(infile, line)) { + const StringRef trimmed_line = StringRef(line).trim(); + if (trimmed_line.is_empty() || trimmed_line[0] == '#') { + continue; + } + + if (!seen_version_number) { + /* The very first non-ignored line should be the version declaration. */ + const bool is_valid_version = this->parse_version_line(trimmed_line); + if (!is_valid_version) { + std::cerr << catalog_definition_file_path + << ": first line should be version declaration; ignoring file." << std::endl; + break; + } + seen_version_number = true; + continue; + } + + std::unique_ptr<AssetCatalog> catalog = this->parse_catalog_line(trimmed_line); + if (!catalog) { + continue; + } + + AssetCatalog *non_owning_ptr = catalog.get(); + const bool keep_catalog = catalog_loaded_callback(std::move(catalog)); + if (!keep_catalog) { + continue; + } + + if (this->contains(non_owning_ptr->catalog_id)) { + std::cerr << catalog_definition_file_path << ": multiple definitions of catalog " + << non_owning_ptr->catalog_id << " in the same file, using first occurrence." + << std::endl; + /* Don't store 'catalog'; unique_ptr will free its memory. */ + continue; + } + + /* The AssetDefinitionFile should include this catalog when writing it back to disk. */ + this->add_new(non_owning_ptr); + } +} + +bool AssetCatalogDefinitionFile::parse_version_line(const StringRef line) +{ + if (!line.startswith(VERSION_MARKER)) { + return false; + } + + const std::string version_string = line.substr(VERSION_MARKER.length()); + const int file_version = std::atoi(version_string.c_str()); + + /* No versioning, just a blunt check whether it's the right one. */ + return file_version == SUPPORTED_VERSION; +} + +std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(const StringRef line) +{ + const char delim = ':'; + const int64_t first_delim = line.find_first_of(delim); + if (first_delim == StringRef::not_found) { + std::cerr << "Invalid catalog line in " << this->file_path << ": " << line << std::endl; + return std::unique_ptr<AssetCatalog>(nullptr); + } + + /* Parse the catalog ID. */ + const std::string id_as_string = line.substr(0, first_delim).trim(); + bUUID catalog_id; + const bool uuid_parsed_ok = BLI_uuid_parse_string(&catalog_id, id_as_string.c_str()); + if (!uuid_parsed_ok) { + std::cerr << "Invalid UUID in " << this->file_path << ": " << line << std::endl; + return std::unique_ptr<AssetCatalog>(nullptr); + } + + /* Parse the path and simple name. */ + const StringRef path_and_simple_name = line.substr(first_delim + 1); + const int64_t second_delim = path_and_simple_name.find_first_of(delim); + + std::string path_in_file; + std::string simple_name; + if (second_delim == 0) { + /* Delimiter as first character means there is no path. These lines are to be ignored. */ + return std::unique_ptr<AssetCatalog>(nullptr); + } + + if (second_delim == StringRef::not_found) { + /* No delimiter means no simple name, just treat it as all "path". */ + path_in_file = path_and_simple_name; + simple_name = ""; + } + else { + path_in_file = path_and_simple_name.substr(0, second_delim); + simple_name = path_and_simple_name.substr(second_delim + 1).trim(); + } + + AssetCatalogPath catalog_path = path_in_file; + return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name); +} + +bool AssetCatalogDefinitionFile::write_to_disk() const +{ + BLI_assert_msg(!this->file_path.empty(), "Writing to CDF requires its file path to be known"); + return this->write_to_disk(this->file_path); +} + +bool AssetCatalogDefinitionFile::write_to_disk(const CatalogFilePath &dest_file_path) const +{ + const CatalogFilePath writable_path = dest_file_path + ".writing"; + const CatalogFilePath backup_path = dest_file_path + "~"; + + if (!this->write_to_disk_unsafe(writable_path)) { + /* TODO: communicate what went wrong. */ + return false; + } + if (BLI_exists(dest_file_path.c_str())) { + if (BLI_rename(dest_file_path.c_str(), backup_path.c_str())) { + /* TODO: communicate what went wrong. */ + return false; + } + } + if (BLI_rename(writable_path.c_str(), dest_file_path.c_str())) { + /* TODO: communicate what went wrong. */ + return false; + } + + return true; +} + +bool AssetCatalogDefinitionFile::write_to_disk_unsafe(const CatalogFilePath &dest_file_path) const +{ + char directory[PATH_MAX]; + BLI_split_dir_part(dest_file_path.c_str(), directory, sizeof(directory)); + if (!ensure_directory_exists(directory)) { + /* TODO(Sybren): pass errors to the UI somehow. */ + return false; + } + + std::ofstream output(dest_file_path); + + // TODO(@sybren): remember the line ending style that was originally read, then use that to write + // the file again. + + // Write the header. + output << HEADER; + output << "" << std::endl; + output << VERSION_MARKER << SUPPORTED_VERSION << std::endl; + output << "" << std::endl; + + // Write the catalogs, ordered by path (primary) and UUID (secondary). + AssetCatalogOrderedSet catalogs_by_path; + for (const AssetCatalog *catalog : catalogs_.values()) { + if (catalog->flags.is_deleted) { + continue; + } + catalogs_by_path.insert(catalog); + } + + for (const AssetCatalog *catalog : catalogs_by_path) { + output << catalog->catalog_id << ":" << catalog->path << ":" << catalog->simple_name + << std::endl; + } + output.close(); + return !output.bad(); +} + +bool AssetCatalogDefinitionFile::ensure_directory_exists( + const CatalogFilePath directory_path) const +{ + /* TODO(@sybren): design a way to get such errors presented to users (or ensure that they never + * occur). */ + if (directory_path.empty()) { + std::cerr + << "AssetCatalogService: no asset library root configured, unable to ensure it exists." + << std::endl; + return false; + } + + if (BLI_exists(directory_path.data())) { + if (!BLI_is_dir(directory_path.data())) { + std::cerr << "AssetCatalogService: " << directory_path + << " exists but is not a directory, this is not a supported situation." + << std::endl; + return false; + } + + /* Root directory exists, work is done. */ + return true; + } + + /* Ensure the root directory exists. */ + std::error_code err_code; + if (!BLI_dir_create_recursive(directory_path.data())) { + std::cerr << "AssetCatalogService: error creating directory " << directory_path << ": " + << err_code << std::endl; + return false; + } + + /* Root directory has been created, work is done. */ + return true; +} + +AssetCatalog::AssetCatalog(const CatalogID catalog_id, + const AssetCatalogPath &path, + const std::string &simple_name) + : catalog_id(catalog_id), path(path), simple_name(simple_name) +{ +} + +std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const AssetCatalogPath &path) +{ + const AssetCatalogPath clean_path = path.cleanup(); + const CatalogID cat_id = BLI_uuid_generate_random(); + const std::string simple_name = sensible_simple_name_for_path(clean_path); + auto catalog = std::make_unique<AssetCatalog>(cat_id, clean_path, simple_name); + return catalog; +} + +std::string AssetCatalog::sensible_simple_name_for_path(const AssetCatalogPath &path) +{ + std::string name = path.str(); + std::replace(name.begin(), name.end(), AssetCatalogPath::SEPARATOR, '-'); + if (name.length() < MAX_NAME - 1) { + return name; + } + + /* Trim off the start of the path, as that's the most generic part and thus contains the least + * information. */ + return "..." + name.substr(name.length() - 60); +} + +AssetCatalogFilter::AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids) + : matching_catalog_ids(std::move(matching_catalog_ids)) +{ +} + +bool AssetCatalogFilter::contains(const CatalogID asset_catalog_id) const +{ + return matching_catalog_ids.contains(asset_catalog_id); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_catalog_path.cc b/source/blender/blenkernel/intern/asset_catalog_path.cc new file mode 100644 index 00000000000..85b8969cb8c --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog_path.cc @@ -0,0 +1,228 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#include "BKE_asset_catalog_path.hh" + +#include "BLI_path_util.h" + +namespace blender::bke { + +const char AssetCatalogPath::SEPARATOR = '/'; + +AssetCatalogPath::AssetCatalogPath(const std::string &path) : path_(path) +{ +} + +AssetCatalogPath::AssetCatalogPath(StringRef path) : path_(path) +{ +} + +AssetCatalogPath::AssetCatalogPath(const char *path) : path_(path) +{ +} + +AssetCatalogPath::AssetCatalogPath(AssetCatalogPath &&other_path) noexcept + : path_(std::move(other_path.path_)) +{ +} + +uint64_t AssetCatalogPath::hash() const +{ + std::hash<std::string> hasher{}; + return hasher(this->path_); +} + +uint64_t AssetCatalogPath::length() const +{ + return this->path_.length(); +} + +const char *AssetCatalogPath::c_str() const +{ + return this->path_.c_str(); +} + +const std::string &AssetCatalogPath::str() const +{ + return this->path_; +} + +/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor. + * Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */ +bool AssetCatalogPath::operator==(const AssetCatalogPath &other_path) const +{ + return this->path_ == other_path.path_; +} + +bool AssetCatalogPath::operator!=(const AssetCatalogPath &other_path) const +{ + return !(*this == other_path); +} + +bool AssetCatalogPath::operator<(const AssetCatalogPath &other_path) const +{ + return this->path_ < other_path.path_; +} + +AssetCatalogPath AssetCatalogPath::operator/(const AssetCatalogPath &path_to_append) const +{ + /* `"" / "path"` or `"path" / ""` should just result in `"path"` */ + if (!*this) { + return path_to_append; + } + if (!path_to_append) { + return *this; + } + + std::stringstream new_path; + new_path << this->path_ << SEPARATOR << path_to_append.path_; + return AssetCatalogPath(new_path.str()); +} + +AssetCatalogPath::operator bool() const +{ + return !this->path_.empty(); +} + +std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append) +{ + stream << path_to_append.path_; + return stream; +} + +AssetCatalogPath AssetCatalogPath::cleanup() const +{ + std::stringstream clean_components; + bool first_component_seen = false; + + this->iterate_components([&clean_components, &first_component_seen](StringRef component_name, + bool /*is_last_component*/) { + const std::string clean_component = cleanup_component(component_name); + + if (clean_component.empty()) { + /* These are caused by leading, trailing, or double slashes. */ + return; + } + + /* If a previous path component has been streamed already, we need a path separator. This + * cannot use the `is_last_component` boolean, because the last component might be skipped due + * to the condition above. */ + if (first_component_seen) { + clean_components << SEPARATOR; + } + first_component_seen = true; + + clean_components << clean_component; + }); + + return AssetCatalogPath(clean_components.str()); +} + +std::string AssetCatalogPath::cleanup_component(StringRef component) +{ + std::string cleaned = component.trim(); + /* Replace colons with something else, as those are used in the CDF file as delimiter. */ + std::replace(cleaned.begin(), cleaned.end(), ':', '-'); + return cleaned; +} + +bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const +{ + if (!other_path) { + /* The empty path contains all other paths. */ + return true; + } + + if (this->path_ == other_path.path_) { + /* Weak is-in relation: equal paths contain each other. */ + return true; + } + + /* To be a child path of 'other_path', our path must be at least a separator and another + * character longer. */ + if (this->length() < other_path.length() + 2) { + return false; + } + + /* Create StringRef to be able to use .startswith(). */ + const StringRef this_path(this->path_); + const bool prefix_ok = this_path.startswith(other_path.path_); + const char next_char = this_path[other_path.length()]; + return prefix_ok && next_char == SEPARATOR; +} + +AssetCatalogPath AssetCatalogPath::parent() const +{ + if (!*this) { + return AssetCatalogPath(""); + } + std::string::size_type last_sep_index = this->path_.rfind(SEPARATOR); + if (last_sep_index == std::string::npos) { + return AssetCatalogPath(""); + } + return AssetCatalogPath(this->path_.substr(0, last_sep_index)); +} + +void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const +{ + const char *next_slash_ptr; + + for (const char *path_component = this->path_.data(); path_component && path_component[0]; + /* Jump to one after the next slash if there is any. */ + path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) { + next_slash_ptr = BLI_path_slash_find(path_component); + + const bool is_last_component = next_slash_ptr == nullptr; + /* Note that this won't be null terminated. */ + const StringRef component_name = is_last_component ? + path_component : + StringRef(path_component, + next_slash_ptr - path_component); + + callback(component_name, is_last_component); + } +} + +AssetCatalogPath AssetCatalogPath::rebase(const AssetCatalogPath &from_path, + const AssetCatalogPath &to_path) const +{ + if (!from_path) { + if (!to_path) { + return AssetCatalogPath(""); + } + return to_path / *this; + } + + if (!this->is_contained_in(from_path)) { + return AssetCatalogPath(""); + } + + if (*this == from_path) { + /* Early return, because otherwise the length+1 below is going to cause problems. */ + return to_path; + } + + /* When from_path = "test", we need to skip "test/" to get the rest of the path, hence the +1. */ + const StringRef suffix = StringRef(this->path_).substr(from_path.length() + 1); + const AssetCatalogPath path_suffix(suffix); + return to_path / path_suffix; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_catalog_path_test.cc b/source/blender/blenkernel/intern/asset_catalog_path_test.cc new file mode 100644 index 00000000000..af15cbf405a --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog_path_test.cc @@ -0,0 +1,251 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation + * All rights reserved. + */ + +#include "BKE_asset_catalog_path.hh" + +#include "BLI_set.hh" +#include "BLI_vector.hh" + +#include <set> +#include <sstream> + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(AssetCatalogPathTest, construction) +{ + AssetCatalogPath from_char_literal("the/path"); + + const std::string str_const = "the/path"; + AssetCatalogPath from_string_constant(str_const); + + std::string str_variable = "the/path"; + AssetCatalogPath from_string_variable(str_variable); + + std::string long_string = "this is a long/string/with/a/path in the middle"; + StringRef long_string_ref(long_string); + StringRef middle_bit = long_string_ref.substr(10, 23); + AssetCatalogPath from_string_ref(middle_bit); + EXPECT_EQ(from_string_ref, "long/string/with/a/path"); +} + +TEST(AssetCatalogPathTest, length) +{ + const AssetCatalogPath one("1"); + EXPECT_EQ(1, one.length()); + + const AssetCatalogPath empty(""); + EXPECT_EQ(0, empty.length()); + + const AssetCatalogPath utf8("some/родитель"); + EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes."; +} + +TEST(AssetCatalogPathTest, comparison_operators) +{ + const AssetCatalogPath empty(""); + const AssetCatalogPath the_path("the/path"); + const AssetCatalogPath the_path_child("the/path/child"); + const AssetCatalogPath unrelated_path("unrelated/path"); + const AssetCatalogPath other_instance_same_path("the/path"); + + EXPECT_LT(empty, the_path); + EXPECT_LT(the_path, the_path_child); + EXPECT_LT(the_path, unrelated_path); + + EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal."; + EXPECT_EQ(empty, "") << "Comparison to empty string should be possible."; + EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal."; + EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible."; + EXPECT_EQ(the_path, other_instance_same_path) + << "Different instances with equal path should compare equal."; + + EXPECT_NE(the_path, the_path_child); + EXPECT_NE(the_path, unrelated_path); + EXPECT_NE(the_path, empty); + + EXPECT_FALSE(empty); + EXPECT_TRUE(the_path); +} + +TEST(AssetCatalogPathTest, move_semantics) +{ + AssetCatalogPath source_path("source/path"); + EXPECT_TRUE(source_path); + + AssetCatalogPath dest_path = std::move(source_path); + EXPECT_FALSE(source_path); + EXPECT_TRUE(dest_path); +} + +TEST(AssetCatalogPathTest, concatenation) +{ + AssetCatalogPath some_parent("some/родитель"); + AssetCatalogPath child = some_parent / "ребенок"; + + EXPECT_EQ(some_parent, "some/родитель") + << "Appending a child path should not modify the parent."; + EXPECT_EQ(child, "some/родитель/ребенок"); + + AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук"; + EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук"); + + AssetCatalogPath empty(""); + AssetCatalogPath child_of_the_void = empty / "child"; + EXPECT_EQ(child_of_the_void, "child") + << "Appending to an empty path should not create an initial slash."; + + AssetCatalogPath parent_of_the_void = some_parent / empty; + EXPECT_EQ(parent_of_the_void, "some/родитель") + << "Prepending to an empty path should not create a trailing slash."; + + std::string subpath = "child"; + AssetCatalogPath concatenated_with_string = some_parent / subpath; + EXPECT_EQ(concatenated_with_string, "some/родитель/child"); +} + +TEST(AssetCatalogPathTest, hashable) +{ + AssetCatalogPath path("heyyyyy"); + + std::set<AssetCatalogPath> path_std_set; + path_std_set.insert(path); + + blender::Set<AssetCatalogPath> path_blender_set; + path_blender_set.add(path); +} + +TEST(AssetCatalogPathTest, stream_operator) +{ + AssetCatalogPath path("путь/в/Пермь"); + std::stringstream sstream; + sstream << path; + EXPECT_EQ("путь/в/Пермь", sstream.str()); +} + +TEST(AssetCatalogPathTest, is_contained_in) +{ + const AssetCatalogPath catpath("simple/path/child"); + EXPECT_FALSE(catpath.is_contained_in("unrelated")); + EXPECT_FALSE(catpath.is_contained_in("sim")); + EXPECT_FALSE(catpath.is_contained_in("simple/pathx")); + EXPECT_FALSE(catpath.is_contained_in("simple/path/c")); + EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild")); + EXPECT_FALSE(catpath.is_contained_in("simple/path/")) + << "Non-normalized paths are not expected to work."; + + EXPECT_TRUE(catpath.is_contained_in("")); + EXPECT_TRUE(catpath.is_contained_in("simple")); + EXPECT_TRUE(catpath.is_contained_in("simple/path")); + + /* Test with some UTF8 non-ASCII characters. */ + AssetCatalogPath some_parent("some/родитель"); + AssetCatalogPath child = some_parent / "ребенок"; + + EXPECT_TRUE(child.is_contained_in(some_parent)); + EXPECT_TRUE(child.is_contained_in("some")); + + AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук"; + EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent)); + EXPECT_TRUE(appended_compound_path.is_contained_in(child)); + + /* Test "going up" directory-style. */ + AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part"; + EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent)) + << "dotdot path components should have no meaning"; +} + +TEST(AssetCatalogPathTest, cleanup) +{ + AssetCatalogPath ugly_path("/ some / родитель / "); + AssetCatalogPath clean_path = ugly_path.cleanup(); + + EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path) + << "cleanup should not modify the path instance itself"; + + EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path); + + AssetCatalogPath double_slashed("some//родитель"); + EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup()); + + AssetCatalogPath with_colons("some/key:subkey=value/path"); + EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup()); +} + +TEST(AssetCatalogPathTest, iterate_components) +{ + AssetCatalogPath path("путь/в/Пермь"); + Vector<std::pair<std::string, bool>> seen_components; + + path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) { + std::pair<std::string, bool> parameter_pair = std::make_pair<std::string, bool>( + component_name, bool(is_last_component)); + seen_components.append(parameter_pair); + }); + + ASSERT_EQ(3, seen_components.size()); + + EXPECT_EQ("путь", seen_components[0].first); + EXPECT_EQ("в", seen_components[1].first); + EXPECT_EQ("Пермь", seen_components[2].first); + + EXPECT_FALSE(seen_components[0].second); + EXPECT_FALSE(seen_components[1].second); + EXPECT_TRUE(seen_components[2].second); +} + +TEST(AssetCatalogPathTest, rebase) +{ + AssetCatalogPath path("some/path/to/some/catalog"); + EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog"); + EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog"); + + EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"), + "some/path/to/some/catalog") + << "Rebasing to itself should not change the path."; + + EXPECT_EQ(path.rebase("path/to", "new/base"), "") + << "Non-matching base path should return empty string to indicate 'NO'."; + + /* Empty strings should be handled without crashing or other nasty side-effects. */ + AssetCatalogPath empty(""); + EXPECT_EQ(empty.rebase("path/to", "new/base"), ""); + EXPECT_EQ(empty.rebase("", "new/base"), "new/base"); + EXPECT_EQ(empty.rebase("", ""), ""); +} + +TEST(AssetCatalogPathTest, parent) +{ + const AssetCatalogPath ascii_path("path/with/missing/parents"); + EXPECT_EQ(ascii_path.parent(), "path/with/missing"); + + const AssetCatalogPath path("путь/в/Пермь/долог/и/далек"); + EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и"); + EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог"); + EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь"); + + const AssetCatalogPath one_level("one"); + EXPECT_EQ(one_level.parent(), ""); + + const AssetCatalogPath empty(""); + EXPECT_EQ(empty.parent(), ""); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc new file mode 100644 index 00000000000..fb471a8ee7b --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -0,0 +1,965 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation + * All rights reserved. + */ + +#include "BKE_appdir.h" +#include "BKE_asset_catalog.hh" +#include "BKE_preferences.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" + +#include "DNA_userdef_types.h" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +/* UUIDs from lib/tests/asset_library/blender_assets.cats.txt */ +const bUUID UUID_ID_WITHOUT_PATH("e34dd2c5-5d2e-4668-9794-1db5de2a4f71"); +const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78"); +const bUUID UUID_POSES_ELLIE_WHITESPACE("b06132f6-5687-4751-a6dd-392740eb3c46"); +const bUUID UUID_POSES_ELLIE_TRAILING_SLASH("3376b94b-a28d-4d05-86c1-bf30b937130d"); +const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed"); +const bUUID UUID_POSES_RUZENA_HAND("81811c31-1a88-4bd7-bb34-c6fc2607a12e"); +const bUUID UUID_POSES_RUZENA_FACE("82162c1f-06cc-4d91-a9bf-4f72c104e348"); +const bUUID UUID_WITHOUT_SIMPLENAME("d7916a31-6ca9-4909-955f-182ca2b81fa3"); + +/* UUIDs from lib/tests/asset_library/modified_assets.cats.txt */ +const bUUID UUID_AGENT_47("c5744ba5-43f5-4f73-8e52-010ad4a61b34"); + +/* Subclass that adds accessors such that protected fields can be used in tests. */ +class TestableAssetCatalogService : public AssetCatalogService { + public: + TestableAssetCatalogService() = default; + + explicit TestableAssetCatalogService(const CatalogFilePath &asset_library_root) + : AssetCatalogService(asset_library_root) + { + } + + AssetCatalogDefinitionFile *get_catalog_definition_file() + { + return catalog_definition_file_.get(); + } + + void create_missing_catalogs() + { + AssetCatalogService::create_missing_catalogs(); + } + + int64_t count_catalogs_with_path(const CatalogFilePath &path) + { + int64_t count = 0; + for (auto &catalog_uptr : catalogs_.values()) { + if (catalog_uptr->path == path) { + count++; + } + } + return count; + } +}; + +class AssetCatalogTest : public testing::Test { + protected: + CatalogFilePath asset_library_root_; + CatalogFilePath temp_library_path_; + + void SetUp() override + { + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + asset_library_root_ = test_files_dir + "/" + "asset_library"; + temp_library_path_ = ""; + } + + /* Register a temporary path, which will be removed at the end of the test. + * The returned path ends in a slash. */ + CatalogFilePath use_temp_path() + { + BKE_tempdir_init(""); + const CatalogFilePath tempdir = BKE_tempdir_session(); + temp_library_path_ = tempdir + "test-temporary-path/"; + return temp_library_path_; + } + + CatalogFilePath create_temp_path() + { + CatalogFilePath path = use_temp_path(); + BLI_dir_create_recursive(path.c_str()); + return path; + } + + struct CatalogPathInfo { + StringRef name; + int parent_count; + }; + + void assert_expected_item(const CatalogPathInfo &expected_path, + const AssetCatalogTreeItem &actual_item) + { + char expected_filename[FILE_MAXFILE]; + /* Is the catalog name as expected? "character", "Ellie", ... */ + BLI_split_file_part(expected_path.name.data(), expected_filename, sizeof(expected_filename)); + EXPECT_EQ(expected_filename, actual_item.get_name()); + /* Does the computed number of parents match? */ + EXPECT_EQ(expected_path.parent_count, actual_item.count_parents()); + EXPECT_EQ(expected_path.name, actual_item.catalog_path().str()); + } + + /** + * Recursively iterate over all tree items using #AssetCatalogTree::foreach_item() and check if + * the items map exactly to \a expected_paths. + */ + void assert_expected_tree_items(AssetCatalogTree *tree, + const std::vector<CatalogPathInfo> &expected_paths) + { + int i = 0; + tree->foreach_item([&](const AssetCatalogTreeItem &actual_item) { + ASSERT_LT(i, expected_paths.size()) + << "More catalogs in tree than expected; did not expect " << actual_item.catalog_path(); + assert_expected_item(expected_paths[i], actual_item); + i++; + }); + } + + /** + * Iterate over the root items of \a tree and check if the items map exactly to \a + * expected_paths. Similar to #assert_expected_tree_items() but calls + * #AssetCatalogTree::foreach_root_item() instead of #AssetCatalogTree::foreach_item(). + */ + void assert_expected_tree_root_items(AssetCatalogTree *tree, + const std::vector<CatalogPathInfo> &expected_paths) + { + int i = 0; + tree->foreach_root_item([&](const AssetCatalogTreeItem &actual_item) { + ASSERT_LT(i, expected_paths.size()) + << "More catalogs in tree root than expected; did not expect " + << actual_item.catalog_path(); + assert_expected_item(expected_paths[i], actual_item); + i++; + }); + } + + /** + * Iterate over the child items of \a parent_item and check if the items map exactly to \a + * expected_paths. Similar to #assert_expected_tree_items() but calls + * #AssetCatalogTreeItem::foreach_child() instead of #AssetCatalogTree::foreach_item(). + */ + void assert_expected_tree_item_child_items(AssetCatalogTreeItem *parent_item, + const std::vector<CatalogPathInfo> &expected_paths) + { + int i = 0; + parent_item->foreach_child([&](const AssetCatalogTreeItem &actual_item) { + ASSERT_LT(i, expected_paths.size()) + << "More catalogs in tree item than expected; did not expect " + << actual_item.catalog_path(); + assert_expected_item(expected_paths[i], actual_item); + i++; + }); + } + + void TearDown() override + { + if (!temp_library_path_.empty()) { + BLI_delete(temp_library_path_.c_str(), true, true); + temp_library_path_ = ""; + } + } +}; + +TEST_F(AssetCatalogTest, load_single_file) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + /* Test getting a non-existent catalog ID. */ + EXPECT_EQ(nullptr, service.find_catalog(BLI_uuid_generate_random())); + + /* Test getting an invalid catalog (without path definition). */ + AssetCatalog *cat_without_path = service.find_catalog(UUID_ID_WITHOUT_PATH); + ASSERT_EQ(nullptr, cat_without_path); + + /* Test getting a regular catalog. */ + AssetCatalog *poses_ellie = service.find_catalog(UUID_POSES_ELLIE); + ASSERT_NE(nullptr, poses_ellie); + EXPECT_EQ(UUID_POSES_ELLIE, poses_ellie->catalog_id); + EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str()); + EXPECT_EQ("POSES_ELLIE", poses_ellie->simple_name); + + /* Test white-space stripping and support in the path. */ + AssetCatalog *poses_whitespace = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE); + ASSERT_NE(nullptr, poses_whitespace); + EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, poses_whitespace->catalog_id); + EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path.str()); + EXPECT_EQ("POSES_ELLIE WHITESPACE", poses_whitespace->simple_name); + + /* Test getting a UTF-8 catalog ID. */ + AssetCatalog *poses_ruzena = service.find_catalog(UUID_POSES_RUZENA); + ASSERT_NE(nullptr, poses_ruzena); + EXPECT_EQ(UUID_POSES_RUZENA, poses_ruzena->catalog_id); + EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path.str()); + EXPECT_EQ("POSES_RUŽENA", poses_ruzena->simple_name); +} + +TEST_F(AssetCatalogTest, insert_item_into_tree) +{ + { + AssetCatalogTree tree; + std::unique_ptr<AssetCatalog> catalog_empty_path = AssetCatalog::from_path(""); + tree.insert_item(*catalog_empty_path); + + assert_expected_tree_items(&tree, {}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}}); + + /* Insert child after parent already exists. */ + std::unique_ptr<AssetCatalog> child_catalog = AssetCatalog::from_path("item/child"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}, {"item/child", 1}}); + + std::vector<CatalogPathInfo> expected_paths; + + /* Test inserting multi-component sub-path. */ + std::unique_ptr<AssetCatalog> grandgrandchild_catalog = AssetCatalog::from_path( + "item/child/grandchild/grandgrandchild"); + tree.insert_item(*catalog); + expected_paths = {{"item", 0}, + {"item/child", 1}, + {"item/child/grandchild", 2}, + {"item/child/grandchild/grandgrandchild", 3}}; + assert_expected_tree_items(&tree, expected_paths); + + std::unique_ptr<AssetCatalog> root_level_catalog = AssetCatalog::from_path("root level"); + tree.insert_item(*catalog); + expected_paths = {{"item", 0}, + {"item/child", 1}, + {"item/child/grandchild", 2}, + {"item/child/grandchild/grandgrandchild", 3}, + {"root level", 0}}; + assert_expected_tree_items(&tree, expected_paths); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item/child"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}, {"item/child", 1}}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("white space"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"white space", 0}}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("/item/white space"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}, {"item/white space", 1}}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog_unicode_path = AssetCatalog::from_path("Ružena"); + tree.insert_item(*catalog_unicode_path); + assert_expected_tree_items(&tree, {{"Ružena", 0}}); + + catalog_unicode_path = AssetCatalog::from_path("Ružena/Ružena"); + tree.insert_item(*catalog_unicode_path); + assert_expected_tree_items(&tree, {{"Ružena", 0}, {"Ružena/Ružena", 1}}); + } +} + +TEST_F(AssetCatalogTest, load_single_file_into_tree) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + /* Contains not only paths from the CDF but also the missing parents (implicitly defined + * catalogs). */ + std::vector<CatalogPathInfo> expected_paths{ + {"character", 0}, + {"character/Ellie", 1}, + {"character/Ellie/poselib", 2}, + {"character/Ellie/poselib/tailslash", 3}, + {"character/Ellie/poselib/white space", 3}, + {"character/Ružena", 1}, + {"character/Ružena/poselib", 2}, + {"character/Ružena/poselib/face", 3}, + {"character/Ružena/poselib/hand", 3}, + {"path", 0}, /* Implicit. */ + {"path/without", 1}, /* Implicit. */ + {"path/without/simplename", 2}, /* From CDF. */ + }; + + AssetCatalogTree *tree = service.get_catalog_tree(); + assert_expected_tree_items(tree, expected_paths); +} + +TEST_F(AssetCatalogTest, foreach_in_tree) +{ + { + AssetCatalogTree tree{}; + const std::vector<CatalogPathInfo> no_catalogs{}; + + assert_expected_tree_items(&tree, no_catalogs); + assert_expected_tree_root_items(&tree, no_catalogs); + /* Need a root item to check child items. */ + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("something"); + tree.insert_item(*catalog); + tree.foreach_root_item([&no_catalogs, this](AssetCatalogTreeItem &item) { + assert_expected_tree_item_child_items(&item, no_catalogs); + }); + } + + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + std::vector<CatalogPathInfo> expected_root_items{{"character", 0}, {"path", 0}}; + AssetCatalogTree *tree = service.get_catalog_tree(); + assert_expected_tree_root_items(tree, expected_root_items); + + /* Test if the direct children of the root item are what's expected. */ + std::vector<std::vector<CatalogPathInfo>> expected_root_child_items = { + /* Children of the "character" root item. */ + {{"character/Ellie", 1}, {"character/Ružena", 1}}, + /* Children of the "path" root item. */ + {{"path/without", 1}}, + }; + int i = 0; + tree->foreach_root_item([&expected_root_child_items, &i, this](AssetCatalogTreeItem &item) { + assert_expected_tree_item_child_items(&item, expected_root_child_items[i]); + i++; + }); +} + +TEST_F(AssetCatalogTest, find_catalog_by_path) +{ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + AssetCatalog *catalog; + + EXPECT_EQ(nullptr, service.find_catalog_by_path("")); + catalog = service.find_catalog_by_path("character/Ellie/poselib/white space"); + EXPECT_NE(nullptr, catalog); + EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, catalog->catalog_id); + catalog = service.find_catalog_by_path("character/Ružena/poselib"); + EXPECT_NE(nullptr, catalog); + EXPECT_EQ(UUID_POSES_RUZENA, catalog->catalog_id); + + /* "character/Ellie/poselib" is used by two catalogs. Check if it's using the first one. */ + catalog = service.find_catalog_by_path("character/Ellie/poselib"); + EXPECT_NE(nullptr, catalog); + EXPECT_EQ(UUID_POSES_ELLIE, catalog->catalog_id); + EXPECT_NE(UUID_POSES_ELLIE_TRAILING_SLASH, catalog->catalog_id); +} + +TEST_F(AssetCatalogTest, write_single_file) +{ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + const CatalogFilePath save_to_path = use_temp_path() + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + cdf->write_to_disk(save_to_path); + + AssetCatalogService loaded_service(save_to_path); + loaded_service.load_from_disk(); + + /* Test that the expected catalogs are there. */ + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); + + /* Test that the invalid catalog definition wasn't copied. */ + EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_ID_WITHOUT_PATH)); + + /* TODO(@sybren): test ordering of catalogs in the file. */ +} + +TEST_F(AssetCatalogTest, no_writing_empty_files) +{ + const CatalogFilePath temp_lib_root = create_temp_path(); + AssetCatalogService service(temp_lib_root); + service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + + const CatalogFilePath default_cdf_path = temp_lib_root + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + EXPECT_FALSE(BLI_exists(default_cdf_path.c_str())); +} + +/* Already loaded a CDF, saving to some unrelated directory. */ +TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf) +{ + const CatalogFilePath top_level_dir = create_temp_path(); /* Has trailing slash. */ + + /* Create a copy of the CDF in SVN, so we can safely write to it. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath cdf_dirname = top_level_dir + "other_dir/"; + const CatalogFilePath cdf_filename = cdf_dirname + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_TRUE(BLI_dir_create_recursive(cdf_dirname.c_str())); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), cdf_filename.c_str())) + << "Unable to copy " << original_cdf_file << " to " << cdf_filename; + + /* Load the CDF, add a catalog, and trigger a write. This should write to the loaded CDF. */ + TestableAssetCatalogService service(cdf_filename); + service.load_from_disk(); + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path); + + /* Test that the CDF was created in the expected location. */ + const CatalogFilePath backup_filename = cdf_filename + "~"; + EXPECT_TRUE(BLI_exists(cdf_filename.c_str())); + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the on-disk CDF contains the expected catalogs. */ + AssetCatalogService loaded_service(cdf_filename); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)) + << "Expected to see the newly-created catalog."; + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)) + << "Expected to see the already-existing catalog."; +} + +/* Create some catalogs in memory, save to directory that doesn't contain anything else. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory) +{ + const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */ + + TestableAssetCatalogService service; + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + const CatalogFilePath blendfilename = target_dir + "some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + + /* Test that the CDF was created in the expected location. */ + const CatalogFilePath expected_cdf_path = target_dir + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + EXPECT_TRUE(BLI_exists(expected_cdf_path.c_str())); + + /* Test that the in-memory CDF has been created, and contains the expected catalog. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_TRUE(cdf->contains(cat->catalog_id)); + + /* Test that the on-disk CDF contains the expected catalog. */ + AssetCatalogService loaded_service(expected_cdf_path); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); +} + +/* Create some catalogs in memory, save to directory that contains a default CDF. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_merge) +{ + const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath writable_cdf_file = target_dir + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Create the catalog service without loading the already-existing CDF. */ + TestableAssetCatalogService service; + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + /* Mock that the blend file is written to a subdirectory of the asset library. */ + const CatalogFilePath blendfilename = target_dir + "some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + + /* Test that the CDF still exists in the expected location. */ + const CatalogFilePath backup_filename = writable_cdf_file + "~"; + EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str())); + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the in-memory CDF has the expected file path. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_EQ(writable_cdf_file, cdf->file_path); + + /* Test that the in-memory catalogs have been merged with the on-disk one. */ + AssetCatalogService loaded_service(writable_cdf_file); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); +} + +/* Create some catalogs in memory, save to subdirectory of a registered asset library. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib) +{ + const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath registered_asset_lib = target_dir + "my_asset_library/"; + CatalogFilePath writable_cdf_file = registered_asset_lib + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + BLI_path_slash_native(writable_cdf_file.data()); + + /* Set up a temporary asset library for testing. */ + bUserAssetLibrary *asset_lib_pref = BKE_preferences_asset_library_add( + &U, "Test", registered_asset_lib.c_str()); + ASSERT_NE(nullptr, asset_lib_pref); + ASSERT_TRUE(BLI_dir_create_recursive(registered_asset_lib.c_str())); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Create the catalog service without loading the already-existing CDF. */ + TestableAssetCatalogService service; + const CatalogFilePath blenddirname = registered_asset_lib + "subdirectory/"; + const CatalogFilePath blendfilename = blenddirname + "some_file.blend"; + ASSERT_TRUE(BLI_dir_create_recursive(blenddirname.c_str())); + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + /* Mock that the blend file is written to the directory already containing a CDF. */ + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + + /* Test that the CDF still exists in the expected location. */ + EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str())); + const CatalogFilePath backup_filename = writable_cdf_file + "~"; + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the in-memory CDF has the expected file path. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + BLI_path_slash_native(cdf->file_path.data()); + EXPECT_EQ(writable_cdf_file, cdf->file_path); + + /* Test that the in-memory catalogs have been merged with the on-disk one. */ + AssetCatalogService loaded_service(writable_cdf_file); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + + BKE_preferences_asset_library_remove(&U, asset_lib_pref); +} + +TEST_F(AssetCatalogTest, create_first_catalog_from_scratch) +{ + /* Even from scratch a root directory should be known. */ + const CatalogFilePath temp_lib_root = use_temp_path(); + AssetCatalogService service; + + /* Just creating the service should NOT create the path. */ + EXPECT_FALSE(BLI_exists(temp_lib_root.c_str())); + + AssetCatalog *cat = service.create_catalog("some/catalog/path"); + ASSERT_NE(nullptr, cat); + EXPECT_EQ(cat->path, "some/catalog/path"); + EXPECT_EQ(cat->simple_name, "some-catalog-path"); + + /* Creating a new catalog should not save anything to disk yet. */ + EXPECT_FALSE(BLI_exists(temp_lib_root.c_str())); + + /* Writing to disk should create the directory + the default file. */ + service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str())); + + const CatalogFilePath definition_file_path = temp_lib_root + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + EXPECT_TRUE(BLI_is_file(definition_file_path.c_str())); + + AssetCatalogService loaded_service(temp_lib_root); + loaded_service.load_from_disk(); + + /* Test that the expected catalog is there. */ + AssetCatalog *written_cat = loaded_service.find_catalog(cat->catalog_id); + ASSERT_NE(nullptr, written_cat); + EXPECT_EQ(written_cat->catalog_id, cat->catalog_id); + EXPECT_EQ(written_cat->path, cat->path.str()); +} + +TEST_F(AssetCatalogTest, create_catalog_after_loading_file) +{ + const CatalogFilePath temp_lib_root = create_temp_path(); + + /* Copy the asset catalog definition files to a separate location, so that we can test without + * overwriting the test file in SVN. */ + const CatalogFilePath default_catalog_path = asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + const CatalogFilePath writable_catalog_path = temp_lib_root + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_EQ(0, BLI_copy(default_catalog_path.c_str(), writable_catalog_path.c_str())); + EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str())); + EXPECT_TRUE(BLI_is_file(writable_catalog_path.c_str())); + + TestableAssetCatalogService service(temp_lib_root); + service.load_from_disk(); + EXPECT_EQ(writable_catalog_path, service.get_catalog_definition_file()->file_path); + EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE)) << "expected catalogs to be loaded"; + + /* This should create a new catalog but not write to disk. */ + const AssetCatalog *new_catalog = service.create_catalog("new/catalog"); + const bUUID new_catalog_id = new_catalog->catalog_id; + + /* Reload the on-disk catalog file. */ + TestableAssetCatalogService loaded_service(temp_lib_root); + loaded_service.load_from_disk(); + EXPECT_EQ(writable_catalog_path, loaded_service.get_catalog_definition_file()->file_path); + + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)) + << "expected pre-existing catalogs to be kept in the file"; + EXPECT_EQ(nullptr, loaded_service.find_catalog(new_catalog_id)) + << "expecting newly added catalog to not yet be saved to " << temp_lib_root; + + /* Write and reload the catalog file. */ + service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + AssetCatalogService reloaded_service(temp_lib_root); + reloaded_service.load_from_disk(); + EXPECT_NE(nullptr, reloaded_service.find_catalog(UUID_POSES_ELLIE)) + << "expected pre-existing catalogs to be kept in the file"; + EXPECT_NE(nullptr, reloaded_service.find_catalog(new_catalog_id)) + << "expecting newly added catalog to exist in the file"; +} + +TEST_F(AssetCatalogTest, create_catalog_path_cleanup) +{ + AssetCatalogService service; + AssetCatalog *cat = service.create_catalog(" /some/path / "); + + EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id)); + EXPECT_EQ("some/path", cat->path.str()); + EXPECT_EQ("some-path", cat->simple_name); +} + +TEST_F(AssetCatalogTest, create_catalog_simple_name) +{ + AssetCatalogService service; + AssetCatalog *cat = service.create_catalog( + "production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands"); + + EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id)); + EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands", + cat->path.str()); + EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name); +} + +TEST_F(AssetCatalogTest, delete_catalog_leaf) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + /* Delete a leaf catalog, i.e. one that is not a parent of another catalog. + * This keeps this particular test easy. */ + service.delete_catalog(UUID_POSES_RUZENA_HAND); + EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND)); + + /* Contains not only paths from the CDF but also the missing parents (implicitly defined + * catalogs). This is why a leaf catalog was deleted. */ + std::vector<CatalogPathInfo> expected_paths{ + {"character", 0}, + {"character/Ellie", 1}, + {"character/Ellie/poselib", 2}, + {"character/Ellie/poselib/tailslash", 3}, + {"character/Ellie/poselib/white space", 3}, + {"character/Ružena", 1}, + {"character/Ružena/poselib", 2}, + {"character/Ružena/poselib/face", 3}, + // {"character/Ružena/poselib/hand", 3}, /* This is the deleted one. */ + {"path", 0}, + {"path/without", 1}, + {"path/without/simplename", 2}, + }; + + AssetCatalogTree *tree = service.get_catalog_tree(); + assert_expected_tree_items(tree, expected_paths); +} + +TEST_F(AssetCatalogTest, delete_catalog_write_to_disk) +{ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + service.delete_catalog(UUID_POSES_ELLIE); + + const CatalogFilePath save_to_path = use_temp_path(); + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + cdf->write_to_disk(save_to_path + "/" + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + AssetCatalogService loaded_service(save_to_path); + loaded_service.load_from_disk(); + + /* Test that the expected catalogs are there, except the deleted one. */ + EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); +} + +TEST_F(AssetCatalogTest, update_catalog_path) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA); + const AssetCatalogPath orig_path = orig_cat->path; + + service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena"); + + EXPECT_EQ(nullptr, service.find_catalog_by_path(orig_path)) + << "The original (pre-rename) path should not be associated with a catalog any more."; + + const AssetCatalog *renamed_cat = service.find_catalog(UUID_POSES_RUZENA); + ASSERT_NE(nullptr, renamed_cat); + ASSERT_EQ(orig_cat, renamed_cat) << "Changing the path should not reallocate the catalog."; + EXPECT_EQ(orig_cat->simple_name, renamed_cat->simple_name) + << "Changing the path should not change the simple name."; + EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id) + << "Changing the path should not change the catalog ID."; + + EXPECT_EQ("charlib/Ružena", renamed_cat->path.str()) + << "Changing the path should change the path. Surprise."; + + EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str()) + << "Changing the path should update children."; + EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str()) + << "Changing the path should update children."; +} + +TEST_F(AssetCatalogTest, merge_catalog_files) +{ + const CatalogFilePath cdf_dir = create_temp_path(); + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath modified_cdf_file = asset_library_root_ + "/modified_assets.cats.txt"; + const CatalogFilePath temp_cdf_file = cdf_dir + "blender_assets.cats.txt"; + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str())); + + /* Load the unmodified, original CDF. */ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(cdf_dir); + + /* Copy a modified file, to mimic a situation where someone changed the + * CDF after we loaded it. */ + ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str())); + + /* Overwrite the modified file. This should merge the on-disk file with our catalogs. */ + service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + + AssetCatalogService loaded_service(cdf_dir); + loaded_service.load_from_disk(); + + /* Test that the expected catalogs are there. */ + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_AGENT_47)); /* New in the modified file. */ + + /* When there are overlaps, the in-memory (i.e. last-saved) paths should win. */ + const AssetCatalog *ruzena_face = loaded_service.find_catalog(UUID_POSES_RUZENA_FACE); + EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path.str()); +} + +TEST_F(AssetCatalogTest, backups) +{ + const CatalogFilePath cdf_dir = create_temp_path(); + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath writable_cdf_file = cdf_dir + "/blender_assets.cats.txt"; + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Read a CDF, modify, and write it. */ + AssetCatalogService service(cdf_dir); + service.load_from_disk(); + service.delete_catalog(UUID_POSES_ELLIE); + service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + + const CatalogFilePath backup_path = writable_cdf_file + "~"; + ASSERT_TRUE(BLI_is_file(backup_path.c_str())); + + AssetCatalogService loaded_service; + loaded_service.load_from_disk(backup_path); + + /* Test that the expected catalogs are there, including the deleted one. + * This is the backup, after all. */ + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); +} + +TEST_F(AssetCatalogTest, order_by_path) +{ + const bUUID cat2_uuid("22222222-b847-44d9-bdca-ff04db1c24f5"); + const bUUID cat4_uuid("11111111-b847-44d9-bdca-ff04db1c24f5"); /* Sorts earlier than above. */ + const AssetCatalog cat1(BLI_uuid_generate_random(), "simple/path/child", ""); + const AssetCatalog cat2(cat2_uuid, "simple/path", ""); + const AssetCatalog cat3(BLI_uuid_generate_random(), "complex/path/...or/is/it?", ""); + const AssetCatalog cat4( + cat4_uuid, "simple/path", "different ID, same path"); /* should be kept */ + const AssetCatalog cat5(cat4_uuid, "simple/path", "same ID, same path"); /* disappears */ + + AssetCatalogOrderedSet by_path; + by_path.insert(&cat1); + by_path.insert(&cat2); + by_path.insert(&cat3); + by_path.insert(&cat4); + by_path.insert(&cat5); + + AssetCatalogOrderedSet::const_iterator set_iter = by_path.begin(); + + EXPECT_EQ(1, by_path.count(&cat1)); + EXPECT_EQ(1, by_path.count(&cat2)); + EXPECT_EQ(1, by_path.count(&cat3)); + EXPECT_EQ(1, by_path.count(&cat4)); + ASSERT_EQ(4, by_path.size()) << "Expecting cat5 to not be stored in the set, as it duplicates " + "an already-existing path + UUID"; + + EXPECT_EQ(cat3.catalog_id, (*(set_iter++))->catalog_id); /* complex/path */ + EXPECT_EQ(cat4.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 111.. ID */ + EXPECT_EQ(cat2.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 222.. ID */ + EXPECT_EQ(cat1.catalog_id, (*(set_iter++))->catalog_id); /* simple/path/child */ + + if (set_iter != by_path.end()) { + const AssetCatalog *next_cat = *set_iter; + FAIL() << "Did not expect more items in the set, had at least " << next_cat->catalog_id << ":" + << next_cat->path; + } +} + +TEST_F(AssetCatalogTest, create_missing_catalogs) +{ + TestableAssetCatalogService new_service; + new_service.create_catalog("path/with/missing/parents"); + + EXPECT_EQ(nullptr, new_service.find_catalog_by_path("path/with/missing")) + << "Missing parents should not be immediately created."; + EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) << "Empty path should never be valid"; + + new_service.create_missing_catalogs(); + + EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with/missing")); + EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with")); + EXPECT_NE(nullptr, new_service.find_catalog_by_path("path")); + EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) + << "Empty path should never be valid, even when after missing catalogs"; +} + +TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading) +{ + TestableAssetCatalogService loaded_service(asset_library_root_); + loaded_service.load_from_disk(); + + const AssetCatalog *cat_char = loaded_service.find_catalog_by_path("character"); + const AssetCatalog *cat_ellie = loaded_service.find_catalog_by_path("character/Ellie"); + const AssetCatalog *cat_ruzena = loaded_service.find_catalog_by_path("character/Ružena"); + ASSERT_NE(nullptr, cat_char) << "Missing parents should be created immediately after loading."; + ASSERT_NE(nullptr, cat_ellie) << "Missing parents should be created immediately after loading."; + ASSERT_NE(nullptr, cat_ruzena) << "Missing parents should be created immediately after loading."; + + AssetCatalogDefinitionFile *cdf = loaded_service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_TRUE(cdf->contains(cat_char->catalog_id)) << "Missing parents should be saved to a CDF."; + EXPECT_TRUE(cdf->contains(cat_ellie->catalog_id)) << "Missing parents should be saved to a CDF."; + EXPECT_TRUE(cdf->contains(cat_ruzena->catalog_id)) + << "Missing parents should be saved to a CDF."; + + /* Check that each missing parent is only created once. The CDF contains multiple paths that + * could trigger the creation of missing parents, so this test makes sense. */ + EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character")); + EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ellie")); + EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ružena")); +} + +TEST_F(AssetCatalogTest, create_catalog_filter) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(); + + /* Alias for the same catalog as the main one. */ + AssetCatalog *alias_ruzena = service.create_catalog("character/Ružena/poselib"); + /* Alias for a sub-catalog. */ + AssetCatalog *alias_ruzena_hand = service.create_catalog("character/Ružena/poselib/hand"); + + AssetCatalogFilter filter = service.create_catalog_filter(UUID_POSES_RUZENA); + + /* Positive test for loaded-from-disk catalogs. */ + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA)) + << "Main catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_HAND)) + << "Sub-catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_FACE)) + << "Sub-catalog should be included in the filter."; + + /* Positive test for newly-created catalogs. */ + EXPECT_TRUE(filter.contains(alias_ruzena->catalog_id)) + << "Alias of main catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(alias_ruzena_hand->catalog_id)) + << "Alias of sub-catalog should be included in the filter."; + + /* Negative test for unrelated catalogs. */ + EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included."; + EXPECT_FALSE(filter.contains(UUID_ID_WITHOUT_PATH)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_FALSE(filter.contains(UUID_WITHOUT_SIMPLENAME)); +} + +TEST_F(AssetCatalogTest, create_catalog_filter_for_unknown_uuid) +{ + AssetCatalogService service; + const bUUID unknown_uuid = BLI_uuid_generate_random(); + + AssetCatalogFilter filter = service.create_catalog_filter(unknown_uuid); + EXPECT_TRUE(filter.contains(unknown_uuid)); + + EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included."; + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); +} + +TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets) +{ + AssetCatalogService service; + + AssetCatalogFilter filter = service.create_catalog_filter(BLI_uuid_nil()); + EXPECT_TRUE(filter.contains(BLI_uuid_nil())); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc new file mode 100644 index 00000000000..27e66ee5725 --- /dev/null +++ b/source/blender/blenkernel/intern/asset_library.cc @@ -0,0 +1,141 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#include "BKE_asset_library.hh" +#include "BKE_callbacks.h" +#include "BKE_main.h" +#include "BKE_preferences.h" + +#include "BLI_path_util.h" + +#include "DNA_userdef_types.h" + +#include "MEM_guardedalloc.h" + +#include <memory> + +/** + * Loading an asset library at this point only means loading the catalogs. Later on this should + * invoke reading of asset representations too. + */ +struct AssetLibrary *BKE_asset_library_load(const char *library_path) +{ + blender::bke::AssetLibrary *lib = new blender::bke::AssetLibrary(); + lib->on_save_handler_register(); + lib->load(library_path); + return reinterpret_cast<struct AssetLibrary *>(lib); +} + +void BKE_asset_library_free(struct AssetLibrary *asset_library) +{ + blender::bke::AssetLibrary *lib = reinterpret_cast<blender::bke::AssetLibrary *>(asset_library); + lib->on_save_handler_unregister(); + delete lib; +} + +bool BKE_asset_library_find_suitable_root_path_from_path(const char *input_path, + char *r_library_path) +{ + if (bUserAssetLibrary *preferences_lib = BKE_preferences_asset_library_containing_path( + &U, input_path)) { + BLI_strncpy(r_library_path, preferences_lib->path, FILE_MAXDIR); + return true; + } + + BLI_split_dir_part(input_path, r_library_path, FILE_MAXDIR); + return r_library_path[0] != '\0'; +} + +bool BKE_asset_library_find_suitable_root_path_from_main(const Main *bmain, char *r_library_path) +{ + return BKE_asset_library_find_suitable_root_path_from_path(bmain->name, r_library_path); +} + +blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( + const ::AssetLibrary *library_c) +{ + if (library_c == nullptr) { + return nullptr; + } + + const blender::bke::AssetLibrary &library = reinterpret_cast<const blender::bke::AssetLibrary &>( + *library_c); + return library.catalog_service.get(); +} + +blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library) +{ + blender::bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service( + library); + if (catalog_service == nullptr) { + return nullptr; + } + + return catalog_service->get_catalog_tree(); +} + +namespace blender::bke { + +void AssetLibrary::load(StringRefNull library_root_directory) +{ + auto catalog_service = std::make_unique<AssetCatalogService>(library_root_directory); + catalog_service->load_from_disk(); + this->catalog_service = std::move(catalog_service); +} + +namespace { +void asset_library_on_save_post(struct Main *main, + struct PointerRNA **pointers, + const int num_pointers, + void *arg) +{ + AssetLibrary *asset_lib = static_cast<AssetLibrary *>(arg); + asset_lib->on_save_post(main, pointers, num_pointers); +} +} // namespace + +void AssetLibrary::on_save_handler_register() +{ + /* The callback system doesn't own `on_save_callback_store_`. */ + on_save_callback_store_.alloc = false; + + on_save_callback_store_.func = asset_library_on_save_post; + on_save_callback_store_.arg = this; + + BKE_callback_add(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); +} + +void AssetLibrary::on_save_handler_unregister() +{ + BKE_callback_remove(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); +} + +void AssetLibrary::on_save_post(struct Main *main, + struct PointerRNA ** /*pointers*/, + const int /*num_pointers*/) +{ + if (this->catalog_service == nullptr) { + return; + } + + this->catalog_service->write_to_disk_on_blendfile_save(main->name); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_library_test.cc b/source/blender/blenkernel/intern/asset_library_test.cc new file mode 100644 index 00000000000..30ac4dc6ad8 --- /dev/null +++ b/source/blender/blenkernel/intern/asset_library_test.cc @@ -0,0 +1,82 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation + * All rights reserved. + */ + +#include "BKE_appdir.h" +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(AssetLibraryTest, load_and_free_c_functions) +{ + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + /* Load the asset library. */ + const std::string library_path = test_files_dir + "/" + "asset_library"; + ::AssetLibrary *library_c_ptr = BKE_asset_library_load(library_path.data()); + ASSERT_NE(nullptr, library_c_ptr); + + /* Check that it can be cast to the C++ type and has a Catalog Service. */ + blender::bke::AssetLibrary *library_cpp_ptr = reinterpret_cast<blender::bke::AssetLibrary *>( + library_c_ptr); + AssetCatalogService *service = library_cpp_ptr->catalog_service.get(); + ASSERT_NE(nullptr, service); + + /* Check that the catalogs defined in the library are actually loaded. This just tests one single + * catalog, as that indicates the file has been loaded. Testing that that loading went OK is for + * the asset catalog service tests. */ + const bUUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78"); + AssetCatalog *poses_ellie = service->find_catalog(uuid_poses_ellie); + ASSERT_NE(nullptr, poses_ellie) << "unable to find POSES_ELLIE catalog"; + EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str()); + + BKE_asset_library_free(library_c_ptr); +} + +TEST(AssetLibraryTest, load_nonexistent_directory) +{ + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + /* Load the asset library. */ + const std::string library_path = test_files_dir + "/" + + "asset_library/this/subdir/does/not/exist"; + ::AssetLibrary *library_c_ptr = BKE_asset_library_load(library_path.data()); + ASSERT_NE(nullptr, library_c_ptr); + + /* Check that it can be cast to the C++ type and has a Catalog Service. */ + blender::bke::AssetLibrary *library_cpp_ptr = reinterpret_cast<blender::bke::AssetLibrary *>( + library_c_ptr); + AssetCatalogService *service = library_cpp_ptr->catalog_service.get(); + ASSERT_NE(nullptr, service); + + /* Check that the catalog service doesn't have any catalogs. */ + EXPECT_TRUE(service->is_empty()); + + BKE_asset_library_free(library_c_ptr); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/asset_test.cc b/source/blender/blenkernel/intern/asset_test.cc new file mode 100644 index 00000000000..77b98a8ac0a --- /dev/null +++ b/source/blender/blenkernel/intern/asset_test.cc @@ -0,0 +1,70 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation + * All rights reserved. + */ + +#include "BKE_asset.h" + +#include "BLI_uuid.h" + +#include "DNA_asset_types.h" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(AssetMetadataTest, set_catalog_id) +{ + AssetMetaData meta; + const bUUID uuid = BLI_uuid_generate_random(); + + /* Test trivial values. */ + BKE_asset_metadata_catalog_id_clear(&meta); + EXPECT_TRUE(BLI_uuid_is_nil(meta.catalog_id)); + EXPECT_STREQ("", meta.catalog_simple_name); + + /* Test simple situation where the given short name is used as-is. */ + BKE_asset_metadata_catalog_id_set(&meta, uuid, "simple"); + EXPECT_TRUE(BLI_uuid_equal(uuid, meta.catalog_id)); + EXPECT_STREQ("simple", meta.catalog_simple_name); + + /* Test white-space trimming. */ + BKE_asset_metadata_catalog_id_set(&meta, uuid, " GovoriÅ¡ angleÅ¡ko? "); + EXPECT_STREQ("GovoriÅ¡ angleÅ¡ko?", meta.catalog_simple_name); + + /* Test length trimming to 63 chars + terminating zero. */ + constexpr char len66[] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"; + constexpr char len63[] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1"; + BKE_asset_metadata_catalog_id_set(&meta, uuid, len66); + EXPECT_STREQ(len63, meta.catalog_simple_name); + + /* Test length trimming happens after white-space trimming. */ + constexpr char len68[] = + " \ + 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 "; + BKE_asset_metadata_catalog_id_set(&meta, uuid, len68); + EXPECT_STREQ(len63, meta.catalog_simple_name); + + /* Test length trimming to 63 bytes, and not 63 characters. ✓ in UTF-8 is three bytes long. */ + constexpr char with_utf8[] = + "00010203040506✓0708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"; + BKE_asset_metadata_catalog_id_set(&meta, uuid, with_utf8); + EXPECT_STREQ("00010203040506✓0708090a0b0c0d0e0f101112131415161718191a1b1c1d", + meta.catalog_simple_name); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 8c4f87be91f..c2837b522c4 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -55,6 +55,21 @@ using blender::fn::GVArray_For_SingleValue; namespace blender::bke { +std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id) +{ + if (attribute_id.is_named()) { + stream << attribute_id.name(); + } + else if (attribute_id.is_anonymous()) { + const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id(); + stream << "<" << BKE_anonymous_attribute_id_debug_name(&anonymous_id) << ">"; + } + else { + stream << "<none>"; + } + return stream; +} + const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type) { switch (type) { diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index 1c5d8804280..6957f9b5a69 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -344,6 +344,13 @@ static void setup_app_data(bContext *C, do_versions_ipos_to_animato(bmain); } + /* FIXME: Same as above, readfile's `do_version` do not allow to create new IDs. */ + /* TODO: Once this is definitively validated for 3.0 and option to not do it is removed, add a + * version bump and check here. */ + if (!USER_EXPERIMENTAL_TEST(&U, no_proxy_to_override_conversion)) { + BKE_lib_override_library_main_proxy_convert(bmain, reports); + } + bmain->recovered = 0; /* startup.blend or recovered startup */ diff --git a/source/blender/blenkernel/intern/callbacks.c b/source/blender/blenkernel/intern/callbacks.c index 11ee9492b44..87d5961b12e 100644 --- a/source/blender/blenkernel/intern/callbacks.c +++ b/source/blender/blenkernel/intern/callbacks.c @@ -80,6 +80,15 @@ void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt) BLI_addtail(lb, funcstore); } +void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt) +{ + ListBase *lb = &callback_slots[evt]; + BLI_remlink(lb, funcstore); + if (funcstore->alloc) { + MEM_freeN(funcstore); + } +} + void BKE_callback_global_init(void) { /* do nothing */ @@ -95,10 +104,7 @@ void BKE_callback_global_finalize(void) bCallbackFuncStore *funcstore_next; for (funcstore = lb->first; funcstore; funcstore = funcstore_next) { funcstore_next = funcstore->next; - BLI_remlink(lb, funcstore); - if (funcstore->alloc) { - MEM_freeN(funcstore); - } + BKE_callback_remove(funcstore, evt); } } } diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c index 2d172f23428..8e50b9e9534 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -597,7 +597,7 @@ static Collection *collection_duplicate_recursive(Main *bmain, } else if (collection_old->id.newid == NULL) { collection_new = (Collection *)BKE_id_copy_for_duplicate( - bmain, (ID *)collection_old, duplicate_flags); + bmain, (ID *)collection_old, duplicate_flags, LIB_ID_COPY_DEFAULT); if (collection_new == collection_old) { return collection_new; diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c index f2c2e552a9f..62b817487fc 100644 --- a/source/blender/blenkernel/intern/colortools.c +++ b/source/blender/blenkernel/intern/colortools.c @@ -1212,6 +1212,20 @@ void BKE_curvemapping_init(CurveMapping *cumap) } } +void BKE_curvemapping_table_F(const CurveMapping *cumap, float **array, int *size) +{ + int a; + + *size = CM_TABLE + 1; + *array = MEM_callocN(sizeof(float) * (*size) * 4, "CurveMapping"); + + for (a = 0; a < *size; a++) { + if (cumap->cm[0].table) { + (*array)[a * 4 + 0] = cumap->cm[0].table[a].y; + } + } +} + void BKE_curvemapping_table_RGBA(const CurveMapping *cumap, float **array, int *size) { int a; diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index b9b15eba6a4..b2b03d28483 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -3499,7 +3499,7 @@ static void stretchto_new_data(void *cdata) bStretchToConstraint *data = (bStretchToConstraint *)cdata; data->volmode = 0; - data->plane = 0; + data->plane = SWING_Y; data->orglength = 0.0; data->bulge = 1.0; data->bulge_max = 1.0f; diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c index b0d196b2bb0..0dcfea78ca5 100644 --- a/source/blender/blenkernel/intern/curve.c +++ b/source/blender/blenkernel/intern/curve.c @@ -404,6 +404,7 @@ void BKE_curve_init(Curve *cu, const short curve_type) } else if (cu->type == OB_SURF) { cu->flag |= CU_3D; + cu->resolu = 4; cu->resolv = 4; } cu->bevel_profile = NULL; diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index ad2d5d267d5..3bb02e1856b 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -1856,6 +1856,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { NULL, NULL, NULL}, + /* 51: CD_HAIRLENGTH */ + {sizeof(float), "float", 1, NULL, NULL, NULL, NULL, NULL, NULL}, }; static const char *LAYERTYPENAMES[CD_NUMTYPES] = { @@ -1912,6 +1914,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = { "CDPropFloat3", "CDPropFloat2", "CDPropBoolean", + "CDHairLength", }; const CustomData_MeshMasks CD_MASK_BAREMESH = { diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index e756daa1156..0776f3b9a68 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -261,7 +261,7 @@ bool BKE_displist_surfindex_get( return true; } -/* ****************** make displists ********************* */ +/* ****************** Make #DispList ********************* */ #ifdef __INTEL_COMPILER /* ICC with the optimization -02 causes crashes. */ # pragma intel optimization_level 1 @@ -1540,23 +1540,6 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, boundbox_displist_object(ob); } -void BKE_displist_make_curveTypes_forRender( - Depsgraph *depsgraph, const Scene *scene, Object *ob, ListBase *r_dispbase, Mesh **r_final) -{ - if (ob->runtime.curve_cache == nullptr) { - ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); - } - - if (ob->type == OB_SURF) { - evaluate_surface_object(depsgraph, scene, ob, true, r_dispbase, r_final); - } - else { - GeometrySet geometry_set = evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase); - MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); - *r_final = mesh_component.release(); - } -} - void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) { bool doit = false; diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index d75b3259148..9083c507160 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -317,7 +317,7 @@ static bool setError(DynamicPaintCanvasSettings *canvas, const char *string) static int dynamicPaint_surfaceNumOfPoints(DynamicPaintSurface *surface) { if (surface->format == MOD_DPAINT_SURFACE_F_PTEX) { - return 0; /* not supported atm */ + return 0; /* Not supported at the moment. */ } if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) { const Mesh *canvas_mesh = dynamicPaint_canvas_mesh_get(surface->canvas); @@ -1231,7 +1231,7 @@ void dynamicPaint_Modifier_copy(const struct DynamicPaintModifierData *pmd, /* copy existing surfaces */ for (surface = pmd->canvas->surfaces.first; surface; surface = surface->next) { DynamicPaintSurface *t_surface = dynamicPaint_createNewSurface(tpmd->canvas, NULL); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* TODO(sergey): Consider passing some tips to the surface * creation to avoid this allocate-and-free cache behavior. */ BKE_ptcache_free_list(&t_surface->ptcaches); diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 1324b37f39c..e272b71acb8 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -5094,7 +5094,7 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd, /* pointcache options */ BKE_ptcache_free_list(&(tfds->ptcaches[0])); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* Share the cache with the original object's modifier. */ tfmd->modifier.flag |= eModifierFlag_SharedCaches; tfds->point_cache[0] = fds->point_cache[0]; diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c index aa13f86523a..0e159418724 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/font.c @@ -34,6 +34,7 @@ #include "BLI_ghash.h" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_base_safe.h" #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_string_utf8.h" @@ -490,15 +491,15 @@ static void build_underline(Curve *cu, mul_v2_fl(bp[3].vec, font_size); } -static void buildchar(Curve *cu, - ListBase *nubase, - unsigned int character, - CharInfo *info, - float ofsx, - float ofsy, - float rot, - int charidx, - const float fsize) +void BKE_vfont_build_char(Curve *cu, + ListBase *nubase, + unsigned int character, + CharInfo *info, + float ofsx, + float ofsy, + float rot, + int charidx, + const float fsize) { VFontData *vfd = vfont_get_data(which_vfont(cu, info)); if (!vfd) { @@ -794,8 +795,8 @@ static bool vfont_to_curve(Object *ob, bool ok = false; const float font_size = cu->fsize * iter_data->scale_to_fit; const bool word_wrap = iter_data->word_wrap; - const float xof_scale = cu->xof / font_size; - const float yof_scale = cu->yof / font_size; + const float xof_scale = safe_divide(cu->xof, font_size); + const float yof_scale = safe_divide(cu->yof, font_size); int last_line = -1; /* Length of the text disregarding \n breaks. */ float current_line_length = 0.0f; @@ -889,7 +890,7 @@ static bool vfont_to_curve(Object *ob, linedist = cu->linedist; curbox = 0; - textbox_scale(&tb_scale, &cu->tb[curbox], 1.0f / font_size); + textbox_scale(&tb_scale, &cu->tb[curbox], safe_divide(1.0f, font_size)); use_textbox = (tb_scale.w != 0.0f); xof = MARGIN_X_MIN; @@ -1525,7 +1526,7 @@ static bool vfont_to_curve(Object *ob, } /* We do not want to see any character for \n or \r */ if (cha != '\n') { - buildchar(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size); + BKE_vfont_build_char(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size); } if ((info->flag & CU_CHINFO_UNDERLINE) && (cha != '\n')) { diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 7d0537178ef..73c628d3f0f 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -535,6 +535,9 @@ static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve) * array implementations try to make it workable in common situations. * \{ */ +/** + * Individual spans in \a data may be empty if that spline contains no data for the attribute. + */ template<typename T> static void point_attribute_materialize(Span<Span<T>> data, Span<int> offsets, @@ -546,7 +549,15 @@ static void point_attribute_materialize(Span<Span<T>> data, for (const int spline_index : data.index_range()) { const int offset = offsets[spline_index]; const int next_offset = offsets[spline_index + 1]; - r_span.slice(offset, next_offset - offset).copy_from(data[spline_index]); + + Span<T> src = data[spline_index]; + MutableSpan<T> dst = r_span.slice(offset, next_offset - offset); + if (src.is_empty()) { + dst.fill(T()); + } + else { + dst.copy_from(src); + } } } else { @@ -557,11 +568,20 @@ static void point_attribute_materialize(Span<Span<T>> data, } const int index_in_spline = dst_index - offsets[spline_index]; - r_span[dst_index] = data[spline_index][index_in_spline]; + Span<T> src = data[spline_index]; + if (src.is_empty()) { + r_span[dst_index] = T(); + } + else { + r_span[dst_index] = src[index_in_spline]; + } } } } +/** + * Individual spans in \a data may be empty if that spline contains no data for the attribute. + */ template<typename T> static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, Span<int> offsets, @@ -574,7 +594,14 @@ static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, for (const int spline_index : data.index_range()) { const int offset = offsets[spline_index]; const int next_offset = offsets[spline_index + 1]; - uninitialized_copy_n(data[spline_index].data(), next_offset - offset, dst + offset); + + Span<T> src = data[spline_index]; + if (src.is_empty()) { + uninitialized_fill_n(dst + offset, next_offset - offset, T()); + } + else { + uninitialized_copy_n(src.data(), next_offset - offset, dst + offset); + } } } else { @@ -585,7 +612,13 @@ static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, } const int index_in_spline = dst_index - offsets[spline_index]; - new (dst + dst_index) T(data[spline_index][index_in_spline]); + Span<T> src = data[spline_index]; + if (src.is_empty()) { + new (dst + dst_index) T(); + } + else { + new (dst + dst_index) T(src[index_in_spline]); + } } } } @@ -769,6 +802,169 @@ class VMutableArray_For_SplinePosition final : public VMutableArray<float3> { } }; +class VArray_For_BezierHandle final : public VArray<float3> { + private: + Span<SplinePtr> splines_; + Array<int> offsets_; + bool is_right_; + + public: + VArray_For_BezierHandle(Span<SplinePtr> splines, Array<int> offsets, const bool is_right) + : VArray<float3>(offsets.last()), + splines_(std::move(splines)), + offsets_(std::move(offsets)), + is_right_(is_right) + { + } + + static float3 get_internal(const int64_t index, + Span<SplinePtr> splines, + Span<int> offsets, + const bool is_right) + { + const PointIndices indices = lookup_point_indices(offsets, index); + const Spline &spline = *splines[indices.spline_index]; + if (spline.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(spline); + return is_right ? bezier_spline.handle_positions_right()[indices.point_index] : + bezier_spline.handle_positions_left()[indices.point_index]; + } + return float3(0); + } + + float3 get_impl(const int64_t index) const final + { + return get_internal(index, splines_, offsets_, is_right_); + } + + /** + * Utility so we can pass handle positions to the materialize functions above. + * + * \note This relies on the ability of the materialize implementations to + * handle empty spans, since only Bezier splines have handles. + */ + static Array<Span<float3>> get_handle_spans(Span<SplinePtr> splines, const bool is_right) + { + Array<Span<float3>> spans(splines.size()); + for (const int i : spans.index_range()) { + if (splines[i]->type() == Spline::Type::Bezier) { + BezierSpline &bezier_spline = static_cast<BezierSpline &>(*splines[i]); + spans[i] = is_right ? bezier_spline.handle_positions_right() : + bezier_spline.handle_positions_left(); + } + else { + spans[i] = {}; + } + } + return spans; + } + + static void materialize_internal(const IndexMask mask, + Span<SplinePtr> splines, + Span<int> offsets, + const bool is_right, + MutableSpan<float3> r_span) + { + Array<Span<float3>> spans = get_handle_spans(splines, is_right); + point_attribute_materialize(spans.as_span(), offsets, mask, r_span); + } + + static void materialize_to_uninitialized_internal(const IndexMask mask, + Span<SplinePtr> splines, + Span<int> offsets, + const bool is_right, + MutableSpan<float3> r_span) + { + Array<Span<float3>> spans = get_handle_spans(splines, is_right); + point_attribute_materialize_to_uninitialized(spans.as_span(), offsets, mask, r_span); + } + + void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final + { + materialize_internal(mask, splines_, offsets_, is_right_, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, + MutableSpan<float3> r_span) const final + { + materialize_to_uninitialized_internal(mask, splines_, offsets_, is_right_, r_span); + } +}; + +class VMutableArray_For_BezierHandles final : public VMutableArray<float3> { + private: + MutableSpan<SplinePtr> splines_; + Array<int> offsets_; + bool is_right_; + + public: + VMutableArray_For_BezierHandles(MutableSpan<SplinePtr> splines, + Array<int> offsets, + const bool is_right) + : VMutableArray<float3>(offsets.last()), + splines_(splines), + offsets_(std::move(offsets)), + is_right_(is_right) + { + } + + float3 get_impl(const int64_t index) const final + { + return VArray_For_BezierHandle::get_internal(index, splines_, offsets_, is_right_); + } + + void set_impl(const int64_t index, float3 value) final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + Spline &spline = *splines_[indices.spline_index]; + if (spline.type() == Spline::Type::Bezier) { + BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline); + if (is_right_) { + bezier_spline.set_handle_position_right(indices.point_index, value); + } + else { + bezier_spline.set_handle_position_left(indices.point_index, value); + } + bezier_spline.mark_cache_invalid(); + } + } + + void set_all_impl(Span<float3> src) final + { + for (const int spline_index : splines_.index_range()) { + Spline &spline = *splines_[spline_index]; + if (spline.type() == Spline::Type::Bezier) { + const int offset = offsets_[spline_index]; + + BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline); + if (is_right_) { + for (const int i : IndexRange(bezier_spline.size())) { + bezier_spline.set_handle_position_right(i, src[offset + i]); + } + } + else { + for (const int i : IndexRange(bezier_spline.size())) { + bezier_spline.set_handle_position_left(i, src[offset + i]); + } + } + bezier_spline.mark_cache_invalid(); + } + } + } + + void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final + { + VArray_For_BezierHandle::materialize_internal(mask, splines_, offsets_, is_right_, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, + MutableSpan<float3> r_span) const final + { + VArray_For_BezierHandle::materialize_to_uninitialized_internal( + mask, splines_, offsets_, is_right_, r_span); + } +}; + /** * Provider for any builtin control point attribute that doesn't need * special handling like access to other arrays in the spline. @@ -906,6 +1102,78 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProvider<flo } }; +class BezierHandleAttributeProvider : public BuiltinAttributeProvider { + private: + bool is_right_; + + public: + BezierHandleAttributeProvider(const bool is_right) + : BuiltinAttributeProvider(is_right ? "handle_right" : "handle_left", + ATTR_DOMAIN_POINT, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable), + is_right_(is_right) + { + } + + GVArrayPtr try_get_for_read(const GeometryComponent &component) const override + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return {}; + } + + if (!curve->has_spline_with_type(Spline::Type::Bezier)) { + return {}; + } + + Array<int> offsets = curve->control_point_offsets(); + return std::make_unique<fn::GVArray_For_EmbeddedVArray<float3, VArray_For_BezierHandle>>( + offsets.last(), curve->splines(), std::move(offsets), is_right_); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + + if (!curve->has_spline_with_type(Spline::Type::Bezier)) { + return {}; + } + + Array<int> offsets = curve->control_point_offsets(); + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_BezierHandles>>( + offsets.last(), curve->splines(), std::move(offsets), is_right_); + } + + bool try_delete(GeometryComponent &UNUSED(component)) const final + { + return false; + } + + bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const final + { + return false; + } + + bool exists(const GeometryComponent &component) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return false; + } + + return curve->has_spline_with_type(Spline::Type::Bezier) && + component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0; + } +}; + /** \} */ /* -------------------------------------------------------------------- */ @@ -1196,6 +1464,8 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() spline_custom_data_access); static PositionAttributeProvider position; + static BezierHandleAttributeProvider handles_start(false); + static BezierHandleAttributeProvider handles_end(true); static BuiltinPointAttributeProvider<float> radius( "radius", @@ -1213,8 +1483,9 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() static DynamicPointAttributeProvider point_custom_data; - return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, - {&spline_custom_data, &point_custom_data}); + return ComponentAttributeProviders( + {&position, &radius, &tilt, &handles_start, &handles_end, &resolution, &cyclic}, + {&spline_custom_data, &point_custom_data}); } } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 9479d012cb8..4204d62e1a7 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -14,11 +14,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include <mutex> + #include "BLI_float4x4.hh" #include "BLI_map.hh" #include "BLI_rand.hh" #include "BLI_set.hh" #include "BLI_span.hh" +#include "BLI_task.hh" #include "BLI_vector.hh" #include "DNA_collection_types.h" @@ -122,44 +125,14 @@ blender::Span<int> InstancesComponent::instance_ids() const } /** - * If references have a collection or object type, convert them into geometry instances. This - * will join geometry components from nested instances if necessary. After that, the geometry - * sets can be edited. - */ -void InstancesComponent::ensure_geometry_instances() -{ - VectorSet<InstanceReference> new_references; - new_references.reserve(references_.size()); - for (const InstanceReference &reference : references_) { - if (reference.type() == InstanceReference::Type::Object) { - GeometrySet geometry_set; - InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); - const int handle = instances.add_reference(reference.object()); - instances.add_instance(handle, float4x4::identity()); - new_references.add_new(geometry_set); - } - else if (reference.type() == InstanceReference::Type::Collection) { - GeometrySet geometry_set; - InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); - const int handle = instances.add_reference(reference.collection()); - instances.add_instance(handle, float4x4::identity()); - new_references.add_new(geometry_set); - } - else { - new_references.add_new(reference); - } - } - references_ = std::move(new_references); -} - -/** * With write access to the instances component, the data in the instanced geometry sets can be * changed. This is a function on the component rather than each reference to ensure `const` * correctness for that reason. */ GeometrySet &InstancesComponent::geometry_set_from_reference(const int reference_index) { - /* If this assert fails, it means #ensure_geometry_instances must be called first. */ + /* If this assert fails, it means #ensure_geometry_instances must be called first or that the + * reference can't be converted to a geometry set. */ BLI_assert(references_[reference_index].type() == InstanceReference::Type::GeometrySet); /* The const cast is okay because the instance's hash in the set @@ -182,6 +155,86 @@ blender::Span<InstanceReference> InstancesComponent::references() const return references_; } +void InstancesComponent::remove_unused_references() +{ + using namespace blender; + using namespace blender::bke; + + const int tot_instances = this->instances_amount(); + const int tot_references_before = references_.size(); + + if (tot_instances == 0) { + /* If there are no instances, no reference is needed. */ + references_.clear(); + return; + } + if (tot_references_before == 1) { + /* There is only one reference and at least one instance. So the only existing reference is + * used. Nothing to do here. */ + return; + } + + Array<bool> usage_by_handle(tot_references_before, false); + std::mutex mutex; + + /* Loop over all instances to see which references are used. */ + threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) { + /* Use local counter to avoid lock contention. */ + Array<bool> local_usage_by_handle(tot_references_before, false); + + for (const int i : range) { + const int handle = instance_reference_handles_[i]; + BLI_assert(handle >= 0 && handle < tot_references_before); + local_usage_by_handle[handle] = true; + } + + std::lock_guard lock{mutex}; + for (const int i : IndexRange(tot_references_before)) { + usage_by_handle[i] |= local_usage_by_handle[i]; + } + }); + + if (!usage_by_handle.as_span().contains(false)) { + /* All references are used. */ + return; + } + + /* Create new references and a mapping for the handles. */ + Vector<int> handle_mapping; + VectorSet<InstanceReference> new_references; + int next_new_handle = 0; + bool handles_have_to_be_updated = false; + for (const int old_handle : IndexRange(tot_references_before)) { + if (!usage_by_handle[old_handle]) { + /* Add some dummy value. It won't be read again. */ + handle_mapping.append(-1); + } + else { + const InstanceReference &reference = references_[old_handle]; + handle_mapping.append(next_new_handle); + new_references.add_new(reference); + if (old_handle != next_new_handle) { + handles_have_to_be_updated = true; + } + next_new_handle++; + } + } + references_ = new_references; + + if (!handles_have_to_be_updated) { + /* All remaining handles are the same as before, so they don't have to be updated. This happens + * when unused handles are only at the end. */ + return; + } + + /* Update handles of instances. */ + threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) { + for (const int i : range) { + instance_reference_handles_[i] = handle_mapping[instance_reference_handles_[i]]; + } + }); +} + int InstancesComponent::instances_amount() const { return instance_transforms_.size(); diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index e717d289894..0aac6ae3adf 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -15,6 +15,7 @@ */ #include "BLI_map.hh" +#include "BLI_task.hh" #include "BKE_attribute.h" #include "BKE_attribute_access.hh" @@ -151,6 +152,19 @@ void GeometrySet::remove(const GeometryComponentType component_type) components_.remove(component_type); } +/** + * Remove all geometry components with types that are not in the provided list. + */ +void GeometrySet::keep_only(const blender::Span<GeometryComponentType> component_types) +{ + for (auto it = components_.keys().begin(); it != components_.keys().end(); ++it) { + const GeometryComponentType type = *it; + if (!component_types.contains(type)) { + components_.remove(it); + } + } +} + void GeometrySet::add(const GeometryComponent &component) { BLI_assert(!components_.contains(component.type())); @@ -291,6 +305,29 @@ bool GeometrySet::has_curve() const return component != nullptr && component->has_curve(); } +/* Returns true when the geometry set has any data that is not an instance. */ +bool GeometrySet::has_realized_data() const +{ + if (components_.is_empty()) { + return false; + } + if (components_.size() > 1) { + return true; + } + /* Check if the only component is an #InstancesComponent. */ + return this->get_component_for_read<InstancesComponent>() == nullptr; +} + +/* Return true if the geometry set has any component that isn't empty. */ +bool GeometrySet::is_empty() const +{ + if (components_.is_empty()) { + return true; + } + return !(this->has_mesh() || this->has_curve() || this->has_pointcloud() || + this->has_instances()); +} + /* Create a new geometry set that only contains the given mesh. */ GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -375,6 +412,108 @@ CurveEval *GeometrySet::get_curve_for_write() return component.get_for_write(); } +void GeometrySet::attribute_foreach(const Span<GeometryComponentType> component_types, + const bool include_instances, + const AttributeForeachCallback callback) const +{ + using namespace blender; + using namespace blender::bke; + for (const GeometryComponentType component_type : component_types) { + if (!this->has(component_type)) { + continue; + } + const GeometryComponent &component = *this->get_component_for_read(component_type); + component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + callback(attribute_id, meta_data, component); + return true; + }); + } + if (include_instances && this->has_instances()) { + const InstancesComponent &instances = *this->get_component_for_read<InstancesComponent>(); + instances.foreach_referenced_geometry([&](const GeometrySet &instance_geometry_set) { + instance_geometry_set.attribute_foreach(component_types, include_instances, callback); + }); + } +} + +void GeometrySet::gather_attributes_for_propagation( + const Span<GeometryComponentType> component_types, + const GeometryComponentType dst_component_type, + bool include_instances, + blender::Map<blender::bke::AttributeIDRef, AttributeKind> &r_attributes) const +{ + using namespace blender; + using namespace blender::bke; + /* Only needed right now to check if an attribute is built-in on this component type. + * TODO: Get rid of the dummy component. */ + const GeometryComponent *dummy_component = GeometryComponent::create(dst_component_type); + this->attribute_foreach( + component_types, + include_instances, + [&](const AttributeIDRef &attribute_id, + const AttributeMetaData &meta_data, + const GeometryComponent &component) { + if (component.attribute_is_builtin(attribute_id)) { + if (!dummy_component->attribute_is_builtin(attribute_id)) { + /* Don't propagate built-in attributes that are not built-in on the destination + * component. */ + return; + } + } + if (attribute_id.is_anonymous()) { + if (!BKE_anonymous_attribute_id_has_strong_references(&attribute_id.anonymous_id())) { + /* Don't propagate anonymous attributes that are not used anymore. */ + return; + } + } + auto add_info = [&](AttributeKind *attribute_kind) { + attribute_kind->domain = meta_data.domain; + attribute_kind->data_type = meta_data.data_type; + }; + auto modify_info = [&](AttributeKind *attribute_kind) { + attribute_kind->domain = bke::attribute_domain_highest_priority( + {attribute_kind->domain, meta_data.domain}); + attribute_kind->data_type = bke::attribute_data_type_highest_complexity( + {attribute_kind->data_type, meta_data.data_type}); + }; + r_attributes.add_or_modify(attribute_id, add_info, modify_info); + }); + delete dummy_component; +} + +static void gather_mutable_geometry_sets(GeometrySet &geometry_set, + Vector<GeometrySet *> &r_geometry_sets) +{ + r_geometry_sets.append(&geometry_set); + if (!geometry_set.has_instances()) { + return; + } + /* In the future this can be improved by deduplicating instance references across different + * instances. */ + InstancesComponent &instances_component = + geometry_set.get_component_for_write<InstancesComponent>(); + instances_component.ensure_geometry_instances(); + for (const int handle : instances_component.references().index_range()) { + if (instances_component.references()[handle].type() == InstanceReference::Type::GeometrySet) { + GeometrySet &instance_geometry = instances_component.geometry_set_from_reference(handle); + gather_mutable_geometry_sets(instance_geometry, r_geometry_sets); + } + } +} + +/** + * Modify every (recursive) instance separately. This is often more efficient than realizing all + * instances just to change the same thing on all of them. + */ +void GeometrySet::modify_geometry_sets(ForeachSubGeometryCallback callback) +{ + Vector<GeometrySet *> geometry_sets; + gather_mutable_geometry_sets(*this, geometry_sets); + blender::threading::parallel_for_each( + geometry_sets, [&](GeometrySet *geometry_set) { callback(*geometry_set); }); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 9dca2c2907e..77348c3d22c 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -14,6 +14,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_collection.h" #include "BKE_geometry_set_instances.hh" #include "BKE_material.h" #include "BKE_mesh.h" @@ -23,6 +24,7 @@ #include "BKE_spline.hh" #include "DNA_collection_types.h" +#include "DNA_layer_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" @@ -187,134 +189,6 @@ void geometry_set_gather_instances(const GeometrySet &geometry_set, geometry_set_collect_recursive(geometry_set, unit_transform, r_instance_groups); } -static bool collection_instance_attribute_foreach(const Collection &collection, - const AttributeForeachCallback callback, - const int limit, - int &count); - -static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit, - int &count); - -static bool object_instance_attribute_foreach(const Object &object, - const AttributeForeachCallback callback, - const int limit, - int &count) -{ - GeometrySet instance_geometry_set = object_get_geometry_set_for_read(object); - if (!instances_attribute_foreach_recursive(instance_geometry_set, callback, limit, count)) { - return false; - } - - if (object.type == OB_EMPTY) { - const Collection *collection_instance = object.instance_collection; - if (collection_instance != nullptr) { - if (!collection_instance_attribute_foreach(*collection_instance, callback, limit, count)) { - return false; - } - } - } - return true; -} - -static bool collection_instance_attribute_foreach(const Collection &collection, - const AttributeForeachCallback callback, - const int limit, - int &count) -{ - LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) { - BLI_assert(collection_object->ob != nullptr); - const Object &object = *collection_object->ob; - if (!object_instance_attribute_foreach(object, callback, limit, count)) { - return false; - } - } - LISTBASE_FOREACH (const CollectionChild *, collection_child, &collection.children) { - BLI_assert(collection_child->collection != nullptr); - const Collection &collection = *collection_child->collection; - if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { - return false; - } - } - return true; -} - -/** - * \return True if the recursive iteration should continue, false if the limit is reached or the - * callback has returned false indicating it should stop. - */ -static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit, - int &count) -{ - for (const GeometryComponent *component : geometry_set.get_components_for_read()) { - if (!component->attribute_foreach(callback)) { - return false; - } - } - - /* Now that this geometry set is visited, increase the count and check with the limit. */ - if (limit > 0 && count++ > limit) { - return false; - } - - const InstancesComponent *instances_component = - geometry_set.get_component_for_read<InstancesComponent>(); - if (instances_component == nullptr) { - return true; - } - - for (const InstanceReference &reference : instances_component->references()) { - switch (reference.type()) { - case InstanceReference::Type::Object: { - const Object &object = reference.object(); - if (!object_instance_attribute_foreach(object, callback, limit, count)) { - return false; - } - break; - } - case InstanceReference::Type::Collection: { - const Collection &collection = reference.collection(); - if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { - return false; - } - break; - } - case InstanceReference::Type::GeometrySet: { - const GeometrySet &geometry_set = reference.geometry_set(); - if (!instances_attribute_foreach_recursive(geometry_set, callback, limit, count)) { - return false; - } - break; - } - case InstanceReference::Type::None: { - break; - } - } - } - - return true; -} - -/** - * Call the callback on all of this geometry set's components, including geometry sets from - * instances and recursive instances. This is necessary to access available attributes without - * making all of the set's geometry real. - * - * \param limit: The total number of geometry sets to visit before returning early. This is used - * to avoid looking through too many geometry sets recursively, as an explicit tradeoff in favor - * of performance at the cost of visiting every unique attribute. - */ -void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit) -{ - int count = 0; - instances_attribute_foreach_recursive(geometry_set, callback, limit, count); -} - void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, Span<GeometryComponentType> component_types, const Set<std::string> &ignored_attributes, @@ -700,7 +574,7 @@ static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, G geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_CURVE}, - {"position", "radius", "tilt", "cyclic", "resolution"}, + {"position", "radius", "tilt", "handle_left", "handle_right", "cyclic", "resolution"}, attributes); join_attributes(set_groups, {GEO_COMPONENT_TYPE_CURVE}, @@ -745,3 +619,91 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set) } } // namespace blender::bke + +void InstancesComponent::foreach_referenced_geometry( + blender::FunctionRef<void(const GeometrySet &geometry_set)> callback) const +{ + using namespace blender::bke; + for (const InstanceReference &reference : references_) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + const Object &object = reference.object(); + const GeometrySet object_geometry_set = object_get_geometry_set_for_read(object); + callback(object_geometry_set); + break; + } + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) { + const GeometrySet object_geometry_set = object_get_geometry_set_for_read(*object); + callback(object_geometry_set); + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + break; + } + case InstanceReference::Type::GeometrySet: { + const GeometrySet &instance_geometry_set = reference.geometry_set(); + callback(instance_geometry_set); + break; + } + case InstanceReference::Type::None: { + break; + } + } + } +} + +/** + * If references have a collection or object type, convert them into geometry instances + * recursively. After that, the geometry sets can be edited. There may still be instances of other + * types of they can't be converted to geometry sets. + */ +void InstancesComponent::ensure_geometry_instances() +{ + using namespace blender; + using namespace blender::bke; + VectorSet<InstanceReference> new_references; + new_references.reserve(references_.size()); + for (const InstanceReference &reference : references_) { + switch (reference.type()) { + case InstanceReference::Type::None: + case InstanceReference::Type::GeometrySet: { + /* Those references can stay as their were. */ + new_references.add_new(reference); + break; + } + case InstanceReference::Type::Object: { + /* Create a new reference that contains the geometry set of the object. We may want to + * treat e.g. lamps and similar object types separately here. */ + const Object &object = reference.object(); + GeometrySet object_geometry_set = object_get_geometry_set_for_read(object); + if (object_geometry_set.has_instances()) { + InstancesComponent &component = + object_geometry_set.get_component_for_write<InstancesComponent>(); + component.ensure_geometry_instances(); + } + new_references.add_new(std::move(object_geometry_set)); + break; + } + case InstanceReference::Type::Collection: { + /* Create a new reference that contains a geometry set that contains all objects from the + * collection as instances. */ + GeometrySet collection_geometry_set; + InstancesComponent &component = + collection_geometry_set.get_component_for_write<InstancesComponent>(); + Collection &collection = reference.collection(); + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) { + const int handle = component.add_reference(*object); + component.add_instance(handle, object->obmat); + float4x4 &transform = component.instance_transforms().last(); + sub_v3_v3(transform.values[3], collection.instance_offset); + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + component.ensure_geometry_instances(); + new_references.add_new(std::move(collection_geometry_set)); + break; + } + } + } + references_ = std::move(new_references); +} diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 82a44afbbb1..ed84694a919 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -319,7 +319,7 @@ IDTypeInfo IDType_ID_GD = { .name = "GPencil", .name_plural = "grease_pencils", .translation_context = BLT_I18NCONTEXT_ID_GPENCIL, - .flags = 0, + .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, .init_data = NULL, .copy_data = greasepencil_copy_data, diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 0752424df71..3819c0699f4 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -543,7 +543,7 @@ void BKE_gpencil_convert_curve(Main *bmain, int actcol = ob_gp->actcol; for (int slot = 1; slot <= ob_gp->totcol; slot++) { - while (slot <= ob_gp->totcol && !BKE_object_material_slot_used(ob_gp->data, slot)) { + while (slot <= ob_gp->totcol && !BKE_object_material_slot_used(ob_gp, slot)) { ob_gp->actcol = slot; BKE_object_material_slot_remove(bmain, ob_gp); diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc index 976b26a1f3a..debdf44b0bb 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.cc +++ b/source/blender/blenkernel/intern/gpencil_geom.cc @@ -738,8 +738,8 @@ bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, sub_v3_v3v3(vec1, &gps->points[start_i].x, &gps->points[start_i + dir_i].x); /* In general curvature = 1/radius. For the case without the - * weights introduced by #segment_influence, the calculation is - * curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length */ + * weights introduced by #segment_influence, the calculation is: + * `curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length` */ float curvature = normalize_v3(total_angle) / overshoot_length; /* Compensate for the weights powf(added_len, segment_influence). */ curvature /= powf(overshoot_length / fminf(overshoot_parameter, (float)j), segment_influence); diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index 6be03bffb3c..a6164340477 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -65,7 +65,7 @@ static CLG_LogRef LOG = {"bke.gpencil_modifier"}; static GpencilModifierTypeInfo *modifier_gpencil_types[NUM_GREASEPENCIL_MODIFIER_TYPES] = {NULL}; #if 0 -/* Note that GPencil actually does not support these atm, but might do in the future. */ +/* Note that GPencil actually does not support these at the moment, but might do in the future. */ static GpencilVirtualModifierData virtualModifierCommonData; #endif @@ -129,7 +129,8 @@ GpencilModifierData *BKE_gpencil_modifiers_get_virtual_modifierlist( GpencilModifierData *md = ob->greasepencil_modifiers.first; #if 0 - /* Note that GPencil actually does not support these atm, but might do in the future. */ + /* Note that GPencil actually does not support these at the moment, + * but might do in the future. */ *virtualModifierData = virtualModifierCommonData; if (ob->parent) { if (ob->parent->type == OB_ARMATURE && ob->partype == PARSKEL) { @@ -328,8 +329,9 @@ void BKE_gpencil_modifier_init(void) gpencil_modifier_type_init(modifier_gpencil_types); /* MOD_gpencil_util.c */ #if 0 - /* Note that GPencil actually does not support these atm, but might do in the future. */ - /* Initialize global cmmon storage used for virtual modifier list */ + /* Note that GPencil actually does not support these at the moment, + * but might do in the future. */ + /* Initialize global common storage used for virtual modifier list. */ GpencilModifierData *md; md = BKE_gpencil_modifier_new(eGpencilModifierType_Armature); virtualModifierCommonData.amd = *((ArmatureGpencilModifierData *)md); @@ -518,7 +520,7 @@ static void gpencil_modifier_copy_data_id_us_cb(void *UNUSED(userData), * Copy grease pencil modifier data. * \param md: Source modifier data * \param target: Target modifier data - * \parm flag: Flags + * \param flag: Flags */ void BKE_gpencil_modifier_copydata_ex(GpencilModifierData *md, GpencilModifierData *target, diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c index 9b72a2d1a72..26a1240080f 100644 --- a/source/blender/blenkernel/intern/ipo.c +++ b/source/blender/blenkernel/intern/ipo.c @@ -2013,7 +2013,8 @@ static void nlastrips_to_animdata(ID *id, ListBase *strips) } } - /* try to add this strip to the current NLA-Track (i.e. the 'last' one on the stack atm) */ + /* Try to add this strip to the current NLA-Track + * (i.e. the 'last' one on the stack at the moment). */ if (BKE_nlatrack_add_strip(nlt, strip, false) == 0) { /* trying to add to the current failed (no space), * so add a new track to the stack, and add to that... diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index 44fc86877a7..c09fcf0715e 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -1904,7 +1904,7 @@ KeyBlock *BKE_keyblock_add_ctime(Key *key, const char *name, const bool do_force return kb; } -/* only the active keyblock */ +/* Only the active key-block. */ KeyBlock *BKE_keyblock_from_object(Object *ob) { Key *key = BKE_key_from_object(ob); @@ -2247,7 +2247,7 @@ void BKE_keyblock_convert_to_mesh(KeyBlock *kb, Mesh *me) * Computes normals (vertices, polygons and/or loops ones) of given mesh for given shape key. * * \param kb: the KeyBlock to use to compute normals. - * \param mesh: the Mesh to apply keyblock to. + * \param mesh: the Mesh to apply key-block to. * \param r_vertnors: if non-NULL, an array of vectors, same length as number of vertices. * \param r_polynors: if non-NULL, an array of vectors, same length as number of polygons. * \param r_loopnors: if non-NULL, an array of vectors, same length as number of loops. @@ -2345,7 +2345,7 @@ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*ve return; } - /* Copy coords to keyblock */ + /* Copy coords to key-block. */ if (ELEM(ob->type, OB_MESH, OB_LATTICE)) { for (a = 0; a < tot; a++, fp += 3, co++) { copy_v3_v3(fp, *co); @@ -2405,7 +2405,7 @@ void BKE_keyblock_convert_from_vertcos(Object *ob, KeyBlock *kb, const float (*v kb->data = MEM_mallocN(tot * elemsize, __func__); - /* Copy coords to keyblock */ + /* Copy coords to key-block. */ BKE_keyblock_update_from_vertcos(ob, kb, vertCos); } @@ -2594,7 +2594,7 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index) } /** - * Check if given keyblock (as index) is used as basis by others in given key. + * Check if given key-block (as index) is used as basis by others in given key. */ bool BKE_keyblock_is_basis(Key *key, const int index) { diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 18824e73ee5..3b2d2c5d2c3 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -674,7 +674,10 @@ ID *BKE_id_copy(Main *bmain, const ID *id) * Invokes the appropriate copy method for the block and returns the result in * newid, unless test. Returns true if the block can be copied. */ -ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplicate_flags) +ID *BKE_id_copy_for_duplicate(Main *bmain, + ID *id, + const eDupli_ID_Flags duplicate_flags, + const int copy_flags) { if (id == NULL) { return id; @@ -685,7 +688,7 @@ ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplica return id; } - ID *id_new = BKE_id_copy(bmain, id); + ID *id_new = BKE_id_copy_ex(bmain, id, NULL, copy_flags); /* Copying add one user by default, need to get rid of that one. */ id_us_min(id_new); ID_NEW_SET(id, id_new); diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index c60a9104144..68675e5fc91 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -1000,9 +1000,92 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain, DEG_id_tag_update(&ob_proxy->id, ID_RECALC_COPY_ON_WRITE); + /* In case of proxy conversion, remap all local ID usages to linked IDs to their newly created + * overrides. + * While this might not be 100% the desired behavior, it is likely to be the case most of the + * time. Ref: T91711. */ + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { + if (!ID_IS_LINKED(id_iter)) { + id_iter->tag |= LIB_TAG_DOIT; + } + } + FOREACH_MAIN_ID_END; + return BKE_lib_override_library_create(bmain, scene, view_layer, id_root, id_reference, NULL); } +static void lib_override_library_proxy_convert_do(Main *bmain, + Scene *scene, + Object *ob_proxy, + BlendFileReadReport *reports) +{ + Object *ob_proxy_group = ob_proxy->proxy_group; + const bool is_override_instancing_object = ob_proxy_group != NULL; + + const bool success = BKE_lib_override_library_proxy_convert(bmain, scene, NULL, ob_proxy); + + if (success) { + CLOG_INFO(&LOG, + 4, + "Proxy object '%s' successfuly converted to library overrides", + ob_proxy->id.name); + /* Remove the instance empty from this scene, the items now have an overridden collection + * instead. */ + if (is_override_instancing_object) { + BKE_scene_collections_object_remove(bmain, scene, ob_proxy_group, true); + } + reports->count.proxies_to_lib_overrides_success++; + } +} + +/** + * Convert all proxy objects into library overrides. + * + * \note Only affects local proxies, linked ones are not affected. + * + * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in + * which case \a scene's master collection children hierarchy is used instead). + */ +void BKE_lib_override_library_main_proxy_convert(Main *bmain, BlendFileReadReport *reports) +{ + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + FOREACH_SCENE_OBJECT_BEGIN (scene, object) { + if (object->proxy_group == NULL) { + continue; + } + + lib_override_library_proxy_convert_do(bmain, scene, object, reports); + } + FOREACH_SCENE_OBJECT_END; + + FOREACH_SCENE_OBJECT_BEGIN (scene, object) { + if (object->proxy == NULL) { + continue; + } + + lib_override_library_proxy_convert_do(bmain, scene, object, reports); + } + FOREACH_SCENE_OBJECT_END; + } + + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (ID_IS_LINKED(object)) { + if (object->proxy != NULL) { + CLOG_WARN(&LOG, "Did not try to convert linked proxy object '%s'", object->id.name); + reports->count.linked_proxies++; + } + continue; + } + + if (object->proxy_group != NULL || object->proxy != NULL) { + CLOG_WARN( + &LOG, "Proxy object '%s' failed to be converted to library override", object->id.name); + reports->count.proxies_to_lib_overrides_failures++; + } + } +} + /** * Advanced 'smart' function to resync, re-create fully functional overrides up-to-date with linked * data, from an existing override hierarchy. @@ -2889,6 +2972,31 @@ void BKE_lib_override_library_main_update(Main *bmain) G_MAIN = orig_gmain; } +/** In case an ID is used by another liboverride ID, user may not be allowed to delete it. */ +bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID *id) +{ + if (!(ID_IS_LINKED(id) || ID_IS_OVERRIDE_LIBRARY(id))) { + return true; + } + + /* The only strong known case currently are objects used by override collections. */ + /* TODO: There are most likely other cases... This may need to be addressed in a better way at + * some point. */ + if (GS(id->name) != ID_OB) { + return true; + } + Object *ob = (Object *)id; + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + if (!ID_IS_OVERRIDE_LIBRARY(collection)) { + continue; + } + if (BKE_collection_has_object(collection, ob)) { + return false; + } + } + return true; +} + /** * Storage (how to store overriding data into `.blend` files). * diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 250b8d4d515..48396c5e6d9 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -345,7 +345,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o static void libblock_remap_data_postprocess_nodetree_update(Main *bmain, ID *new_id) { /* Update all group nodes using a node group. */ - ntreeUpdateAllUsers(bmain, new_id); + ntreeUpdateAllUsers(bmain, new_id, 0); } /** diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index 26dcadcc77b..9c3291edbcc 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -485,6 +485,7 @@ void BKE_main_library_weak_reference_add_item(GHash *library_weak_reference_mapp const bool already_exist_in_mapping = BLI_ghash_ensure_p( library_weak_reference_mapping, key, &id_p); BLI_assert(!already_exist_in_mapping); + UNUSED_VARS_NDEBUG(already_exist_in_mapping); BLI_strncpy(new_id->library_weak_reference->library_filepath, library_filepath, diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 5f53d5e1ae8..fa3fbd457d1 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -46,6 +46,7 @@ #include "DNA_meta_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" +#include "DNA_particle_types.h" #include "DNA_pointcloud_types.h" #include "DNA_scene_types.h" #include "DNA_volume_types.h" @@ -73,6 +74,7 @@ #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_node.h" +#include "BKE_object.h" #include "BKE_scene.h" #include "DEG_depsgraph.h" @@ -462,21 +464,33 @@ static void material_data_index_remove_id(ID *id, short index) } } -bool BKE_object_material_slot_used(ID *id, short actcol) +bool BKE_object_material_slot_used(Object *object, short actcol) { - /* ensure we don't try get materials from non-obdata */ - BLI_assert(OB_DATA_SUPPORT_ID(GS(id->name))); + if (!BKE_object_supports_material_slots(object)) { + return false; + } - switch (GS(id->name)) { + LISTBASE_FOREACH (ParticleSystem *, psys, &object->particlesystem) { + if (psys->part->omat == actcol) { + return true; + } + } + + ID *ob_data = object->data; + if (ob_data == NULL || !OB_DATA_SUPPORT_ID(GS(ob_data->name))) { + return false; + } + + switch (GS(ob_data->name)) { case ID_ME: - return BKE_mesh_material_index_used((Mesh *)id, actcol - 1); + return BKE_mesh_material_index_used((Mesh *)ob_data, actcol - 1); case ID_CU: - return BKE_curve_material_index_used((Curve *)id, actcol - 1); + return BKE_curve_material_index_used((Curve *)ob_data, actcol - 1); case ID_MB: - /* meta-elems don't have materials atm */ + /* Meta-elements don't support materials at the moment. */ return false; case ID_GD: - return BKE_gpencil_material_index_used((bGPdata *)id, actcol - 1); + return BKE_gpencil_material_index_used((bGPdata *)ob_data, actcol - 1); default: return false; } diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c index 9dd583b4c6b..a2590171abd 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.c +++ b/source/blender/blenkernel/intern/mball_tessellate.c @@ -454,7 +454,7 @@ static void make_face(PROCESS *process, int i1, int i2, int i3, int i4) cur = process->indices[process->curindex++]; - /* displists now support array drawing, we treat tri's as fake quad */ + /* #DispList supports array drawing, treat tri's as fake quad. */ cur[0] = i1; cur[1] = i2; diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 467f7d4543e..59cdb6a2b27 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -41,6 +41,7 @@ #include "BKE_deform.h" #include "BKE_displist.h" #include "BKE_editmesh.h" +#include "BKE_geometry_set.hh" #include "BKE_key.h" #include "BKE_lib_id.h" #include "BKE_lib_query.h" @@ -51,6 +52,7 @@ #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" +#include "BKE_spline.hh" /* these 2 are only used by conversion functions */ #include "BKE_curve.h" /* -- */ @@ -58,6 +60,8 @@ /* -- */ #include "BKE_pointcloud.h" +#include "BKE_curve_to_mesh.hh" + #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -237,7 +241,7 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu, int a, b, ofs, vertcount, startvert, totvert = 0, totedge = 0, totloop = 0, totpoly = 0; int p1, p2, p3, p4, *index; const bool conv_polys = ( - /* 2d polys are filled with DL_INDEX3 displists */ + /* 2D polys are filled with #DispList.type == #DL_INDEX3. */ (CU_DO_2DFILL(cu) == false) || /* surf polys are never filled */ BKE_curve_type_get(cu) == OB_SURF); @@ -573,90 +577,6 @@ Mesh *BKE_mesh_new_nomain_from_curve(const Object *ob) return BKE_mesh_new_nomain_from_curve_displist(ob, &disp); } -static void mesh_from_nurbs_displist(Object *ob, ListBase *dispbase, const char *obdata_name) -{ - if (ob->runtime.data_eval && GS(((ID *)ob->runtime.data_eval)->name) != ID_ME) { - return; - } - - Mesh *me_eval = (Mesh *)ob->runtime.data_eval; - Mesh *me; - MVert *allvert = nullptr; - MEdge *alledge = nullptr; - MLoop *allloop = nullptr; - MLoopUV *alluv = nullptr; - MPoly *allpoly = nullptr; - int totvert, totedge, totloop, totpoly; - - Curve *cu = (Curve *)ob->data; - - if (me_eval == nullptr) { - if (mesh_nurbs_displist_to_mdata(cu, - dispbase, - &allvert, - &totvert, - &alledge, - &totedge, - &allloop, - &allpoly, - &alluv, - &totloop, - &totpoly) != 0) { - /* Error initializing */ - return; - } - - /* make mesh */ - me = (Mesh *)BKE_id_new_nomain(ID_ME, obdata_name); - - me->totvert = totvert; - me->totedge = totedge; - me->totloop = totloop; - me->totpoly = totpoly; - - me->mvert = (MVert *)CustomData_add_layer( - &me->vdata, CD_MVERT, CD_ASSIGN, allvert, me->totvert); - me->medge = (MEdge *)CustomData_add_layer( - &me->edata, CD_MEDGE, CD_ASSIGN, alledge, me->totedge); - me->mloop = (MLoop *)CustomData_add_layer( - &me->ldata, CD_MLOOP, CD_ASSIGN, allloop, me->totloop); - me->mpoly = (MPoly *)CustomData_add_layer( - &me->pdata, CD_MPOLY, CD_ASSIGN, allpoly, me->totpoly); - - if (alluv) { - const char *uvname = "UVMap"; - me->mloopuv = (MLoopUV *)CustomData_add_layer_named( - &me->ldata, CD_MLOOPUV, CD_ASSIGN, alluv, me->totloop, uvname); - } - - BKE_mesh_calc_normals(me); - } - else { - me = (Mesh *)BKE_id_new_nomain(ID_ME, obdata_name); - - ob->runtime.data_eval = nullptr; - BKE_mesh_nomain_to_mesh(me_eval, me, ob, &CD_MASK_MESH, true); - } - - me->totcol = cu->totcol; - me->mat = cu->mat; - - mesh_copy_texture_space_from_curve_type(cu, me); - - cu->mat = nullptr; - cu->totcol = 0; - - /* Do not decrement ob->data usercount here, - * it's done at end of func with BKE_id_free_us() call. */ - ob->data = me; - ob->type = OB_MESH; - - /* For temporary objects in BKE_mesh_new_from_object don't remap - * the entire scene with associated depsgraph updates, which are - * problematic for renderers exporting data. */ - BKE_id_free(nullptr, cu); -} - struct EdgeLink { struct EdgeLink *next, *prev; void *edge; @@ -948,54 +868,32 @@ void BKE_pointcloud_to_mesh(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(sce BKE_object_free_derived_caches(ob); } -/* Create a temporary object to be used for nurbs-to-mesh conversion. - * - * This is more complex that it should be because #mesh_from_nurbs_displist will do more than - * simply conversion and will attempt to take over ownership of evaluated result and will also - * modify the input object. */ -static Object *object_for_curve_to_mesh_create(Object *object) +/* Create a temporary object to be used for nurbs-to-mesh conversion. */ +static Object *object_for_curve_to_mesh_create(const Object *object) { - Curve *curve = (Curve *)object->data; + const Curve *curve = (const Curve *)object->data; - /* Create object itself. */ + /* Create a temporary object which can be evaluated and modified by generic + * curve evaluation (hence the #LIB_ID_COPY_SET_COPIED_ON_WRITE flag). */ Object *temp_object = (Object *)BKE_id_copy_ex( - nullptr, &object->id, nullptr, LIB_ID_COPY_LOCALIZE); + nullptr, &object->id, nullptr, LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_SET_COPIED_ON_WRITE); /* Remove all modifiers, since we don't want them to be applied. */ BKE_object_free_modifiers(temp_object, LIB_ID_CREATE_NO_USER_REFCOUNT); - /* Copy relevant evaluated fields of curve cache. - * - * Note that there are extra fields in there like bevel and path, but those are not needed during - * conversion, so they are not copied to save unnecessary allocations. */ - if (temp_object->runtime.curve_cache == nullptr) { - temp_object->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), - "CurveCache for curve types"); - } - - if (object->runtime.curve_cache != nullptr) { - BKE_displist_copy(&temp_object->runtime.curve_cache->disp, &object->runtime.curve_cache->disp); - } - - /* Constructive modifiers will use mesh to store result. */ - if (object->runtime.data_eval != nullptr) { - BKE_id_copy_ex( - nullptr, object->runtime.data_eval, &temp_object->runtime.data_eval, LIB_ID_COPY_LOCALIZE); - } - - /* Need to create copy of curve itself as well, it will be freed by underlying conversion - * functions. - * - * NOTE: Copies the data, but not the shapekeys. */ - BKE_id_copy_ex( - nullptr, (const ID *)object->data, (ID **)&temp_object->data, LIB_ID_COPY_LOCALIZE); + /* Need to create copy of curve itself as well, since it will be changed by the curve evaluation + * process. NOTE: Copies the data, but not the shape-keys. */ + temp_object->data = BKE_id_copy_ex(nullptr, + (const ID *)object->data, + nullptr, + LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_SET_COPIED_ON_WRITE); Curve *temp_curve = (Curve *)temp_object->data; /* Make sure texture space is calculated for a copy of curve, it will be used for the final * result. */ BKE_curve_texspace_calc(temp_curve); - /* Temporarily set edit so we get updates from edit mode, but also because for text datablocks + /* Temporarily set edit so we get updates from edit mode, but also because for text data-blocks * copying it while in edit mode gives invalid data structures. */ temp_curve->editfont = curve->editfont; temp_curve->editnurb = curve->editnurb; @@ -1006,23 +904,10 @@ static Object *object_for_curve_to_mesh_create(Object *object) /** * Populate `object->runtime.curve_cache` which is then used to create the mesh. */ -static void curve_to_mesh_eval_ensure(Object *object) +static void curve_to_mesh_eval_ensure(Object &object) { - Curve *curve = (Curve *)object->data; - Curve remapped_curve = *curve; - Object remapped_object = *object; - BKE_object_runtime_reset(&remapped_object); - - remapped_object.data = &remapped_curve; - - if (object->runtime.curve_cache == nullptr) { - object->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), - "CurveCache for Curve"); - } - - /* Temporarily share the curve-cache with the temporary object, owned by `object`. */ - remapped_object.runtime.curve_cache = object->runtime.curve_cache; - + BLI_assert(GS(static_cast<ID *>(object.data)->name) == ID_CU); + Curve &curve = *static_cast<Curve *>(object.data); /* Clear all modifiers for the bevel object. * * This is because they can not be reliably evaluated for an original object (at least because @@ -1031,83 +916,97 @@ static void curve_to_mesh_eval_ensure(Object *object) * So we create temporary copy of the object which will use same data as the original bevel, but * will have no modifiers. */ Object bevel_object = {{nullptr}}; - if (remapped_curve.bevobj != nullptr) { - bevel_object = *remapped_curve.bevobj; + if (curve.bevobj != nullptr) { + bevel_object = *curve.bevobj; BLI_listbase_clear(&bevel_object.modifiers); BKE_object_runtime_reset(&bevel_object); - remapped_curve.bevobj = &bevel_object; + curve.bevobj = &bevel_object; } /* Same thing for taper. */ Object taper_object = {{nullptr}}; - if (remapped_curve.taperobj != nullptr) { - taper_object = *remapped_curve.taperobj; + if (curve.taperobj != nullptr) { + taper_object = *curve.taperobj; BLI_listbase_clear(&taper_object.modifiers); BKE_object_runtime_reset(&taper_object); - remapped_curve.taperobj = &taper_object; + curve.taperobj = &taper_object; } /* NOTE: We don't have dependency graph or scene here, so we pass nullptr. This is all fine since * they are only used for modifier stack, which we have explicitly disabled for all objects. * * TODO(sergey): This is a very fragile logic, but proper solution requires re-writing quite a - * bit of internal functions (#mesh_from_nurbs_displist, BKE_mesh_nomain_to_mesh) and also - * Mesh From Curve operator. + * bit of internal functions (#BKE_mesh_nomain_to_mesh) and also Mesh From Curve operator. * Brecht says hold off with that. */ - Mesh *mesh_eval = nullptr; - BKE_displist_make_curveTypes_forRender( - nullptr, nullptr, &remapped_object, &remapped_object.runtime.curve_cache->disp, &mesh_eval); + BKE_displist_make_curveTypes(nullptr, nullptr, &object, true); - /* NOTE: this is to be consistent with `BKE_displist_make_curveTypes()`, however that is not a - * real issue currently, code here is broken in more than one way, fix(es) will be done - * separately. */ - if (mesh_eval != nullptr) { - BKE_object_eval_assign_data(&remapped_object, &mesh_eval->id, true); - } - - /* Owned by `object` & needed by the caller to create the mesh. */ - remapped_object.runtime.curve_cache = nullptr; - - BKE_object_runtime_free_data(&remapped_object); - BKE_object_runtime_free_data(&taper_object); + BKE_object_runtime_free_data(&bevel_object); BKE_object_runtime_free_data(&taper_object); } -static Mesh *mesh_new_from_curve_type_object(Object *object) +/* Necessary because #BKE_object_get_evaluated_mesh doesn't look in the geometry set yet. */ +static const Mesh *get_evaluated_mesh_from_object(const Object *object) { - Curve *curve = (Curve *)object->data; - Object *temp_object = object_for_curve_to_mesh_create(object); - Curve *temp_curve = (Curve *)temp_object->data; + const Mesh *mesh = BKE_object_get_evaluated_mesh(object); + if (mesh) { + return mesh; + } + GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval; + if (geometry_set_eval) { + return geometry_set_eval->get_mesh_for_read(); + } + return nullptr; +} - /* When input object is an original one, we don't have evaluated curve cache yet, so need to - * create it in the temporary object. */ - if (!DEG_is_evaluated_object(object)) { - curve_to_mesh_eval_ensure(temp_object); +static const CurveEval *get_evaluated_curve_from_object(const Object *object) +{ + GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval; + if (geometry_set_eval) { + return geometry_set_eval->get_curve_for_read(); } + return nullptr; +} - /* Reset pointers before conversion. */ - temp_curve->editfont = nullptr; - temp_curve->editnurb = nullptr; +static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_object) +{ + const Mesh *mesh = get_evaluated_mesh_from_object(evaluated_object); + if (mesh) { + return BKE_mesh_copy_for_eval(mesh, false); + } + const CurveEval *curve = get_evaluated_curve_from_object(evaluated_object); + if (curve) { + return blender::bke::curve_to_wire_mesh(*curve); + } + return nullptr; +} - /* Convert to mesh. */ - mesh_from_nurbs_displist( - temp_object, &temp_object->runtime.curve_cache->disp, curve->id.name + 2); +static Mesh *mesh_new_from_curve_type_object(const Object *object) +{ + /* If the object is evaluated, it should either have an evaluated mesh or curve data already. + * The mesh can be duplicated, or the curve converted to wire mesh edges. */ + if (DEG_is_evaluated_object(object)) { + return mesh_new_from_evaluated_curve_type_object(object); + } - /* #mesh_from_nurbs_displist changes the type to a mesh, check it worked. If it didn't - * the curve did not have any segments or otherwise would have generated an empty mesh. */ - if (temp_object->type != OB_MESH) { - BKE_id_free(nullptr, temp_object->data); - BKE_id_free(nullptr, temp_object); - return nullptr; + /* Otherwise, create a temporary "fake" evaluated object and try again. This might have + * different results, since in order to avoid having adverse affects to other original objects, + * modifiers are cleared. An alternative would be to create a temporary depsgraph only for this + * object and its dependencies. */ + Object *temp_object = object_for_curve_to_mesh_create(object); + ID *temp_data = static_cast<ID *>(temp_object->data); + curve_to_mesh_eval_ensure(*temp_object); + + /* If evaluating the curve replaced object data with different data, free the original data. */ + if (temp_data != temp_object->data) { + BKE_id_free(nullptr, temp_data); } - Mesh *mesh_result = (Mesh *)temp_object->data; + Mesh *mesh = mesh_new_from_evaluated_curve_type_object(temp_object); + BKE_id_free(nullptr, temp_object->data); BKE_id_free(nullptr, temp_object); - /* NOTE: Materials are copied in #mesh_from_nurbs_displist(). */ - - return mesh_result; + return mesh; } static Mesh *mesh_new_from_mball_object(Object *object) @@ -1290,7 +1189,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, return mesh_in_bmain; } - /* Make sure mesh only points original datablocks, also increase users of materials and other + /* Make sure mesh only points original data-blocks, also increase users of materials and other * possibly referenced data-blocks. * * Going to original data-blocks is required to have bmain in a consistent state, where diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index b55b02c7bf2..6f6cf12f023 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -100,7 +100,7 @@ void BKE_modifier_init(void) /* Initialize modifier types */ modifier_type_init(modifier_types); /* MOD_utils.c */ - /* Initialize global cmmon storage used for virtual modifier list */ + /* Initialize global common storage used for virtual modifier list. */ md = BKE_modifier_new(eModifierType_Armature); virtualModifierCommonData.amd = *((ArmatureModifierData *)md); BKE_modifier_free(md); diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c index 4ce2ae3c11f..487e925df79 100644 --- a/source/blender/blenkernel/intern/nla.c +++ b/source/blender/blenkernel/intern/nla.c @@ -1484,7 +1484,7 @@ void BKE_nlastrip_recalculate_bounds(NlaStrip *strip) } /* Is the given NLA-strip the first one to occur for the given AnimData block */ -// TODO: make this an api method if necessary, but need to add prefix first +/* TODO: make this an api method if necessary, but need to add prefix first */ static bool nlastrip_is_first(AnimData *adt, NlaStrip *strip) { NlaTrack *nlt; diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index d56a7bf8fb4..73060caa2f8 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -52,9 +52,12 @@ #include "BLI_map.hh" #include "BLI_math.h" #include "BLI_path_util.h" +#include "BLI_set.hh" +#include "BLI_stack.hh" #include "BLI_string.h" #include "BLI_string_utils.h" #include "BLI_utildefines.h" +#include "BLI_vector_set.hh" #include "BLT_translation.h" @@ -80,6 +83,7 @@ #include "NOD_function.h" #include "NOD_geometry.h" #include "NOD_node_declaration.hh" +#include "NOD_node_tree_ref.hh" #include "NOD_shader.h" #include "NOD_socket.h" #include "NOD_texture.h" @@ -93,6 +97,21 @@ #define NODE_DEFAULT_MAX_WIDTH 700 +using blender::Array; +using blender::MutableSpan; +using blender::Set; +using blender::Span; +using blender::Stack; +using blender::Vector; +using blender::VectorSet; +using blender::nodes::FieldInferencingInterface; +using blender::nodes::InputSocketFieldType; +using blender::nodes::NodeDeclaration; +using blender::nodes::OutputFieldDependency; +using blender::nodes::OutputSocketFieldType; +using blender::nodes::SocketDeclaration; +using namespace blender::nodes::node_tree_ref_types; + /* Fallback types for undefined tree, nodes, sockets */ static bNodeTreeType NodeTreeTypeUndefined; bNodeType NodeTypeUndefined; @@ -110,6 +129,10 @@ static void node_socket_interface_free(bNodeTree *UNUSED(ntree), static void nodeMuteRerouteOutputLinks(struct bNodeTree *ntree, struct bNode *node, const bool mute); +static FieldInferencingInterface *node_field_inferencing_interface_copy( + const FieldInferencingInterface &field_inferencing_interface); +static void node_field_inferencing_interface_free( + const FieldInferencingInterface *field_inferencing_interface); static void ntree_init_data(ID *id) { @@ -220,6 +243,11 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c /* node tree will generate its own interface type */ ntree_dst->interface_type = nullptr; + + if (ntree_src->field_inferencing_interface) { + ntree_dst->field_inferencing_interface = node_field_inferencing_interface_copy( + *ntree_src->field_inferencing_interface); + } } static void ntree_free_data(ID *id) @@ -265,6 +293,8 @@ static void ntree_free_data(ID *id) MEM_freeN(sock); } + node_field_inferencing_interface_free(ntree->field_inferencing_interface); + /* free preview hash */ if (ntree->previews) { BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free); @@ -508,7 +538,7 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) if (node->storage) { /* could be handlerized at some point, now only 1 exception still */ if (ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY) && - ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) { + ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB, SH_NODE_CURVE_FLOAT)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); } else if ((ntree->type == NTREE_GEOMETRY) && @@ -647,6 +677,8 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) ntree->progress = nullptr; ntree->execdata = nullptr; + ntree->field_inferencing_interface = nullptr; + BLO_read_data_address(reader, &ntree->adt); BKE_animdata_blend_read_data(reader, ntree->adt); @@ -682,6 +714,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) switch (node->type) { case SH_NODE_CURVE_VEC: case SH_NODE_CURVE_RGB: + case SH_NODE_CURVE_FLOAT: case CMP_NODE_TIME: case CMP_NODE_CURVE_VEC: case CMP_NODE_CURVE_RGB: @@ -792,6 +825,11 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) /* TODO: should be dealt by new generic cache handling of IDs... */ ntree->previews = nullptr; + if (ntree->type == NTREE_GEOMETRY) { + /* Update field referencing for the geometry nodes modifier. */ + ntree->update |= NTREE_UPDATE_FIELD_INFERENCING; + } + /* type verification is in lib-link */ } @@ -1092,7 +1130,7 @@ static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node) RNA_pointer_create((ID *)ntree, &RNA_Node, node, &ptr); /* XXX Warning: context can be nullptr in case nodes are added in do_versions. - * Delayed init is not supported for nodes with context-based initfunc_api atm. + * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment. */ BLI_assert(C != nullptr); ntype->initfunc_api(C, &ptr); @@ -4425,7 +4463,510 @@ void ntreeUpdateAllNew(Main *main) FOREACH_NODETREE_END; } -void ntreeUpdateAllUsers(Main *main, ID *id) +static FieldInferencingInterface *node_field_inferencing_interface_copy( + const FieldInferencingInterface &field_inferencing_interface) +{ + return new FieldInferencingInterface(field_inferencing_interface); +} + +static void node_field_inferencing_interface_free( + const FieldInferencingInterface *field_inferencing_interface) +{ + delete field_inferencing_interface; +} + +namespace blender::bke::node_field_inferencing { + +static bool is_field_socket_type(eNodeSocketDatatype type) +{ + return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA); +} + +static bool is_field_socket_type(const SocketRef &socket) +{ + return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type); +} + +static bool update_field_inferencing(bNodeTree &btree); + +static InputSocketFieldType get_interface_input_field_type(const NodeRef &node, + const InputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + return InputSocketFieldType::None; + } + if (node.is_reroute_node()) { + return InputSocketFieldType::IsSupported; + } + if (node.is_group_output_node()) { + /* Outputs always support fields when the data type is correct. */ + return InputSocketFieldType::IsSupported; + } + if (node.is_undefined()) { + return InputSocketFieldType::None; + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + /* Get the field type from the declaration. */ + const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()]; + const InputSocketFieldType field_type = socket_decl.input_field_type(); + if (field_type == InputSocketFieldType::Implicit) { + return field_type; + } + if (node_decl->is_function_node()) { + /* In a function node, every socket supports fields. */ + return InputSocketFieldType::IsSupported; + } + return field_type; +} + +static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node, + const OutputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + /* Non-field sockets always output data. */ + return OutputFieldDependency::ForDataSource(); + } + if (node.is_reroute_node()) { + /* The reroute just forwards what is passed in. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_group_input_node()) { + /* Input nodes get special treatment in #determine_group_input_states. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_undefined()) { + return OutputFieldDependency::ForDataSource(); + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + if (node_decl->is_function_node()) { + /* In a generic function node, all outputs depend on all inputs. */ + return OutputFieldDependency::ForDependentField(); + } + + /* Use the socket declaration. */ + const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()]; + return socket_decl.output_field_dependency(); +} + +/** + * Retrieves information about how the node interacts with fields. + * In the future, this information can be stored in the node declaration. This would allow this + * function to return a reference, making it more efficient. + */ +static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node) +{ + /* Node groups already reference all required information, so just return that. */ + if (node.is_group_node()) { + bNodeTree *group = (bNodeTree *)node.bnode()->id; + if (group == nullptr) { + return FieldInferencingInterface(); + } + if (group->field_inferencing_interface == nullptr) { + /* Update group recursively. */ + update_field_inferencing(*group); + } + return *group->field_inferencing_interface; + } + + FieldInferencingInterface inferencing_interface; + for (const InputSocketRef *input_socket : node.inputs()) { + inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket)); + } + + for (const OutputSocketRef *output_socket : node.outputs()) { + inferencing_interface.outputs.append( + get_interface_output_field_dependency(node, *output_socket)); + } + return inferencing_interface; +} + +/** + * This struct contains information for every socket. The values are propagated through the + * network. + */ +struct SocketFieldState { + /* This socket is currently a single value. It could become a field though. */ + bool is_single = true; + /* This socket is required to be a single value. It must not be a field. */ + bool requires_single = false; + /* This socket starts a new field. */ + bool is_field_source = false; +}; + +static Vector<const InputSocketRef *> gather_input_socket_dependencies( + const OutputFieldDependency &field_dependency, const NodeRef &node) +{ + const OutputSocketFieldType type = field_dependency.field_type(); + Vector<const InputSocketRef *> input_sockets; + switch (type) { + case OutputSocketFieldType::FieldSource: + case OutputSocketFieldType::None: { + break; + } + case OutputSocketFieldType::DependentField: { + /* This output depends on all inputs. */ + input_sockets.extend(node.inputs()); + break; + } + case OutputSocketFieldType::PartiallyDependent: { + /* This output depends only on a few inputs. */ + for (const int i : field_dependency.linked_input_indices()) { + input_sockets.append(&node.input(i)); + } + break; + } + } + return input_sockets; +} + +/** + * Check what the group output socket depends on. Potentially traverses the node tree + * to figure out if it is always a field or if it depends on any group inputs. + */ +static OutputFieldDependency find_group_output_dependencies( + const InputSocketRef &group_output_socket, + const Span<SocketFieldState> field_state_by_socket_id) +{ + if (!is_field_socket_type(group_output_socket)) { + return OutputFieldDependency::ForDataSource(); + } + + /* Use a Set here instead of an array indexed by socket id, because we my only need to look at + * very few sockets. */ + Set<const InputSocketRef *> handled_sockets; + Stack<const InputSocketRef *> sockets_to_check; + + handled_sockets.add(&group_output_socket); + sockets_to_check.push(&group_output_socket); + + /* Keeps track of group input indices that are (indirectly) connected to the output. */ + Vector<int> linked_input_indices; + + while (!sockets_to_check.is_empty()) { + const InputSocketRef *input_socket = sockets_to_check.pop(); + + for (const OutputSocketRef *origin_socket : input_socket->logically_linked_sockets()) { + const NodeRef &origin_node = origin_socket->node(); + const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()]; + + if (origin_state.is_field_source) { + if (origin_node.is_group_input_node()) { + /* Found a group input that the group output depends on. */ + linked_input_indices.append_non_duplicates(origin_socket->index()); + } + else { + /* Found a field source that is not the group input. So the output is always a field. */ + return OutputFieldDependency::ForFieldSource(); + } + } + else if (!origin_state.is_single) { + const FieldInferencingInterface inferencing_interface = + get_node_field_inferencing_interface(origin_node); + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[origin_socket->index()]; + + /* Propagate search further to the left. */ + for (const InputSocketRef *origin_input_socket : + gather_input_socket_dependencies(field_dependency, origin_node)) { + if (!field_state_by_socket_id[origin_input_socket->id()].is_single) { + if (handled_sockets.add(origin_input_socket)) { + sockets_to_check.push(origin_input_socket); + } + } + } + } + } + } + return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices)); +} + +static void propagate_data_requirements_from_right_to_left( + const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + const Vector<const NodeRef *> sorted_nodes = tree.toposort( + NodeTreeRef::ToposortDirection::RightToLeft); + + for (const NodeRef *node : sorted_nodes) { + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { + continue; + } + if (field_dependency.field_type() == OutputSocketFieldType::None) { + state.requires_single = true; + continue; + } + + /* The output is required to be a single value when it is connected to any input that does + * not support fields. */ + for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) { + state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single; + } + + if (state.requires_single) { + bool any_input_is_field_implicitly = false; + const Vector<const InputSocketRef *> connected_inputs = gather_input_socket_dependencies( + field_dependency, *node); + for (const InputSocketRef *input_socket : connected_inputs) { + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + if (!input_socket->is_logically_linked()) { + any_input_is_field_implicitly = true; + break; + } + } + } + if (any_input_is_field_implicitly) { + /* This output isn't a single value actually. */ + state.requires_single = false; + } + else { + /* If the output is required to be a single value, the connected inputs in the same node + * must not be fields as well. */ + for (const InputSocketRef *input_socket : connected_inputs) { + field_state_by_socket_id[input_socket->id()].requires_single = true; + } + } + } + } + + /* Some inputs do not require fields independent of what the outputs are connected to. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { + state.requires_single = true; + } + } + } +} + +static void determine_group_input_states( + const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + { + /* Non-field inputs never support fields. */ + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) { + if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) { + new_inferencing_interface.inputs[index] = InputSocketFieldType::None; + } + } + } + /* Check if group inputs are required to be single values, because they are (indirectly) + * connected to some socket that does not support fields. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + if (state.requires_single) { + new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None; + } + } + } + /* If an input does not support fields, this should be reflected in all Group Input nodes. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] != + InputSocketFieldType::None; + if (supports_field) { + state.is_single = false; + state.is_field_source = true; + } + else { + state.requires_single = true; + } + } + SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()]; + dummy_socket_state.requires_single = true; + } +} + +static void propagate_field_status_from_left_to_right( + const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + Vector<const NodeRef *> sorted_nodes = tree.toposort( + NodeTreeRef::ToposortDirection::LeftToRight); + + for (const NodeRef *node : sorted_nodes) { + if (node->is_group_input_node()) { + continue; + } + + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + /* Update field state of input sockets, also taking into account linked origin sockets. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (state.requires_single) { + state.is_single = true; + continue; + } + state.is_single = true; + if (input_socket->logically_linked_sockets().is_empty()) { + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + state.is_single = false; + } + } + else { + for (const OutputSocketRef *origin_socket : input_socket->logically_linked_sockets()) { + if (!field_state_by_socket_id[origin_socket->id()].is_single) { + state.is_single = false; + break; + } + } + } + } + + /* Update field state of output sockets, also taking into account input sockets. */ + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + switch (field_dependency.field_type()) { + case OutputSocketFieldType::None: { + state.is_single = true; + break; + } + case OutputSocketFieldType::FieldSource: { + state.is_single = false; + state.is_field_source = true; + break; + } + case OutputSocketFieldType::PartiallyDependent: + case OutputSocketFieldType::DependentField: { + for (const InputSocketRef *input_socket : + gather_input_socket_dependencies(field_dependency, *node)) { + if (!field_state_by_socket_id[input_socket->id()].is_single) { + state.is_single = false; + break; + } + } + break; + } + } + } + } +} + +static void determine_group_output_states(const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const Span<SocketFieldState> field_state_by_socket_id) +{ + for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) { + /* Ignore inactive group output nodes. */ + if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) { + continue; + } + /* Determine dependencies of all group outputs. */ + for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) { + OutputFieldDependency field_dependency = find_group_output_dependencies( + *group_output_socket, field_state_by_socket_id); + new_inferencing_interface.outputs[group_output_socket->index()] = std::move( + field_dependency); + } + break; + } +} + +static void update_socket_shapes(const NodeTreeRef &tree, + const Span<SocketFieldState> field_state_by_socket_id) +{ + const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE; + const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; + const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND; + + for (const InputSocketRef *socket : tree.input_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + if (state.requires_single) { + bsocket->display_shape = requires_data_shape; + } + else if (state.is_single) { + bsocket->display_shape = data_but_can_be_field_shape; + } + else { + bsocket->display_shape = is_field_shape; + } + } + for (const OutputSocketRef *socket : tree.output_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + if (state.requires_single) { + bsocket->display_shape = requires_data_shape; + } + else if (state.is_single) { + bsocket->display_shape = data_but_can_be_field_shape; + } + else { + bsocket->display_shape = is_field_shape; + } + } +} + +static bool update_field_inferencing(bNodeTree &btree) +{ + using namespace blender::nodes; + if (btree.type != NTREE_GEOMETRY) { + return false; + } + + /* Create new inferencing interface for this node group. */ + FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface(); + new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs), + InputSocketFieldType::IsSupported); + new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs), + OutputFieldDependency::ForDataSource()); + + /* Create #NodeTreeRef to accelerate various queries on the node tree (e.g. linked sockets). */ + const NodeTreeRef tree{&btree}; + + /* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */ + Array<SocketFieldState> field_state_by_socket_id(tree.sockets().size()); + + propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id); + determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id); + propagate_field_status_from_left_to_right(tree, field_state_by_socket_id); + determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id); + update_socket_shapes(tree, field_state_by_socket_id); + + /* Update the previous group interface. */ + const bool group_interface_changed = btree.field_inferencing_interface == nullptr || + *btree.field_inferencing_interface != + *new_inferencing_interface; + delete btree.field_inferencing_interface; + btree.field_inferencing_interface = new_inferencing_interface; + + return group_interface_changed; +} + +} // namespace blender::bke::node_field_inferencing + +/** + * \param tree_update_flag: #eNodeTreeUpdate enum. + */ +void ntreeUpdateAllUsers(Main *main, ID *id, const int tree_update_flag) { if (id == nullptr) { return; @@ -4446,7 +4987,8 @@ void ntreeUpdateAllUsers(Main *main, ID *id) } if (need_update) { - ntreeUpdateTree(nullptr, ntree); + ntree->update |= tree_update_flag; + ntreeUpdateTree(tree_update_flag ? main : nullptr, ntree); } } FOREACH_NODETREE_END; @@ -4508,8 +5050,18 @@ void ntreeUpdateTree(Main *bmain, bNodeTree *ntree) ntreeInterfaceTypeUpdate(ntree); } + int tree_user_update_flag = 0; + + if (ntree->update & NTREE_UPDATE) { + /* If the field interface of this node tree has changed, all node trees using + * this group will need to recalculate their interface as well. */ + if (blender::bke::node_field_inferencing::update_field_inferencing(*ntree)) { + tree_user_update_flag |= NTREE_UPDATE_FIELD_INFERENCING; + } + } + if (bmain) { - ntreeUpdateAllUsers(bmain, &ntree->id); + ntreeUpdateAllUsers(bmain, &ntree->id, tree_user_update_flag); } if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { @@ -5023,6 +5575,7 @@ static void registerShaderNodes() register_node_type_sh_shadertorgb(); register_node_type_sh_normal(); register_node_type_sh_mapping(); + register_node_type_sh_curve_float(); register_node_type_sh_curve_vec(); register_node_type_sh_curve_rgb(); register_node_type_sh_map_range(); @@ -5156,10 +5709,17 @@ static void registerGeometryNodes() { register_node_type_geo_group(); + register_node_type_geo_legacy_curve_set_handles(); + register_node_type_geo_legacy_attribute_proximity(); + register_node_type_geo_legacy_attribute_randomize(); register_node_type_geo_legacy_material_assign(); register_node_type_geo_legacy_select_by_material(); + register_node_type_geo_legacy_curve_spline_type(); + register_node_type_geo_legacy_curve_reverse(); + register_node_type_geo_legacy_curve_subdivide(); register_node_type_geo_align_rotation_to_vector(); + register_node_type_geo_attribute_capture(); register_node_type_geo_attribute_clamp(); register_node_type_geo_attribute_color_ramp(); register_node_type_geo_attribute_combine_xyz(); @@ -5167,12 +5727,9 @@ static void registerGeometryNodes() register_node_type_geo_attribute_convert(); register_node_type_geo_attribute_curve_map(); register_node_type_geo_attribute_fill(); - register_node_type_geo_attribute_capture(); register_node_type_geo_attribute_map_range(); register_node_type_geo_attribute_math(); register_node_type_geo_attribute_mix(); - register_node_type_geo_attribute_proximity(); - register_node_type_geo_attribute_randomize(); register_node_type_geo_attribute_remove(); register_node_type_geo_attribute_separate_xyz(); register_node_type_geo_attribute_statistic(); @@ -5183,9 +5740,9 @@ static void registerGeometryNodes() register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); register_node_type_geo_convex_hull(); - register_node_type_geo_curve_sample(); register_node_type_geo_curve_endpoints(); register_node_type_geo_curve_fill(); + register_node_type_geo_curve_fillet(); register_node_type_geo_curve_length(); register_node_type_geo_curve_parameter(); register_node_type_geo_curve_primitive_bezier_segment(); @@ -5197,24 +5754,28 @@ static void registerGeometryNodes() register_node_type_geo_curve_primitive_star(); register_node_type_geo_curve_resample(); register_node_type_geo_curve_reverse(); + register_node_type_geo_curve_sample(); register_node_type_geo_curve_set_handles(); register_node_type_geo_curve_spline_type(); register_node_type_geo_curve_subdivide(); - register_node_type_geo_curve_fillet(); register_node_type_geo_curve_to_mesh(); register_node_type_geo_curve_to_points(); register_node_type_geo_curve_trim(); register_node_type_geo_delete_geometry(); + register_node_type_geo_distribute_points_on_faces(); register_node_type_geo_edge_split(); register_node_type_geo_input_index(); register_node_type_geo_input_material(); register_node_type_geo_input_normal(); register_node_type_geo_input_position(); register_node_type_geo_input_tangent(); + register_node_type_geo_input_spline_length(); + register_node_type_geo_instance_on_points(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); register_node_type_geo_material_assign(); register_node_type_geo_material_replace(); + register_node_type_geo_material_selection(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); register_node_type_geo_mesh_primitive_cube(); @@ -5225,6 +5786,7 @@ static void registerGeometryNodes() register_node_type_geo_mesh_primitive_uv_sphere(); register_node_type_geo_mesh_subdivide(); register_node_type_geo_mesh_to_curve(); + register_node_type_geo_mesh_to_points(); register_node_type_geo_object_info(); register_node_type_geo_point_distribute(); register_node_type_geo_point_instance(); @@ -5232,15 +5794,17 @@ static void registerGeometryNodes() register_node_type_geo_point_scale(); register_node_type_geo_point_separate(); register_node_type_geo_point_translate(); + register_node_type_geo_points_to_vertices(); register_node_type_geo_points_to_volume(); + register_node_type_geo_proximity(); register_node_type_geo_raycast(); register_node_type_geo_realize_instances(); register_node_type_geo_sample_texture(); register_node_type_geo_select_by_handle_type(); - register_node_type_geo_string_join(); - register_node_type_geo_material_selection(); register_node_type_geo_separate_components(); register_node_type_geo_set_position(); + register_node_type_geo_string_join(); + register_node_type_geo_string_to_curves(); register_node_type_geo_subdivision_surface(); register_node_type_geo_switch(); register_node_type_geo_transform(); @@ -5251,12 +5815,16 @@ static void registerGeometryNodes() static void registerFunctionNodes() { + register_node_type_fn_legacy_random_float(); + register_node_type_fn_boolean_math(); register_node_type_fn_float_compare(); register_node_type_fn_float_to_int(); + register_node_type_fn_input_special_characters(); register_node_type_fn_input_string(); register_node_type_fn_input_vector(); - register_node_type_fn_random_float(); + register_node_type_fn_random_value(); + register_node_type_fn_rotate_euler(); register_node_type_fn_string_length(); register_node_type_fn_string_substring(); register_node_type_fn_value_to_string(); diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 465ec9dc665..ec39c5b45c4 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -2634,10 +2634,16 @@ Object *BKE_object_duplicate(Main *bmain, { const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0; const bool is_root_id = (duplicate_options & LIB_ID_DUPLICATE_IS_ROOT_ID) != 0; + int copy_flags = LIB_ID_COPY_DEFAULT; if (!is_subprocess) { BKE_main_id_newptr_and_tag_clear(bmain); } + else { + /* In case copying object is a sub-process of collection (or scene) copying, do not try to + * re-assign RB objects to existing RBW collections. */ + copy_flags |= LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING; + } if (is_root_id) { /* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate * all expected linked data. */ @@ -2649,24 +2655,22 @@ Object *BKE_object_duplicate(Main *bmain, Material ***matarar; - Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag); + Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag, copy_flags); /* 0 == full linked. */ if (dupflag == 0) { return obn; } - BKE_animdata_duplicate_id_action(bmain, &obn->id, dupflag); - if (dupflag & USER_DUP_MAT) { for (int i = 0; i < obn->totcol; i++) { - BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag); + BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag, copy_flags); } } if (dupflag & USER_DUP_PSYS) { ParticleSystem *psys; for (psys = obn->particlesystem.first; psys; psys = psys->next) { - BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag); + BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag, copy_flags); } } @@ -2677,77 +2681,77 @@ Object *BKE_object_duplicate(Main *bmain, switch (obn->type) { case OB_MESH: if (dupflag & USER_DUP_MESH) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_CURVE: if (dupflag & USER_DUP_CURVE) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_SURF: if (dupflag & USER_DUP_SURF) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_FONT: if (dupflag & USER_DUP_FONT) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_MBALL: if (dupflag & USER_DUP_MBALL) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_LAMP: if (dupflag & USER_DUP_LAMP) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_ARMATURE: if (dupflag & USER_DUP_ARM) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_LATTICE: if (dupflag != 0) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_CAMERA: if (dupflag != 0) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_LIGHTPROBE: if (dupflag & USER_DUP_LIGHTPROBE) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_SPEAKER: if (dupflag != 0) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_GPENCIL: if (dupflag & USER_DUP_GPENCIL) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_HAIR: if (dupflag & USER_DUP_HAIR) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_POINTCLOUD: if (dupflag & USER_DUP_POINTCLOUD) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_VOLUME: if (dupflag & USER_DUP_VOLUME) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; } @@ -2758,7 +2762,7 @@ Object *BKE_object_duplicate(Main *bmain, matarar = BKE_object_material_array_p(obn); if (matarar) { for (int i = 0; i < obn->totcol; i++) { - BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag); + BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag, copy_flags); } } } diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index 50b0fb1c9f5..7b2a1af7086 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -4619,11 +4619,11 @@ void psys_get_particle_on_path(ParticleSimulationData *sim, pind.cache = cached ? psys->pointcache : NULL; pind.epoint = NULL; pind.bspline = (psys->part->flag & PART_HAIR_BSPLINE); - /* pind.dm disabled in editmode means we don't get effectors taken into - * account when subdividing for instance */ + /* `pind.dm` disabled in edit-mode means we don't get effectors taken into + * account when subdividing for instance. */ pind.mesh = psys_in_edit_mode(sim->depsgraph, psys) ? NULL : - psys->hair_out_mesh; /* XXX Sybren EEK */ + psys->hair_out_mesh; /* XXX(@sybren) EEK. */ init_particle_interpolation(sim->ob, psys, pa, &pind); do_particle_interpolation(psys, p, pa, t, &pind, state); diff --git a/source/blender/blenkernel/intern/preferences.c b/source/blender/blenkernel/intern/preferences.c index 8dcf6de164a..0b8e8d7c311 100644 --- a/source/blender/blenkernel/intern/preferences.c +++ b/source/blender/blenkernel/intern/preferences.c @@ -61,6 +61,15 @@ bUserAssetLibrary *BKE_preferences_asset_library_add(UserDef *userdef, return library; } +/** + * Unlink and free a library preference member. + * \note Free's \a library itself. + */ +void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library) +{ + BLI_freelinkN(&userdef->asset_libraries, library); +} + void BKE_preferences_asset_library_name_set(UserDef *userdef, bUserAssetLibrary *library, const char *name) @@ -74,15 +83,6 @@ void BKE_preferences_asset_library_name_set(UserDef *userdef, sizeof(library->name)); } -/** - * Unlink and free a library preference member. - * \note Free's \a library itself. - */ -void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library) -{ - BLI_freelinkN(&userdef->asset_libraries, library); -} - bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(const UserDef *userdef, int index) { return BLI_findlink(&userdef->asset_libraries, index); @@ -94,6 +94,17 @@ bUserAssetLibrary *BKE_preferences_asset_library_find_from_name(const UserDef *u return BLI_findstring(&userdef->asset_libraries, name, offsetof(bUserAssetLibrary, name)); } +bUserAssetLibrary *BKE_preferences_asset_library_containing_path(const UserDef *userdef, + const char *path) +{ + LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) { + if (BLI_path_contains(asset_lib_pref->path, path)) { + return asset_lib_pref; + } + } + return NULL; +} + int BKE_preferences_asset_library_get_index(const UserDef *userdef, const bUserAssetLibrary *library) { diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c index 328c54fc21b..1ea659b2d41 100644 --- a/source/blender/blenkernel/intern/rigidbody.c +++ b/source/blender/blenkernel/intern/rigidbody.c @@ -302,7 +302,7 @@ void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src ob_dst->rigidbody_object = rigidbody_copy_object(ob_src, flag); ob_dst->rigidbody_constraint = rigidbody_copy_constraint(ob_src, flag); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if ((flag & (LIB_ID_CREATE_NO_MAIN | LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING)) != 0) { return; } @@ -1211,8 +1211,8 @@ RigidBodyWorld *BKE_rigidbody_world_copy(RigidBodyWorld *rbw, const int flag) id_us_plus((ID *)rbw_copy->constraints); } - if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) { - /* This is a regular copy, and not a CoW copy for depsgraph evaluation */ + if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0) { + /* This is a regular copy, and not a CoW copy for depsgraph evaluation. */ rbw_copy->shared = MEM_callocN(sizeof(*rbw_copy->shared), "RigidBodyWorld_Shared"); BKE_ptcache_copy_list(&rbw_copy->shared->ptcaches, &rbw->shared->ptcaches, LIB_ID_COPY_CACHES); rbw_copy->shared->pointcache = rbw_copy->shared->ptcaches.first; diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 6b5c94a2786..a9a8cd93b1d 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -63,6 +63,8 @@ #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLO_readfile.h" + #include "BLT_translation.h" #include "BKE_action.h" @@ -993,8 +995,13 @@ static void link_recurs_seq(BlendDataReader *reader, ListBase *lb) { BLO_read_list(reader, lb); - LISTBASE_FOREACH (Sequence *, seq, lb) { - if (seq->seqbase.first) { + LISTBASE_FOREACH_MUTABLE (Sequence *, seq, lb) { + /* Sanity check. */ + if (!SEQ_valid_strip_channel(seq)) { + BLI_freelinkN(lb, seq); + BLO_read_data_reports(reader)->count.vse_strips_skipped++; + } + else if (seq->seqbase.first) { link_recurs_seq(reader, &seq->seqbase); } } @@ -1794,6 +1801,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) /* Scene duplication is always root of duplication currently. */ const bool is_subprocess = false; const bool is_root_id = true; + const int copy_flags = LIB_ID_COPY_DEFAULT; if (!is_subprocess) { BKE_main_id_newptr_and_tag_clear(bmain); @@ -1809,21 +1817,40 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) /* Copy Freestyle LineStyle datablocks. */ LISTBASE_FOREACH (ViewLayer *, view_layer_dst, &sce_copy->view_layers) { LISTBASE_FOREACH (FreestyleLineSet *, lineset, &view_layer_dst->freestyle_config.linesets) { - BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags); + BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags, copy_flags); } } /* Full copy of world (included animations) */ - BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags); + BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags, copy_flags); /* Full copy of GreasePencil. */ - BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags); + BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags, copy_flags); /* Deep-duplicate collections and objects (using preferences' settings for which sub-data to * duplicate along the object itself). */ BKE_collection_duplicate( bmain, NULL, sce_copy->master_collection, duplicate_flags, LIB_ID_DUPLICATE_IS_SUBPROCESS); + /* Rigid body world collections may not be instantiated as scene's collections, ensure they + * also get properly duplicated. */ + if (sce_copy->rigidbody_world != NULL) { + if (sce_copy->rigidbody_world->group != NULL) { + BKE_collection_duplicate(bmain, + NULL, + sce_copy->rigidbody_world->group, + duplicate_flags, + LIB_ID_DUPLICATE_IS_SUBPROCESS); + } + if (sce_copy->rigidbody_world->constraints != NULL) { + BKE_collection_duplicate(bmain, + NULL, + sce_copy->rigidbody_world->constraints, + duplicate_flags, + LIB_ID_DUPLICATE_IS_SUBPROCESS); + } + } + if (!is_subprocess) { /* This code will follow into all ID links using an ID tagged with LIB_TAG_NEW. */ BKE_libblock_relink_to_newid(&sce_copy->id); @@ -2465,7 +2492,7 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on // DEG_debug_graph_relations_validate(depsgraph, bmain, scene); /* Flush editing data if needed. */ prepare_mesh_for_viewport_render(bmain, view_layer); - /* Update all objects: drivers, matrices, displists, etc. flags set + /* Update all objects: drivers, matrices, #DispList, etc. flags set * by depsgraph or manual, no layer check here, gets correct flushed. */ DEG_evaluate_on_refresh(depsgraph); /* Update sound system. */ @@ -2541,7 +2568,7 @@ void BKE_scene_graph_update_for_newframe_ex(Depsgraph *depsgraph, const bool cle BKE_image_editors_update_frame(bmain, scene->r.cfra); BKE_sound_set_cfra(scene->r.cfra); DEG_graph_relations_update(depsgraph); - /* Update all objects: drivers, matrices, displists, etc. flags set + /* Update all objects: drivers, matrices, #DispList, etc. flags set * by depgraph or manual, no layer check here, gets correct flushed. * * NOTE: Only update for new frame on first iteration. Second iteration is for ensuring user diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index fbc781f5eb9..b7eb9d31b23 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -2295,7 +2295,7 @@ static void softbody_calc_forces( sb_sfesf_threads_run(depsgraph, scene, ob, timenow, sb->totspring, NULL); } - /* after spring scan because it uses Effoctors too */ + /* After spring scan because it uses effectors too. */ ListBase *effectors = BKE_effectors_create(depsgraph, ob, NULL, sb->effector_weights, false); if (do_deflector) { diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index b36d7a21669..f719a1cfda2 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -289,6 +289,56 @@ void BezierSpline::transform(const blender::float4x4 &matrix) this->mark_cache_invalid(); } +static void set_handle_position(const float3 &position, + const BezierSpline::HandleType type, + const BezierSpline::HandleType type_other, + const float3 &new_value, + float3 &handle, + float3 &handle_other) +{ + /* Don't bother when the handle positions are calculated automatically anyway. */ + if (ELEM(type, BezierSpline::HandleType::Auto, BezierSpline::HandleType::Vector)) { + return; + } + + handle = new_value; + if (type_other == BezierSpline::HandleType::Align) { + /* Keep track of the old length of the opposite handle. */ + const float length = float3::distance(handle_other, position); + /* Set the other handle to directly opposite from the current handle. */ + const float3 dir = (handle - position).normalized(); + handle_other = position - dir * length; + } +} + +/** + * Set positions for the right handle of the control point, ensuring that + * aligned handles stay aligned. Has no effect for auto and vector type handles. + */ +void BezierSpline::set_handle_position_right(const int index, const blender::float3 &value) +{ + set_handle_position(positions_[index], + handle_types_right_[index], + handle_types_left_[index], + value, + handle_positions_right_[index], + handle_positions_left_[index]); +} + +/** + * Set positions for the left handle of the control point, ensuring that + * aligned handles stay aligned. Has no effect for auto and vector type handles. + */ +void BezierSpline::set_handle_position_left(const int index, const blender::float3 &value) +{ + set_handle_position(positions_[index], + handle_types_left_[index], + handle_types_right_[index], + value, + handle_positions_left_[index], + handle_positions_right_[index]); +} + bool BezierSpline::point_is_sharp(const int index) const { return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) || diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c index e9cd0b70019..01bccab1bbd 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_mesh.c @@ -50,7 +50,7 @@ typedef struct SubdivMeshContext { const Mesh *coarse_mesh; Subdiv *subdiv; Mesh *subdiv_mesh; - /* Cached custom data arrays for fastter access. */ + /* Cached custom data arrays for faster access. */ int *vert_origindex; int *edge_origindex; int *loop_origindex; diff --git a/source/blender/blenkernel/intern/tracking.c b/source/blender/blenkernel/intern/tracking.c index 068d048fd08..3cdb8e927a6 100644 --- a/source/blender/blenkernel/intern/tracking.c +++ b/source/blender/blenkernel/intern/tracking.c @@ -3014,6 +3014,61 @@ static int channels_average_error_sort(const void *a, const void *b) return 0; } +static int compare_firstlast_putting_undefined_first( + bool inverse, bool a_markerless, int a_value, bool b_markerless, int b_value) +{ + if (a_markerless && b_markerless) { + /* Neither channel has not-disabled markers, return whatever. */ + return 0; + } + if (a_markerless) { + /* Put the markerless channel first. */ + return 0; + } + if (b_markerless) { + /* Put the markerless channel first. */ + return 1; + } + + /* Both channels have markers. */ + + if (inverse) { + if (a_value < b_value) { + return 1; + } + return 0; + } + + if (a_value > b_value) { + return 1; + } + return 0; +} + +static int channels_start_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(false, + channel_a->tot_segment == 0, + channel_a->first_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->first_not_disabled_marker_framenr); +} + +static int channels_end_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(false, + channel_a->tot_segment == 0, + channel_a->last_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->last_not_disabled_marker_framenr); +} + static int channels_alpha_inverse_sort(const void *a, const void *b) { if (channels_alpha_sort(a, b)) { @@ -3053,22 +3108,51 @@ static int channels_average_error_inverse_sort(const void *a, const void *b) return 0; } +static int channels_start_inverse_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(true, + channel_a->tot_segment == 0, + channel_a->first_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->first_not_disabled_marker_framenr); +} + +static int channels_end_inverse_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(true, + channel_a->tot_segment == 0, + channel_a->last_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->last_not_disabled_marker_framenr); +} + /* Calculate frames segments at which track is tracked continuously. */ static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChannel *channel) { MovieTrackingTrack *track = channel->track; int i, segment; + bool first_not_disabled_marker_framenr_set; channel->tot_segment = 0; channel->max_segment = 0; channel->total_frames = 0; + channel->first_not_disabled_marker_framenr = 0; + channel->last_not_disabled_marker_framenr = 0; + /* TODO(sergey): looks a bit code-duplicated, need to look into * logic de-duplication here. */ /* count */ i = 0; + first_not_disabled_marker_framenr_set = false; while (i < track->markersnr) { MovieTrackingMarker *marker = &track->markers[i]; @@ -3086,6 +3170,12 @@ static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChan break; } + if (!first_not_disabled_marker_framenr_set) { + channel->first_not_disabled_marker_framenr = marker->framenr; + first_not_disabled_marker_framenr_set = true; + } + channel->last_not_disabled_marker_framenr = marker->framenr; + prev_fra = marker->framenr; len++; i++; @@ -3203,6 +3293,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking, else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) { BLI_listbase_sort(&dopesheet->channels, channels_average_error_inverse_sort); } + else if (sort_method == TRACKING_DOPE_SORT_START) { + BLI_listbase_sort(&dopesheet->channels, channels_start_inverse_sort); + } + else if (sort_method == TRACKING_DOPE_SORT_END) { + BLI_listbase_sort(&dopesheet->channels, channels_end_inverse_sort); + } } else { if (sort_method == TRACKING_DOPE_SORT_NAME) { @@ -3217,6 +3313,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking, else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) { BLI_listbase_sort(&dopesheet->channels, channels_average_error_sort); } + else if (sort_method == TRACKING_DOPE_SORT_START) { + BLI_listbase_sort(&dopesheet->channels, channels_start_sort); + } + else if (sort_method == TRACKING_DOPE_SORT_END) { + BLI_listbase_sort(&dopesheet->channels, channels_end_sort); + } } } diff --git a/source/blender/blenlib/BLI_float4x4.hh b/source/blender/blenlib/BLI_float4x4.hh index 347ce2caa34..14e61d53845 100644 --- a/source/blender/blenlib/BLI_float4x4.hh +++ b/source/blender/blenlib/BLI_float4x4.hh @@ -45,6 +45,13 @@ struct float4x4 { return mat; } + static float4x4 from_location(const float3 location) + { + float4x4 mat = float4x4::identity(); + copy_v3_v3(mat.values[3], location); + return mat; + } + static float4x4 from_normalized_axis_data(const float3 location, const float3 forward, const float3 up) diff --git a/source/blender/blenlib/BLI_noise.hh b/source/blender/blenlib/BLI_noise.hh index 760ff082d06..7e1655f7864 100644 --- a/source/blender/blenlib/BLI_noise.hh +++ b/source/blender/blenlib/BLI_noise.hh @@ -22,36 +22,66 @@ namespace blender::noise { -/* Perlin noise in the range [-1, 1]. */ +/* -------------------------------------------------------------------- + * Hash functions. + + * Create a randomized hash from the given inputs. Contrary to hash functions in `BLI_hash.hh` + * these functions produce better randomness but are more expensive to compute. + */ + +/* Hash integers to `uint32_t`. */ +uint32_t hash(uint32_t kx); +uint32_t hash(uint32_t kx, uint32_t ky); +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz); +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw); + +/* Hash floats to `uint32_t`. */ +uint32_t hash_float(float kx); +uint32_t hash_float(float2 k); +uint32_t hash_float(float3 k); +uint32_t hash_float(float4 k); + +/* Hash integers to `float` between 0 and 1. */ +float hash_to_float(uint32_t kx); +float hash_to_float(uint32_t kx, uint32_t ky); +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz); +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw); + +/* Hash floats to `float` between 0 and 1. */ +float hash_float_to_float(float k); +float hash_float_to_float(float2 k); +float hash_float_to_float(float3 k); +float hash_float_to_float(float4 k); +/* -------------------------------------------------------------------- + * Perlin noise. + */ + +/* Perlin noise in the range [-1, 1]. */ float perlin_signed(float position); float perlin_signed(float2 position); float perlin_signed(float3 position); float perlin_signed(float4 position); /* Perlin noise in the range [0, 1]. */ - float perlin(float position); float perlin(float2 position); float perlin(float3 position); float perlin(float4 position); /* Fractal perlin noise in the range [0, 1]. */ - float perlin_fractal(float position, float octaves, float roughness); float perlin_fractal(float2 position, float octaves, float roughness); float perlin_fractal(float3 position, float octaves, float roughness); float perlin_fractal(float4 position, float octaves, float roughness); /* Positive distorted fractal perlin noise. */ - float perlin_fractal_distorted(float position, float octaves, float roughness, float distortion); float perlin_fractal_distorted(float2 position, float octaves, float roughness, float distortion); float perlin_fractal_distorted(float3 position, float octaves, float roughness, float distortion); float perlin_fractal_distorted(float4 position, float octaves, float roughness, float distortion); /* Positive distorted fractal perlin noise that outputs a float3. */ - float3 perlin_float3_fractal_distorted(float position, float octaves, float roughness, diff --git a/source/blender/blenlib/BLI_path_util.h b/source/blender/blenlib/BLI_path_util.h index 2a56d11276a..e4774c58e84 100644 --- a/source/blender/blenlib/BLI_path_util.h +++ b/source/blender/blenlib/BLI_path_util.h @@ -55,6 +55,10 @@ bool BLI_path_name_at_index(const char *__restrict path, int *__restrict r_offset, int *__restrict r_len) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; +/** Return true only if #containee_path is contained in #container_path. */ +bool BLI_path_contains(const char *container_path, + const char *containee_path) ATTR_WARN_UNUSED_RESULT; + const char *BLI_path_slash_rfind(const char *string) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; int BLI_path_slash_ensure(char *string) ATTR_NONNULL(); void BLI_path_slash_rstrip(char *string) ATTR_NONNULL(); diff --git a/source/blender/blenlib/BLI_resource_scope.hh b/source/blender/blenlib/BLI_resource_scope.hh index edffb148477..761e1ef834c 100644 --- a/source/blender/blenlib/BLI_resource_scope.hh +++ b/source/blender/blenlib/BLI_resource_scope.hh @@ -73,7 +73,6 @@ class ResourceScope : NonCopyable, NonMovable { */ template<typename T> T *add(std::unique_ptr<T> resource) { - BLI_assert(resource.get() != nullptr); T *ptr = resource.release(); if (ptr == nullptr) { return nullptr; diff --git a/source/blender/blenlib/BLI_uuid.h b/source/blender/blenlib/BLI_uuid.h index 9b85f8e65bc..ed0d31b625f 100644 --- a/source/blender/blenlib/BLI_uuid.h +++ b/source/blender/blenlib/BLI_uuid.h @@ -68,9 +68,41 @@ bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) ATTR_NONNULL(); #ifdef __cplusplus } +# include <initializer_list> # include <ostream> /** Output the UUID as formatted ASCII string, see #BLI_uuid_format(). */ std::ostream &operator<<(std::ostream &stream, bUUID uuid); +namespace blender { + +class bUUID : public ::bUUID { + public: + /** + * Default constructor, used with `bUUID value{};`, will initialize to the nil UUID. + */ + bUUID() = default; + + /** Initialize from the bUUID DNA struct. */ + bUUID(const ::bUUID &struct_uuid); + + /** Initialize from 11 integers, 5 for the regular fields and 6 for the `node` array. */ + bUUID(std::initializer_list<uint32_t> field_values); + + /** Initialize by parsing the string; undefined behavior when the string is invalid. */ + explicit bUUID(const std::string &string_formatted_uuid); + + uint64_t hash() const; +}; // namespace blender + +bool operator==(bUUID uuid1, bUUID uuid2); +bool operator!=(bUUID uuid1, bUUID uuid2); + +/** + * Lexicographic comparison of the UUIDs. + * Equivalent to string comparison on the formatted UUIDs. */ +bool operator<(bUUID uuid1, bUUID uuid2); + +} // namespace blender + #endif diff --git a/source/blender/blenlib/intern/fileops.c b/source/blender/blenlib/intern/fileops.c index 532a29b8147..2ef4d1093a8 100644 --- a/source/blender/blenlib/intern/fileops.c +++ b/source/blender/blenlib/intern/fileops.c @@ -570,6 +570,7 @@ bool BLI_dir_create_recursive(const char *dirname) * blah1/blah2 (without slash) */ BLI_strncpy(tmp, dirname, sizeof(tmp)); + BLI_path_slash_native(tmp); BLI_path_slash_rstrip(tmp); /* check special case "c:\foo", don't try create "c:", harmless but prints an error below */ @@ -760,7 +761,7 @@ static int recursive_operation(const char *startfrom, # endif if (is_dir) { - /* recursively dig into a subfolder */ + /* Recurse into sub-directories. */ ret = recursive_operation( from_path, to_path, callback_dir_pre, callback_file, callback_dir_post); } diff --git a/source/blender/blenlib/intern/kdtree_impl.h b/source/blender/blenlib/intern/kdtree_impl.h index 0c9de0aa128..0b47be1f7ea 100644 --- a/source/blender/blenlib/intern/kdtree_impl.h +++ b/source/blender/blenlib/intern/kdtree_impl.h @@ -190,7 +190,7 @@ static uint kdtree_balance(KDTreeNode *nodes, uint nodes_len, uint axis, const u } } - /* set node and sort subnodes */ + /* Set node and sort sub-nodes. */ node = &nodes[median]; node->d = axis; axis = (axis + 1) % KD_DIMS; diff --git a/source/blender/blenlib/intern/list_sort_impl.h b/source/blender/blenlib/intern/list_sort_impl.h index 680044f9ccb..71f7f0e29a8 100644 --- a/source/blender/blenlib/intern/list_sort_impl.h +++ b/source/blender/blenlib/intern/list_sort_impl.h @@ -202,7 +202,7 @@ BLI_INLINE list_node *sweep_up(struct SortInfo *si, list_node *list, unsigned in } /** - * The 'ranks' array essentially captures the recursion stack of a mergesort. + * The 'ranks' array essentially captures the recursion stack of a merge-sort. * The merge tree is built in a bottom-up manner. The control loop for * updating the 'ranks' array is analogous to incrementing a binary integer, * and the `O(n)` time for counting `upto` n translates to `O(n)` merges when diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc index c057c12e543..e80975f618c 100644 --- a/source/blender/blenlib/intern/noise.cc +++ b/source/blender/blenlib/intern/noise.cc @@ -109,7 +109,7 @@ BLI_INLINE void hash_bit_final(uint32_t &a, uint32_t &b, uint32_t &c) c -= hash_bit_rotate(b, 24); } -BLI_INLINE uint32_t hash(uint32_t kx) +uint32_t hash(uint32_t kx) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (1 << 2) + 13; @@ -120,7 +120,7 @@ BLI_INLINE uint32_t hash(uint32_t kx) return c; } -BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky) +uint32_t hash(uint32_t kx, uint32_t ky) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (2 << 2) + 13; @@ -132,7 +132,7 @@ BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky) return c; } -BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (3 << 2) + 13; @@ -145,7 +145,7 @@ BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) return c; } -BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (4 << 2) + 13; @@ -161,59 +161,83 @@ BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) return c; } -/* Hashing a number of uint32_t into a float in the range [0, 1]. */ +BLI_INLINE uint32_t float_as_uint(float f) +{ + union { + uint32_t i; + float f; + } u; + u.f = f; + return u.i; +} -BLI_INLINE float hash_to_float(uint32_t kx) +uint32_t hash_float(float kx) { - return static_cast<float>(hash(kx)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(kx)); } -BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky) +uint32_t hash_float(float2 k) { - return static_cast<float>(hash(kx, ky)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(k.x), float_as_uint(k.y)); } -BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz) +uint32_t hash_float(float3 k) { - return static_cast<float>(hash(kx, ky, kz)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z)); } -BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +uint32_t hash_float(float4 k) { - return static_cast<float>(hash(kx, ky, kz, kw)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z), float_as_uint(k.w)); } -/* Hashing a number of floats into a float in the range [0, 1]. */ +/* Hashing a number of uint32_t into a float in the range [0, 1]. */ -BLI_INLINE uint32_t float_as_uint(float f) +BLI_INLINE float uint_to_float_01(uint32_t k) { - union { - uint32_t i; - float f; - } u; - u.f = f; - return u.i; + return static_cast<float>(k) / static_cast<float>(0xFFFFFFFFu); +} + +float hash_to_float(uint32_t kx) +{ + return uint_to_float_01(hash(kx)); +} + +float hash_to_float(uint32_t kx, uint32_t ky) +{ + return uint_to_float_01(hash(kx, ky)); } -BLI_INLINE float hash_to_float(float k) +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz) +{ + return uint_to_float_01(hash(kx, ky, kz)); +} + +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +{ + return uint_to_float_01(hash(kx, ky, kz, kw)); +} + +/* Hashing a number of floats into a float in the range [0, 1]. */ + +float hash_float_to_float(float k) { - return hash_to_float(float_as_uint(k)); + return hash_to_float(hash_float(k)); } -BLI_INLINE float hash_to_float(float2 k) +float hash_float_to_float(float2 k) { - return hash_to_float(float_as_uint(k.x), float_as_uint(k.y)); + return hash_to_float(hash_float(k)); } -BLI_INLINE float hash_to_float(float3 k) +float hash_float_to_float(float3 k) { - return hash_to_float(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z)); + return hash_to_float(hash_float(k)); } -BLI_INLINE float hash_to_float(float4 k) +float hash_float_to_float(float4 k) { - return hash_to_float( - float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z), float_as_uint(k.w)); + return hash_to_float(hash_float(k)); } /* ------------ @@ -565,28 +589,28 @@ float perlin_fractal(float4 position, float octaves, float roughness) BLI_INLINE float random_float_offset(float seed) { - return 100.0f + hash_to_float(seed) * 100.0f; + return 100.0f + hash_float_to_float(seed) * 100.0f; } BLI_INLINE float2 random_float2_offset(float seed) { - return float2(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f); + return float2(100.0f + hash_float_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 1.0f)) * 100.0f); } BLI_INLINE float3 random_float3_offset(float seed) { - return float3(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f); + return float3(100.0f + hash_float_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 1.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 2.0f)) * 100.0f); } BLI_INLINE float4 random_float4_offset(float seed) { - return float4(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 3.0f)) * 100.0f); + return float4(100.0f + hash_float_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 1.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 2.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 3.0f)) * 100.0f); } /* Perlin noises to be added to the position to distort other noises. */ diff --git a/source/blender/blenlib/intern/path_util.c b/source/blender/blenlib/intern/path_util.c index 4d0678035ba..066749f3a94 100644 --- a/source/blender/blenlib/intern/path_util.c +++ b/source/blender/blenlib/intern/path_util.c @@ -1935,6 +1935,39 @@ bool BLI_path_name_at_index(const char *__restrict path, return false; } +bool BLI_path_contains(const char *container_path, const char *containee_path) +{ + char container_native[PATH_MAX]; + char containee_native[PATH_MAX]; + + /* Keep space for a trailing slash. If the path is truncated by this, the containee path is + * longer than PATH_MAX and the result is ill-defined. */ + BLI_strncpy(container_native, container_path, PATH_MAX - 1); + BLI_strncpy(containee_native, containee_path, PATH_MAX); + + BLI_path_slash_native(container_native); + BLI_path_slash_native(containee_native); + + BLI_path_normalize(NULL, container_native); + BLI_path_normalize(NULL, containee_native); + +#ifdef WIN32 + BLI_str_tolower_ascii(container_native, PATH_MAX); + BLI_str_tolower_ascii(containee_native, PATH_MAX); +#endif + + if (STREQ(container_native, containee_native)) { + /* The paths are equal, they contain each other. */ + return true; + } + + /* Add a trailing slash to prevent same-prefix directories from matching. + * e.g. "/some/path" doesn't contain "/some/path_lib". */ + BLI_path_slash_ensure(container_native); + + return BLI_str_startswith(containee_native, container_native); +} + /** * Returns pointer to the leftmost path separator in string. Not actually used anywhere. */ diff --git a/source/blender/blenlib/intern/scanfill.c b/source/blender/blenlib/intern/scanfill.c index f0cf19bf508..8845167f536 100644 --- a/source/blender/blenlib/intern/scanfill.c +++ b/source/blender/blenlib/intern/scanfill.c @@ -1040,13 +1040,13 @@ unsigned int BLI_scanfill_calc_ex(ScanFillContext *sf_ctx, const int flag, const } /* CURRENT STATUS: - * - eve->f :1 = available in edges - * - eve->poly_nr :polynumber - * - eve->edge_tot :amount of edges connected to vertex - * - eve->tmp.v :store! original vertex number + * - `eve->f`: 1 = available in edges. + * - `eve->poly_nr`: poly-number. + * - `eve->edge_tot`: amount of edges connected to vertex. + * - `eve->tmp.v`: store! original vertex number. * - * - eed->f :1 = boundary edge (optionally set by caller) - * - eed->poly_nr :poly number + * - `eed->f`: 1 = boundary edge (optionally set by caller). + * - `eed->poly_nr`: poly number. */ /* STEP 3: MAKE POLYFILL STRUCT */ diff --git a/source/blender/blenlib/intern/uuid.cc b/source/blender/blenlib/intern/uuid.cc index ae34bcb3d32..3c86238036c 100644 --- a/source/blender/blenlib/intern/uuid.cc +++ b/source/blender/blenlib/intern/uuid.cc @@ -18,13 +18,16 @@ * \ingroup bli */ +#include "BLI_assert.h" #include "BLI_uuid.h" #include <cstdio> #include <cstring> #include <ctime> #include <random> +#include <sstream> #include <string> +#include <tuple> /* Ensure the UUID struct doesn't have any padding, to be compatible with memcmp(). */ static_assert(sizeof(bUUID) == 16, "expect UUIDs to be 128 bit exactly"); @@ -137,3 +140,72 @@ std::ostream &operator<<(std::ostream &stream, bUUID uuid) stream << buffer; return stream; } + +namespace blender { + +bUUID::bUUID(const std::initializer_list<uint32_t> field_values) +{ + BLI_assert_msg(field_values.size() == 11, "bUUID requires 5 regular fields + 6 `node` values"); + + const auto *field_iter = field_values.begin(); + + this->time_low = *field_iter++; + this->time_mid = static_cast<uint16_t>(*field_iter++); + this->time_hi_and_version = static_cast<uint16_t>(*field_iter++); + this->clock_seq_hi_and_reserved = static_cast<uint8_t>(*field_iter++); + this->clock_seq_low = static_cast<uint8_t>(*field_iter++); + + std::copy(field_iter, field_values.end(), this->node); +} + +bUUID::bUUID(const std::string &string_formatted_uuid) +{ + const bool parsed_ok = BLI_uuid_parse_string(this, string_formatted_uuid.c_str()); + if (!parsed_ok) { + std::stringstream ss; + ss << "invalid UUID string " << string_formatted_uuid; + throw std::runtime_error(ss.str()); + } +} + +bUUID::bUUID(const ::bUUID &struct_uuid) +{ + *(static_cast<::bUUID *>(this)) = struct_uuid; +} + +uint64_t bUUID::hash() const +{ + /* Convert the struct into two 64-bit numbers, and XOR them to get the hash. */ + const uint64_t *uuid_as_int64 = reinterpret_cast<const uint64_t *>(this); + return uuid_as_int64[0] ^ uuid_as_int64[1]; +} + +bool operator==(const bUUID uuid1, const bUUID uuid2) +{ + return BLI_uuid_equal(uuid1, uuid2); +} + +bool operator!=(const bUUID uuid1, const bUUID uuid2) +{ + return !(uuid1 == uuid2); +} + +bool operator<(const bUUID uuid1, const bUUID uuid2) +{ + auto simple_fields1 = std::tie(uuid1.time_low, + uuid1.time_mid, + uuid1.time_hi_and_version, + uuid1.clock_seq_hi_and_reserved, + uuid1.clock_seq_low); + auto simple_fields2 = std::tie(uuid2.time_low, + uuid2.time_mid, + uuid2.time_hi_and_version, + uuid2.clock_seq_hi_and_reserved, + uuid2.clock_seq_low); + if (simple_fields1 == simple_fields2) { + return std::memcmp(uuid1.node, uuid2.node, sizeof(uuid1.node)) < 0; + } + return simple_fields1 < simple_fields2; +} + +} // namespace blender diff --git a/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc b/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc index 0329fc156c0..68111fb8eb1 100644 --- a/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc +++ b/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc @@ -457,8 +457,8 @@ TEST(mesh_intersect, TwoTris) {4, 11, 6, 4}, /* 9: T11 edge (-1,1,1)(0,1/2,1/2) inside T4 edge. */ {4, 12, 6, 2}, /* 10: parallel planes, not intersecting. */ {4, 13, 6, 2}, /* 11: non-parallel planes, not intersecting, all one side. */ - {0, 14, 6, 2}, /* 12: non-paralel planes, not intersecting, alternate sides. */ - /* Following are all coplanar cases. */ + {0, 14, 6, 2}, /* 12: non-parallel planes, not intersecting, alternate sides. */ + /* Following are all co-planar cases. */ {15, 16, 6, 8}, /* 13: T16 inside T15. NOTE: dup'd tri is expected. */ {15, 17, 8, 8}, /* 14: T17 intersects one edge of T15 at (1,1,0)(3,3,0). */ {15, 18, 10, 12}, /* 15: T18 intersects T15 at (1,1,0)(3,3,0)(3,15/4,1/2)(0,3,2). */ @@ -970,7 +970,7 @@ static void fill_sphere_data(int nrings, static void spheresphere_test(int nrings, double y_offset, bool use_self) { - /* Make two uvspheres with nrings rings ad 2*nrings segments. */ + /* Make two UV-spheres with nrings rings ad 2*nrings segments. */ if (nrings < 2) { return; } diff --git a/source/blender/blenlib/tests/BLI_path_util_test.cc b/source/blender/blenlib/tests/BLI_path_util_test.cc index cf5135731e2..65b02a19960 100644 --- a/source/blender/blenlib/tests/BLI_path_util_test.cc +++ b/source/blender/blenlib/tests/BLI_path_util_test.cc @@ -655,3 +655,34 @@ TEST(path_util, PathRelPath) # undef PATH_REL #endif + +/* BLI_path_contains */ +TEST(path_util, PathContains) +{ + EXPECT_TRUE(BLI_path_contains("/some/path", "/some/path")) << "A path contains itself"; + EXPECT_TRUE(BLI_path_contains("/some/path", "/some/path/inside")) + << "A path contains its subdirectory"; + EXPECT_TRUE(BLI_path_contains("/some/path", "/some/path/../path/inside")) + << "Paths should be normalised"; + EXPECT_TRUE(BLI_path_contains("C:\\some\\path", "C:\\some\\path\\inside")) + << "Windows paths should be supported as well"; + + EXPECT_FALSE(BLI_path_contains("C:\\some\\path", "C:\\some\\other\\path")) + << "Windows paths should be supported as well"; + EXPECT_FALSE(BLI_path_contains("/some/path", "/")) + << "Root directory not be contained in a subdirectory"; + EXPECT_FALSE(BLI_path_contains("/some/path", "/some/path/../outside")) + << "Paths should be normalised"; + EXPECT_FALSE(BLI_path_contains("/some/path", "/some/path_library")) + << "Just sharing a suffix is not enough, path semantics should be followed"; + EXPECT_FALSE(BLI_path_contains("/some/path", "./contents")) + << "Relative paths are not supported"; +} + +#ifdef WIN32 +TEST(path_util, PathContains_Windows_case_insensitive) +{ + EXPECT_TRUE(BLI_path_contains("C:\\some\\path", "c:\\SOME\\path\\inside")) + << "On Windows path comparison should ignore case"; +} +#endif diff --git a/source/blender/blenlib/tests/BLI_uuid_test.cc b/source/blender/blenlib/tests/BLI_uuid_test.cc index 731489c6c9e..b406a0521a1 100644 --- a/source/blender/blenlib/tests/BLI_uuid_test.cc +++ b/source/blender/blenlib/tests/BLI_uuid_test.cc @@ -18,6 +18,8 @@ #include "BLI_uuid.h" +namespace blender::tests { + TEST(BLI_uuid, generate_random) { const bUUID uuid = BLI_uuid_generate_random(); @@ -38,7 +40,7 @@ TEST(BLI_uuid, generate_many_random) /* Generate lots of UUIDs to get some indication that the randomness is okay. */ for (int i = 0; i < 1000000; ++i) { const bUUID uuid = BLI_uuid_generate_random(); - EXPECT_FALSE(BLI_uuid_equal(first_uuid, uuid)); + EXPECT_NE(first_uuid, uuid); // Check that the non-random bits are set according to RFC4122. const uint16_t version = uuid.time_hi_and_version >> 12; @@ -51,10 +53,13 @@ TEST(BLI_uuid, generate_many_random) TEST(BLI_uuid, nil_value) { const bUUID nil_uuid = BLI_uuid_nil(); - const bUUID zeroes_uuid = {0, 0, 0, 0, 0, 0}; + const bUUID zeroes_uuid{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + const bUUID default_constructed{}; - EXPECT_TRUE(BLI_uuid_equal(nil_uuid, zeroes_uuid)); + EXPECT_EQ(nil_uuid, zeroes_uuid); EXPECT_TRUE(BLI_uuid_is_nil(nil_uuid)); + EXPECT_TRUE(BLI_uuid_is_nil(default_constructed)) + << "Default constructor should produce the nil value."; std::string buffer(36, '\0'); BLI_uuid_format(buffer.data(), nil_uuid); @@ -66,8 +71,31 @@ TEST(BLI_uuid, equality) const bUUID uuid1 = BLI_uuid_generate_random(); const bUUID uuid2 = BLI_uuid_generate_random(); - EXPECT_TRUE(BLI_uuid_equal(uuid1, uuid1)); - EXPECT_FALSE(BLI_uuid_equal(uuid1, uuid2)); + EXPECT_EQ(uuid1, uuid1); + EXPECT_NE(uuid1, uuid2); +} + +TEST(BLI_uuid, comparison_trivial) +{ + const bUUID uuid0{}; + const bUUID uuid1("11111111-1111-1111-1111-111111111111"); + const bUUID uuid2("22222222-2222-2222-2222-222222222222"); + + EXPECT_LT(uuid0, uuid1); + EXPECT_LT(uuid0, uuid2); + EXPECT_LT(uuid1, uuid2); +} + +TEST(BLI_uuid, comparison_byte_order_check) +{ + const bUUID uuid0{}; + /* Chosen to test byte ordering is taken into account correctly when comparing. */ + const bUUID uuid12("12222222-2222-2222-2222-222222222222"); + const bUUID uuid21("21111111-1111-1111-1111-111111111111"); + + EXPECT_LT(uuid0, uuid12); + EXPECT_LT(uuid0, uuid21); + EXPECT_LT(uuid12, uuid21); } TEST(BLI_uuid, string_formatting) @@ -91,13 +119,13 @@ TEST(BLI_uuid, string_formatting) EXPECT_EQ("00000001-0002-0003-0405-060000000007", buffer); /* Somewhat more complex bit patterns. This is a version 1 UUID generated from Python. */ - const bUUID uuid1 = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; + const bUUID uuid1 = {3540651616, 5282, 4588, 139, 153, 0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}; BLI_uuid_format(buffer.data(), uuid1); EXPECT_EQ("d30a0e60-14a2-11ec-8b99-f7736944db8b", buffer); /* Namespace UUID, example listed in RFC4211. */ const bUUID namespace_dns = { - 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, {0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; + 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}; BLI_uuid_format(buffer.data(), namespace_dns); EXPECT_EQ("6ba7b810-9dad-11d1-80b4-00c04fd430c8", buffer); } @@ -139,7 +167,9 @@ TEST(BLI_uuid, string_parsing_fail) TEST(BLI_uuid, stream_operator) { std::stringstream ss; - const bUUID uuid = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; + const bUUID uuid = {3540651616, 5282, 4588, 139, 153, 0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}; ss << uuid; EXPECT_EQ(ss.str(), "d30a0e60-14a2-11ec-8b99-f7736944db8b"); } + +} // namespace blender::tests diff --git a/source/blender/blenloader/BLO_readfile.h b/source/blender/blenloader/BLO_readfile.h index 4b7f29dd7dc..fa29b09af02 100644 --- a/source/blender/blenloader/BLO_readfile.h +++ b/source/blender/blenloader/BLO_readfile.h @@ -111,8 +111,18 @@ typedef struct BlendFileReadReport { /* 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 (non-converted) linked proxies. */ + int linked_proxies; + /* 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 vse_strips_skipped; } count; /* Number of libraries which had overrides that needed to be resynced, and a single linked list @@ -168,9 +178,8 @@ struct LinkNode *BLO_blendhandle_get_datablock_names(BlendHandle *bh, const bool use_assets_only, int *r_tot_names); -struct LinkNode *BLO_blendhandle_get_datablock_info(BlendHandle *bh, - int ofblocktype, - int *r_tot_info_items); +struct LinkNode * /*BLODataBlockInfo */ BLO_blendhandle_get_datablock_info( + BlendHandle *bh, int ofblocktype, const bool use_assets_only, int *r_tot_info_items); struct LinkNode *BLO_blendhandle_get_previews(BlendHandle *bh, int ofblocktype, int *r_tot_prev); struct PreviewImage *BLO_blendhandle_get_preview_for_id(BlendHandle *bh, int ofblocktype, @@ -209,6 +218,16 @@ typedef enum eBLOLibLinkFlags { * don't need to remember to set this flag. */ BLO_LIBLINK_NEEDS_ID_TAG_DOIT = 1 << 18, + /** Set fake user on appended IDs. */ + BLO_LIBLINK_APPEND_SET_FAKEUSER = 1 << 19, + /** Append (make local) also indirect dependencies of appended IDs. */ + 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, + /** 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, } eBLOLibLinkFlags; /** diff --git a/source/blender/blenloader/intern/readblenentry.c b/source/blender/blenloader/intern/readblenentry.c index f88b470809c..3306eb9e454 100644 --- a/source/blender/blenloader/intern/readblenentry.c +++ b/source/blender/blenloader/intern/readblenentry.c @@ -170,17 +170,19 @@ LinkNode *BLO_blendhandle_get_datablock_names(BlendHandle *bh, } /** - * Gets the names and asset-data (if ID is an asset) of all the data-blocks in a file of a certain - * type (e.g. all the scene names in a file). + * 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 tot_info_items: The length of the returned list. * \return A BLI_linklist of BLODataBlockInfo *. The links and #BLODataBlockInfo.asset_data should * be freed with MEM_freeN. */ LinkNode *BLO_blendhandle_get_datablock_info(BlendHandle *bh, int ofblocktype, + const bool use_assets_only, int *r_tot_info_items) { FileData *fd = (FileData *)bh; @@ -189,27 +191,34 @@ LinkNode *BLO_blendhandle_get_datablock_info(BlendHandle *bh, int tot = 0; for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) { + if (bhead->code == ENDB) { + break; + } if (bhead->code == ofblocktype) { - struct BLODataBlockInfo *info = MEM_mallocN(sizeof(*info), __func__); const char *name = blo_bhead_id_name(fd, bhead) + 2; + AssetMetaData *asset_meta_data = blo_bhead_id_asset_data_address(fd, bhead); - STRNCPY(info->name, name); + const bool is_asset = asset_meta_data != NULL; + const bool skip_datablock = use_assets_only && !is_asset; + if (skip_datablock) { + continue; + } + struct BLODataBlockInfo *info = MEM_mallocN(sizeof(*info), __func__); /* Lastly, read asset data from the following blocks. */ - info->asset_data = blo_bhead_id_asset_data_address(fd, bhead); - if (info->asset_data) { - bhead = blo_read_asset_data_block(fd, bhead, &info->asset_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. */ + 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; + BLI_linklist_prepend(&infos, info); tot++; } - else if (bhead->code == ENDB) { - break; - } } *r_tot_info_items = tot; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 8203f988eed..cb3e81ba6d5 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -4500,7 +4500,7 @@ static void add_loose_objects_to_scene(Main *mainvar, ViewLayer *view_layer, const View3D *v3d, Library *lib, - const short flag) + const int flag) { Collection *active_collection = NULL; const bool do_append = (flag & FILE_LINK) == 0; @@ -4510,7 +4510,10 @@ static void add_loose_objects_to_scene(Main *mainvar, /* Give all objects which are LIB_TAG_INDIRECT a base, * or for a collection when *lib has been set. */ LISTBASE_FOREACH (Object *, ob, &mainvar->objects) { - bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0; + /* NOTE: Even if this is a directly linked object and is tagged for instantiation, it might + * have already been instantiated through one of its owner collections, in which case we do not + * want to re-instantiate it in the active collection here. */ + bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0 && !BKE_scene_object_find(scene, ob); if (do_it || ((ob->id.tag & LIB_TAG_INDIRECT) != 0 && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) { if (do_append) { @@ -4560,9 +4563,9 @@ static void add_loose_object_data_to_scene(Main *mainvar, Scene *scene, ViewLayer *view_layer, const View3D *v3d, - const short flag) + const int flag) { - if ((flag & FILE_OBDATA_INSTANCE) == 0) { + if ((flag & BLO_LIBLINK_OBDATA_INSTANCE) == 0) { return; } @@ -4621,7 +4624,7 @@ static void add_collections_to_scene(Main *mainvar, ViewLayer *view_layer, const View3D *v3d, Library *lib, - const short flag) + const int flag) { Collection *active_collection = scene->master_collection; if (flag & FILE_ACTIVE_COLLECTION) { @@ -4631,7 +4634,7 @@ static void add_collections_to_scene(Main *mainvar, /* Give all objects which are tagged a base. */ LISTBASE_FOREACH (Collection *, collection, &mainvar->collections) { - if ((flag & FILE_COLLECTION_INSTANCE) && (collection->id.tag & LIB_TAG_DOIT)) { + if ((flag & BLO_LIBLINK_COLLECTION_INSTANCE) && (collection->id.tag & LIB_TAG_DOIT)) { /* Any indirect collection should not have been tagged. */ BLI_assert((collection->id.tag & LIB_TAG_INDIRECT) == 0); @@ -4780,7 +4783,7 @@ int BLO_library_link_copypaste(Main *mainl, BlendHandle *bh, const uint64_t id_t if (blo_bhead_is_id_valid_type(bhead) && BKE_idtype_idcode_is_linkable((short)bhead->code) && (id_types_mask == 0 || (BKE_idtype_idcode_to_idfilter((short)bhead->code) & id_types_mask) != 0)) { - read_libblock(fd, mainl, bhead, LIB_TAG_NEED_EXPAND | LIB_TAG_INDIRECT, false, &id); + read_libblock(fd, mainl, bhead, LIB_TAG_NEED_EXPAND | LIB_TAG_EXTERN, false, &id); num_directly_linked++; } @@ -4789,6 +4792,13 @@ int BLO_library_link_copypaste(Main *mainl, BlendHandle *bh, const uint64_t id_t ListBase *lb = which_libbase(mainl, GS(id->name)); id_sort_by_name(lb, id, NULL); + /* Tag as loose object (or data associated with objects) + * needing to be instantiated (see also #link_named_part and its usage of + * #BLO_LIBLINK_NEEDS_ID_TAG_DOIT above). */ + if (library_link_idcode_needs_tag_check(GS(id->name), BLO_LIBLINK_NEEDS_ID_TAG_DOIT)) { + id->tag |= LIB_TAG_DOIT; + } + if (bhead->code == ID_OB) { /* Instead of instancing Base's directly, postpone until after collections are loaded * otherwise the base's flag is set incorrectly when collections are used */ @@ -4834,7 +4844,7 @@ static bool library_link_idcode_needs_tag_check(const short idcode, const int fl if (ELEM(idcode, ID_OB, ID_GR)) { return true; } - if (flag & FILE_OBDATA_INSTANCE) { + if (flag & BLO_LIBLINK_OBDATA_INSTANCE) { if (OB_DATA_SUPPORT_ID(idcode)) { return true; } diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 69b67460a5d..d8f4d01a2e9 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -3394,7 +3394,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) SpaceImage *sima = (SpaceImage *)sl; sima->flag &= ~(SI_FLAG_UNUSED_0 | SI_FLAG_UNUSED_1 | SI_FLAG_UNUSED_3 | SI_FLAG_UNUSED_6 | SI_FLAG_UNUSED_7 | SI_FLAG_UNUSED_8 | - SI_FLAG_UNUSED_17 | SI_FLAG_UNUSED_18 | SI_FLAG_UNUSED_23 | + SI_FLAG_UNUSED_17 | SI_CUSTOM_GRID | SI_FLAG_UNUSED_23 | SI_FLAG_UNUSED_24); break; } @@ -3416,8 +3416,8 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) case SPACE_FILE: { SpaceFile *sfile = (SpaceFile *)sl; if (sfile->params) { - sfile->params->flag &= ~(FILE_APPEND_SET_FAKEUSER | FILE_APPEND_RECURSIVE | - FILE_OBDATA_INSTANCE); + sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_2 | + FILE_PARAMS_FLAG_UNUSED_3); } break; } diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index be8c4b735be..bf5b0bdbf3c 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -1540,7 +1540,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (STREQ(node->idname, "GeometryNodeRandomAttribute")) { - STRNCPY(node->idname, "GeometryNodeAttributeRandomize"); + STRNCPY(node->idname, "GeometryLegacyNodeAttributeRandomize"); } } } diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 3bb76a4d4b6..323391d9715 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -376,6 +376,7 @@ static void move_vertex_group_names_to_object_data(Main *bmain) /* Clear the list in case the it was already assigned from another object. */ BLI_freelistN(new_defbase); *new_defbase = object->defbase; + BKE_object_defgroup_active_index_set(object, object->actdef); } } } @@ -449,6 +450,79 @@ static void do_versions_sequencer_speed_effect_recursive(Scene *scene, const Lis #undef SEQ_SPEED_COMPRESS_IPO_Y } +static bool do_versions_sequencer_color_tags(Sequence *seq, void *UNUSED(user_data)) +{ + seq->color_tag = SEQUENCE_COLOR_NONE; + return true; +} + +static bNodeLink *find_connected_link(bNodeTree *ntree, bNodeSocket *in_socket) +{ + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (link->tosock == in_socket) { + return link; + } + } + return NULL; +} + +static void add_realize_instances_before_socket(bNodeTree *ntree, + bNode *node, + bNodeSocket *geometry_socket) +{ + BLI_assert(geometry_socket->type == SOCK_GEOMETRY); + bNodeLink *link = find_connected_link(ntree, geometry_socket); + if (link == NULL) { + return; + } + + /* If the realize instances node is already before this socket, no need to continue. */ + if (link->fromnode->type == GEO_NODE_REALIZE_INSTANCES) { + return; + } + + bNode *realize_node = nodeAddStaticNode(NULL, ntree, GEO_NODE_REALIZE_INSTANCES); + realize_node->parent = node->parent; + realize_node->locx = node->locx - 100; + realize_node->locy = node->locy; + nodeAddLink(ntree, link->fromnode, link->fromsock, realize_node, realize_node->inputs.first); + link->fromnode = realize_node; + link->fromsock = realize_node->outputs.first; +} + +/** + * If a node used to realize instances implicitly and will no longer do so in 3.0, add a "Realize + * Instances" node in front of it to avoid changing behavior. Don't do this if the node will be + * replaced anyway though. + */ +static void version_geometry_nodes_add_realize_instance_nodes(bNodeTree *ntree) +{ + LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { + if (ELEM(node->type, + GEO_NODE_ATTRIBUTE_CAPTURE, + GEO_NODE_SEPARATE_COMPONENTS, + GEO_NODE_CONVEX_HULL, + GEO_NODE_CURVE_LENGTH, + GEO_NODE_BOOLEAN, + GEO_NODE_CURVE_FILLET, + GEO_NODE_CURVE_RESAMPLE, + GEO_NODE_CURVE_TO_MESH, + GEO_NODE_CURVE_TRIM, + GEO_NODE_MATERIAL_REPLACE, + GEO_NODE_MESH_SUBDIVIDE, + GEO_NODE_ATTRIBUTE_REMOVE, + GEO_NODE_TRIANGULATE)) { + bNodeSocket *geometry_socket = node->inputs.first; + add_realize_instances_before_socket(ntree, node, geometry_socket); + } + /* Also realize instances for the profile input of the curve to mesh node. */ + if (node->type == GEO_NODE_CURVE_TO_MESH) { + bNodeSocket *profile_socket = node->inputs.last; + add_realize_instances_before_socket(ntree, node, profile_socket); + } + } +} + void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) { if (MAIN_VERSION_ATLEAST(bmain, 300, 0) && !MAIN_VERSION_ATLEAST(bmain, 300, 1)) { @@ -505,6 +579,70 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) } } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 26)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + ToolSettings *tool_settings = scene->toolsettings; + ImagePaintSettings *imapaint = &tool_settings->imapaint; + if (imapaint->canvas != NULL && + ELEM(imapaint->canvas->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + imapaint->canvas = NULL; + } + if (imapaint->stencil != NULL && + ELEM(imapaint->stencil->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + imapaint->stencil = NULL; + } + if (imapaint->clone != NULL && + ELEM(imapaint->clone->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + imapaint->clone = NULL; + } + } + + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->clone.image != NULL && + ELEM(brush->clone.image->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + brush->clone.image = NULL; + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 28)) { + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + version_geometry_nodes_add_realize_instance_nodes(ntree); + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 30)) { + do_versions_idproperty_ui_data(bmain); + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 32)) { + /* Update Switch Node Non-Fields switch input to Switch_001. */ + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type != NTREE_GEOMETRY) { + continue; + } + + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (link->tonode->type == GEO_NODE_SWITCH) { + if (STREQ(link->tosock->identifier, "Switch")) { + bNode *to_node = link->tonode; + + uint8_t mode = ((NodeSwitch *)to_node->storage)->input_type; + if (ELEM(mode, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_COLLECTION, + SOCK_TEXTURE, + SOCK_MATERIAL)) { + link->tosock = link->tosock->next; + } + } + } + } + } + } /** * Versioning code until next subversion bump goes here. * @@ -517,7 +655,16 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) */ { /* Keep this block, even when empty. */ - do_versions_idproperty_ui_data(bmain); + + /* This was missing from #move_vertex_group_names_to_object_data. */ + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (ELEM(object->type, OB_MESH, OB_LATTICE, OB_GPENCIL)) { + /* This uses the fact that the active vertex group index starts counting at 1. */ + if (BKE_object_defgroup_active_index_get(object) == 0) { + BKE_object_defgroup_active_index_set(object, object->actdef); + } + } + } } } @@ -656,10 +803,8 @@ static bool geometry_node_is_293_legacy(const short node_type) switch (node_type) { /* Not legacy: No attribute inputs or outputs. */ case GEO_NODE_TRIANGULATE: - case GEO_NODE_EDGE_SPLIT: case GEO_NODE_TRANSFORM: case GEO_NODE_BOOLEAN: - case GEO_NODE_SUBDIVISION_SURFACE: case GEO_NODE_IS_VIEWPORT: case GEO_NODE_MESH_SUBDIVIDE: case GEO_NODE_MESH_PRIMITIVE_CUBE: @@ -706,16 +851,16 @@ static bool geometry_node_is_293_legacy(const short node_type) case GEO_NODE_COLLECTION_INFO: return false; - /* Maybe legacy: Transferred *all* attributes before, will not transfer all built-ins now. */ - case GEO_NODE_CURVE_ENDPOINTS: - case GEO_NODE_CURVE_TO_POINTS: - return false; - - /* Maybe legacy: Special case for grid names? Or finish patch from level set branch to generate - * a mesh for all grids in the volume. */ + /* Maybe legacy: Special case for grid names? Or finish patch from level set branch to + * generate a mesh for all grids in the volume. */ case GEO_NODE_VOLUME_TO_MESH: return false; + /* Legacy: Transferred *all* attributes before, will not transfer all built-ins now. */ + case GEO_NODE_LEGACY_CURVE_ENDPOINTS: + case GEO_NODE_LEGACY_CURVE_TO_POINTS: + return true; + /* Legacy: Attribute operation completely replaced by field nodes. */ case GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE: case GEO_NODE_LEGACY_ATTRIBUTE_MATH: @@ -727,10 +872,10 @@ static bool geometry_node_is_293_legacy(const short node_type) case GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR: case GEO_NODE_LEGACY_POINT_SCALE: case GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE: - case GEO_NODE_ATTRIBUTE_VECTOR_ROTATE: + case GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE: case GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP: case GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE: - case GEO_NODE_LECAGY_ATTRIBUTE_CLAMP: + case GEO_NODE_LEGACY_ATTRIBUTE_CLAMP: case GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH: case GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ: case GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ: @@ -752,15 +897,17 @@ static bool geometry_node_is_293_legacy(const short node_type) case GEO_NODE_LEGACY_CURVE_SET_HANDLES: return true; - /* Legacy: More complex attribute inputs or outputs. */ - case GEO_NODE_LEGACY_DELETE_GEOMETRY: /* Needs field input, domain drop-down. */ - case GEO_NODE_LEGACY_CURVE_SUBDIVIDE: /* Needs field count input. */ - case GEO_NODE_LEGACY_POINTS_TO_VOLUME: /* Needs field radius input. */ - case GEO_NODE_LEGACY_SELECT_BY_MATERIAL: /* Output anonymous attribute. */ - case GEO_NODE_LEGACY_POINT_TRANSLATE: /* Needs field inputs. */ - case GEO_NODE_LEGACY_POINT_INSTANCE: /* Needs field inputs. */ - case GEO_NODE_LEGACY_POINT_DISTRIBUTE: /* Needs field input, remove max for random mode. */ - case GEO_NODE_LEGACY_ATTRIBUTE_CONVERT: /* Attribute Capture, Store Attribute. */ + /* Legacy: More complex attribute inputs or outputs. */ + case GEO_NODE_LEGACY_SUBDIVISION_SURFACE: /* Used "crease" attribute. */ + case GEO_NODE_LEGACY_EDGE_SPLIT: /* Needs selection input version. */ + case GEO_NODE_LEGACY_DELETE_GEOMETRY: /* Needs field input, domain drop-down. */ + case GEO_NODE_LEGACY_CURVE_SUBDIVIDE: /* Needs field count input. */ + case GEO_NODE_LEGACY_POINTS_TO_VOLUME: /* Needs field radius input. */ + case GEO_NODE_LEGACY_SELECT_BY_MATERIAL: /* Output anonymous attribute. */ + case GEO_NODE_LEGACY_POINT_TRANSLATE: /* Needs field inputs. */ + case GEO_NODE_LEGACY_POINT_INSTANCE: /* Needs field inputs. */ + case GEO_NODE_LEGACY_POINT_DISTRIBUTE: /* Needs field input, remove max for random mode. */ + case GEO_NODE_LEGACY_ATTRIBUTE_CONVERT: /* Attribute Capture, Store Attribute. */ return true; } return false; @@ -785,6 +932,7 @@ static void version_geometry_nodes_change_legacy_names(bNodeTree *ntree) } } } + static bool seq_transform_origin_set(Sequence *seq, void *UNUSED(user_data)) { StripTransform *transform = seq->strip->transform; @@ -1111,6 +1259,15 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + if (ob->type == OB_GPENCIL) { + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (md->type == eGpencilModifierType_Lineart) { + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + lmd->flags |= LRT_GPENCIL_USE_CACHE; + lmd->chain_smooth_tolerance = 0.2f; + } + } + } } } @@ -1241,7 +1398,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_SUBDIVISION_SURFACE) { + if (node->type == GEO_NODE_LEGACY_SUBDIVISION_SURFACE) { if (node->storage == NULL) { NodeGeometrySubdivisionSurface *data = MEM_callocN( sizeof(NodeGeometrySubdivisionSurface), __func__); @@ -1312,11 +1469,6 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } if (!MAIN_VERSION_ATLEAST(bmain, 300, 22)) { - LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { - if (ntree->type == NTREE_GEOMETRY) { - version_geometry_nodes_change_legacy_names(ntree); - } - } if (!DNA_struct_elem_find( fd->filesdna, "LineartGpencilModifierData", "bool", "use_crease_on_smooth")) { LISTBASE_FOREACH (Object *, ob, &bmain->objects) { @@ -1428,6 +1580,142 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 26)) { + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (md->type == eModifierType_Nodes) { + version_geometry_nodes_add_attribute_input_settings((NodesModifierData *)md); + } + } + } + + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + switch (sl->spacetype) { + case SPACE_FILE: { + SpaceFile *sfile = (SpaceFile *)sl; + if (sfile->params) { + sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_2 | + FILE_PARAMS_FLAG_UNUSED_3 | FILE_PARAMS_FLAG_UNUSED_4); + } + + /* New default import type: Append with reuse. */ + if (sfile->asset_params) { + sfile->asset_params->import_type = FILE_ASSET_IMPORT_APPEND_REUSE; + } + break; + } + default: + break; + } + } + } + } + + /* Deprecate the random float node in favor of the random value node. */ + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type != NTREE_GEOMETRY) { + continue; + } + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type != FN_NODE_LEGACY_RANDOM_FLOAT) { + continue; + } + if (strstr(node->idname, "Legacy")) { + /* Make sure we haven't changed this idname already. */ + continue; + } + + char temp_idname[sizeof(node->idname)]; + BLI_strncpy(temp_idname, node->idname, sizeof(node->idname)); + + BLI_snprintf(node->idname, + sizeof(node->idname), + "FunctionNodeLegacy%s", + temp_idname + strlen("FunctionNode")); + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 29)) { + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + switch (sl->spacetype) { + case SPACE_SEQ: { + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : + &sl->regionbase; + LISTBASE_FOREACH (ARegion *, region, regionbase) { + if (region->regiontype == RGN_TYPE_WINDOW) { + region->v2d.max[1] = MAXSEQ; + } + } + break; + } + case SPACE_IMAGE: { + SpaceImage *sima = (SpaceImage *)sl; + sima->custom_grid_subdiv = 10; + break; + } + } + } + } + } + + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + version_geometry_nodes_change_legacy_names(ntree); + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 31)) { + /* Swap header with the tool header so the regular header is always on the edge. */ + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : + &sl->regionbase; + ARegion *region_tool = NULL, *region_head = NULL; + int region_tool_index = -1, region_head_index = -1, i; + LISTBASE_FOREACH_INDEX (ARegion *, region, regionbase, i) { + if (region->regiontype == RGN_TYPE_TOOL_HEADER) { + region_tool = region; + region_tool_index = i; + } + else if (region->regiontype == RGN_TYPE_HEADER) { + region_head = region; + region_head_index = i; + } + } + if ((region_tool && region_head) && (region_head_index > region_tool_index)) { + BLI_listbase_swaplinks(regionbase, region_tool, region_head); + } + } + } + } + + /* Set strip color tags to SEQUENCE_COLOR_NONE. */ + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->ed != NULL) { + SEQ_for_each_callback(&scene->ed->seqbase, do_versions_sequencer_color_tags, NULL); + } + } + + /* Show sequencer color tags by default. */ + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_SEQ) { + SpaceSeq *sseq = (SpaceSeq *)sl; + sseq->timeline_overlay.flag |= SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG; + } + } + } + } + } + /** * Versioning code until next subversion bump goes here. * @@ -1439,12 +1727,5 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) */ { /* Keep this block, even when empty. */ - LISTBASE_FOREACH (Object *, ob, &bmain->objects) { - LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { - if (md->type == eModifierType_Nodes) { - version_geometry_nodes_add_attribute_input_settings((NodesModifierData *)md); - } - } - } } } diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 152ef79a38f..c383c1cc4e5 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -160,7 +160,8 @@ static void blo_update_defaults_screen(bScreen *screen, 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_DURATION | SEQ_TIMELINE_SHOW_GRID | + SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG; seq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED; } else if (area->spacetype == SPACE_TEXT) { diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index f4853ff803f..cd365b6be78 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -291,6 +291,16 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme) btheme->space_sequencer.grid[3] = 255; } + if (!USER_VERSION_ATLEAST(300, 30)) { + FROM_DEFAULT_V4_UCHAR(space_node.wire); + } + + if (!USER_VERSION_ATLEAST(300, 31)) { + for (int i = 0; i < SEQUENCE_COLOR_TOT; ++i) { + FROM_DEFAULT_V4_UCHAR(strip_color[i].color); + } + } + /** * Versioning code until next subversion bump goes here. * @@ -728,7 +738,7 @@ void blo_do_versions_userdef(UserDef *userdef) } } - /* patch to set Dupli Lightprobes and Grease Pencil */ + /* Patch to set dupli light-probes and grease-pencil. */ if (!USER_VERSION_ATLEAST(280, 58)) { userdef->dupflag |= USER_DUP_LIGHTPROBE; userdef->dupflag |= USER_DUP_GPENCIL; diff --git a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c index 86a7d8153f0..103d7621f87 100644 --- a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c +++ b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c @@ -1135,11 +1135,13 @@ static BMVert *bm_face_split_edgenet_partial_connect(BMesh *bm, BMVert *v_delimi BMVert *v_other = BM_edge_other_vert(e_face_init ? e_face_init : v_delimit->e, v_delimit); BLI_SMALLSTACK_PUSH(search, v_other); - BM_elem_flag_disable(v_other, VERT_NOT_IN_STACK); + if (BM_elem_flag_test(v_other, VERT_NOT_IN_STACK)) { + BM_elem_flag_disable(v_other, VERT_NOT_IN_STACK); + BLI_linklist_prepend_alloca(&vert_stack, v_other); + } while ((v_other = BLI_SMALLSTACK_POP(search))) { BLI_assert(BM_elem_flag_test(v_other, VERT_NOT_IN_STACK) == false); - BLI_linklist_prepend_alloca(&vert_stack, v_other); BMEdge *e_iter = v_other->e; do { BMVert *v_step = BM_edge_other_vert(e_iter, v_other); @@ -1147,6 +1149,7 @@ static BMVert *bm_face_split_edgenet_partial_connect(BMesh *bm, BMVert *v_delimi if (BM_elem_flag_test(v_step, VERT_NOT_IN_STACK)) { BM_elem_flag_disable(v_step, VERT_NOT_IN_STACK); BLI_SMALLSTACK_PUSH(search, v_step); + BLI_linklist_prepend_alloca(&vert_stack, v_step); } } } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_other)) != v_other->e); diff --git a/source/blender/bmesh/intern/bmesh_structure.c b/source/blender/bmesh/intern/bmesh_structure.c index d5d72cd4ba3..1f1ad0bae5b 100644 --- a/source/blender/bmesh/intern/bmesh_structure.c +++ b/source/blender/bmesh/intern/bmesh_structure.c @@ -86,7 +86,8 @@ void bmesh_disk_vert_replace(BMEdge *e, BMVert *v_dst, BMVert *v_src) /** * \section bm_cycles BMesh Cycles - * (this is somewhat outdate, though bits of its API are still used) - joeedh + * + * NOTE(@joeedh): this is somewhat outdated, though bits of its API are still used. * * Cycles are circular doubly linked lists that form the basis of adjacency * information in the BME modeler. Full adjacency relations can be derived diff --git a/source/blender/bmesh/intern/bmesh_walkers.c b/source/blender/bmesh/intern/bmesh_walkers.c index 8bdf205babd..b8fdd534842 100644 --- a/source/blender/bmesh/intern/bmesh_walkers.c +++ b/source/blender/bmesh/intern/bmesh_walkers.c @@ -31,8 +31,7 @@ #include "bmesh_walkers_private.h" /** - * - joeedh - - * design notes: + * NOTE(@joeedh): Details on design. * * original design: walkers directly emulation recursive functions. * functions save their state onto a worklist, and also add new states diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index e306fe47770..1f759e9ef43 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -111,7 +111,7 @@ typedef struct EdgeHalf { bool is_bev; /** Is e->v2 the vertex at this end? */ bool is_rev; - /** Is e a seam for custom loopdata (e.g., UVs)? */ + /** Is e a seam for custom loop-data (e.g., UV's). */ bool is_seam; /** Used during the custom profile orientation pass. */ bool visited_rpo; @@ -3582,7 +3582,7 @@ static void adjust_the_cycle_or_chain(BoundVert *vstart, bool iscycle) } /* Residue np + 2*i (if cycle) else np - 1 + 2*i: - * right offset for parm i matches its spec; weighted. */ + * right offset for parameter i matches its spec; weighted. */ int row = iscycle ? np + 2 * i : np - 1 + 2 * i; EIG_linear_solver_matrix_add(solver, row, i, weight); EIG_linear_solver_right_hand_side_add(solver, 0, row, weight * eright->offset_r); @@ -3595,7 +3595,7 @@ static void adjust_the_cycle_or_chain(BoundVert *vstart, bool iscycle) #endif /* Residue np + 2*i + 1 (if cycle) else np - 1 + 2*i + 1: - * left offset for parm i matches its spec; weighted. */ + * left offset for parameter i matches its spec; weighted. */ row = row + 1; EIG_linear_solver_matrix_add( solver, row, (i == np - 1) ? 0 : i + 1, weight * v->adjchain->sinratio); diff --git a/source/blender/compositor/COM_defines.h b/source/blender/compositor/COM_defines.h index 73c4343a230..9991414aba4 100644 --- a/source/blender/compositor/COM_defines.h +++ b/source/blender/compositor/COM_defines.h @@ -18,11 +18,14 @@ #pragma once +#include "BLI_float2.hh" #include "BLI_index_range.hh" #include "BLI_rect.h" namespace blender::compositor { +using Size2f = float2; + enum class eExecutionModel { /** * Operations are executed from outputs to inputs grouped in execution groups and rendered @@ -121,26 +124,7 @@ constexpr float COM_PREVIEW_SIZE = 140.f; constexpr float COM_RULE_OF_THIRDS_DIVIDER = 100.0f; constexpr float COM_BLUR_BOKEH_PIXELS = 512; -constexpr rcti COM_SINGLE_ELEM_AREA = {0, 1, 0, 1}; - -constexpr IndexRange XRange(const rcti &area) -{ - return IndexRange(area.xmin, area.xmax - area.xmin); -} - -constexpr IndexRange YRange(const rcti &area) -{ - return IndexRange(area.ymin, area.ymax - area.ymin); -} - -constexpr IndexRange XRange(const rcti *area) -{ - return XRange(*area); -} - -constexpr IndexRange YRange(const rcti *area) -{ - return YRange(*area); -} +constexpr rcti COM_AREA_NONE = {0, 0, 0, 0}; +constexpr rcti COM_CONSTANT_INPUT_AREA_OF_INTEREST = COM_AREA_NONE; } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_BufferOperation.cc b/source/blender/compositor/intern/COM_BufferOperation.cc index cafdff89c8e..c6530cf6bd1 100644 --- a/source/blender/compositor/intern/COM_BufferOperation.cc +++ b/source/blender/compositor/intern/COM_BufferOperation.cc @@ -24,12 +24,7 @@ BufferOperation::BufferOperation(MemoryBuffer *buffer, DataType data_type) { buffer_ = buffer; inflated_buffer_ = nullptr; - /* TODO: Implement a MemoryBuffer get_size() method returning a Size2d type. Shorten following - * code to: set_resolution(buffer.get_size()) */ - unsigned int resolution[2]; - resolution[0] = buffer->getWidth(); - resolution[1] = buffer->getHeight(); - setResolution(resolution); + set_canvas(buffer->get_rect()); addOutputSocket(data_type); flags.is_constant_operation = buffer_->is_a_single_elem(); flags.is_fullframe_operation = false; diff --git a/source/blender/compositor/intern/COM_CompositorContext.cc b/source/blender/compositor/intern/COM_CompositorContext.cc index a93820b66dc..f5f490b0bf6 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.cc +++ b/source/blender/compositor/intern/COM_CompositorContext.cc @@ -43,6 +43,12 @@ int CompositorContext::getFramenumber() const return m_rd->cfra; } +Size2f CompositorContext::get_render_size() const +{ + return {getRenderData()->xsch * getRenderPercentageAsFactor(), + getRenderData()->ysch * getRenderPercentageAsFactor()}; +} + eExecutionModel CompositorContext::get_execution_model() const { if (U.experimental.use_full_frame_compositor) { diff --git a/source/blender/compositor/intern/COM_CompositorContext.h b/source/blender/compositor/intern/COM_CompositorContext.h index c6e83f93777..ae298c5a65a 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.h +++ b/source/blender/compositor/intern/COM_CompositorContext.h @@ -288,6 +288,8 @@ class CompositorContext { return m_rd->size * 0.01f; } + Size2f get_render_size() const; + /** * Get active execution model. */ diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index 4b103c21c75..ee77beb8a82 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -460,14 +460,16 @@ NodeOperation *COM_convert_data_type(const NodeOperationOutput &from, const Node return nullptr; } -void COM_convert_resolution(NodeOperationBuilder &builder, - NodeOperationOutput *fromSocket, - NodeOperationInput *toSocket) +void COM_convert_canvas(NodeOperationBuilder &builder, + NodeOperationOutput *fromSocket, + NodeOperationInput *toSocket) { /* Data type conversions are executed before resolutions to ensure convert operations have * resolution. This method have to ensure same datatypes are linked for new operations. */ BLI_assert(fromSocket->getDataType() == toSocket->getDataType()); + ResizeMode mode = toSocket->getResizeMode(); + BLI_assert(mode != ResizeMode::None); NodeOperation *toOperation = &toSocket->getOperation(); const float toWidth = toOperation->getWidth(); @@ -477,13 +479,12 @@ void COM_convert_resolution(NodeOperationBuilder &builder, const float fromHeight = fromOperation->getHeight(); bool doCenter = false; bool doScale = false; - float addX = (toWidth - fromWidth) / 2.0f; - float addY = (toHeight - fromHeight) / 2.0f; float scaleX = 0; float scaleY = 0; switch (mode) { case ResizeMode::None: + case ResizeMode::Align: break; case ResizeMode::Center: doCenter = true; @@ -518,63 +519,74 @@ void COM_convert_resolution(NodeOperationBuilder &builder, break; } - if (doCenter) { - NodeOperation *first = nullptr; - ScaleOperation *scaleOperation = nullptr; - if (doScale) { - scaleOperation = new ScaleRelativeOperation(fromSocket->getDataType()); - scaleOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); - scaleOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); - first = scaleOperation; - SetValueOperation *sxop = new SetValueOperation(); - sxop->setValue(scaleX); - builder.addLink(sxop->getOutputSocket(), scaleOperation->getInputSocket(1)); - SetValueOperation *syop = new SetValueOperation(); - syop->setValue(scaleY); - builder.addLink(syop->getOutputSocket(), scaleOperation->getInputSocket(2)); - builder.addOperation(sxop); - builder.addOperation(syop); - - unsigned int resolution[2] = {fromOperation->getWidth(), fromOperation->getHeight()}; - scaleOperation->setResolution(resolution); - sxop->setResolution(resolution); - syop->setResolution(resolution); - builder.addOperation(scaleOperation); - } + float addX = doCenter ? (toWidth - fromWidth) / 2.0f : 0.0f; + float addY = doCenter ? (toHeight - fromHeight) / 2.0f : 0.0f; + NodeOperation *first = nullptr; + ScaleOperation *scaleOperation = nullptr; + if (doScale) { + scaleOperation = new ScaleRelativeOperation(fromSocket->getDataType()); + scaleOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); + scaleOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); + first = scaleOperation; + SetValueOperation *sxop = new SetValueOperation(); + sxop->setValue(scaleX); + builder.addLink(sxop->getOutputSocket(), scaleOperation->getInputSocket(1)); + SetValueOperation *syop = new SetValueOperation(); + syop->setValue(scaleY); + builder.addLink(syop->getOutputSocket(), scaleOperation->getInputSocket(2)); + builder.addOperation(sxop); + builder.addOperation(syop); - TranslateOperation *translateOperation = new TranslateOperation(toSocket->getDataType()); - translateOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); - translateOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); - if (!first) { - first = translateOperation; + rcti scale_canvas = fromOperation->get_canvas(); + if (builder.context().get_execution_model() == eExecutionModel::FullFrame) { + ScaleOperation::scale_area(scale_canvas, scaleX, scaleY); + scale_canvas.xmax = scale_canvas.xmin + toOperation->getWidth(); + scale_canvas.ymax = scale_canvas.ymin + toOperation->getHeight(); + addX = 0; + addY = 0; } - SetValueOperation *xop = new SetValueOperation(); - xop->setValue(addX); - builder.addLink(xop->getOutputSocket(), translateOperation->getInputSocket(1)); - SetValueOperation *yop = new SetValueOperation(); - yop->setValue(addY); - builder.addLink(yop->getOutputSocket(), translateOperation->getInputSocket(2)); - builder.addOperation(xop); - builder.addOperation(yop); + scaleOperation->set_canvas(scale_canvas); + sxop->set_canvas(scale_canvas); + syop->set_canvas(scale_canvas); + builder.addOperation(scaleOperation); + } - unsigned int resolution[2] = {toOperation->getWidth(), toOperation->getHeight()}; - translateOperation->setResolution(resolution); - xop->setResolution(resolution); - yop->setResolution(resolution); - builder.addOperation(translateOperation); + TranslateOperation *translateOperation = new TranslateOperation(toSocket->getDataType()); + translateOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); + translateOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); + if (!first) { + first = translateOperation; + } + SetValueOperation *xop = new SetValueOperation(); + xop->setValue(addX); + builder.addLink(xop->getOutputSocket(), translateOperation->getInputSocket(1)); + SetValueOperation *yop = new SetValueOperation(); + yop->setValue(addY); + builder.addLink(yop->getOutputSocket(), translateOperation->getInputSocket(2)); + builder.addOperation(xop); + builder.addOperation(yop); - if (doScale) { - translateOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); - builder.addLink(scaleOperation->getOutputSocket(), translateOperation->getInputSocket(0)); - } + rcti translate_canvas = toOperation->get_canvas(); + if (mode == ResizeMode::Align) { + translate_canvas.xmax = translate_canvas.xmin + fromWidth; + translate_canvas.ymax = translate_canvas.ymin + fromHeight; + } + translateOperation->set_canvas(translate_canvas); + xop->set_canvas(translate_canvas); + yop->set_canvas(translate_canvas); + builder.addOperation(translateOperation); - /* remove previous link and replace */ - builder.removeInputLink(toSocket); - first->getInputSocket(0)->setResizeMode(ResizeMode::None); - toSocket->setResizeMode(ResizeMode::None); - builder.addLink(fromSocket, first->getInputSocket(0)); - builder.addLink(translateOperation->getOutputSocket(), toSocket); + if (doScale) { + translateOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); + builder.addLink(scaleOperation->getOutputSocket(), translateOperation->getInputSocket(0)); } + + /* remove previous link and replace */ + builder.removeInputLink(toSocket); + first->getInputSocket(0)->setResizeMode(ResizeMode::None); + toSocket->setResizeMode(ResizeMode::None); + builder.addLink(fromSocket, first->getInputSocket(0)); + builder.addLink(translateOperation->getOutputSocket(), toSocket); } } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Converter.h b/source/blender/compositor/intern/COM_Converter.h index 28136437103..7f0402d4e70 100644 --- a/source/blender/compositor/intern/COM_Converter.h +++ b/source/blender/compositor/intern/COM_Converter.h @@ -65,8 +65,8 @@ NodeOperation *COM_convert_data_type(const NodeOperationOutput &from, * \note Conversion logic is implemented in this function. * \see InputSocketResizeMode for the possible conversions. */ -void COM_convert_resolution(NodeOperationBuilder &builder, - NodeOperationOutput *fromSocket, - NodeOperationInput *toSocket); +void COM_convert_canvas(NodeOperationBuilder &builder, + NodeOperationOutput *fromSocket, + NodeOperationInput *toSocket); } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index f2dcba65b7c..9e47aa9fcb5 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -162,8 +162,10 @@ int DebugInfo::graphviz_operation(const ExecutionSystem *system, len += snprintf(str + len, maxlen > len ? maxlen - len : 0, - "#%d (%u,%u)", + "#%d (%i,%i) (%u,%u)", operation->get_id(), + operation->get_canvas().xmin, + operation->get_canvas().ymin, operation->getWidth(), operation->getHeight()); diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc index bd3a481d691..c44a168390b 100644 --- a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc @@ -74,37 +74,61 @@ void FullFrameExecutionModel::determine_areas_to_render_and_reads() } } -Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op) +/** + * Returns input buffers with an offset relative to given output coordinates. Returned memory + * buffers must be deleted. + */ +Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op, + const int output_x, + const int output_y) { const int num_inputs = op->getNumberOfInputSockets(); Vector<MemoryBuffer *> inputs_buffers(num_inputs); for (int i = 0; i < num_inputs; i++) { - NodeOperation *input_op = op->get_input_operation(i); - inputs_buffers[i] = active_buffers_.get_rendered_buffer(input_op); + NodeOperation *input = op->get_input_operation(i); + const int offset_x = (input->get_canvas().xmin - op->get_canvas().xmin) + output_x; + const int offset_y = (input->get_canvas().ymin - op->get_canvas().ymin) + output_y; + MemoryBuffer *buf = active_buffers_.get_rendered_buffer(input); + + rcti rect = buf->get_rect(); + BLI_rcti_translate(&rect, offset_x, offset_y); + inputs_buffers[i] = new MemoryBuffer( + buf->getBuffer(), buf->get_num_channels(), rect, buf->is_a_single_elem()); } return inputs_buffers; } -MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op) +MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op, + const int output_x, + const int output_y) { - rcti op_rect; - BLI_rcti_init(&op_rect, 0, op->getWidth(), 0, op->getHeight()); + rcti rect; + BLI_rcti_init(&rect, output_x, output_x + op->getWidth(), output_y, output_y + op->getHeight()); const DataType data_type = op->getOutputSocket(0)->getDataType(); const bool is_a_single_elem = op->get_flags().is_constant_operation; - return new MemoryBuffer(data_type, op_rect, is_a_single_elem); + return new MemoryBuffer(data_type, rect, is_a_single_elem); } void FullFrameExecutionModel::render_operation(NodeOperation *op) { - Vector<MemoryBuffer *> input_bufs = get_input_buffers(op); + /* Output has no offset for easier image algorithms implementation on operations. */ + constexpr int output_x = 0; + constexpr int output_y = 0; const bool has_outputs = op->getNumberOfOutputSockets() > 0; - MemoryBuffer *op_buf = has_outputs ? create_operation_buffer(op) : nullptr; + MemoryBuffer *op_buf = has_outputs ? create_operation_buffer(op, output_x, output_y) : nullptr; if (op->getWidth() > 0 && op->getHeight() > 0) { - Span<rcti> areas = active_buffers_.get_areas_to_render(op); + Vector<MemoryBuffer *> input_bufs = get_input_buffers(op, output_x, output_y); + const int op_offset_x = output_x - op->get_canvas().xmin; + const int op_offset_y = output_y - op->get_canvas().ymin; + Span<rcti> areas = active_buffers_.get_areas_to_render(op, op_offset_x, op_offset_y); op->render(op_buf, areas, input_bufs); DebugInfo::operation_rendered(op, op_buf); + + for (MemoryBuffer *buf : input_bufs) { + delete buf; + } } /* Even if operation has no resolution set the empty buffer. It will be clipped with a * TranslateOperation from convert resolutions if linked to an operation with resolution. */ @@ -190,7 +214,8 @@ void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *output_op std::pair<NodeOperation *, rcti> pair = stack.pop_last(); NodeOperation *operation = pair.first; const rcti &render_area = pair.second; - if (active_buffers_.is_area_registered(operation, render_area)) { + if (BLI_rcti_is_empty(&render_area) || + active_buffers_.is_area_registered(operation, render_area)) { continue; } @@ -199,12 +224,11 @@ void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *output_op const int num_inputs = operation->getNumberOfInputSockets(); for (int i = 0; i < num_inputs; i++) { NodeOperation *input_op = operation->get_input_operation(i); - rcti input_op_rect, input_area; - BLI_rcti_init(&input_op_rect, 0, input_op->getWidth(), 0, input_op->getHeight()); + rcti input_area; operation->get_area_of_interest(input_op, render_area, input_area); /* Ensure area of interest is within operation bounds, cropping areas outside. */ - BLI_rcti_isect(&input_area, &input_op_rect, &input_area); + BLI_rcti_isect(&input_area, &input_op->get_canvas(), &input_area); stack.append({input_op, input_area}); } @@ -243,9 +267,8 @@ void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, r BLI_assert(output_op->isOutputOperation(context_.isRendering())); /* By default return operation bounds (no border). */ - const int op_width = output_op->getWidth(); - const int op_height = output_op->getHeight(); - BLI_rcti_init(&r_area, 0, op_width, 0, op_height); + rcti canvas = output_op->get_canvas(); + r_area = canvas; const bool has_viewer_border = border_.use_viewer_border && (output_op->get_flags().is_viewer_operation || @@ -255,12 +278,13 @@ void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, r /* Get border with normalized coordinates. */ const rctf *norm_border = has_viewer_border ? border_.viewer_border : border_.render_border; - /* Return de-normalized border. */ - BLI_rcti_init(&r_area, - norm_border->xmin * op_width, - norm_border->xmax * op_width, - norm_border->ymin * op_height, - norm_border->ymax * op_height); + /* Return de-normalized border within canvas. */ + const int w = output_op->getWidth(); + const int h = output_op->getHeight(); + r_area.xmin = canvas.xmin + norm_border->xmin * w; + r_area.xmax = canvas.xmin + norm_border->xmax * w; + r_area.ymin = canvas.ymin + norm_border->ymin * h; + r_area.ymax = canvas.ymin + norm_border->ymax * h; } } diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.h b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h index f75d4f1afdc..66dfb8f052c 100644 --- a/source/blender/compositor/intern/COM_FullFrameExecutionModel.h +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h @@ -61,8 +61,10 @@ class FullFrameExecutionModel : public ExecutionModel { void determine_areas_to_render_and_reads(); void render_operations(); void render_output_dependencies(NodeOperation *output_op); - Vector<MemoryBuffer *> get_input_buffers(NodeOperation *op); - MemoryBuffer *create_operation_buffer(NodeOperation *op); + Vector<MemoryBuffer *> get_input_buffers(NodeOperation *op, + const int output_x, + const int output_y); + MemoryBuffer *create_operation_buffer(NodeOperation *op, const int output_x, const int output_y); void render_operation(NodeOperation *op); void operation_finished(NodeOperation *operation); diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index 5327be50b53..f57f0f055bf 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -122,6 +122,8 @@ void MemoryBuffer::set_strides() this->elem_stride = m_num_channels; this->row_stride = getWidth() * m_num_channels; } + to_positive_x_stride_ = m_rect.xmin < 0 ? -m_rect.xmin + 1 : (m_rect.xmin == 0 ? 1 : 0); + to_positive_y_stride_ = m_rect.ymin < 0 ? -m_rect.ymin + 1 : (m_rect.ymin == 0 ? 1 : 0); } void MemoryBuffer::clear() diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index f730d53acec..9e173f73f63 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -114,6 +114,12 @@ class MemoryBuffer { */ bool owns_data_; + /** Stride to make any x coordinate within buffer positive (non-zero). */ + int to_positive_x_stride_; + + /** Stride to make any y coordinate within buffer positive (non-zero). */ + int to_positive_y_stride_; + public: /** * \brief construct new temporarily MemoryBuffer for an area @@ -166,9 +172,9 @@ class MemoryBuffer { /** * Get offset needed to jump from buffer start to given coordinates. */ - int get_coords_offset(int x, int y) const + intptr_t get_coords_offset(int x, int y) const { - return (y - m_rect.ymin) * row_stride + (x - m_rect.xmin) * elem_stride; + return ((intptr_t)y - m_rect.ymin) * row_stride + ((intptr_t)x - m_rect.xmin) * elem_stride; } /** @@ -176,7 +182,7 @@ class MemoryBuffer { */ float *get_elem(int x, int y) { - BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + BLI_assert(has_coords(x, y)); return m_buffer + get_coords_offset(x, y); } @@ -185,7 +191,7 @@ class MemoryBuffer { */ const float *get_elem(int x, int y) const { - BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + BLI_assert(has_coords(x, y)); return m_buffer + get_coords_offset(x, y); } @@ -196,7 +202,7 @@ class MemoryBuffer { void read_elem_checked(int x, int y, float *out) const { - if (x < m_rect.xmin || x >= m_rect.xmax || y < m_rect.ymin || y >= m_rect.ymax) { + if (!has_coords(x, y)) { clear_elem(out); } else { @@ -206,12 +212,7 @@ class MemoryBuffer { void read_elem_checked(float x, float y, float *out) const { - if (x < m_rect.xmin || x >= m_rect.xmax || y < m_rect.ymin || y >= m_rect.ymax) { - clear_elem(out); - } - else { - read_elem(x, y, out); - } + read_elem_checked(floor_x(x), floor_y(y), out); } void read_elem_bilinear(float x, float y, float *out) const @@ -286,8 +287,7 @@ class MemoryBuffer { */ float &get_value(int x, int y, int channel) { - BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && - channel >= 0 && channel < m_num_channels); + BLI_assert(has_coords(x, y) && channel >= 0 && channel < m_num_channels); return m_buffer[get_coords_offset(x, y) + channel]; } @@ -296,8 +296,7 @@ class MemoryBuffer { */ const float &get_value(int x, int y, int channel) const { - BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && - channel >= 0 && channel < m_num_channels); + BLI_assert(has_coords(x, y) && channel >= 0 && channel < m_num_channels); return m_buffer[get_coords_offset(x, y) + channel]; } @@ -306,7 +305,7 @@ class MemoryBuffer { */ const float *get_row_end(int y) const { - BLI_assert(y >= 0 && y < getHeight()); + BLI_assert(has_y(y)); return m_buffer + (is_a_single_elem() ? m_num_channels : get_coords_offset(getWidth(), y)); } @@ -681,6 +680,34 @@ class MemoryBuffer { return y - m_rect.ymin; } + template<typename T> bool has_coords(T x, T y) const + { + return has_x(x) && has_y(y); + } + + template<typename T> bool has_x(T x) const + { + return x >= m_rect.xmin && x < m_rect.xmax; + } + + template<typename T> bool has_y(T y) const + { + return y >= m_rect.ymin && y < m_rect.ymax; + } + + /* Fast `floor(..)` functions. The caller should check result is within buffer bounds. + * It `ceil(..)` in near cases and when given coordinate + * is negative and less than buffer rect `min - 1`. */ + int floor_x(float x) const + { + return (int)(x + to_positive_x_stride_) - to_positive_x_stride_; + } + + int floor_y(float y) const + { + return (int)(y + to_positive_y_stride_) - to_positive_y_stride_; + } + void copy_single_elem_from(const MemoryBuffer *src, int channel_offset, int elem_size, diff --git a/source/blender/compositor/intern/COM_NodeOperation.cc b/source/blender/compositor/intern/COM_NodeOperation.cc index 3bbd1b22d60..a6a395261f2 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.cc +++ b/source/blender/compositor/intern/COM_NodeOperation.cc @@ -35,12 +35,29 @@ namespace blender::compositor { NodeOperation::NodeOperation() { - this->m_resolutionInputSocketIndex = 0; - this->m_width = 0; - this->m_height = 0; + canvas_input_index_ = 0; + canvas_ = COM_AREA_NONE; this->m_btree = nullptr; } +/** Get constant value when operation is constant, otherwise return default_value. */ +float NodeOperation::get_constant_value_default(float default_value) +{ + BLI_assert(m_outputs.size() > 0 && getOutputSocket()->getDataType() == DataType::Value); + return *get_constant_elem_default(&default_value); +} + +/** Get constant elem when operation is constant, otherwise return default_elem. */ +const float *NodeOperation::get_constant_elem_default(const float *default_elem) +{ + BLI_assert(m_outputs.size() > 0); + if (get_flags().is_constant_operation) { + return static_cast<ConstantOperation *>(this)->get_constant_elem(); + } + + return default_elem; +} + /** * Generate a hash that identifies the operation result in the current execution. * Requires `hash_output_params` to be implemented, otherwise `std::nullopt` is returned. @@ -48,7 +65,7 @@ NodeOperation::NodeOperation() */ std::optional<NodeOperationHash> NodeOperation::generate_hash() { - params_hash_ = get_default_hash_2(m_width, m_height); + params_hash_ = get_default_hash_2(canvas_.xmin, canvas_.xmax); /* Hash subclasses params. */ is_hash_output_params_implemented_ = true; @@ -57,7 +74,11 @@ std::optional<NodeOperationHash> NodeOperation::generate_hash() return std::nullopt; } - hash_param(getOutputSocket()->getDataType()); + hash_params(canvas_.ymin, canvas_.ymax); + if (m_outputs.size() > 0) { + BLI_assert(m_outputs.size() == 1); + hash_param(this->getOutputSocket()->getDataType()); + } NodeOperationHash hash; hash.params_hash_ = params_hash_; @@ -108,48 +129,46 @@ void NodeOperation::addOutputSocket(DataType datatype) m_outputs.append(NodeOperationOutput(this, datatype)); } -void NodeOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void NodeOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - unsigned int used_resolution_index = 0; - if (m_resolutionInputSocketIndex == RESOLUTION_INPUT_ANY) { + unsigned int used_canvas_index = 0; + if (canvas_input_index_ == RESOLUTION_INPUT_ANY) { for (NodeOperationInput &input : m_inputs) { - unsigned int any_resolution[2] = {0, 0}; - input.determineResolution(any_resolution, preferredResolution); - if (any_resolution[0] * any_resolution[1] > 0) { - resolution[0] = any_resolution[0]; - resolution[1] = any_resolution[1]; + rcti any_area = COM_AREA_NONE; + const bool determined = input.determine_canvas(preferred_area, any_area); + if (determined) { + r_area = any_area; break; } - used_resolution_index += 1; + used_canvas_index += 1; } } - else if (m_resolutionInputSocketIndex < m_inputs.size()) { - NodeOperationInput &input = m_inputs[m_resolutionInputSocketIndex]; - input.determineResolution(resolution, preferredResolution); - used_resolution_index = m_resolutionInputSocketIndex; + else if (canvas_input_index_ < m_inputs.size()) { + NodeOperationInput &input = m_inputs[canvas_input_index_]; + input.determine_canvas(preferred_area, r_area); + used_canvas_index = canvas_input_index_; } - if (modify_determined_resolution_fn_) { - modify_determined_resolution_fn_(resolution); + if (modify_determined_canvas_fn_) { + modify_determined_canvas_fn_(r_area); } - unsigned int temp2[2] = {resolution[0], resolution[1]}; - unsigned int temp[2]; + rcti unused_area; + const rcti &local_preferred_area = r_area; for (unsigned int index = 0; index < m_inputs.size(); index++) { - if (index == used_resolution_index) { + if (index == used_canvas_index) { continue; } NodeOperationInput &input = m_inputs[index]; if (input.isConnected()) { - input.determineResolution(temp, temp2); + input.determine_canvas(local_preferred_area, unused_area); } } } -void NodeOperation::setResolutionInputSocketIndex(unsigned int index) +void NodeOperation::set_canvas_input_index(unsigned int index) { - this->m_resolutionInputSocketIndex = index; + this->canvas_input_index_ = index; } void NodeOperation::init_data() @@ -185,6 +204,28 @@ void NodeOperation::deinitExecution() { /* pass */ } + +void NodeOperation::set_canvas(const rcti &canvas_area) +{ + canvas_ = canvas_area; + flags.is_canvas_set = true; +} + +const rcti &NodeOperation::get_canvas() const +{ + return canvas_; +} + +/** + * Mainly used for re-determining canvas of constant operations in cases where preferred canvas + * depends on the constant element. + */ +void NodeOperation::unset_canvas() +{ + BLI_assert(m_inputs.size() == 0); + flags.is_canvas_set = false; +} + SocketReader *NodeOperation::getInputSocketReader(unsigned int inputSocketIndex) { return this->getInputSocket(inputSocketIndex)->getReader(); @@ -260,7 +301,7 @@ void NodeOperation::get_area_of_interest(const int input_idx, /* Non full-frame operations never implement this method. To ensure correctness assume * whole area is used. */ NodeOperation *input_op = getInputOperation(input_idx); - BLI_rcti_init(&r_input_area, 0, input_op->getWidth(), 0, input_op->getHeight()); + r_input_area = input_op->get_canvas(); } } @@ -420,12 +461,16 @@ SocketReader *NodeOperationInput::getReader() return nullptr; } -void NodeOperationInput::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +/** + * \return Whether canvas area could be determined. + */ +bool NodeOperationInput::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (m_link) { - m_link->determineResolution(resolution, preferredResolution); + m_link->determine_canvas(preferred_area, r_area); + return !BLI_rcti_is_empty(&r_area); } + return false; } /****************** @@ -437,18 +482,16 @@ NodeOperationOutput::NodeOperationOutput(NodeOperation *op, DataType datatype) { } -void NodeOperationOutput::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void NodeOperationOutput::determine_canvas(const rcti &preferred_area, rcti &r_area) { NodeOperation &operation = getOperation(); - if (operation.get_flags().is_resolution_set) { - resolution[0] = operation.getWidth(); - resolution[1] = operation.getHeight(); + if (operation.get_flags().is_canvas_set) { + r_area = operation.get_canvas(); } else { - operation.determineResolution(resolution, preferredResolution); - if (resolution[0] > 0 && resolution[1] > 0) { - operation.setResolution(resolution); + operation.determine_canvas(preferred_area, r_area); + if (!BLI_rcti_is_empty(&r_area)) { + operation.set_canvas(r_area); } } } @@ -470,8 +513,8 @@ std::ostream &operator<<(std::ostream &os, const NodeOperationFlags &node_operat if (node_operation_flags.use_viewer_border) { os << "view_border,"; } - if (node_operation_flags.is_resolution_set) { - os << "resolution_set,"; + if (node_operation_flags.is_canvas_set) { + os << "canvas_set,"; } if (node_operation_flags.is_set_operation) { os << "set_operation,"; diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h index ef7cf319222..f507665bee3 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.h +++ b/source/blender/compositor/intern/COM_NodeOperation.h @@ -62,9 +62,13 @@ enum class ResizeMode { /** \brief Center the input image to the center of the working area of the node, no resizing * occurs */ Center = NS_CR_CENTER, - /** \brief The bottom left of the input image is the bottom left of the working area of the node, - * no resizing occurs */ + /** No resizing or translation. */ None = NS_CR_NONE, + /** + * Input image is translated so that its bottom left matches the bottom left of the working area + * of the node, no resizing occurs. + */ + Align = 100, /** \brief Fit the width of the input image to the width of the working area of the node */ FitWidth = NS_CR_FIT_WIDTH, /** \brief Fit the height of the input image to the height of the working area of the node */ @@ -130,7 +134,7 @@ class NodeOperationInput { SocketReader *getReader(); - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + bool determine_canvas(const rcti &preferred_area, rcti &r_area); #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") @@ -158,12 +162,7 @@ class NodeOperationOutput { return m_datatype; } - /** - * \brief determine the resolution of this data going through this socket - * \param resolution: the result of this operation - * \param preferredResolution: the preferable resolution as no resolution could be determined - */ - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + void determine_canvas(const rcti &preferred_area, rcti &r_area); #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") @@ -211,9 +210,9 @@ struct NodeOperationFlags { bool use_viewer_border : 1; /** - * Is the resolution of the operation set. + * Is the canvas of the operation set. */ - bool is_resolution_set : 1; + bool is_canvas_set : 1; /** * Is this a set operation (value, color, vector). @@ -257,7 +256,7 @@ struct NodeOperationFlags { open_cl = false; use_render_border = false; use_viewer_border = false; - is_resolution_set = false; + is_canvas_set = false; is_set_operation = false; is_read_buffer_operation = false; is_write_buffer_operation = false; @@ -324,11 +323,11 @@ class NodeOperation { bool is_hash_output_params_implemented_; /** - * \brief the index of the input socket that will be used to determine the resolution + * \brief the index of the input socket that will be used to determine the canvas */ - unsigned int m_resolutionInputSocketIndex; + unsigned int canvas_input_index_; - std::function<void(unsigned int resolution[2])> modify_determined_resolution_fn_; + std::function<void(rcti &canvas)> modify_determined_canvas_fn_; /** * \brief mutex reference for very special node initializations @@ -352,15 +351,7 @@ class NodeOperation { */ eExecutionModel execution_model_; - /** - * Width of the output of this operation. - */ - unsigned int m_width; - - /** - * Height of the output of this operation. - */ - unsigned int m_height; + rcti canvas_; /** * Flags how to evaluate this operation. @@ -374,11 +365,6 @@ class NodeOperation { { } - void set_execution_model(const eExecutionModel model) - { - execution_model_ = model; - } - void set_name(const std::string name) { m_name = name; @@ -399,6 +385,9 @@ class NodeOperation { return m_id; } + float get_constant_value_default(float default_value); + const float *get_constant_elem_default(const float *default_elem); + const NodeOperationFlags get_flags() const { return flags; @@ -424,14 +413,7 @@ class NodeOperation { return getInputOperation(index); } - /** - * \brief determine the resolution of this node - * \note this method will not set the resolution, this is the responsibility of the caller - * \param resolution: the result of this operation - * \param preferredResolution: the preferable resolution as no resolution could be determined - */ - virtual void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]); + virtual void determine_canvas(const rcti &preferred_area, rcti &r_area); /** * \brief isOutputOperation determines whether this operation is an output of the @@ -453,6 +435,11 @@ class NodeOperation { return false; } + void set_execution_model(const eExecutionModel model) + { + execution_model_ = model; + } + void setbNodeTree(const bNodeTree *tree) { this->m_btree = tree; @@ -527,18 +514,9 @@ class NodeOperation { } virtual void deinitExecution(); - /** - * \brief set the resolution - * \param resolution: the resolution to set - */ - void setResolution(unsigned int resolution[2]) - { - if (!this->flags.is_resolution_set) { - this->m_width = resolution[0]; - this->m_height = resolution[1]; - this->flags.is_resolution_set = true; - } - } + void set_canvas(const rcti &canvas_area); + const rcti &get_canvas() const; + void unset_canvas(); /** * \brief is this operation the active viewer output @@ -557,18 +535,18 @@ class NodeOperation { rcti *output); /** - * \brief set the index of the input socket that will determine the resolution of this + * \brief set the index of the input socket that will determine the canvas of this * operation \param index: the index to set */ - void setResolutionInputSocketIndex(unsigned int index); + void set_canvas_input_index(unsigned int index); /** - * Set a custom function to modify determined resolution from main input just before setting it - * as preferred resolution for the other inputs. + * Set a custom function to modify determined canvas from main input just before setting it + * as preferred for the other inputs. */ - void set_determined_resolution_modifier(std::function<void(unsigned int resolution[2])> fn) + void set_determined_canvas_modifier(std::function<void(rcti &canvas)> fn) { - modify_determined_resolution_fn_ = fn; + modify_determined_canvas_fn_ = fn; } /** @@ -595,12 +573,12 @@ class NodeOperation { unsigned int getWidth() const { - return m_width; + return BLI_rcti_size_x(&get_canvas()); } unsigned int getHeight() const { - return m_height; + return BLI_rcti_size_y(&get_canvas()); } inline void readSampled(float result[4], float x, float y, PixelSampler sampler) @@ -697,16 +675,18 @@ class NodeOperation { void addInputSocket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center); void addOutputSocket(DataType datatype); + /* TODO(manzanilla): to be removed with tiled implementation. */ void setWidth(unsigned int width) { - this->m_width = width; - this->flags.is_resolution_set = true; + canvas_.xmax = canvas_.xmin + width; + this->flags.is_canvas_set = true; } void setHeight(unsigned int height) { - this->m_height = height; - this->flags.is_resolution_set = true; + canvas_.ymax = canvas_.ymin + height; + this->flags.is_canvas_set = true; } + SocketReader *getInputSocketReader(unsigned int inputSocketindex); NodeOperation *getInputOperation(unsigned int inputSocketindex); diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc index b2cd76be2c3..acb7f61f6dd 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc @@ -33,6 +33,7 @@ #include "COM_SetValueOperation.h" #include "COM_SetVectorOperation.h" #include "COM_SocketProxyOperation.h" +#include "COM_TranslateOperation.h" #include "COM_ViewerOperation.h" #include "COM_WriteBufferOperation.h" @@ -106,7 +107,7 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) folder.fold_operations(); } - determineResolutions(); + determine_canvases(); save_graphviz("compositor_prior_merging"); merge_equal_operations(); @@ -423,41 +424,50 @@ void NodeOperationBuilder::resolve_proxies() } } -void NodeOperationBuilder::determineResolutions() +void NodeOperationBuilder::determine_canvases() { - /* determine all resolutions of the operations (Width/Height) */ + /* Determine all canvas areas of the operations. */ + const rcti &preferred_area = COM_AREA_NONE; for (NodeOperation *op : m_operations) { if (op->isOutputOperation(m_context->isRendering()) && !op->get_flags().is_preview_operation) { - unsigned int resolution[2] = {0, 0}; - unsigned int preferredResolution[2] = {0, 0}; - op->determineResolution(resolution, preferredResolution); - op->setResolution(resolution); + rcti canvas = COM_AREA_NONE; + op->determine_canvas(preferred_area, canvas); + op->set_canvas(canvas); } } for (NodeOperation *op : m_operations) { if (op->isOutputOperation(m_context->isRendering()) && op->get_flags().is_preview_operation) { - unsigned int resolution[2] = {0, 0}; - unsigned int preferredResolution[2] = {0, 0}; - op->determineResolution(resolution, preferredResolution); - op->setResolution(resolution); + rcti canvas = COM_AREA_NONE; + op->determine_canvas(preferred_area, canvas); + op->set_canvas(canvas); } } - /* add convert resolution operations when needed */ + /* Convert operation canvases when needed. */ { Vector<Link> convert_links; for (const Link &link : m_links) { if (link.to()->getResizeMode() != ResizeMode::None) { - NodeOperation &from_op = link.from()->getOperation(); - NodeOperation &to_op = link.to()->getOperation(); - if (from_op.getWidth() != to_op.getWidth() || from_op.getHeight() != to_op.getHeight()) { + const rcti &from_canvas = link.from()->getOperation().get_canvas(); + const rcti &to_canvas = link.to()->getOperation().get_canvas(); + + bool needs_conversion; + if (link.to()->getResizeMode() == ResizeMode::Align) { + needs_conversion = from_canvas.xmin != to_canvas.xmin || + from_canvas.ymin != to_canvas.ymin; + } + else { + needs_conversion = !BLI_rcti_compare(&from_canvas, &to_canvas); + } + + if (needs_conversion) { convert_links.append(link); } } } for (const Link &link : convert_links) { - COM_convert_resolution(*this, link.from(), link.to()); + COM_convert_canvas(*this, link.from(), link.to()); } } } diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.h b/source/blender/compositor/intern/COM_NodeOperationBuilder.h index aca4d043d41..1f9c86b51cd 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.h +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.h @@ -145,8 +145,8 @@ class NodeOperationBuilder { /** Replace proxy operations with direct links */ void resolve_proxies(); - /** Calculate resolution for each operation */ - void determineResolutions(); + /** Calculate canvas area for each operation. */ + void determine_canvases(); /** Helper function to store connected inputs for replacement */ Vector<NodeOperationInput *> cache_output_links(NodeOperationOutput *output) const; diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.cc b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc index 7e0486b0f54..55153bd4f0a 100644 --- a/source/blender/compositor/intern/COM_SharedOperationBuffers.cc +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc @@ -76,9 +76,17 @@ void SharedOperationBuffers::register_read(NodeOperation *read_op) /** * Get registered areas given operation needs to render. */ -blender::Span<rcti> SharedOperationBuffers::get_areas_to_render(NodeOperation *op) +Vector<rcti> SharedOperationBuffers::get_areas_to_render(NodeOperation *op, + const int offset_x, + const int offset_y) { - return get_buffer_data(op).render_areas.as_span(); + Span<rcti> render_areas = get_buffer_data(op).render_areas.as_span(); + Vector<rcti> dst_areas; + for (rcti dst : render_areas) { + BLI_rcti_translate(&dst, offset_x, offset_y); + dst_areas.append(std::move(dst)); + } + return dst_areas; } /** diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.h b/source/blender/compositor/intern/COM_SharedOperationBuffers.h index f7763cd8ae4..4461ba75cbe 100644 --- a/source/blender/compositor/intern/COM_SharedOperationBuffers.h +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.h @@ -53,7 +53,7 @@ class SharedOperationBuffers { bool has_registered_reads(NodeOperation *op); void register_read(NodeOperation *read_op); - blender::Span<rcti> get_areas_to_render(NodeOperation *op); + Vector<rcti> get_areas_to_render(NodeOperation *op, int offset_x, int offset_y); bool is_operation_rendered(NodeOperation *op); void set_rendered_buffer(NodeOperation *op, std::unique_ptr<MemoryBuffer> buffer); MemoryBuffer *get_rendered_buffer(NodeOperation *op); diff --git a/source/blender/compositor/nodes/COM_AlphaOverNode.cc b/source/blender/compositor/nodes/COM_AlphaOverNode.cc index 5e09902aee2..c9038886b0d 100644 --- a/source/blender/compositor/nodes/COM_AlphaOverNode.cc +++ b/source/blender/compositor/nodes/COM_AlphaOverNode.cc @@ -51,13 +51,13 @@ void AlphaOverNode::convertToOperations(NodeConverter &converter, convertProg->setUseValueAlphaMultiply(false); if (color1Socket->isLinked()) { - convertProg->setResolutionInputSocketIndex(1); + convertProg->set_canvas_input_index(1); } else if (color2Socket->isLinked()) { - convertProg->setResolutionInputSocketIndex(2); + convertProg->set_canvas_input_index(2); } else { - convertProg->setResolutionInputSocketIndex(0); + convertProg->set_canvas_input_index(0); } converter.addOperation(convertProg); diff --git a/source/blender/compositor/nodes/COM_BoxMaskNode.cc b/source/blender/compositor/nodes/COM_BoxMaskNode.cc index 14f42cc42f7..8017e063a69 100644 --- a/source/blender/compositor/nodes/COM_BoxMaskNode.cc +++ b/source/blender/compositor/nodes/COM_BoxMaskNode.cc @@ -62,7 +62,7 @@ void BoxMaskNode::convertToOperations(NodeConverter &converter, scaleOperation->setOffset(0.0f, 0.0f); scaleOperation->setNewWidth(rd->xsch * render_size_factor); scaleOperation->setNewHeight(rd->ysch * render_size_factor); - scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); + scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::Align); converter.addOperation(scaleOperation); converter.addLink(valueOperation->getOutputSocket(0), scaleOperation->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_CombineColorNode.cc b/source/blender/compositor/nodes/COM_CombineColorNode.cc index 8a2bbba1c1e..dd68780dc19 100644 --- a/source/blender/compositor/nodes/COM_CombineColorNode.cc +++ b/source/blender/compositor/nodes/COM_CombineColorNode.cc @@ -37,16 +37,16 @@ void CombineColorNode::convertToOperations(NodeConverter &converter, CombineChannelsOperation *operation = new CombineChannelsOperation(); if (inputRSocket->isLinked()) { - operation->setResolutionInputSocketIndex(0); + operation->set_canvas_input_index(0); } else if (inputGSocket->isLinked()) { - operation->setResolutionInputSocketIndex(1); + operation->set_canvas_input_index(1); } else if (inputBSocket->isLinked()) { - operation->setResolutionInputSocketIndex(2); + operation->set_canvas_input_index(2); } else { - operation->setResolutionInputSocketIndex(3); + operation->set_canvas_input_index(3); } converter.addOperation(operation); diff --git a/source/blender/compositor/nodes/COM_EllipseMaskNode.cc b/source/blender/compositor/nodes/COM_EllipseMaskNode.cc index 3b4f5ca8c94..752597ef937 100644 --- a/source/blender/compositor/nodes/COM_EllipseMaskNode.cc +++ b/source/blender/compositor/nodes/COM_EllipseMaskNode.cc @@ -62,7 +62,7 @@ void EllipseMaskNode::convertToOperations(NodeConverter &converter, scaleOperation->setOffset(0.0f, 0.0f); scaleOperation->setNewWidth(rd->xsch * render_size_factor); scaleOperation->setNewHeight(rd->ysch * render_size_factor); - scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); + scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::Align); converter.addOperation(scaleOperation); converter.addLink(valueOperation->getOutputSocket(0), scaleOperation->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_GlareNode.cc b/source/blender/compositor/nodes/COM_GlareNode.cc index cd0b5306be1..9c26d7c86a9 100644 --- a/source/blender/compositor/nodes/COM_GlareNode.cc +++ b/source/blender/compositor/nodes/COM_GlareNode.cc @@ -66,7 +66,7 @@ void GlareNode::convertToOperations(NodeConverter &converter, mixvalueoperation->setValue(glare->mix); MixGlareOperation *mixoperation = new MixGlareOperation(); - mixoperation->setResolutionInputSocketIndex(1); + mixoperation->set_canvas_input_index(1); mixoperation->getInputSocket(2)->setResizeMode(ResizeMode::FitAny); converter.addOperation(glareoperation); diff --git a/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc b/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc index 5042d217f9a..e7b1664c354 100644 --- a/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc +++ b/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc @@ -53,7 +53,7 @@ void HueSaturationValueCorrectNode::convertToOperations( converter.addOperation(changeHSV); MixBlendOperation *blend = new MixBlendOperation(); - blend->setResolutionInputSocketIndex(1); + blend->set_canvas_input_index(1); converter.addOperation(blend); converter.mapInputSocket(colorSocket, rgbToHSV->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc b/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc index 54d2caa75af..29e5f39a144 100644 --- a/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc +++ b/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc @@ -56,7 +56,7 @@ void HueSaturationValueNode::convertToOperations(NodeConverter &converter, converter.addOperation(changeHSV); MixBlendOperation *blend = new MixBlendOperation(); - blend->setResolutionInputSocketIndex(1); + blend->set_canvas_input_index(1); converter.addOperation(blend); converter.mapInputSocket(colorSocket, rgbToHSV->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_MapUVNode.cc b/source/blender/compositor/nodes/COM_MapUVNode.cc index 4b7a9e8af0f..bbf9e8f3aeb 100644 --- a/source/blender/compositor/nodes/COM_MapUVNode.cc +++ b/source/blender/compositor/nodes/COM_MapUVNode.cc @@ -34,7 +34,7 @@ void MapUVNode::convertToOperations(NodeConverter &converter, MapUVOperation *operation = new MapUVOperation(); operation->setAlpha((float)node->custom1); - operation->setResolutionInputSocketIndex(1); + operation->set_canvas_input_index(1); converter.addOperation(operation); converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_ScaleNode.cc b/source/blender/compositor/nodes/COM_ScaleNode.cc index 819d2e72f30..f1f41375eba 100644 --- a/source/blender/compositor/nodes/COM_ScaleNode.cc +++ b/source/blender/compositor/nodes/COM_ScaleNode.cc @@ -52,6 +52,8 @@ void ScaleNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 1.5f); + break; } case CMP_SCALE_SCENEPERCENT: { @@ -68,6 +70,7 @@ void ScaleNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 1.5f); break; } @@ -81,13 +84,13 @@ void ScaleNode::convertToOperations(NodeConverter &converter, operation->setOffset(bnode->custom3, bnode->custom4); operation->setNewWidth(rd->xsch * render_size_factor); operation->setNewHeight(rd->ysch * render_size_factor); - operation->getInputSocket(0)->setResizeMode(ResizeMode::None); converter.addOperation(operation); converter.mapInputSocket(inputSocket, operation->getInputSocket(0)); converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 3.0f); break; } @@ -102,6 +105,7 @@ void ScaleNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 1.5f); break; } diff --git a/source/blender/compositor/nodes/COM_SetAlphaNode.cc b/source/blender/compositor/nodes/COM_SetAlphaNode.cc index dc41c126ba8..c7365b09f71 100644 --- a/source/blender/compositor/nodes/COM_SetAlphaNode.cc +++ b/source/blender/compositor/nodes/COM_SetAlphaNode.cc @@ -39,7 +39,7 @@ void SetAlphaNode::convertToOperations(NodeConverter &converter, } if (!this->getInputSocket(0)->isLinked() && this->getInputSocket(1)->isLinked()) { - operation->setResolutionInputSocketIndex(1); + operation->set_canvas_input_index(1); } converter.addOperation(operation); diff --git a/source/blender/compositor/nodes/COM_Stabilize2dNode.cc b/source/blender/compositor/nodes/COM_Stabilize2dNode.cc index 90f62c6d562..3d8f0bbda7e 100644 --- a/source/blender/compositor/nodes/COM_Stabilize2dNode.cc +++ b/source/blender/compositor/nodes/COM_Stabilize2dNode.cc @@ -123,17 +123,54 @@ void Stabilize2dNode::convertToOperations(NodeConverter &converter, break; } case eExecutionModel::FullFrame: { - TransformOperation *transform_op = new TransformOperation(); - transform_op->set_sampler(sampler); - transform_op->set_convert_rotate_degree_to_rad(false); - transform_op->set_invert(invert); - converter.addOperation(transform_op); - converter.mapInputSocket(imageInput, transform_op->getInputSocket(0)); - converter.addLink(xAttribute->getOutputSocket(), transform_op->getInputSocket(1)); - converter.addLink(yAttribute->getOutputSocket(), transform_op->getInputSocket(2)); - converter.addLink(angleAttribute->getOutputSocket(), transform_op->getInputSocket(3)); - converter.addLink(scaleAttribute->getOutputSocket(), transform_op->getInputSocket(4)); - converter.mapOutputSocket(getOutputSocket(), transform_op->getOutputSocket()); + ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); + scaleOperation->setSampler(sampler); + RotateOperation *rotateOperation = new RotateOperation(); + rotateOperation->setDoDegree2RadConversion(false); + rotateOperation->set_sampler(sampler); + TranslateOperation *translateOperation = new TranslateCanvasOperation(); + + converter.addOperation(scaleOperation); + converter.addOperation(translateOperation); + converter.addOperation(rotateOperation); + + converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(1)); + converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(2)); + + converter.addLink(angleAttribute->getOutputSocket(), rotateOperation->getInputSocket(1)); + + converter.addLink(xAttribute->getOutputSocket(), translateOperation->getInputSocket(1)); + converter.addLink(yAttribute->getOutputSocket(), translateOperation->getInputSocket(2)); + + NodeOperationInput *stabilization_socket = nullptr; + if (invert) { + /* Translate -> Rotate -> Scale. */ + stabilization_socket = translateOperation->getInputSocket(0); + converter.mapInputSocket(imageInput, translateOperation->getInputSocket(0)); + + converter.addLink(translateOperation->getOutputSocket(), + rotateOperation->getInputSocket(0)); + converter.addLink(rotateOperation->getOutputSocket(), scaleOperation->getInputSocket(0)); + + converter.mapOutputSocket(getOutputSocket(), scaleOperation->getOutputSocket()); + } + else { + /* Scale -> Rotate -> Translate. */ + stabilization_socket = scaleOperation->getInputSocket(0); + converter.mapInputSocket(imageInput, scaleOperation->getInputSocket(0)); + + converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); + converter.addLink(rotateOperation->getOutputSocket(), + translateOperation->getInputSocket(0)); + + converter.mapOutputSocket(getOutputSocket(), translateOperation->getOutputSocket()); + } + + xAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + yAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + scaleAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + angleAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + break; } } } diff --git a/source/blender/compositor/nodes/COM_TransformNode.cc b/source/blender/compositor/nodes/COM_TransformNode.cc index d2fb7b54633..b38aad78d90 100644 --- a/source/blender/compositor/nodes/COM_TransformNode.cc +++ b/source/blender/compositor/nodes/COM_TransformNode.cc @@ -73,16 +73,33 @@ void TransformNode::convertToOperations(NodeConverter &converter, break; } case eExecutionModel::FullFrame: { - TransformOperation *op = new TransformOperation(); - op->set_sampler((PixelSampler)this->getbNode()->custom1); - converter.addOperation(op); - - converter.mapInputSocket(imageInput, op->getInputSocket(0)); - converter.mapInputSocket(xInput, op->getInputSocket(1)); - converter.mapInputSocket(yInput, op->getInputSocket(2)); - converter.mapInputSocket(angleInput, op->getInputSocket(3)); - converter.mapInputSocket(scaleInput, op->getInputSocket(4)); - converter.mapOutputSocket(getOutputSocket(), op->getOutputSocket()); + ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); + converter.addOperation(scaleOperation); + + RotateOperation *rotateOperation = new RotateOperation(); + rotateOperation->setDoDegree2RadConversion(false); + converter.addOperation(rotateOperation); + + TranslateOperation *translateOperation = new TranslateCanvasOperation(); + converter.addOperation(translateOperation); + + PixelSampler sampler = (PixelSampler)this->getbNode()->custom1; + scaleOperation->setSampler(sampler); + rotateOperation->set_sampler(sampler); + scaleOperation->set_scale_canvas_max_size(context.get_render_size()); + + converter.mapInputSocket(imageInput, scaleOperation->getInputSocket(0)); + converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(1)); + converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(2)); // xscale = yscale + + converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); + converter.mapInputSocket(angleInput, rotateOperation->getInputSocket(1)); + + converter.addLink(rotateOperation->getOutputSocket(), translateOperation->getInputSocket(0)); + converter.mapInputSocket(xInput, translateOperation->getInputSocket(1)); + converter.mapInputSocket(yInput, translateOperation->getInputSocket(2)); + + converter.mapOutputSocket(getOutputSocket(), translateOperation->getOutputSocket()); break; } } diff --git a/source/blender/compositor/nodes/COM_TranslateNode.cc b/source/blender/compositor/nodes/COM_TranslateNode.cc index 3a3e98c3472..165a03baf41 100644 --- a/source/blender/compositor/nodes/COM_TranslateNode.cc +++ b/source/blender/compositor/nodes/COM_TranslateNode.cc @@ -41,7 +41,9 @@ void TranslateNode::convertToOperations(NodeConverter &converter, NodeInput *inputYSocket = this->getInputSocket(2); NodeOutput *outputSocket = this->getOutputSocket(0); - TranslateOperation *operation = new TranslateOperation(); + TranslateOperation *operation = context.get_execution_model() == eExecutionModel::Tiled ? + new TranslateOperation() : + new TranslateCanvasOperation(); operation->set_wrapping(data->wrap_axis); if (data->relative) { const RenderData *rd = context.getRenderData(); diff --git a/source/blender/compositor/nodes/COM_ViewerNode.cc b/source/blender/compositor/nodes/COM_ViewerNode.cc index 3833a8d7ca8..4dbcdbe9e40 100644 --- a/source/blender/compositor/nodes/COM_ViewerNode.cc +++ b/source/blender/compositor/nodes/COM_ViewerNode.cc @@ -60,10 +60,10 @@ void ViewerNode::convertToOperations(NodeConverter &converter, viewerOperation->setViewSettings(context.getViewSettings()); viewerOperation->setDisplaySettings(context.getDisplaySettings()); - viewerOperation->setResolutionInputSocketIndex(0); + viewerOperation->set_canvas_input_index(0); if (!imageSocket->isLinked()) { if (alphaSocket->isLinked()) { - viewerOperation->setResolutionInputSocketIndex(1); + viewerOperation->set_canvas_input_index(1); } } diff --git a/source/blender/compositor/nodes/COM_ZCombineNode.cc b/source/blender/compositor/nodes/COM_ZCombineNode.cc index e29748dc317..9753e812a8b 100644 --- a/source/blender/compositor/nodes/COM_ZCombineNode.cc +++ b/source/blender/compositor/nodes/COM_ZCombineNode.cc @@ -64,18 +64,14 @@ void ZCombineNode::convertToOperations(NodeConverter &converter, NodeOperation *maskoperation; if (this->getbNode()->custom1) { maskoperation = new MathGreaterThanOperation(); - converter.addOperation(maskoperation); - - converter.mapInputSocket(getInputSocket(1), maskoperation->getInputSocket(0)); - converter.mapInputSocket(getInputSocket(3), maskoperation->getInputSocket(1)); } else { maskoperation = new MathLessThanOperation(); - converter.addOperation(maskoperation); - - converter.mapInputSocket(getInputSocket(1), maskoperation->getInputSocket(0)); - converter.mapInputSocket(getInputSocket(3), maskoperation->getInputSocket(1)); } + converter.addOperation(maskoperation); + + converter.mapInputSocket(getInputSocket(1), maskoperation->getInputSocket(0)); + converter.mapInputSocket(getInputSocket(3), maskoperation->getInputSocket(1)); /* Step 2 anti alias mask bit of an expensive operation, but does the trick. */ AntiAliasOperation *antialiasoperation = new AntiAliasOperation(); diff --git a/source/blender/compositor/operations/COM_BlurBaseOperation.cc b/source/blender/compositor/operations/COM_BlurBaseOperation.cc index 058b422c4a5..412632e2e22 100644 --- a/source/blender/compositor/operations/COM_BlurBaseOperation.cc +++ b/source/blender/compositor/operations/COM_BlurBaseOperation.cc @@ -212,31 +212,30 @@ void BlurBaseOperation::updateSize() this->m_sizeavailable = true; } -void BlurBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void BlurBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (!m_extend_bounds) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); return; } switch (execution_model_) { case eExecutionModel::Tiled: { - NodeOperation::determineResolution(resolution, preferredResolution); - resolution[0] += 2 * m_size * m_data.sizex; - resolution[1] += 2 * m_size * m_data.sizey; + NodeOperation::determine_canvas(preferred_area, r_area); + r_area.xmax += 2 * m_size * m_data.sizex; + r_area.ymax += 2 * m_size * m_data.sizey; break; } case eExecutionModel::FullFrame: { /* Setting a modifier ensures all non main inputs have extended bounds as preferred - * resolution, avoiding unnecessary resolution conversions that would hide constant + * canvas, avoiding unnecessary canvas conversions that would hide constant * operations. */ - set_determined_resolution_modifier([=](unsigned int res[2]) { + set_determined_canvas_modifier([=](rcti &canvas) { /* Rounding to even prevents jiggling in backdrop while switching size values. */ - res[0] += round_to_even(2 * m_size * m_data.sizex); - res[1] += round_to_even(2 * m_size * m_data.sizey); + canvas.xmax += round_to_even(2 * m_size * m_data.sizex); + canvas.ymax += round_to_even(2 * m_size * m_data.sizey); }); - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); break; } } @@ -251,7 +250,7 @@ void BlurBaseOperation::get_area_of_interest(const int input_idx, r_input_area = output_area; break; case 1: - r_input_area = use_variable_size_ ? output_area : COM_SINGLE_ELEM_AREA; + r_input_area = use_variable_size_ ? output_area : COM_CONSTANT_INPUT_AREA_OF_INTEREST; break; } } diff --git a/source/blender/compositor/operations/COM_BlurBaseOperation.h b/source/blender/compositor/operations/COM_BlurBaseOperation.h index 78b1e919aa6..9ab9bf5a173 100644 --- a/source/blender/compositor/operations/COM_BlurBaseOperation.h +++ b/source/blender/compositor/operations/COM_BlurBaseOperation.h @@ -85,8 +85,7 @@ class BlurBaseOperation : public MultiThreadedOperation, public QualityStepHelpe int get_blur_size(eDimension dim) const; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; virtual void get_area_of_interest(int input_idx, const rcti &output_area, diff --git a/source/blender/compositor/operations/COM_BokehBlurOperation.cc b/source/blender/compositor/operations/COM_BokehBlurOperation.cc index f2a43b7dbca..93482dd2a54 100644 --- a/source/blender/compositor/operations/COM_BokehBlurOperation.cc +++ b/source/blender/compositor/operations/COM_BokehBlurOperation.cc @@ -34,7 +34,7 @@ constexpr int SIZE_INPUT_INDEX = 3; BokehBlurOperation::BokehBlurOperation() { this->addInputSocket(DataType::Color); - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addInputSocket(DataType::Value); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); @@ -266,31 +266,30 @@ void BokehBlurOperation::updateSize() this->m_sizeavailable = true; } -void BokehBlurOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void BokehBlurOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (!m_extend_bounds) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); return; } switch (execution_model_) { case eExecutionModel::Tiled: { - NodeOperation::determineResolution(resolution, preferredResolution); - const float max_dim = MAX2(resolution[0], resolution[1]); - resolution[0] += 2 * this->m_size * max_dim / 100.0f; - resolution[1] += 2 * this->m_size * max_dim / 100.0f; + NodeOperation::determine_canvas(preferred_area, r_area); + const float max_dim = MAX2(BLI_rcti_size_x(&r_area), BLI_rcti_size_y(&r_area)); + r_area.xmax += 2 * this->m_size * max_dim / 100.0f; + r_area.ymax += 2 * this->m_size * max_dim / 100.0f; break; } case eExecutionModel::FullFrame: { - set_determined_resolution_modifier([=](unsigned int res[2]) { - const float max_dim = MAX2(res[0], res[1]); + set_determined_canvas_modifier([=](rcti &canvas) { + const float max_dim = MAX2(BLI_rcti_size_x(&canvas), BLI_rcti_size_y(&canvas)); /* Rounding to even prevents image jiggling in backdrop while switching size values. */ float add_size = round_to_even(2 * this->m_size * max_dim / 100.0f); - res[0] += add_size; - res[1] += add_size; + canvas.xmax += add_size; + canvas.ymax += add_size; }); - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); break; } } @@ -312,17 +311,14 @@ void BokehBlurOperation::get_area_of_interest(const int input_idx, } case BOKEH_INPUT_INDEX: { NodeOperation *bokeh_input = getInputOperation(BOKEH_INPUT_INDEX); - r_input_area.xmin = 0; - r_input_area.xmax = bokeh_input->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = bokeh_input->getHeight(); + r_input_area = bokeh_input->get_canvas(); break; } case BOUNDING_BOX_INPUT_INDEX: r_input_area = output_area; break; case SIZE_INPUT_INDEX: { - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; break; } } diff --git a/source/blender/compositor/operations/COM_BokehBlurOperation.h b/source/blender/compositor/operations/COM_BokehBlurOperation.h index 59c14305393..84f5a8293ba 100644 --- a/source/blender/compositor/operations/COM_BokehBlurOperation.h +++ b/source/blender/compositor/operations/COM_BokehBlurOperation.h @@ -80,8 +80,7 @@ class BokehBlurOperation : public MultiThreadedOperation, public QualityStepHelp this->m_extend_bounds = extend_bounds; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_BokehImageOperation.cc b/source/blender/compositor/operations/COM_BokehImageOperation.cc index bd5b25b5af8..5c9c8b36ee0 100644 --- a/source/blender/compositor/operations/COM_BokehImageOperation.cc +++ b/source/blender/compositor/operations/COM_BokehImageOperation.cc @@ -145,11 +145,13 @@ void BokehImageOperation::deinitExecution() } } -void BokehImageOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void BokehImageOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = COM_BLUR_BOKEH_PIXELS; - resolution[1] = COM_BLUR_BOKEH_PIXELS; + BLI_rcti_init(&r_area, + preferred_area.xmin, + preferred_area.xmin + COM_BLUR_BOKEH_PIXELS, + preferred_area.ymin, + preferred_area.ymin + COM_BLUR_BOKEH_PIXELS); } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_BokehImageOperation.h b/source/blender/compositor/operations/COM_BokehImageOperation.h index 2527233fabd..f7fec5d71a5 100644 --- a/source/blender/compositor/operations/COM_BokehImageOperation.h +++ b/source/blender/compositor/operations/COM_BokehImageOperation.h @@ -128,8 +128,7 @@ class BokehImageOperation : public MultiThreadedOperation { * \brief determine the resolution of this operation. currently fixed at [COM_BLUR_BOKEH_PIXELS, * COM_BLUR_BOKEH_PIXELS] \param resolution: \param preferredResolution: */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; /** * \brief set the node data diff --git a/source/blender/compositor/operations/COM_CalculateMeanOperation.cc b/source/blender/compositor/operations/COM_CalculateMeanOperation.cc index 7457ac9e227..a573a9d7eed 100644 --- a/source/blender/compositor/operations/COM_CalculateMeanOperation.cc +++ b/source/blender/compositor/operations/COM_CalculateMeanOperation.cc @@ -27,7 +27,7 @@ namespace blender::compositor { CalculateMeanOperation::CalculateMeanOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Value); this->m_imageReader = nullptr; this->m_iscalculated = false; @@ -165,11 +165,7 @@ void CalculateMeanOperation::get_area_of_interest(int input_idx, rcti &r_input_area) { BLI_assert(input_idx == 0); - NodeOperation *operation = getInputOperation(input_idx); - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = operation->getWidth(); - r_input_area.ymax = operation->getHeight(); + r_input_area = get_input_operation(input_idx)->get_canvas(); } void CalculateMeanOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), diff --git a/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc b/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc index aee8c0d52e8..0b6590ae4c7 100644 --- a/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc +++ b/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc @@ -40,7 +40,7 @@ ColorBalanceASCCDLOperation::ColorBalanceASCCDLOperation() this->addOutputSocket(DataType::Color); this->m_inputValueOperation = nullptr; this->m_inputColorOperation = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); flags.can_be_constant = true; } diff --git a/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc b/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc index 674cb79a238..c658ecd6394 100644 --- a/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc +++ b/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc @@ -45,7 +45,7 @@ ColorBalanceLGGOperation::ColorBalanceLGGOperation() this->addOutputSocket(DataType::Color); this->m_inputValueOperation = nullptr; this->m_inputColorOperation = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); flags.can_be_constant = true; } diff --git a/source/blender/compositor/operations/COM_ColorCurveOperation.cc b/source/blender/compositor/operations/COM_ColorCurveOperation.cc index 646238460ba..364b310945e 100644 --- a/source/blender/compositor/operations/COM_ColorCurveOperation.cc +++ b/source/blender/compositor/operations/COM_ColorCurveOperation.cc @@ -37,7 +37,7 @@ ColorCurveOperation::ColorCurveOperation() this->m_inputBlackProgram = nullptr; this->m_inputWhiteProgram = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); } void ColorCurveOperation::initExecution() { @@ -139,7 +139,7 @@ ConstantLevelColorCurveOperation::ConstantLevelColorCurveOperation() this->m_inputFacProgram = nullptr; this->m_inputImageProgram = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); } void ConstantLevelColorCurveOperation::initExecution() { diff --git a/source/blender/compositor/operations/COM_CompositorOperation.cc b/source/blender/compositor/operations/COM_CompositorOperation.cc index fb9e2e43c60..f7466b5db34 100644 --- a/source/blender/compositor/operations/COM_CompositorOperation.cc +++ b/source/blender/compositor/operations/COM_CompositorOperation.cc @@ -236,8 +236,7 @@ void CompositorOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(outp depth_buf.copy_from(inputs[2], area); } -void CompositorOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void CompositorOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { int width = this->m_rd->xsch * this->m_rd->size / 100; int height = this->m_rd->ysch * this->m_rd->size / 100; @@ -254,13 +253,19 @@ void CompositorOperation::determineResolution(unsigned int resolution[2], RE_ReleaseResult(re); } - preferredResolution[0] = width; - preferredResolution[1] = height; - - NodeOperation::determineResolution(resolution, preferredResolution); - - resolution[0] = width; - resolution[1] = height; + rcti local_preferred; + BLI_rcti_init(&local_preferred, 0, width, 0, height); + + switch (execution_model_) { + case eExecutionModel::Tiled: + NodeOperation::determine_canvas(local_preferred, r_area); + r_area = local_preferred; + break; + case eExecutionModel::FullFrame: + set_determined_canvas_modifier([&](rcti &canvas) { canvas = local_preferred; }); + NodeOperation::determine_canvas(local_preferred, r_area); + break; + } } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_CompositorOperation.h b/source/blender/compositor/operations/COM_CompositorOperation.h index 66367ec8bae..6eb96e01b47 100644 --- a/source/blender/compositor/operations/COM_CompositorOperation.h +++ b/source/blender/compositor/operations/COM_CompositorOperation.h @@ -115,8 +115,7 @@ class CompositorOperation : public MultiThreadedOperation { { return eCompositorPriority::Medium; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setUseAlphaInput(bool value) { this->m_useAlphaInput = value; diff --git a/source/blender/compositor/operations/COM_ConstantOperation.cc b/source/blender/compositor/operations/COM_ConstantOperation.cc index 33d51cca432..c127860a89c 100644 --- a/source/blender/compositor/operations/COM_ConstantOperation.cc +++ b/source/blender/compositor/operations/COM_ConstantOperation.cc @@ -22,14 +22,14 @@ namespace blender::compositor { ConstantOperation::ConstantOperation() { - needs_resolution_to_get_constant_ = false; + needs_canvas_to_get_constant_ = false; flags.is_constant_operation = true; flags.is_fullframe_operation = true; } bool ConstantOperation::can_get_constant_elem() const { - return !needs_resolution_to_get_constant_ || this->flags.is_resolution_set; + return !needs_canvas_to_get_constant_ || this->flags.is_canvas_set; } void ConstantOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_ConstantOperation.h b/source/blender/compositor/operations/COM_ConstantOperation.h index 31b8d30254b..d44d1939424 100644 --- a/source/blender/compositor/operations/COM_ConstantOperation.h +++ b/source/blender/compositor/operations/COM_ConstantOperation.h @@ -31,7 +31,7 @@ namespace blender::compositor { */ class ConstantOperation : public NodeOperation { protected: - bool needs_resolution_to_get_constant_; + bool needs_canvas_to_get_constant_; public: ConstantOperation(); diff --git a/source/blender/compositor/operations/COM_ConvertOperation.cc b/source/blender/compositor/operations/COM_ConvertOperation.cc index 9a3733dda5b..7b2721ebbb2 100644 --- a/source/blender/compositor/operations/COM_ConvertOperation.cc +++ b/source/blender/compositor/operations/COM_ConvertOperation.cc @@ -587,7 +587,7 @@ CombineChannelsOperation::CombineChannelsOperation() this->addInputSocket(DataType::Value); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputChannel1Operation = nullptr; this->m_inputChannel2Operation = nullptr; this->m_inputChannel3Operation = nullptr; diff --git a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc index 11a077229fd..807223fd45f 100644 --- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc +++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc @@ -29,7 +29,7 @@ ConvolutionFilterOperation::ConvolutionFilterOperation() this->addInputSocket(DataType::Color); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->flags.complex = true; } diff --git a/source/blender/compositor/operations/COM_CropOperation.cc b/source/blender/compositor/operations/COM_CropOperation.cc index 12833660fcb..6ac30c22ad1 100644 --- a/source/blender/compositor/operations/COM_CropOperation.cc +++ b/source/blender/compositor/operations/COM_CropOperation.cc @@ -23,7 +23,7 @@ namespace blender::compositor { CropBaseOperation::CropBaseOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Color); this->m_inputOperation = nullptr; this->m_settings = nullptr; @@ -142,13 +142,12 @@ void CropImageOperation::get_area_of_interest(const int input_idx, r_input_area.ymin = output_area.ymin + this->m_ymin; } -void CropImageOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void CropImageOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); updateArea(); - resolution[0] = this->m_xmax - this->m_xmin; - resolution[1] = this->m_ymax - this->m_ymin; + r_area.xmax = r_area.xmin + (m_xmax - m_xmin); + r_area.ymax = r_area.ymin + (m_ymax - m_ymin); } void CropImageOperation::executePixelSampled(float output[4], diff --git a/source/blender/compositor/operations/COM_CropOperation.h b/source/blender/compositor/operations/COM_CropOperation.h index 57caa4e5834..a156727402b 100644 --- a/source/blender/compositor/operations/COM_CropOperation.h +++ b/source/blender/compositor/operations/COM_CropOperation.h @@ -66,8 +66,7 @@ class CropImageOperation : public CropBaseOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.cc b/source/blender/compositor/operations/COM_DenoiseOperation.cc index 0c660e0b723..f8a575acc3a 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.cc +++ b/source/blender/compositor/operations/COM_DenoiseOperation.cc @@ -153,10 +153,7 @@ void DenoiseBaseOperation::get_area_of_interest(const int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } DenoiseOperation::DenoiseOperation() diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.cc b/source/blender/compositor/operations/COM_DespeckleOperation.cc index 19bd7b2af6f..df637ee6709 100644 --- a/source/blender/compositor/operations/COM_DespeckleOperation.cc +++ b/source/blender/compositor/operations/COM_DespeckleOperation.cc @@ -29,7 +29,7 @@ DespeckleOperation::DespeckleOperation() this->addInputSocket(DataType::Color); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->flags.complex = true; } diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.cc b/source/blender/compositor/operations/COM_DilateErodeOperation.cc index 28b40021cd9..b7fd714ba5b 100644 --- a/source/blender/compositor/operations/COM_DilateErodeOperation.cc +++ b/source/blender/compositor/operations/COM_DilateErodeOperation.cc @@ -783,7 +783,8 @@ static void step_update_memory_buffer(MemoryBuffer *output, start = half_window + (i - 1) * window + 1; for (int y = -MIN2(0, start); y < window - MAX2(0, start + window - bheight); y++) { - result.get_value(x, y + start + area.ymin, 0) = selector(temp[y], temp[y + window - 1]); + result.get_value(x + area.xmin, y + start + area.ymin, 0) = selector(temp[y], + temp[y + window - 1]); } } } diff --git a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc index 102025ed915..e69124205d0 100644 --- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc +++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc @@ -152,10 +152,7 @@ void DirectionalBlurOperation::get_area_of_interest(const int input_idx, { BLI_assert(input_idx == 0); UNUSED_VARS_NDEBUG(input_idx); - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } void DirectionalBlurOperation::update_memory_buffer_partial(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_DisplaceOperation.cc b/source/blender/compositor/operations/COM_DisplaceOperation.cc index a4c01fda7ca..d08ff60d5d0 100644 --- a/source/blender/compositor/operations/COM_DisplaceOperation.cc +++ b/source/blender/compositor/operations/COM_DisplaceOperation.cc @@ -211,10 +211,7 @@ void DisplaceOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case 0: { - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = getInputOperation(input_idx)->getWidth(); - r_input_area.ymax = getInputOperation(input_idx)->getHeight(); + r_input_area = getInputOperation(input_idx)->get_canvas(); break; } case 1: { diff --git a/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc b/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc index e1c531bd49e..712b61be805 100644 --- a/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc +++ b/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc @@ -138,10 +138,7 @@ void DisplaceSimpleOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case 0: { - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = getInputOperation(input_idx)->getWidth(); - r_input_area.ymax = getInputOperation(input_idx)->getHeight(); + r_input_area = get_input_operation(input_idx)->get_canvas(); break; } default: { diff --git a/source/blender/compositor/operations/COM_DotproductOperation.cc b/source/blender/compositor/operations/COM_DotproductOperation.cc index 875b161e208..aa18ff1e827 100644 --- a/source/blender/compositor/operations/COM_DotproductOperation.cc +++ b/source/blender/compositor/operations/COM_DotproductOperation.cc @@ -25,7 +25,7 @@ DotproductOperation::DotproductOperation() this->addInputSocket(DataType::Vector); this->addInputSocket(DataType::Vector); this->addOutputSocket(DataType::Value); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_input1Operation = nullptr; this->m_input2Operation = nullptr; flags.can_be_constant = true; diff --git a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc index 55ae19ad194..d112334b749 100644 --- a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc +++ b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc @@ -1399,10 +1399,7 @@ void DoubleEdgeMaskOperation::get_area_of_interest(int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmax = this->getWidth(); - r_input_area.xmin = 0; - r_input_area.ymax = this->getHeight(); - r_input_area.ymin = 0; + r_input_area = this->get_canvas(); } void DoubleEdgeMaskOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_EllipseMaskOperation.cc b/source/blender/compositor/operations/COM_EllipseMaskOperation.cc index eb1fd98a590..bf6eee6d3f9 100644 --- a/source/blender/compositor/operations/COM_EllipseMaskOperation.cc +++ b/source/blender/compositor/operations/COM_EllipseMaskOperation.cc @@ -162,13 +162,13 @@ void EllipseMaskOperation::apply_mask(MemoryBuffer *output, const float half_h = this->m_data->height / 2.0f; const float tx = half_w * half_w; const float ty = half_h * half_h; - for (const int y : YRange(area)) { + for (int y = area.ymin; y < area.ymax; y++) { const float op_ry = y / op_h; const float dy = (op_ry - this->m_data->y) / m_aspectRatio; float *out = output->get_elem(area.xmin, y); const float *mask = input_mask->get_elem(area.xmin, y); const float *value = input_value->get_elem(area.xmin, y); - for (const int x : XRange(area)) { + for (int x = area.xmin; x < area.xmax; x++) { const float op_rx = x / op_w; const float dx = op_rx - this->m_data->x; const float rx = this->m_data->x + (m_cosine * dx + m_sine * dy); diff --git a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc index e0fc45811cb..f45b77c6ebc 100644 --- a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc +++ b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc @@ -271,10 +271,7 @@ void FastGaussianBlurOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case IMAGE_INPUT_INDEX: - r_input_area.xmin = 0; - r_input_area.xmax = getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = getHeight(); + r_input_area = this->get_canvas(); break; default: BlurBaseOperation::get_area_of_interest(input_idx, output_area, r_input_area); @@ -411,10 +408,7 @@ void FastGaussianBlurValueOperation::get_area_of_interest(const int UNUSED(input const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmin = 0; - r_input_area.xmax = getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = getHeight(); + r_input_area = this->get_canvas(); } void FastGaussianBlurValueOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), diff --git a/source/blender/compositor/operations/COM_FlipOperation.cc b/source/blender/compositor/operations/COM_FlipOperation.cc index d0dc6c0b570..2d8865e41e0 100644 --- a/source/blender/compositor/operations/COM_FlipOperation.cc +++ b/source/blender/compositor/operations/COM_FlipOperation.cc @@ -22,9 +22,9 @@ namespace blender::compositor { FlipOperation::FlipOperation() { - this->addInputSocket(DataType::Color); + this->addInputSocket(DataType::Color, ResizeMode::None); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_flipX = true; this->m_flipY = false; @@ -75,6 +75,24 @@ bool FlipOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void FlipOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + NodeOperation::determine_canvas(preferred_area, r_area); + if (execution_model_ == eExecutionModel::FullFrame) { + rcti input_area = r_area; + if (m_flipX) { + const int width = BLI_rcti_size_x(&input_area) - 1; + r_area.xmax = (width - input_area.xmin) + 1; + r_area.xmin = (width - input_area.xmax) + 1; + } + if (m_flipY) { + const int height = BLI_rcti_size_y(&input_area) - 1; + r_area.ymax = (height - input_area.ymin) + 1; + r_area.ymin = (height - input_area.ymax) + 1; + } + } +} + void FlipOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) @@ -84,7 +102,7 @@ void FlipOperation::get_area_of_interest(const int input_idx, if (this->m_flipX) { const int w = (int)this->getWidth() - 1; r_input_area.xmax = (w - output_area.xmin) + 1; - r_input_area.xmin = (w - output_area.xmax) - 1; + r_input_area.xmin = (w - output_area.xmax) + 1; } else { r_input_area.xmin = output_area.xmin; @@ -93,7 +111,7 @@ void FlipOperation::get_area_of_interest(const int input_idx, if (this->m_flipY) { const int h = (int)this->getHeight() - 1; r_input_area.ymax = (h - output_area.ymin) + 1; - r_input_area.ymin = (h - output_area.ymax) - 1; + r_input_area.ymin = (h - output_area.ymax) + 1; } else { r_input_area.ymin = output_area.ymin; @@ -106,10 +124,12 @@ void FlipOperation::update_memory_buffer_partial(MemoryBuffer *output, Span<MemoryBuffer *> inputs) { const MemoryBuffer *input_img = inputs[0]; + const int input_offset_x = input_img->get_rect().xmin; + const int input_offset_y = input_img->get_rect().ymin; for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { const int nx = this->m_flipX ? ((int)this->getWidth() - 1) - it.x : it.x; const int ny = this->m_flipY ? ((int)this->getHeight() - 1) - it.y : it.y; - input_img->read_elem(nx, ny, it.out); + input_img->read_elem(input_offset_x + nx, input_offset_y + ny, it.out); } } diff --git a/source/blender/compositor/operations/COM_FlipOperation.h b/source/blender/compositor/operations/COM_FlipOperation.h index dba7f82c341..963996a5b0d 100644 --- a/source/blender/compositor/operations/COM_FlipOperation.h +++ b/source/blender/compositor/operations/COM_FlipOperation.h @@ -46,6 +46,7 @@ class FlipOperation : public MultiThreadedOperation { this->m_flipY = flipY; } + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_GlareBaseOperation.cc b/source/blender/compositor/operations/COM_GlareBaseOperation.cc index 90755d9f27a..cd4607b1dde 100644 --- a/source/blender/compositor/operations/COM_GlareBaseOperation.cc +++ b/source/blender/compositor/operations/COM_GlareBaseOperation.cc @@ -26,6 +26,8 @@ GlareBaseOperation::GlareBaseOperation() this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); this->m_settings = nullptr; + flags.is_fullframe_operation = true; + is_output_rendered_ = false; } void GlareBaseOperation::initExecution() { @@ -69,4 +71,36 @@ bool GlareBaseOperation::determineDependingAreaOfInterest(rcti * /*input*/, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void GlareBaseOperation::get_area_of_interest(const int input_idx, + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = 0; + r_input_area.xmax = this->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = this->getHeight(); +} + +void GlareBaseOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) +{ + if (!is_output_rendered_) { + MemoryBuffer *input = inputs[0]; + const bool is_input_inflated = input->is_a_single_elem(); + if (is_input_inflated) { + input = input->inflate(); + } + + this->generateGlare(output->getBuffer(), input, m_settings); + is_output_rendered_ = true; + + if (is_input_inflated) { + delete input; + } + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_GlareBaseOperation.h b/source/blender/compositor/operations/COM_GlareBaseOperation.h index 6dac6f5ecc7..5ca240a9e66 100644 --- a/source/blender/compositor/operations/COM_GlareBaseOperation.h +++ b/source/blender/compositor/operations/COM_GlareBaseOperation.h @@ -49,6 +49,8 @@ class GlareBaseOperation : public SingleThreadedOperation { */ NodeGlare *m_settings; + bool is_output_rendered_; + public: /** * Initialize the execution @@ -68,6 +70,14 @@ class GlareBaseOperation : public SingleThreadedOperation { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) final; + + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) final; + protected: GlareBaseOperation(); diff --git a/source/blender/compositor/operations/COM_GlareThresholdOperation.cc b/source/blender/compositor/operations/COM_GlareThresholdOperation.cc index 1d3402f5b7b..1bf7cf5ae07 100644 --- a/source/blender/compositor/operations/COM_GlareThresholdOperation.cc +++ b/source/blender/compositor/operations/COM_GlareThresholdOperation.cc @@ -30,12 +30,13 @@ GlareThresholdOperation::GlareThresholdOperation() this->m_inputProgram = nullptr; } -void GlareThresholdOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void GlareThresholdOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - NodeOperation::determineResolution(resolution, preferredResolution); - resolution[0] = resolution[0] / (1 << this->m_settings->quality); - resolution[1] = resolution[1] / (1 << this->m_settings->quality); + NodeOperation::determine_canvas(preferred_area, r_area); + const int width = BLI_rcti_size_x(&r_area) / (1 << this->m_settings->quality); + const int height = BLI_rcti_size_y(&r_area) / (1 << this->m_settings->quality); + r_area.xmax = r_area.xmin + width; + r_area.ymax = r_area.ymin + height; } void GlareThresholdOperation::initExecution() @@ -70,4 +71,24 @@ void GlareThresholdOperation::deinitExecution() this->m_inputProgram = nullptr; } +void GlareThresholdOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const float threshold = this->m_settings->threshold; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float *color = it.in(0); + if (IMB_colormanagement_get_luminance(color) >= threshold) { + it.out[0] = color[0] - threshold; + it.out[1] = color[1] - threshold; + it.out[2] = color[2] - threshold; + + CLAMP3_MIN(it.out, 0.0f); + } + else { + zero_v3(it.out); + } + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_GlareThresholdOperation.h b/source/blender/compositor/operations/COM_GlareThresholdOperation.h index a6e971dada7..44f2d717c0e 100644 --- a/source/blender/compositor/operations/COM_GlareThresholdOperation.h +++ b/source/blender/compositor/operations/COM_GlareThresholdOperation.h @@ -18,12 +18,12 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_light_types.h" namespace blender::compositor { -class GlareThresholdOperation : public NodeOperation { +class GlareThresholdOperation : public MultiThreadedOperation { private: /** * \brief Cached reference to the inputProgram @@ -58,8 +58,10 @@ class GlareThresholdOperation : public NodeOperation { this->m_settings = settings; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ImageOperation.cc b/source/blender/compositor/operations/COM_ImageOperation.cc index e78d389410f..ff389093f5a 100644 --- a/source/blender/compositor/operations/COM_ImageOperation.cc +++ b/source/blender/compositor/operations/COM_ImageOperation.cc @@ -112,17 +112,14 @@ void BaseImageOperation::deinitExecution() } } -void BaseImageOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void BaseImageOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { ImBuf *stackbuf = getImBuf(); - resolution[0] = 0; - resolution[1] = 0; + r_area = COM_AREA_NONE; if (stackbuf) { - resolution[0] = stackbuf->x; - resolution[1] = stackbuf->y; + BLI_rcti_init(&r_area, 0, stackbuf->x, 0, stackbuf->y); } BKE_image_release_ibuf(this->m_image, stackbuf, nullptr); @@ -222,7 +219,7 @@ void ImageDepthOperation::executePixelSampled(float output[4], output[0] = 0.0f; } else { - int offset = y * this->m_width + x; + int offset = y * getWidth() + x; output[0] = this->m_depthBuffer[offset]; } } diff --git a/source/blender/compositor/operations/COM_ImageOperation.h b/source/blender/compositor/operations/COM_ImageOperation.h index f8b4239c9f8..e2fdfbf6f81 100644 --- a/source/blender/compositor/operations/COM_ImageOperation.h +++ b/source/blender/compositor/operations/COM_ImageOperation.h @@ -54,8 +54,7 @@ class BaseImageOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; virtual ImBuf *getImBuf(); diff --git a/source/blender/compositor/operations/COM_InpaintOperation.cc b/source/blender/compositor/operations/COM_InpaintOperation.cc index 5e76c41752c..5d440dd7d76 100644 --- a/source/blender/compositor/operations/COM_InpaintOperation.cc +++ b/source/blender/compositor/operations/COM_InpaintOperation.cc @@ -293,10 +293,7 @@ void InpaintSimpleOperation::get_area_of_interest(const int input_idx, { BLI_assert(input_idx == 0); UNUSED_VARS_NDEBUG(input_idx); - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } void InpaintSimpleOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_InvertOperation.cc b/source/blender/compositor/operations/COM_InvertOperation.cc index 4f71a1d0d1d..d406b809c7a 100644 --- a/source/blender/compositor/operations/COM_InvertOperation.cc +++ b/source/blender/compositor/operations/COM_InvertOperation.cc @@ -29,7 +29,7 @@ InvertOperation::InvertOperation() this->m_inputColorProgram = nullptr; this->m_color = true; this->m_alpha = false; - setResolutionInputSocketIndex(1); + set_canvas_input_index(1); this->flags.can_be_constant = true; } void InvertOperation::initExecution() diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc index c00aafc19a2..b4840926d55 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc @@ -303,11 +303,9 @@ void KeyingScreenOperation::deinitializeTileData(rcti * /*rect*/, void *data) MEM_freeN(tile_data); } -void KeyingScreenOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void KeyingScreenOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = 0; - resolution[1] = 0; + r_area = COM_AREA_NONE; if (this->m_movieClip) { MovieClipUser user = {0}; @@ -317,9 +315,9 @@ void KeyingScreenOperation::determineResolution(unsigned int resolution[2], BKE_movieclip_user_set_frame(&user, clip_frame); BKE_movieclip_get_size(this->m_movieClip, &user, &width, &height); - - resolution[0] = width; - resolution[1] = height; + r_area = preferred_area; + r_area.xmax = r_area.xmin + width; + r_area.ymax = r_area.ymin + height; } } diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.h b/source/blender/compositor/operations/COM_KeyingScreenOperation.h index 0bc47dbea30..7a7dda27710 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.h +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.h @@ -57,8 +57,7 @@ class KeyingScreenOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; TriangulationData *buildVoronoiTriangulation(); diff --git a/source/blender/compositor/operations/COM_MapUVOperation.cc b/source/blender/compositor/operations/COM_MapUVOperation.cc index ad047c619f8..ba38e583b30 100644 --- a/source/blender/compositor/operations/COM_MapUVOperation.cc +++ b/source/blender/compositor/operations/COM_MapUVOperation.cc @@ -23,12 +23,12 @@ namespace blender::compositor { MapUVOperation::MapUVOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addInputSocket(DataType::Vector); this->addOutputSocket(DataType::Color); this->m_alpha = 0.0f; this->flags.complex = true; - setResolutionInputSocketIndex(1); + set_canvas_input_index(UV_INPUT_INDEX); this->m_inputUVProgram = nullptr; this->m_inputColorProgram = nullptr; @@ -36,11 +36,11 @@ MapUVOperation::MapUVOperation() void MapUVOperation::init_data() { - NodeOperation *image_input = get_input_operation(0); + NodeOperation *image_input = get_input_operation(IMAGE_INPUT_INDEX); image_width_ = image_input->getWidth(); image_height_ = image_input->getHeight(); - NodeOperation *uv_input = get_input_operation(1); + NodeOperation *uv_input = get_input_operation(UV_INPUT_INDEX); uv_width_ = uv_input->getWidth(); uv_height_ = uv_input->getHeight(); } @@ -205,14 +205,11 @@ void MapUVOperation::get_area_of_interest(const int input_idx, rcti &r_input_area) { switch (input_idx) { - case 0: { - r_input_area.xmin = 0; - r_input_area.xmax = image_width_; - r_input_area.ymin = 0; - r_input_area.ymax = image_height_; + case IMAGE_INPUT_INDEX: { + r_input_area = get_input_operation(IMAGE_INPUT_INDEX)->get_canvas(); break; } - case 1: { + case UV_INPUT_INDEX: { r_input_area = output_area; expand_area_for_sampler(r_input_area, PixelSampler::Bilinear); break; @@ -224,7 +221,7 @@ void MapUVOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), const rcti &UNUSED(area), Span<MemoryBuffer *> inputs) { - const MemoryBuffer *uv_input = inputs[1]; + const MemoryBuffer *uv_input = inputs[UV_INPUT_INDEX]; uv_input_read_fn_ = [=](float x, float y, float *out) { uv_input->read_elem_bilinear(x, y, out); }; @@ -234,7 +231,7 @@ void MapUVOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) { - const MemoryBuffer *input_image = inputs[0]; + const MemoryBuffer *input_image = inputs[IMAGE_INPUT_INDEX]; for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { float xy[2] = {(float)it.x, (float)it.y}; float uv[2]; diff --git a/source/blender/compositor/operations/COM_MapUVOperation.h b/source/blender/compositor/operations/COM_MapUVOperation.h index 65fbcb461c9..49c6689f700 100644 --- a/source/blender/compositor/operations/COM_MapUVOperation.h +++ b/source/blender/compositor/operations/COM_MapUVOperation.h @@ -24,6 +24,8 @@ namespace blender::compositor { class MapUVOperation : public MultiThreadedOperation { private: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int UV_INPUT_INDEX = 1; /** * Cached reference to the inputProgram */ diff --git a/source/blender/compositor/operations/COM_MaskOperation.cc b/source/blender/compositor/operations/COM_MaskOperation.cc index 84992f23924..65b89a8c79a 100644 --- a/source/blender/compositor/operations/COM_MaskOperation.cc +++ b/source/blender/compositor/operations/COM_MaskOperation.cc @@ -109,22 +109,15 @@ void MaskOperation::deinitExecution() } } -void MaskOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MaskOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (this->m_maskWidth == 0 || this->m_maskHeight == 0) { - NodeOperation::determineResolution(resolution, preferredResolution); + r_area = COM_AREA_NONE; } else { - unsigned int nr[2]; - - nr[0] = this->m_maskWidth; - nr[1] = this->m_maskHeight; - - NodeOperation::determineResolution(resolution, nr); - - resolution[0] = this->m_maskWidth; - resolution[1] = this->m_maskHeight; + r_area = preferred_area; + r_area.xmax = r_area.xmin + m_maskWidth; + r_area.ymax = r_area.ymin + m_maskHeight; } } diff --git a/source/blender/compositor/operations/COM_MaskOperation.h b/source/blender/compositor/operations/COM_MaskOperation.h index 81e344c0451..cc7eb0c022c 100644 --- a/source/blender/compositor/operations/COM_MaskOperation.h +++ b/source/blender/compositor/operations/COM_MaskOperation.h @@ -54,8 +54,7 @@ class MaskOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; public: MaskOperation(); diff --git a/source/blender/compositor/operations/COM_MathBaseOperation.cc b/source/blender/compositor/operations/COM_MathBaseOperation.cc index 2256dce011b..d3fb83caf7c 100644 --- a/source/blender/compositor/operations/COM_MathBaseOperation.cc +++ b/source/blender/compositor/operations/COM_MathBaseOperation.cc @@ -51,22 +51,19 @@ void MathBaseOperation::deinitExecution() this->m_inputValue3Operation = nullptr; } -void MathBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MathBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { NodeOperationInput *socket; - unsigned int tempPreferredResolution[2] = {0, 0}; - unsigned int tempResolution[2]; - + rcti temp_area; socket = this->getInputSocket(0); - socket->determineResolution(tempResolution, tempPreferredResolution); - if ((tempResolution[0] != 0) && (tempResolution[1] != 0)) { - this->setResolutionInputSocketIndex(0); + const bool determined = socket->determine_canvas(COM_AREA_NONE, temp_area); + if (determined) { + this->set_canvas_input_index(0); } else { - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); } - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); } void MathBaseOperation::clampIfNeeded(float *color) diff --git a/source/blender/compositor/operations/COM_MathBaseOperation.h b/source/blender/compositor/operations/COM_MathBaseOperation.h index d2da05db68e..0188eb50fa8 100644 --- a/source/blender/compositor/operations/COM_MathBaseOperation.h +++ b/source/blender/compositor/operations/COM_MathBaseOperation.h @@ -75,8 +75,7 @@ class MathBaseOperation : public MultiThreadedOperation { /** * Determine resolution */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setUseClamp(bool value) { diff --git a/source/blender/compositor/operations/COM_MixOperation.cc b/source/blender/compositor/operations/COM_MixOperation.cc index 77ecbf60356..895d32e6fee 100644 --- a/source/blender/compositor/operations/COM_MixOperation.cc +++ b/source/blender/compositor/operations/COM_MixOperation.cc @@ -66,29 +66,27 @@ void MixBaseOperation::executePixelSampled(float output[4], float x, float y, Pi output[3] = inputColor1[3]; } -void MixBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MixBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { NodeOperationInput *socket; - unsigned int tempPreferredResolution[2] = {0, 0}; - unsigned int tempResolution[2]; + rcti temp_area; socket = this->getInputSocket(1); - socket->determineResolution(tempResolution, tempPreferredResolution); - if ((tempResolution[0] != 0) && (tempResolution[1] != 0)) { - this->setResolutionInputSocketIndex(1); + bool determined = socket->determine_canvas(COM_AREA_NONE, temp_area); + if (determined) { + this->set_canvas_input_index(1); } else { socket = this->getInputSocket(2); - socket->determineResolution(tempResolution, tempPreferredResolution); - if ((tempResolution[0] != 0) && (tempResolution[1] != 0)) { - this->setResolutionInputSocketIndex(2); + determined = socket->determine_canvas(COM_AREA_NONE, temp_area); + if (determined) { + this->set_canvas_input_index(2); } else { - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); } } - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); } void MixBaseOperation::deinitExecution() @@ -111,7 +109,7 @@ void MixBaseOperation::update_memory_buffer_partial(MemoryBuffer *output, p.value_stride = input_value->elem_stride; p.color1_stride = input_color1->elem_stride; p.color2_stride = input_color2->elem_stride; - for (const int y : YRange(area)) { + for (int y = area.ymin; y < area.ymax; y++) { p.out = output->get_elem(area.xmin, y); p.row_end = p.out + width * output->elem_stride; p.value = input_value->get_elem(area.xmin, y); diff --git a/source/blender/compositor/operations/COM_MixOperation.h b/source/blender/compositor/operations/COM_MixOperation.h index 7ef9d78d58f..fabbea422eb 100644 --- a/source/blender/compositor/operations/COM_MixOperation.h +++ b/source/blender/compositor/operations/COM_MixOperation.h @@ -87,8 +87,7 @@ class MixBaseOperation : public MultiThreadedOperation { */ void deinitExecution() override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setUseValueAlphaMultiply(const bool value) { diff --git a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc index e36e93984fb..aed91d4d46e 100644 --- a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc +++ b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc @@ -29,8 +29,9 @@ MovieClipAttributeOperation::MovieClipAttributeOperation() this->m_framenumber = 0; this->m_attribute = MCA_X; this->m_invert = false; - needs_resolution_to_get_constant_ = true; + needs_canvas_to_get_constant_ = true; is_value_calculated_ = false; + stabilization_resolution_socket_ = nullptr; } void MovieClipAttributeOperation::initExecution() @@ -42,7 +43,7 @@ void MovieClipAttributeOperation::initExecution() void MovieClipAttributeOperation::calc_value() { - BLI_assert(this->get_flags().is_resolution_set); + BLI_assert(this->get_flags().is_canvas_set); is_value_calculated_ = true; if (this->m_clip == nullptr) { return; @@ -53,8 +54,17 @@ void MovieClipAttributeOperation::calc_value() scale = 1.0f; angle = 0.0f; int clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(this->m_clip, this->m_framenumber); - BKE_tracking_stabilization_data_get( - this->m_clip, clip_framenr, getWidth(), getHeight(), loc, &scale, &angle); + NodeOperation &stabilization_operation = + stabilization_resolution_socket_ ? + stabilization_resolution_socket_->getLink()->getOperation() : + *this; + BKE_tracking_stabilization_data_get(this->m_clip, + clip_framenr, + stabilization_operation.getWidth(), + stabilization_operation.getHeight(), + loc, + &scale, + &angle); switch (this->m_attribute) { case MCA_SCALE: this->m_value = scale; @@ -87,11 +97,9 @@ void MovieClipAttributeOperation::executePixelSampled(float output[4], output[0] = this->m_value; } -void MovieClipAttributeOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MovieClipAttributeOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } const float *MovieClipAttributeOperation::get_constant_elem() diff --git a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h index 28c39d4dad3..50680653aea 100644 --- a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h +++ b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h @@ -42,6 +42,7 @@ class MovieClipAttributeOperation : public ConstantOperation { bool m_invert; MovieClipAttribute m_attribute; bool is_value_calculated_; + NodeOperationInput *stabilization_resolution_socket_; public: /** @@ -55,8 +56,7 @@ class MovieClipAttributeOperation : public ConstantOperation { * The inner loop of this operation. */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; const float *get_constant_elem() override; @@ -77,6 +77,14 @@ class MovieClipAttributeOperation : public ConstantOperation { this->m_invert = invert; } + /** + * Set an operation socket which input will be used to get the resolution for stabilization. + */ + void set_socket_input_resolution_for_stabilization(NodeOperationInput *input_socket) + { + stabilization_resolution_socket_ = input_socket; + } + private: void calc_value(); }; diff --git a/source/blender/compositor/operations/COM_MovieClipOperation.cc b/source/blender/compositor/operations/COM_MovieClipOperation.cc index e520b928edf..1ca33b12432 100644 --- a/source/blender/compositor/operations/COM_MovieClipOperation.cc +++ b/source/blender/compositor/operations/COM_MovieClipOperation.cc @@ -71,19 +71,13 @@ void MovieClipBaseOperation::deinitExecution() } } -void MovieClipBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void MovieClipBaseOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { - resolution[0] = 0; - resolution[1] = 0; - + r_area = COM_AREA_NONE; if (this->m_movieClip) { int width, height; - BKE_movieclip_get_size(this->m_movieClip, this->m_movieClipUser, &width, &height); - - resolution[0] = width; - resolution[1] = height; + BLI_rcti_init(&r_area, 0, width, 0, height); } } diff --git a/source/blender/compositor/operations/COM_MovieClipOperation.h b/source/blender/compositor/operations/COM_MovieClipOperation.h index 0a0c4c00f81..465ccd4fc00 100644 --- a/source/blender/compositor/operations/COM_MovieClipOperation.h +++ b/source/blender/compositor/operations/COM_MovieClipOperation.h @@ -41,8 +41,7 @@ class MovieClipBaseOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; public: MovieClipBaseOperation(); diff --git a/source/blender/compositor/operations/COM_MovieDistortionOperation.cc b/source/blender/compositor/operations/COM_MovieDistortionOperation.cc index d3424959061..49f43d2c1a7 100644 --- a/source/blender/compositor/operations/COM_MovieDistortionOperation.cc +++ b/source/blender/compositor/operations/COM_MovieDistortionOperation.cc @@ -29,15 +29,14 @@ MovieDistortionOperation::MovieDistortionOperation(bool distortion) { this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_movieClip = nullptr; this->m_apply = distortion; } -void MovieDistortionOperation::initExecution() +void MovieDistortionOperation::init_data() { - this->m_inputOperation = this->getInputSocketReader(0); if (this->m_movieClip) { MovieTracking *tracking = &this->m_movieClip->tracking; MovieClipUser clipUser = {0}; @@ -49,10 +48,10 @@ void MovieDistortionOperation::initExecution() float delta[2]; rcti full_frame; full_frame.xmin = full_frame.ymin = 0; - full_frame.xmax = this->m_width; - full_frame.ymax = this->m_height; + full_frame.xmax = this->getWidth(); + full_frame.ymax = this->getHeight(); BKE_tracking_max_distortion_delta_across_bound( - tracking, this->m_width, this->m_height, &full_frame, !this->m_apply, delta); + tracking, this->getWidth(), this->getHeight(), &full_frame, !this->m_apply, delta); /* 5 is just in case we didn't hit real max of distortion in * BKE_tracking_max_undistortion_delta_across_bound @@ -60,15 +59,25 @@ void MovieDistortionOperation::initExecution() m_margin[0] = delta[0] + 5; m_margin[1] = delta[1] + 5; - this->m_distortion = BKE_tracking_distortion_new( - tracking, calibration_width, calibration_height); this->m_calibration_width = calibration_width; this->m_calibration_height = calibration_height; this->m_pixel_aspect = tracking->camera.pixel_aspect; } else { m_margin[0] = m_margin[1] = 0; - this->m_distortion = nullptr; + } +} + +void MovieDistortionOperation::initExecution() +{ + m_inputOperation = this->getInputSocketReader(0); + if (m_movieClip) { + MovieTracking *tracking = &m_movieClip->tracking; + m_distortion = BKE_tracking_distortion_new( + tracking, m_calibration_width, m_calibration_height); + } + else { + m_distortion = nullptr; } } @@ -89,8 +98,8 @@ void MovieDistortionOperation::executePixelSampled(float output[4], if (this->m_distortion != nullptr) { /* float overscan = 0.0f; */ const float pixel_aspect = this->m_pixel_aspect; - const float w = (float)this->m_width /* / (1 + overscan) */; - const float h = (float)this->m_height /* / (1 + overscan) */; + const float w = (float)this->getWidth() /* / (1 + overscan) */; + const float h = (float)this->getHeight() /* / (1 + overscan) */; const float aspx = w / (float)this->m_calibration_width; const float aspy = h / (float)this->m_calibration_height; float in[2]; @@ -152,8 +161,8 @@ void MovieDistortionOperation::update_memory_buffer_partial(MemoryBuffer *output /* `float overscan = 0.0f;` */ const float pixel_aspect = this->m_pixel_aspect; - const float w = (float)this->m_width /* `/ (1 + overscan)` */; - const float h = (float)this->m_height /* `/ (1 + overscan)` */; + const float w = (float)this->getWidth() /* `/ (1 + overscan)` */; + const float h = (float)this->getHeight() /* `/ (1 + overscan)` */; const float aspx = w / (float)this->m_calibration_width; const float aspy = h / (float)this->m_calibration_height; float xy[2]; diff --git a/source/blender/compositor/operations/COM_MovieDistortionOperation.h b/source/blender/compositor/operations/COM_MovieDistortionOperation.h index 69c2f9c269c..abf0553b75b 100644 --- a/source/blender/compositor/operations/COM_MovieDistortionOperation.h +++ b/source/blender/compositor/operations/COM_MovieDistortionOperation.h @@ -44,6 +44,7 @@ class MovieDistortionOperation : public MultiThreadedOperation { MovieDistortionOperation(bool distortion); void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + void init_data() override; void initExecution() override; void deinitExecution() override; diff --git a/source/blender/compositor/operations/COM_NormalizeOperation.cc b/source/blender/compositor/operations/COM_NormalizeOperation.cc index c3e72d2575f..7d79b375b45 100644 --- a/source/blender/compositor/operations/COM_NormalizeOperation.cc +++ b/source/blender/compositor/operations/COM_NormalizeOperation.cc @@ -133,11 +133,7 @@ void NormalizeOperation::get_area_of_interest(const int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - NodeOperation *input = get_input_operation(0); - r_input_area.xmin = 0; - r_input_area.xmax = input->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = input->getHeight(); + r_input_area = get_input_operation(0)->get_canvas(); } void NormalizeOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), diff --git a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc index 5b6f650d40e..d436b00a6e3 100644 --- a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc +++ b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc @@ -86,8 +86,8 @@ void *OutputOpenExrSingleLayerMultiViewOperation::get_handle(const char *filenam /* prepare the file with all the channels */ - if (IMB_exr_begin_write( - exrhandle, filename, width, height, this->m_format->exr_codec, nullptr) == 0) { + if (!IMB_exr_begin_write( + exrhandle, filename, width, height, this->m_format->exr_codec, nullptr)) { printf("Error Writing Singlelayer Multiview Openexr\n"); IMB_exr_close(exrhandle); } @@ -200,8 +200,7 @@ void *OutputOpenExrMultiLayerMultiViewOperation::get_handle(const char *filename /* prepare the file with all the channels for the header */ StampData *stamp_data = createStampData(); - if (IMB_exr_begin_write(exrhandle, filename, width, height, this->m_exr_codec, stamp_data) == - 0) { + if (!IMB_exr_begin_write(exrhandle, filename, width, height, this->m_exr_codec, stamp_data)) { printf("Error Writing Multilayer Multiview Openexr\n"); IMB_exr_close(exrhandle); BKE_stamp_data_free(stamp_data); diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.cc b/source/blender/compositor/operations/COM_OutputFileOperation.cc index 402d29893a4..79be95bb686 100644 --- a/source/blender/compositor/operations/COM_OutputFileOperation.cc +++ b/source/blender/compositor/operations/COM_OutputFileOperation.cc @@ -339,7 +339,7 @@ OutputOpenExrMultiLayerOperation::OutputOpenExrMultiLayerOperation(const Scene * this->m_exr_codec = exr_codec; this->m_exr_half_float = exr_half_float; this->m_viewName = viewName; - this->setResolutionInputSocketIndex(RESOLUTION_INPUT_ANY); + this->set_canvas_input_index(RESOLUTION_INPUT_ANY); } void OutputOpenExrMultiLayerOperation::add_layer(const char *name, diff --git a/source/blender/compositor/operations/COM_PixelateOperation.cc b/source/blender/compositor/operations/COM_PixelateOperation.cc index 94827cd1b02..4fc238bc094 100644 --- a/source/blender/compositor/operations/COM_PixelateOperation.cc +++ b/source/blender/compositor/operations/COM_PixelateOperation.cc @@ -24,7 +24,7 @@ PixelateOperation::PixelateOperation(DataType datatype) { this->addInputSocket(datatype); this->addOutputSocket(datatype); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; } diff --git a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc index d2a06ddd7c4..65cd08456ef 100644 --- a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc @@ -209,15 +209,13 @@ void *PlaneCornerPinMaskOperation::initializeTileData(rcti *rect) return data; } -void PlaneCornerPinMaskOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void PlaneCornerPinMaskOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (execution_model_ == eExecutionModel::FullFrame) { - /* Determine inputs resolution. */ - PlaneDistortMaskOperation::determineResolution(resolution, preferredResolution); + /* Determine input canvases. */ + PlaneDistortMaskOperation::determine_canvas(preferred_area, r_area); } - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } void PlaneCornerPinMaskOperation::get_area_of_interest(const int UNUSED(input_idx), @@ -225,7 +223,7 @@ void PlaneCornerPinMaskOperation::get_area_of_interest(const int UNUSED(input_id rcti &r_input_area) { /* All corner inputs are used as constants. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; } /* ******** PlaneCornerPinWarpImageOperation ******** */ @@ -322,7 +320,7 @@ void PlaneCornerPinWarpImageOperation::get_area_of_interest(const int input_idx, } else { /* Corner inputs are used as constants. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; } } diff --git a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h index 2831e937147..7b3917923d2 100644 --- a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h +++ b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h @@ -43,8 +43,7 @@ class PlaneCornerPinMaskOperation : public PlaneDistortMaskOperation { void *initializeTileData(rcti *rect) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; }; diff --git a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc index ccabb3cf11c..31ef41789fd 100644 --- a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc @@ -73,7 +73,7 @@ BLI_INLINE void warpCoord(float x, float y, float matrix[3][3], float uv[2], flo PlaneDistortWarpImageOperation::PlaneDistortWarpImageOperation() : PlaneDistortBaseOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Color); this->m_pixelReader = nullptr; this->flags.complex = true; @@ -196,10 +196,7 @@ void PlaneDistortWarpImageOperation::get_area_of_interest(const int input_idx, } /* TODO: figure out the area needed for warping and EWA filtering. */ - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = get_input_operation(0)->getWidth(); - r_input_area.ymax = get_input_operation(0)->getHeight(); + r_input_area = get_input_operation(0)->get_canvas(); /* Old implementation but resulting coordinates are way out of input operation bounds and in some * cases the area result may incorrectly cause cropping. */ diff --git a/source/blender/compositor/operations/COM_PlaneTrackOperation.cc b/source/blender/compositor/operations/COM_PlaneTrackOperation.cc index bf24f843ca2..7226a133a52 100644 --- a/source/blender/compositor/operations/COM_PlaneTrackOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneTrackOperation.cc @@ -83,19 +83,17 @@ void PlaneTrackCommon::readCornersFromTrack(float corners[4][2], float frame) } } -void PlaneTrackCommon::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void PlaneTrackCommon::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = 0; - resolution[1] = 0; - + r_area = COM_AREA_NONE; if (this->m_movieClip) { int width, height; MovieClipUser user = {0}; BKE_movieclip_user_set_frame(&user, this->m_framenumber); BKE_movieclip_get_size(this->m_movieClip, &user, &width, &height); - resolution[0] = width; - resolution[1] = height; + r_area = preferred_area; + r_area.xmax = r_area.xmin + width; + r_area.ymax = r_area.ymin + height; } } diff --git a/source/blender/compositor/operations/COM_PlaneTrackOperation.h b/source/blender/compositor/operations/COM_PlaneTrackOperation.h index d2027755162..60845aac514 100644 --- a/source/blender/compositor/operations/COM_PlaneTrackOperation.h +++ b/source/blender/compositor/operations/COM_PlaneTrackOperation.h @@ -41,7 +41,7 @@ class PlaneTrackCommon { * implementation classes must make wrappers to use these methods, see below. */ void read_and_calculate_corners(PlaneDistortBaseOperation *distort_op); - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + void determine_canvas(const rcti &preferred_area, rcti &r_area); public: PlaneTrackCommon(); @@ -77,13 +77,13 @@ class PlaneTrackMaskOperation : public PlaneDistortMaskOperation, public PlaneTr void initExecution() override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override + void determine_canvas(const rcti &preferred_area, rcti &r_area) override { - PlaneTrackCommon::determineResolution(resolution, preferredResolution); + PlaneTrackCommon::determine_canvas(preferred_area, r_area); - unsigned int temp[2]; - NodeOperation::determineResolution(temp, resolution); + rcti unused; + rcti &preferred = r_area; + NodeOperation::determine_canvas(preferred, unused); } }; @@ -98,12 +98,13 @@ class PlaneTrackWarpImageOperation : public PlaneDistortWarpImageOperation, void initExecution() override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override + void determine_canvas(const rcti &preferred_area, rcti &r_area) override { - PlaneTrackCommon::determineResolution(resolution, preferredResolution); - unsigned int temp[2]; - NodeOperation::determineResolution(temp, resolution); + PlaneTrackCommon::determine_canvas(preferred_area, r_area); + + rcti unused; + rcti &preferred = r_area; + NodeOperation::determine_canvas(preferred, unused); } }; diff --git a/source/blender/compositor/operations/COM_PreviewOperation.cc b/source/blender/compositor/operations/COM_PreviewOperation.cc index fa8b5ffcabf..34520264d54 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.cc +++ b/source/blender/compositor/operations/COM_PreviewOperation.cc @@ -41,7 +41,7 @@ PreviewOperation::PreviewOperation(const ColorManagedViewSettings *viewSettings, const unsigned int defaultHeight) { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->m_preview = nullptr; this->m_outputBuffer = nullptr; this->m_input = nullptr; @@ -130,14 +130,14 @@ bool PreviewOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } -void PreviewOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void PreviewOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { /* Use default preview resolution as preferred ensuring it has size so that * generated inputs (which don't have resolution on their own) are displayed */ BLI_assert(this->m_defaultWidth > 0 && this->m_defaultHeight > 0); - unsigned int previewPreferredRes[2] = {this->m_defaultWidth, this->m_defaultHeight}; - NodeOperation::determineResolution(resolution, previewPreferredRes); + rcti local_preferred; + BLI_rcti_init(&local_preferred, 0, m_defaultWidth, 0, m_defaultHeight); + NodeOperation::determine_canvas(local_preferred, r_area); /* If resolution is 0 there are two possible scenarios: * - Either node is not connected at all @@ -148,8 +148,8 @@ void PreviewOperation::determineResolution(unsigned int resolution[2], * The latter case would only happen if an input doesn't set any resolution ignoring output * preferred resolution. In such case preview size will be 0 too. */ - int width = resolution[0]; - int height = resolution[1]; + int width = BLI_rcti_size_x(&r_area); + int height = BLI_rcti_size_y(&r_area); this->m_divider = 0.0f; if (width > 0 && height > 0) { if (width > height) { @@ -162,8 +162,7 @@ void PreviewOperation::determineResolution(unsigned int resolution[2], width = width * this->m_divider; height = height * this->m_divider; - resolution[0] = width; - resolution[1] = height; + BLI_rcti_init(&r_area, r_area.xmin, r_area.xmin + width, r_area.ymin, r_area.ymin + height); } eCompositorPriority PreviewOperation::getRenderPriority() const diff --git a/source/blender/compositor/operations/COM_PreviewOperation.h b/source/blender/compositor/operations/COM_PreviewOperation.h index 05dae9c4dd8..4bd0f07d882 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.h +++ b/source/blender/compositor/operations/COM_PreviewOperation.h @@ -58,8 +58,7 @@ class PreviewOperation : public MultiThreadedOperation { eCompositorPriority getRenderPriority() const override; void executeRegion(rcti *rect, unsigned int tileNumber) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; diff --git a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc index fcab5dd5751..2982f59a019 100644 --- a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc +++ b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc @@ -131,13 +131,30 @@ void ProjectorLensDistortionOperation::updateDispersion() this->unlockMutex(); } +void ProjectorLensDistortionOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + switch (execution_model_) { + case eExecutionModel::FullFrame: { + set_determined_canvas_modifier([=](rcti &canvas) { + /* Ensure screen space. */ + BLI_rcti_translate(&canvas, -canvas.xmin, -canvas.ymin); + }); + break; + } + default: + break; + } + + NodeOperation::determine_canvas(preferred_area, r_area); +} + void ProjectorLensDistortionOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { if (input_idx == 1) { /* Dispersion input is used as constant only. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; return; } diff --git a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h index 7c7626bf271..b42fa3a361c 100644 --- a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h +++ b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h @@ -62,6 +62,7 @@ class ProjectorLensDistortionOperation : public MultiThreadedOperation { void updateDispersion(); + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_ReadBufferOperation.cc b/source/blender/compositor/operations/COM_ReadBufferOperation.cc index d35d2516f16..599370751bb 100644 --- a/source/blender/compositor/operations/COM_ReadBufferOperation.cc +++ b/source/blender/compositor/operations/COM_ReadBufferOperation.cc @@ -36,16 +36,17 @@ void *ReadBufferOperation::initializeTileData(rcti * /*rect*/) return m_buffer; } -void ReadBufferOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void ReadBufferOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (this->m_memoryProxy != nullptr) { WriteBufferOperation *operation = this->m_memoryProxy->getWriteBufferOperation(); - operation->determineResolution(resolution, preferredResolution); - operation->setResolution(resolution); + operation->determine_canvas(preferred_area, r_area); + operation->set_canvas(r_area); /** \todo may not occur! But does with blur node. */ if (this->m_memoryProxy->getExecutor()) { + uint resolution[2] = {static_cast<uint>(BLI_rcti_size_x(&r_area)), + static_cast<uint>(BLI_rcti_size_y(&r_area))}; this->m_memoryProxy->getExecutor()->setResolution(resolution); } diff --git a/source/blender/compositor/operations/COM_ReadBufferOperation.h b/source/blender/compositor/operations/COM_ReadBufferOperation.h index 8b96b961a43..02f6eccd246 100644 --- a/source/blender/compositor/operations/COM_ReadBufferOperation.h +++ b/source/blender/compositor/operations/COM_ReadBufferOperation.h @@ -43,8 +43,7 @@ class ReadBufferOperation : public NodeOperation { return this->m_memoryProxy; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void *initializeTileData(rcti *rect) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.cc b/source/blender/compositor/operations/COM_RenderLayersProg.cc index 72e2c92c9cf..2ac551ffe6f 100644 --- a/source/blender/compositor/operations/COM_RenderLayersProg.cc +++ b/source/blender/compositor/operations/COM_RenderLayersProg.cc @@ -196,15 +196,13 @@ void RenderLayersProg::deinitExecution() } } -void RenderLayersProg::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void RenderLayersProg::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { Scene *sce = this->getScene(); Render *re = (sce) ? RE_GetSceneRender(sce) : nullptr; RenderResult *rr = nullptr; - resolution[0] = 0; - resolution[1] = 0; + r_area = COM_AREA_NONE; if (re) { rr = RE_AcquireResultRead(re); @@ -215,8 +213,7 @@ void RenderLayersProg::determineResolution(unsigned int resolution[2], if (view_layer) { RenderLayer *rl = RE_GetRenderLayer(rr, view_layer->name); if (rl) { - resolution[0] = rl->rectx; - resolution[1] = rl->recty; + BLI_rcti_init(&r_area, 0, rl->rectx, 0, rl->recty); } } } diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.h b/source/blender/compositor/operations/COM_RenderLayersProg.h index dd76a56d645..b499afd45df 100644 --- a/source/blender/compositor/operations/COM_RenderLayersProg.h +++ b/source/blender/compositor/operations/COM_RenderLayersProg.h @@ -73,8 +73,7 @@ class RenderLayersProg : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; /** * retrieve the reference to the float buffer of the renderer. diff --git a/source/blender/compositor/operations/COM_RotateOperation.cc b/source/blender/compositor/operations/COM_RotateOperation.cc index e3c482c15cb..9e26c93feac 100644 --- a/source/blender/compositor/operations/COM_RotateOperation.cc +++ b/source/blender/compositor/operations/COM_RotateOperation.cc @@ -25,10 +25,10 @@ namespace blender::compositor { RotateOperation::RotateOperation() { - this->addInputSocket(DataType::Color); - this->addInputSocket(DataType::Value); + this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Value, ResizeMode::None); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_imageSocket = nullptr; this->m_degreeSocket = nullptr; this->m_doDegree2RadConversion = false; @@ -36,6 +36,21 @@ RotateOperation::RotateOperation() sampler_ = PixelSampler::Bilinear; } +void RotateOperation::get_rotation_center(const rcti &area, float &r_x, float &r_y) +{ + r_x = (BLI_rcti_size_x(&area) - 1) / 2.0; + r_y = (BLI_rcti_size_y(&area) - 1) / 2.0; +} + +void RotateOperation::get_rotation_offset(const rcti &input_canvas, + const rcti &rotate_canvas, + float &r_offset_x, + float &r_offset_y) +{ + r_offset_x = (BLI_rcti_size_x(&input_canvas) - BLI_rcti_size_x(&rotate_canvas)) / 2.0f; + r_offset_y = (BLI_rcti_size_y(&input_canvas) - BLI_rcti_size_y(&rotate_canvas)) / 2.0f; +} + void RotateOperation::get_area_rotation_bounds(const rcti &area, const float center_x, const float center_y, @@ -48,14 +63,14 @@ void RotateOperation::get_area_rotation_bounds(const rcti &area, const float dxmax = area.xmax - center_x; const float dymax = area.ymax - center_y; - const float x1 = center_x + (cosine * dxmin + sine * dymin); - const float x2 = center_x + (cosine * dxmax + sine * dymin); - const float x3 = center_x + (cosine * dxmin + sine * dymax); - const float x4 = center_x + (cosine * dxmax + sine * dymax); - const float y1 = center_y + (-sine * dxmin + cosine * dymin); - const float y2 = center_y + (-sine * dxmax + cosine * dymin); - const float y3 = center_y + (-sine * dxmin + cosine * dymax); - const float y4 = center_y + (-sine * dxmax + cosine * dymax); + const float x1 = center_x + (cosine * dxmin + (-sine) * dymin); + const float x2 = center_x + (cosine * dxmax + (-sine) * dymin); + const float x3 = center_x + (cosine * dxmin + (-sine) * dymax); + const float x4 = center_x + (cosine * dxmax + (-sine) * dymax); + const float y1 = center_y + (sine * dxmin + cosine * dymin); + const float y2 = center_y + (sine * dxmax + cosine * dymin); + const float y3 = center_y + (sine * dxmin + cosine * dymax); + const float y4 = center_y + (sine * dxmax + cosine * dymax); const float minx = MIN2(x1, MIN2(x2, MIN2(x3, x4))); const float maxx = MAX2(x1, MAX2(x2, MAX2(x3, x4))); const float miny = MIN2(y1, MIN2(y2, MIN2(y3, y4))); @@ -67,10 +82,56 @@ void RotateOperation::get_area_rotation_bounds(const rcti &area, r_bounds.ymax = ceil(maxy); } +void RotateOperation::get_area_rotation_bounds_inverted(const rcti &area, + const float center_x, + const float center_y, + const float sine, + const float cosine, + rcti &r_bounds) +{ + get_area_rotation_bounds(area, center_x, center_y, -sine, cosine, r_bounds); +} + +void RotateOperation::get_rotation_area_of_interest(const rcti &input_canvas, + const rcti &rotate_canvas, + const float sine, + const float cosine, + const rcti &output_area, + rcti &r_input_area) +{ + float center_x, center_y; + get_rotation_center(input_canvas, center_x, center_y); + + float rotate_offset_x, rotate_offset_y; + get_rotation_offset(input_canvas, rotate_canvas, rotate_offset_x, rotate_offset_y); + + r_input_area = output_area; + BLI_rcti_translate(&r_input_area, rotate_offset_x, rotate_offset_y); + get_area_rotation_bounds_inverted(r_input_area, center_x, center_y, sine, cosine, r_input_area); +} + +void RotateOperation::get_rotation_canvas(const rcti &input_canvas, + const float sine, + const float cosine, + rcti &r_canvas) +{ + float center_x, center_y; + get_rotation_center(input_canvas, center_x, center_y); + + rcti rot_bounds; + get_area_rotation_bounds(input_canvas, center_x, center_y, sine, cosine, rot_bounds); + + float offset_x, offset_y; + get_rotation_offset(input_canvas, rot_bounds, offset_x, offset_y); + r_canvas = rot_bounds; + BLI_rcti_translate(&r_canvas, -offset_x, -offset_y); +} + void RotateOperation::init_data() { - this->m_centerX = (getWidth() - 1) / 2.0; - this->m_centerY = (getHeight() - 1) / 2.0; + if (execution_model_ == eExecutionModel::Tiled) { + get_rotation_center(get_canvas(), m_centerX, m_centerY); + } } void RotateOperation::initExecution() @@ -94,11 +155,7 @@ inline void RotateOperation::ensureDegree() this->m_degreeSocket->readSampled(degree, 0, 0, PixelSampler::Nearest); break; case eExecutionModel::FullFrame: - NodeOperation *degree_op = getInputOperation(DEGREE_INPUT_INDEX); - const bool is_constant_degree = degree_op->get_flags().is_constant_operation; - degree[0] = is_constant_degree ? - static_cast<ConstantOperation *>(degree_op)->get_constant_elem()[0] : - 0.0f; + degree[0] = get_input_operation(DEGREE_INPUT_INDEX)->get_constant_value_default(0.0f); break; } @@ -159,18 +216,40 @@ bool RotateOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void RotateOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + if (execution_model_ == eExecutionModel::Tiled) { + NodeOperation::determine_canvas(preferred_area, r_area); + return; + } + + const bool image_determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (image_determined) { + rcti input_canvas = r_area; + rcti unused; + getInputSocket(DEGREE_INPUT_INDEX)->determine_canvas(input_canvas, unused); + + ensureDegree(); + + get_rotation_canvas(input_canvas, m_sine, m_cosine, r_area); + } +} + void RotateOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { if (input_idx == DEGREE_INPUT_INDEX) { - /* Degrees input is always used as constant. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; return; } ensureDegree(); - get_area_rotation_bounds(output_area, m_centerX, m_centerY, m_sine, m_cosine, r_input_area); + + const rcti &input_image_canvas = get_input_operation(IMAGE_INPUT_INDEX)->get_canvas(); + get_rotation_area_of_interest( + input_image_canvas, this->get_canvas(), m_sine, m_cosine, output_area, r_input_area); expand_area_for_sampler(r_input_area, sampler_); } @@ -178,13 +257,20 @@ void RotateOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) { - ensureDegree(); const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX]; + + NodeOperation *image_op = get_input_operation(IMAGE_INPUT_INDEX); + float center_x, center_y; + get_rotation_center(image_op->get_canvas(), center_x, center_y); + float rotate_offset_x, rotate_offset_y; + get_rotation_offset( + image_op->get_canvas(), this->get_canvas(), rotate_offset_x, rotate_offset_y); + for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { - float x = it.x; - float y = it.y; - rotate_coords(x, y, m_centerX, m_centerY, m_sine, m_cosine); - input_img->read_elem_sampled(x, y, sampler_, it.out); + float x = rotate_offset_x + it.x + canvas_.xmin; + float y = rotate_offset_y + it.y + canvas_.ymin; + rotate_coords(x, y, center_x, center_y, m_sine, m_cosine); + input_img->read_elem_sampled(x - canvas_.xmin, y - canvas_.ymin, sampler_, it.out); } } diff --git a/source/blender/compositor/operations/COM_RotateOperation.h b/source/blender/compositor/operations/COM_RotateOperation.h index f0de699f9ce..76f203cfbc0 100644 --- a/source/blender/compositor/operations/COM_RotateOperation.h +++ b/source/blender/compositor/operations/COM_RotateOperation.h @@ -29,8 +29,10 @@ class RotateOperation : public MultiThreadedOperation { SocketReader *m_imageSocket; SocketReader *m_degreeSocket; + /* TODO(manzanilla): to be removed with tiled implementation. */ float m_centerX; float m_centerY; + float m_cosine; float m_sine; bool m_doDegree2RadConversion; @@ -49,12 +51,33 @@ class RotateOperation : public MultiThreadedOperation { y = center_y + (-sine * dx + cosine * dy); } + static void get_rotation_center(const rcti &area, float &r_x, float &r_y); + static void get_rotation_offset(const rcti &input_canvas, + const rcti &rotate_canvas, + float &r_offset_x, + float &r_offset_y); static void get_area_rotation_bounds(const rcti &area, const float center_x, const float center_y, const float sine, const float cosine, rcti &r_bounds); + static void get_area_rotation_bounds_inverted(const rcti &area, + const float center_x, + const float center_y, + const float sine, + const float cosine, + rcti &r_bounds); + static void get_rotation_area_of_interest(const rcti &input_canvas, + const rcti &rotate_canvas, + const float sine, + const float cosine, + const rcti &output_area, + rcti &r_input_area); + static void get_rotation_canvas(const rcti &input_canvas, + const float sine, + const float cosine, + rcti &r_canvas); bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, @@ -80,6 +103,8 @@ class RotateOperation : public MultiThreadedOperation { void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) override; + + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ScaleOperation.cc b/source/blender/compositor/operations/COM_ScaleOperation.cc index 0161b837915..dbd8faf0f1d 100644 --- a/source/blender/compositor/operations/COM_ScaleOperation.cc +++ b/source/blender/compositor/operations/COM_ScaleOperation.cc @@ -38,17 +38,21 @@ BaseScaleOperation::BaseScaleOperation() m_variable_size = false; } +void BaseScaleOperation::set_scale_canvas_max_size(Size2f size) +{ + max_scale_canvas_size_ = size; +} + ScaleOperation::ScaleOperation() : ScaleOperation(DataType::Color) { } ScaleOperation::ScaleOperation(DataType data_type) : BaseScaleOperation() { - this->addInputSocket(data_type); + this->addInputSocket(data_type, ResizeMode::None); this->addInputSocket(DataType::Value); this->addInputSocket(DataType::Value); this->addOutputSocket(data_type); - this->setResolutionInputSocketIndex(0); this->m_inputOperation = nullptr; this->m_inputXOperation = nullptr; this->m_inputYOperation = nullptr; @@ -64,34 +68,52 @@ float ScaleOperation::get_constant_scale(const int input_op_idx, const float fac return 1.0f; } -float ScaleOperation::get_constant_scale_x() +float ScaleOperation::get_constant_scale_x(const float width) +{ + return get_constant_scale(X_INPUT_INDEX, get_relative_scale_x_factor(width)); +} + +float ScaleOperation::get_constant_scale_y(const float height) { - return get_constant_scale(1, get_relative_scale_x_factor()); + return get_constant_scale(Y_INPUT_INDEX, get_relative_scale_y_factor(height)); } -float ScaleOperation::get_constant_scale_y() +bool ScaleOperation::is_scaling_variable() { - return get_constant_scale(2, get_relative_scale_y_factor()); + return !get_input_operation(X_INPUT_INDEX)->get_flags().is_constant_operation || + !get_input_operation(Y_INPUT_INDEX)->get_flags().is_constant_operation; } -void ScaleOperation::scale_area( - rcti &rect, float center_x, float center_y, float scale_x, float scale_y) +void ScaleOperation::scale_area(rcti &area, float relative_scale_x, float relative_scale_y) { - rect.xmin = scale_coord(rect.xmin, center_x, scale_x); - rect.xmax = scale_coord(rect.xmax, center_x, scale_x); - rect.ymin = scale_coord(rect.ymin, center_y, scale_y); - rect.ymax = scale_coord(rect.ymax, center_y, scale_y); + const rcti src_area = area; + const float center_x = BLI_rcti_size_x(&area) / 2.0f; + const float center_y = BLI_rcti_size_y(&area) / 2.0f; + area.xmin = floorf(scale_coord(area.xmin, center_x, relative_scale_x)); + area.xmax = ceilf(scale_coord(area.xmax, center_x, relative_scale_x)); + area.ymin = floorf(scale_coord(area.ymin, center_y, relative_scale_y)); + area.ymax = ceilf(scale_coord(area.ymax, center_y, relative_scale_y)); + + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(src_area, area, scale_offset_x, scale_offset_y); + BLI_rcti_translate(&area, -scale_offset_x, -scale_offset_y); } -void ScaleOperation::scale_area(rcti &rect, float scale_x, float scale_y) +void ScaleOperation::clamp_area_size_max(rcti &area, Size2f max_size) { - scale_area(rect, m_centerX, m_centerY, scale_x, scale_y); + + if (BLI_rcti_size_x(&area) > max_size.x) { + area.xmax = area.xmin + max_size.x; + } + if (BLI_rcti_size_y(&area) > max_size.y) { + area.ymax = area.ymin + max_size.y; + } } void ScaleOperation::init_data() { - m_centerX = getWidth() / 2.0f; - m_centerY = getHeight() / 2.0f; + canvas_center_x_ = canvas_.xmin + getWidth() / 2.0f; + canvas_center_y_ = canvas_.ymin + getHeight() / 2.0f; } void ScaleOperation::initExecution() @@ -108,18 +130,52 @@ void ScaleOperation::deinitExecution() this->m_inputYOperation = nullptr; } +void ScaleOperation::get_scale_offset(const rcti &input_canvas, + const rcti &scale_canvas, + float &r_scale_offset_x, + float &r_scale_offset_y) +{ + r_scale_offset_x = (BLI_rcti_size_x(&input_canvas) - BLI_rcti_size_x(&scale_canvas)) / 2.0f; + r_scale_offset_y = (BLI_rcti_size_y(&input_canvas) - BLI_rcti_size_y(&scale_canvas)) / 2.0f; +} + +void ScaleOperation::get_scale_area_of_interest(const rcti &input_canvas, + const rcti &scale_canvas, + const float relative_scale_x, + const float relative_scale_y, + const rcti &output_area, + rcti &r_input_area) +{ + const float scale_center_x = BLI_rcti_size_x(&input_canvas) / 2.0f; + const float scale_center_y = BLI_rcti_size_y(&input_canvas) / 2.0f; + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(input_canvas, scale_canvas, scale_offset_x, scale_offset_y); + + r_input_area.xmin = floorf( + scale_coord_inverted(output_area.xmin + scale_offset_x, scale_center_x, relative_scale_x)); + r_input_area.xmax = ceilf( + scale_coord_inverted(output_area.xmax + scale_offset_x, scale_center_x, relative_scale_x)); + r_input_area.ymin = floorf( + scale_coord_inverted(output_area.ymin + scale_offset_y, scale_center_y, relative_scale_y)); + r_input_area.ymax = ceilf( + scale_coord_inverted(output_area.ymax + scale_offset_y, scale_center_y, relative_scale_y)); +} + void ScaleOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { r_input_area = output_area; - if (input_idx != 0 || m_variable_size) { + if (input_idx != 0 || is_scaling_variable()) { return; } - float scale_x = get_constant_scale_x(); - float scale_y = get_constant_scale_y(); - scale_area(r_input_area, scale_x, scale_y); + NodeOperation *image_op = get_input_operation(IMAGE_INPUT_INDEX); + const float scale_x = get_constant_scale_x(image_op->getWidth()); + const float scale_y = get_constant_scale_y(image_op->getHeight()); + + get_scale_area_of_interest( + image_op->get_canvas(), this->get_canvas(), scale_x, scale_y, output_area, r_input_area); expand_area_for_sampler(r_input_area, (PixelSampler)m_sampler); } @@ -127,18 +183,70 @@ void ScaleOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) { - const MemoryBuffer *input_img = inputs[0]; - MemoryBuffer *input_x = inputs[1]; - MemoryBuffer *input_y = inputs[2]; - const float scale_x_factor = get_relative_scale_x_factor(); - const float scale_y_factor = get_relative_scale_y_factor(); + NodeOperation *input_image_op = get_input_operation(IMAGE_INPUT_INDEX); + const int input_image_width = input_image_op->getWidth(); + const int input_image_height = input_image_op->getHeight(); + const float scale_x_factor = get_relative_scale_x_factor(input_image_width); + const float scale_y_factor = get_relative_scale_y_factor(input_image_height); + const float scale_center_x = input_image_width / 2.0f; + const float scale_center_y = input_image_height / 2.0f; + float from_scale_offset_x, from_scale_offset_y; + ScaleOperation::get_scale_offset( + input_image_op->get_canvas(), this->get_canvas(), from_scale_offset_x, from_scale_offset_y); + + const MemoryBuffer *input_image = inputs[IMAGE_INPUT_INDEX]; + MemoryBuffer *input_x = inputs[X_INPUT_INDEX]; + MemoryBuffer *input_y = inputs[Y_INPUT_INDEX]; BuffersIterator<float> it = output->iterate_with({input_x, input_y}, area); for (; !it.is_end(); ++it) { const float rel_scale_x = *it.in(0) * scale_x_factor; const float rel_scale_y = *it.in(1) * scale_y_factor; - const float scaled_x = scale_coord(it.x, m_centerX, rel_scale_x); - const float scaled_y = scale_coord(it.y, m_centerY, rel_scale_y); - input_img->read_elem_sampled(scaled_x, scaled_y, (PixelSampler)m_sampler, it.out); + const float scaled_x = scale_coord_inverted( + from_scale_offset_x + canvas_.xmin + it.x, scale_center_x, rel_scale_x); + const float scaled_y = scale_coord_inverted( + from_scale_offset_y + canvas_.ymin + it.y, scale_center_y, rel_scale_y); + + input_image->read_elem_sampled( + scaled_x - canvas_.xmin, scaled_y - canvas_.ymin, (PixelSampler)m_sampler, it.out); + } +} + +void ScaleOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + if (execution_model_ == eExecutionModel::Tiled) { + NodeOperation::determine_canvas(preferred_area, r_area); + return; + } + + const bool image_determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (image_determined) { + rcti image_canvas = r_area; + rcti unused; + NodeOperationInput *x_socket = getInputSocket(X_INPUT_INDEX); + NodeOperationInput *y_socket = getInputSocket(Y_INPUT_INDEX); + x_socket->determine_canvas(image_canvas, unused); + y_socket->determine_canvas(image_canvas, unused); + if (is_scaling_variable()) { + /* Do not scale canvas. */ + return; + } + + /* Determine scaled canvas. */ + const float input_width = BLI_rcti_size_x(&r_area); + const float input_height = BLI_rcti_size_y(&r_area); + const float scale_x = get_constant_scale_x(input_width); + const float scale_y = get_constant_scale_y(input_height); + scale_area(r_area, scale_x, scale_y); + const Size2f max_scale_size = {MAX2(input_width, max_scale_canvas_size_.x), + MAX2(input_height, max_scale_canvas_size_.y)}; + clamp_area_size_max(r_area, max_scale_size); + + /* Re-determine canvases of x and y constant inputs with scaled canvas as preferred. */ + get_input_operation(X_INPUT_INDEX)->unset_canvas(); + get_input_operation(Y_INPUT_INDEX)->unset_canvas(); + x_socket->determine_canvas(r_area, unused); + y_socket->determine_canvas(r_area, unused); } } @@ -166,8 +274,8 @@ void ScaleRelativeOperation::executePixelSampled(float output[4], const float scx = scaleX[0]; const float scy = scaleY[0]; - float nx = this->m_centerX + (x - this->m_centerX) / scx; - float ny = this->m_centerY + (y - this->m_centerY) / scy; + float nx = this->canvas_center_x_ + (x - this->canvas_center_x_) / scx; + float ny = this->canvas_center_y_ + (y - this->canvas_center_y_) / scy; this->m_inputOperation->readSampled(output, nx, ny, effective_sampler); } @@ -186,10 +294,10 @@ bool ScaleRelativeOperation::determineDependingAreaOfInterest(rcti *input, const float scx = scaleX[0]; const float scy = scaleY[0]; - newInput.xmax = this->m_centerX + (input->xmax - this->m_centerX) / scx + 1; - newInput.xmin = this->m_centerX + (input->xmin - this->m_centerX) / scx - 1; - newInput.ymax = this->m_centerY + (input->ymax - this->m_centerY) / scy + 1; - newInput.ymin = this->m_centerY + (input->ymin - this->m_centerY) / scy - 1; + newInput.xmax = this->canvas_center_x_ + (input->xmax - this->canvas_center_x_) / scx + 1; + newInput.xmin = this->canvas_center_x_ + (input->xmin - this->canvas_center_x_) / scx - 1; + newInput.ymax = this->canvas_center_y_ + (input->ymax - this->canvas_center_y_) / scy + 1; + newInput.ymin = this->canvas_center_y_ + (input->ymin - this->canvas_center_y_) / scy - 1; } else { newInput.xmax = this->getWidth(); @@ -222,8 +330,8 @@ void ScaleAbsoluteOperation::executePixelSampled(float output[4], float relativeXScale = scx / width; float relativeYScale = scy / height; - float nx = this->m_centerX + (x - this->m_centerX) / relativeXScale; - float ny = this->m_centerY + (y - this->m_centerY) / relativeYScale; + float nx = this->canvas_center_x_ + (x - this->canvas_center_x_) / relativeXScale; + float ny = this->canvas_center_y_ + (y - this->canvas_center_y_) / relativeYScale; this->m_inputOperation->readSampled(output, nx, ny, effective_sampler); } @@ -248,10 +356,14 @@ bool ScaleAbsoluteOperation::determineDependingAreaOfInterest(rcti *input, float relateveXScale = scx / width; float relateveYScale = scy / height; - newInput.xmax = this->m_centerX + (input->xmax - this->m_centerX) / relateveXScale; - newInput.xmin = this->m_centerX + (input->xmin - this->m_centerX) / relateveXScale; - newInput.ymax = this->m_centerY + (input->ymax - this->m_centerY) / relateveYScale; - newInput.ymin = this->m_centerY + (input->ymin - this->m_centerY) / relateveYScale; + newInput.xmax = this->canvas_center_x_ + + (input->xmax - this->canvas_center_x_) / relateveXScale; + newInput.xmin = this->canvas_center_x_ + + (input->xmin - this->canvas_center_x_) / relateveXScale; + newInput.ymax = this->canvas_center_y_ + + (input->ymax - this->canvas_center_y_) / relateveYScale; + newInput.ymin = this->canvas_center_y_ + + (input->ymin - this->canvas_center_y_) / relateveYScale; } else { newInput.xmax = this->getWidth(); @@ -267,16 +379,17 @@ ScaleFixedSizeOperation::ScaleFixedSizeOperation() : BaseScaleOperation() { this->addInputSocket(DataType::Color, ResizeMode::None); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_is_offset = false; } -void ScaleFixedSizeOperation::init_data() +void ScaleFixedSizeOperation::init_data(const rcti &input_canvas) { - const NodeOperation *input_op = getInputOperation(0); - this->m_relX = input_op->getWidth() / (float)this->m_newWidth; - this->m_relY = input_op->getHeight() / (float)this->m_newHeight; + const int input_width = BLI_rcti_size_x(&input_canvas); + const int input_height = BLI_rcti_size_y(&input_canvas); + this->m_relX = input_width / (float)this->m_newWidth; + this->m_relY = input_height / (float)this->m_newHeight; /* *** all the options below are for a fairly special case - camera framing *** */ if (this->m_offsetX != 0.0f || this->m_offsetY != 0.0f) { @@ -294,8 +407,8 @@ void ScaleFixedSizeOperation::init_data() if (this->m_is_aspect) { /* apply aspect from clip */ - const float w_src = input_op->getWidth(); - const float h_src = input_op->getHeight(); + const float w_src = input_width; + const float h_src = input_height; /* destination aspect is already applied from the camera frame */ const float w_dst = this->m_newWidth; @@ -310,12 +423,32 @@ void ScaleFixedSizeOperation::init_data() const float div = asp_src / asp_dst; this->m_relX /= div; this->m_offsetX += ((w_src - (w_src * div)) / (w_src / w_dst)) / 2.0f; + if (m_is_crop && execution_model_ == eExecutionModel::FullFrame) { + int fit_width = m_newWidth * div; + if (fit_width > max_scale_canvas_size_.x) { + fit_width = max_scale_canvas_size_.x; + } + + const int added_width = fit_width - m_newWidth; + m_newWidth += added_width; + m_offsetX += added_width / 2.0f; + } } else { /* fit Y */ const float div = asp_dst / asp_src; this->m_relY /= div; this->m_offsetY += ((h_src - (h_src * div)) / (h_src / h_dst)) / 2.0f; + if (m_is_crop && execution_model_ == eExecutionModel::FullFrame) { + int fit_height = m_newHeight * div; + if (fit_height > max_scale_canvas_size_.y) { + fit_height = max_scale_canvas_size_.y; + } + + const int added_height = fit_height - m_newHeight; + m_newHeight += added_height; + m_offsetY += added_height / 2.0f; + } } this->m_is_offset = true; @@ -366,15 +499,26 @@ bool ScaleFixedSizeOperation::determineDependingAreaOfInterest(rcti *input, return BaseScaleOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } -void ScaleFixedSizeOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void ScaleFixedSizeOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - unsigned int nr[2]; - nr[0] = this->m_newWidth; - nr[1] = this->m_newHeight; - BaseScaleOperation::determineResolution(resolution, nr); - resolution[0] = this->m_newWidth; - resolution[1] = this->m_newHeight; + rcti local_preferred = preferred_area; + local_preferred.xmax = local_preferred.xmin + m_newWidth; + local_preferred.ymax = local_preferred.ymin + m_newHeight; + rcti input_canvas; + const bool input_determined = getInputSocket(0)->determine_canvas(local_preferred, input_canvas); + if (input_determined) { + init_data(input_canvas); + r_area = input_canvas; + if (execution_model_ == eExecutionModel::FullFrame) { + r_area.xmin /= m_relX; + r_area.ymin /= m_relY; + r_area.xmin += m_offsetX; + r_area.ymin += m_offsetY; + } + + r_area.xmax = r_area.xmin + m_newWidth; + r_area.ymax = r_area.ymin + m_newHeight; + } } void ScaleFixedSizeOperation::get_area_of_interest(const int input_idx, @@ -383,10 +527,11 @@ void ScaleFixedSizeOperation::get_area_of_interest(const int input_idx, { BLI_assert(input_idx == 0); UNUSED_VARS_NDEBUG(input_idx); - r_input_area.xmax = (output_area.xmax - m_offsetX) * this->m_relX; - r_input_area.xmin = (output_area.xmin - m_offsetX) * this->m_relX; - r_input_area.ymax = (output_area.ymax - m_offsetY) * this->m_relY; - r_input_area.ymin = (output_area.ymin - m_offsetY) * this->m_relY; + + r_input_area.xmax = ceilf((output_area.xmax - m_offsetX) * this->m_relX); + r_input_area.xmin = floorf((output_area.xmin - m_offsetX) * this->m_relX); + r_input_area.ymax = ceilf((output_area.ymax - m_offsetY) * this->m_relY); + r_input_area.ymin = floorf((output_area.ymin - m_offsetY) * this->m_relY); expand_area_for_sampler(r_input_area, (PixelSampler)m_sampler); } @@ -399,14 +544,17 @@ void ScaleFixedSizeOperation::update_memory_buffer_partial(MemoryBuffer *output, BuffersIterator<float> it = output->iterate_with({}, area); if (this->m_is_offset) { for (; !it.is_end(); ++it) { - const float nx = (it.x - this->m_offsetX) * this->m_relX; - const float ny = (it.y - this->m_offsetY) * this->m_relY; - input_img->read_elem_sampled(nx, ny, sampler, it.out); + const float nx = (canvas_.xmin + it.x - this->m_offsetX) * this->m_relX; + const float ny = (canvas_.ymin + it.y - this->m_offsetY) * this->m_relY; + input_img->read_elem_sampled(nx - canvas_.xmin, ny - canvas_.ymin, sampler, it.out); } } else { for (; !it.is_end(); ++it) { - input_img->read_elem_sampled(it.x * this->m_relX, it.y * this->m_relY, sampler, it.out); + input_img->read_elem_sampled((canvas_.xmin + it.x) * this->m_relX - canvas_.xmin, + (canvas_.ymin + it.y) * this->m_relY - canvas_.ymin, + sampler, + it.out); } } } diff --git a/source/blender/compositor/operations/COM_ScaleOperation.h b/source/blender/compositor/operations/COM_ScaleOperation.h index 65762d1ce62..746e490d900 100644 --- a/source/blender/compositor/operations/COM_ScaleOperation.h +++ b/source/blender/compositor/operations/COM_ScaleOperation.h @@ -24,6 +24,9 @@ namespace blender::compositor { class BaseScaleOperation : public MultiThreadedOperation { public: + static constexpr float DEFAULT_MAX_SCALE_CANVAS_SIZE = 12000; + + public: void setSampler(PixelSampler sampler) { this->m_sampler = (int)sampler; @@ -33,6 +36,8 @@ class BaseScaleOperation : public MultiThreadedOperation { m_variable_size = variable_size; }; + void set_scale_canvas_max_size(Size2f size); + protected: BaseScaleOperation(); @@ -41,20 +46,26 @@ class BaseScaleOperation : public MultiThreadedOperation { return (m_sampler == -1) ? sampler : (PixelSampler)m_sampler; } + Size2f max_scale_canvas_size_ = {DEFAULT_MAX_SCALE_CANVAS_SIZE, DEFAULT_MAX_SCALE_CANVAS_SIZE}; int m_sampler; + /* TODO(manzanilla): to be removed with tiled implementation. */ bool m_variable_size; }; class ScaleOperation : public BaseScaleOperation { public: - static constexpr float MIN_SCALE = 0.0001f; + static constexpr float MIN_RELATIVE_SCALE = 0.0001f; protected: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int X_INPUT_INDEX = 1; + static constexpr int Y_INPUT_INDEX = 2; + SocketReader *m_inputOperation; SocketReader *m_inputXOperation; SocketReader *m_inputYOperation; - float m_centerX; - float m_centerY; + float canvas_center_x_; + float canvas_center_y_; public: ScaleOperation(); @@ -62,9 +73,28 @@ class ScaleOperation : public BaseScaleOperation { static float scale_coord(const float coord, const float center, const float relative_scale) { - return center + (coord - center) / MAX2(relative_scale, MIN_SCALE); + return center + (coord - center) * MAX2(relative_scale, MIN_RELATIVE_SCALE); } - static void scale_area(rcti &rect, float center_x, float center_y, float scale_x, float scale_y); + + static float scale_coord_inverted(const float coord, + const float center, + const float relative_scale) + { + return center + (coord - center) / MAX2(relative_scale, MIN_RELATIVE_SCALE); + } + + static void get_scale_offset(const rcti &input_canvas, + const rcti &scale_canvas, + float &r_scale_offset_x, + float &r_scale_offset_y); + static void scale_area(rcti &area, float relative_scale_x, float relative_scale_y); + static void get_scale_area_of_interest(const rcti &input_canvas, + const rcti &scale_canvas, + const float relative_scale_x, + const float relative_scale_y, + const rcti &output_area, + rcti &r_input_area); + static void clamp_area_size_max(rcti &area, Size2f max_size); void init_data() override; void initExecution() override; @@ -75,15 +105,17 @@ class ScaleOperation : public BaseScaleOperation { const rcti &area, Span<MemoryBuffer *> inputs) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; + protected: - virtual float get_relative_scale_x_factor() = 0; - virtual float get_relative_scale_y_factor() = 0; + virtual float get_relative_scale_x_factor(float width) = 0; + virtual float get_relative_scale_y_factor(float height) = 0; private: + bool is_scaling_variable(); float get_constant_scale(int input_op_idx, float factor); - float get_constant_scale_x(); - float get_constant_scale_y(); - void scale_area(rcti &rect, float scale_x, float scale_y); + float get_constant_scale_x(float width); + float get_constant_scale_y(float height); }; class ScaleRelativeOperation : public ScaleOperation { @@ -94,11 +126,13 @@ class ScaleRelativeOperation : public ScaleOperation { ReadBufferOperation *readOperation, rcti *output) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - float get_relative_scale_x_factor() override + + float get_relative_scale_x_factor(float UNUSED(width)) override { return 1.0f; } - float get_relative_scale_y_factor() override + + float get_relative_scale_y_factor(float UNUSED(height)) override { return 1.0f; } @@ -110,13 +144,15 @@ class ScaleAbsoluteOperation : public ScaleOperation { ReadBufferOperation *readOperation, rcti *output) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - float get_relative_scale_x_factor() override + + float get_relative_scale_x_factor(float width) override { - return 1.0f / getWidth(); + return 1.0f / width; } - float get_relative_scale_y_factor() override + + float get_relative_scale_y_factor(float height) override { - return 1.0f / getHeight(); + return 1.0f / height; } }; @@ -141,11 +177,9 @@ class ScaleFixedSizeOperation : public BaseScaleOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void init_data() override; void initExecution() override; void deinitExecution() override; void setNewWidth(int width) @@ -174,6 +208,9 @@ class ScaleFixedSizeOperation : public BaseScaleOperation { void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) override; + + private: + void init_data(const rcti &input_canvas); }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc index 628da686a42..21d9210bdac 100644 --- a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc +++ b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc @@ -382,13 +382,30 @@ void ScreenLensDistortionOperation::updateVariables(float distortion, float disp mul_v3_v3fl(m_k4, m_k, 4.0f); } +void ScreenLensDistortionOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + switch (execution_model_) { + case eExecutionModel::FullFrame: { + set_determined_canvas_modifier([=](rcti &canvas) { + /* Ensure screen space. */ + BLI_rcti_translate(&canvas, -canvas.xmin, -canvas.ymin); + }); + break; + } + default: + break; + } + + NodeOperation::determine_canvas(preferred_area, r_area); +} + void ScreenLensDistortionOperation::get_area_of_interest(const int input_idx, const rcti &UNUSED(output_area), rcti &r_input_area) { if (input_idx != 0) { /* Dispersion and distortion inputs are used as constants only. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; } /* XXX the original method of estimating the area-of-interest does not work @@ -398,10 +415,7 @@ void ScreenLensDistortionOperation::get_area_of_interest(const int input_idx, */ #if 1 NodeOperation *image = getInputOperation(0); - r_input_area.xmax = image->getWidth(); - r_input_area.xmin = 0; - r_input_area.ymax = image->getHeight(); - r_input_area.ymin = 0; + r_input_area = image->get_canvas(); #else /* Original method in tiled implementation. */ rcti newInput; diff --git a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h index 616fc8883b0..93681b2f934 100644 --- a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h +++ b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h @@ -86,6 +86,7 @@ class ScreenLensDistortionOperation : public MultiThreadedOperation { ReadBufferOperation *readOperation, rcti *output) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_SetColorOperation.cc b/source/blender/compositor/operations/COM_SetColorOperation.cc index dbe45fa60db..1e7a950e727 100644 --- a/source/blender/compositor/operations/COM_SetColorOperation.cc +++ b/source/blender/compositor/operations/COM_SetColorOperation.cc @@ -34,11 +34,9 @@ void SetColorOperation::executePixelSampled(float output[4], copy_v4_v4(output, this->m_color); } -void SetColorOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SetColorOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetColorOperation.h b/source/blender/compositor/operations/COM_SetColorOperation.h index f546d5e7668..34ea35bdcbc 100644 --- a/source/blender/compositor/operations/COM_SetColorOperation.h +++ b/source/blender/compositor/operations/COM_SetColorOperation.h @@ -83,8 +83,7 @@ class SetColorOperation : public ConstantOperation { */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetValueOperation.cc b/source/blender/compositor/operations/COM_SetValueOperation.cc index ef43cf64653..b7c50f94887 100644 --- a/source/blender/compositor/operations/COM_SetValueOperation.cc +++ b/source/blender/compositor/operations/COM_SetValueOperation.cc @@ -34,11 +34,9 @@ void SetValueOperation::executePixelSampled(float output[4], output[0] = this->m_value; } -void SetValueOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SetValueOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetValueOperation.h b/source/blender/compositor/operations/COM_SetValueOperation.h index 726624c1c6a..e72652450a9 100644 --- a/source/blender/compositor/operations/COM_SetValueOperation.h +++ b/source/blender/compositor/operations/COM_SetValueOperation.h @@ -54,8 +54,8 @@ class SetValueOperation : public ConstantOperation { * The inner loop of this operation. */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetVectorOperation.cc b/source/blender/compositor/operations/COM_SetVectorOperation.cc index 7b8cf44048c..3e8514f1f59 100644 --- a/source/blender/compositor/operations/COM_SetVectorOperation.cc +++ b/source/blender/compositor/operations/COM_SetVectorOperation.cc @@ -37,11 +37,9 @@ void SetVectorOperation::executePixelSampled(float output[4], output[2] = vector_.z; } -void SetVectorOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SetVectorOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetVectorOperation.h b/source/blender/compositor/operations/COM_SetVectorOperation.h index 41fd06659d6..920ea8051e4 100644 --- a/source/blender/compositor/operations/COM_SetVectorOperation.h +++ b/source/blender/compositor/operations/COM_SetVectorOperation.h @@ -84,8 +84,7 @@ class SetVectorOperation : public ConstantOperation { */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setVector(const float vector[3]) { diff --git a/source/blender/compositor/operations/COM_SplitOperation.cc b/source/blender/compositor/operations/COM_SplitOperation.cc index b2a50e2c740..47b2bbb7e94 100644 --- a/source/blender/compositor/operations/COM_SplitOperation.cc +++ b/source/blender/compositor/operations/COM_SplitOperation.cc @@ -67,16 +67,14 @@ void SplitOperation::executePixelSampled(float output[4], } } -void SplitOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SplitOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - unsigned int tempPreferredResolution[2] = {0, 0}; - unsigned int tempResolution[2]; + rcti unused_area; - this->getInputSocket(0)->determineResolution(tempResolution, tempPreferredResolution); - this->setResolutionInputSocketIndex((tempResolution[0] && tempResolution[1]) ? 0 : 1); + const bool determined = this->getInputSocket(0)->determine_canvas(COM_AREA_NONE, unused_area); + this->set_canvas_input_index(determined ? 0 : 1); - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); } void SplitOperation::update_memory_buffer_partial(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_SplitOperation.h b/source/blender/compositor/operations/COM_SplitOperation.h index 2d09d2a07dc..f923c9f8b7a 100644 --- a/source/blender/compositor/operations/COM_SplitOperation.h +++ b/source/blender/compositor/operations/COM_SplitOperation.h @@ -35,8 +35,7 @@ class SplitOperation : public MultiThreadedOperation { void initExecution() override; void deinitExecution() override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setSplitPercentage(float splitPercentage) { this->m_splitPercentage = splitPercentage; diff --git a/source/blender/compositor/operations/COM_SunBeamsOperation.cc b/source/blender/compositor/operations/COM_SunBeamsOperation.cc index ad96e3a02ba..494506389c5 100644 --- a/source/blender/compositor/operations/COM_SunBeamsOperation.cc +++ b/source/blender/compositor/operations/COM_SunBeamsOperation.cc @@ -25,7 +25,7 @@ SunBeamsOperation::SunBeamsOperation() { this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->flags.complex = true; } diff --git a/source/blender/compositor/operations/COM_TextureOperation.cc b/source/blender/compositor/operations/COM_TextureOperation.cc index c8e0844d35f..c06e3ac7cb0 100644 --- a/source/blender/compositor/operations/COM_TextureOperation.cc +++ b/source/blender/compositor/operations/COM_TextureOperation.cc @@ -72,34 +72,20 @@ void TextureBaseOperation::deinitExecution() NodeOperation::deinitExecution(); } -void TextureBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void TextureBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - switch (execution_model_) { - case eExecutionModel::Tiled: { - if (preferredResolution[0] == 0 || preferredResolution[1] == 0) { - int width = this->m_rd->xsch * this->m_rd->size / 100; - int height = this->m_rd->ysch * this->m_rd->size / 100; - resolution[0] = width; - resolution[1] = height; - } - else { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; - } - break; - } - case eExecutionModel::FullFrame: { - /* Determine inputs resolutions. */ - unsigned int temp[2]; - NodeOperation::determineResolution(temp, preferredResolution); - - /* We don't use inputs resolutions because they are only used as parameters, not image data. - */ - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; - break; - } + r_area = preferred_area; + if (BLI_rcti_is_empty(&preferred_area)) { + int width = this->m_rd->xsch * this->m_rd->size / 100; + int height = this->m_rd->ysch * this->m_rd->size / 100; + r_area.xmax = preferred_area.xmin + width; + r_area.ymax = preferred_area.ymin + height; + } + + if (execution_model_ == eExecutionModel::FullFrame) { + /* Determine inputs. */ + rcti temp; + NodeOperation::determine_canvas(r_area, temp); } } diff --git a/source/blender/compositor/operations/COM_TextureOperation.h b/source/blender/compositor/operations/COM_TextureOperation.h index 1e95cb270d0..5bc21da1f96 100644 --- a/source/blender/compositor/operations/COM_TextureOperation.h +++ b/source/blender/compositor/operations/COM_TextureOperation.h @@ -46,8 +46,7 @@ class TextureBaseOperation : public MultiThreadedOperation { /** * Determine the output resolution. */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; /** * Constructor diff --git a/source/blender/compositor/operations/COM_TonemapOperation.cc b/source/blender/compositor/operations/COM_TonemapOperation.cc index 20da468eeb1..cb671c54abe 100644 --- a/source/blender/compositor/operations/COM_TonemapOperation.cc +++ b/source/blender/compositor/operations/COM_TonemapOperation.cc @@ -28,7 +28,7 @@ namespace blender::compositor { TonemapOperation::TonemapOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Color); this->m_imageReader = nullptr; this->m_data = nullptr; @@ -160,11 +160,7 @@ void TonemapOperation::get_area_of_interest(const int input_idx, rcti &r_input_area) { BLI_assert(input_idx == 0); - NodeOperation *operation = getInputOperation(input_idx); - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = operation->getWidth(); - r_input_area.ymax = operation->getHeight(); + r_input_area = get_input_operation(input_idx)->get_canvas(); } struct Luminance { diff --git a/source/blender/compositor/operations/COM_TrackPositionOperation.cc b/source/blender/compositor/operations/COM_TrackPositionOperation.cc index 0f4be16a620..1929c578177 100644 --- a/source/blender/compositor/operations/COM_TrackPositionOperation.cc +++ b/source/blender/compositor/operations/COM_TrackPositionOperation.cc @@ -157,11 +157,9 @@ const float *TrackPositionOperation::get_constant_elem() return &track_position_; } -void TrackPositionOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void TrackPositionOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_TrackPositionOperation.h b/source/blender/compositor/operations/COM_TrackPositionOperation.h index f716bd97737..a47d3e03ed4 100644 --- a/source/blender/compositor/operations/COM_TrackPositionOperation.h +++ b/source/blender/compositor/operations/COM_TrackPositionOperation.h @@ -53,8 +53,7 @@ class TrackPositionOperation : public ConstantOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; public: TrackPositionOperation(); diff --git a/source/blender/compositor/operations/COM_TransformOperation.cc b/source/blender/compositor/operations/COM_TransformOperation.cc index 2feaa0ae16d..5f6e9ed4d21 100644 --- a/source/blender/compositor/operations/COM_TransformOperation.cc +++ b/source/blender/compositor/operations/COM_TransformOperation.cc @@ -27,55 +27,40 @@ namespace blender::compositor { TransformOperation::TransformOperation() { - addInputSocket(DataType::Color); - addInputSocket(DataType::Value); - addInputSocket(DataType::Value); - addInputSocket(DataType::Value); - addInputSocket(DataType::Value); + addInputSocket(DataType::Color, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); addOutputSocket(DataType::Color); translate_factor_x_ = 1.0f; translate_factor_y_ = 1.0f; convert_degree_to_rad_ = false; sampler_ = PixelSampler::Bilinear; invert_ = false; + max_scale_canvas_size_ = {ScaleOperation::DEFAULT_MAX_SCALE_CANVAS_SIZE, + ScaleOperation::DEFAULT_MAX_SCALE_CANVAS_SIZE}; +} + +void TransformOperation::set_scale_canvas_max_size(Size2f size) +{ + max_scale_canvas_size_ = size; } void TransformOperation::init_data() { - /* Translation. */ - translate_x_ = 0; - NodeOperation *x_op = getInputOperation(X_INPUT_INDEX); - if (x_op->get_flags().is_constant_operation) { - translate_x_ = static_cast<ConstantOperation *>(x_op)->get_constant_elem()[0] * - translate_factor_x_; - } - translate_y_ = 0; - NodeOperation *y_op = getInputOperation(Y_INPUT_INDEX); - if (y_op->get_flags().is_constant_operation) { - translate_y_ = static_cast<ConstantOperation *>(y_op)->get_constant_elem()[0] * - translate_factor_y_; - } - /* Scaling. */ - scale_center_x_ = getWidth() / 2.0; - scale_center_y_ = getHeight() / 2.0; - constant_scale_ = 1.0f; - NodeOperation *scale_op = getInputOperation(SCALE_INPUT_INDEX); - if (scale_op->get_flags().is_constant_operation) { - constant_scale_ = static_cast<ConstantOperation *>(scale_op)->get_constant_elem()[0]; - } + translate_x_ = get_input_operation(X_INPUT_INDEX)->get_constant_value_default(0.0f) * + translate_factor_x_; + translate_y_ = get_input_operation(Y_INPUT_INDEX)->get_constant_value_default(0.0f) * + translate_factor_y_; - /* Rotation. */ - rotate_center_x_ = (getWidth() - 1.0) / 2.0; - rotate_center_y_ = (getHeight() - 1.0) / 2.0; - NodeOperation *degree_op = getInputOperation(DEGREE_INPUT_INDEX); - const bool is_constant_degree = degree_op->get_flags().is_constant_operation; - const float degree = is_constant_degree ? - static_cast<ConstantOperation *>(degree_op)->get_constant_elem()[0] : - 0.0f; + const float degree = get_input_operation(DEGREE_INPUT_INDEX)->get_constant_value_default(0.0f); const double rad = convert_degree_to_rad_ ? DEG2RAD((double)degree) : degree; rotate_cosine_ = cos(rad); rotate_sine_ = sin(rad); + + scale_ = get_input_operation(SCALE_INPUT_INDEX)->get_constant_value_default(1.0f); } void TransformOperation::get_area_of_interest(const int input_idx, @@ -84,26 +69,41 @@ void TransformOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case IMAGE_INPUT_INDEX: { - BLI_rcti_translate(&r_input_area, translate_x_, translate_y_); - ScaleOperation::scale_area( - r_input_area, scale_center_x_, scale_center_y_, constant_scale_, constant_scale_); - RotateOperation::get_area_rotation_bounds(r_input_area, - rotate_center_x_, - rotate_center_y_, - rotate_sine_, - rotate_cosine_, - r_input_area); + NodeOperation *image_op = get_input_operation(IMAGE_INPUT_INDEX); + const rcti &image_canvas = image_op->get_canvas(); + if (invert_) { + /* Scale -> Rotate -> Translate. */ + r_input_area = output_area; + BLI_rcti_translate(&r_input_area, -translate_x_, -translate_y_); + RotateOperation::get_rotation_area_of_interest(scale_canvas_, + rotate_canvas_, + rotate_sine_, + rotate_cosine_, + r_input_area, + r_input_area); + ScaleOperation::get_scale_area_of_interest( + image_canvas, scale_canvas_, scale_, scale_, r_input_area, r_input_area); + } + else { + /* Translate -> Rotate -> Scale. */ + ScaleOperation::get_scale_area_of_interest( + rotate_canvas_, scale_canvas_, scale_, scale_, output_area, r_input_area); + RotateOperation::get_rotation_area_of_interest(translate_canvas_, + rotate_canvas_, + rotate_sine_, + rotate_cosine_, + r_input_area, + r_input_area); + BLI_rcti_translate(&r_input_area, -translate_x_, -translate_y_); + } expand_area_for_sampler(r_input_area, sampler_); break; } case X_INPUT_INDEX: case Y_INPUT_INDEX: - case DEGREE_INPUT_INDEX: { - r_input_area = COM_SINGLE_ELEM_AREA; - break; - } + case DEGREE_INPUT_INDEX: case SCALE_INPUT_INDEX: { - r_input_area = output_area; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; break; } } @@ -114,8 +114,7 @@ void TransformOperation::update_memory_buffer_partial(MemoryBuffer *output, Span<MemoryBuffer *> inputs) { const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX]; - MemoryBuffer *input_scale = inputs[SCALE_INPUT_INDEX]; - BuffersIterator<float> it = output->iterate_with({input_scale}, area); + BuffersIterator<float> it = output->iterate_with({}, area); if (invert_) { transform_inverted(it, input_img); } @@ -124,31 +123,111 @@ void TransformOperation::update_memory_buffer_partial(MemoryBuffer *output, } } +void TransformOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + const bool image_determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (image_determined) { + rcti image_canvas = r_area; + rcti unused; + getInputSocket(X_INPUT_INDEX)->determine_canvas(image_canvas, unused); + getInputSocket(Y_INPUT_INDEX)->determine_canvas(image_canvas, unused); + getInputSocket(DEGREE_INPUT_INDEX)->determine_canvas(image_canvas, unused); + getInputSocket(SCALE_INPUT_INDEX)->determine_canvas(image_canvas, unused); + + init_data(); + if (invert_) { + /* Scale -> Rotate -> Translate. */ + scale_canvas_ = image_canvas; + ScaleOperation::scale_area(scale_canvas_, scale_, scale_); + const Size2f max_scale_size = { + MAX2(BLI_rcti_size_x(&image_canvas), max_scale_canvas_size_.x), + MAX2(BLI_rcti_size_y(&image_canvas), max_scale_canvas_size_.y)}; + ScaleOperation::clamp_area_size_max(scale_canvas_, max_scale_size); + + RotateOperation::get_rotation_canvas( + scale_canvas_, rotate_sine_, rotate_cosine_, rotate_canvas_); + + translate_canvas_ = rotate_canvas_; + BLI_rcti_translate(&translate_canvas_, translate_x_, translate_y_); + + r_area = translate_canvas_; + } + else { + /* Translate -> Rotate -> Scale. */ + translate_canvas_ = image_canvas; + BLI_rcti_translate(&translate_canvas_, translate_x_, translate_y_); + + RotateOperation::get_rotation_canvas( + translate_canvas_, rotate_sine_, rotate_cosine_, rotate_canvas_); + + scale_canvas_ = rotate_canvas_; + ScaleOperation::scale_area(scale_canvas_, scale_, scale_); + + const Size2f max_scale_size = { + MAX2(BLI_rcti_size_x(&rotate_canvas_), max_scale_canvas_size_.x), + MAX2(BLI_rcti_size_y(&rotate_canvas_), max_scale_canvas_size_.y)}; + ScaleOperation::clamp_area_size_max(scale_canvas_, max_scale_size); + + r_area = scale_canvas_; + } + } +} + +/** Translate -> Rotate -> Scale. */ void TransformOperation::transform(BuffersIterator<float> &it, const MemoryBuffer *input_img) { + float rotate_center_x, rotate_center_y; + RotateOperation::get_rotation_center(translate_canvas_, rotate_center_x, rotate_center_y); + float rotate_offset_x, rotate_offset_y; + RotateOperation::get_rotation_offset( + translate_canvas_, rotate_canvas_, rotate_offset_x, rotate_offset_y); + + const float scale_center_x = BLI_rcti_size_x(&rotate_canvas_) / 2.0f; + const float scale_center_y = BLI_rcti_size_y(&rotate_canvas_) / 2.0f; + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(rotate_canvas_, scale_canvas_, scale_offset_x, scale_offset_y); + for (; !it.is_end(); ++it) { - const float scale = *it.in(0); - float x = it.x - translate_x_; - float y = it.y - translate_y_; + float x = ScaleOperation::scale_coord_inverted(it.x + scale_offset_x, scale_center_x, scale_); + float y = ScaleOperation::scale_coord_inverted(it.y + scale_offset_y, scale_center_y, scale_); + + x = rotate_offset_x + x; + y = rotate_offset_y + y; RotateOperation::rotate_coords( - x, y, rotate_center_x_, rotate_center_y_, rotate_sine_, rotate_cosine_); - x = ScaleOperation::scale_coord(x, scale_center_x_, scale); - y = ScaleOperation::scale_coord(y, scale_center_y_, scale); - input_img->read_elem_sampled(x, y, sampler_, it.out); + x, y, rotate_center_x, rotate_center_y, rotate_sine_, rotate_cosine_); + + input_img->read_elem_sampled(x - translate_x_, y - translate_y_, sampler_, it.out); } } +/** Scale -> Rotate -> Translate. */ void TransformOperation::transform_inverted(BuffersIterator<float> &it, const MemoryBuffer *input_img) { + const rcti &image_canvas = get_input_operation(IMAGE_INPUT_INDEX)->get_canvas(); + const float scale_center_x = BLI_rcti_size_x(&image_canvas) / 2.0f - translate_x_; + const float scale_center_y = BLI_rcti_size_y(&image_canvas) / 2.0f - translate_y_; + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(image_canvas, scale_canvas_, scale_offset_x, scale_offset_y); + + float rotate_center_x, rotate_center_y; + RotateOperation::get_rotation_center(translate_canvas_, rotate_center_x, rotate_center_y); + rotate_center_x -= translate_x_; + rotate_center_y -= translate_y_; + float rotate_offset_x, rotate_offset_y; + RotateOperation::get_rotation_offset( + scale_canvas_, rotate_canvas_, rotate_offset_x, rotate_offset_y); + for (; !it.is_end(); ++it) { - const float scale = *it.in(0); - float x = ScaleOperation::scale_coord(it.x, scale_center_x_, scale); - float y = ScaleOperation::scale_coord(it.y, scale_center_y_, scale); + float x = rotate_offset_x + (it.x - translate_x_); + float y = rotate_offset_y + (it.y - translate_y_); RotateOperation::rotate_coords( - x, y, rotate_center_x_, rotate_center_y_, rotate_sine_, rotate_cosine_); - x -= translate_x_; - y -= translate_y_; + x, y, rotate_center_x, rotate_center_y, rotate_sine_, rotate_cosine_); + + x = ScaleOperation::scale_coord_inverted(x + scale_offset_x, scale_center_x, scale_); + y = ScaleOperation::scale_coord_inverted(y + scale_offset_y, scale_center_y, scale_); + input_img->read_elem_sampled(x, y, sampler_, it.out); } } diff --git a/source/blender/compositor/operations/COM_TransformOperation.h b/source/blender/compositor/operations/COM_TransformOperation.h index 480998a0207..3c5584a1bea 100644 --- a/source/blender/compositor/operations/COM_TransformOperation.h +++ b/source/blender/compositor/operations/COM_TransformOperation.h @@ -30,15 +30,14 @@ class TransformOperation : public MultiThreadedOperation { constexpr static int DEGREE_INPUT_INDEX = 3; constexpr static int SCALE_INPUT_INDEX = 4; - float scale_center_x_; - float scale_center_y_; - float rotate_center_x_; - float rotate_center_y_; float rotate_cosine_; float rotate_sine_; - float translate_x_; - float translate_y_; - float constant_scale_; + int translate_x_; + int translate_y_; + float scale_; + rcti scale_canvas_; + rcti rotate_canvas_; + rcti translate_canvas_; /* Set variables. */ PixelSampler sampler_; @@ -46,6 +45,7 @@ class TransformOperation : public MultiThreadedOperation { float translate_factor_x_; float translate_factor_y_; bool invert_; + Size2f max_scale_canvas_size_; public: TransformOperation(); @@ -71,16 +71,18 @@ class TransformOperation : public MultiThreadedOperation { invert_ = value; } + void set_scale_canvas_max_size(Size2f size); + void init_data() override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; + private: - /** Translate -> Rotate -> Scale. */ void transform(BuffersIterator<float> &it, const MemoryBuffer *input_img); - /** Scale -> Rotate -> Translate. */ void transform_inverted(BuffersIterator<float> &it, const MemoryBuffer *input_img); }; diff --git a/source/blender/compositor/operations/COM_TranslateOperation.cc b/source/blender/compositor/operations/COM_TranslateOperation.cc index a3db086e974..9868f21654e 100644 --- a/source/blender/compositor/operations/COM_TranslateOperation.cc +++ b/source/blender/compositor/operations/COM_TranslateOperation.cc @@ -23,13 +23,13 @@ namespace blender::compositor { TranslateOperation::TranslateOperation() : TranslateOperation(DataType::Color) { } -TranslateOperation::TranslateOperation(DataType data_type) +TranslateOperation::TranslateOperation(DataType data_type, ResizeMode resize_mode) { - this->addInputSocket(data_type); - this->addInputSocket(DataType::Value); - this->addInputSocket(DataType::Value); + this->addInputSocket(data_type, resize_mode); + this->addInputSocket(DataType::Value, ResizeMode::None); + this->addInputSocket(DataType::Value, ResizeMode::None); this->addOutputSocket(data_type); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_inputXOperation = nullptr; this->m_inputYOperation = nullptr; @@ -39,6 +39,7 @@ TranslateOperation::TranslateOperation(DataType data_type) this->x_extend_mode_ = MemoryBufferExtend::Clip; this->y_extend_mode_ = MemoryBufferExtend::Clip; } + void TranslateOperation::initExecution() { this->m_inputOperation = this->getInputSocketReader(0); @@ -122,6 +123,9 @@ void TranslateOperation::get_area_of_interest(const int input_idx, BLI_rcti_translate(&r_input_area, 0, -delta_y); } } + else { + r_input_area = output_area; + } } void TranslateOperation::update_memory_buffer_partial(MemoryBuffer *output, @@ -142,4 +146,27 @@ void TranslateOperation::update_memory_buffer_partial(MemoryBuffer *output, } } +TranslateCanvasOperation::TranslateCanvasOperation() + : TranslateOperation(DataType::Color, ResizeMode::None) +{ +} + +void TranslateCanvasOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + const bool determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (determined) { + NodeOperationInput *x_socket = getInputSocket(X_INPUT_INDEX); + NodeOperationInput *y_socket = getInputSocket(Y_INPUT_INDEX); + rcti unused; + x_socket->determine_canvas(r_area, unused); + y_socket->determine_canvas(r_area, unused); + + ensureDelta(); + const float delta_x = x_extend_mode_ == MemoryBufferExtend::Clip ? getDeltaX() : 0.0f; + const float delta_y = y_extend_mode_ == MemoryBufferExtend::Clip ? getDeltaY() : 0.0f; + BLI_rcti_translate(&r_area, delta_x, delta_y); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_TranslateOperation.h b/source/blender/compositor/operations/COM_TranslateOperation.h index ce1965cecef..c5a3d1ffd99 100644 --- a/source/blender/compositor/operations/COM_TranslateOperation.h +++ b/source/blender/compositor/operations/COM_TranslateOperation.h @@ -24,6 +24,11 @@ namespace blender::compositor { class TranslateOperation : public MultiThreadedOperation { + protected: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int X_INPUT_INDEX = 1; + static constexpr int Y_INPUT_INDEX = 2; + private: SocketReader *m_inputOperation; SocketReader *m_inputXOperation; @@ -33,12 +38,14 @@ class TranslateOperation : public MultiThreadedOperation { bool m_isDeltaSet; float m_factorX; float m_factorY; + + protected: MemoryBufferExtend x_extend_mode_; MemoryBufferExtend y_extend_mode_; public: TranslateOperation(); - TranslateOperation(DataType data_type); + TranslateOperation(DataType data_type, ResizeMode mode = ResizeMode::Center); bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; @@ -67,16 +74,8 @@ class TranslateOperation : public MultiThreadedOperation { this->m_deltaY = tempDelta[0]; } else { - this->m_deltaX = 0; - NodeOperation *x_op = getInputOperation(1); - if (x_op->get_flags().is_constant_operation) { - this->m_deltaX = ((ConstantOperation *)x_op)->get_constant_elem()[0]; - } - this->m_deltaY = 0; - NodeOperation *y_op = getInputOperation(2); - if (y_op->get_flags().is_constant_operation) { - this->m_deltaY = ((ConstantOperation *)y_op)->get_constant_elem()[0]; - } + m_deltaX = get_input_operation(X_INPUT_INDEX)->get_constant_value_default(0.0f); + m_deltaY = get_input_operation(Y_INPUT_INDEX)->get_constant_value_default(0.0f); } this->m_isDeltaSet = true; @@ -93,4 +92,10 @@ class TranslateOperation : public MultiThreadedOperation { Span<MemoryBuffer *> inputs) override; }; +class TranslateCanvasOperation : public TranslateOperation { + public: + TranslateCanvasOperation(); + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; +}; + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc index b8274576cb5..c524447a4fa 100644 --- a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc +++ b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc @@ -28,8 +28,8 @@ namespace blender::compositor { VariableSizeBokehBlurOperation::VariableSizeBokehBlurOperation() { this->addInputSocket(DataType::Color); - this->addInputSocket(DataType::Color, ResizeMode::None); /* Do not resize the bokeh image. */ - this->addInputSocket(DataType::Value); /* Radius. */ + this->addInputSocket(DataType::Color, ResizeMode::Align); /* Do not resize the bokeh image. */ + this->addInputSocket(DataType::Value); /* Radius. */ #ifdef COM_DEFOCUS_SEARCH /* Inverse search radius optimization structure. */ this->addInputSocket(DataType::Color, ResizeMode::None); @@ -77,7 +77,7 @@ void *VariableSizeBokehBlurOperation::initializeTileData(rcti *rect) this->determineDependingAreaOfInterest( rect, (ReadBufferOperation *)this->m_inputSizeProgram, &rect2); - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(this->getWidth(), this->getHeight()); const float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; data->maxBlurScalar = (int)(data->size->get_max_value(rect2) * scalar); @@ -105,7 +105,7 @@ void VariableSizeBokehBlurOperation::executePixel(float output[4], int x, int y, float multiplier_accum[4]; float color_accum[4]; - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(getWidth(), getHeight()); const float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; int maxBlurScalar = tileData->maxBlurScalar; @@ -125,8 +125,8 @@ void VariableSizeBokehBlurOperation::executePixel(float output[4], int x, int y, #else int minx = MAX2(x - maxBlurScalar, 0); int miny = MAX2(y - maxBlurScalar, 0); - int maxx = MIN2(x + maxBlurScalar, (int)m_width); - int maxy = MIN2(y + maxBlurScalar, (int)m_height); + int maxx = MIN2(x + maxBlurScalar, (int)getWidth()); + int maxy = MIN2(y + maxBlurScalar, (int)getHeight()); #endif { inputSizeBuffer->readNoCheck(tempSize, x, y); @@ -200,7 +200,7 @@ void VariableSizeBokehBlurOperation::executeOpenCL(OpenCLDevice *device, MemoryBuffer *sizeMemoryBuffer = this->m_inputSizeProgram->getInputMemoryBuffer( inputMemoryBuffers); - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(getWidth(), getHeight()); cl_float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; maxBlur = (cl_int)min_ff(sizeMemoryBuffer->get_max_value() * scalar, (float)this->m_maxBlur); @@ -238,7 +238,7 @@ bool VariableSizeBokehBlurOperation::determineDependingAreaOfInterest( rcti newInput; rcti bokehInput; - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(getWidth(), getHeight()); const float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; int maxBlurScalar = this->m_maxBlur * scalar; @@ -294,10 +294,9 @@ void VariableSizeBokehBlurOperation::get_area_of_interest(const int input_idx, break; } case BOKEH_INPUT_INDEX: { - r_input_area.xmax = COM_BLUR_BOKEH_PIXELS; - r_input_area.xmin = 0; - r_input_area.ymax = COM_BLUR_BOKEH_PIXELS; - r_input_area.ymin = 0; + r_input_area = output_area; + r_input_area.xmax = r_input_area.xmin + COM_BLUR_BOKEH_PIXELS; + r_input_area.ymax = r_input_area.ymin + COM_BLUR_BOKEH_PIXELS; break; } #ifdef COM_DEFOCUS_SEARCH @@ -441,7 +440,7 @@ void VariableSizeBokehBlurOperation::update_memory_buffer_partial(MemoryBuffer * /* #InverseSearchRadiusOperation. */ InverseSearchRadiusOperation::InverseSearchRadiusOperation() { - this->addInputSocket(DataType::Value, ResizeMode::None); /* Radius. */ + this->addInputSocket(DataType::Value, ResizeMode::Align); /* Radius. */ this->addOutputSocket(DataType::Color); this->flags.complex = true; this->m_inputRadius = nullptr; diff --git a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h index 56b4677087b..3634bc2f1d7 100644 --- a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h +++ b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h @@ -130,8 +130,7 @@ class InverseSearchRadiusOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setMaxBlur(int maxRadius) { diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.cc b/source/blender/compositor/operations/COM_VectorBlurOperation.cc index 5405e6d424a..63956410b60 100644 --- a/source/blender/compositor/operations/COM_VectorBlurOperation.cc +++ b/source/blender/compositor/operations/COM_VectorBlurOperation.cc @@ -126,10 +126,7 @@ void VectorBlurOperation::get_area_of_interest(const int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } void VectorBlurOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_ViewerOperation.cc b/source/blender/compositor/operations/COM_ViewerOperation.cc index a038e8994d8..1faff0fd07f 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.cc +++ b/source/blender/compositor/operations/COM_ViewerOperation.cc @@ -23,6 +23,7 @@ #include "BLI_math_color.h" #include "BLI_math_vector.h" #include "BLI_utildefines.h" +#include "COM_ExecutionSystem.h" #include "MEM_guardedalloc.h" #include "PIL_time.h" #include "WM_api.h" @@ -34,6 +35,8 @@ namespace blender::compositor { +static int MAX_VIEWER_TRANSLATION_PADDING = 12000; + ViewerOperation::ViewerOperation() { this->setImage(nullptr); @@ -67,7 +70,7 @@ void ViewerOperation::initExecution() this->m_depthInput = getInputSocketReader(2); this->m_doDepthBuffer = (this->m_depthInput != nullptr); - if (isActiveViewerOutput()) { + if (isActiveViewerOutput() && !exec_system_->is_breaked()) { initImage(); } } @@ -122,15 +125,16 @@ void ViewerOperation::executeRegion(rcti *rect, unsigned int /*tileNumber*/) updateImage(rect); } -void ViewerOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void ViewerOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { const int sceneRenderWidth = this->m_rd->xsch * this->m_rd->size / 100; const int sceneRenderHeight = this->m_rd->ysch * this->m_rd->size / 100; - unsigned int localPrefRes[2] = {static_cast<unsigned int>(sceneRenderWidth), - static_cast<unsigned int>(sceneRenderHeight)}; - NodeOperation::determineResolution(resolution, localPrefRes); + rcti local_preferred = preferred_area; + local_preferred.xmax = local_preferred.xmin + sceneRenderWidth; + local_preferred.ymax = local_preferred.ymin + sceneRenderHeight; + + NodeOperation::determine_canvas(local_preferred, r_area); } void ViewerOperation::initImage() @@ -155,13 +159,24 @@ void ViewerOperation::initImage() BLI_thread_unlock(LOCK_DRAW_IMAGE); return; } - if (ibuf->x != (int)getWidth() || ibuf->y != (int)getHeight()) { + int padding_x = abs(canvas_.xmin) * 2; + int padding_y = abs(canvas_.ymin) * 2; + if (padding_x > MAX_VIEWER_TRANSLATION_PADDING) { + padding_x = MAX_VIEWER_TRANSLATION_PADDING; + } + if (padding_y > MAX_VIEWER_TRANSLATION_PADDING) { + padding_y = MAX_VIEWER_TRANSLATION_PADDING; + } + + display_width_ = getWidth() + padding_x; + display_height_ = getHeight() + padding_y; + if (ibuf->x != display_width_ || ibuf->y != display_height_) { imb_freerectImBuf(ibuf); imb_freerectfloatImBuf(ibuf); IMB_freezbuffloatImBuf(ibuf); - ibuf->x = getWidth(); - ibuf->y = getHeight(); + ibuf->x = display_width_; + ibuf->y = display_height_; /* zero size can happen if no image buffers exist to define a sensible resolution */ if (ibuf->x > 0 && ibuf->y > 0) { imb_addrectfloatImBuf(ibuf); @@ -193,11 +208,15 @@ void ViewerOperation::initImage() void ViewerOperation::updateImage(const rcti *rect) { + if (exec_system_->is_breaked()) { + return; + } + float *buffer = m_outputBuffer; IMB_partial_display_buffer_update(this->m_ibuf, buffer, nullptr, - getWidth(), + display_width_, 0, 0, this->m_viewSettings, @@ -227,29 +246,46 @@ void ViewerOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(output), return; } + const int offset_x = area.xmin + (canvas_.xmin > 0 ? canvas_.xmin * 2 : 0); + const int offset_y = area.ymin + (canvas_.ymin > 0 ? canvas_.ymin * 2 : 0); MemoryBuffer output_buffer( - m_outputBuffer, COM_DATA_TYPE_COLOR_CHANNELS, getWidth(), getHeight()); + m_outputBuffer, COM_DATA_TYPE_COLOR_CHANNELS, display_width_, display_height_); const MemoryBuffer *input_image = inputs[0]; - output_buffer.copy_from(input_image, area); + output_buffer.copy_from(input_image, area, offset_x, offset_y); if (this->m_useAlphaInput) { const MemoryBuffer *input_alpha = inputs[1]; - output_buffer.copy_from(input_alpha, area, 0, COM_DATA_TYPE_VALUE_CHANNELS, 3); + output_buffer.copy_from( + input_alpha, area, 0, COM_DATA_TYPE_VALUE_CHANNELS, offset_x, offset_y, 3); } if (m_depthBuffer) { MemoryBuffer depth_buffer( - m_depthBuffer, COM_DATA_TYPE_VALUE_CHANNELS, getWidth(), getHeight()); + m_depthBuffer, COM_DATA_TYPE_VALUE_CHANNELS, display_width_, display_height_); const MemoryBuffer *input_depth = inputs[2]; - depth_buffer.copy_from(input_depth, area); + depth_buffer.copy_from(input_depth, area, offset_x, offset_y); } - updateImage(&area); + rcti display_area; + BLI_rcti_init(&display_area, + offset_x, + offset_x + BLI_rcti_size_x(&area), + offset_y, + offset_y + BLI_rcti_size_y(&area)); + updateImage(&display_area); } void ViewerOperation::clear_display_buffer() { BLI_assert(isActiveViewerOutput()); + if (exec_system_->is_breaked()) { + return; + } + initImage(); + if (m_outputBuffer == nullptr) { + return; + } + size_t buf_bytes = (size_t)m_ibuf->y * m_ibuf->x * COM_DATA_TYPE_COLOR_CHANNELS * sizeof(float); if (buf_bytes > 0) { memset(m_outputBuffer, 0, buf_bytes); diff --git a/source/blender/compositor/operations/COM_ViewerOperation.h b/source/blender/compositor/operations/COM_ViewerOperation.h index 06ac501a535..95ee982f692 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.h +++ b/source/blender/compositor/operations/COM_ViewerOperation.h @@ -50,13 +50,15 @@ class ViewerOperation : public MultiThreadedOperation { SocketReader *m_alphaInput; SocketReader *m_depthInput; + int display_width_; + int display_height_; + public: ViewerOperation(); void initExecution() override; void deinitExecution() override; void executeRegion(rcti *rect, unsigned int tileNumber) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; bool isOutputOperation(bool /*rendering*/) const override { if (G.background) { diff --git a/source/blender/compositor/operations/COM_WrapOperation.cc b/source/blender/compositor/operations/COM_WrapOperation.cc index 888602114cc..393128ded41 100644 --- a/source/blender/compositor/operations/COM_WrapOperation.cc +++ b/source/blender/compositor/operations/COM_WrapOperation.cc @@ -33,7 +33,7 @@ inline float WrapOperation::getWrappedOriginalXPos(float x) return 0; } while (x < 0) { - x += this->m_width; + x += this->getWidth(); } return fmodf(x, this->getWidth()); } @@ -44,7 +44,7 @@ inline float WrapOperation::getWrappedOriginalYPos(float y) return 0; } while (y < 0) { - y += this->m_height; + y += this->getHeight(); } return fmodf(y, this->getHeight()); } diff --git a/source/blender/compositor/operations/COM_WriteBufferOperation.cc b/source/blender/compositor/operations/COM_WriteBufferOperation.cc index 6380f6bf3df..a1c1e514eb7 100644 --- a/source/blender/compositor/operations/COM_WriteBufferOperation.cc +++ b/source/blender/compositor/operations/COM_WriteBufferOperation.cc @@ -50,7 +50,7 @@ void WriteBufferOperation::executePixelSampled(float output[4], void WriteBufferOperation::initExecution() { this->m_input = this->getInputOperation(0); - this->m_memoryProxy->allocate(this->m_width, this->m_height); + this->m_memoryProxy->allocate(this->getWidth(), this->getHeight()); } void WriteBufferOperation::deinitExecution() @@ -206,18 +206,17 @@ void WriteBufferOperation::executeOpenCLRegion(OpenCLDevice *device, delete clKernelsToCleanUp; } -void WriteBufferOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void WriteBufferOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); /* make sure there is at least one pixel stored in case the input is a single value */ m_single_value = false; - if (resolution[0] == 0) { - resolution[0] = 1; + if (BLI_rcti_size_x(&r_area) == 0) { + r_area.xmax += 1; m_single_value = true; } - if (resolution[1] == 0) { - resolution[1] = 1; + if (BLI_rcti_size_y(&r_area) == 0) { + r_area.ymax += 1; m_single_value = true; } } diff --git a/source/blender/compositor/operations/COM_WriteBufferOperation.h b/source/blender/compositor/operations/COM_WriteBufferOperation.h index 2817fbe24b9..b7dededc69f 100644 --- a/source/blender/compositor/operations/COM_WriteBufferOperation.h +++ b/source/blender/compositor/operations/COM_WriteBufferOperation.h @@ -56,8 +56,7 @@ class WriteBufferOperation : public NodeOperation { unsigned int chunkNumber, MemoryBuffer **memoryBuffers, MemoryBuffer *outputBuffer) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void readResolutionFromInputSocket(); inline NodeOperation *getInput() { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 36c6b56caae..a09f79ffa39 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -63,6 +63,7 @@ #include "DNA_sound_types.h" #include "DNA_speaker_types.h" #include "DNA_texture_types.h" +#include "DNA_vfont_types.h" #include "DNA_world_types.h" #include "BKE_action.h" @@ -1466,7 +1467,7 @@ void DepsgraphNodeBuilder::build_shapekeys(Key *key) } /* ObData Geometry Evaluation */ -// XXX: what happens if the datablock is shared! +/* XXX: what happens if the datablock is shared! */ void DepsgraphNodeBuilder::build_object_data_geometry(Object *object, bool is_object_visible) { OperationNode *op_node; @@ -1764,6 +1765,9 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) else if (id_type == ID_MC) { build_movieclip((MovieClip *)id); } + else if (id_type == ID_VF) { + build_vfont((VFont *)id); + } else if (ELEM(bnode->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); @@ -1780,7 +1784,7 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) build_idproperties(socket->prop); } - // TODO: link from nodetree to owner_component? + /* TODO: link from nodetree to owner_component? */ } /* Recursively build graph for material */ @@ -2015,6 +2019,17 @@ void DepsgraphNodeBuilder::build_simulation(Simulation *simulation) }); } +void DepsgraphNodeBuilder::build_vfont(VFont *vfont) +{ + if (built_map_.checkIsBuiltAndTag(vfont)) { + return; + } + build_parameters(&vfont->id); + build_idproperties(vfont->id.properties); + add_operation_node( + &vfont->id, NodeType::GENERIC_DATABLOCK, OperationCode::GENERIC_DATABLOCK_UPDATE); +} + static bool seq_node_build_cb(Sequence *seq, void *user_data) { DepsgraphNodeBuilder *nb = (DepsgraphNodeBuilder *)user_data; diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h index 2378f3fc100..d31290ecbff 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h @@ -55,6 +55,7 @@ struct Scene; struct Simulation; struct Speaker; struct Tex; +struct VFont; struct World; struct bAction; struct bArmature; @@ -235,6 +236,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder { virtual void build_scene_sequencer(Scene *scene); virtual void build_scene_audio(Scene *scene); virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer); + virtual void build_vfont(VFont *vfont); /* Per-ID information about what was already in the dependency graph. * Allows to re-use certain values, to speed up following evaluation. */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 28cfc5a9e1a..55e8c5ed033 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -65,6 +65,7 @@ #include "DNA_sound_types.h" #include "DNA_speaker_types.h" #include "DNA_texture_types.h" +#include "DNA_vfont_types.h" #include "DNA_volume_types.h" #include "DNA_world_types.h" @@ -2496,6 +2497,11 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) OperationKey clip_key(id, NodeType::PARAMETERS, OperationCode::MOVIECLIP_EVAL); add_relation(clip_key, shading_key, "Clip -> Node"); } + else if (id_type == ID_VF) { + build_vfont((VFont *)id); + ComponentKey vfont_key(id, NodeType::GENERIC_DATABLOCK); + add_relation(vfont_key, shading_key, "VFont -> Node"); + } else if (ELEM(bnode->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); @@ -2842,6 +2848,15 @@ void DepsgraphRelationBuilder::build_scene_speakers(Scene * /*scene*/, ViewLayer } } +void DepsgraphRelationBuilder::build_vfont(VFont *vfont) +{ + if (built_map_.checkIsBuiltAndTag(vfont)) { + return; + } + build_parameters(&vfont->id); + build_idproperties(vfont->id.properties); +} + void DepsgraphRelationBuilder::build_copy_on_write_relations() { for (IDNode *id_node : graph_->id_nodes) { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h index 1ad61c25305..f0393544511 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h @@ -72,6 +72,7 @@ struct Simulation; struct Speaker; struct Tex; struct ViewLayer; +struct VFont; struct World; struct bAction; struct bArmature; @@ -296,6 +297,7 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder { virtual void build_scene_sequencer(Scene *scene); virtual void build_scene_audio(Scene *scene); virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer); + virtual void build_vfont(VFont *vfont); virtual void build_nested_datablock(ID *owner, ID *id); virtual void build_nested_nodetree(ID *owner, bNodeTree *ntree); diff --git a/source/blender/depsgraph/intern/node/deg_node.cc b/source/blender/depsgraph/intern/node/deg_node.cc index fee5342df59..16089ba27dd 100644 --- a/source/blender/depsgraph/intern/node/deg_node.cc +++ b/source/blender/depsgraph/intern/node/deg_node.cc @@ -177,7 +177,7 @@ eDepsSceneComponentType nodeTypeToSceneComponent(NodeType type) case NodeType::SIMULATION: return DEG_SCENE_COMP_PARAMETERS; } - BLI_assert_msg(0, "Unhandled node type, not suppsed to happen."); + BLI_assert_msg(0, "Unhandled node type, not supposed to happen."); return DEG_SCENE_COMP_PARAMETERS; } diff --git a/source/blender/depsgraph/intern/node/deg_node_component.cc b/source/blender/depsgraph/intern/node/deg_node_component.cc index a29618cefa8..0947fad7670 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.cc +++ b/source/blender/depsgraph/intern/node/deg_node_component.cc @@ -90,7 +90,7 @@ ComponentNode::ComponentNode() void ComponentNode::init(const ID * /*id*/, const char * /*subdata*/) { /* hook up eval context? */ - // XXX: maybe this needs a special API? + /* XXX: maybe this needs a special API? */ } /* Free 'component' node */ diff --git a/source/blender/draw/engines/eevee/eevee_cryptomatte.c b/source/blender/draw/engines/eevee/eevee_cryptomatte.c index 49780abc6f4..ea60dd0753e 100644 --- a/source/blender/draw/engines/eevee/eevee_cryptomatte.c +++ b/source/blender/draw/engines/eevee/eevee_cryptomatte.c @@ -255,7 +255,7 @@ static void eevee_cryptomatte_hair_cache_populate(EEVEE_Data *vedata, { DRWShadingGroup *grp = eevee_cryptomatte_shading_group_create( vedata, sldata, ob, material, true); - DRW_shgroup_hair_create_sub(ob, psys, md, grp); + DRW_shgroup_hair_create_sub(ob, psys, md, grp, NULL); } void EEVEE_cryptomatte_object_hair_cache_populate(EEVEE_Data *vedata, diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c index 9ecb737192e..a627bcd9488 100644 --- a/source/blender/draw/engines/eevee/eevee_materials.c +++ b/source/blender/draw/engines/eevee/eevee_materials.c @@ -769,15 +769,16 @@ static void eevee_hair_cache_populate(EEVEE_Data *vedata, EeveeMaterialCache matcache = eevee_material_cache_get(vedata, sldata, ob, matnr - 1, true); if (matcache.depth_grp) { - *matcache.depth_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.depth_grp); + *matcache.depth_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.depth_grp, NULL); DRW_shgroup_add_material_resources(*matcache.depth_grp_p, matcache.shading_gpumat); } if (matcache.shading_grp) { - *matcache.shading_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.shading_grp); + *matcache.shading_grp_p = DRW_shgroup_hair_create_sub( + ob, psys, md, matcache.shading_grp, matcache.shading_gpumat); DRW_shgroup_add_material_resources(*matcache.shading_grp_p, matcache.shading_gpumat); } if (matcache.shadow_grp) { - *matcache.shadow_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.shadow_grp); + *matcache.shadow_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.shadow_grp, NULL); DRW_shgroup_add_material_resources(*matcache.shadow_grp_p, matcache.shading_gpumat); *cast_shadow = true; } diff --git a/source/blender/draw/engines/eevee/eevee_motion_blur.c b/source/blender/draw/engines/eevee/eevee_motion_blur.c index 2e200c8e053..e8c2514d908 100644 --- a/source/blender/draw/engines/eevee/eevee_motion_blur.c +++ b/source/blender/draw/engines/eevee/eevee_motion_blur.c @@ -269,7 +269,7 @@ void EEVEE_motion_blur_hair_cache_populate(EEVEE_ViewLayerData *UNUSED(sldata), GPUTexture *tex_prev = mb_hair->psys[psys_id].hair_pos_tx[MB_PREV]; GPUTexture *tex_next = mb_hair->psys[psys_id].hair_pos_tx[MB_NEXT]; - grp = DRW_shgroup_hair_create_sub(ob, psys, md, effects->motion_blur.hair_grp); + grp = DRW_shgroup_hair_create_sub(ob, psys, md, effects->motion_blur.hair_grp, NULL); DRW_shgroup_uniform_mat4(grp, "prevModelMatrix", mb_data->obmat[MB_PREV]); DRW_shgroup_uniform_mat4(grp, "currModelMatrix", mb_data->obmat[MB_CURR]); DRW_shgroup_uniform_mat4(grp, "nextModelMatrix", mb_data->obmat[MB_NEXT]); diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 34fe29055d6..674aca29662 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -298,14 +298,14 @@ typedef struct GPENCIL_PrivateData { /* Current frame */ int cfra; /* If we are rendering for final render (F12). - * NOTE: set to false for viewport and opengl rendering (including VSE scene rendering), but set - * to true when rendering in `OB_RENDER` shading mode (viewport or opengl rendering) */ + * NOTE: set to false for viewport and opengl rendering (including sequencer scene rendering), + * but set to true when rendering in #OB_RENDER shading mode (viewport or opengl rendering). */ bool is_render; /* If we are in viewport display (used for VFX). */ bool is_viewport; /* True in selection and auto_depth drawing */ bool draw_depth_only; - /* Is shading set to wireframe. */ + /* Is shading set to wire-frame. */ bool draw_wireframe; /* Used by the depth merge step. */ int is_stroke_order_3d; diff --git a/source/blender/draw/engines/overlay/overlay_edit_uv.c b/source/blender/draw/engines/overlay/overlay_edit_uv.c index 985f8a6785c..983df1ceac8 100644 --- a/source/blender/draw/engines/overlay/overlay_edit_uv.c +++ b/source/blender/draw/engines/overlay/overlay_edit_uv.c @@ -340,34 +340,42 @@ void OVERLAY_edit_uv_cache_init(OVERLAY_Data *vedata) if (pd->edit_uv.do_stencil_overlay) { const Brush *brush = BKE_paint_brush(&ts->imapaint.paint); - - DRW_PASS_CREATE(psl->edit_uv_stencil_ps, - DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS | DRW_STATE_BLEND_ALPHA_PREMUL); - GPUShader *sh = OVERLAY_shader_edit_uv_stencil_image(); - GPUBatch *geom = DRW_cache_quad_get(); - DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->edit_uv_stencil_ps); Image *stencil_image = brush->clone.image; ImBuf *stencil_ibuf = BKE_image_acquire_ibuf(stencil_image, NULL, &pd->edit_uv.stencil_lock); - pd->edit_uv.stencil_ibuf = stencil_ibuf; - pd->edit_uv.stencil_image = stencil_image; - GPUTexture *stencil_texture = BKE_image_get_gpu_texture(stencil_image, NULL, stencil_ibuf); - DRW_shgroup_uniform_texture(grp, "imgTexture", stencil_texture); - DRW_shgroup_uniform_bool_copy(grp, "imgPremultiplied", true); - DRW_shgroup_uniform_bool_copy(grp, "imgAlphaBlend", true); - DRW_shgroup_uniform_vec4_copy(grp, "color", (float[4]){1.0f, 1.0f, 1.0f, brush->clone.alpha}); - - float size_image[2]; - BKE_image_get_size_fl(image, NULL, size_image); - float size_stencil_image[2] = {stencil_ibuf->x, stencil_ibuf->y}; - float obmat[4][4]; - unit_m4(obmat); - obmat[3][1] = brush->clone.offset[1]; - obmat[3][0] = brush->clone.offset[0]; - obmat[0][0] = size_stencil_image[0] / size_image[0]; - obmat[1][1] = size_stencil_image[1] / size_image[1]; + if (stencil_ibuf == NULL) { + pd->edit_uv.stencil_ibuf = NULL; + pd->edit_uv.stencil_image = NULL; + } + else { + DRW_PASS_CREATE(psl->edit_uv_stencil_ps, + DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS | + DRW_STATE_BLEND_ALPHA_PREMUL); + GPUShader *sh = OVERLAY_shader_edit_uv_stencil_image(); + GPUBatch *geom = DRW_cache_quad_get(); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->edit_uv_stencil_ps); + pd->edit_uv.stencil_ibuf = stencil_ibuf; + pd->edit_uv.stencil_image = stencil_image; + GPUTexture *stencil_texture = BKE_image_get_gpu_texture(stencil_image, NULL, stencil_ibuf); + DRW_shgroup_uniform_texture(grp, "imgTexture", stencil_texture); + DRW_shgroup_uniform_bool_copy(grp, "imgPremultiplied", true); + DRW_shgroup_uniform_bool_copy(grp, "imgAlphaBlend", true); + DRW_shgroup_uniform_vec4_copy( + grp, "color", (float[4]){1.0f, 1.0f, 1.0f, brush->clone.alpha}); + + float size_image[2]; + BKE_image_get_size_fl(image, NULL, size_image); + float size_stencil_image[2] = {stencil_ibuf->x, stencil_ibuf->y}; + + float obmat[4][4]; + unit_m4(obmat); + obmat[3][1] = brush->clone.offset[1]; + obmat[3][0] = brush->clone.offset[0]; + obmat[0][0] = size_stencil_image[0] / size_image[0]; + obmat[1][1] = size_stencil_image[1] / size_image[1]; - DRW_shgroup_call_obmat(grp, geom, obmat); + DRW_shgroup_call_obmat(grp, geom, obmat); + } } else { pd->edit_uv.stencil_ibuf = NULL; diff --git a/source/blender/draw/engines/overlay/overlay_extra.c b/source/blender/draw/engines/overlay/overlay_extra.c index 8f9db7f6f13..98db7136398 100644 --- a/source/blender/draw/engines/overlay/overlay_extra.c +++ b/source/blender/draw/engines/overlay/overlay_extra.c @@ -696,7 +696,7 @@ void OVERLAY_light_cache_populate(OVERLAY_Data *vedata, Object *ob) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Lightprobe +/** \name Light-probe * \{ */ void OVERLAY_lightprobe_cache_populate(OVERLAY_Data *vedata, Object *ob) diff --git a/source/blender/draw/engines/overlay/overlay_grid.c b/source/blender/draw/engines/overlay/overlay_grid.c index ea8735419f9..cc8ab00baef 100644 --- a/source/blender/draw/engines/overlay/overlay_grid.c +++ b/source/blender/draw/engines/overlay/overlay_grid.c @@ -23,6 +23,7 @@ #include "DRW_render.h" #include "DNA_camera_types.h" +#include "DNA_screen_types.h" #include "DEG_depsgraph_query.h" @@ -46,6 +47,7 @@ enum { GRID_BACK = (1 << 9), GRID_CAMERA = (1 << 10), PLANE_IMAGE = (1 << 11), + CUSTOM_GRID = (1 << 12), }; void OVERLAY_grid_init(OVERLAY_Data *vedata) @@ -61,6 +63,7 @@ void OVERLAY_grid_init(OVERLAY_Data *vedata) if (pd->space_type == SPACE_IMAGE) { SpaceImage *sima = (SpaceImage *)draw_ctx->space_data; + View2D *v2d = &draw_ctx->region->v2d; if (sima->mode == SI_MODE_UV || !ED_space_image_has_buffer(sima)) { shd->grid_flag = GRID_BACK | PLANE_IMAGE | SHOW_GRID; } @@ -68,15 +71,21 @@ void OVERLAY_grid_init(OVERLAY_Data *vedata) shd->grid_flag = 0; } + if (sima->flag & SI_CUSTOM_GRID) { + shd->grid_flag |= CUSTOM_GRID; + } + shd->grid_distance = 1.0f; copy_v3_fl3(shd->grid_size, 1.0f, 1.0f, 1.0f); if (sima->mode == SI_MODE_UV) { shd->grid_size[0] = (float)sima->tile_grid_shape[0]; shd->grid_size[1] = (float)sima->tile_grid_shape[1]; } - for (int step = 0; step < 8; step++) { - shd->grid_steps[step] = powf(4, step) * (1.0f / 16.0f); - } + + const int grid_size = SI_GRID_STEPS_LEN; + shd->zoom_factor = ED_space_image_zoom_level(v2d, grid_size); + ED_space_image_grid_steps(sima, shd->grid_steps, grid_size); + return; } @@ -257,6 +266,7 @@ void OVERLAY_grid_cache_init(OVERLAY_Data *vedata) grp = DRW_shgroup_create(sh, psl->grid_ps); DRW_shgroup_uniform_int(grp, "gridFlag", &shd->grid_flag, 1); + DRW_shgroup_uniform_float_copy(grp, "zoomFactor", shd->zoom_factor); DRW_shgroup_uniform_vec3(grp, "planeAxes", shd->grid_axes, 1); DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo); DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth); diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h index 23df571e8de..def278f98df 100644 --- a/source/blender/draw/engines/overlay/overlay_private.h +++ b/source/blender/draw/engines/overlay/overlay_private.h @@ -139,9 +139,10 @@ typedef struct OVERLAY_ShadingData { /** Grid */ float grid_axes[3], grid_distance; float zplane_axes[3], grid_size[3]; - float grid_steps[8]; + float grid_steps[SI_GRID_STEPS_LEN]; float inv_viewport_size[2]; float grid_line_size; + float zoom_factor; /* Only for UV editor */ int grid_flag; int zpos_flag; int zneg_flag; diff --git a/source/blender/draw/engines/overlay/shaders/grid_frag.glsl b/source/blender/draw/engines/overlay/shaders/grid_frag.glsl index 3220adbff36..9feca644bd3 100644 --- a/source/blender/draw/engines/overlay/shaders/grid_frag.glsl +++ b/source/blender/draw/engines/overlay/shaders/grid_frag.glsl @@ -15,8 +15,9 @@ uniform float lineKernel = 0.0; uniform sampler2D depthBuffer; uniform int gridFlag; +uniform float zoomFactor; -#define STEPS_LEN 8 +#define STEPS_LEN 8 /* Match: #SI_GRID_STEPS_LEN */ uniform float gridSteps[STEPS_LEN] = float[](0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0, 10000.0); #define AXIS_X (1 << 0) @@ -28,6 +29,8 @@ uniform float gridSteps[STEPS_LEN] = float[](0.001, 0.01, 0.1, 1.0, 10.0, 100.0, #define PLANE_YZ (1 << 6) #define GRID_BACK (1 << 9) /* grid is behind objects */ #define GRID_CAMERA (1 << 10) /* In camera view */ +#define PLANE_IMAGE (1 << 11) /* UV/Image Image editor */ +#define CUSTOM_GRID (1 << 12) /* UV Editor only */ #define M_1_SQRTPI 0.5641895835477563 /* 1/sqrt(pi) */ @@ -122,9 +125,17 @@ void main() * would be more accurate, but not really necessary. */ float grid_res = dot(dFdxPos, screenVecs[0].xyz); - /* The gride begins to appear when it comprises 4 pixels */ + /* The grid begins to appear when it comprises 4 pixels */ grid_res *= 4; + /* For UV/Image editor use zoomFactor */ + if ((gridFlag & PLANE_IMAGE) != 0 && + /* Grid begins to appear when the length of one grid unit is at least + * (256/grid_size) pixels Value of grid_size defined in `overlay_grid.c`. */ + (gridFlag & CUSTOM_GRID) == 0) { + grid_res = zoomFactor; + } + /* from biggest to smallest */ vec4 scale; #if 0 diff --git a/source/blender/draw/engines/workbench/workbench_engine.c b/source/blender/draw/engines/workbench/workbench_engine.c index 635aa7cef25..a5281427fa8 100644 --- a/source/blender/draw/engines/workbench/workbench_engine.c +++ b/source/blender/draw/engines/workbench/workbench_engine.c @@ -238,7 +238,7 @@ static void workbench_cache_hair_populate(WORKBENCH_PrivateData *wpd, workbench_image_hair_setup(wpd, ob, matnr, ima, NULL, state) : workbench_material_hair_setup(wpd, ob, matnr, color_type); - DRW_shgroup_hair_create_sub(ob, psys, md, grp); + DRW_shgroup_hair_create_sub(ob, psys, md, grp, NULL); } /** diff --git a/source/blender/draw/intern/draw_cache_impl_curve.cc b/source/blender/draw/intern/draw_cache_impl_curve.cc index 0804745fab5..dc8f382b7f8 100644 --- a/source/blender/draw/intern/draw_cache_impl_curve.cc +++ b/source/blender/draw/intern/draw_cache_impl_curve.cc @@ -342,6 +342,9 @@ static void curve_cd_calc_used_gpu_layers(CustomDataMask *cd_layers, case CD_ORCO: *cd_layers |= CD_MASK_ORCO; break; + case CD_HAIRLENGTH: + *cd_layers |= CD_MASK_HAIRLENGTH; + break; } } } diff --git a/source/blender/draw/intern/draw_cache_impl_hair.c b/source/blender/draw/intern/draw_cache_impl_hair.c index 6424b21666d..41a0cca8a8f 100644 --- a/source/blender/draw/intern/draw_cache_impl_hair.c +++ b/source/blender/draw/intern/draw_cache_impl_hair.c @@ -27,6 +27,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_listbase.h" #include "BLI_math_base.h" #include "BLI_math_vector.h" #include "BLI_utildefines.h" @@ -37,6 +38,7 @@ #include "BKE_hair.h" #include "GPU_batch.h" +#include "GPU_material.h" #include "GPU_texture.h" #include "draw_cache_impl.h" /* own include */ @@ -141,7 +143,9 @@ static void ensure_seg_pt_count(Hair *hair, ParticleHairCache *hair_cache) } } -static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, GPUVertBufRaw *attr_step) +static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, + GPUVertBufRaw *attr_step, + GPUVertBufRaw *length_step) { /* TODO: use hair radius layer if available. */ HairCurve *curve = hair->curves; @@ -162,6 +166,8 @@ static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, GPUVertBufRaw *a seg_data[3] = total_len; co_prev = curve_co[j]; } + /* Assign length value*/ + *(float *)GPU_vertbuf_raw_step(length_step) = total_len; if (total_len > 0.0f) { /* Divide by total length to have a [0-1] number. */ for (int j = 0; j < curve->numpoints; j++, seg_data_first += 4) { @@ -171,28 +177,48 @@ static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, GPUVertBufRaw *a } } -static void hair_batch_cache_ensure_procedural_pos(Hair *hair, ParticleHairCache *cache) +static void hair_batch_cache_ensure_procedural_pos(Hair *hair, + ParticleHairCache *cache, + GPUMaterial *gpu_material) { - if (cache->proc_point_buf != NULL) { - return; - } + if (cache->proc_point_buf == NULL) { + /* initialize vertex format */ + GPUVertFormat format = {0}; + uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - /* initialize vertex format */ - GPUVertFormat format = {0}; - uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + cache->proc_point_buf = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); - cache->proc_point_buf = GPU_vertbuf_create_with_format(&format); - GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); + GPUVertBufRaw point_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &point_step); - GPUVertBufRaw pos_step; - GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step); + GPUVertFormat length_format = {0}; + uint length_id = GPU_vertformat_attr_add( + &length_format, "hairLength", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); - hair_batch_cache_fill_segments_proc_pos(hair, &pos_step); + cache->proc_length_buf = GPU_vertbuf_create_with_format(&length_format); + GPU_vertbuf_data_alloc(cache->proc_length_buf, cache->strands_len); - /* Create vbo immediately to bind to texture buffer. */ - GPU_vertbuf_use(cache->proc_point_buf); + GPUVertBufRaw length_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_length_buf, length_id, &length_step); + + hair_batch_cache_fill_segments_proc_pos(hair, &point_step, &length_step); - cache->point_tex = GPU_texture_create_from_vertbuf("hair_point", cache->proc_point_buf); + /* Create vbo immediately to bind to texture buffer. */ + GPU_vertbuf_use(cache->proc_point_buf); + cache->point_tex = GPU_texture_create_from_vertbuf("hair_point", cache->proc_point_buf); + } + + if (gpu_material && cache->proc_length_buf != NULL && cache->length_tex) { + ListBase gpu_attrs = GPU_material_attributes(gpu_material); + LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &gpu_attrs) { + if (attr->type == CD_HAIRLENGTH) { + GPU_vertbuf_use(cache->proc_length_buf); + cache->length_tex = GPU_texture_create_from_vertbuf("hair_length", cache->proc_length_buf); + break; + } + } + } } static void hair_batch_cache_fill_strands_data(Hair *hair, @@ -310,6 +336,7 @@ static void hair_batch_cache_ensure_procedural_indices(Hair *hair, /* Ensure all textures and buffers needed for GPU accelerated drawing. */ bool hair_ensure_procedural_data(Object *object, ParticleHairCache **r_hair_cache, + GPUMaterial *gpu_material, int subdiv, int thickness_res) { @@ -325,7 +352,7 @@ bool hair_ensure_procedural_data(Object *object, /* Refreshed on combing and simulation. */ if ((*r_hair_cache)->proc_point_buf == NULL) { ensure_seg_pt_count(hair, &cache->hair); - hair_batch_cache_ensure_procedural_pos(hair, &cache->hair); + hair_batch_cache_ensure_procedural_pos(hair, &cache->hair, gpu_material); need_ft_update = true; } diff --git a/source/blender/draw/intern/draw_cache_impl_particles.c b/source/blender/draw/intern/draw_cache_impl_particles.c index 5c51f24a435..96bdca7d935 100644 --- a/source/blender/draw/intern/draw_cache_impl_particles.c +++ b/source/blender/draw/intern/draw_cache_impl_particles.c @@ -46,6 +46,7 @@ #include "ED_particle.h" #include "GPU_batch.h" +#include "GPU_material.h" #include "DEG_depsgraph_query.h" @@ -183,7 +184,9 @@ void particle_batch_cache_clear_hair(ParticleHairCache *hair_cache) { /* TODO: more granular update tagging. */ GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_point_buf); + GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_length_buf); DRW_TEXTURE_FREE_SAFE(hair_cache->point_tex); + DRW_TEXTURE_FREE_SAFE(hair_cache->length_tex); GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_buf); GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_seg_buf); @@ -609,7 +612,8 @@ static int particle_batch_cache_fill_segments(ParticleSystem *psys, static void particle_batch_cache_fill_segments_proc_pos(ParticleCacheKey **path_cache, const int num_path_keys, - GPUVertBufRaw *attr_step) + GPUVertBufRaw *attr_step, + GPUVertBufRaw *length_step) { for (int i = 0; i < num_path_keys; i++) { ParticleCacheKey *path = path_cache[i]; @@ -630,6 +634,8 @@ static void particle_batch_cache_fill_segments_proc_pos(ParticleCacheKey **path_ seg_data[3] = total_len; co_prev = path[j].co; } + /* Assign length value*/ + *(float *)GPU_vertbuf_raw_step(length_step) = total_len; if (total_len > 0.0f) { /* Divide by total length to have a [0-1] number. */ for (int j = 0; j <= path->segments; j++, seg_data_first += 4) { @@ -1079,40 +1085,64 @@ static void particle_batch_cache_ensure_procedural_indices(PTCacheEdit *edit, static void particle_batch_cache_ensure_procedural_pos(PTCacheEdit *edit, ParticleSystem *psys, - ParticleHairCache *cache) + ParticleHairCache *cache, + GPUMaterial *gpu_material) { - if (cache->proc_point_buf != NULL) { - return; - } + if (cache->proc_point_buf == NULL) { + /* initialize vertex format */ + GPUVertFormat pos_format = {0}; + uint pos_id = GPU_vertformat_attr_add( + &pos_format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - /* initialize vertex format */ - GPUVertFormat format = {0}; - uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + cache->proc_point_buf = GPU_vertbuf_create_with_format(&pos_format); + GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); - cache->proc_point_buf = GPU_vertbuf_create_with_format(&format); - GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); + GPUVertBufRaw pos_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step); - GPUVertBufRaw pos_step; - GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step); + GPUVertFormat length_format = {0}; + uint length_id = GPU_vertformat_attr_add( + &length_format, "hairLength", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); - if (edit != NULL && edit->pathcache != NULL) { - particle_batch_cache_fill_segments_proc_pos(edit->pathcache, edit->totcached, &pos_step); - } - else { - if ((psys->pathcache != NULL) && - (!psys->childcache || (psys->part->draw & PART_DRAW_PARENT))) { - particle_batch_cache_fill_segments_proc_pos(psys->pathcache, psys->totpart, &pos_step); + cache->proc_length_buf = GPU_vertbuf_create_with_format(&length_format); + GPU_vertbuf_data_alloc(cache->proc_length_buf, cache->strands_len); + + GPUVertBufRaw length_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_length_buf, length_id, &length_step); + + if (edit != NULL && edit->pathcache != NULL) { + particle_batch_cache_fill_segments_proc_pos( + edit->pathcache, edit->totcached, &pos_step, &length_step); } - if (psys->childcache) { - const int child_count = psys->totchild * psys->part->disp / 100; - particle_batch_cache_fill_segments_proc_pos(psys->childcache, child_count, &pos_step); + else { + if ((psys->pathcache != NULL) && + (!psys->childcache || (psys->part->draw & PART_DRAW_PARENT))) { + particle_batch_cache_fill_segments_proc_pos( + psys->pathcache, psys->totpart, &pos_step, &length_step); + } + if (psys->childcache) { + const int child_count = psys->totchild * psys->part->disp / 100; + particle_batch_cache_fill_segments_proc_pos( + psys->childcache, child_count, &pos_step, &length_step); + } } - } - /* Create vbo immediately to bind to texture buffer. */ - GPU_vertbuf_use(cache->proc_point_buf); + /* Create vbo immediately to bind to texture buffer. */ + GPU_vertbuf_use(cache->proc_point_buf); + cache->point_tex = GPU_texture_create_from_vertbuf("part_point", cache->proc_point_buf); + } - cache->point_tex = GPU_texture_create_from_vertbuf("part_point", cache->proc_point_buf); + /* Checking hair length separately, only allocating gpu memory when needed. */ + if (gpu_material && cache->proc_length_buf != NULL && cache->length_tex == NULL) { + ListBase gpu_attrs = GPU_material_attributes(gpu_material); + LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &gpu_attrs) { + if (attr->type == CD_HAIRLENGTH) { + GPU_vertbuf_use(cache->proc_length_buf); + cache->length_tex = GPU_texture_create_from_vertbuf("hair_length", cache->proc_length_buf); + break; + } + } + } } static void particle_batch_cache_ensure_pos_and_seg(PTCacheEdit *edit, @@ -1649,6 +1679,7 @@ bool particles_ensure_procedural_data(Object *object, ParticleSystem *psys, ModifierData *md, ParticleHairCache **r_hair_cache, + GPUMaterial *gpu_material, int subdiv, int thickness_res) { @@ -1666,9 +1697,11 @@ bool particles_ensure_procedural_data(Object *object, (*r_hair_cache)->final[subdiv].strands_res = 1 << (part->draw_step + subdiv); /* Refreshed on combing and simulation. */ - if ((*r_hair_cache)->proc_point_buf == NULL) { + if ((*r_hair_cache)->proc_point_buf == NULL || + (gpu_material && (*r_hair_cache)->length_tex == NULL)) { ensure_seg_pt_count(source.edit, source.psys, &cache->hair); - particle_batch_cache_ensure_procedural_pos(source.edit, source.psys, &cache->hair); + particle_batch_cache_ensure_procedural_pos( + source.edit, source.psys, &cache->hair, gpu_material); need_ft_update = true; } diff --git a/source/blender/draw/intern/draw_common.h b/source/blender/draw/intern/draw_common.h index 1eaf2bee236..2913877c9c1 100644 --- a/source/blender/draw/intern/draw_common.h +++ b/source/blender/draw/intern/draw_common.h @@ -29,6 +29,7 @@ struct Object; struct ParticleSystem; struct RegionView3D; struct ViewLayer; +struct GPUMaterial; #define UBO_FIRST_COLOR colorWire #define UBO_LAST_COLOR colorUVShadow @@ -175,7 +176,8 @@ bool DRW_object_axis_orthogonal_to_view(struct Object *ob, int axis); struct DRWShadingGroup *DRW_shgroup_hair_create_sub(struct Object *object, struct ParticleSystem *psys, struct ModifierData *md, - struct DRWShadingGroup *shgrp); + struct DRWShadingGroup *shgrp, + struct GPUMaterial *gpu_material); struct GPUVertBuf *DRW_hair_pos_buffer_get(struct Object *object, struct ParticleSystem *psys, struct ModifierData *md); diff --git a/source/blender/draw/intern/draw_hair.c b/source/blender/draw/intern/draw_hair.c index c2e25389091..5c7eb083fc9 100644 --- a/source/blender/draw/intern/draw_hair.c +++ b/source/blender/draw/intern/draw_hair.c @@ -38,6 +38,7 @@ #include "GPU_batch.h" #include "GPU_capabilities.h" #include "GPU_compute.h" +#include "GPU_material.h" #include "GPU_shader.h" #include "GPU_texture.h" #include "GPU_vertex_buffer.h" @@ -172,18 +173,23 @@ static void drw_hair_particle_cache_update_transform_feedback(ParticleHairCache } } -static ParticleHairCache *drw_hair_particle_cache_get( - Object *object, ParticleSystem *psys, ModifierData *md, int subdiv, int thickness_res) +static ParticleHairCache *drw_hair_particle_cache_get(Object *object, + ParticleSystem *psys, + ModifierData *md, + GPUMaterial *gpu_material, + int subdiv, + int thickness_res) { bool update; ParticleHairCache *cache; if (psys) { /* Old particle hair. */ - update = particles_ensure_procedural_data(object, psys, md, &cache, subdiv, thickness_res); + update = particles_ensure_procedural_data( + object, psys, md, &cache, gpu_material, subdiv, thickness_res); } else { /* New hair object. */ - update = hair_ensure_procedural_data(object, &cache, subdiv, thickness_res); + update = hair_ensure_procedural_data(object, &cache, gpu_material, subdiv, thickness_res); } if (update) { @@ -206,7 +212,8 @@ GPUVertBuf *DRW_hair_pos_buffer_get(Object *object, ParticleSystem *psys, Modifi int subdiv = scene->r.hair_subdiv; int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2; - ParticleHairCache *cache = drw_hair_particle_cache_get(object, psys, md, subdiv, thickness_res); + ParticleHairCache *cache = drw_hair_particle_cache_get( + object, psys, md, NULL, subdiv, thickness_res); return cache->final[subdiv].proc_buf; } @@ -248,7 +255,8 @@ void DRW_hair_duplimat_get(Object *object, DRWShadingGroup *DRW_shgroup_hair_create_sub(Object *object, ParticleSystem *psys, ModifierData *md, - DRWShadingGroup *shgrp_parent) + DRWShadingGroup *shgrp_parent, + GPUMaterial *gpu_material) { const DRWContextState *draw_ctx = DRW_context_state_get(); Scene *scene = draw_ctx->scene; @@ -258,7 +266,7 @@ DRWShadingGroup *DRW_shgroup_hair_create_sub(Object *object, int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2; ParticleHairCache *hair_cache = drw_hair_particle_cache_get( - object, psys, md, subdiv, thickness_res); + object, psys, md, gpu_material, subdiv, thickness_res); DRWShadingGroup *shgrp = DRW_shgroup_create_sub(shgrp_parent); @@ -308,6 +316,9 @@ DRWShadingGroup *DRW_shgroup_hair_create_sub(Object *object, } DRW_shgroup_uniform_texture(shgrp, "hairPointBuffer", hair_cache->final[subdiv].proc_tex); + if (hair_cache->length_tex) { + DRW_shgroup_uniform_texture(shgrp, "hairLen", hair_cache->length_tex); + } DRW_shgroup_uniform_int(shgrp, "hairStrandsRes", &hair_cache->final[subdiv].strands_res, 1); DRW_shgroup_uniform_int_copy(shgrp, "hairThicknessRes", thickness_res); DRW_shgroup_uniform_float_copy(shgrp, "hairRadShape", hair_rad_shape); diff --git a/source/blender/draw/intern/draw_hair_private.h b/source/blender/draw/intern/draw_hair_private.h index 1f58d8d0ead..289a1690fc6 100644 --- a/source/blender/draw/intern/draw_hair_private.h +++ b/source/blender/draw/intern/draw_hair_private.h @@ -66,6 +66,10 @@ typedef struct ParticleHairCache { GPUVertBuf *proc_strand_buf; GPUTexture *strand_tex; + /* Hair Length */ + GPUVertBuf *proc_length_buf; + GPUTexture *length_tex; + GPUVertBuf *proc_strand_seg_buf; GPUTexture *strand_seg_tex; @@ -93,11 +97,13 @@ bool particles_ensure_procedural_data(struct Object *object, struct ParticleSystem *psys, struct ModifierData *md, struct ParticleHairCache **r_hair_cache, + struct GPUMaterial *gpu_material, int subdiv, int thickness_res); bool hair_ensure_procedural_data(struct Object *object, struct ParticleHairCache **r_hair_cache, + struct GPUMaterial *gpu_material, int subdiv, int thickness_res); diff --git a/source/blender/draw/intern/shaders/common_hair_lib.glsl b/source/blender/draw/intern/shaders/common_hair_lib.glsl index 02c335ddae2..6cc7f09a852 100644 --- a/source/blender/draw/intern/shaders/common_hair_lib.glsl +++ b/source/blender/draw/intern/shaders/common_hair_lib.glsl @@ -210,6 +210,12 @@ void hair_get_pos_tan_binor_time(bool is_persp, } } +float hair_get_customdata_float(const samplerBuffer cd_buf) +{ + int id = hair_get_strand_id(); + return texelFetch(cd_buf, id).r; +} + vec2 hair_get_customdata_vec2(const samplerBuffer cd_buf) { int id = hair_get_strand_id(); diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index b12e0ae5cab..e1d046428a8 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -3087,7 +3087,10 @@ static size_t animdata_filter_dopesheet_movieclips(bAnimContext *ac, } /* Helper for animdata_filter_dopesheet() - For checking if an object should be included or not */ -static bool animdata_filter_base_is_ok(bDopeSheet *ads, Base *base, int filter_mode) +static bool animdata_filter_base_is_ok(bDopeSheet *ads, + Base *base, + const eObjectMode object_mode, + int filter_mode) { Object *ob = base->object; @@ -3144,10 +3147,21 @@ static bool animdata_filter_base_is_ok(bDopeSheet *ads, Base *base, int filter_m } /* check selection and object type filters */ - if ((ads->filterflag & ADS_FILTER_ONLYSEL) && - !((base->flag & BASE_SELECTED) /*|| (base == sce->basact) */)) { - /* only selected should be shown */ - return false; + if (ads->filterflag & ADS_FILTER_ONLYSEL) { + if (object_mode & OB_MODE_POSE) { + /* When in pose-mode handle all pose-mode objects. + * This avoids problems with pose-mode where objects may be unselected, + * where a selected bone of an unselected object would be hidden. see: T81922. */ + if (!(base->object->mode & object_mode)) { + return false; + } + } + else { + /* only selected should be shown (ignore active) */ + if (!(base->flag & BASE_SELECTED)) { + return false; + } + } } /* check if object belongs to the filtering group if option to filter @@ -3185,7 +3199,7 @@ static Base **animdata_filter_ds_sorted_bases(bDopeSheet *ads, Base **sorted_bases = MEM_mallocN(sizeof(Base *) * tot_bases, "Dopesheet Usable Sorted Bases"); LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (animdata_filter_base_is_ok(ads, base, filter_mode)) { + if (animdata_filter_base_is_ok(ads, base, OB_MODE_OBJECT, filter_mode)) { sorted_bases[num_bases++] = base; } } @@ -3278,8 +3292,10 @@ static size_t animdata_filter_dopesheet(bAnimContext *ac, /* Filter and add contents of each base (i.e. object) without them sorting first * NOTE: This saves performance in cases where order doesn't matter */ + Object *obact = OBACT(view_layer); + const eObjectMode object_mode = obact ? obact->mode : OB_MODE_OBJECT; LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (animdata_filter_base_is_ok(ads, base, filter_mode)) { + if (animdata_filter_base_is_ok(ads, base, object_mode, filter_mode)) { /* since we're still here, this object should be usable */ items += animdata_filter_dopesheet_ob(ac, anim_data, ads, base, filter_mode); } diff --git a/source/blender/editors/animation/anim_ipo_utils.c b/source/blender/editors/animation/anim_ipo_utils.c index 33b4882927a..6fe32699907 100644 --- a/source/blender/editors/animation/anim_ipo_utils.c +++ b/source/blender/editors/animation/anim_ipo_utils.c @@ -126,9 +126,10 @@ int getname_anim_fcurve(char *name, ID *id, FCurve *fcu) structname = RNA_struct_ui_name(ptr.type); } - /* For the VSE, a strip's 'Transform' or 'Crop' is a nested (under Sequence) struct, but - * displaying the struct name alone is no meaningful information (and also cannot be - * filtered well), same for modifiers. So display strip name alongside as well. */ + /* For the sequencer, a strip's 'Transform' or 'Crop' is a nested (under Sequence) + * struct, but displaying the struct name alone is no meaningful information + * (and also cannot be filtered well), same for modifiers. + * So display strip name alongside as well. */ if (GS(ptr.owner_id->name) == ID_SCE) { char stripname[256]; if (BLI_str_quoted_substr( diff --git a/source/blender/editors/animation/anim_ops.c b/source/blender/editors/animation/anim_ops.c index b4ea33920b2..3958c7f9e34 100644 --- a/source/blender/editors/animation/anim_ops.c +++ b/source/blender/editors/animation/anim_ops.c @@ -74,9 +74,16 @@ static bool change_frame_poll(bContext *C) * this shouldn't show up in 3D editor (or others without 2D timeline view) via search */ if (area) { - if (ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH)) { + if (ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP)) { return true; } + if (area->spacetype == SPACE_GRAPH) { + const SpaceGraph *sipo = area->spacedata.first; + /* Driver Editor's X axis is not time. */ + if (sipo->mode != SIPO_MODE_DRIVERS) { + return true; + } + } } CTX_wm_operator_poll_msg_set(C, "Expected an animation area to be active"); diff --git a/source/blender/editors/animation/drivers.c b/source/blender/editors/animation/drivers.c index bfaa76b3bf9..dbf379971fa 100644 --- a/source/blender/editors/animation/drivers.c +++ b/source/blender/editors/animation/drivers.c @@ -1115,7 +1115,7 @@ static int add_driver_button_invoke(bContext *C, wmOperator *op, const wmEvent * } /* 2) Show editing panel for setting up this driver */ - /* TODO: Use a different one from the editing popever, so we can have the single/all toggle? */ + /* TODO: Use a different one from the editing popover, so we can have the single/all toggle? */ UI_popover_panel_invoke(C, "GRAPH_PT_drivers_popover", true, op->reports); } diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c index ac7db9f4f46..e3ea8f0ab21 100644 --- a/source/blender/editors/animation/keyframes_draw.c +++ b/source/blender/editors/animation/keyframes_draw.c @@ -146,31 +146,31 @@ void draw_keyframe_shape(float x, /* Handle type to outline shape. */ switch (handle_type) { case KEYFRAME_HANDLE_AUTO_CLAMP: - flags = 0x2; + flags = GPU_KEYFRAME_SHAPE_CIRCLE; break; /* circle */ case KEYFRAME_HANDLE_AUTO: - flags = 0x12; + flags = GPU_KEYFRAME_SHAPE_CIRCLE | GPU_KEYFRAME_SHAPE_INNER_DOT; break; /* circle with dot */ case KEYFRAME_HANDLE_VECTOR: - flags = 0xC; + flags = GPU_KEYFRAME_SHAPE_SQUARE; break; /* square */ case KEYFRAME_HANDLE_ALIGNED: - flags = 0x5; + flags = GPU_KEYFRAME_SHAPE_DIAMOND | GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL; break; /* clipped diamond */ case KEYFRAME_HANDLE_FREE: default: - flags = 1; /* diamond */ + flags = GPU_KEYFRAME_SHAPE_DIAMOND; /* diamond */ } /* Extreme type to arrow-like shading. */ if (extreme_type & KEYFRAME_EXTREME_MAX) { - flags |= 0x100; + flags |= GPU_KEYFRAME_SHAPE_ARROW_END_MAX; } if (extreme_type & KEYFRAME_EXTREME_MIN) { - flags |= 0x200; + flags |= GPU_KEYFRAME_SHAPE_ARROW_END_MIN; } - if (extreme_type & KEYFRAME_EXTREME_MIXED) { + if (extreme_type & GPU_KEYFRAME_SHAPE_ARROW_END_MIXED) { flags |= 0x400; } } @@ -584,7 +584,7 @@ static void ED_keylist_draw_list_draw_keys(AnimKeylistDrawList *draw_list, View2 sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1); immBegin(GPU_PRIM_POINTS, visible_key_len); diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c index 8dc4aed9f0e..1ef7ee755ea 100644 --- a/source/blender/editors/animation/keyframing.c +++ b/source/blender/editors/animation/keyframing.c @@ -2107,10 +2107,12 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k return OPERATOR_CANCELLED; } + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "confirm_success"); + bool confirm = (prop != NULL && RNA_property_boolean_get(op->ptr, prop)); + if (num_channels > 0) { /* if the appropriate properties have been set, make a note that we've inserted something */ - PropertyRNA *prop = RNA_struct_find_property(op->ptr, "confirm_success"); - if (prop != NULL && RNA_property_boolean_get(op->ptr, prop)) { + if (confirm) { BKE_reportf(op->reports, RPT_INFO, "Successfully removed %d keyframes for keying set '%s'", @@ -2121,7 +2123,7 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k /* send notifiers that keyframes have been changed */ WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_REMOVED, NULL); } - else { + else if (confirm) { BKE_report(op->reports, RPT_WARNING, "Keying set failed to remove any keyframes"); } @@ -2289,6 +2291,8 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op) int selected_objects_success_len = 0; int success_multi = 0; + bool confirm = op->flag & OP_IS_INVOKE; + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { ID *id = &ob->id; int success = 0; @@ -2370,20 +2374,20 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op) /* report success (or failure) */ if (selected_objects_success_len) { - BKE_reportf(op->reports, - RPT_INFO, - "%d object(s) successfully had %d keyframes removed", - selected_objects_success_len, - success_multi); + if (confirm) { + BKE_reportf(op->reports, + RPT_INFO, + "%d object(s) successfully had %d keyframes removed", + selected_objects_success_len, + success_multi); + } + /* send updates */ + WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL); } - else { + else if (confirm) { BKE_reportf( op->reports, RPT_ERROR, "No keyframes removed from %d object(s)", selected_objects_len); } - - /* send updates */ - WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL); - return OPERATOR_FINISHED; } diff --git a/source/blender/editors/animation/keyingsets.c b/source/blender/editors/animation/keyingsets.c index 0206aabd359..e1fd3b07f46 100644 --- a/source/blender/editors/animation/keyingsets.c +++ b/source/blender/editors/animation/keyingsets.c @@ -610,7 +610,7 @@ void ANIM_keyingset_info_unregister(Main *bmain, KeyingSetInfo *ksi) KeyingSet *ks, *ksn; /* find relevant builtin KeyingSets which use this, and remove them */ - /* TODO: this isn't done now, since unregister is really only used atm when we + /* TODO: this isn't done now, since unregister is really only used at the moment when we * reload the scripts, which kindof defeats the purpose of "builtin"? */ for (ks = builtin_keyingsets.first; ks; ks = ksn) { ksn = ks->next; diff --git a/source/blender/editors/animation/time_scrub_ui.c b/source/blender/editors/animation/time_scrub_ui.c index 8aeb6a57124..b0eb014c5d9 100644 --- a/source/blender/editors/animation/time_scrub_ui.c +++ b/source/blender/editors/animation/time_scrub_ui.c @@ -91,8 +91,7 @@ static void draw_current_frame(const Scene *scene, bool display_seconds, const View2D *v2d, const rcti *scrub_region_rect, - int current_frame, - bool draw_line) + int current_frame) { const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; int frame_x = UI_view2d_view_to_region_x(v2d, current_frame); @@ -106,21 +105,18 @@ static void draw_current_frame(const Scene *scene, float bg_color[4]; UI_GetThemeColorShade4fv(TH_CFRAME, -5, bg_color); - if (draw_line) { - /* Draw vertical line to from the bottom of the current frame box to the bottom of the screen. - */ - const float subframe_x = UI_view2d_view_to_region_x(v2d, BKE_scene_ctime_get(scene)); - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformThemeColor(TH_CFRAME); - immRectf(pos, - subframe_x - U.pixelsize, - scrub_region_rect->ymax - box_padding, - subframe_x + U.pixelsize, - 0.0f); - immUnbindProgram(); - } + /* Draw vertical line from the bottom of the current frame box to the bottom of the screen. */ + const float subframe_x = UI_view2d_view_to_region_x(v2d, BKE_scene_ctime_get(scene)); + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immUniformThemeColor(TH_CFRAME); + immRectf(pos, + subframe_x - U.pixelsize, + scrub_region_rect->ymax - box_padding, + subframe_x + U.pixelsize, + 0.0f); + immUnbindProgram(); UI_draw_roundbox_corner_set(UI_CNR_ALL); @@ -152,8 +148,7 @@ static void draw_current_frame(const Scene *scene, void ED_time_scrub_draw_current_frame(const ARegion *region, const Scene *scene, - bool display_seconds, - bool draw_line) + bool display_seconds) { const View2D *v2d = ®ion->v2d; GPU_matrix_push_projection(); @@ -162,7 +157,7 @@ void ED_time_scrub_draw_current_frame(const ARegion *region, rcti scrub_region_rect; get_time_scrub_region_rect(region, &scrub_region_rect); - draw_current_frame(scene, display_seconds, v2d, &scrub_region_rect, scene->r.cfra, draw_line); + draw_current_frame(scene, display_seconds, v2d, &scrub_region_rect, scene->r.cfra); GPU_matrix_pop_projection(); } diff --git a/source/blender/editors/armature/pose_transform.c b/source/blender/editors/armature/pose_transform.c index 3798ca308ed..279f79ac44b 100644 --- a/source/blender/editors/armature/pose_transform.c +++ b/source/blender/editors/armature/pose_transform.c @@ -1184,7 +1184,7 @@ static int pose_clear_transform_generic_exec(bContext *C, ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { - /* XXX: UGLY HACK (for autokey + clear transforms) */ + /* XXX: UGLY HACK (for auto-key + clear transforms). */ Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter); ListBase dsources = {NULL, NULL}; bool changed = false; diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index 31c07580570..b6657bfca63 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -31,6 +31,7 @@ set(INC_SYS ) set(SRC + intern/asset_catalog.cc intern/asset_filter.cc intern/asset_handle.cc intern/asset_library_reference.cc @@ -40,6 +41,7 @@ set(SRC intern/asset_ops.cc intern/asset_temp_id_consumer.cc + ED_asset_catalog.hh ED_asset_filter.h ED_asset_handle.h ED_asset_library.h diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh new file mode 100644 index 00000000000..cffd7728a60 --- /dev/null +++ b/source/blender/editors/asset/ED_asset_catalog.hh @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edasset + */ + +#pragma once + +#include "BKE_asset_catalog.hh" + +#include "BLI_string_ref.hh" + +struct AssetLibrary; +namespace blender::bke { +class AssetCatalog; +} // namespace blender::bke + +blender::bke::AssetCatalog *ED_asset_catalog_add(AssetLibrary *library, + blender::StringRefNull name, + blender::StringRef parent_path = nullptr); +void ED_asset_catalog_remove(AssetLibrary *library, const blender::bke::CatalogID &catalog_id); diff --git a/source/blender/editors/asset/ED_asset_mark_clear.h b/source/blender/editors/asset/ED_asset_mark_clear.h index 7524ec6b02a..d8b8f15a109 100644 --- a/source/blender/editors/asset/ED_asset_mark_clear.h +++ b/source/blender/editors/asset/ED_asset_mark_clear.h @@ -27,7 +27,22 @@ extern "C" { struct ID; struct bContext; +/** + * Mark the datablock as asset. + * + * To ensure the datablock is saved, this sets Fake User. + * + * \return whether the datablock was marked as asset; false when it is not capable of becoming an + * asset, or when it already was an asset. */ bool ED_asset_mark_id(const struct bContext *C, struct ID *id); + +/** + * Remove the asset metadata, turning the ID into a "normal" ID. + * + * This clears the Fake User. If for some reason the datablock is meant to be saved anyway, the + * caller is responsible for explicitly setting the Fake User. + * + * \return whether the asset metadata was actually removed; false when the ID was not an asset. */ bool ED_asset_clear_id(struct ID *id); bool ED_asset_can_mark_single_from_context(const struct bContext *C); diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc new file mode 100644 index 00000000000..6e49ca2dd5c --- /dev/null +++ b/source/blender/editors/asset/intern/asset_catalog.cc @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edasset + */ + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_catalog_path.hh" +#include "BKE_asset_library.hh" + +#include "BLI_string_utils.h" + +#include "ED_asset_catalog.hh" + +using namespace blender; +using namespace blender::bke; + +struct CatalogUniqueNameFnData { + const AssetCatalogService &catalog_service; + StringRef parent_path; +}; + +static bool catalog_name_exists_fn(void *arg, const char *name) +{ + CatalogUniqueNameFnData &fn_data = *static_cast<CatalogUniqueNameFnData *>(arg); + AssetCatalogPath fullpath = AssetCatalogPath(fn_data.parent_path) / name; + return fn_data.catalog_service.find_catalog_by_path(fullpath); +} + +static std::string catalog_name_ensure_unique(AssetCatalogService &catalog_service, + StringRefNull name, + StringRef parent_path) +{ + CatalogUniqueNameFnData fn_data = {catalog_service, parent_path}; + + char unique_name[MAX_NAME] = ""; + BLI_uniquename_cb( + catalog_name_exists_fn, &fn_data, name.c_str(), '.', unique_name, sizeof(unique_name)); + + return unique_name; +} + +AssetCatalog *ED_asset_catalog_add(::AssetLibrary *library, + StringRefNull name, + StringRef parent_path) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + return nullptr; + } + + std::string unique_name = catalog_name_ensure_unique(*catalog_service, name, parent_path); + AssetCatalogPath fullpath = AssetCatalogPath(parent_path) / unique_name; + + return catalog_service->create_catalog(fullpath); +} + +void ED_asset_catalog_remove(::AssetLibrary *library, const CatalogID &catalog_id) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + BLI_assert_unreachable(); + return; + } + + catalog_service->delete_catalog(catalog_id); +} diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc index 57c25e1614b..400b3572c9b 100644 --- a/source/blender/editors/asset/intern/asset_list.cc +++ b/source/blender/editors/asset/intern/asset_list.cc @@ -157,7 +157,7 @@ void AssetList::setup() /* Relevant bits from file_refresh(). */ /* TODO pass options properly. */ - filelist_setrecursion(files, 1); + filelist_setrecursion(files, FILE_SELECT_MAX_RECURSIONS); filelist_setsorting(files, FILE_SORT_ALPHA, false); filelist_setlibrary(files, &library_ref_); filelist_setfilter_options( @@ -390,7 +390,7 @@ std::optional<eFileSelectType> AssetListStorage::asset_library_reference_to_file { switch (library_reference.type) { case ASSET_LIBRARY_CUSTOM: - return FILE_LOADLIB; + return FILE_ASSET_LIBRARY; case ASSET_LIBRARY_LOCAL: return FILE_MAIN_ASSET; } diff --git a/source/blender/editors/asset/intern/asset_mark_clear.cc b/source/blender/editors/asset/intern/asset_mark_clear.cc index ba348e38823..8290124c209 100644 --- a/source/blender/editors/asset/intern/asset_mark_clear.cc +++ b/source/blender/editors/asset/intern/asset_mark_clear.cc @@ -67,8 +67,7 @@ bool ED_asset_clear_id(ID *id) return false; } BKE_asset_metadata_free(&id->asset_data); - /* Don't clear fake user here, there's no guarantee that it was actually set by - * #ED_asset_mark_id(), it might have been something/someone else. */ + id_fake_user_clear(id); /* Important for asset storage to update properly! */ ED_assetlist_storage_tag_main_data_dirty(); diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index d69a2cae94d..5424bae77b4 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -18,27 +18,55 @@ * \ingroup edasset */ +#include "BKE_asset_catalog.hh" #include "BKE_context.h" +#include "BKE_lib_id.h" #include "BKE_report.h" +#include "BLI_string_ref.hh" #include "BLI_vector.hh" #include "ED_asset.h" +#include "ED_asset_catalog.hh" +/* XXX needs access to the file list, should all be done via the asset system in future. */ +#include "ED_fileselect.h" #include "RNA_access.h" +#include "RNA_define.h" #include "WM_api.h" #include "WM_types.h" +using namespace blender; + /* -------------------------------------------------------------------- */ using PointerRNAVec = blender::Vector<PointerRNA>; +static PointerRNAVec asset_operation_get_ids_from_context(const bContext *C); +static PointerRNAVec asset_operation_get_nonexperimental_ids_from_context(const bContext *C); +static bool asset_type_is_nonexperimental(const ID_Type id_type); + static bool asset_operation_poll(bContext * /*C*/) { + /* At this moment only the pose library is non-experimental. Still, directly marking arbitrary + * Actions as asset is not part of the stable functionality; instead, the pose library "Create + * Pose Asset" operator should be used. Actions can still be marked as asset via + * `the_action.asset_mark()` (so a function call instead of this operator), which is what the + * pose library uses internally. */ return U.experimental.use_extended_asset_browser; } +static bool asset_clear_poll(bContext *C) +{ + if (asset_operation_poll(C)) { + return true; + } + + PointerRNAVec pointers = asset_operation_get_nonexperimental_ids_from_context(C); + return !pointers.is_empty(); +} + /** * Return the IDs to operate on as PointerRNA vector. Either a single one ("id" context member) or * multiple ones ("selected_ids" context member). @@ -64,6 +92,28 @@ static PointerRNAVec asset_operation_get_ids_from_context(const bContext *C) return ids; } +static PointerRNAVec asset_operation_get_nonexperimental_ids_from_context(const bContext *C) +{ + PointerRNAVec nonexperimental; + PointerRNAVec pointers = asset_operation_get_ids_from_context(C); + for (PointerRNA &ptr : pointers) { + BLI_assert(RNA_struct_is_ID(ptr.type)); + + ID *id = static_cast<ID *>(ptr.data); + if (asset_type_is_nonexperimental(GS(id->name))) { + nonexperimental.append(ptr); + } + } + return nonexperimental; +} + +static bool asset_type_is_nonexperimental(const ID_Type id_type) +{ + /* At this moment only the pose library is non-experimental. For simplicity, allow asset + * operations on all Action datablocks (even though pose assets are limited to single frames). */ + return ELEM(id_type, ID_AC); +} + /* -------------------------------------------------------------------- */ class AssetMarkHelper { @@ -166,7 +216,13 @@ static void ASSET_OT_mark(wmOperatorType *ot) /* -------------------------------------------------------------------- */ class AssetClearHelper { + const bool set_fake_user_; + public: + AssetClearHelper(const bool set_fake_user) : set_fake_user_(set_fake_user) + { + } + void operator()(PointerRNAVec &ids); void reportResults(const bContext *C, ReportList &reports) const; @@ -191,10 +247,16 @@ void AssetClearHelper::operator()(PointerRNAVec &ids) continue; } - if (ED_asset_clear_id(id)) { - stats.tot_cleared++; - stats.last_id = id; + if (!ED_asset_clear_id(id)) { + continue; } + + if (set_fake_user_) { + id_fake_user_set(id); + } + + stats.tot_cleared++; + stats.last_id = id; } } @@ -234,7 +296,8 @@ static int asset_clear_exec(bContext *C, wmOperator *op) { PointerRNAVec ids = asset_operation_get_ids_from_context(C); - AssetClearHelper clear_helper; + const bool set_fake_user = RNA_boolean_get(op->ptr, "set_fake_user"); + AssetClearHelper clear_helper(set_fake_user); clear_helper(ids); clear_helper.reportResults(C, *op->reports); @@ -248,18 +311,39 @@ static int asset_clear_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static char *asset_clear_get_description(struct bContext *UNUSED(C), + struct wmOperatorType *UNUSED(op), + struct PointerRNA *values) +{ + const bool set_fake_user = RNA_boolean_get(values, "set_fake_user"); + if (!set_fake_user) { + return nullptr; + } + + return BLI_strdup( + "Delete all asset metadata, turning the selected asset data-blocks back into normal " + "data-blocks, and set Fake User to ensure the data-blocks will still be saved"); +} + static void ASSET_OT_clear(wmOperatorType *ot) { ot->name = "Clear Asset"; ot->description = "Delete all asset metadata and turn the selected asset data-blocks back into normal " "data-blocks"; + ot->get_description = asset_clear_get_description; ot->idname = "ASSET_OT_clear"; ot->exec = asset_clear_exec; - ot->poll = asset_operation_poll; + ot->poll = asset_clear_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, + "set_fake_user", + false, + "Set Fake User", + "Ensure the data-block is saved, even when it is no longer marked as asset"); } /* -------------------------------------------------------------------- */ @@ -295,10 +379,91 @@ static void ASSET_OT_list_refresh(struct wmOperatorType *ot) /* -------------------------------------------------------------------- */ +static bool asset_catalog_operator_poll(bContext *C) +{ + const SpaceFile *sfile = CTX_wm_space_file(C); + return asset_operation_poll(C) && sfile && ED_fileselect_active_asset_library_get(sfile); +} + +static int asset_catalog_new_exec(bContext *C, wmOperator *op) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); + char *parent_path = RNA_string_get_alloc(op->ptr, "parent_path", nullptr, 0, nullptr); + + ED_asset_catalog_add(asset_library, "Catalog", parent_path); + + MEM_freeN(parent_path); + + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + + return OPERATOR_FINISHED; +} + +static void ASSET_OT_catalog_new(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "New Asset Catalog"; + ot->description = "Create a new catalog to put assets in"; + ot->idname = "ASSET_OT_catalog_new"; + + /* api callbacks */ + ot->exec = asset_catalog_new_exec; + ot->poll = asset_catalog_operator_poll; + + RNA_def_string(ot->srna, + "parent_path", + nullptr, + 0, + "Parent Path", + "Optional path defining the location to put the new catalog under"); +} + +static int asset_catalog_delete_exec(bContext *C, wmOperator *op) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); + char *catalog_id_str = RNA_string_get_alloc(op->ptr, "catalog_id", nullptr, 0, nullptr); + bke::CatalogID catalog_id; + if (!BLI_uuid_parse_string(&catalog_id, catalog_id_str)) { + return OPERATOR_CANCELLED; + } + + ED_asset_catalog_remove(asset_library, catalog_id); + + MEM_freeN(catalog_id_str); + + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + + return OPERATOR_FINISHED; +} + +static void ASSET_OT_catalog_delete(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Delete Asset Catalog"; + ot->description = + "Remove an asset catalog from the asset library (contained assets will not be affected and " + "show up as unassigned)"; + ot->idname = "ASSET_OT_catalog_delete"; + + /* api callbacks */ + ot->exec = asset_catalog_delete_exec; + ot->invoke = WM_operator_confirm; + ot->poll = asset_catalog_operator_poll; + + RNA_def_string(ot->srna, "catalog_id", nullptr, 0, "Catalog ID", "ID of the catalog to delete"); +} + +/* -------------------------------------------------------------------- */ + void ED_operatortypes_asset(void) { WM_operatortype_append(ASSET_OT_mark); WM_operatortype_append(ASSET_OT_clear); + WM_operatortype_append(ASSET_OT_catalog_new); + WM_operatortype_append(ASSET_OT_catalog_delete); + WM_operatortype_append(ASSET_OT_list_refresh); } diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index 68e2aece6e2..59ea105fbbb 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -2104,7 +2104,7 @@ static void annotation_draw_apply_event( p->flags |= GP_PAINTFLAG_USE_STABILIZER_TEMP; } } - /* We are using the temporal stabilizer flag atm, + /* We are using the temporal stabilizer flag at the moment, * but shift is not pressed as well as the permanent flag is not used, * so we don't need the cursor anymore. */ else if (p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) { diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 75ddfa47c57..1f31c60367e 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -4729,7 +4729,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) /* Remove unused slots. */ int actcol = ob_dst->actcol; for (int slot = 1; slot <= ob_dst->totcol; slot++) { - while (slot <= ob_dst->totcol && !BKE_object_material_slot_used(ob_dst->data, slot)) { + while (slot <= ob_dst->totcol && !BKE_object_material_slot_used(ob_dst, slot)) { ob_dst->actcol = slot; BKE_object_material_slot_remove(bmain, ob_dst); if (actcol >= slot) { diff --git a/source/blender/editors/gpencil/gpencil_mesh.c b/source/blender/editors/gpencil/gpencil_mesh.c index 0939d53736b..079089786d0 100644 --- a/source/blender/editors/gpencil/gpencil_mesh.c +++ b/source/blender/editors/gpencil/gpencil_mesh.c @@ -345,7 +345,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op) /* Remove unused materials. */ int actcol = ob_gpencil->actcol; for (int slot = 1; slot <= ob_gpencil->totcol; slot++) { - while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil->data, slot)) { + while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) { ob_gpencil->actcol = slot; BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 9b157224178..957d8087bbd 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1001,8 +1001,6 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); gps->dvert = NULL; - /* drawing batch cache is dirty now */ - gpencil_update_cache(p->gpd); /* set pointer to first non-initialized point */ pt = gps->points + (gps->totpoints - totelem); if (gps->dvert != NULL) { diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h index 6a0a42ee77b..e9601220f2e 100644 --- a/source/blender/editors/include/ED_anim_api.h +++ b/source/blender/editors/include/ED_anim_api.h @@ -861,7 +861,7 @@ void ED_operatormacros_action(void); /* XXX: Should we be doing these here, or at all? */ /* Action Editor - Action Management */ -struct AnimData *ED_actedit_animdata_from_context(struct bContext *C); +struct AnimData *ED_actedit_animdata_from_context(struct bContext *C, struct ID **r_adt_id_owner); void ED_animedit_unlink_action(struct bContext *C, struct ID *id, struct AnimData *adt, diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h index 82057c726a5..423d619f41a 100644 --- a/source/blender/editors/include/ED_fileselect.h +++ b/source/blender/editors/include/ED_fileselect.h @@ -142,6 +142,7 @@ void ED_fileselect_exit(struct wmWindowManager *wm, struct SpaceFile *sfile); bool ED_fileselect_is_file_browser(const struct SpaceFile *sfile); bool ED_fileselect_is_asset_browser(const struct SpaceFile *sfile); +struct AssetLibrary *ED_fileselect_active_asset_library_get(const struct SpaceFile *sfile); struct ID *ED_fileselect_active_asset_get(const struct SpaceFile *sfile); /* Activate the file that corresponds to the given ID. diff --git a/source/blender/editors/include/ED_image.h b/source/blender/editors/include/ED_image.h index 6b0b9f4a27c..9532035a1cd 100644 --- a/source/blender/editors/include/ED_image.h +++ b/source/blender/editors/include/ED_image.h @@ -41,6 +41,16 @@ struct SpaceImage; struct bContext; struct wmOperator; struct wmWindowManager; +struct View2D; + +/* image_draw.c */ +float ED_space_image_zoom_level(const struct View2D *v2d, const int grid_dimension); +void ED_space_image_grid_steps(struct SpaceImage *sima, + float grid_steps[SI_GRID_STEPS_LEN], + const int grid_dimension); +float ED_space_image_increment_snap_value(const int grid_dimesnions, + const float grid_steps[SI_GRID_STEPS_LEN], + const float zoom_factor); /* image_edit.c, exported for transform */ struct Image *ED_space_image(struct SpaceImage *sima); diff --git a/source/blender/editors/include/ED_keyframes_draw.h b/source/blender/editors/include/ED_keyframes_draw.h index 61e37f20b1b..6a7037c3eed 100644 --- a/source/blender/editors/include/ED_keyframes_draw.h +++ b/source/blender/editors/include/ED_keyframes_draw.h @@ -41,7 +41,7 @@ struct bDopeSheet; struct bGPDlayer; /* draw simple diamond-shape keyframe */ -/* caller should set up vertex format, bind GPU_SHADER_KEYFRAME_DIAMOND, +/* caller should set up vertex format, bind GPU_SHADER_KEYFRAME_SHAPE, * immBegin(GPU_PRIM_POINTS, n), then call this n times */ typedef struct KeyframeShaderBindings { uint pos_id; diff --git a/source/blender/editors/include/ED_text.h b/source/blender/editors/include/ED_text.h index 2284c82b3d5..6e012ec1a91 100644 --- a/source/blender/editors/include/ED_text.h +++ b/source/blender/editors/include/ED_text.h @@ -34,6 +34,8 @@ struct UndoStep; struct UndoType; struct bContext; +bool ED_text_activate_in_screen(struct bContext *C, struct Text *text); + void ED_text_scroll_to_cursor(struct SpaceText *st, struct ARegion *region, bool center); bool ED_text_region_location_from_cursor(struct SpaceText *st, diff --git a/source/blender/editors/include/ED_time_scrub_ui.h b/source/blender/editors/include/ED_time_scrub_ui.h index 6420aaf5ef0..812cb31c9b0 100644 --- a/source/blender/editors/include/ED_time_scrub_ui.h +++ b/source/blender/editors/include/ED_time_scrub_ui.h @@ -33,8 +33,7 @@ struct wmEvent; void ED_time_scrub_draw_current_frame(const struct ARegion *region, const struct Scene *scene, - bool display_seconds, - bool draw_line); + bool display_seconds); void ED_time_scrub_draw(const struct ARegion *region, const struct Scene *scene, diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h index ea3d921f2c5..f3aba12a924 100644 --- a/source/blender/editors/include/ED_uvedit.h +++ b/source/blender/editors/include/ED_uvedit.h @@ -237,6 +237,18 @@ void ED_image_draw_cursor(struct ARegion *region, const float cursor[2]); void ED_uvedit_buttons_register(struct ARegionType *art); /* uvedit_islands.c */ + +struct UVMapUDIM_Params { + const struct Image *image; + /** Copied from #SpaceImage.tile_grid_shape */ + int grid_shape[2]; + bool use_target_udim; + int target_udim; +}; +bool ED_uvedit_udim_params_from_image_space(const struct SpaceImage *sima, + bool use_active, + struct UVMapUDIM_Params *udim_params); + struct UVPackIsland_Params { uint rotate : 1; /** -1 not to align to axis, otherwise 0,1 for X,Y. */ @@ -246,9 +258,14 @@ struct UVPackIsland_Params { uint use_seams : 1; uint correct_aspect : 1; }; + +bool uv_coords_isect_udim(const struct Image *image, + const int udim_grid[2], + const float coords[2]); void ED_uvedit_pack_islands_multi(const struct Scene *scene, Object **objects, const uint objects_len, + const struct UVMapUDIM_Params *udim_params, const struct UVPackIsland_Params *params); #ifdef __cplusplus diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h index ddd9ca4a98c..8a7df5b54ff 100644 --- a/source/blender/editors/include/UI_icons.h +++ b/source/blender/editors/include/UI_icons.h @@ -989,6 +989,16 @@ DEF_ICON_VECTOR(COLLECTION_COLOR_06) DEF_ICON_VECTOR(COLLECTION_COLOR_07) DEF_ICON_VECTOR(COLLECTION_COLOR_08) +DEF_ICON_VECTOR(SEQUENCE_COLOR_01) +DEF_ICON_VECTOR(SEQUENCE_COLOR_02) +DEF_ICON_VECTOR(SEQUENCE_COLOR_03) +DEF_ICON_VECTOR(SEQUENCE_COLOR_04) +DEF_ICON_VECTOR(SEQUENCE_COLOR_05) +DEF_ICON_VECTOR(SEQUENCE_COLOR_06) +DEF_ICON_VECTOR(SEQUENCE_COLOR_07) +DEF_ICON_VECTOR(SEQUENCE_COLOR_08) +DEF_ICON_VECTOR(SEQUENCE_COLOR_09) + /* Events. */ DEF_ICON_COLOR(EVENT_A) DEF_ICON_COLOR(EVENT_B) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 0f74d81f7bb..59d688ad2d0 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -84,6 +84,10 @@ typedef struct uiBlock uiBlock; typedef struct uiBut uiBut; typedef struct uiLayout uiLayout; typedef struct uiPopupBlockHandle uiPopupBlockHandle; +/* C handle for C++ #ui::AbstractTreeView type. */ +typedef struct uiTreeViewHandle uiTreeViewHandle; +/* C handle for C++ #ui::AbstractTreeViewItem type. */ +typedef struct uiTreeViewItemHandle uiTreeViewItemHandle; /* Defines */ @@ -389,6 +393,8 @@ typedef enum { UI_BTYPE_GRIP = 57 << 9, UI_BTYPE_DECORATOR = 58 << 9, UI_BTYPE_DATASETROW = 59 << 9, + /* An item in a tree view. Parent items may be collapsible. */ + UI_BTYPE_TREEROW = 60 << 9, } eButType; #define BUTTYPE (63 << 9) @@ -1672,6 +1678,7 @@ void UI_but_datasetrow_component_set(uiBut *but, uint8_t geometry_component_type void UI_but_datasetrow_domain_set(uiBut *but, uint8_t attribute_domain); uint8_t UI_but_datasetrow_component_get(uiBut *but); uint8_t UI_but_datasetrow_domain_get(uiBut *but); +void UI_but_treerow_indentation_set(uiBut *but, int indentation); void UI_but_node_link_set(uiBut *but, struct bNodeSocket *socket, const float draw_color[4]); @@ -2587,6 +2594,7 @@ typedef struct uiDragColorHandle { void ED_operatortypes_ui(void); void ED_keymap_ui(struct wmKeyConfig *keyconf); +void ED_dropboxes_ui(void); void ED_uilisttypes_ui(void); void UI_drop_color_copy(struct wmDrag *drag, struct wmDropBox *drop); @@ -2755,6 +2763,17 @@ void UI_interface_tag_script_reload(void); /* Support click-drag motion which presses the button and closes a popover (like a menu). */ #define USE_UI_POPOVER_ONCE +bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item); +bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b); +bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const struct wmDrag *drag); +bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const struct ListBase *drags); +char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, + const struct bContext *C, + const struct wmDrag *drag, + const struct wmEvent *event); + +uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, int x, int y); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh new file mode 100644 index 00000000000..4a583d0225e --- /dev/null +++ b/source/blender/editors/include/UI_interface.hh @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup editorui + */ + +#pragma once + +#include <memory> + +#include "BLI_string_ref.hh" + +struct uiBlock; +namespace blender::ui { +class AbstractTreeView; +} + +blender::ui::AbstractTreeView *UI_block_add_view( + uiBlock &block, + blender::StringRef idname, + std::unique_ptr<blender::ui::AbstractTreeView> tree_view); diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh new file mode 100644 index 00000000000..d36e688dd65 --- /dev/null +++ b/source/blender/editors/include/UI_tree_view.hh @@ -0,0 +1,257 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup editorui + */ + +#pragma once + +#include <functional> +#include <memory> +#include <string> + +#include "BLI_function_ref.hh" +#include "BLI_vector.hh" + +#include "UI_resources.h" + +struct bContext; +struct PointerRNA; +struct uiBlock; +struct uiBut; +struct uiButTreeRow; +struct uiLayout; +struct wmEvent; +struct wmDrag; + +namespace blender::ui { + +class AbstractTreeView; +class AbstractTreeViewItem; + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Item Container + * \{ */ + +/** + * Helper base class to expose common child-item data and functionality to both #AbstractTreeView + * and #AbstractTreeViewItem. + * + * That means this type can be used whenever either a #AbstractTreeView or a + * #AbstractTreeViewItem is needed. + */ +class TreeViewItemContainer { + friend class AbstractTreeView; + friend class AbstractTreeViewItem; + + /* Private constructor, so only the friends above can create this! */ + TreeViewItemContainer() = default; + + protected: + Vector<std::unique_ptr<AbstractTreeViewItem>> children_; + /** Adding the first item to the root will set this, then it's passed on to all children. */ + TreeViewItemContainer *root_ = nullptr; + /** Pointer back to the owning item. */ + AbstractTreeViewItem *parent_ = nullptr; + + public: + enum class IterOptions { + None = 0, + SkipCollapsed = 1 << 0, + + /* Keep ENUM_OPERATORS() below updated! */ + }; + using ItemIterFn = FunctionRef<void(AbstractTreeViewItem &)>; + + /** + * Convenience wrapper taking the arguments needed to construct an item of type \a ItemT. Calls + * the version just below. + */ + template<class ItemT, typename... Args> ItemT &add_tree_item(Args &&...args) + { + static_assert(std::is_base_of<AbstractTreeViewItem, ItemT>::value, + "Type must derive from and implement the AbstractTreeViewItem interface"); + + return dynamic_cast<ItemT &>( + add_tree_item(std::make_unique<ItemT>(std::forward<Args>(args)...))); + } + + AbstractTreeViewItem &add_tree_item(std::unique_ptr<AbstractTreeViewItem> item); + + protected: + void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; +}; + +ENUM_OPERATORS(TreeViewItemContainer::IterOptions, + TreeViewItemContainer::IterOptions::SkipCollapsed); + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Builders + * \{ */ + +class TreeViewBuilder { + uiBlock &block_; + + public: + TreeViewBuilder(uiBlock &block); + + void build_tree_view(AbstractTreeView &tree_view); +}; + +class TreeViewLayoutBuilder { + uiBlock &block_; + + friend TreeViewBuilder; + + public: + void build_row(AbstractTreeViewItem &item) const; + uiBlock &block() const; + uiLayout *current_layout() const; + + private: + /* Created through #TreeViewBuilder. */ + TreeViewLayoutBuilder(uiBlock &block); +}; + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Base Class + * \{ */ + +class AbstractTreeView : public TreeViewItemContainer { + friend TreeViewBuilder; + friend TreeViewLayoutBuilder; + + public: + virtual ~AbstractTreeView() = default; + + void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; + + protected: + virtual void build_tree() = 0; + + private: + /** Match the tree-view against an earlier version of itself (if any) and copy the old UI state + * (e.g. collapsed, active, selected) to the new one. See + * #AbstractTreeViewItem.update_from_old(). */ + void update_from_old(uiBlock &new_block); + static void update_children_from_old_recursive(const TreeViewItemContainer &new_items, + const TreeViewItemContainer &old_items); + static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item, + const TreeViewItemContainer &items); + void build_layout_from_tree(const TreeViewLayoutBuilder &builder); +}; + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Item Type + * \{ */ + +/** \brief Abstract base class for defining a customizable tree-view item. + * + * The tree-view item defines how to build its data into a tree-row. There are implementations for + * common layouts, e.g. #BasicTreeViewItem. + * It also stores state information that needs to be persistent over redraws, like the collapsed + * state. + */ +class AbstractTreeViewItem : public TreeViewItemContainer { + friend class AbstractTreeView; + + bool is_open_ = false; + bool is_active_ = false; + + protected: + /** This label is used for identifying an item (together with its parent's labels). */ + std::string label_{}; + + public: + virtual ~AbstractTreeViewItem() = default; + + virtual void build_row(uiLayout &row) = 0; + + virtual void on_activate(); + virtual bool on_drop(const wmDrag &drag); + virtual bool can_drop(const wmDrag &drag) const; + /** Custom text to display when dragging over a tree item. Should explain what happens when + * dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop() + * returns true, so the implementing override doesn't have to check that again. + * The returned value must be a translated string. */ + virtual std::string drop_tooltip(const bContext &C, + const wmDrag &drag, + const wmEvent &event) const; + + /** Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of + * the last redraw to this item. If sub-classes introduce more advanced state they should + * override this and make it update their state accordingly. */ + virtual void update_from_old(const AbstractTreeViewItem &old); + /** Compare this item to \a other to check if they represent the same data. This is critical for + * being able to recognize an item from a previous redraw, to be able to keep its state (e.g. + * open/closed, active, etc.). Items are only matched if their parents also match. + * By default this just matches the items names/labels (if their parents match!). If that isn't + * good enough for a sub-class, that can override it. */ + virtual bool matches(const AbstractTreeViewItem &other) const; + + const AbstractTreeView &get_tree_view() const; + int count_parents() const; + void set_active(bool value = true); + bool is_active() const; + void toggle_collapsed(); + bool is_collapsed() const; + void set_collapsed(bool collapsed); + bool is_collapsible() const; +}; + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Predefined Tree-View Item Types + * + * Common, Basic Tree-View Item Types. + * \{ */ + +/** + * The most basic type, just a label with an icon. + */ +class BasicTreeViewItem : public AbstractTreeViewItem { + public: + using ActivateFn = std::function<void(BasicTreeViewItem &new_active)>; + BIFIconID icon; + + BasicTreeViewItem(StringRef label, BIFIconID icon = ICON_NONE, ActivateFn activate_fn = nullptr); + + void build_row(uiLayout &row) override; + void on_activate() override; + + protected: + /** Created in the #build() function. */ + uiButTreeRow *tree_row_but_ = nullptr; + /** Optionally passed to the #BasicTreeViewItem constructor. Called when activating this tree + * view item. This way users don't have to sub-class #BasicTreeViewItem, just to implement + * custom activation behavior (a common thing to do). */ + ActivateFn activate_fn_; + + uiBut *button(); + BIFIconID get_draw_icon() const; +}; + +/** \} */ + +} // namespace blender::ui diff --git a/source/blender/editors/include/UI_view2d.h b/source/blender/editors/include/UI_view2d.h index e3c02b4c249..fdfa07a7e02 100644 --- a/source/blender/editors/include/UI_view2d.h +++ b/source/blender/editors/include/UI_view2d.h @@ -123,6 +123,7 @@ void UI_view2d_region_reinit(struct View2D *v2d, short type, int winx, int winy) void UI_view2d_curRect_validate(struct View2D *v2d); void UI_view2d_curRect_reset(struct View2D *v2d); +bool UI_view2d_area_supports_sync(struct ScrArea *area); void UI_view2d_sync(struct bScreen *screen, struct ScrArea *area, struct View2D *v2dcur, int flag); /* Perform all required updates after `v2d->cur` as been modified. diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 64c874e26a9..f27ff9694c2 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -42,6 +42,7 @@ set(SRC interface_button_group.c interface_context_menu.c interface_draw.c + interface_dropboxes.cc interface_eyedropper.c interface_eyedropper_color.c interface_eyedropper_colorband.c @@ -73,8 +74,10 @@ set(SRC interface_templates.c interface_undo.c interface_utils.c + interface_view.cc interface_widgets.c resources.c + tree_view.cc view2d.c view2d_draw.c view2d_edge_pan.c diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index fd75be5b847..c53bffca778 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -743,6 +743,15 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) return false; } + if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) { + uiButTreeRow *but_treerow = (uiButTreeRow *)but; + uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut; + if (!but_treerow->tree_item || !oldbut_treerow->tree_item || + !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) { + return false; + } + } + return true; } @@ -856,10 +865,21 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) oldbut->hardmax = but->hardmax; } - if (oldbut->type == UI_BTYPE_PROGRESS_BAR) { - uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; - uiButProgressbar *progress_but = (uiButProgressbar *)but; - progress_oldbut->progress = progress_but->progress; + switch (oldbut->type) { + case UI_BTYPE_PROGRESS_BAR: { + uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; + uiButProgressbar *progress_but = (uiButProgressbar *)but; + progress_oldbut->progress = progress_but->progress; + break; + } + case UI_BTYPE_TREEROW: { + uiButTreeRow *treerow_oldbut = (uiButTreeRow *)oldbut; + uiButTreeRow *treerow_newbut = (uiButTreeRow *)but; + SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item); + break; + } + default: + break; } /* move/copy string from the new button to the old */ @@ -2203,6 +2223,15 @@ int ui_but_is_pushed_ex(uiBut *but, double *value) } } break; + case UI_BTYPE_TREEROW: { + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + + is_push = -1; + if (tree_row_but->tree_item) { + is_push = UI_tree_view_item_is_active(tree_row_but->tree_item); + } + break; + } default: is_push = -1; break; @@ -3447,6 +3476,7 @@ void UI_block_free(const bContext *C, uiBlock *block) BLI_freelistN(&block->color_pickers.list); ui_block_free_button_groups(block); + ui_block_free_views(block); MEM_freeN(block); } @@ -3942,6 +3972,10 @@ static void ui_but_alloc_info(const eButType type, alloc_size = sizeof(uiButDatasetRow); alloc_str = "uiButDatasetRow"; break; + case UI_BTYPE_TREEROW: + alloc_size = sizeof(uiButTreeRow); + alloc_str = "uiButTreeRow"; + break; default: alloc_size = sizeof(uiBut); alloc_str = "uiBut"; @@ -4141,6 +4175,7 @@ static uiBut *ui_def_but(uiBlock *block, UI_BTYPE_BUT_MENU, UI_BTYPE_SEARCH_MENU, UI_BTYPE_DATASETROW, + UI_BTYPE_TREEROW, UI_BTYPE_POPOVER)) { but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); } @@ -6198,6 +6233,13 @@ void UI_but_drag_set_asset(uiBut *but, asset_drag->id_type = ED_asset_handle_get_id_type(asset); asset_drag->import_type = import_type; + /* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the + * #wmDropBox. + * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its + * copy callback. + * */ + asset_drag->evil_C = but->block->evil_C; + but->dragtype = WM_DRAG_ASSET; ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { @@ -6878,6 +6920,15 @@ void UI_but_datasetrow_indentation_set(uiBut *but, int indentation) BLI_assert(indentation >= 0); } +void UI_but_treerow_indentation_set(uiBut *but, int indentation) +{ + uiButTreeRow *but_row = (uiButTreeRow *)but; + BLI_assert(but->type == UI_BTYPE_TREEROW); + + but_row->indentation = indentation; + BLI_assert(indentation >= 0); +} + /** * Adds a hint to the button which draws right aligned, grayed out and never clipped. */ diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc new file mode 100644 index 00000000000..cb33e7f736e --- /dev/null +++ b/source/blender/editors/interface/interface_dropboxes.cc @@ -0,0 +1,66 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include "BKE_context.h" + +#include "DNA_space_types.h" + +#include "WM_api.h" + +#include "UI_interface.h" + +static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) +{ + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, event->x, event->y); + if (!hovered_tree_item) { + return false; + } + + return UI_tree_view_item_can_drop(hovered_tree_item, drag); +} + +static char *ui_tree_view_drop_tooltip(bContext *C, + wmDrag *drag, + const wmEvent *event, + wmDropBox *UNUSED(drop)) +{ + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, event->x, event->y); + if (!hovered_tree_item) { + return nullptr; + } + + return UI_tree_view_item_drop_tooltip(hovered_tree_item, C, drag, event); +} + +void ED_dropboxes_ui() +{ + ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0); + + WM_dropbox_add(lb, + "UI_OT_tree_view_drop", + ui_tree_view_drop_poll, + nullptr, + nullptr, + ui_tree_view_drop_tooltip); +} diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 77ae16d7cc7..aee66ec3a93 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -384,6 +384,8 @@ typedef struct uiHandleButtonData { /* booleans (could be made into flags) */ bool cancel, escapecancel; bool applied, applied_interactive; + /* Button is being applied through an extra icon. */ + bool apply_through_extra_icon; bool changed_cursor; wmTimer *flashtimer; @@ -1164,6 +1166,16 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu data->applied = true; } +static void ui_apply_but_TREEROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + if (data->apply_through_extra_icon) { + /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons. + */ + return; + } + ui_apply_but_ROW(C, block, but, data); +} + /** * \note Ownership of \a properties is moved here. The #uiAfterFunc owns it now. * @@ -2307,6 +2319,9 @@ static void ui_apply_but( case UI_BTYPE_ROW: ui_apply_but_ROW(C, block, but, data); break; + case UI_BTYPE_TREEROW: + ui_apply_but_TREEROW(C, block, but, data); + break; case UI_BTYPE_LISTROW: ui_apply_but_LISTROW(C, block, but, data); break; @@ -4194,6 +4209,8 @@ static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleBu static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon) { + but->active->apply_through_extra_icon = true; + if (but->active->interactive) { ui_apply_but(C, but->block, but, but->active, true); } @@ -4737,7 +4754,7 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons /* Behave like other menu items. */ do_activate = (event->val == KM_RELEASE); } - else { + else if (!ui_do_but_extra_operator_icon(C, but, data, event)) { /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */ do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK); } @@ -7966,6 +7983,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_CHECKBOX: case UI_BTYPE_CHECKBOX_N: case UI_BTYPE_ROW: + case UI_BTYPE_TREEROW: case UI_BTYPE_DATASETROW: retval = ui_do_but_TOG(C, but, data, event); break; diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index f739830cfdb..c20129b4184 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -47,6 +47,7 @@ #include "DNA_gpencil_types.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" +#include "DNA_sequence_types.h" #include "DNA_space_types.h" #include "RNA_access.h" @@ -309,7 +310,7 @@ static void vicon_keytype_draw_wrapper( sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", -1.0f, -1.0f); immBegin(GPU_PRIM_POINTS, 1); @@ -480,6 +481,35 @@ DEF_ICON_COLLECTION_COLOR_DRAW(08, COLLECTION_COLOR_08); # undef DEF_ICON_COLLECTION_COLOR_DRAW +static void vicon_strip_color_draw( + short color_tag, int x, int y, int w, int UNUSED(h), float UNUSED(alpha)) +{ + bTheme *btheme = UI_GetTheme(); + const ThemeStripColor *strip_color = &btheme->strip_color[color_tag]; + + const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w; + + UI_icon_draw_ex(x, y, ICON_SNAP_FACE, aspect, 1.0f, 0.0f, strip_color->color, true); +} + +# define DEF_ICON_STRIP_COLOR_DRAW(index, color) \ + static void vicon_strip_color_draw_##index(int x, int y, int w, int h, float alpha) \ + { \ + vicon_strip_color_draw(color, x, y, w, h, alpha); \ + } + +DEF_ICON_STRIP_COLOR_DRAW(01, SEQUENCE_COLOR_01); +DEF_ICON_STRIP_COLOR_DRAW(02, SEQUENCE_COLOR_02); +DEF_ICON_STRIP_COLOR_DRAW(03, SEQUENCE_COLOR_03); +DEF_ICON_STRIP_COLOR_DRAW(04, SEQUENCE_COLOR_04); +DEF_ICON_STRIP_COLOR_DRAW(05, SEQUENCE_COLOR_05); +DEF_ICON_STRIP_COLOR_DRAW(06, SEQUENCE_COLOR_06); +DEF_ICON_STRIP_COLOR_DRAW(07, SEQUENCE_COLOR_07); +DEF_ICON_STRIP_COLOR_DRAW(08, SEQUENCE_COLOR_08); +DEF_ICON_STRIP_COLOR_DRAW(09, SEQUENCE_COLOR_09); + +# undef DEF_ICON_STRIP_COLOR_DRAW + /* Dynamically render icon instead of rendering a plain color to a texture/buffer * This is not strictly a "vicon", as it needs access to icon->obj to get the color info, * but it works in a very similar way. @@ -995,6 +1025,16 @@ static void init_internal_icons(void) def_internal_vicon(ICON_COLLECTION_COLOR_06, vicon_collection_color_draw_06); def_internal_vicon(ICON_COLLECTION_COLOR_07, vicon_collection_color_draw_07); def_internal_vicon(ICON_COLLECTION_COLOR_08, vicon_collection_color_draw_08); + + def_internal_vicon(ICON_SEQUENCE_COLOR_01, vicon_strip_color_draw_01); + def_internal_vicon(ICON_SEQUENCE_COLOR_02, vicon_strip_color_draw_02); + def_internal_vicon(ICON_SEQUENCE_COLOR_03, vicon_strip_color_draw_03); + def_internal_vicon(ICON_SEQUENCE_COLOR_04, vicon_strip_color_draw_04); + def_internal_vicon(ICON_SEQUENCE_COLOR_05, vicon_strip_color_draw_05); + def_internal_vicon(ICON_SEQUENCE_COLOR_06, vicon_strip_color_draw_06); + def_internal_vicon(ICON_SEQUENCE_COLOR_07, vicon_strip_color_draw_07); + def_internal_vicon(ICON_SEQUENCE_COLOR_08, vicon_strip_color_draw_08); + def_internal_vicon(ICON_SEQUENCE_COLOR_09, vicon_strip_color_draw_09); } static void init_iconfile_list(struct ListBase *list) diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index d61104f094e..8b45d9faae6 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -360,6 +360,14 @@ typedef struct uiButDatasetRow { int indentation; } uiButDatasetRow; +/** Derived struct for #UI_BTYPE_TREEROW. */ +typedef struct uiButTreeRow { + uiBut but; + + uiTreeViewItemHandle *tree_item; + int indentation; +} uiButTreeRow; + /** Derived struct for #UI_BTYPE_HSVCUBE. */ typedef struct uiButHSVCube { uiBut but; @@ -488,6 +496,11 @@ struct uiBlock { ListBase contexts; + /** A block can store "views" on data-sets. Currently tree-views (#AbstractTreeView) only. + * Others are imaginable, e.g. table-views, grid-views, etc. These are stored here to support + * state that is persistent over redraws (e.g. collapsed tree-view items). */ + ListBase views; + char name[UI_MAX_NAME_STR]; float winmat[4][4]; @@ -1158,6 +1171,7 @@ uiBut *ui_list_row_find_mouse_over(const struct ARegion *region, uiBut *ui_list_row_find_from_index(const struct ARegion *region, const int index, uiBut *listbox) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int x, const int y); typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata); uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region, @@ -1274,6 +1288,11 @@ bool ui_jump_to_target_button_poll(struct bContext *C); /* interface_queries.c */ void ui_interface_tag_script_reload_queries(void); +/* interface_view.cc */ +void ui_block_free_views(struct uiBlock *block); +uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiTreeViewHandle *new_view); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 66c75c63050..64c16e57f56 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -288,35 +288,85 @@ static bool ui_layout_variable_size(uiLayout *layout) return ui_layout_vary_direction(layout) == UI_ITEM_VARY_X || layout->variable_size; } -/* estimated size of text + icon */ -static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) +/** + * Factors to apply to #UI_UNIT_X when calculating button width. + * This is used when the layout is a varying size, see #ui_layout_variable_size. + */ +struct uiTextIconPadFactor { + float text; + float icon; + float icon_only; +}; + +/** + * This adds over an icons width of padding even when no icon is used, + * this is done because most buttons need additional space (drop-down chevron for example). + * menus and labels use much smaller `text` values compared to this default. + * + * \note It may seem odd that the icon only adds 0.25 + * but taking margins into account its fine, + * except for #ui_text_pad_compact where a bit more margin is required. + */ +static const struct uiTextIconPadFactor ui_text_pad_default = { + .text = 1.50f, + .icon = 0.25f, + .icon_only = 0.0f, +}; + +/** #ui_text_pad_default scaled down. */ +static const struct uiTextIconPadFactor ui_text_pad_compact = { + .text = 1.25f, + .icon = 0.35f, + .icon_only = 0.0f, +}; + +/** Least amount of padding not to clip the text or icon. */ +static const struct uiTextIconPadFactor ui_text_pad_none = { + .text = 0.25f, + .icon = 1.50f, + .icon_only = 0.0f, +}; + +/** + * Estimated size of text + icon. + */ +static int ui_text_icon_width_ex(uiLayout *layout, + const char *name, + int icon, + const struct uiTextIconPadFactor *pad_factor) { const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f); + /* When there is no text, always behave as if this is an icon-only button + * since it's not useful to return empty space. */ if (icon && !name[0]) { - return unit_x; /* icon only */ + return unit_x * (1.0f + pad_factor->icon_only); } if (ui_layout_variable_size(layout)) { if (!icon && !name[0]) { - return unit_x; /* No icon or name. */ + return unit_x * (1.0f + pad_factor->icon_only); } + if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) { layout->item.flag |= UI_ITEM_FIXED_SIZE; } const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - float margin = compact ? 1.25 : 1.50; + float margin = pad_factor->text; if (icon) { - /* It may seem odd that the icon only adds (unit_x / 4) - * but taking margins into account its fine, except - * in compact mode a bit more margin is required. */ - margin += compact ? 0.35 : 0.25; + margin += pad_factor->icon; } return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin); } return unit_x * 10; } +static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) +{ + return ui_text_icon_width_ex( + layout, name, icon, compact ? &ui_text_pad_compact : &ui_text_pad_default); +} + static void ui_item_size(uiItem *item, int *r_w, int *r_h) { if (item->type == ITEM_BUTTON) { @@ -2857,23 +2907,23 @@ static uiBut *ui_item_menu(uiLayout *layout, icon = ICON_BLANK1; } - int w = ui_text_icon_width(layout, name, icon, 1); - const int h = UI_UNIT_Y; - + struct uiTextIconPadFactor pad_factor = ui_text_pad_compact; if (layout->root->type == UI_LAYOUT_HEADER) { /* Ugly! */ if (icon == ICON_NONE && force_menu) { /* pass */ } else if (force_menu) { - w += 0.6f * UI_UNIT_X; + pad_factor.text = 1.85; + pad_factor.icon_only = 0.6f; } else { - if (name[0]) { - w -= UI_UNIT_X / 2; - } + pad_factor.text = 0.75f; } } + const int w = ui_text_icon_width_ex(layout, name, icon, &pad_factor); + const int h = UI_UNIT_Y; + if (heading_layout) { ui_layout_heading_label_add(layout, heading_layout, true, true); } @@ -3133,8 +3183,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon) icon = ICON_BLANK1; } - const int w = ui_text_icon_width(layout, name, icon, 0); - + const int w = ui_text_icon_width_ex(layout, name, icon, &ui_text_pad_none); uiBut *but; if (icon && name[0]) { but = uiDefIconTextBut( diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index dd10d942fc9..1fc07bce341 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -1381,16 +1381,7 @@ static int editsource_text_edit(bContext *C, /* naughty!, find text area to set, not good behavior * but since this is a developer tool lets allow it - campbell */ - ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0); - if (area) { - SpaceText *st = area->spacedata.first; - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - st->text = text; - if (region) { - ED_text_scroll_to_cursor(st, region, true); - } - } - else { + if (!ED_text_activate_in_screen(C, text)) { BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); } @@ -1927,6 +1918,51 @@ static void UI_OT_list_start_filter(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name UI Tree-View Drop Operator + * \{ */ + +static bool ui_tree_view_drop_poll(bContext *C) +{ + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, win->eventstate->x, win->eventstate->y); + + return hovered_tree_item != NULL; +} + +static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + if (event->custom != EVT_DATA_DRAGDROP) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + const ARegion *region = CTX_wm_region(C); + uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, event->x, event->y); + + if (!UI_tree_view_item_drop_handle(hovered_tree_item, event->customdata)) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_tree_view_drop(wmOperatorType *ot) +{ + ot->name = "Tree View drop"; + ot->idname = "UI_OT_tree_view_drop"; + ot->description = "Drag and drop items onto a tree item"; + + ot->invoke = ui_tree_view_drop_invoke; + ot->poll = ui_tree_view_drop_poll; + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Operator & Keymap Registration * \{ */ @@ -1953,6 +1989,8 @@ void ED_operatortypes_ui(void) WM_operatortype_append(UI_OT_list_start_filter); + WM_operatortype_append(UI_OT_tree_view_drop); + /* external */ WM_operatortype_append(UI_OT_eyedropper_color); WM_operatortype_append(UI_OT_eyedropper_colorramp); diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c index 09429bb6df5..2f6bda3252d 100644 --- a/source/blender/editors/interface/interface_query.c +++ b/source/blender/editors/interface/interface_query.c @@ -69,7 +69,8 @@ bool ui_but_is_toggle(const uiBut *but) UI_BTYPE_CHECKBOX, UI_BTYPE_CHECKBOX_N, UI_BTYPE_ROW, - UI_BTYPE_DATASETROW); + UI_BTYPE_DATASETROW, + UI_BTYPE_TREEROW); } /** @@ -462,6 +463,16 @@ uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut return ui_but_find(region, ui_but_is_listrow_at_index, &data); } +static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata)) +{ + return but->type == UI_BTYPE_TREEROW; +} + +uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int x, const int y) +{ + return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_treerow, NULL); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc new file mode 100644 index 00000000000..b199ce9562e --- /dev/null +++ b/source/blender/editors/interface/interface_view.cc @@ -0,0 +1,133 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + * + * This part of the UI-View API is mostly needed to support persistent state of items within the + * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we + * can compare the old view items with the new view items and keep state persistent for matching + * ones. + */ + +#include <memory> +#include <variant> + +#include "DNA_screen_types.h" + +#include "BLI_listbase.h" + +#include "interface_intern.h" + +#include "UI_interface.hh" +#include "UI_tree_view.hh" + +using namespace blender; +using namespace blender::ui; + +/** + * Wrapper to store views in a #ListBase. There's no `uiView` base class, we just store views as a + * #std::variant. + */ +struct ViewLink : public Link { + using TreeViewPtr = std::unique_ptr<AbstractTreeView>; + + std::string idname; + /* Note: Can't use std::get() on this until minimum macOS deployment target is 10.14. */ + std::variant<TreeViewPtr> view; +}; + +template<class T> T *get_view_from_link(ViewLink &link) +{ + auto *t_uptr = std::get_if<std::unique_ptr<T>>(&link.view); + return t_uptr ? t_uptr->get() : nullptr; +} + +/** + * Override this for all available tree types. + */ +AbstractTreeView *UI_block_add_view(uiBlock &block, + StringRef idname, + std::unique_ptr<AbstractTreeView> tree_view) +{ + ViewLink *view_link = OBJECT_GUARDED_NEW(ViewLink); + BLI_addtail(&block.views, view_link); + + view_link->view = std::move(tree_view); + view_link->idname = idname; + + return get_view_from_link<AbstractTreeView>(*view_link); +} + +void ui_block_free_views(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { + OBJECT_GUARDED_DELETE(link, ViewLink); + } +} + +/** + * \param x, y: Coordinate to find a tree-row item at, in window space. + */ +uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region, + const int x, + const int y) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, x, y); + if (!tree_row_but) { + return nullptr; + } + + return tree_row_but->tree_item; +} + +static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractTreeView &view) +{ + /* First get the idname the of the view we're looking for. */ + LISTBASE_FOREACH (ViewLink *, view_link, &block.views) { + if (get_view_from_link<AbstractTreeView>(*view_link) == &view) { + return view_link->idname; + } + } + + return {}; +} + +uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiTreeViewHandle *new_view_handle) +{ + const AbstractTreeView &needle_view = reinterpret_cast<const AbstractTreeView &>( + *new_view_handle); + + uiBlock *old_block = new_block->oldblock; + if (!old_block) { + return nullptr; + } + + StringRef idname = ui_block_view_find_idname(*new_block, needle_view); + if (idname.is_empty()) { + return nullptr; + } + + LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) { + if (old_view_link->idname == idname) { + return reinterpret_cast<uiTreeViewHandle *>( + get_view_from_link<AbstractTreeView>(*old_view_link)); + } + } + + return nullptr; +} diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index 0dc7c2d3f9a..466deedf3bb 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -115,6 +115,7 @@ typedef enum { UI_WTYPE_PROGRESSBAR, UI_WTYPE_NODESOCKET, UI_WTYPE_DATASETROW, + UI_WTYPE_TREEROW, } uiWidgetTypeEnum; /* Button state argument shares bits with 'uiBut.flag'. @@ -3679,10 +3680,9 @@ static void widget_progressbar( widgetbase_draw(&wtb_bar, wcol); } -static void widget_datasetrow( - uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign)) +static void widget_treerow_exec( + uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign), int indentation) { - uiButDatasetRow *but_componentrow = (uiButDatasetRow *)but; uiWidgetBase wtb; widget_init(&wtb); @@ -3695,10 +3695,24 @@ static void widget_datasetrow( widgetbase_draw(&wtb, wcol); } - BLI_rcti_resize(rect, - BLI_rcti_size_x(rect) - UI_UNIT_X * but_componentrow->indentation, - BLI_rcti_size_y(rect)); - BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * but_componentrow->indentation, 0); + BLI_rcti_resize(rect, BLI_rcti_size_x(rect) - UI_UNIT_X * indentation, BLI_rcti_size_y(rect)); + BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * indentation, 0); +} + +static void widget_treerow( + uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiButTreeRow *tree_row = (uiButTreeRow *)but; + BLI_assert(but->type == UI_BTYPE_TREEROW); + widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation); +} + +static void widget_datasetrow( + uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiButDatasetRow *dataset_row = (uiButDatasetRow *)but; + BLI_assert(but->type == UI_BTYPE_DATASETROW); + widget_treerow_exec(wcol, rect, state, roundboxalign, dataset_row->indentation); } static void widget_nodesocket( @@ -4492,6 +4506,10 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) wt.custom = widget_datasetrow; break; + case UI_WTYPE_TREEROW: + wt.custom = widget_treerow; + break; + case UI_WTYPE_NODESOCKET: wt.custom = widget_nodesocket; break; @@ -4824,6 +4842,11 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu fstyle = &style->widgetlabel; break; + case UI_BTYPE_TREEROW: + wt = widget_type(UI_WTYPE_TREEROW); + fstyle = &style->widgetlabel; + break; + case UI_BTYPE_SCROLL: wt = widget_type(UI_WTYPE_SCROLL); break; @@ -5348,7 +5371,7 @@ void ui_draw_menu_item(const uiFontStyle *fstyle, } } else { - BLI_assert_msg(0, "Unknwon menu item separator type"); + BLI_assert_msg(0, "Unknown menu item separator type"); } } } diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc new file mode 100644 index 00000000000..0ea15a2a5bb --- /dev/null +++ b/source/blender/editors/interface/tree_view.cc @@ -0,0 +1,381 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include "DNA_userdef_types.h" + +#include "BLT_translation.h" + +#include "interface_intern.h" + +#include "UI_interface.h" + +#include "UI_tree_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ + +/** + * Add a tree-item to the container. This is the only place where items should be added, it handles + * important invariants! + */ +AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( + std::unique_ptr<AbstractTreeViewItem> item) +{ + children_.append(std::move(item)); + + /* The first item that will be added to the root sets this. */ + if (root_ == nullptr) { + root_ = this; + } + + AbstractTreeViewItem &added_item = *children_.last(); + added_item.root_ = root_; + if (root_ != this) { + /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely + * nice to static_cast this, but well... */ + added_item.parent_ = static_cast<AbstractTreeViewItem *>(this); + } + + return added_item; +} + +void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const +{ + for (const auto &child : children_) { + iter_fn(*child); + if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) { + continue; + } + + child->foreach_item_recursive(iter_fn, options); + } +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const +{ + foreach_item_recursive(iter_fn, options); +} + +void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder) +{ + uiLayout *prev_layout = builder.current_layout(); + + uiLayoutColumn(prev_layout, true); + + foreach_item([&builder](AbstractTreeViewItem &item) { builder.build_row(item); }, + IterOptions::SkipCollapsed); + + UI_block_layout_set_current(&builder.block(), prev_layout); +} + +void AbstractTreeView::update_from_old(uiBlock &new_block) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + return; + } + + uiTreeViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block( + &new_block, reinterpret_cast<uiTreeViewHandle *>(this)); + if (!old_view_handle) { + return; + } + + AbstractTreeView &old_view = reinterpret_cast<AbstractTreeView &>(*old_view_handle); + update_children_from_old_recursive(*this, old_view); +} + +void AbstractTreeView::update_children_from_old_recursive(const TreeViewItemContainer &new_items, + const TreeViewItemContainer &old_items) +{ + for (const auto &new_item : new_items.children_) { + AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items); + if (!matching_old_item) { + continue; + } + + new_item->update_from_old(*matching_old_item); + + /* Recurse into children of the matched item. */ + update_children_from_old_recursive(*new_item, *matching_old_item); + } +} + +AbstractTreeViewItem *AbstractTreeView::find_matching_child( + const AbstractTreeViewItem &lookup_item, const TreeViewItemContainer &items) +{ + for (const auto &iter_item : items.children_) { + if (lookup_item.matches(*iter_item)) { + /* We have a matching item! */ + return iter_item.get(); + } + } + + return nullptr; +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeViewItem::on_activate() +{ + /* Do nothing by default. */ +} + +bool AbstractTreeViewItem::on_drop(const wmDrag & /*drag*/) +{ + /* Do nothing by default. */ + return false; +} + +bool AbstractTreeViewItem::can_drop(const wmDrag & /*drag*/) const +{ + return false; +} + +std::string AbstractTreeViewItem::drop_tooltip(const bContext & /*C*/, + const wmDrag & /*drag*/, + const wmEvent & /*event*/) const +{ + return TIP_("Drop into/onto tree item"); +} + +void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old) +{ + is_open_ = old.is_open_; + is_active_ = old.is_active_; +} + +bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const +{ + return label_ == other.label_; +} + +const AbstractTreeView &AbstractTreeViewItem::get_tree_view() const +{ + return static_cast<AbstractTreeView &>(*root_); +} + +int AbstractTreeViewItem::count_parents() const +{ + int i = 0; + for (TreeViewItemContainer *parent = parent_; parent; parent = parent->parent_) { + i++; + } + return i; +} + +void AbstractTreeViewItem::set_active(bool value) +{ + if (value && !is_active()) { + /* Deactivate other items in the tree. */ + get_tree_view().foreach_item([](auto &item) { item.set_active(false); }); + on_activate(); + } + is_active_ = value; +} + +bool AbstractTreeViewItem::is_active() const +{ + return is_active_; +} + +bool AbstractTreeViewItem::is_collapsed() const +{ + return is_collapsible() && !is_open_; +} + +void AbstractTreeViewItem::toggle_collapsed() +{ + is_open_ = !is_open_; +} + +void AbstractTreeViewItem::set_collapsed(bool collapsed) +{ + is_open_ = !collapsed; +} + +bool AbstractTreeViewItem::is_collapsible() const +{ + return !children_.is_empty(); +} + +/* ---------------------------------------------------------------------- */ + +TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view) +{ + tree_view.build_tree(); + tree_view.update_from_old(block_); + tree_view.build_layout_from_tree(TreeViewLayoutBuilder(block_)); +} + +/* ---------------------------------------------------------------------- */ + +TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const +{ + uiLayout *prev_layout = current_layout(); + uiLayout *row = uiLayoutRow(prev_layout, false); + + item.build_row(*row); + + UI_block_layout_set_current(&block(), prev_layout); +} + +uiBlock &TreeViewLayoutBuilder::block() const +{ + return block_; +} + +uiLayout *TreeViewLayoutBuilder::current_layout() const +{ + return block().curlayout; +} + +/* ---------------------------------------------------------------------- */ + +BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_, ActivateFn activate_fn) + : icon(icon_), activate_fn_(activate_fn) +{ + label_ = label; +} + +static void tree_row_click_fn(struct bContext *UNUSED(C), void *but_arg1, void *UNUSED(arg2)) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1; + AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>( + *tree_row_but->tree_item); + + /* Let a click on an opened item activate it, a second click will close it then. + * TODO Should this be for asset catalogs only? */ + if (tree_item.is_collapsed() || tree_item.is_active()) { + tree_item.toggle_collapsed(); + } + tree_item.set_active(); +} + +void BasicTreeViewItem::build_row(uiLayout &row) +{ + uiBlock *block = uiLayoutGetBlock(&row); + tree_row_but_ = (uiButTreeRow *)uiDefIconTextBut(block, + UI_BTYPE_TREEROW, + 0, + /* TODO allow icon besides the chevron icon? */ + get_draw_icon(), + label_.data(), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + + tree_row_but_->tree_item = reinterpret_cast<uiTreeViewItemHandle *>(this); + UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr); + UI_but_treerow_indentation_set(&tree_row_but_->but, count_parents()); +} + +void BasicTreeViewItem::on_activate() +{ + if (activate_fn_) { + activate_fn_(*this); + } +} + +BIFIconID BasicTreeViewItem::get_draw_icon() const +{ + if (icon) { + return icon; + } + + if (is_collapsible()) { + return is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN; + } + + return ICON_NONE; +} + +uiBut *BasicTreeViewItem::button() +{ + return &tree_row_but_->but; +} + +} // namespace blender::ui + +using namespace blender::ui; + +bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle); + return item.is_active(); +} + +bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle, + const uiTreeViewItemHandle *b_handle) +{ + const AbstractTreeViewItem &a = reinterpret_cast<const AbstractTreeViewItem &>(*a_handle); + const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle); + return a.matches(b); +} + +bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const wmDrag *drag) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); + return item.can_drop(*drag); +} + +char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, + const bContext *C, + const wmDrag *drag, + const wmEvent *event) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); + return BLI_strdup(item.drop_tooltip(*C, *drag, *event).c_str()); +} + +/** + * Let a tree-view item handle a drop event. + * \return True if the drop was handled by the tree-view item. + */ +bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const ListBase *drags) +{ + AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_); + + LISTBASE_FOREACH (const wmDrag *, drag, drags) { + if (item.can_drop(*drag)) { + return item.on_drop(*drag); + } + } + + return false; +} diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c index 23c8a0d35bf..0036a812a87 100644 --- a/source/blender/editors/interface/view2d.c +++ b/source/blender/editors/interface/view2d.c @@ -866,6 +866,11 @@ void UI_view2d_curRect_changed(const bContext *C, View2D *v2d) /* ------------------ */ +bool UI_view2d_area_supports_sync(ScrArea *area) +{ + return ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH); +} + /* Called by menus to activate it, or by view2d operators * to make sure 'related' views stay in synchrony */ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) @@ -903,6 +908,9 @@ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) /* check if doing whole screen syncing (i.e. time/horizontal) */ if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) { LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) { + if (!UI_view2d_area_supports_sync(area_iter)) { + continue; + } LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) { /* don't operate on self */ if (v2dcur != ®ion->v2d) { diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c index eee4aec7459..64c008acf8e 100644 --- a/source/blender/editors/mesh/editmesh_knife.c +++ b/source/blender/editors/mesh/editmesh_knife.c @@ -113,7 +113,7 @@ typedef struct KnifeColors { uchar axis_extra[3]; } KnifeColors; -/* Knifetool Operator. */ +/* Knife-tool Operator. */ typedef struct KnifeVert { Object *ob; uint base_index; @@ -333,6 +333,9 @@ enum { KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE, KNF_MODAL_DEPTH_TEST_TOGGLE, KNF_MODAL_PANNING, + KNF_MODAL_X_AXIS, + KNF_MODAL_Y_AXIS, + KNF_MODAL_Z_AXIS, KNF_MODAL_ADD_CUT_CLOSED, }; @@ -366,7 +369,6 @@ enum { /** \name Drawing * \{ */ -#if 1 static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], float r_v2[3]) { float planes[4][4]; @@ -380,10 +382,6 @@ static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], float lambda_best[2] = {-FLT_MAX, FLT_MAX}; int i; - /* We (sometimes) need the lines to be at the same depth before projecting. */ -# if 0 - sub_v3_v3v3(ray_dir, kcd->curr.cage, kcd->prev.cage); -# else { float curr_cage_adjust[3]; float co_depth[3]; @@ -393,7 +391,6 @@ static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], sub_v3_v3v3(ray_dir, curr_cage_adjust, kcd->prev.cage); } -# endif for (i = 0; i < 4; i++) { float ray_hit[3]; @@ -445,7 +442,7 @@ static void knifetool_draw_orientation_locking(const KnifeTool_OpData *kcd) if (!compare_v3v3(kcd->prev.cage, kcd->curr.cage, KNIFE_FLT_EPSBIG)) { float v1[3], v2[3]; - /* This is causing buggyness when prev.cage and curr.cage are too close together. */ + /* This is causing buggy behavior when `prev.cage` and `curr.cage` are too close together. */ knifetool_raycast_planes(kcd, v1, v2); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); @@ -480,7 +477,6 @@ static void knifetool_draw_orientation_locking(const KnifeTool_OpData *kcd) immUnbindProgram(); } } -#endif static void knifetool_draw_visible_distances(const KnifeTool_OpData *kcd) { @@ -1112,7 +1108,7 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k "%s: start/define cut, %s: close cut, %s: new cut, " "%s: midpoint snap (%s), %s: ignore snap (%s), " "%s: angle constraint %.2f(%.2f) (%s%s%s%s), %s: cut through (%s), " - "%s: panning, XYZ: orientation lock (%s), " + "%s: panning, %s%s%s: orientation lock (%s), " "%s: distance/angle measurements (%s), " "%s: x-ray (%s)"), WM_MODALKEY(KNF_MODAL_CONFIRM), @@ -1144,6 +1140,9 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k WM_MODALKEY(KNF_MODAL_CUT_THROUGH_TOGGLE), WM_bool_as_string(kcd->cut_through), WM_MODALKEY(KNF_MODAL_PANNING), + WM_MODALKEY(KNF_MODAL_X_AXIS), + WM_MODALKEY(KNF_MODAL_Y_AXIS), + WM_MODALKEY(KNF_MODAL_Z_AXIS), (kcd->axis_constrained ? kcd->axis_string : WM_bool_as_string(kcd->axis_constrained)), WM_MODALKEY(KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE), WM_bool_as_string(kcd->show_dist_angle), @@ -3900,71 +3899,69 @@ static void knifetool_undo(KnifeTool_OpData *kcd) KnifeUndoFrame *undo; BLI_mempool_iter iterkfe; - if (!BLI_stack_is_empty(kcd->undostack)) { - undo = BLI_stack_peek(kcd->undostack); + undo = BLI_stack_peek(kcd->undostack); - /* Undo edge splitting. */ - for (int i = 0; i < undo->splits; i++) { - BLI_stack_pop(kcd->splitstack, &newkfe); - BLI_stack_pop(kcd->splitstack, &kfe); - knife_join_edge(newkfe, kfe); - } + /* Undo edge splitting. */ + for (int i = 0; i < undo->splits; i++) { + BLI_stack_pop(kcd->splitstack, &newkfe); + BLI_stack_pop(kcd->splitstack, &kfe); + knife_join_edge(newkfe, kfe); + } - for (int i = 0; i < undo->cuts; i++) { + for (int i = 0; i < undo->cuts; i++) { - BLI_mempool_iternew(kcd->kedges, &iterkfe); - for (kfe = BLI_mempool_iterstep(&iterkfe); kfe; kfe = BLI_mempool_iterstep(&iterkfe)) { - if (!kfe->is_cut || kfe->is_invalid || kfe->splits) { - continue; - } - lastkfe = kfe; + BLI_mempool_iternew(kcd->kedges, &iterkfe); + for (kfe = BLI_mempool_iterstep(&iterkfe); kfe; kfe = BLI_mempool_iterstep(&iterkfe)) { + if (!kfe->is_cut || kfe->is_invalid || kfe->splits) { + continue; } + lastkfe = kfe; + } - if (lastkfe) { - lastkfe->is_invalid = true; - - /* TODO: Are they always guaranteed to be in this order? */ - v1 = lastkfe->v1; - v2 = lastkfe->v2; - - /* Only remove first vertex if it is the start segment of the cut. */ - if (!v1->is_invalid && !v1->is_splitting) { - v1->is_invalid = true; - /* If the first vertex is touching any other cut edges don't remove it. */ - for (ref = v1->edges.first; ref; ref = ref->next) { - kfe = ref->ref; - if (kfe->is_cut && !kfe->is_invalid) { - v1->is_invalid = false; - break; - } + if (lastkfe) { + lastkfe->is_invalid = true; + + /* TODO: Are they always guaranteed to be in this order? */ + v1 = lastkfe->v1; + v2 = lastkfe->v2; + + /* Only remove first vertex if it is the start segment of the cut. */ + if (!v1->is_invalid && !v1->is_splitting) { + v1->is_invalid = true; + /* If the first vertex is touching any other cut edges don't remove it. */ + for (ref = v1->edges.first; ref; ref = ref->next) { + kfe = ref->ref; + if (kfe->is_cut && !kfe->is_invalid) { + v1->is_invalid = false; + break; } } + } - /* Only remove second vertex if it is the end segment of the cut. */ - if (!v2->is_invalid && !v2->is_splitting) { - v2->is_invalid = true; - /* If the second vertex is touching any other cut edges don't remove it. */ - for (ref = v2->edges.first; ref; ref = ref->next) { - kfe = ref->ref; - if (kfe->is_cut && !kfe->is_invalid) { - v2->is_invalid = false; - break; - } + /* Only remove second vertex if it is the end segment of the cut. */ + if (!v2->is_invalid && !v2->is_splitting) { + v2->is_invalid = true; + /* If the second vertex is touching any other cut edges don't remove it. */ + for (ref = v2->edges.first; ref; ref = ref->next) { + kfe = ref->ref; + if (kfe->is_cut && !kfe->is_invalid) { + v2->is_invalid = false; + break; } } } } + } - if (kcd->mode == MODE_DRAGGING) { - /* Restore kcd->prev. */ - kcd->prev = undo->pos; - } + if (kcd->mode == MODE_DRAGGING) { + /* Restore kcd->prev. */ + kcd->prev = undo->pos; + } - /* Restore data for distance and angle measurements. */ - kcd->mdata = undo->mdata; + /* Restore data for distance and angle measurements. */ + kcd->mdata = undo->mdata; - BLI_stack_discard(kcd->undostack); - } + BLI_stack_discard(kcd->undostack); } /** \} */ @@ -4100,7 +4097,7 @@ static void knifetool_init(bContext *C, kcd->axis_string[0] = ' '; kcd->axis_string[1] = '\0'; - /* Initialise num input handling for angle snapping. */ + /* Initialize number input handling for angle snapping. */ initNumInput(&kcd->num); kcd->num.idx_max = 0; kcd->num.val_flag[0] |= NUM_NO_NEGATIVE; @@ -4151,7 +4148,7 @@ static void knifetool_exit_ex(KnifeTool_OpData *kcd) MEM_freeN(kcd->cagecos); knife_bvh_free(kcd); - /* Linehits cleanup. */ + /* Line-hits cleanup. */ if (kcd->linehits) { MEM_freeN(kcd->linehits); } @@ -4307,6 +4304,9 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf) {KNF_MODAL_ADD_CUT, "ADD_CUT", 0, "Add Cut", ""}, {KNF_MODAL_ADD_CUT_CLOSED, "ADD_CUT_CLOSED", 0, "Add Cut Closed", ""}, {KNF_MODAL_PANNING, "PANNING", 0, "Panning", ""}, + {KNF_MODAL_X_AXIS, "X_AXIS", 0, "X Axis Locking", ""}, + {KNF_MODAL_Y_AXIS, "Y_AXIS", 0, "Y Axis Locking", ""}, + {KNF_MODAL_Z_AXIS, "Z_AXIS", 0, "Z Axis Locking", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -4404,6 +4404,12 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_FINISHED; case KNF_MODAL_UNDO: + if (BLI_stack_is_empty(kcd->undostack)) { + ED_region_tag_redraw(kcd->region); + knifetool_exit(op); + ED_workspace_status_text(C, NULL); + return OPERATOR_CANCELLED; + } knifetool_undo(kcd); knife_update_active(C, kcd); ED_region_tag_redraw(kcd->region); @@ -4614,52 +4620,58 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) if (kcd->num.str_cur >= 2) { knife_reset_snap_angle_input(kcd); } - /* Modal numinput inactive, try to handle numeric inputs last... */ - if (!handled && event->val == KM_PRESS && handleNumInput(C, &kcd->num, event)) { - applyNumInput(&kcd->num, &snapping_increment_temp); - /* Restrict number key input to 0 - 90 degree range. */ - if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && - snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { - kcd->angle_snapping_increment = snapping_increment_temp; + if (event->type != EVT_MODAL_MAP) { + /* Modal number-input inactive, try to handle numeric inputs last. */ + if (!handled && event->val == KM_PRESS && handleNumInput(C, &kcd->num, event)) { + applyNumInput(&kcd->num, &snapping_increment_temp); + /* Restrict number key input to 0 - 90 degree range. */ + if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && + snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { + kcd->angle_snapping_increment = snapping_increment_temp; + } + knife_update_active(C, kcd); + knife_update_header(C, op, kcd); + ED_region_tag_redraw(kcd->region); + return OPERATOR_RUNNING_MODAL; } - knife_update_active(C, kcd); - knife_update_header(C, op, kcd); - ED_region_tag_redraw(kcd->region); - return OPERATOR_RUNNING_MODAL; } } /* Constrain axes with X,Y,Z keys. */ - if (event->val == KM_PRESS && ELEM(event->type, EVT_XKEY, EVT_YKEY, EVT_ZKEY)) { - if (event->type == EVT_XKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_X) { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_X; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; - kcd->axis_string[0] = 'X'; - } - else if (event->type == EVT_YKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Y) { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Y; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; - kcd->axis_string[0] = 'Y'; - } - else if (event->type == EVT_ZKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Z) { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Z; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; - kcd->axis_string[0] = 'Z'; - } - else { - /* Cycle through modes with repeated key presses. */ - if (kcd->constrain_axis_mode != KNF_CONSTRAIN_AXIS_MODE_LOCAL) { - kcd->constrain_axis_mode++; - kcd->axis_string[0] += 32; /* Lower case. */ + if (event->type == EVT_MODAL_MAP) { + if (ELEM(event->val, KNF_MODAL_X_AXIS, KNF_MODAL_Y_AXIS, KNF_MODAL_Z_AXIS)) { + if (event->val == KNF_MODAL_X_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_X) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_X; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'X'; + } + else if (event->val == KNF_MODAL_Y_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Y) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Y; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'Y'; + } + else if (event->val == KNF_MODAL_Z_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Z) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Z; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'Z'; } else { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_NONE; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE; + /* Cycle through modes with repeated key presses. */ + if (kcd->constrain_axis_mode != KNF_CONSTRAIN_AXIS_MODE_LOCAL) { + kcd->constrain_axis_mode++; + kcd->axis_string[0] += 32; /* Lower case. */ + } + else { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_NONE; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE; + } } + kcd->axis_constrained = (kcd->constrain_axis != KNF_CONSTRAIN_AXIS_NONE); + knifetool_disable_angle_snapping(kcd); + knife_update_header(C, op, kcd); + ED_region_tag_redraw(kcd->region); + do_refresh = true; } - kcd->axis_constrained = (kcd->constrain_axis != KNF_CONSTRAIN_AXIS_NONE); - knifetool_disable_angle_snapping(kcd); - knife_update_header(C, op, kcd); } if (kcd->mode == MODE_DRAGGING) { diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index beadbf2689e..cc4f2acc346 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -75,6 +75,7 @@ #include "BKE_lattice.h" #include "BKE_layer.h" #include "BKE_lib_id.h" +#include "BKE_lib_override.h" #include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_light.h" @@ -2015,6 +2016,15 @@ static int object_delete_exec(bContext *C, wmOperator *op) continue; } + if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) { + /* Can this case ever happen? */ + BKE_reportf(op->reports, + RPT_WARNING, + "Cannot delete object '%s' as it used by override collections", + ob->id.name + 2); + continue; + } + if (ID_REAL_USERS(ob) <= 1 && ID_EXTRA_USERS(ob) == 0 && BKE_library_ID_is_indirectly_used(bmain, ob)) { BKE_reportf(op->reports, @@ -2828,8 +2838,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* Remove unused materials. */ int actcol = ob_gpencil->actcol; for (int slot = 1; slot <= ob_gpencil->totcol; slot++) { - while (slot <= ob_gpencil->totcol && - !BKE_object_material_slot_used(ob_gpencil->data, slot)) { + while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) { ob_gpencil->actcol = slot; BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil); diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index 5697c2c973d..2bd0ae5f121 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -1739,7 +1739,7 @@ void OBJECT_OT_mode_set_with_submode(wmOperatorType *ot) OBJECT_OT_mode_set(ot); /* identifiers */ - ot->name = "Set Object Mode with Submode"; + ot->name = "Set Object Mode with Sub-mode"; ot->idname = "OBJECT_OT_mode_set_with_submode"; /* properties */ diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 50dd9322c5c..d00e6efeb29 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -78,7 +78,6 @@ void OBJECT_OT_mode_set(struct wmOperatorType *ot); void OBJECT_OT_mode_set_with_submode(struct wmOperatorType *ot); void OBJECT_OT_editmode_toggle(struct wmOperatorType *ot); void OBJECT_OT_posemode_toggle(struct wmOperatorType *ot); -void OBJECT_OT_proxy_make(struct wmOperatorType *ot); void OBJECT_OT_shade_smooth(struct wmOperatorType *ot); void OBJECT_OT_shade_flat(struct wmOperatorType *ot); void OBJECT_OT_paths_calculate(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index b9942bc563a..efe19785f31 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -1044,6 +1044,10 @@ bool edit_modifier_poll_generic(bContext *C, Object *ob = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C); ModifierData *mod = ptr.data; /* May be NULL. */ + if (mod == NULL && ob != NULL) { + mod = BKE_object_active_modifier(ob); + } + if (!ob || ID_IS_LINKED(ob)) { return false; } diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index aa9ae082317..fa0208a7022 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -56,7 +56,6 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_mode_set_with_submode); WM_operatortype_append(OBJECT_OT_editmode_toggle); WM_operatortype_append(OBJECT_OT_posemode_toggle); - WM_operatortype_append(OBJECT_OT_proxy_make); WM_operatortype_append(OBJECT_OT_shade_smooth); WM_operatortype_append(OBJECT_OT_shade_flat); WM_operatortype_append(OBJECT_OT_paths_calculate); diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 75269dffec8..5c7e1e1fa01 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -331,167 +331,6 @@ void OBJECT_OT_vertex_parent_set(wmOperatorType *ot) /** \} */ /* ------------------------------------------------------------------- */ -/** \name Make Proxy Operator - * \{ */ - -/* set the object to proxify */ -static int make_proxy_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Scene *scene = CTX_data_scene(C); - Object *ob = ED_object_active_context(C); - - /* sanity checks */ - if (!scene || ID_IS_LINKED(scene) || !ob) { - return OPERATOR_CANCELLED; - } - - /* Get object to work on - use a menu if we need to... */ - if (ob->instance_collection && ID_IS_LINKED(ob->instance_collection)) { - /* gives menu with list of objects in group */ - /* proxy_group_objects_menu(C, op, ob, ob->instance_collection); */ - WM_enum_search_invoke(C, op, event); - return OPERATOR_CANCELLED; - } - if (ID_IS_LINKED(ob)) { - uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("OK?"), ICON_QUESTION); - uiLayout *layout = UI_popup_menu_layout(pup); - - /* create operator menu item with relevant properties filled in */ - PointerRNA opptr_dummy; - uiItemFullO_ptr( - layout, op->type, op->type->name, ICON_NONE, NULL, WM_OP_EXEC_REGION_WIN, 0, &opptr_dummy); - - /* present the menu and be done... */ - UI_popup_menu_end(C, pup); - - /* this invoke just calls another instance of this operator... */ - return OPERATOR_INTERFACE; - } - - /* error.. cannot continue */ - BKE_report(op->reports, RPT_ERROR, "Can only make proxy for a referenced object or collection"); - return OPERATOR_CANCELLED; -} - -static int make_proxy_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - Object *ob, *gob = ED_object_active_context(C); - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - - if (gob->instance_collection != NULL) { - const ListBase instance_collection_objects = BKE_collection_object_cache_get( - gob->instance_collection); - Base *base = BLI_findlink(&instance_collection_objects, RNA_enum_get(op->ptr, "object")); - ob = base->object; - } - else { - ob = gob; - gob = NULL; - } - - if (ob) { - Object *newob; - char name[MAX_ID_NAME + 4]; - - BLI_snprintf(name, sizeof(name), "%s_proxy", ((ID *)(gob ? gob : ob))->name + 2); - - /* Add new object for the proxy */ - newob = BKE_object_add_from(bmain, scene, view_layer, OB_EMPTY, name, gob ? gob : ob); - - /* set layers OK */ - BKE_object_make_proxy(bmain, newob, ob, gob); - - /* Set back pointer immediately so dependency graph knows that this is - * is a proxy and will act accordingly. Otherwise correctness of graph - * will depend on order of bases. - * - * TODO(sergey): We really need to get rid of this bi-directional links - * in proxies with something like library overrides. - */ - if (newob->proxy != NULL) { - newob->proxy->proxy_from = newob; - } - else { - BKE_report(op->reports, RPT_ERROR, "Unable to assign proxy"); - } - - /* depsgraph flushes are needed for the new data */ - DEG_relations_tag_update(bmain); - DEG_id_tag_update(&newob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, newob); - } - else { - BKE_report(op->reports, RPT_ERROR, "No object to make proxy for"); - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -/* Generic itemf's for operators that take library args */ -static const EnumPropertyItem *proxy_collection_object_itemf(bContext *C, - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) -{ - EnumPropertyItem item_tmp = {0}, *item = NULL; - int totitem = 0; - int i = 0; - Object *ob = ED_object_active_context(C); - - if (!ob || !ob->instance_collection) { - return DummyRNA_DEFAULT_items; - } - - /* find the object to affect */ - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (ob->instance_collection, object) { - item_tmp.identifier = item_tmp.name = object->id.name + 2; - item_tmp.value = i++; - RNA_enum_item_add(&item, &totitem, &item_tmp); - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - -void OBJECT_OT_proxy_make(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Make Proxy"; - ot->idname = "OBJECT_OT_proxy_make"; - ot->description = "Add empty object to become local replacement data of a library-linked object"; - - /* callbacks */ - ot->invoke = make_proxy_invoke; - ot->exec = make_proxy_exec; - ot->poll = ED_operator_object_active; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - /* XXX, relies on hard coded ID at the moment */ - prop = RNA_def_enum(ot->srna, - "object", - DummyRNA_DEFAULT_items, - 0, - "Proxy Object", - "Name of library-linked/collection object to make a proxy for"); - RNA_def_enum_funcs(prop, proxy_collection_object_itemf); - RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); - ot->prop = prop; -} - -/** \} */ - -/* ------------------------------------------------------------------- */ /** \name Clear Parent Operator * \{ */ diff --git a/source/blender/editors/render/render_shading.c b/source/blender/editors/render/render_shading.c index 8a3d8f9636b..7b2667905ff 100644 --- a/source/blender/editors/render/render_shading.c +++ b/source/blender/editors/render/render_shading.c @@ -690,7 +690,7 @@ static int material_slot_remove_unused_exec(bContext *C, wmOperator *op) Object *ob = objects[ob_index]; int actcol = ob->actcol; for (int slot = 1; slot <= ob->totcol; slot++) { - while (slot <= ob->totcol && !BKE_object_material_slot_used(ob->data, slot)) { + while (slot <= ob->totcol && !BKE_object_material_slot_used(ob, slot)) { ob->actcol = slot; BKE_object_material_slot_remove(bmain, ob); diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index c71e68df2fd..833c9accf95 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1284,8 +1284,8 @@ bool ED_region_is_overlap(int spacetype, int regiontype) RGN_TYPE_TOOLS, RGN_TYPE_UI, RGN_TYPE_TOOL_PROPS, - RGN_TYPE_HEADER, - RGN_TYPE_FOOTER)) { + RGN_TYPE_FOOTER, + RGN_TYPE_TOOL_HEADER)) { return true; } } @@ -1699,6 +1699,9 @@ static void ed_default_handlers( wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "User Interface", 0, 0); WM_event_add_keymap_handler(handlers, keymap); + ListBase *dropboxes = WM_dropboxmap_find("User Interface", 0, 0); + WM_event_add_dropbox_handler(handlers, dropboxes); + /* user interface widgets */ UI_region_handlers_add(handlers); } @@ -3000,7 +3003,7 @@ void ED_region_panels_layout_ex(const bContext *C, /* before setting the view */ if (region_layout_based) { - /* XXX, only single panel support atm. + /* XXX, only single panel support at the moment. * Can't use x/y values calculated above because they're not using the real height of panels, * instead they calculate offsets for the next panel to start drawing. */ Panel *panel = region->panels.last; diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index 2ccefb993c7..3d447d90626 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -1073,9 +1073,14 @@ static eContextResult screen_ctx_ui_list(const bContext *C, bContextDataResult * { wmWindow *win = CTX_wm_window(C); ARegion *region = CTX_wm_region(C); - uiList *list = UI_list_find_mouse_over(region, win->eventstate); - CTX_data_pointer_set(result, NULL, &RNA_UIList, list); - return CTX_RESULT_OK; + if (region) { + uiList *list = UI_list_find_mouse_over(region, win->eventstate); + if (list) { + CTX_data_pointer_set(result, NULL, &RNA_UIList, list); + return CTX_RESULT_OK; + } + } + return CTX_RESULT_NO_DATA; } /* Registry of context callback functions. */ diff --git a/source/blender/editors/screen/screen_intern.h b/source/blender/editors/screen/screen_intern.h index 4016ef84bfd..04ee62b1631 100644 --- a/source/blender/editors/screen/screen_intern.h +++ b/source/blender/editors/screen/screen_intern.h @@ -62,7 +62,7 @@ typedef enum eScreenAxis { #define AREAJOINTOLERANCEY (HEADERY * U.dpi_fac) /* Expanded interaction influence of area borders. */ -#define BORDERPADDING (U.dpi_fac + U.pixelsize) +#define BORDERPADDING ((2.0f * U.dpi_fac) + U.pixelsize) /* area.c */ void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, const bool do_free); diff --git a/source/blender/editors/screen/workspace_edit.c b/source/blender/editors/screen/workspace_edit.c index b99cb831bee..4b81e713080 100644 --- a/source/blender/editors/screen/workspace_edit.c +++ b/source/blender/editors/screen/workspace_edit.c @@ -310,7 +310,14 @@ static int workspace_append_activate_exec(bContext *C, wmOperator *op) RNA_string_get(op->ptr, "filepath", filepath); WorkSpace *appended_workspace = (WorkSpace *)WM_file_append_datablock( - bmain, CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C), filepath, ID_WS, idname); + bmain, + CTX_data_scene(C), + CTX_data_view_layer(C), + CTX_wm_view3d(C), + filepath, + ID_WS, + idname, + BLO_LIBLINK_APPEND_RECURSIVE); if (appended_workspace) { /* Set defaults. */ diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index a58b1947b0c..0176b4a1b13 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -1659,7 +1659,9 @@ static float project_paint_uvpixel_mask(const ProjPaintState *ps, if (other_tpage && (ibuf_other = BKE_image_acquire_ibuf(other_tpage, NULL, NULL))) { const MLoopTri *lt_other = &ps->mlooptri_eval[tri_index]; - const float *lt_other_tri_uv[3] = {PS_LOOPTRI_AS_UV_3(ps->poly_to_loop_uv, lt_other)}; + const float *lt_other_tri_uv[3] = {ps->mloopuv_stencil_eval[lt_other->tri[0]].uv, + ps->mloopuv_stencil_eval[lt_other->tri[1]].uv, + ps->mloopuv_stencil_eval[lt_other->tri[2]].uv}; /* #BKE_image_acquire_ibuf - TODO: this may be slow. */ uchar rgba_ub[4]; diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c index 38165b7622f..1bfe8e1cbf1 100644 --- a/source/blender/editors/sculpt_paint/sculpt_smooth.c +++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c @@ -395,7 +395,12 @@ void SCULPT_smooth(Sculpt *sd, void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; - if (ss->cache->bstrength <= 0.0f) { + + /* NOTE: The enhance brush needs to initialize its state on the first brush step. The stroke + * strength can become 0 during the stroke, but it can not change sign (the sign is determined + * in the beginning of the stroke. So here it is important to not switch to enhance brush in the + * middle of the stroke. */ + if (ss->cache->bstrength < 0.0f) { /* Invert mode, intensify details. */ SCULPT_enhance_details_brush(sd, ob, nodes, totnode); } diff --git a/source/blender/editors/space_action/action_data.c b/source/blender/editors/space_action/action_data.c index 717d87c4972..a4fd2d81778 100644 --- a/source/blender/editors/space_action/action_data.c +++ b/source/blender/editors/space_action/action_data.c @@ -71,7 +71,7 @@ /* ACTION CREATION */ /* Helper function to find the active AnimData block from the Action Editor context */ -AnimData *ED_actedit_animdata_from_context(bContext *C) +AnimData *ED_actedit_animdata_from_context(bContext *C, ID **r_adt_id_owner) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); Object *ob = CTX_data_active_object(C); @@ -82,12 +82,18 @@ AnimData *ED_actedit_animdata_from_context(bContext *C) /* Currently, "Action Editor" means object-level only... */ if (ob) { adt = ob->adt; + if (r_adt_id_owner) { + *r_adt_id_owner = &ob->id; + } } } else if (saction->mode == SACTCONT_SHAPEKEY) { Key *key = BKE_key_from_object(ob); if (key) { adt = key->adt; + if (r_adt_id_owner) { + *r_adt_id_owner = &key->id; + } } } @@ -212,6 +218,7 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op)) bAction *oldact = NULL; AnimData *adt = NULL; + ID *adt_id_owner = NULL; /* hook into UI */ UI_context_active_but_prop_get_templateID(C, &ptr, &prop); @@ -225,13 +232,14 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op)) /* stash the old action to prevent it from being lost */ if (ptr.type == &RNA_AnimData) { adt = ptr.data; + adt_id_owner = ptr.owner_id; } else if (ptr.type == &RNA_SpaceDopeSheetEditor) { - adt = ED_actedit_animdata_from_context(C); + adt = ED_actedit_animdata_from_context(C, &adt_id_owner); } } else { - adt = ED_actedit_animdata_from_context(C); + adt = ED_actedit_animdata_from_context(C, &adt_id_owner); oldact = adt->action; } { @@ -239,8 +247,9 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op)) /* Perform stashing operation - But only if there is an action */ if (adt && oldact) { + BLI_assert(adt_id_owner != NULL); /* stash the action */ - if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ptr.owner_id))) { + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { /* The stash operation will remove the user already * (and unlink the action from the AnimData action slot). * Hence, we must unset the ref to the action in the @@ -306,7 +315,7 @@ static bool action_pushdown_poll(bContext *C) { if (ED_operator_action_active(C)) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); /* Check for AnimData, Actions, and that tweak-mode is off. */ if (adt && saction->action) { @@ -326,7 +335,8 @@ static bool action_pushdown_poll(bContext *C) static int action_pushdown_exec(bContext *C, wmOperator *op) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); /* Do the deed... */ if (adt) { @@ -339,8 +349,7 @@ static int action_pushdown_exec(bContext *C, wmOperator *op) } /* action can be safely added */ - const Object *ob = CTX_data_active_object(C); - BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(ob)); + BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner)); /* Stop displaying this action in this editor * NOTE: The editor itself doesn't set a user... @@ -373,7 +382,8 @@ void ACTION_OT_push_down(wmOperatorType *ot) static int action_stash_exec(bContext *C, wmOperator *op) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); /* Perform stashing operation */ if (adt) { @@ -385,8 +395,7 @@ static int action_stash_exec(bContext *C, wmOperator *op) } /* stash the action */ - Object *ob = CTX_data_active_object(C); - if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ob))) { + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { /* The stash operation will remove the user already, * so the flushing step later shouldn't double up * the user-count fixes. Hence, we must unset this ref @@ -439,7 +448,7 @@ void ACTION_OT_stash(wmOperatorType *ot) static bool action_stash_create_poll(bContext *C) { if (ED_operator_action_active(C)) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); /* Check tweak-mode is off (as you don't want to be tampering with the action in that case) */ /* NOTE: unlike for pushdown, @@ -471,7 +480,8 @@ static bool action_stash_create_poll(bContext *C) static int action_stash_create_exec(bContext *C, wmOperator *op) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); /* Check for no action... */ if (saction->action == NULL) { @@ -488,8 +498,7 @@ static int action_stash_create_exec(bContext *C, wmOperator *op) } /* stash the action */ - Object *ob = CTX_data_active_object(C); - if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ob))) { + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { bAction *new_action = NULL; /* Create new action not based on the old one @@ -636,7 +645,7 @@ static bool action_unlink_poll(bContext *C) { if (ED_operator_action_active(C)) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); /* Only when there's an active action, in the right modes... */ if (saction->action && adt) { @@ -650,7 +659,7 @@ static bool action_unlink_poll(bContext *C) static int action_unlink_exec(bContext *C, wmOperator *op) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); bool force_delete = RNA_boolean_get(op->ptr, "force_delete"); if (adt && adt->action) { @@ -775,7 +784,7 @@ static bool action_layer_next_poll(bContext *C) { /* Action Editor's action editing modes only */ if (ED_operator_action_active(C)) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); if (adt) { /* only allow if we're in tweak-mode, and there's something above us... */ if (adt->flag & ADT_NLA_EDIT_ON) { @@ -809,7 +818,7 @@ static bool action_layer_next_poll(bContext *C) static int action_layer_next_exec(bContext *C, wmOperator *op) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); NlaTrack *act_track; Scene *scene = CTX_data_scene(C); @@ -886,7 +895,7 @@ static bool action_layer_prev_poll(bContext *C) { /* Action Editor's action editing modes only */ if (ED_operator_action_active(C)) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); if (adt) { if (adt->flag & ADT_NLA_EDIT_ON) { /* Tweak Mode: We need to check if there are any tracks below the active one @@ -920,7 +929,7 @@ static bool action_layer_prev_poll(bContext *C) static int action_layer_prev_exec(bContext *C, wmOperator *op) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); NlaTrack *act_track; NlaTrack *nlt; diff --git a/source/blender/editors/space_action/space_action.c b/source/blender/editors/space_action/space_action.c index f59429ba981..738eeb21e2e 100644 --- a/source/blender/editors/space_action/space_action.c +++ b/source/blender/editors/space_action/space_action.c @@ -247,7 +247,7 @@ static void action_main_region_draw_overlay(const bContext *C, ARegion *region) View2D *v2d = ®ion->v2d; /* scrubbing region */ - ED_time_scrub_draw_current_frame(region, scene, saction->flag & SACTION_DRAWTIME, true); + ED_time_scrub_draw_current_frame(region, scene, saction->flag & SACTION_DRAWTIME); /* scrollers */ UI_view2d_scrollers_draw(v2d, NULL); diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 404c5698b42..149067a94fe 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -177,6 +177,7 @@ void ED_spacemacros_init(void) ED_operatormacros_gpencil(); /* Register dropboxes (can use macros). */ + ED_dropboxes_ui(); const ListBase *spacetypes = BKE_spacetypes_list(); LISTBASE_FOREACH (const SpaceType *, type, spacetypes) { if (type->dropboxes) { diff --git a/source/blender/editors/space_clip/clip_dopesheet_draw.c b/source/blender/editors/space_clip/clip_dopesheet_draw.c index 8aaf3faffec..42fda3ef464 100644 --- a/source/blender/editors/space_clip/clip_dopesheet_draw.c +++ b/source/blender/editors/space_clip/clip_dopesheet_draw.c @@ -224,7 +224,7 @@ void clip_draw_dopesheet_main(SpaceClip *sc, ARegion *region, Scene *scene) uint flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f( "ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1); diff --git a/source/blender/editors/space_clip/tracking_ops.c b/source/blender/editors/space_clip/tracking_ops.c index ff62bcf0cfa..4965099642b 100644 --- a/source/blender/editors/space_clip/tracking_ops.c +++ b/source/blender/editors/space_clip/tracking_ops.c @@ -417,7 +417,7 @@ static SlideMarkerData *create_slide_marker_data(SpaceClip *sc, data->pos = marker->pos; data->offset = track->offset; data->old_markers = MEM_callocN(sizeof(*data->old_markers) * track->markersnr, - "slide marekrs"); + "slide markers"); for (int a = 0; a < track->markersnr; a++) { copy_v2_v2(data->old_markers[a], track->markers[a].pos); } diff --git a/source/blender/editors/space_clip/tracking_ops_track.c b/source/blender/editors/space_clip/tracking_ops_track.c index 0a99d1020f6..0e78a3e9a1e 100644 --- a/source/blender/editors/space_clip/tracking_ops_track.c +++ b/source/blender/editors/space_clip/tracking_ops_track.c @@ -196,8 +196,8 @@ static bool track_markers_initjob(bContext *C, TrackMarkersJob *tmj, bool backwa /* XXX: silly to store this, but this data is needed to update scene and * movie-clip numbers when tracking is finished. This introduces * better feedback for artists. - * Maybe there's another way to solve this problem, but can't think - * better way atm. + * Maybe there's another way to solve this problem, + * but can't think better way at the moment. * Anyway, this way isn't more unstable as animation rendering * animation which uses the same approach (except storing screen). */ diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index 993a52b9084..4b508f16c1e 100644 --- a/source/blender/editors/space_file/CMakeLists.txt +++ b/source/blender/editors/space_file/CMakeLists.txt @@ -34,6 +34,7 @@ set(INC ) set(SRC + asset_catalog_tree_view.cc file_draw.c file_ops.c file_panels.c @@ -49,6 +50,7 @@ set(SRC ) set(LIB + bf_blenkernel ) if(WITH_HEADLESS) diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc new file mode 100644 index 00000000000..7eea9af925b --- /dev/null +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -0,0 +1,230 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2007 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup spfile + */ + +#include "ED_fileselect.h" + +#include "DNA_space_types.h" + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" + +#include "BLI_string_ref.hh" + +#include "BLT_translation.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_resources.h" +#include "UI_tree_view.hh" + +#include "WM_api.h" +#include "WM_types.h" + +#include "file_intern.h" + +using namespace blender; +using namespace blender::bke; + +namespace blender::ed::asset_browser { + +class AssetCatalogTreeView : public ui::AbstractTreeView { + /** The asset catalog tree this tree-view represents. */ + bke::AssetCatalogTree *catalog_tree_; + FileAssetSelectParams *params_; + + friend class AssetCatalogTreeViewItem; + + public: + AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params); + + void build_tree() override; + + private: + ui::BasicTreeViewItem &build_catalog_items_recursive(ui::TreeViewItemContainer &view_parent_item, + AssetCatalogTreeItem &catalog); + + void add_all_item(); + void add_unassigned_item(); + bool is_active_catalog(CatalogID catalog_id) const; +}; +/* ---------------------------------------------------------------------- */ + +class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem { + /** The catalog tree item this tree view item represents. */ + AssetCatalogTreeItem &catalog_item_; + + public: + AssetCatalogTreeViewItem(AssetCatalogTreeItem *catalog_item) + : BasicTreeViewItem(catalog_item->get_name()), catalog_item_(*catalog_item) + { + } + + void on_activate() override + { + const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( + get_tree_view()); + tree_view.params_->asset_catalog_visibility = FILE_SHOW_ASSETS_FROM_CATALOG; + tree_view.params_->catalog_id = catalog_item_.get_catalog_id(); + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + } + + void build_row(uiLayout &row) override + { + ui::BasicTreeViewItem::build_row(row); + + if (!is_active()) { + return; + } + + PointerRNA *props; + const CatalogID catalog_id = catalog_item_.get_catalog_id(); + + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + RNA_string_set(props, "parent_path", catalog_item_.catalog_path().c_str()); + + /* Tree items without a catalog ID represent components of catalog paths that are not + * associated with an actual catalog. They exist merely by the presence of a child catalog, and + * thus cannot be deleted themselves. */ + if (!BLI_uuid_is_nil(catalog_id)) { + char catalog_id_str_buffer[UUID_STRING_LEN] = ""; + BLI_uuid_format(catalog_id_str_buffer, catalog_id); + + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_delete", WM_OP_INVOKE_DEFAULT, ICON_X); + RNA_string_set(props, "catalog_id", catalog_id_str_buffer); + } + } +}; + +/** Only reason this isn't just `BasicTreeViewItem` is to add a '+' icon for adding a root level + * catalog. */ +class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { + using BasicTreeViewItem::BasicTreeViewItem; + + void build_row(uiLayout &row) override + { + ui::BasicTreeViewItem::build_row(row); + + if (!is_active()) { + return; + } + + PointerRNA *props; + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + /* No parent path to use the root level. */ + RNA_string_set(props, "parent_path", nullptr); + } +}; + +AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params) + : catalog_tree_(BKE_asset_library_get_catalog_tree(library)), params_(params) +{ +} + +void AssetCatalogTreeView::build_tree() +{ + add_all_item(); + + if (catalog_tree_) { + catalog_tree_->foreach_root_item([this](AssetCatalogTreeItem &item) { + ui::BasicTreeViewItem &child_view_item = build_catalog_items_recursive(*this, item); + + /* Open root-level items by default. */ + child_view_item.set_collapsed(false); + }); + } + + add_unassigned_item(); +} + +ui::BasicTreeViewItem &AssetCatalogTreeView::build_catalog_items_recursive( + ui::TreeViewItemContainer &view_parent_item, AssetCatalogTreeItem &catalog) +{ + ui::BasicTreeViewItem &view_item = view_parent_item.add_tree_item<AssetCatalogTreeViewItem>( + &catalog); + if (is_active_catalog(catalog.get_catalog_id())) { + view_item.set_active(); + } + + catalog.foreach_child([&view_item, this](AssetCatalogTreeItem &child) { + build_catalog_items_recursive(view_item, child); + }); + return view_item; +} + +void AssetCatalogTreeView::add_all_item() +{ + FileAssetSelectParams *params = params_; + + ui::AbstractTreeViewItem &item = add_tree_item<AssetCatalogTreeViewAllItem>( + IFACE_("All"), ICON_HOME, [params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_ALL_CATALOGS; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_ALL_CATALOGS) { + item.set_active(); + } +} + +void AssetCatalogTreeView::add_unassigned_item() +{ + FileAssetSelectParams *params = params_; + + ui::AbstractTreeViewItem &item = add_tree_item<ui::BasicTreeViewItem>( + IFACE_("Unassigned"), ICON_FILE_HIDDEN, [params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_WITHOUT_CATALOG; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_WITHOUT_CATALOG) { + item.set_active(); + } +} + +bool AssetCatalogTreeView::is_active_catalog(CatalogID catalog_id) const +{ + return (params_->asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG) && + (params_->catalog_id == catalog_id); +} + +} // namespace blender::ed::asset_browser + +/* ---------------------------------------------------------------------- */ + +void file_create_asset_catalog_tree_view_in_layout(::AssetLibrary *asset_library, + uiLayout *layout, + FileAssetSelectParams *params) +{ + uiBlock *block = uiLayoutGetBlock(layout); + + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "asset catalog tree view", + std::make_unique<ed::asset_browser::AssetCatalogTreeView>(asset_library, params)); + + ui::TreeViewBuilder builder(*block); + builder.build_tree_view(*tree_view); +} diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 905c0aeb8e0..d39aefff691 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -23,12 +23,19 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + /* internal exports only */ struct ARegion; struct ARegionType; +struct AssetLibrary; struct FileSelectParams; +struct FileAssetSelectParams; struct SpaceFile; +struct uiLayout; struct View2D; /* file_draw.c */ @@ -147,8 +154,19 @@ void file_on_reload_callback_register(struct SpaceFile *sfile, /* file_panels.c */ void file_tool_props_region_panels_register(struct ARegionType *art); void file_execute_region_panels_register(struct ARegionType *art); +void file_tools_region_panels_register(struct ARegionType *art); /* file_utils.c */ void file_tile_boundbox(const ARegion *region, FileLayout *layout, const int file, rcti *r_bounds); void file_path_to_ui_path(const char *path, char *r_pathi, int max_size); + +/* asset_catalog_tree_view.cc */ + +void file_create_asset_catalog_tree_view_in_layout(struct AssetLibrary *asset_library, + struct uiLayout *layout, + struct FileAssetSelectParams *params); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 2f1acd2ca4d..d0f2a4fdc4c 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -536,7 +536,7 @@ static rcti file_select_mval_to_select_rect(const int mval[2]) return rect; } -static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int file_select_exec(bContext *C, wmOperator *op) { ARegion *region = CTX_wm_region(C); SpaceFile *sfile = CTX_wm_space_file(C); @@ -549,17 +549,27 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) const bool only_activate_if_selected = RNA_boolean_get(op->ptr, "only_activate_if_selected"); /* Used so right mouse clicks can do both, activate and spawn the context menu. */ const bool pass_through = RNA_boolean_get(op->ptr, "pass_through"); + bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); if (region->regiontype != RGN_TYPE_WINDOW) { return OPERATOR_CANCELLED; } - rect = file_select_mval_to_select_rect(event->mval); + int mval[2]; + mval[0] = RNA_int_get(op->ptr, "mouse_x"); + mval[1] = RNA_int_get(op->ptr, "mouse_y"); + rect = file_select_mval_to_select_rect(mval); if (!ED_fileselect_layout_is_inside_pt(sfile->layout, ®ion->v2d, rect.xmin, rect.ymin)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } + if (extend || fill) { + wait_to_deselect_others = false; + } + + int ret_val = OPERATOR_FINISHED; + const FileSelectParams *params = ED_fileselect_get_active_params(sfile); if (sfile && params) { int idx = params->highlight_file; @@ -571,6 +581,9 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (only_activate_if_selected && is_selected) { /* Don't deselect other items. */ } + else if (wait_to_deselect_others && is_selected) { + ret_val = OPERATOR_RUNNING_MODAL; + } /* single select, deselect all selected first */ else if (!extend) { file_select_deselect_all(sfile, FILE_SEL_SELECTED); @@ -601,7 +614,10 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) WM_event_add_mousemove(CTX_wm_window(C)); /* for directory changes */ WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); - return pass_through ? (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH) : OPERATOR_FINISHED; + if ((ret_val == OPERATOR_FINISHED) && pass_through) { + ret_val |= OPERATOR_PASS_THROUGH; + } + return ret_val; } void FILE_OT_select(wmOperatorType *ot) @@ -614,11 +630,14 @@ void FILE_OT_select(wmOperatorType *ot) ot->description = "Handle mouse clicks to select and activate items"; /* api callbacks */ - ot->invoke = file_select_invoke; + ot->invoke = WM_generic_select_invoke; + ot->exec = file_select_exec; + ot->modal = WM_generic_select_modal; /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; /* properties */ + WM_operator_properties_generic_select(ot); prop = RNA_def_boolean(ot->srna, "extend", false, diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c index 7032d55b331..95aad202f1a 100644 --- a/source/blender/editors/space_file/file_panels.c +++ b/source/blender/editors/space_file/file_panels.c @@ -47,6 +47,7 @@ #include "WM_types.h" #include "file_intern.h" +#include "filelist.h" #include "fsmenu.h" #include <string.h> @@ -57,6 +58,12 @@ static bool file_panel_operator_poll(const bContext *C, PanelType *UNUSED(pt)) return (sfile && sfile->op); } +static bool file_panel_asset_browsing_poll(const bContext *C, PanelType *UNUSED(pt)) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + return sfile && sfile->files && ED_fileselect_is_asset_browser(sfile); +} + static void file_panel_operator_header(const bContext *C, Panel *panel) { SpaceFile *sfile = CTX_wm_space_file(C); @@ -222,3 +229,28 @@ void file_execute_region_panels_register(ARegionType *art) pt->draw = file_panel_execution_buttons_draw; BLI_addtail(&art->paneltypes, pt); } + +static void file_panel_asset_catalog_buttons_draw(const bContext *C, Panel *panel) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + /* May be null if the library wasn't loaded yet. */ + struct AssetLibrary *asset_library = filelist_asset_library(sfile->files); + FileAssetSelectParams *params = ED_fileselect_get_asset_params(sfile); + BLI_assert(params != NULL); + + file_create_asset_catalog_tree_view_in_layout(asset_library, panel->layout, params); +} + +void file_tools_region_panels_register(ARegionType *art) +{ + PanelType *pt; + + pt = MEM_callocN(sizeof(PanelType), "spacetype file asset catalog buttons"); + strcpy(pt->idname, "FILE_PT_asset_catalog_buttons"); + strcpy(pt->label, N_("Asset Catalogs")); + strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + pt->flag = PANEL_TYPE_NO_HEADER; + pt->poll = file_panel_asset_browsing_poll; + pt->draw = file_panel_asset_catalog_buttons_draw; + BLI_addtail(&art->paneltypes, pt); +} diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 511b5b255e9..bdf61e792d3 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -50,12 +50,14 @@ #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLI_uuid.h" #ifdef WIN32 # include "BLI_winstuff.h" #endif #include "BKE_asset.h" +#include "BKE_asset_library.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_icons.h" @@ -368,6 +370,9 @@ typedef struct FileListFilter { char filter_glob[FILE_MAXFILE]; char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ short flags; + + eFileSel_Params_AssetCatalogVisibility asset_catalog_visibility; + bUUID asset_catalog_id; } FileListFilter; /* FileListFilter.flags */ @@ -386,6 +391,7 @@ typedef struct FileList { eFileSelectType type; /* The library this list was created for. Stored here so we know when to re-read. */ AssetLibraryReference *asset_library_ref; + struct AssetLibrary *asset_library; short flags; @@ -471,6 +477,10 @@ static void filelist_readjob_dir(struct FileListReadJob *job_params, short *stop, short *do_update, float *progress); +static void filelist_readjob_asset_library(struct FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); static void filelist_readjob_main_assets(struct FileListReadJob *job_params, short *stop, short *do_update, @@ -887,6 +897,39 @@ static bool is_filtered_id_file(const FileListInternEntry *file, return is_filtered; } +/** + * Get the asset metadata of a file, if it represents an asset. This may either be of a local ID + * (ID in the current #Main) or read from an external asset library. + */ +static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file) +{ + const ID *local_id = file->local_data.id; + return local_id ? local_id->asset_data : file->imported_asset_data; +} + +static bool is_filtered_asset(FileListInternEntry *file, FileListFilter *filter) +{ + const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file); + bool is_visible = false; + + switch (filter->asset_catalog_visibility) { + case FILE_SHOW_ASSETS_WITHOUT_CATALOG: + is_visible = BLI_uuid_is_nil(asset_data->catalog_id); + break; + case FILE_SHOW_ASSETS_FROM_CATALOG: + /* TODO show all assets that are in child catalogs of the selected catalog. */ + is_visible = !BLI_uuid_is_nil(filter->asset_catalog_id) && + BLI_uuid_equal(filter->asset_catalog_id, asset_data->catalog_id); + break; + case FILE_SHOW_ASSETS_ALL_CATALOGS: + /* All asset files should be visible. */ + is_visible = true; + break; + } + + return is_visible; +} + static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) { bool is_filtered; @@ -904,6 +947,13 @@ static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileLis return is_filtered; } +static bool is_filtered_asset_library(FileListInternEntry *file, + const char *root, + FileListFilter *filter) +{ + return is_filtered_lib(file, root, filter) && is_filtered_asset(file, filter); +} + static bool is_filtered_main(FileListInternEntry *file, const char *UNUSED(dir), FileListFilter *filter) @@ -916,7 +966,8 @@ static bool is_filtered_main_assets(FileListInternEntry *file, FileListFilter *filter) { /* "Filtered" means *not* being filtered out... So return true if the file should be visible. */ - return is_filtered_id_file(file, file->relpath, file->name, filter); + return is_filtered_id_file(file, file->relpath, file->name, filter) && + is_filtered_asset(file, filter); } static void filelist_filter_clear(FileList *filelist) @@ -1032,6 +1083,33 @@ void filelist_setfilter_options(FileList *filelist, } /** + * \param catalog_id: The catalog that should be filtered by if \a catalog_visibility is + * #FILE_SHOW_ASSETS_FROM_CATALOG. May be NULL otherwise. + */ +void filelist_set_asset_catalog_filter_options( + FileList *filelist, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + const bUUID *catalog_id) +{ + bool update = false; + + if (filelist->filter_data.asset_catalog_visibility != catalog_visibility) { + filelist->filter_data.asset_catalog_visibility = catalog_visibility; + update = true; + } + + if (filelist->filter_data.asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG && + catalog_id && !BLI_uuid_equal(filelist->filter_data.asset_catalog_id, *catalog_id)) { + filelist->filter_data.asset_catalog_id = *catalog_id; + update = true; + } + + if (update) { + filelist_filter_clear(filelist); + } +} + +/** * Checks two libraries for equality. * \return True if the libraries match. */ @@ -1723,6 +1801,11 @@ void filelist_settype(FileList *filelist, short type) filelist->read_job_fn = filelist_readjob_lib; filelist->filter_fn = is_filtered_lib; break; + case FILE_ASSET_LIBRARY: + filelist->check_dir_fn = filelist_checkdir_lib; + filelist->read_job_fn = filelist_readjob_asset_library; + filelist->filter_fn = is_filtered_asset_library; + break; case FILE_MAIN_ASSET: filelist->check_dir_fn = filelist_checkdir_main_assets; filelist->read_job_fn = filelist_readjob_main_assets; @@ -1739,7 +1822,10 @@ void filelist_settype(FileList *filelist, short type) filelist->flags |= FL_FORCE_RESET; } -void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection) +void filelist_clear_ex(struct FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection) { if (!filelist) { return; @@ -1758,11 +1844,18 @@ void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const boo if (do_selection && filelist->selection_state) { BLI_ghash_clear(filelist->selection_state, NULL, NULL); } + + if (do_asset_library && (filelist->asset_library != NULL)) { + /* There is no way to refresh the catalogs stored by the AssetLibrary struct, so instead of + * "clearing" it, the entire struct is freed. It will be reallocated when needed. */ + BKE_asset_library_free(filelist->asset_library); + filelist->asset_library = NULL; + } } void filelist_clear(struct FileList *filelist) { - filelist_clear_ex(filelist, true, true); + filelist_clear_ex(filelist, true, true, true); } void filelist_free(struct FileList *filelist) @@ -1773,7 +1866,7 @@ void filelist_free(struct FileList *filelist) } /* No need to clear cache & selection_state, we free them anyway. */ - filelist_clear_ex(filelist, false, false); + filelist_clear_ex(filelist, true, false, false); filelist_cache_free(&filelist->filelist_cache); if (filelist->selection_state) { @@ -1788,6 +1881,11 @@ void filelist_free(struct FileList *filelist) filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING); } +AssetLibrary *filelist_asset_library(FileList *filelist) +{ + return filelist->asset_library; +} + void filelist_freelib(struct FileList *filelist) { if (filelist->libfiledata) { @@ -2879,76 +2977,129 @@ static int filelist_readjob_list_dir(const char *root, return nbr_entries; } -static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar) +typedef enum ListLibOptions { + /* Will read both the groups + actual ids from the library. Reduces the amount of times that + * a library needs to be opened. */ + LIST_LIB_RECURSIVE = (1 << 0), + + /* Will only list assets. */ + LIST_LIB_ASSETS_ONLY = (1 << 1), + + /* Add given root as result. */ + LIST_LIB_ADD_PARENT = (1 << 2), +} ListLibOptions; + +static FileListInternEntry *filelist_readjob_list_lib_group_create(const int idcode, + const char *group_name) +{ + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = BLI_strdup(group_name); + entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR; + entry->blentype = idcode; + return entry; +} + +static void filelist_readjob_list_lib_add_datablocks(ListBase *entries, + LinkNode *datablock_infos, + const bool prefix_relpath_with_group_name, + const int idcode, + const char *group_name) +{ + for (LinkNode *ln = datablock_infos; ln; ln = ln->next) { + struct BLODataBlockInfo *info = ln->link; + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); + if (prefix_relpath_with_group_name) { + entry->relpath = BLI_sprintfN("%s/%s", group_name, info->name); + } + else { + entry->relpath = BLI_strdup(info->name); + } + entry->typeflag |= FILE_TYPE_BLENDERLIB; + if (info && info->asset_data) { + entry->typeflag |= FILE_TYPE_ASSET; + /* Moves ownership! */ + entry->imported_asset_data = info->asset_data; + } + entry->blentype = idcode; + BLI_addtail(entries, entry); + } +} + +static int filelist_readjob_list_lib(const char *root, + ListBase *entries, + const ListLibOptions options) { - FileListInternEntry *entry; - LinkNode *ln, *names = NULL, *datablock_infos = NULL; - int i, nitems, idcode = 0, nbr_entries = 0; char dir[FILE_MAX_LIBEXTRA], *group; - bool ok; struct BlendHandle *libfiledata = NULL; - /* name test */ - ok = BLO_library_path_explode(root, dir, &group, NULL); - if (!ok) { - return nbr_entries; + /* Check if the given root is actually a library. All folders are passed to + * `filelist_readjob_list_lib` and based on the number of found entries `filelist_readjob_do` + * will do a dir listing only when this function does not return any entries. */ + /* TODO: We should consider introducing its own function to detect if it is a lib and + * call it directly from `filelist_readjob_do` to increase readability. */ + const bool is_lib = BLO_library_path_explode(root, dir, &group, NULL); + if (!is_lib) { + return 0; } - /* there we go */ + /* Open the library file. */ BlendFileReadReport bf_reports = {.reports = NULL}; libfiledata = BLO_blendhandle_from_file(dir, &bf_reports); if (libfiledata == NULL) { - return nbr_entries; - } - - /* memory for strings is passed into filelist[i].entry->relpath - * and freed in filelist_entry_free. */ - if (group) { - idcode = groupname_to_code(group); - datablock_infos = BLO_blendhandle_get_datablock_info(libfiledata, idcode, &nitems); - } - else { - names = BLO_blendhandle_get_linkable_groups(libfiledata); - nitems = BLI_linklist_count(names); + return 0; } - BLO_blendhandle_close(libfiledata); - - if (!skip_currpar) { - entry = MEM_callocN(sizeof(*entry), __func__); + /* Add current parent when requested. */ + int parent_len = 0; + if (options & LIST_LIB_ADD_PARENT) { + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); entry->relpath = BLI_strdup(FILENAME_PARENT); entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); BLI_addtail(entries, entry); - nbr_entries++; + parent_len = 1; } - for (i = 0, ln = (datablock_infos ? datablock_infos : names); i < nitems; i++, ln = ln->next) { - struct BLODataBlockInfo *info = datablock_infos ? ln->link : NULL; - const char *blockname = info ? info->name : ln->link; - - entry = MEM_callocN(sizeof(*entry), __func__); - entry->relpath = BLI_strdup(blockname); - entry->typeflag |= FILE_TYPE_BLENDERLIB; - if (info && info->asset_data) { - entry->typeflag |= FILE_TYPE_ASSET; - /* Moves ownership! */ - entry->imported_asset_data = info->asset_data; - } - if (!(group && idcode)) { - entry->typeflag |= FILE_TYPE_DIR; - entry->blentype = groupname_to_code(blockname); - } - else { - entry->blentype = idcode; + int group_len = 0; + int datablock_len = 0; + const bool group_came_from_path = group != NULL; + if (group_came_from_path) { + const int idcode = groupname_to_code(group); + LinkNode *datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &datablock_len); + filelist_readjob_list_lib_add_datablocks(entries, datablock_infos, false, idcode, group); + BLI_linklist_freeN(datablock_infos); + } + else { + LinkNode *groups = BLO_blendhandle_get_linkable_groups(libfiledata); + group_len = BLI_linklist_count(groups); + + for (LinkNode *ln = groups; ln; ln = ln->next) { + const char *group_name = ln->link; + const int idcode = groupname_to_code(group_name); + FileListInternEntry *group_entry = filelist_readjob_list_lib_group_create(idcode, + group_name); + BLI_addtail(entries, group_entry); + + if (options & LIST_LIB_RECURSIVE) { + int group_datablock_len; + LinkNode *group_datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &group_datablock_len); + filelist_readjob_list_lib_add_datablocks( + entries, group_datablock_infos, true, idcode, group_name); + BLI_linklist_freeN(group_datablock_infos); + datablock_len += group_datablock_len; + } } - BLI_addtail(entries, entry); - nbr_entries++; + + BLI_linklist_freeN(groups); } - BLI_linklist_freeN(datablock_infos ? datablock_infos : names); + BLO_blendhandle_close(libfiledata); - return nbr_entries; + /* Return the number of items added to entries. */ + int added_entries_len = group_len + datablock_len + parent_len; + return added_entries_len; } #if 0 @@ -3136,11 +3287,43 @@ typedef struct FileListReadJob { * The job system calls #filelist_readjob_update which moves any read file from #tmp_filelist * into #filelist in a thread-safe way. * + * #tmp_filelist also keeps an `AssetLibrary *` so that it can be loaded in the same thread, and + * moved to #filelist once all categories are loaded. + * * NOTE: #tmp_filelist is freed in #filelist_readjob_free, so any copied pointers need to be set * to NULL to avoid double-freeing them. */ struct FileList *tmp_filelist; } FileListReadJob; +static bool filelist_readjob_should_recurse_into_entry(const int max_recursion, + const int current_recursion_level, + FileListInternEntry *entry) +{ + if (max_recursion == 0) { + /* Recursive loading is disabled. */ + return false; + } + if (current_recursion_level >= max_recursion) { + /* No more levels of recursion left. */ + return false; + } + if (entry->typeflag & FILE_TYPE_BLENDERLIB) { + /* Libraries are already loaded recursively when recursive loaded is used. No need to add + * them another time. This loading is done with the `LIST_LIB_RECURSIVE` option. */ + return false; + } + if (!(entry->typeflag & FILE_TYPE_DIR)) { + /* Cannot recurse into regular file entries. */ + return false; + } + if (FILENAME_IS_CURRPAR(entry->relpath)) { + /* Don't schedule go to parent entry, (`..`) */ + return false; + } + + return true; +} + static void filelist_readjob_do(const bool do_lib, FileListReadJob *job_params, const short *stop, @@ -3177,7 +3360,6 @@ static void filelist_readjob_do(const bool do_lib, while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { FileListInternEntry *entry; int nbr_entries = 0; - bool is_lib = do_lib; char *subdir; char rel_subdir[FILE_MAX_LIBEXTRA]; @@ -3200,45 +3382,54 @@ static void filelist_readjob_do(const bool do_lib, BLI_path_normalize_dir(root, rel_subdir); BLI_path_rel(rel_subdir, root); + bool is_lib = false; if (do_lib) { - nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar); + ListLibOptions list_lib_options = 0; + if (!skip_currpar) { + list_lib_options |= LIST_LIB_ADD_PARENT; + } + + /* Libraries are loaded recursively when max_recursion is set. It doesn't check if there is + * still a recursion level over. */ + if (max_recursion > 0) { + list_lib_options |= LIST_LIB_RECURSIVE; + } + /* Only load assets when browsing an asset library. For normal file browsing we return all + * entries. `FLF_ASSETS_ONLY` filter can be enabled/disabled by the user.*/ + if (filelist->asset_library_ref) { + list_lib_options |= LIST_LIB_ASSETS_ONLY; + } + nbr_entries = filelist_readjob_list_lib(subdir, &entries, list_lib_options); + if (nbr_entries > 0) { + is_lib = true; + } } - if (!nbr_entries) { - is_lib = false; + + if (!is_lib) { nbr_entries = filelist_readjob_list_dir( subdir, &entries, filter_glob, do_lib, job_params->main_name, skip_currpar); } for (entry = entries.first; entry; entry = entry->next) { - BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); - entry->uid = filelist_uid_generate(filelist); - /* Only thing we change in direntry here, so we need to free it first. */ + /* When loading entries recursive, the rel_path should be relative from the root dir. + * we combine the relative path to the subdir with the relative path of the entry. */ + BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); MEM_freeN(entry->relpath); entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' * added by BLI_path_rel to rel_subdir. */ entry->name = fileentry_uiname(root, entry->relpath, entry->typeflag, dir); entry->free_name = true; - /* Here we decide whether current filedirentry is to be listed too, or not. */ - if (max_recursion && (is_lib || (recursion_level <= max_recursion))) { - if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) { - /* Skip... */ - } - else if (!is_lib && (recursion_level >= max_recursion) && - ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) { - /* Do not recurse in real directories in this case, only in .blend libs. */ - } - else { - /* We have a directory we want to list, add it to todo list! */ - BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); - BLI_path_normalize_dir(job_params->main_name, dir); - td_dir = BLI_stack_push_r(todo_dirs); - td_dir->level = recursion_level + 1; - td_dir->dir = BLI_strdup(dir); - nbr_todo_dirs++; - } + if (filelist_readjob_should_recurse_into_entry(max_recursion, recursion_level, entry)) { + /* We have a directory we want to list, add it to todo list! */ + BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); + BLI_path_normalize_dir(job_params->main_name, dir); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = recursion_level + 1; + td_dir->dir = BLI_strdup(dir); + nbr_todo_dirs++; } } @@ -3284,6 +3475,47 @@ static void filelist_readjob_lib(FileListReadJob *job_params, filelist_readjob_do(true, job_params, stop, do_update, progress); } +static void filelist_asset_library_path(const FileListReadJob *job_params, + char r_library_root_path[FILE_MAX]) +{ + if (job_params->filelist->type == FILE_MAIN_ASSET) { + /* For the "Current File" library (#FILE_MAIN_ASSET) we get the asset library root path based + * on main. */ + BKE_asset_library_find_suitable_root_path_from_main(job_params->current_main, + r_library_root_path); + } + else { + BLI_strncpy(r_library_root_path, job_params->tmp_filelist->filelist.root, FILE_MAX); + } +} + +/** + * Load asset library data, which currently means loading the asset catalogs for the library. + */ +static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params, short *do_update) +{ + FileList *tmp_filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + if (job_params->filelist->asset_library_ref != NULL) { + char library_root_path[FILE_MAX]; + filelist_asset_library_path(job_params, library_root_path); + + /* Load asset catalogs, into the temp filelist for thread-safety. + * #filelist_readjob_endjob() will move it into the real filelist. */ + tmp_filelist->asset_library = BKE_asset_library_load(library_root_path); + *do_update = true; + } +} + +static void filelist_readjob_asset_library(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + filelist_readjob_load_asset_library_data(job_params, do_update); + filelist_readjob_lib(job_params, stop, do_update, progress); +} + static void filelist_readjob_main(FileListReadJob *job_params, short *stop, short *do_update, @@ -3305,6 +3537,8 @@ static void filelist_readjob_main_assets(FileListReadJob *job_params, BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET)); + filelist_readjob_load_asset_library_data(job_params, do_update); + /* A valid, but empty directory from now. */ filelist->filelist.nbr_entries = 0; @@ -3355,6 +3589,8 @@ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update BLI_mutex_lock(&flrj->lock); BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist); + BLI_assert_msg(flrj->filelist->asset_library == NULL, + "Asset library should not yet be assigned at start of read job"); flrj->tmp_filelist = MEM_dupallocN(flrj->filelist); @@ -3394,11 +3630,17 @@ static void filelist_readjob_update(void *flrjv) flrj->tmp_filelist->filelist.nbr_entries = 0; } + if (flrj->tmp_filelist->asset_library) { + flrj->filelist->asset_library = flrj->tmp_filelist->asset_library; + flrj->tmp_filelist->asset_library = NULL; /* MUST be NULL to avoid double-free. */ + } + BLI_mutex_unlock(&flrj->lock); if (new_nbr_entries) { - /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! */ - filelist_clear_ex(flrj->filelist, true, false); + /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! Keep + * the asset library data we just read. */ + filelist_clear_ex(flrj->filelist, false, true, false); flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING); } diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h index 1fb05e0f9ac..d1f37b5b365 100644 --- a/source/blender/editors/space_file/filelist.h +++ b/source/blender/editors/space_file/filelist.h @@ -31,6 +31,7 @@ struct AssetLibraryReference; struct BlendHandle; struct FileList; struct FileSelection; +struct bUUID; struct wmWindowManager; struct FileDirEntry; @@ -71,6 +72,10 @@ void filelist_setfilter_options(struct FileList *filelist, const bool filter_assets_only, const char *filter_glob, const char *filter_search); +void filelist_set_asset_catalog_filter_options( + struct FileList *filelist, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + const struct bUUID *catalog_id); void filelist_filter(struct FileList *filelist); void filelist_setlibrary(struct FileList *filelist, const struct AssetLibraryReference *asset_library_ref); @@ -86,7 +91,10 @@ int filelist_geticon(struct FileList *filelist, const int index, const bool is_m struct FileList *filelist_new(short type); void filelist_settype(struct FileList *filelist, short type); void filelist_clear(struct FileList *filelist); -void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection); +void filelist_clear_ex(struct FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection); void filelist_free(struct FileList *filelist); const char *filelist_dir(struct FileList *filelist); @@ -141,6 +149,8 @@ void filelist_entry_parent_select_set(struct FileList *filelist, void filelist_setrecursion(struct FileList *filelist, const int recursion_level); +struct AssetLibrary *filelist_asset_library(struct FileList *filelist); + struct BlendHandle *filelist_lib(struct FileList *filelist); bool filelist_islibrary(struct FileList *filelist, char *dir, char **r_group); void filelist_freelib(struct FileList *filelist); diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index f7bdb4326a5..83b33fe8aa9 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -120,19 +120,16 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile) asset_params->base_params.details_flags = U_default.file_space_data.details_flags; asset_params->asset_library_ref.type = ASSET_LIBRARY_LOCAL; asset_params->asset_library_ref.custom_library_index = -1; - asset_params->import_type = FILE_ASSET_IMPORT_APPEND; + asset_params->import_type = FILE_ASSET_IMPORT_APPEND_REUSE; } FileSelectParams *base_params = &asset_params->base_params; base_params->file[0] = '\0'; base_params->filter_glob[0] = '\0'; - /* TODO: this way of using filters to form categories is notably slower than specifying a - * "group" to read. That's because all types are read and filtering is applied afterwards. Would - * be nice if we could lazy-read individual groups. */ base_params->flag |= U_default.file_space_data.flag | FILE_ASSETS_ONLY | FILE_FILTER; base_params->flag &= ~FILE_DIRSEL_ONLY; base_params->filter |= FILE_TYPE_BLENDERLIB; - base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR; + base_params->filter_id = FILTER_ID_ALL; base_params->display = FILE_IMGDISPLAY; base_params->sort = FILE_SORT_ALPHA; /* Asset libraries include all sub-directories, so enable maximal recursion. */ @@ -440,7 +437,8 @@ static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params) BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir)); break; } - base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : FILE_LOADLIB; + base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : + FILE_ASSET_LIBRARY; } void fileselect_refresh_params(SpaceFile *sfile) @@ -461,6 +459,15 @@ bool ED_fileselect_is_asset_browser(const SpaceFile *sfile) return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS); } +struct AssetLibrary *ED_fileselect_active_asset_library_get(const SpaceFile *sfile) +{ + if (!ED_fileselect_is_asset_browser(sfile) || !sfile->files) { + return NULL; + } + + return filelist_asset_library(sfile->files); +} + struct ID *ED_fileselect_active_asset_get(const SpaceFile *sfile) { if (!ED_fileselect_is_asset_browser(sfile)) { diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c index 776bb0b3bb7..091c2d5f434 100644 --- a/source/blender/editors/space_file/fsmenu.c +++ b/source/blender/editors/space_file/fsmenu.c @@ -958,7 +958,7 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) found = 1; } if (endmntent(fp) == 0) { - fprintf(stderr, "could not close the list of mounted filesystems\n"); + fprintf(stderr, "could not close the list of mounted file-systems\n"); } } /* Check gvfs shares. */ diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index 42a9c4aa2d5..a563f24e24e 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -1,4 +1,4 @@ -/* +/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 @@ -335,9 +335,9 @@ static void file_refresh(const bContext *C, ScrArea *area) params->highlight_file = -1; /* added this so it opens nicer (ton) */ } - if (!U.experimental.use_extended_asset_browser && ED_fileselect_is_asset_browser(sfile)) { + if (ED_fileselect_is_asset_browser(sfile)) { /* Only poses supported as non-experimental right now. */ - params->filter_id = FILTER_ID_AC; + params->filter_id = U.experimental.use_extended_asset_browser ? FILTER_ID_ALL : FILTER_ID_AC; } filelist_settype(sfile->files, params->type); @@ -355,6 +355,10 @@ static void file_refresh(const bContext *C, ScrArea *area) (params->flag & FILE_ASSETS_ONLY) != 0, params->filter_glob, params->filter_search); + if (asset_params) { + filelist_set_asset_catalog_filter_options( + sfile->files, asset_params->asset_catalog_visibility, &asset_params->catalog_id); + } /* Update the active indices of bookmarks & co. */ sfile->systemnr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_SYSTEM, params->dir); @@ -1050,6 +1054,7 @@ void ED_spacetype_file(void) art->init = file_tools_region_init; art->draw = file_tools_region_draw; BLI_addhead(&st->regiontypes, art); + file_tools_region_panels_register(art); /* regions: tool properties */ art = MEM_callocN(sizeof(ARegionType), "spacetype file operator region"); diff --git a/source/blender/editors/space_graph/space_graph.c b/source/blender/editors/space_graph/space_graph.c index 720d69eaf4f..0e2c9b85bc6 100644 --- a/source/blender/editors/space_graph/space_graph.c +++ b/source/blender/editors/space_graph/space_graph.c @@ -237,29 +237,27 @@ static void graph_main_region_draw(const bContext *C, ARegion *region) v2d->tot.xmax += 10.0f; } - if (((sipo->flag & SIPO_NODRAWCURSOR) == 0) || (sipo->mode == SIPO_MODE_DRIVERS)) { + if (((sipo->flag & SIPO_NODRAWCURSOR) == 0)) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); /* horizontal component of value-cursor (value line before the current frame line) */ - if ((sipo->flag & SIPO_NODRAWCURSOR) == 0) { - float y = sipo->cursorVal; + float y = sipo->cursorVal; - /* Draw a green line to indicate the cursor value */ - immUniformThemeColorShadeAlpha(TH_CFRAME, -10, -50); - GPU_blend(GPU_BLEND_ALPHA); - GPU_line_width(2.0); + /* Draw a line to indicate the cursor value. */ + immUniformThemeColorShadeAlpha(TH_CFRAME, -10, -50); + GPU_blend(GPU_BLEND_ALPHA); + GPU_line_width(2.0); - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos, v2d->cur.xmin, y); - immVertex2f(pos, v2d->cur.xmax, y); - immEnd(); + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, v2d->cur.xmin, y); + immVertex2f(pos, v2d->cur.xmax, y); + immEnd(); - GPU_blend(GPU_BLEND_NONE); - } + GPU_blend(GPU_BLEND_NONE); - /* current frame or vertical component of vertical component of the cursor */ + /* Vertical component of of the cursor. */ if (sipo->mode == SIPO_MODE_DRIVERS) { /* cursor x-value */ float x = sipo->cursorTime; @@ -311,12 +309,17 @@ static void graph_main_region_draw_overlay(const bContext *C, ARegion *region) { /* draw entirely, view changes should be handled here */ const SpaceGraph *sipo = CTX_wm_space_graph(C); + + /* Driver Editor's X axis is not time. */ + if (sipo->mode == SIPO_MODE_DRIVERS) { + return; + } + const Scene *scene = CTX_data_scene(C); - const bool draw_vert_line = sipo->mode != SIPO_MODE_DRIVERS; View2D *v2d = ®ion->v2d; /* scrubbing region */ - ED_time_scrub_draw_current_frame(region, scene, sipo->flag & SIPO_DRAWTIME, draw_vert_line); + ED_time_scrub_draw_current_frame(region, scene, sipo->flag & SIPO_DRAWTIME); /* scrollers */ /* FIXME: args for scrollers depend on the type of data being shown. */ diff --git a/source/blender/editors/space_image/image_draw.c b/source/blender/editors/space_image/image_draw.c index d2af26aa1d7..fc04ec1fe02 100644 --- a/source/blender/editors/space_image/image_draw.c +++ b/source/blender/editors/space_image/image_draw.c @@ -34,6 +34,7 @@ #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" +#include "DNA_view2d_types.h" #include "PIL_time.h" @@ -576,3 +577,62 @@ void draw_image_cache(const bContext *C, ARegion *region) ED_mask_draw_frames(mask, region, cfra, sfra, efra); } } + +float ED_space_image_zoom_level(const View2D *v2d, const int grid_dimension) +{ + /* UV-space length per pixel */ + float xzoom = (v2d->cur.xmax - v2d->cur.xmin) / ((float)(v2d->mask.xmax - v2d->mask.xmin)); + float yzoom = (v2d->cur.ymax - v2d->cur.ymin) / ((float)(v2d->mask.ymax - v2d->mask.ymin)); + + /* Zoom_factor for UV/Image editor is calculated based on: + * - Default grid size on startup, which is 256x256 pixels + * - How blend factor for grid lines is set up in the fragment shader `grid_frag.glsl`. */ + float zoom_factor; + zoom_factor = (xzoom + yzoom) / 2.0f; /* Average for accuracy. */ + zoom_factor *= 256.0f / (powf(grid_dimension, 2)); + return zoom_factor; +} + +void ED_space_image_grid_steps(SpaceImage *sima, + float grid_steps[SI_GRID_STEPS_LEN], + const int grid_dimension) +{ + if (sima->flag & SI_CUSTOM_GRID) { + for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { + grid_steps[step] = powf(1, step) * (1.0f / ((float)sima->custom_grid_subdiv)); + } + } + else { + for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { + grid_steps[step] = powf(grid_dimension, step) * + (1.0f / (powf(grid_dimension, SI_GRID_STEPS_LEN))); + } + } +} + +/** + * Calculate the increment snapping value for UV/image editor based on the zoom factor + * The code in here (except the offset part) is used in `grid_frag.glsl` (see `grid_res`) for + * drawing the grid overlay for the UV/Image editor. + */ +float ED_space_image_increment_snap_value(const int grid_dimesnions, + const float grid_steps[SI_GRID_STEPS_LEN], + const float zoom_factor) +{ + /* Small offset on each grid_steps[] so that snapping value doesn't change until grid lines are + * significantly visible. + * `Offset = 3/4 * (grid_steps[i] - (grid_steps[i] / grid_dimesnsions))` + * + * Refer `grid_frag.glsl` to find out when grid lines actually start appearing */ + + for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { + float offset = (3.0f / 4.0f) * (grid_steps[step] - (grid_steps[step] / grid_dimesnions)); + + if ((grid_steps[step] - offset) > zoom_factor) { + return grid_steps[step]; + } + } + + /* Fallback */ + return grid_steps[0]; +} diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c index de8e4684d45..f14a8266cdd 100644 --- a/source/blender/editors/space_image/space_image.c +++ b/source/blender/editors/space_image/space_image.c @@ -126,13 +126,7 @@ static SpaceLink *image_create(const ScrArea *UNUSED(area), const Scene *UNUSED( simage->tile_grid_shape[0] = 1; simage->tile_grid_shape[1] = 1; - /* tool header */ - region = MEM_callocN(sizeof(ARegion), "tool header for image"); - - BLI_addtail(&simage->regionbase, region); - region->regiontype = RGN_TYPE_TOOL_HEADER; - region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; - region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; + simage->custom_grid_subdiv = 10; /* header */ region = MEM_callocN(sizeof(ARegion), "header for image"); @@ -141,6 +135,14 @@ static SpaceLink *image_create(const ScrArea *UNUSED(area), const Scene *UNUSED( region->regiontype = RGN_TYPE_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + /* tool header */ + region = MEM_callocN(sizeof(ARegion), "tool header for image"); + + BLI_addtail(&simage->regionbase, region); + region->regiontype = RGN_TYPE_TOOL_HEADER; + region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; + /* buttons/list view */ region = MEM_callocN(sizeof(ARegion), "buttons for image"); diff --git a/source/blender/editors/space_info/info_ops.c b/source/blender/editors/space_info/info_ops.c index a99396ecdf0..8e37e5fe9a8 100644 --- a/source/blender/editors/space_info/info_ops.c +++ b/source/blender/editors/space_info/info_ops.c @@ -564,7 +564,7 @@ void FILE_OT_find_missing_files(wmOperatorType *ot) /** \name Report Box Operator * \{ */ -/* NOTE(matt): Hard to decide whether to keep this as an operator, +/* NOTE(@broken): Hard to decide whether to keep this as an operator, * or turn it into a hard_coded UI control feature, * handling TIMER events for all regions in `interface_handlers.c`. * Not sure how good that is to be accessing UI data from diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index c1b308d213f..4694d8652f6 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -152,7 +152,7 @@ static void nla_action_draw_keyframes( format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1); immBegin(GPU_PRIM_POINTS, key_len); diff --git a/source/blender/editors/space_nla/space_nla.c b/source/blender/editors/space_nla/space_nla.c index 987d06cfe5c..8b44c26f07c 100644 --- a/source/blender/editors/space_nla/space_nla.c +++ b/source/blender/editors/space_nla/space_nla.c @@ -289,7 +289,7 @@ static void nla_main_region_draw_overlay(const bContext *C, ARegion *region) View2D *v2d = ®ion->v2d; /* scrubbing region */ - ED_time_scrub_draw_current_frame(region, scene, snla->flag & SNLA_DRAWTIME, true); + ED_time_scrub_draw_current_frame(region, scene, snla->flag & SNLA_DRAWTIME); /* scrollers */ UI_view2d_scrollers_draw(v2d, NULL); diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 62f40152416..8d6d56fc383 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -164,6 +164,11 @@ static void node_buts_curvevec(uiLayout *layout, bContext *UNUSED(C), PointerRNA uiTemplateCurveMapping(layout, ptr, "mapping", 'v', false, false, false, false); } +static void node_buts_curvefloat(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiTemplateCurveMapping(layout, ptr, "mapping", 0, false, false, false, false); +} + #define SAMPLE_FLT_ISNONE FLT_MAX /* Bad bad, 2.5 will do better? ... no it won't! */ static float _sample_col[4] = {SAMPLE_FLT_ISNONE}; @@ -1183,6 +1188,9 @@ static void node_shader_set_butfunc(bNodeType *ntype) case SH_NODE_CURVE_RGB: ntype->draw_buttons = node_buts_curvecol; break; + case SH_NODE_CURVE_FLOAT: + ntype->draw_buttons = node_buts_curvefloat; + break; case SH_NODE_MAPPING: ntype->draw_buttons = node_shader_buts_mapping; break; @@ -1563,7 +1571,7 @@ static void node_composit_buts_antialiasing(uiLayout *layout, bContext *UNUSED(C uiItemR(col, ptr, "corner_rounding", 0, nullptr, ICON_NONE); } -/* qdn: glare node */ +/* glare node */ static void node_composit_buts_glare(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "glare_type", DEFAULT_FLAGS, "", ICON_NONE); @@ -2079,7 +2087,7 @@ static void node_composit_buts_premulkey(uiLayout *layout, bContext *UNUSED(C), static void node_composit_buts_view_levels(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "channel", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "channel", DEFAULT_FLAGS, "", ICON_NONE); } static void node_composit_buts_colorbalance(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) @@ -3933,9 +3941,13 @@ static struct { uint p0_id, p1_id, p2_id, p3_id; uint colid_id, muted_id; uint dim_factor_id; + uint thickness_id; + uint dash_factor_id; GPUVertBufRaw p0_step, p1_step, p2_step, p3_step; GPUVertBufRaw colid_step, muted_step; GPUVertBufRaw dim_factor_step; + GPUVertBufRaw thickness_step; + GPUVertBufRaw dash_factor_step; uint count; bool enabled; } g_batch_link; @@ -3952,6 +3964,10 @@ static void nodelink_batch_reset() g_batch_link.inst_vbo, g_batch_link.muted_id, &g_batch_link.muted_step); GPU_vertbuf_attr_get_raw_data( g_batch_link.inst_vbo, g_batch_link.dim_factor_id, &g_batch_link.dim_factor_step); + GPU_vertbuf_attr_get_raw_data( + g_batch_link.inst_vbo, g_batch_link.thickness_id, &g_batch_link.thickness_step); + GPU_vertbuf_attr_get_raw_data( + g_batch_link.inst_vbo, g_batch_link.dash_factor_id, &g_batch_link.dash_factor_step); g_batch_link.count = 0; } @@ -4071,6 +4087,10 @@ static void nodelink_batch_init() &format_inst, "domuted", GPU_COMP_U8, 2, GPU_FETCH_INT); g_batch_link.dim_factor_id = GPU_vertformat_attr_add( &format_inst, "dim_factor", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + g_batch_link.thickness_id = GPU_vertformat_attr_add( + &format_inst, "thickness", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + g_batch_link.dash_factor_id = GPU_vertformat_attr_add( + &format_inst, "dash_factor", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); g_batch_link.inst_vbo = GPU_vertbuf_create_with_format_ex(&format_inst, GPU_USAGE_STREAM); /* Alloc max count but only draw the range we need. */ GPU_vertbuf_data_alloc(g_batch_link.inst_vbo, NODELINK_GROUP_SIZE); @@ -4147,7 +4167,9 @@ static void nodelink_batch_add_link(const SpaceNode *snode, int th_col3, bool drawarrow, bool drawmuted, - float dim_factor) + float dim_factor, + float thickness, + float dash_factor) { /* Only allow these colors. If more is needed, you need to modify the shader accordingly. */ BLI_assert(ELEM(th_col1, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT)); @@ -4167,6 +4189,8 @@ static void nodelink_batch_add_link(const SpaceNode *snode, char *muted = (char *)GPU_vertbuf_raw_step(&g_batch_link.muted_step); muted[0] = drawmuted; *(float *)GPU_vertbuf_raw_step(&g_batch_link.dim_factor_step) = dim_factor; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.thickness_step) = thickness; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.dash_factor_step) = dash_factor; if (g_batch_link.count == NODELINK_GROUP_SIZE) { nodelink_batch_draw(snode); @@ -4182,6 +4206,16 @@ void node_draw_link_bezier(const View2D *v2d, int th_col3) { const float dim_factor = node_link_dim_factor(v2d, link); + float thickness = 1.5f; + float dash_factor = 1.0f; + if (snode->edittree->type == NTREE_GEOMETRY) { + if (link->fromsock && link->fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) { + /* Make field links a bit thinner. */ + thickness = 1.0f; + /* Draw field as dashes. */ + dash_factor = 0.75f; + } + } float vec[4][2]; const bool highlighted = link->flag & NODE_LINK_TEMP_HIGHLIGHT; @@ -4205,7 +4239,9 @@ void node_draw_link_bezier(const View2D *v2d, th_col3, drawarrow, drawmuted, - dim_factor); + dim_factor, + thickness, + dash_factor); } else { /* Draw single link. */ @@ -4231,6 +4267,8 @@ void node_draw_link_bezier(const View2D *v2d, GPU_batch_uniform_1i(batch, "doArrow", drawarrow); GPU_batch_uniform_1i(batch, "doMuted", drawmuted); GPU_batch_uniform_1f(batch, "dim_factor", dim_factor); + GPU_batch_uniform_1f(batch, "thickness", thickness); + GPU_batch_uniform_1f(batch, "dash_factor", dash_factor); GPU_batch_draw(batch); } } @@ -4282,6 +4320,13 @@ void node_draw_link(View2D *v2d, SpaceNode *snode, bNodeLink *link) // th_col3 = -1; /* no shadow */ } } + /* Links from field to non-field sockets are not allowed. */ + if (snode->edittree->type == NTREE_GEOMETRY && !(link->flag & NODE_LINK_DRAGGED)) { + if ((link->fromsock && link->fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) && + (link->tosock && link->tosock->display_shape == SOCK_DISPLAY_SHAPE_CIRCLE)) { + th_col1 = th_col2 = th_col3 = TH_REDALERT; + } + } node_draw_link_bezier(v2d, snode, link, th_col1, th_col2, th_col3); } diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 10a3285be8b..9b243290566 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -79,6 +79,7 @@ #include "RNA_access.h" #include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_node_declaration.hh" #include "FN_field_cpp_type.hh" @@ -733,12 +734,6 @@ static void node_draw_mute_line(const View2D *v2d, const SpaceNode *snode, const GPU_blend(GPU_BLEND_NONE); } -/* Flags used in gpu_shader_keyframe_diamond_frag.glsl. */ -#define MARKER_SHAPE_DIAMOND 0x1 -#define MARKER_SHAPE_SQUARE 0xC -#define MARKER_SHAPE_CIRCLE 0x2 -#define MARKER_SHAPE_INNER_DOT 0x10 - static void node_socket_draw(const bNodeSocket *sock, const float color[4], const float color_outline[4], @@ -757,16 +752,16 @@ static void node_socket_draw(const bNodeSocket *sock, switch (sock->display_shape) { case SOCK_DISPLAY_SHAPE_DIAMOND: case SOCK_DISPLAY_SHAPE_DIAMOND_DOT: - flags = MARKER_SHAPE_DIAMOND; + flags = GPU_KEYFRAME_SHAPE_DIAMOND; break; case SOCK_DISPLAY_SHAPE_SQUARE: case SOCK_DISPLAY_SHAPE_SQUARE_DOT: - flags = MARKER_SHAPE_SQUARE; + flags = GPU_KEYFRAME_SHAPE_SQUARE; break; default: case SOCK_DISPLAY_SHAPE_CIRCLE: case SOCK_DISPLAY_SHAPE_CIRCLE_DOT: - flags = MARKER_SHAPE_CIRCLE; + flags = GPU_KEYFRAME_SHAPE_CIRCLE; break; } @@ -774,7 +769,7 @@ static void node_socket_draw(const bNodeSocket *sock, SOCK_DISPLAY_SHAPE_DIAMOND_DOT, SOCK_DISPLAY_SHAPE_SQUARE_DOT, SOCK_DISPLAY_SHAPE_CIRCLE_DOT)) { - flags |= MARKER_SHAPE_INNER_DOT; + flags |= GPU_KEYFRAME_SHAPE_INNER_DOT; } immAttr4fv(col_id, color); @@ -1091,12 +1086,37 @@ static void node_socket_draw_nested(const bContext *C, but, [](bContext *C, void *argN, const char *UNUSED(tip)) { SocketTooltipData *data = (SocketTooltipData *)argN; - std::optional<std::string> str = create_socket_inspection_string( + std::optional<std::string> socket_inspection_str = create_socket_inspection_string( C, *data->ntree, *data->node, *data->socket); - if (str.has_value()) { - return BLI_strdup(str->c_str()); + + std::stringstream output; + if (data->node->declaration != nullptr) { + ListBase *list; + Span<blender::nodes::SocketDeclarationPtr> decl_list; + + if (data->socket->in_out == SOCK_IN) { + list = &data->node->inputs; + decl_list = data->node->declaration->inputs(); + } + else { + list = &data->node->outputs; + decl_list = data->node->declaration->outputs(); + } + + const int socket_index = BLI_findindex(list, data->socket); + const blender::nodes::SocketDeclaration &socket_decl = *decl_list[socket_index]; + blender::StringRef description = socket_decl.description(); + if (!description.is_empty()) { + output << TIP_(description.data()) << ".\n\n"; + } + } + if (socket_inspection_str.has_value()) { + output << *socket_inspection_str; + } + else { + output << TIP_("The socket value has not been computed yet"); } - return BLI_strdup(TIP_("The socket value has not been computed yet")); + return BLI_strdup(output.str().c_str()); }, data, MEM_freeN); @@ -1132,7 +1152,7 @@ void ED_node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[ GPU_blend(GPU_BLEND_ALPHA); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 0.7f); immUniform2f("ViewportSize", -1.0f, -1.0f); @@ -1277,7 +1297,7 @@ void node_draw_sockets(const View2D *v2d, GPU_blend(GPU_BLEND_ALPHA); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 0.7f); immUniform2f("ViewportSize", -1.0f, -1.0f); diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 7d95659e403..b69e7e98bca 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -220,6 +220,7 @@ static LinkData *create_drag_link(Main *bmain, SpaceNode *snode, bNode *node, bN if (node_connected_to_output(bmain, snode->edittree, node)) { oplink->flag |= NODE_LINK_TEST; } + oplink->flag |= NODE_LINK_DRAGGED; return linkdata; } @@ -894,6 +895,8 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links) */ do_tag_update |= (link->flag & NODE_LINK_TEST) != 0; + link->flag &= ~NODE_LINK_DRAGGED; + if (apply_links && link->tosock && link->fromsock) { /* before actually adding the link, * let nodes perform special link insertion handling @@ -1097,6 +1100,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor *oplink = *link; oplink->next = oplink->prev = nullptr; oplink->flag |= NODE_LINK_VALID; + oplink->flag |= NODE_LINK_DRAGGED; /* The link could be disconnected and in that case we * wouldn't be able to check whether tag update is @@ -1150,6 +1154,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor *oplink = *link_to_pick; oplink->next = oplink->prev = nullptr; oplink->flag |= NODE_LINK_VALID; + oplink->flag |= NODE_LINK_DRAGGED; oplink->flag &= ~NODE_LINK_TEST; if (node_connected_to_output(bmain, snode->edittree, link_to_pick->tonode)) { oplink->flag |= NODE_LINK_TEST; diff --git a/source/blender/editors/space_outliner/outliner_select.c b/source/blender/editors/space_outliner/outliner_select.c index 581892ebb3a..5e409db0059 100644 --- a/source/blender/editors/space_outliner/outliner_select.c +++ b/source/blender/editors/space_outliner/outliner_select.c @@ -34,6 +34,7 @@ #include "DNA_scene_types.h" #include "DNA_sequence_types.h" #include "DNA_shader_fx_types.h" +#include "DNA_text_types.h" #include "BLI_listbase.h" #include "BLI_utildefines.h" @@ -63,6 +64,7 @@ #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_sequencer.h" +#include "ED_text.h" #include "ED_undo.h" #include "SEQ_select.h" @@ -737,6 +739,12 @@ static void tree_element_layer_collection_activate(bContext *C, TreeElement *te) WM_main_add_notifier(NC_SCENE | ND_LAYER | NS_LAYER_COLLECTION | NA_ACTIVATED, NULL); } +static void tree_element_text_activate(bContext *C, TreeElement *te) +{ + Text *text = (Text *)te->store_elem->id; + ED_text_activate_in_screen(C, text); +} + /* ---------------------------------------------- */ /* generic call for ID data check or make/check active in UI */ @@ -764,6 +772,9 @@ void tree_element_activate(bContext *C, case ID_CA: tree_element_camera_activate(C, tvc->scene, te); break; + case ID_TXT: + tree_element_text_activate(C, te); + break; } } diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 53f1c35776c..dc5e11b6998 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -107,36 +107,53 @@ static Sequence *special_seq_update = NULL; -void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3]) -{ +void color3ubv_from_seq(const Scene *curscene, + const Sequence *seq, + const bool show_strip_color_tag, + uchar r_col[3]) +{ + if (show_strip_color_tag && (uint)seq->color_tag < SEQUENCE_COLOR_TOT && + seq->color_tag != SEQUENCE_COLOR_NONE) { + bTheme *btheme = UI_GetTheme(); + const ThemeStripColor *strip_color = &btheme->strip_color[seq->color_tag]; + copy_v3_v3_uchar(r_col, strip_color->color); + return; + } + uchar blendcol[3]; + /* Sometimes the active theme is not the sequencer theme, e.g. when an operator invokes the file + * browser. This makes sure we get the right color values for the theme. */ + struct bThemeState theme_state; + UI_Theme_Store(&theme_state); + UI_SetTheme(SPACE_SEQ, RGN_TYPE_WINDOW); + switch (seq->type) { case SEQ_TYPE_IMAGE: - UI_GetThemeColor3ubv(TH_SEQ_IMAGE, col); + UI_GetThemeColor3ubv(TH_SEQ_IMAGE, r_col); break; case SEQ_TYPE_META: - UI_GetThemeColor3ubv(TH_SEQ_META, col); + UI_GetThemeColor3ubv(TH_SEQ_META, r_col); break; case SEQ_TYPE_MOVIE: - UI_GetThemeColor3ubv(TH_SEQ_MOVIE, col); + UI_GetThemeColor3ubv(TH_SEQ_MOVIE, r_col); break; case SEQ_TYPE_MOVIECLIP: - UI_GetThemeColor3ubv(TH_SEQ_MOVIECLIP, col); + UI_GetThemeColor3ubv(TH_SEQ_MOVIECLIP, r_col); break; case SEQ_TYPE_MASK: - UI_GetThemeColor3ubv(TH_SEQ_MASK, col); + UI_GetThemeColor3ubv(TH_SEQ_MASK, r_col); break; case SEQ_TYPE_SCENE: - UI_GetThemeColor3ubv(TH_SEQ_SCENE, col); + UI_GetThemeColor3ubv(TH_SEQ_SCENE, r_col); if (seq->scene == curscene) { - UI_GetColorPtrShade3ubv(col, col, 20); + UI_GetColorPtrShade3ubv(r_col, r_col, 20); } break; @@ -144,9 +161,9 @@ void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3]) case SEQ_TYPE_CROSS: case SEQ_TYPE_GAMCROSS: case SEQ_TYPE_WIPE: - col[0] = 130; - col[1] = 130; - col[2] = 130; + r_col[0] = 130; + r_col[1] = 130; + r_col[2] = 130; break; /* Effects. */ @@ -163,72 +180,74 @@ void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3]) case SEQ_TYPE_ADJUSTMENT: case SEQ_TYPE_GAUSSIAN_BLUR: case SEQ_TYPE_COLORMIX: - UI_GetThemeColor3ubv(TH_SEQ_EFFECT, col); + UI_GetThemeColor3ubv(TH_SEQ_EFFECT, r_col); /* Slightly offset hue to distinguish different effects. */ if (seq->type == SEQ_TYPE_ADD) { - rgb_byte_set_hue_float_offset(col, 0.03); + rgb_byte_set_hue_float_offset(r_col, 0.03); } else if (seq->type == SEQ_TYPE_SUB) { - rgb_byte_set_hue_float_offset(col, 0.06); + rgb_byte_set_hue_float_offset(r_col, 0.06); } else if (seq->type == SEQ_TYPE_MUL) { - rgb_byte_set_hue_float_offset(col, 0.13); + rgb_byte_set_hue_float_offset(r_col, 0.13); } else if (seq->type == SEQ_TYPE_ALPHAOVER) { - rgb_byte_set_hue_float_offset(col, 0.16); + rgb_byte_set_hue_float_offset(r_col, 0.16); } else if (seq->type == SEQ_TYPE_ALPHAUNDER) { - rgb_byte_set_hue_float_offset(col, 0.23); + rgb_byte_set_hue_float_offset(r_col, 0.23); } else if (seq->type == SEQ_TYPE_OVERDROP) { - rgb_byte_set_hue_float_offset(col, 0.26); + rgb_byte_set_hue_float_offset(r_col, 0.26); } else if (seq->type == SEQ_TYPE_COLORMIX) { - rgb_byte_set_hue_float_offset(col, 0.33); + rgb_byte_set_hue_float_offset(r_col, 0.33); } else if (seq->type == SEQ_TYPE_GAUSSIAN_BLUR) { - rgb_byte_set_hue_float_offset(col, 0.43); + rgb_byte_set_hue_float_offset(r_col, 0.43); } else if (seq->type == SEQ_TYPE_GLOW) { - rgb_byte_set_hue_float_offset(col, 0.46); + rgb_byte_set_hue_float_offset(r_col, 0.46); } else if (seq->type == SEQ_TYPE_ADJUSTMENT) { - rgb_byte_set_hue_float_offset(col, 0.55); + rgb_byte_set_hue_float_offset(r_col, 0.55); } else if (seq->type == SEQ_TYPE_SPEED) { - rgb_byte_set_hue_float_offset(col, 0.65); + rgb_byte_set_hue_float_offset(r_col, 0.65); } else if (seq->type == SEQ_TYPE_TRANSFORM) { - rgb_byte_set_hue_float_offset(col, 0.75); + rgb_byte_set_hue_float_offset(r_col, 0.75); } else if (seq->type == SEQ_TYPE_MULTICAM) { - rgb_byte_set_hue_float_offset(col, 0.85); + rgb_byte_set_hue_float_offset(r_col, 0.85); } break; case SEQ_TYPE_COLOR: - UI_GetThemeColor3ubv(TH_SEQ_COLOR, col); + UI_GetThemeColor3ubv(TH_SEQ_COLOR, r_col); break; case SEQ_TYPE_SOUND_RAM: - UI_GetThemeColor3ubv(TH_SEQ_AUDIO, col); + UI_GetThemeColor3ubv(TH_SEQ_AUDIO, r_col); blendcol[0] = blendcol[1] = blendcol[2] = 128; if (seq->flag & SEQ_MUTE) { - UI_GetColorPtrBlendShade3ubv(col, blendcol, col, 0.5, 20); + UI_GetColorPtrBlendShade3ubv(r_col, blendcol, r_col, 0.5, 20); } break; case SEQ_TYPE_TEXT: - UI_GetThemeColor3ubv(TH_SEQ_TEXT, col); + UI_GetThemeColor3ubv(TH_SEQ_TEXT, r_col); break; default: - col[0] = 10; - col[1] = 255; - col[2] = 40; + r_col[0] = 10; + r_col[1] = 255; + r_col[2] = 40; break; } + + UI_Theme_Restore(&theme_state); } typedef struct WaveVizData { @@ -558,7 +577,13 @@ static void draw_seq_waveform_overlay(View2D *v2d, } } -static void drawmeta_contents(Scene *scene, Sequence *seqm, float x1, float y1, float x2, float y2) +static void drawmeta_contents(Scene *scene, + Sequence *seqm, + float x1, + float y1, + float x2, + float y2, + const bool show_strip_color_tag) { Sequence *seq; uchar col[4]; @@ -614,7 +639,7 @@ static void drawmeta_contents(Scene *scene, Sequence *seqm, float x1, float y1, rgb_float_to_uchar(col, colvars->col); } else { - color3ubv_from_seq(scene, seq, col); + color3ubv_from_seq(scene, seq, show_strip_color_tag, col); } if ((seqm->flag & SEQ_MUTE) || (seq->flag & SEQ_MUTE)) { @@ -953,7 +978,8 @@ static void draw_seq_text_overlay(View2D *v2d, UI_view2d_text_cache_add_rectf(v2d, &rect, overlay_string, overlay_string_len, col); } -static void draw_sequence_extensions_overlay(Scene *scene, Sequence *seq, uint pos, float pixely) +static void draw_sequence_extensions_overlay( + Scene *scene, Sequence *seq, uint pos, float pixely, const bool show_strip_color_tag) { float x1, x2, y1, y2; uchar col[4], blend_col[3]; @@ -966,7 +992,7 @@ static void draw_sequence_extensions_overlay(Scene *scene, Sequence *seq, uint p GPU_blend(GPU_BLEND_ALPHA); - color3ubv_from_seq(scene, seq, col); + color3ubv_from_seq(scene, seq, show_strip_color_tag, col); if (seq->flag & SELECT) { UI_GetColorPtrShade3ubv(col, col, 50); } @@ -1036,7 +1062,8 @@ static void draw_seq_background(Scene *scene, float x2, float y1, float y2, - bool is_single_image) + bool is_single_image, + bool show_strip_color_tag) { uchar col[4]; GPU_blend(GPU_BLEND_ALPHA); @@ -1049,11 +1076,11 @@ static void draw_seq_background(Scene *scene, rgb_float_to_uchar(col, colvars->col); } else { - color3ubv_from_seq(scene, seq1, col); + color3ubv_from_seq(scene, seq1, show_strip_color_tag, col); } } else { - color3ubv_from_seq(scene, seq, col); + color3ubv_from_seq(scene, seq, show_strip_color_tag, col); } /* Draw muted strips semi-transparent. */ @@ -1108,7 +1135,7 @@ static void draw_seq_background(Scene *scene, rgb_float_to_uchar(col, colvars->col); } else { - color3ubv_from_seq(scene, seq2, col); + color3ubv_from_seq(scene, seq2, show_strip_color_tag, col); /* If the transition inputs are of the same type, draw the right side slightly darker. */ if (seq1->type == seq2->type) { UI_GetColorPtrShade3ubv(col, col, -15); @@ -1822,6 +1849,10 @@ static void draw_seq_strip(const bContext *C, /* Check if we are doing "solo preview". */ bool is_single_image = (char)SEQ_transform_single_image_check(seq); + /* Use the seq->color_tag to display the tag color. */ + const bool show_strip_color_tag = (sseq->timeline_overlay.flag & + SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG); + /* Draw strip body. */ x1 = (seq->startstill) ? seq->start : seq->startdisp; y1 = seq->machine + SEQ_STRIP_OFSBOTTOM; @@ -1852,7 +1883,7 @@ static void draw_seq_strip(const bContext *C, uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - draw_seq_background(scene, seq, pos, x1, x2, y1, y2, is_single_image); + draw_seq_background(scene, seq, pos, x1, x2, y1, y2, is_single_image, show_strip_color_tag); /* Draw a color band inside color strip. */ if (seq->type == SEQ_TYPE_COLOR && y_threshold) { @@ -1864,7 +1895,7 @@ static void draw_seq_strip(const bContext *C, if (!is_single_image && (seq->startofs || seq->endofs) && pixely > 0) { if ((sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_OFFSETS) || (seq == special_seq_update)) { - draw_sequence_extensions_overlay(scene, seq, pos, pixely); + draw_sequence_extensions_overlay(scene, seq, pos, pixely, show_strip_color_tag); } } } @@ -1875,7 +1906,7 @@ static void draw_seq_strip(const bContext *C, if ((seq->type == SEQ_TYPE_META) || ((seq->type == SEQ_TYPE_SCENE) && (seq->flag & SEQ_SCENE_STRIPS))) { - drawmeta_contents(scene, seq, x1, y1, x2, y2); + drawmeta_contents(scene, seq, x1, y1, x2, y2, show_strip_color_tag); } if ((sseq->flag & SEQ_SHOW_OVERLAY) && @@ -2585,7 +2616,7 @@ static int sequencer_draw_get_transform_preview_frame(Scene *scene) return preview_frame; } -static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq) +static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq, bool is_active_seq) { SpaceSeq *sseq = CTX_wm_space_seq(C); if ((seq->flag & SELECT) == 0) { @@ -2628,7 +2659,12 @@ static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq) immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); float col[3]; - UI_GetThemeColor3fv(TH_SEQ_SELECTED, col); + if (is_active_seq) { + UI_GetThemeColor3fv(TH_SEQ_ACTIVE, col); + } + else { + UI_GetThemeColor3fv(TH_SEQ_SELECTED, col); + } immUniformColor3fv(col); immUniform1f("lineWidth", U.pixelsize); immBegin(GPU_PRIM_LINE_LOOP, 4); @@ -2719,12 +2755,15 @@ void sequencer_draw_preview(const bContext *C, sequencer_draw_borders_overlay(sseq, v2d, scene); } - SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0); - Sequence *seq; - SEQ_ITERATOR_FOREACH (seq, collection) { - seq_draw_image_origin_and_outline(C, seq); + if (!draw_backdrop && scene->ed != NULL) { + SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0); + Sequence *seq; + Sequence *active_seq = SEQ_select_active_get(scene); + SEQ_ITERATOR_FOREACH (seq, collection) { + seq_draw_image_origin_and_outline(C, seq, seq == active_seq); + } + SEQ_collection_free(collection); } - SEQ_collection_free(collection); if (draw_gpencil && show_imbuf && (sseq->flag & SEQ_SHOW_OVERLAY)) { sequencer_draw_gpencil_overlay(C); @@ -3292,6 +3331,6 @@ void draw_timeline_seq_display(const bContext *C, ARegion *region) UI_view2d_view_restore(C); } - ED_time_scrub_draw_current_frame(region, scene, !(sseq->flag & SEQ_DRAWFRAMES), true); + ED_time_scrub_draw_current_frame(region, scene, !(sseq->flag & SEQ_DRAWFRAMES)); UI_view2d_scrollers_draw(v2d, NULL); } diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index 9f21fc0676c..9be947b9112 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -63,6 +63,7 @@ #include "WM_types.h" #include "RNA_define.h" +#include "RNA_enum_types.h" /* For menu, popup, icons, etc. */ #include "ED_numinput.h" @@ -869,9 +870,9 @@ static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *even void SEQUENCER_OT_slip(struct wmOperatorType *ot) { /* Identifiers. */ - ot->name = "Trim Strips"; + ot->name = "Slip Strips"; ot->idname = "SEQUENCER_OT_slip"; - ot->description = "Trim the contents of the active strip"; + ot->description = "Slip the contents of selected strips"; /* Api callbacks. */ ot->invoke = sequencer_slip_invoke; @@ -3322,4 +3323,38 @@ void SEQUENCER_OT_strip_transform_fit(struct wmOperatorType *ot) "Scale fit fit_method"); } +static int sequencer_strip_color_tag_set_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + const short color_tag = RNA_enum_get(op->ptr, "color"); + + LISTBASE_FOREACH (Sequence *, seq, &ed->seqbase) { + if (seq->flag & SELECT) { + seq->color_tag = color_tag; + } + } + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; +} + +void SEQUENCER_OT_strip_color_tag_set(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Set Color Tag"; + ot->idname = "SEQUENCER_OT_strip_color_tag_set"; + ot->description = "Set a color tag for the selected strips"; + + /* Api callbacks. */ + ot->exec = sequencer_strip_color_tag_set_exec; + ot->poll = sequencer_edit_poll; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "color", rna_enum_strip_color_items, SEQUENCE_COLOR_NONE, "Color Tag", ""); +} + /** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h index 5b5c381509f..202eda85dca 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.h +++ b/source/blender/editors/space_sequencer/sequencer_intern.h @@ -51,7 +51,10 @@ void sequencer_draw_preview(const struct bContext *C, int offset, bool draw_overlay, bool draw_backdrop); -void color3ubv_from_seq(struct Scene *curscene, struct Sequence *seq, unsigned char col[3]); +void color3ubv_from_seq(const struct Scene *curscene, + const struct Sequence *seq, + const bool show_strip_color_tag, + uchar r_col[3]); void sequencer_special_update_set(Sequence *seq); float sequence_handle_size_get_clamped(struct Sequence *seq, const float pixelx); @@ -148,6 +151,8 @@ void SEQUENCER_OT_set_range_to_strips(struct wmOperatorType *ot); void SEQUENCER_OT_strip_transform_clear(struct wmOperatorType *ot); void SEQUENCER_OT_strip_transform_fit(struct wmOperatorType *ot); +void SEQUENCER_OT_strip_color_tag_set(struct wmOperatorType *ot); + /* sequencer_select.c */ void SEQUENCER_OT_select_all(struct wmOperatorType *ot); void SEQUENCER_OT_select(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_sequencer/sequencer_ops.c b/source/blender/editors/space_sequencer/sequencer_ops.c index 48e6cfcdcd0..95f7b44264c 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.c +++ b/source/blender/editors/space_sequencer/sequencer_ops.c @@ -79,6 +79,8 @@ void sequencer_operatortypes(void) WM_operatortype_append(SEQUENCER_OT_strip_transform_clear); WM_operatortype_append(SEQUENCER_OT_strip_transform_fit); + WM_operatortype_append(SEQUENCER_OT_strip_color_tag_set); + /* sequencer_select.c */ WM_operatortype_append(SEQUENCER_OT_select_all); WM_operatortype_append(SEQUENCER_OT_select); diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 99b75f82922..ad0ceb82709 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -104,25 +104,25 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce sseq->preview_overlay.flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_PREVIEW_SHOW_OUTLINE_SELECTED; sseq->timeline_overlay.flag = SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID | - SEQ_TIMELINE_SHOW_FCURVES; + SEQ_TIMELINE_SHOW_FCURVES | SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG; BLI_rctf_init(&sseq->runtime.last_thumbnail_area, 0.0f, 0.0f, 0.0f, 0.0f); sseq->runtime.last_displayed_thumbnails = NULL; - /* Tool header. */ - region = MEM_callocN(sizeof(ARegion), "tool header for sequencer"); + /* Header. */ + region = MEM_callocN(sizeof(ARegion), "header for sequencer"); BLI_addtail(&sseq->regionbase, region); - region->regiontype = RGN_TYPE_TOOL_HEADER; + region->regiontype = RGN_TYPE_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; - region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; - /* Header. */ - region = MEM_callocN(sizeof(ARegion), "header for sequencer"); + /* Tool header. */ + region = MEM_callocN(sizeof(ARegion), "tool header for sequencer"); BLI_addtail(&sseq->regionbase, region); - region->regiontype = RGN_TYPE_HEADER; + region->regiontype = RGN_TYPE_TOOL_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; /* Buttons/list view. */ region = MEM_callocN(sizeof(ARegion), "buttons for sequencer"); diff --git a/source/blender/editors/space_text/text_draw.c b/source/blender/editors/space_text/text_draw.c index 99fcb2092c3..b541b65d676 100644 --- a/source/blender/editors/space_text/text_draw.c +++ b/source/blender/editors/space_text/text_draw.c @@ -48,6 +48,9 @@ #include "text_format.h" #include "text_intern.h" +#include "WM_api.h" +#include "WM_types.h" + /******************** text font drawing ******************/ typedef struct TextDrawContext { @@ -1734,6 +1737,23 @@ void text_update_character_width(SpaceText *st) text_font_end(&tdc); } +bool ED_text_activate_in_screen(bContext *C, Text *text) +{ + ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0); + if (area) { + SpaceText *st = area->spacedata.first; + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); + st->text = text; + if (region) { + ED_text_scroll_to_cursor(st, region, true); + } + WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text); + return true; + } + + return false; +} + /* Moves the view to the cursor location, * also used to make sure the view isn't outside the file */ void ED_text_scroll_to_cursor(SpaceText *st, ARegion *region, const bool center) diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index cbc70fc7497..bedc24a6287 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -276,20 +276,20 @@ static SpaceLink *view3d_create(const ScrArea *UNUSED(area), const Scene *scene) v3d->camera = scene->camera; } - /* tool header */ - region = MEM_callocN(sizeof(ARegion), "tool header for view3d"); + /* header */ + region = MEM_callocN(sizeof(ARegion), "header for view3d"); BLI_addtail(&v3d->regionbase, region); - region->regiontype = RGN_TYPE_TOOL_HEADER; + region->regiontype = RGN_TYPE_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; - region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; - /* header */ - region = MEM_callocN(sizeof(ARegion), "header for view3d"); + /* tool header */ + region = MEM_callocN(sizeof(ARegion), "tool header for view3d"); BLI_addtail(&v3d->regionbase, region); - region->regiontype = RGN_TYPE_HEADER; + region->regiontype = RGN_TYPE_TOOL_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; /* tool shelf */ region = MEM_callocN(sizeof(ARegion), "toolshelf for view3d"); @@ -539,6 +539,11 @@ static char *view3d_mat_drop_tooltip(bContext *C, return ED_object_ot_drop_named_material_tooltip(C, drop->ptr, event); } +static bool view3d_world_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) +{ + return view3d_drop_id_in_main_region_poll(C, drag, event, ID_WO); +} + static bool view3d_object_data_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { ID_Type id_type = view3d_drop_id_in_main_region_poll_get_id_type(C, drag, event); @@ -732,6 +737,12 @@ static void view3d_dropboxes(void) view3d_id_drop_copy_with_type, WM_drag_free_imported_drag_ID, view3d_object_data_drop_tooltip); + WM_dropbox_add(lb, + "VIEW3D_OT_drop_world", + view3d_world_drop_poll, + view3d_id_drop_copy, + WM_drag_free_imported_drag_ID, + NULL); } static void view3d_widgets(void) @@ -1555,11 +1566,16 @@ static void space_view3d_listener(const wmSpaceTypeListenerParams *params) switch (wmn->category) { case NC_SCENE: switch (wmn->data) { - case ND_WORLD: - if (v3d->flag2 & V3D_HIDE_OVERLAYS) { + case ND_WORLD: { + const bool use_scene_world = ((v3d->shading.type == OB_MATERIAL) && + (v3d->shading.flag & V3D_SHADING_SCENE_WORLD)) || + ((v3d->shading.type == OB_RENDER) && + (v3d->shading.flag & V3D_SHADING_SCENE_WORLD_RENDER)); + if (v3d->flag2 & V3D_HIDE_OVERLAYS || use_scene_world) { ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); } break; + } } break; case NC_WORLD: diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 8ed134c7fd1..15ccf5891d4 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -34,10 +34,12 @@ #include "DNA_gpencil_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "DNA_world_types.h" #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_dial_2d.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -210,6 +212,9 @@ typedef struct ViewOpsData { * If we want the value before running the operator, add a separate member. */ char persp; + + /** Used for roll */ + Dial *dial; } init; /** Previous state (previous modal event handled). */ @@ -577,6 +582,10 @@ static void viewops_data_free(bContext *C, wmOperator *op) WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer); } + if (vod->init.dial) { + MEM_freeN(vod->init.dial); + } + MEM_freeN(vod); op->customdata = NULL; } @@ -4352,18 +4361,9 @@ static void view_roll_angle( rv3d->view = RV3D_VIEW_USER; } -static void viewroll_apply(ViewOpsData *vod, int x, int UNUSED(y)) +static void viewroll_apply(ViewOpsData *vod, int x, int y) { - float angle = 0.0; - - { - float len1, len2, tot; - - tot = vod->region->winrct.xmax - vod->region->winrct.xmin; - len1 = (vod->region->winrct.xmax - x) / tot; - len2 = (vod->region->winrct.xmax - vod->init.event_xy[0]) / tot; - angle = (len1 - len2) * (float)M_PI * 4.0f; - } + float angle = BLI_dial_angle(vod->init.dial, (const float[2]){x, y}); if (angle != 0.0f) { view_roll_angle(vod->region, vod->rv3d->viewquat, vod->init.quat, vod->init.mousevec, angle); @@ -4409,6 +4409,13 @@ static int viewroll_modal(bContext *C, wmOperator *op, const wmEvent *event) break; } } + else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + /* Note this does not remove auto-keys on locked cameras. */ + copy_qt_qt(vod->rv3d->viewquat, vod->init.quat); + ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d); + viewops_data_free(C, op); + return OPERATOR_CANCELLED; + } else if (event->type == vod->init.event_type && event->val == KM_RELEASE) { event_code = VIEW_CONFIRM; } @@ -4517,6 +4524,9 @@ static int viewroll_invoke(bContext *C, wmOperator *op, const wmEvent *event) viewops_data_alloc(C, op); viewops_data_create(C, op, event, viewops_flag_from_prefs()); vod = op->customdata; + vod->init.dial = BLI_dial_init((const float[2]){BLI_rcti_cent_x(&vod->region->winrct), + BLI_rcti_cent_y(&vod->region->winrct)}, + FLT_EPSILON); ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region); @@ -4865,6 +4875,59 @@ void VIEW3D_OT_background_image_remove(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Drop World Operator + * \{ */ + +static int drop_world_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + + char name[MAX_ID_NAME - 2]; + + RNA_string_get(op->ptr, "name", name); + World *world = (World *)BKE_libblock_find_name(bmain, ID_WO, name); + if (world == NULL) { + return OPERATOR_CANCELLED; + } + + id_us_plus(&world->id); + scene->world = world; + + DEG_id_tag_update(&scene->id, 0); + DEG_relations_tag_update(bmain); + + WM_event_add_notifier(C, NC_SCENE | ND_WORLD, scene); + + return OPERATOR_FINISHED; +} + +static bool drop_world_poll(bContext *C) +{ + return ED_operator_scene_editable(C); +} + +void VIEW3D_OT_drop_world(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Drop World"; + ot->description = "Drop a world into the scene"; + ot->idname = "VIEW3D_OT_drop_world"; + + /* api callbacks */ + ot->exec = drop_world_exec; + ot->poll = drop_world_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; + + /* properties */ + RNA_def_string(ot->srna, "name", "World", MAX_ID_NAME - 2, "Name", "World to assign"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name View Clipping Planes Operator * * Draw border or toggle off. diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h index ab80928e0c1..a21fc006b02 100644 --- a/source/blender/editors/space_view3d/view3d_intern.h +++ b/source/blender/editors/space_view3d/view3d_intern.h @@ -80,6 +80,7 @@ void VIEW3D_OT_view_persportho(struct wmOperatorType *ot); void VIEW3D_OT_navigate(struct wmOperatorType *ot); void VIEW3D_OT_background_image_add(struct wmOperatorType *ot); void VIEW3D_OT_background_image_remove(struct wmOperatorType *ot); +void VIEW3D_OT_drop_world(struct wmOperatorType *ot); void VIEW3D_OT_view_orbit(struct wmOperatorType *ot); void VIEW3D_OT_view_roll(struct wmOperatorType *ot); void VIEW3D_OT_clip_border(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_view3d/view3d_ops.c b/source/blender/editors/space_view3d/view3d_ops.c index 56dedbbdbb2..eb8c043319c 100644 --- a/source/blender/editors/space_view3d/view3d_ops.c +++ b/source/blender/editors/space_view3d/view3d_ops.c @@ -169,6 +169,7 @@ void view3d_operatortypes(void) WM_operatortype_append(VIEW3D_OT_view_persportho); WM_operatortype_append(VIEW3D_OT_background_image_add); WM_operatortype_append(VIEW3D_OT_background_image_remove); + WM_operatortype_append(VIEW3D_OT_drop_world); WM_operatortype_append(VIEW3D_OT_view_selected); WM_operatortype_append(VIEW3D_OT_view_lock_clear); WM_operatortype_append(VIEW3D_OT_view_lock_to_active); diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index bca4f8b4857..ac5a06d22bb 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -2082,7 +2082,7 @@ static int mixed_bones_object_selectbuffer_extended(ViewContext *vc, /** * \param has_bones: When true, skip non-bone hits, also allow bases to be used * that are visible but not select-able, - * since you may be in pose mode with an unselect-able object. + * since you may be in pose mode with an un-selectable object. * * \return the active base or NULL. */ diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index e58e524e341..6ed2c28a7eb 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -28,6 +28,7 @@ #include "DNA_gpencil_types.h" #include "DNA_mask_types.h" #include "DNA_mesh_types.h" +#include "DNA_screen_types.h" #include "BLI_math.h" #include "BLI_rect.h" @@ -1609,8 +1610,16 @@ static void initSnapSpatial(TransInfo *t, float r_snap[2]) } } else if (t->spacetype == SPACE_IMAGE) { - r_snap[0] = 0.0625f; - r_snap[1] = 0.03125f; + SpaceImage *sima = t->area->spacedata.first; + View2D *v2d = &t->region->v2d; + int grid_size = SI_GRID_STEPS_LEN; + float zoom_factor = ED_space_image_zoom_level(v2d, grid_size); + float grid_steps[SI_GRID_STEPS_LEN]; + + ED_space_image_grid_steps(sima, grid_steps, grid_size); + /* Snapping value based on what type of grid is used (adaptive-subdividing or custom-grid). */ + r_snap[0] = ED_space_image_increment_snap_value(grid_size, grid_steps, zoom_factor); + r_snap[1] = r_snap[0] / 2.0f; } else if (t->spacetype == SPACE_CLIP) { r_snap[0] = 0.125f; diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index d3e0f55b127..d19ff123037 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -1496,8 +1496,10 @@ static void bone_children_clear_transflag(int mode, short around, ListBase *lb) } } -/* Sets transform flags in the bones. - * Returns total number of bones with `BONE_TRANSFORM`. */ +/** + * Sets transform flags in the bones. + * Returns total number of bones with #BONE_TRANSFORM. + */ int transform_convert_pose_transflags_update(Object *ob, const int mode, const short around, @@ -1730,7 +1732,7 @@ void special_aftertrans_update__pose(bContext *C, TransInfo *t) BKE_pose_where_is(t->depsgraph, t->scene, pose_ob); } - /* set BONE_TRANSFORM flags for autokey, gizmo draw might have changed them */ + /* Set BONE_TRANSFORM flags for auto-key, gizmo draw might have changed them. */ if (!canceled && (t->mode != TFM_DUMMY)) { transform_convert_pose_transflags_update(ob, t->mode, t->around, NULL); } diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c index 7e5b80c2453..acef8a666e3 100644 --- a/source/blender/editors/transform/transform_convert_nla.c +++ b/source/blender/editors/transform/transform_convert_nla.c @@ -208,30 +208,18 @@ void createTransNlaData(bContext *C, TransInfo *t) /* just set tdn to assume that it only has one handle for now */ tdn->handle = -1; - /* now, link the transform data up to this data */ - if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { - td->loc = tdn->h1; - copy_v3_v3(td->iloc, tdn->h1); + /* Now, link the transform data up to this data. */ + td->loc = tdn->h1; + copy_v3_v3(td->iloc, tdn->h1); - /* store all the other gunk that is required by transform */ + if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { + /* Store all the other gunk that is required by transform. */ copy_v3_v3(td->center, center); - memset(td->axismtx, 0, sizeof(td->axismtx)); td->axismtx[2][2] = 1.0f; - - td->ext = NULL; - td->val = NULL; - td->flag |= TD_SELECTED; - td->dist = 0.0f; - unit_m3(td->mtx); unit_m3(td->smtx); } - else { - /* time scaling only needs single value */ - td->val = &tdn->h1[0]; - td->ival = tdn->h1[0]; - } td->extra = tdn; td++; @@ -241,30 +229,18 @@ void createTransNlaData(bContext *C, TransInfo *t) * then we're doing both, otherwise, only end */ tdn->handle = (tdn->handle) ? 2 : 1; - /* now, link the transform data up to this data */ - if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { - td->loc = tdn->h2; - copy_v3_v3(td->iloc, tdn->h2); + /* Now, link the transform data up to this data. */ + td->loc = tdn->h2; + copy_v3_v3(td->iloc, tdn->h2); - /* store all the other gunk that is required by transform */ + if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { + /* Store all the other gunk that is required by transform. */ copy_v3_v3(td->center, center); - memset(td->axismtx, 0, sizeof(td->axismtx)); td->axismtx[2][2] = 1.0f; - - td->ext = NULL; - td->val = NULL; - td->flag |= TD_SELECTED; - td->dist = 0.0f; - unit_m3(td->mtx); unit_m3(td->smtx); } - else { - /* time scaling only needs single value */ - td->val = &tdn->h2[0]; - td->ival = tdn->h2[0]; - } td->extra = tdn; td++; diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c index ad22b0fc444..09aa4314b32 100644 --- a/source/blender/editors/transform/transform_convert_object.c +++ b/source/blender/editors/transform/transform_convert_object.c @@ -958,25 +958,25 @@ void special_aftertrans_update__object(bContext *C, TransInfo *t) } BLI_freelistN(&pidlist); - /* pointcache refresh */ + /* Point-cache refresh. */ if (BKE_ptcache_object_reset(t->scene, ob, PTCACHE_RESET_OUTDATED)) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } - /* Needed for proper updating of "quick cached" dynamics. */ - /* Creates troubles for moving animated objects without */ - /* autokey though, probably needed is an anim sys override? */ - /* Please remove if some other solution is found. -jahka */ + /* Needed for proper updating of "quick cached" dynamics. + * Creates troubles for moving animated objects without + * auto-key though, probably needed is an animation-system override? + * NOTE(@jahka): Please remove if some other solution is found. */ DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); - /* Set autokey if necessary */ + /* Set auto-key if necessary. */ if (!canceled) { ED_transform_autokeyframe_object(C, t->scene, t->view_layer, ob, t->mode); } motionpath_update |= ED_transform_motionpath_need_update_object(t->scene, ob); - /* restore rigid body transform */ + /* Restore rigid body transform. */ if (ob->rigidbody_object && canceled) { float ctime = BKE_scene_ctime_get(t->scene); if (BKE_rigidbody_check_sim_running(t->scene->rigidbody_world, ctime)) { diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c index 5db9a2e092f..6e3f12de472 100644 --- a/source/blender/editors/transform/transform_convert_sequencer_image.c +++ b/source/blender/editors/transform/transform_convert_sequencer_image.c @@ -113,12 +113,17 @@ static void freeSeqData(TransInfo *UNUSED(t), void createTransSeqImageData(TransInfo *t) { Editing *ed = SEQ_editing_get(t->scene); + + if (ed == NULL) { + return; + } + ListBase *seqbase = SEQ_active_seqbase_get(ed); SeqCollection *strips = SEQ_query_rendered_strips(seqbase, t->scene->r.cfra, 0); SEQ_filter_selected_strips(strips); const int count = SEQ_collection_len(strips); - if (ed == NULL || count == 0) { + if (count == 0) { SEQ_collection_free(strips); return; } diff --git a/source/blender/editors/transform/transform_gizmo_2d.c b/source/blender/editors/transform/transform_gizmo_2d.c index 0d66db0d7e1..4f6556cd2a2 100644 --- a/source/blender/editors/transform/transform_gizmo_2d.c +++ b/source/blender/editors/transform/transform_gizmo_2d.c @@ -32,6 +32,7 @@ #include "DNA_view3d_types.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_layer.h" #include "RNA_access.h" @@ -70,6 +71,10 @@ static bool gizmo2d_generic_poll(const bContext *C, wmGizmoGroupType *gzgt) return false; } + if (G.moving) { + return false; + } + ScrArea *area = CTX_wm_area(C); switch (area->spacetype) { case SPACE_IMAGE: { diff --git a/source/blender/editors/transform/transform_mode_timescale.c b/source/blender/editors/transform/transform_mode_timescale.c index 50fd714727b..0a7ae54982e 100644 --- a/source/blender/editors/transform/transform_mode_timescale.c +++ b/source/blender/editors/transform/transform_mode_timescale.c @@ -87,7 +87,7 @@ static void applyTimeScaleValue(TransInfo *t, float value) } /* now, calculate the new value */ - *(td->val) = ((td->ival - startx) * fac) + startx; + td->loc[0] = ((td->iloc[0] - startx) * fac) + startx; } } } diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index 05a20a14477..39a70f5477e 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -590,6 +590,11 @@ static void initSnappingMode(TransInfo *t) t->tsnap.project = 0; t->tsnap.mode = ts->snap_uv_mode; + if ((t->tsnap.mode & SCE_SNAP_MODE_INCREMENT) && (ts->snap_uv_flag & SCE_SNAP_ABS_GRID) && + (t->mode == TFM_TRANSLATION)) { + t->tsnap.mode &= ~SCE_SNAP_MODE_INCREMENT; + t->tsnap.mode |= SCE_SNAP_MODE_GRID; + } } else if (t->spacetype == SPACE_SEQ) { t->tsnap.mode = SEQ_tool_settings_snap_mode_get(t->scene); @@ -1502,7 +1507,8 @@ bool transform_snap_grid(TransInfo *t, float *val) return false; } - if (t->spacetype != SPACE_VIEW3D) { + /* Don't do grid snapping if not in 3D viewport or UV editor */ + if (!ELEM(t->spacetype, SPACE_VIEW3D, SPACE_IMAGE)) { return false; } diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index 891919fd46c..70297fad4ff 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -734,7 +734,7 @@ static bool raycastMesh(SnapObjectContext *sctx, } /* Test BoundBox */ - BoundBox *bb = BKE_mesh_boundbox_get(ob_eval); + BoundBox *bb = BKE_object_boundbox_get(ob_eval); if (bb) { /* was BKE_boundbox_ray_hit_check, see: cf6ca226fa58 */ if (!isect_ray_aabb_v3_simple( diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt index b396e348845..b339bfbdc47 100644 --- a/source/blender/editors/util/CMakeLists.txt +++ b/source/blender/editors/util/CMakeLists.txt @@ -103,8 +103,10 @@ set(SRC ../include/ED_view3d_offscreen.h ../include/UI_icons.h ../include/UI_interface.h + ../include/UI_interface.hh ../include/UI_interface_icons.h ../include/UI_resources.h + ../include/UI_tree_view.hh ../include/UI_view2d.h ) diff --git a/source/blender/editors/uvedit/uvedit_islands.c b/source/blender/editors/uvedit/uvedit_islands.c index 56bcbc63de1..6159758dbcd 100644 --- a/source/blender/editors/uvedit/uvedit_islands.c +++ b/source/blender/editors/uvedit/uvedit_islands.c @@ -29,6 +29,7 @@ #include "DNA_meshdata_types.h" #include "DNA_scene_types.h" +#include "DNA_space_types.h" #include "BLI_boxpack_2d.h" #include "BLI_convexhull_2d.h" @@ -38,6 +39,7 @@ #include "BKE_customdata.h" #include "BKE_editmesh.h" +#include "BKE_image.h" #include "DEG_depsgraph.h" @@ -232,6 +234,101 @@ static void bm_face_array_uv_scale_y(BMFace **faces, /** \} */ /* -------------------------------------------------------------------- */ +/** \name UDIM packing helper functions + * \{ */ + +/** + * Returns true if UV coordinates lie on a valid tile in UDIM grid or tiled image. + */ +bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + + if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) { + return true; + } + /* Check if selection lies on a valid UDIM image tile. */ + if (is_tiled_image) { + LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { + const int tile_index = tile->tile_number - 1001; + const int target_x = (tile_index % 10); + const int target_y = (tile_index / 10); + if (coords_floor[0] == target_x && coords_floor[1] == target_y) { + return true; + } + } + } + /* Probably not required since UDIM grid checks for 1001. */ + else if (image && !is_tiled_image) { + if (is_zero_v2(coords_floor)) { + return true; + } + } + + return false; +} + +/** + * Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number. + */ +static float uv_nearest_image_tile_distance(const Image *image, + float coords[2], + float nearest_tile_co[2]) +{ + int nearest_image_tile_index = BKE_image_find_nearest_tile(image, coords); + if (nearest_image_tile_index == -1) { + nearest_image_tile_index = 1001; + } + + nearest_tile_co[0] = (nearest_image_tile_index - 1001) % 10; + nearest_tile_co[1] = (nearest_image_tile_index - 1001) / 10; + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** + * Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number. + */ +static float uv_nearest_grid_tile_distance(const int udim_grid[2], + float coords[2], + float nearest_tile_co[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + + if (coords[0] > udim_grid[0]) { + nearest_tile_co[0] = udim_grid[0] - 1; + } + else if (coords[0] < 0) { + nearest_tile_co[0] = 0; + } + else { + nearest_tile_co[0] = coords_floor[0]; + } + + if (coords[1] > udim_grid[1]) { + nearest_tile_co[1] = udim_grid[1] - 1; + } + else if (coords[1] < 0) { + nearest_tile_co[1] = 0; + } + else { + nearest_tile_co[1] = coords_floor[1]; + } + + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Calculate UV Islands * * \note Currently this is a private API/type, it could be made public. @@ -359,6 +456,7 @@ static int bm_mesh_calc_uv_islands(const Scene *scene, void ED_uvedit_pack_islands_multi(const Scene *scene, Object **objects, const uint objects_len, + const struct UVMapUDIM_Params *udim_params, const struct UVPackIsland_Params *params) { /* Align to the Y axis, could make this configurable. */ @@ -407,8 +505,27 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, BoxPack *boxarray = MEM_mallocN(sizeof(*boxarray) * island_list_len, __func__); int index; + /* Coordinates of bounding box containing all selected UVs. */ + float selection_min_co[2], selection_max_co[2]; + INIT_MINMAX2(selection_min_co, selection_max_co); + LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list, index) { + /* Skip calculation if using specified UDIM option. */ + if (udim_params && (udim_params->use_target_udim == false)) { + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < island->faces_len; i++) { + BMFace *f = island->faces[i]; + BM_face_uv_minmax(f, bounds_min, bounds_max, island->cd_loop_uv_offset); + } + + selection_min_co[0] = MIN2(bounds_min[0], selection_min_co[0]); + selection_min_co[1] = MIN2(bounds_min[1], selection_min_co[1]); + selection_max_co[0] = MAX2(bounds_max[0], selection_max_co[0]); + selection_max_co[1] = MAX2(bounds_max[1], selection_max_co[1]); + } + if (params->rotate) { if (island->aspect_y != 1.0f) { bm_face_array_uv_scale_y( @@ -441,6 +558,13 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, } } + /* Center of bounding box containing all selected UVs. */ + float selection_center[2]; + if (udim_params && (udim_params->use_target_udim == false)) { + selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f; + selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f; + } + if (margin > 0.0f) { /* Logic matches behavior from #param_pack, * use area so multiply the margin by the area to give @@ -464,6 +588,53 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, const float scale[2] = {1.0f / boxarray_size[0], 1.0f / boxarray_size[1]}; + /* Tile offset. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* CASE: ignore UDIM. */ + if (udim_params == NULL) { + /* pass */ + } + /* CASE: Active/specified(smart uv project) UDIM. */ + else if (udim_params->use_target_udim) { + + /* Calculate offset based on specified_tile_index. */ + base_offset[0] = (udim_params->target_udim - 1001) % 10; + base_offset[1] = (udim_params->target_udim - 1001) / 10; + } + + /* CASE: Closest UDIM. */ + else { + const Image *image = udim_params->image; + const int *udim_grid = udim_params->grid_shape; + /* Check if selection lies on a valid UDIM grid tile. */ + bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center); + if (is_valid_udim) { + base_offset[0] = floorf(selection_center[0]); + base_offset[1] = floorf(selection_center[1]); + } + /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */ + else { + float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX}; + float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX; + if (image) { + nearest_image_tile_dist = uv_nearest_image_tile_distance( + image, selection_center, nearest_image_tile_co); + } + + float nearest_grid_tile_co[2] = {0.0f, 0.0f}; + nearest_grid_tile_dist = uv_nearest_grid_tile_distance( + udim_grid, selection_center, nearest_grid_tile_co); + + base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[0] : + nearest_grid_tile_co[0]; + base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[1] : + nearest_grid_tile_co[1]; + } + } + for (int i = 0; i < island_list_len; i++) { struct FaceIsland *island = island_array[boxarray[i].index]; const float pivot[2] = { @@ -471,8 +642,8 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, island->bounds_rect.ymin, }; const float offset[2] = { - (boxarray[i].x * scale[0]) - island->bounds_rect.xmin, - (boxarray[i].y * scale[1]) - island->bounds_rect.ymin, + ((boxarray[i].x * scale[0]) - island->bounds_rect.xmin) + base_offset[0], + ((boxarray[i].y * scale[1]) - island->bounds_rect.ymin) + base_offset[1], }; for (int j = 0; j < island->faces_len; j++) { BMFace *efa = island->faces[j]; diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c index 3d5dabda23d..38233b55d15 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c @@ -37,6 +37,7 @@ #include "BLI_alloca.h" #include "BLI_array.h" #include "BLI_linklist.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_memarena.h" #include "BLI_string.h" @@ -64,6 +65,7 @@ #include "PIL_time.h" #include "UI_interface.h" +#include "UI_view2d.h" #include "ED_image.h" #include "ED_mesh.h" @@ -143,6 +145,61 @@ static bool ED_uvedit_ensure_uvs(Object *obedit) /** \} */ /* -------------------------------------------------------------------- */ +/** \name UDIM Access + * \{ */ + +bool ED_uvedit_udim_params_from_image_space(const SpaceImage *sima, + bool use_active, + struct UVMapUDIM_Params *udim_params) +{ + memset(udim_params, 0, sizeof(*udim_params)); + + udim_params->grid_shape[0] = 1; + udim_params->grid_shape[1] = 1; + udim_params->target_udim = 0; + udim_params->use_target_udim = false; + + if (sima == NULL) { + return false; + } + + udim_params->image = sima->image; + udim_params->grid_shape[0] = sima->tile_grid_shape[0]; + udim_params->grid_shape[1] = sima->tile_grid_shape[1]; + + if (use_active) { + int active_udim = 1001; + /* NOTE: Presently, when UDIM grid and tiled image are present together, only active tile for + * the tiled image is considered. */ + Image *image = sima->image; + if (image && image->source == IMA_SRC_TILED) { + ImageTile *active_tile = BLI_findlink(&image->tiles, image->active_tile_index); + if (active_tile) { + active_udim = active_tile->tile_number; + } + } + else { + /* TODO: Support storing an active UDIM when there are no tiles present. + * Until then, use 2D cursor to find the active tile index for the UDIM grid. */ + const float cursor_loc[2] = {sima->cursor[0], sima->cursor[1]}; + if (uv_coords_isect_udim(sima->image, sima->tile_grid_shape, cursor_loc)) { + int tile_number = 1001; + tile_number += floorf(cursor_loc[1]) * 10; + tile_number += floorf(cursor_loc[0]); + active_udim = tile_number; + } + } + + udim_params->target_udim = active_udim; + udim_params->use_target_udim = true; + } + + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Parametrizer Conversion * \{ */ @@ -1005,10 +1062,17 @@ static void uvedit_pack_islands_multi(const Scene *scene, } } +/* Packing targets. */ +enum { + PACK_UDIM_SRC_CLOSEST = 0, + PACK_UDIM_SRC_ACTIVE = 1, +}; + static int pack_islands_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); const Scene *scene = CTX_data_scene(C); + const SpaceImage *sima = CTX_wm_space_image(C); const UnwrapOptions options = { .topology_from_uvs = true, @@ -1018,17 +1082,19 @@ static int pack_islands_exec(bContext *C, wmOperator *op) .correct_aspect = true, }; - bool rotate = RNA_boolean_get(op->ptr, "rotate"); - uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, CTX_wm_view3d(C), &objects_len); + /* Early exit in case no UVs are selected. */ if (!uvedit_have_selection_multi(scene, objects, objects_len, &options)) { MEM_freeN(objects); return OPERATOR_CANCELLED; } + /* RNA props */ + const bool rotate = RNA_boolean_get(op->ptr, "rotate"); + const int udim_source = RNA_enum_get(op->ptr, "udim_source"); if (RNA_struct_property_is_set(op->ptr, "margin")) { scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin"); } @@ -1036,9 +1102,15 @@ static int pack_islands_exec(bContext *C, wmOperator *op) RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin); } + struct UVMapUDIM_Params udim_params; + const bool use_active = (udim_source == PACK_UDIM_SRC_ACTIVE); + const bool use_udim_params = ED_uvedit_udim_params_from_image_space( + sima, use_active, &udim_params); + ED_uvedit_pack_islands_multi(scene, objects, objects_len, + use_udim_params ? &udim_params : NULL, &(struct UVPackIsland_Params){ .rotate = rotate, .rotate_align_axis = -1, @@ -1048,16 +1120,25 @@ static int pack_islands_exec(bContext *C, wmOperator *op) }); MEM_freeN(objects); - return OPERATOR_FINISHED; } void UV_OT_pack_islands(wmOperatorType *ot) { + static const EnumPropertyItem pack_target[] = { + {PACK_UDIM_SRC_CLOSEST, "CLOSEST_UDIM", 0, "Closest UDIM", "Pack islands to closest UDIM"}, + {PACK_UDIM_SRC_ACTIVE, + "ACTIVE_UDIM", + 0, + "Active UDIM", + "Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"}, + {0, NULL, 0, NULL, NULL}, + }; /* identifiers */ ot->name = "Pack Islands"; ot->idname = "UV_OT_pack_islands"; - ot->description = "Transform all islands so that they fill up the UV space as much as possible"; + ot->description = + "Transform all islands so that they fill up the UV/UDIM space as much as possible"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -1066,6 +1147,7 @@ void UV_OT_pack_islands(wmOperatorType *ot) ot->poll = ED_operator_uvedit; /* properties */ + RNA_def_enum(ot->srna, "udim_source", pack_target, PACK_UDIM_SRC_CLOSEST, "Pack to", ""); RNA_def_boolean(ot->srna, "rotate", true, "Rotate", "Rotate islands for best fit"); RNA_def_float_factor( ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f); @@ -2206,6 +2288,7 @@ static int smart_project_exec(bContext *C, wmOperator *op) ED_uvedit_pack_islands_multi(scene, objects_changed, object_changed_len, + NULL, &(struct UVPackIsland_Params){ .rotate = true, /* We could make this optional. */ diff --git a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp index c74fd60fe35..405deaf00b0 100644 --- a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp +++ b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp @@ -494,7 +494,7 @@ void FRS_composite_result(Render *re, ViewLayer *view_layer, Render *freestyle_r if (view_layer->freestyle_config.flags & FREESTYLE_AS_RENDER_PASS) { // Create a blank render pass output. RE_create_render_pass( - re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname); + re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname, true); } return; } @@ -530,7 +530,7 @@ void FRS_composite_result(Render *re, ViewLayer *view_layer, Render *freestyle_r if (view_layer->freestyle_config.flags & FREESTYLE_AS_RENDER_PASS) { RE_create_render_pass( - re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname); + re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname, true); dest = RE_RenderLayerGetPass(rl, RE_PASSNAME_FREESTYLE, re->viewname); } else { diff --git a/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp b/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp index ab39b9ad883..bcb7af0e5a7 100644 --- a/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp +++ b/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp @@ -63,7 +63,7 @@ static char SmoothingShader___doc__[] = "\n" ".. method:: shade(stroke)\n" "\n" - " Smoothes the stroke by moving the vertices to make the stroke\n" + " Smooths the stroke by moving the vertices to make the stroke\n" " smoother. Uses curvature flow to converge towards a curve of\n" " constant curvature. The diffusion method we use is anisotropic to\n" " prevent the diffusion across corners.\n" diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt index 856668f01d7..309b92f1cb4 100644 --- a/source/blender/functions/CMakeLists.txt +++ b/source/blender/functions/CMakeLists.txt @@ -53,9 +53,9 @@ set(SRC FN_multi_function_builder.hh FN_multi_function_context.hh FN_multi_function_data_type.hh + FN_multi_function_parallel.hh FN_multi_function_param_type.hh FN_multi_function_params.hh - FN_multi_function_parallel.hh FN_multi_function_procedure.hh FN_multi_function_procedure_builder.hh FN_multi_function_procedure_executor.hh diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh index d4375b625ce..3ce0993da59 100644 --- a/source/blender/functions/FN_field.hh +++ b/source/blender/functions/FN_field.hh @@ -484,4 +484,13 @@ template<typename T> Field<T> make_constant_field(T value) GField make_field_constant_if_possible(GField field); +class IndexFieldInput final : public FieldInput { + public: + IndexFieldInput(); + + const GVArray *get_varray_for_context(const FieldContext &context, + IndexMask mask, + ResourceScope &scope) const final; +}; + } // namespace blender::fn diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc index 599e4d4595a..39688ef3daf 100644 --- a/source/blender/functions/intern/field.cc +++ b/source/blender/functions/intern/field.cc @@ -261,20 +261,6 @@ static void build_multi_function_procedure_for_fields(MFProcedure &procedure, } /** - * Utility class that destructs elements from a partially initialized array. - */ -struct PartiallyInitializedArray : NonCopyable, NonMovable { - void *buffer; - IndexMask mask; - const CPPType *type; - - ~PartiallyInitializedArray() - { - this->type->destruct_indices(this->buffer, this->mask); - } -}; - -/** * Evaluate fields in the given context. If possible, multiple fields should be evaluated together, * because that can be more efficient when they share common sub-fields. * @@ -387,11 +373,11 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, /* Allocate a new buffer for the computed result. */ buffer = scope.linear_allocator().allocate(type.size() * array_size, type.alignment()); - /* Make sure that elements in the buffer will be destructed. */ - PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(); - destruct_helper.buffer = buffer; - destruct_helper.mask = mask; - destruct_helper.type = &type; + if (!type.is_trivially_destructible()) { + /* Destruct values in the end. */ + scope.add_destruct_call( + [buffer, mask, &type]() { type.destruct_indices(buffer, mask); }); + } r_varrays[out_index] = &scope.construct<GVArray_For_GSpan>( GSpan{type, buffer, array_size}); @@ -418,7 +404,10 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, build_multi_function_procedure_for_fields( procedure, scope, field_tree_info, constant_fields_to_evaluate); MFProcedureExecutor procedure_executor{"Procedure", procedure}; - MFParamsBuilder mf_params{procedure_executor, 1}; + /* Run the code below even when the mask is empty, so that outputs are properly prepared. + * Higher level code can detect this as well and just skip evaluating the field. */ + const int mask_size = mask.is_empty() ? 0 : 1; + MFParamsBuilder mf_params{procedure_executor, mask_size}; MFContextBuilder mf_context; /* Provide inputs to the procedure executor. */ @@ -432,14 +421,14 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, /* Allocate memory where the computed value will be stored in. */ void *buffer = scope.linear_allocator().allocate(type.size(), type.alignment()); - /* Use this to make sure that the value is destructed in the end. */ - PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(); - destruct_helper.buffer = buffer; - destruct_helper.mask = IndexRange(1); - destruct_helper.type = &type; + if (!type.is_trivially_destructible() && mask_size > 0) { + BLI_assert(mask_size == 1); + /* Destruct value in the end. */ + scope.add_destruct_call([buffer, &type]() { type.destruct(buffer); }); + } /* Pass output buffer to the procedure executor. */ - mf_params.add_uninitialized_single_output({type, buffer, 1}); + mf_params.add_uninitialized_single_output({type, buffer, mask_size}); /* Create virtual array that can be used after the procedure has been executed below. */ const int out_index = constant_field_indices[i]; @@ -447,7 +436,7 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, type, array_size, buffer); } - procedure_executor.call(IndexRange(1), mf_params, mf_context); + procedure_executor.call(IndexRange(mask_size), mf_params, mf_context); } /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has @@ -526,6 +515,21 @@ const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input, return field_input.get_varray_for_context(*this, mask, scope); } +IndexFieldInput::IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") +{ +} + +const GVArray *IndexFieldInput::get_varray_for_context(const fn::FieldContext &UNUSED(context), + IndexMask mask, + ResourceScope &scope) const +{ + /* TODO: Investigate a similar method to IndexRange::as_span() */ + auto index_func = [](int i) { return i; }; + return &scope.construct< + fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>( + mask.min_array_size(), mask.min_array_size(), index_func); +} + /* -------------------------------------------------------------------- * FieldOperation. */ diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc index b97282accdd..6d2d121bafd 100644 --- a/source/blender/functions/intern/multi_function_procedure_executor.cc +++ b/source/blender/functions/intern/multi_function_procedure_executor.cc @@ -1022,7 +1022,13 @@ static void execute_call_instruction(const MFCallInstruction &instruction, } } - fn.call(IndexRange(1), params, context); + try { + fn.call(IndexRange(1), params, context); + } + catch (...) { + /* Multi-functions must not throw exceptions. */ + BLI_assert_unreachable(); + } } else { MFParamsBuilder params(fn, &mask); @@ -1038,7 +1044,13 @@ static void execute_call_instruction(const MFCallInstruction &instruction, } } - fn.call(mask, params, context); + try { + fn.call(mask, params, context); + } + catch (...) { + /* Multi-functions must not throw exceptions. */ + BLI_assert_unreachable(); + } } } diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index eb1f61b1862..afcd551d0af 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -69,8 +69,8 @@ set(SRC intern/MOD_gpencilthick.c intern/MOD_gpenciltime.c intern/MOD_gpenciltint.c - intern/MOD_gpencilweight_proximity.c intern/MOD_gpencilweight_angle.c + intern/MOD_gpencilweight_proximity.c MOD_gpencil_lineart.h MOD_gpencil_modifiertypes.h diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index 01488a8b2de..c5ccf1d8229 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -587,6 +587,7 @@ static void chaining_panel_draw(const bContext *UNUSED(C), Panel *panel) is_geom ? IFACE_("Geometry Threshold") : NULL, ICON_NONE); + uiItemR(layout, ptr, "smooth_tolerance", UI_ITEM_R_SLIDER, NULL, ICON_NONE); uiItemR(layout, ptr, "split_angle", UI_ITEM_R_SLIDER, NULL, ICON_NONE); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c index 0885828a3a0..4079485de8d 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c @@ -83,13 +83,13 @@ static float calc_point_weight_by_distance(Object *ob, float dist = len_v3v3(mmd->object->obmat[3], gvert); if (dist > dist_max) { - weight = 0.0f; + weight = 1.0f; } else if (dist <= dist_max && dist > dist_min) { - weight = (dist_max - dist) / max_ff((dist_max - dist_min), 0.0001f); + weight = 1.0f - ((dist_max - dist) / max_ff((dist_max - dist_min), 0.0001f)); } else { - weight = 1.0f; + weight = 0.0f; } return weight; diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 134d9707ade..c00f34185dd 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -317,6 +317,8 @@ typedef struct LineartRenderBuffer { float chaining_image_threshold; float angle_splitting_threshold; + float chain_smooth_tolerance; + /* FIXME(Yiming): Temporary solution for speeding up calculation by not including lines that * are not in the selected source. This will not be needed after we have a proper scene-wise * cache running because multiple modifiers can then select results from that without further @@ -592,6 +594,7 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb); void MOD_lineart_chain_connect(LineartRenderBuffer *rb); void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold); void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad); +void MOD_lineart_smooth_chains(LineartRenderBuffer *rb, float tolerance); int MOD_lineart_chain_count(const LineartEdgeChain *ec); void MOD_lineart_chain_clear_picked_flag(LineartCache *lc); diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c index d86253e7fe0..8935bdd1870 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c @@ -924,6 +924,34 @@ void MOD_lineart_chain_clear_picked_flag(LineartCache *lc) } } +void MOD_lineart_smooth_chains(LineartRenderBuffer *rb, float tolerance) +{ + LISTBASE_FOREACH (LineartEdgeChain *, rlc, &rb->chains) { + LineartEdgeChainItem *next_eci; + for (LineartEdgeChainItem *eci = rlc->chain.first; eci; eci = next_eci) { + next_eci = eci->next; + LineartEdgeChainItem *eci2, *eci3, *eci4; + + /* Not enough point to do simplify. */ + if ((!(eci2 = eci->next)) || (!(eci3 = eci2->next))) { + continue; + } + + /* No need to care for different line types/occlusion and so on, because at this stage they + * are all the same within a chain. */ + + /* If p3 is within the p1-p2 segment of a width of "tolerance" */ + if (dist_to_line_segment_v2(eci3->pos, eci->pos, eci2->pos) < tolerance) { + /* And if p4 is on the extension of p1-p2 , we remove p3. */ + if ((eci4 = eci3->next) && (dist_to_line_v2(eci4->pos, eci->pos, eci2->pos) < tolerance)) { + BLI_remlink(&rlc->chain, eci3); + next_eci = eci; + } + } + } + } +} + /** * This should always be the last stage!, see the end of * #MOD_lineart_chain_split_for_fixed_occlusion(). diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index 725cc0741f0..7441c9a909c 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -2168,6 +2168,12 @@ static void lineart_main_load_geometries( use_mesh = use_ob->data; } else { + /* If DEG_ITER_OBJECT_FLAG_DUPLI is set, the curve objects are going to have a mesh + * equivalent already in the object list, so ignore converting the original curve in this + * case. */ + if (allow_duplicates) { + continue; + } use_mesh = BKE_mesh_new_from_object(depsgraph, use_ob, true, true); } @@ -3056,8 +3062,9 @@ static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene, rb->shift_y /= (1 + rb->overscan); rb->crease_threshold = cos(M_PI - lmd->crease_threshold); - rb->angle_splitting_threshold = lmd->angle_splitting_threshold; rb->chaining_image_threshold = lmd->chaining_image_threshold; + rb->angle_splitting_threshold = lmd->angle_splitting_threshold; + rb->chain_smooth_tolerance = lmd->chain_smooth_tolerance; rb->fuzzy_intersections = (lmd->calculation_flags & LRT_INTERSECTION_AS_CONTOUR) != 0; rb->fuzzy_everything = (lmd->calculation_flags & LRT_EVERYTHING_AS_CONTOUR) != 0; @@ -4172,6 +4179,13 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, /* This configuration ensures there won't be accidental lost of short unchained segments. */ MOD_lineart_chain_discard_short(rb, MIN2(*t_image, 0.001f) - FLT_EPSILON); + if (rb->chain_smooth_tolerance > FLT_EPSILON) { + /* Keeping UI range of 0-1 for ease of read while scaling down the actual value for best + * effective range in image-space (Coordinate only goes from -1 to 1). This value is somewhat + * arbitrary, but works best for the moment. */ + MOD_lineart_smooth_chains(rb, rb->chain_smooth_tolerance / 50); + } + if (rb->angle_splitting_threshold > FLT_EPSILON) { MOD_lineart_chain_split_angle(rb, rb->angle_splitting_threshold); } diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index b7dc3210c41..7a072900473 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -268,8 +268,8 @@ data_to_c_simple(shaders/gpu_shader_2D_edituvs_stretch_vert.glsl SRC) data_to_c_simple(shaders/gpu_shader_text_vert.glsl SRC) data_to_c_simple(shaders/gpu_shader_text_frag.glsl SRC) -data_to_c_simple(shaders/gpu_shader_keyframe_diamond_vert.glsl SRC) -data_to_c_simple(shaders/gpu_shader_keyframe_diamond_frag.glsl SRC) +data_to_c_simple(shaders/gpu_shader_keyframe_shape_vert.glsl SRC) +data_to_c_simple(shaders/gpu_shader_keyframe_shape_frag.glsl SRC) data_to_c_simple(shaders/gpu_shader_codegen_lib.glsl SRC) @@ -296,6 +296,7 @@ data_to_c_simple(shaders/material/gpu_shader_material_diffuse.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_displacement.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_eevee_specular.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_emission.glsl SRC) +data_to_c_simple(shaders/material/gpu_shader_material_float_curve.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_fractal_noise.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_fresnel.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_gamma.glsl SRC) diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h index 62b748b7edf..c6cfac79699 100644 --- a/source/blender/gpu/GPU_shader.h +++ b/source/blender/gpu/GPU_shader.h @@ -169,7 +169,7 @@ void GPU_shader_set_framebuffer_srgb_target(int use_srgb_to_linear); typedef enum eGPUBuiltinShader { /* specialized drawing */ GPU_SHADER_TEXT, - GPU_SHADER_KEYFRAME_DIAMOND, + GPU_SHADER_KEYFRAME_SHAPE, GPU_SHADER_SIMPLE_LIGHTING, /* for simple 2D drawing */ /** @@ -423,6 +423,19 @@ void GPU_shader_free_builtin_shaders(void); /* Determined by the maximum uniform buffer size divided by chunk size. */ #define GPU_MAX_UNIFORM_ATTR 8 +typedef enum eGPUKeyframeShapes { + GPU_KEYFRAME_SHAPE_DIAMOND = (1 << 0), + GPU_KEYFRAME_SHAPE_CIRCLE = (1 << 1), + GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL = (1 << 2), + GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL = (1 << 3), + GPU_KEYFRAME_SHAPE_INNER_DOT = (1 << 4), + GPU_KEYFRAME_SHAPE_ARROW_END_MAX = (1 << 8), + GPU_KEYFRAME_SHAPE_ARROW_END_MIN = (1 << 9), + GPU_KEYFRAME_SHAPE_ARROW_END_MIXED = (1 << 10), +} eGPUKeyframeShapes; +#define GPU_KEYFRAME_SHAPE_SQUARE \ + (GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL) + #ifdef __cplusplus } #endif diff --git a/source/blender/gpu/intern/gpu_codegen.c b/source/blender/gpu/intern/gpu_codegen.c index bb1ebc0e85d..f0046e879a0 100644 --- a/source/blender/gpu/intern/gpu_codegen.c +++ b/source/blender/gpu/intern/gpu_codegen.c @@ -656,6 +656,8 @@ static const char *attr_prefix_get(CustomDataType type) return "c"; case CD_AUTO_FROM_NAME: return "a"; + case CD_HAIRLENGTH: + return "hl"; default: BLI_assert_msg(0, "GPUVertAttr Prefix type not found : This should not happen!"); return ""; @@ -675,7 +677,12 @@ static char *code_generate_interface(GPUNodeGraph *graph, int builtins) BLI_dynstr_append(ds, "\n"); LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph->attributes) { - BLI_dynstr_appendf(ds, "%s var%d;\n", gpu_data_type_to_string(attr->gputype), attr->id); + if (attr->type == CD_HAIRLENGTH) { + BLI_dynstr_appendf(ds, "float var%d;\n", attr->id); + } + else { + BLI_dynstr_appendf(ds, "%s var%d;\n", gpu_data_type_to_string(attr->gputype), attr->id); + } } if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_append(ds, "vec2 barycentricTexCo;\n"); @@ -711,6 +718,10 @@ static char *code_generate_vertex(GPUNodeGraph *graph, BLI_dynstr_append(ds, datatoc_gpu_shader_common_obinfos_lib_glsl); BLI_dynstr_append(ds, "DEFINE_ATTR(vec4, orco);\n"); } + else if (attr->type == CD_HAIRLENGTH) { + BLI_dynstr_append(ds, datatoc_gpu_shader_common_obinfos_lib_glsl); + BLI_dynstr_append(ds, "DEFINE_ATTR(float, hairLen);\n"); + } else if (attr->name[0] == '\0') { BLI_dynstr_appendf(ds, "DEFINE_ATTR(%s, %s);\n", type_str, prefix); BLI_dynstr_appendf(ds, "#define att%d %s\n", attr->id, prefix); @@ -755,6 +766,9 @@ static char *code_generate_vertex(GPUNodeGraph *graph, BLI_dynstr_appendf( ds, " var%d = orco_get(position, modelmatinv, OrcoTexCoFactors, orco);\n", attr->id); } + else if (attr->type == CD_HAIRLENGTH) { + BLI_dynstr_appendf(ds, " var%d = hair_len_get(hair_get_strand_id(), hairLen);\n", attr->id); + } else { const char *type_str = gpu_data_type_to_string(attr->gputype); BLI_dynstr_appendf(ds, " var%d = GET_ATTR(%s, att%d);\n", attr->id, type_str, attr->id); diff --git a/source/blender/gpu/intern/gpu_material_library.c b/source/blender/gpu/intern/gpu_material_library.c index 73a80c62bdc..74e0270c42a 100644 --- a/source/blender/gpu/intern/gpu_material_library.c +++ b/source/blender/gpu/intern/gpu_material_library.c @@ -62,6 +62,7 @@ extern char datatoc_gpu_shader_material_diffuse_glsl[]; extern char datatoc_gpu_shader_material_displacement_glsl[]; extern char datatoc_gpu_shader_material_eevee_specular_glsl[]; extern char datatoc_gpu_shader_material_emission_glsl[]; +extern char datatoc_gpu_shader_material_float_curve_glsl[]; extern char datatoc_gpu_shader_material_fractal_noise_glsl[]; extern char datatoc_gpu_shader_material_fresnel_glsl[]; extern char datatoc_gpu_shader_material_gamma_glsl[]; @@ -262,6 +263,11 @@ static GPUMaterialLibrary gpu_shader_material_emission_library = { .dependencies = {NULL}, }; +static GPUMaterialLibrary gpu_shader_material_float_curve_library = { + .code = datatoc_gpu_shader_material_float_curve_glsl, + .dependencies = {NULL}, +}; + static GPUMaterialLibrary gpu_shader_material_fresnel_library = { .code = datatoc_gpu_shader_material_fresnel_glsl, .dependencies = {NULL}, @@ -591,6 +597,7 @@ static GPUMaterialLibrary *gpu_material_libraries[] = { &gpu_shader_material_color_util_library, &gpu_shader_material_hash_library, &gpu_shader_material_noise_library, + &gpu_shader_material_float_curve_library, &gpu_shader_material_fractal_noise_library, &gpu_shader_material_add_shader_library, &gpu_shader_material_ambient_occlusion_library, diff --git a/source/blender/gpu/intern/gpu_select.c b/source/blender/gpu/intern/gpu_select.c index 88b704a84a1..661c462f60d 100644 --- a/source/blender/gpu/intern/gpu_select.c +++ b/source/blender/gpu/intern/gpu_select.c @@ -20,8 +20,8 @@ /** \file * \ingroup gpu * - * Interface for accessing gpu-related methods for selection. The semantics are - * similar to glRenderMode(GL_SELECT) from older OpenGL versions. + * Interface for accessing GPU-related methods for selection. The semantics are + * similar to `glRenderMode(GL_SELECT)` from older OpenGL versions. */ #include <stdlib.h> #include <string.h> diff --git a/source/blender/gpu/intern/gpu_select_sample_query.cc b/source/blender/gpu/intern/gpu_select_sample_query.cc index 7b9b3020639..047ce0cfb35 100644 --- a/source/blender/gpu/intern/gpu_select_sample_query.cc +++ b/source/blender/gpu/intern/gpu_select_sample_query.cc @@ -20,8 +20,8 @@ /** \file * \ingroup gpu * - * Interface for accessing gpu-related methods for selection. The semantics will be - * similar to glRenderMode(GL_SELECT) since the goal is to maintain compatibility. + * Interface for accessing GPU-related methods for selection. The semantics will be + * similar to `glRenderMode(GL_SELECT)` since the goal is to maintain compatibility. */ #include <cstdlib> diff --git a/source/blender/gpu/intern/gpu_shader_builtin.c b/source/blender/gpu/intern/gpu_shader_builtin.c index c5122b76001..9ea46788f44 100644 --- a/source/blender/gpu/intern/gpu_shader_builtin.c +++ b/source/blender/gpu/intern/gpu_shader_builtin.c @@ -121,8 +121,8 @@ extern char datatoc_gpu_shader_3D_line_dashed_uniform_color_vert_glsl[]; extern char datatoc_gpu_shader_text_vert_glsl[]; extern char datatoc_gpu_shader_text_frag_glsl[]; -extern char datatoc_gpu_shader_keyframe_diamond_vert_glsl[]; -extern char datatoc_gpu_shader_keyframe_diamond_frag_glsl[]; +extern char datatoc_gpu_shader_keyframe_shape_vert_glsl[]; +extern char datatoc_gpu_shader_keyframe_shape_frag_glsl[]; extern char datatoc_gpu_shader_gpencil_stroke_vert_glsl[]; extern char datatoc_gpu_shader_gpencil_stroke_frag_glsl[]; @@ -166,11 +166,11 @@ static const GPUShaderStages builtin_shader_stages[GPU_SHADER_BUILTIN_LEN] = { .vert = datatoc_gpu_shader_text_vert_glsl, .frag = datatoc_gpu_shader_text_frag_glsl, }, - [GPU_SHADER_KEYFRAME_DIAMOND] = + [GPU_SHADER_KEYFRAME_SHAPE] = { - .name = "GPU_SHADER_KEYFRAME_DIAMOND", - .vert = datatoc_gpu_shader_keyframe_diamond_vert_glsl, - .frag = datatoc_gpu_shader_keyframe_diamond_frag_glsl, + .name = "GPU_SHADER_KEYFRAME_SHAPE", + .vert = datatoc_gpu_shader_keyframe_shape_vert_glsl, + .frag = datatoc_gpu_shader_keyframe_shape_frag_glsl, }, [GPU_SHADER_SIMPLE_LIGHTING] = { diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc index d5d13ea269f..2744c0c5e17 100644 --- a/source/blender/gpu/intern/gpu_texture.cc +++ b/source/blender/gpu/intern/gpu_texture.cc @@ -461,7 +461,7 @@ void GPU_texture_generate_mipmap(GPUTexture *tex) reinterpret_cast<Texture *>(tex)->generate_mipmap(); } -/* Copy a texture content to a similar texture. Only Mip 0 is copied. */ +/* Copy a texture content to a similar texture. Only MIP 0 is copied. */ void GPU_texture_copy(GPUTexture *dst_, GPUTexture *src_) { Texture *src = reinterpret_cast<Texture *>(src_); diff --git a/source/blender/gpu/opengl/gl_state.cc b/source/blender/gpu/opengl/gl_state.cc index 1106e3dab50..d737cf88a13 100644 --- a/source/blender/gpu/opengl/gl_state.cc +++ b/source/blender/gpu/opengl/gl_state.cc @@ -52,6 +52,7 @@ GLStateManager::GLStateManager() glDisable(GL_DITHER); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPrimitiveRestartIndex((GLuint)0xFFFFFFFF); diff --git a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl index f07bd7f1d6f..55d5d941290 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl @@ -1,11 +1,41 @@ in float colorGradient; in vec4 finalColor; +in float lineU; +flat in float lineLength; +flat in float dashFactor; +flat in int isMainLine; out vec4 fragColor; +#define DASH_WIDTH 20.0 +#define ANTIALIAS 1.0 + void main() { fragColor = finalColor; + + if ((isMainLine != 0) && (dashFactor < 1.0)) { + float distance_along_line = lineLength * lineU; + float normalized_distance = fract(distance_along_line / DASH_WIDTH); + + /* Checking if `normalized_distance <= dashFactor` is already enough for a basic + * dash, however we want to handle a nice antialias. */ + + float dash_center = DASH_WIDTH * dashFactor * 0.5; + float normalized_distance_triangle = + 1.0 - abs((fract((distance_along_line - dash_center) / DASH_WIDTH)) * 2.0 - 1.0); + float t = ANTIALIAS / DASH_WIDTH; + float slope = 1.0 / (2.0 * t); + + float alpha = min(1.0, max(0.0, slope * (normalized_distance_triangle - dashFactor + t))); + + if (alpha < 0.0) { + discard; + } + + fragColor.a *= 1.0 - alpha; + } + fragColor.a *= smoothstep(1.0, 0.1, abs(colorGradient)); } diff --git a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl index aae7f641af8..8f46c8eda4b 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl @@ -19,6 +19,8 @@ in vec2 P3; in ivec4 colid_doarrow; in ivec2 domuted; in float dim_factor; +in float thickness; +in float dash_factor; uniform vec4 colors[6]; @@ -41,6 +43,8 @@ uniform vec4 colors[3]; uniform bool doArrow; uniform bool doMuted; uniform float dim_factor; +uniform float thickness; +uniform float dash_factor; # define colShadow colors[0] # define colStart colors[1] @@ -54,9 +58,21 @@ uniform mat4 ModelViewProjectionMatrix; out float colorGradient; out vec4 finalColor; +out float lineU; +flat out float lineLength; +flat out float dashFactor; +flat out int isMainLine; void main(void) { + /* Parameters for the dashed line. */ + isMainLine = expand.y != 1.0 ? 0 : 1; + dashFactor = dash_factor; + /* Approximate line length, no need for real bezier length calculation. */ + lineLength = distance(P0, P3); + /* TODO: Incorrect U, this leads to non-uniform dash distribution. */ + lineU = uv.x; + float t = uv.x; float t2 = t * t; float t2_3 = 3.0 * t2; @@ -103,7 +119,7 @@ void main(void) finalColor[3] *= dim_factor; /* Expand into a line */ - gl_Position.xy += exp_axis * expandSize * expand_dist; + gl_Position.xy += exp_axis * expandSize * expand_dist * thickness; /* If the link is not muted or is not a reroute arrow the points are squashed to the center of * the line. Magic numbers are defined in drawnode.c */ diff --git a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl index f7bf3d33361..193a4190cbf 100644 --- a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl +++ b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl @@ -42,6 +42,11 @@ vec3 orco_get(vec3 local_pos, mat4 modelmatinv, vec4 orco_madd[2], const sampler return orco_madd[0].xyz + orco * orco_madd[1].xyz; } +float hair_len_get(int id, const samplerBuffer len) +{ + return texelFetch(len, id).x; +} + vec4 tangent_get(const samplerBuffer attr, mat3 normalmat) { /* Unsupported */ @@ -71,6 +76,11 @@ vec3 orco_get(vec3 local_pos, mat4 modelmatinv, vec4 orco_madd[2], vec4 orco) } } +float hair_len_get(int id, const float len) +{ + return len; +} + vec4 tangent_get(vec4 attr, mat3 normalmat) { vec4 tangent; diff --git a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_frag.glsl b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_frag.glsl index 1c4039bc590..a3b61dca8b4 100644 --- a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_frag.glsl @@ -1,3 +1,16 @@ + +/* Values in GPU_shader.h. */ +#define GPU_KEYFRAME_SHAPE_DIAMOND (1 << 0) +#define GPU_KEYFRAME_SHAPE_CIRCLE (1 << 1) +#define GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL (1 << 2) +#define GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL (1 << 3) +#define GPU_KEYFRAME_SHAPE_INNER_DOT (1 << 4) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MAX (1 << 8) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIN (1 << 9) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIXED (1 << 10) +#define GPU_KEYFRAME_SHAPE_SQUARE \ + (GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL) + flat in vec4 radii; flat in vec4 thresholds; @@ -27,24 +40,24 @@ void main() float outline_dist = -1.0; /* Diamond outline */ - if (test(0x1)) { + if (test(GPU_KEYFRAME_SHAPE_DIAMOND)) { outline_dist = max(outline_dist, radius - radii[0]); } /* Circle outline */ - if (test(0x2)) { + if (test(GPU_KEYFRAME_SHAPE_CIRCLE)) { radius = length(absPos); outline_dist = max(outline_dist, radius - radii[1]); } /* Top & Bottom clamp */ - if (test(0x4)) { + if (test(GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL)) { outline_dist = max(outline_dist, absPos.y - radii[2]); } /* Left & Right clamp */ - if (test(0x8)) { + if (test(GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL)) { outline_dist = max(outline_dist, absPos.x - radii[2]); } @@ -53,20 +66,20 @@ void main() /* Inside the outline. */ if (outline_dist < 0) { /* Middle dot */ - if (test(0x10)) { - alpha = max(alpha, 1 - smoothstep(thresholds[2], thresholds[3], radius)); + if (test(GPU_KEYFRAME_SHAPE_INNER_DOT)) { + alpha = max(alpha, 1 - smoothstep(thresholds[2], thresholds[3], length(absPos))); } /* Up and down arrow-like shading. */ - if (test(0x300)) { + if (test(GPU_KEYFRAME_SHAPE_ARROW_END_MAX | GPU_KEYFRAME_SHAPE_ARROW_END_MIN)) { float ypos = -1.0; /* Up arrow (maximum) */ - if (test(0x100)) { + if (test(GPU_KEYFRAME_SHAPE_ARROW_END_MAX)) { ypos = max(ypos, pos.y); } /* Down arrow (minimum) */ - if (test(0x200)) { + if (test(GPU_KEYFRAME_SHAPE_ARROW_END_MIN)) { ypos = max(ypos, -pos.y); } @@ -75,7 +88,7 @@ void main() float minmax_step = smoothstep(thresholds[0], thresholds[1], minmax_dist * minmax_scale); /* Reduced alpha for uncertain extremes. */ - float minmax_alpha = test(0x400) ? 0.55 : 0.85; + float minmax_alpha = test(GPU_KEYFRAME_SHAPE_ARROW_END_MIXED) ? 0.55 : 0.85; alpha = max(alpha, minmax_step * minmax_alpha); } diff --git a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_vert.glsl b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_vert.glsl index 2ba89230d80..18e8b76ba23 100644 --- a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_vert.glsl +++ b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_vert.glsl @@ -1,4 +1,16 @@ +/* Values in GPU_shader.h. */ +#define GPU_KEYFRAME_SHAPE_DIAMOND (1 << 0) +#define GPU_KEYFRAME_SHAPE_CIRCLE (1 << 1) +#define GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL (1 << 2) +#define GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL (1 << 3) +#define GPU_KEYFRAME_SHAPE_INNER_DOT (1 << 4) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MAX (1 << 8) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIN (1 << 9) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIXED (1 << 10) +#define GPU_KEYFRAME_SHAPE_SQUARE \ + (GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL) + uniform mat4 ModelViewProjectionMatrix; uniform vec2 ViewportSize = vec2(-1, -1); uniform float outline_scale = 1.0; @@ -49,8 +61,9 @@ void main() finalOutlineColor = outlineColor; finalFlags = flags; - if (!test(0xF)) { - finalFlags |= 1; + if (!test(GPU_KEYFRAME_SHAPE_DIAMOND | GPU_KEYFRAME_SHAPE_CIRCLE | + GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL)) { + finalFlags |= GPU_KEYFRAME_SHAPE_DIAMOND; } /* Size-dependent line thickness. */ diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_float_curve.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_float_curve.glsl new file mode 100644 index 00000000000..514409f7fdf --- /dev/null +++ b/source/blender/gpu/shaders/material/gpu_shader_material_float_curve.glsl @@ -0,0 +1,33 @@ +/* ext is vec4(in_x, in_dy, out_x, out_dy). */ +float curve_float_extrapolate(float x, float y, vec4 ext) +{ + if (x < 0.0) { + return y + x * ext.y; + } + else if (x > 1.0) { + return y + (x - 1.0) * ext.w; + } + else { + return y; + } +} + +#define RANGE_RESCALE(x, min, range) ((x - min) * range) + +void curve_float(float fac, + float vec, + sampler1DArray curvemap, + float layer, + float range, + vec4 ext, + out float outvec) +{ + float xyz_min = ext.x; + vec = RANGE_RESCALE(vec, xyz_min, range); + + outvec = texture(curvemap, vec2(vec, layer)).x; + + outvec = curve_float_extrapolate(vec, outvec, ext); + + outvec = mix(vec, outvec, fac); +} diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl index 6330daa4391..6ffa6b59572 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl @@ -10,12 +10,15 @@ float wang_hash_noise(uint s) return fract(float(s) / 4294967296.0); } -void node_hair_info(out float is_strand, +void node_hair_info(float hair_length, + out float is_strand, out float intercept, + out float length, out float thickness, out vec3 tangent, out float random) { + length = hair_length; #ifdef HAIR_SHADER is_strand = 1.0; intercept = hairTime; diff --git a/source/blender/gpu/tests/gpu_shader_builtin_test.cc b/source/blender/gpu/tests/gpu_shader_builtin_test.cc index f0061a6bf5c..523a7e5b881 100644 --- a/source/blender/gpu/tests/gpu_shader_builtin_test.cc +++ b/source/blender/gpu/tests/gpu_shader_builtin_test.cc @@ -32,7 +32,7 @@ static void test_shader_builtin() test_compile_builtin_shader(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA); test_compile_builtin_shader(GPU_SHADER_TEXT, GPU_SHADER_CFG_DEFAULT); - test_compile_builtin_shader(GPU_SHADER_KEYFRAME_DIAMOND, GPU_SHADER_CFG_DEFAULT); + test_compile_builtin_shader(GPU_SHADER_KEYFRAME_SHAPE, GPU_SHADER_CFG_DEFAULT); test_compile_builtin_shader(GPU_SHADER_SIMPLE_LIGHTING, GPU_SHADER_CFG_DEFAULT); test_compile_builtin_shader(GPU_SHADER_2D_UNIFORM_COLOR, GPU_SHADER_CFG_DEFAULT); test_compile_builtin_shader(GPU_SHADER_2D_FLAT_COLOR, GPU_SHADER_CFG_DEFAULT); diff --git a/source/blender/ikplugin/intern/itasc_plugin.cpp b/source/blender/ikplugin/intern/itasc_plugin.cpp index b9411f6dd2d..a9e1692ebf0 100644 --- a/source/blender/ikplugin/intern/itasc_plugin.cpp +++ b/source/blender/ikplugin/intern/itasc_plugin.cpp @@ -644,7 +644,7 @@ static bool base_callback(const iTaSC::Timestamp ×tamp, ikscene->baseFrame = iTaSC::F_identity; } next.setValue(&rootmat[0][0]); - /* if there is a polar target (only during solving otherwise we don't have end efffector) */ + /* If there is a polar target (only during solving otherwise we don't have end effector). */ if (ikscene->polarConstraint && timestamp.update) { /* compute additional rotation of base frame so that armature follows the polar target */ float imat[4][4]; /* IK tree base inverse matrix */ diff --git a/source/blender/imbuf/intern/IMB_indexer.h b/source/blender/imbuf/intern/IMB_indexer.h index 37309ccc13a..6c66b11df4f 100644 --- a/source/blender/imbuf/intern/IMB_indexer.h +++ b/source/blender/imbuf/intern/IMB_indexer.h @@ -33,7 +33,7 @@ * a) different time-codes within one file (like DTS/PTS, Time-code-Track, * "implicit" time-codes within DV-files and HDV-files etc.) * b) seeking difficulties within FFMPEG for files with timestamp holes - * c) broken files that miss several frames / have varying framerates + * c) broken files that miss several frames / have varying frame-rates * d) use proxies accordingly * * ... we need index files, that provide us with diff --git a/source/blender/imbuf/intern/openexr/openexr_api.cpp b/source/blender/imbuf/intern/openexr/openexr_api.cpp index cd323e72003..adf09f8dda8 100644 --- a/source/blender/imbuf/intern/openexr/openexr_api.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_api.cpp @@ -555,7 +555,7 @@ static bool imb_save_openexr_float(ImBuf *ibuf, const char *name, const int flag int xstride = sizeof(float) * channels; int ystride = -xstride * width; - /* last scanline, stride negative */ + /* Last scan-line, stride negative. */ float *rect[4] = {nullptr, nullptr, nullptr, nullptr}; rect[0] = ibuf->rect_float + channels * (height - 1) * width; rect[1] = (channels >= 2) ? rect[0] + 1 : rect[0]; @@ -654,7 +654,7 @@ struct ExrChannel { char name[EXR_TOT_MAXNAME + 1]; /* full name with everything */ struct MultiViewChannelName *m; /* struct to store all multipart channel info */ - int xstride, ystride; /* step to next pixel, to next scanline */ + int xstride, ystride; /* step to next pixel, to next scan-line. */ float *rect; /* first pointer to write in */ char chan_id; /* quick lookup of channel char */ int view_id; /* quick lookup of channel view */ @@ -681,6 +681,8 @@ struct ExrLayer { ListBase passes; }; +static bool imb_exr_multilayer_parse_channels_from_file(ExrHandle *data); + /* ********************** */ void *IMB_exr_get_handle(void) @@ -839,12 +841,12 @@ void IMB_exr_add_channel(void *handle, } /* used for output files (from RenderResult) (single and multilayer, single and multiview) */ -int IMB_exr_begin_write(void *handle, - const char *filename, - int width, - int height, - int compress, - const StampData *stamp) +bool IMB_exr_begin_write(void *handle, + const char *filename, + int width, + int height, + int compress, + const StampData *stamp) { ExrHandle *data = (ExrHandle *)handle; Header header(width, height); @@ -962,51 +964,64 @@ void IMB_exrtile_begin_write( } /* read from file */ -int IMB_exr_begin_read(void *handle, const char *filename, int *width, int *height) +bool IMB_exr_begin_read( + void *handle, const char *filename, int *width, int *height, const bool parse_channels) { ExrHandle *data = (ExrHandle *)handle; ExrChannel *echan; /* 32 is arbitrary, but zero length files crashes exr. */ - if (BLI_exists(filename) && BLI_file_size(filename) > 32) { - /* avoid crash/abort when we don't have permission to write here */ - try { - data->ifile_stream = new IFileStream(filename); - data->ifile = new MultiPartInputFile(*(data->ifile_stream)); - } - catch (const std::exception &) { - delete data->ifile; - delete data->ifile_stream; + if (!(BLI_exists(filename) && BLI_file_size(filename) > 32)) { + return false; + } - data->ifile = nullptr; - data->ifile_stream = nullptr; - } + /* avoid crash/abort when we don't have permission to write here */ + try { + data->ifile_stream = new IFileStream(filename); + data->ifile = new MultiPartInputFile(*(data->ifile_stream)); + } + catch (const std::exception &) { + delete data->ifile; + delete data->ifile_stream; - if (data->ifile) { - Box2i dw = data->ifile->header(0).dataWindow(); - data->width = *width = dw.max.x - dw.min.x + 1; - data->height = *height = dw.max.y - dw.min.y + 1; + data->ifile = nullptr; + data->ifile_stream = nullptr; + } - imb_exr_get_views(*data->ifile, *data->multiView); + if (!data->ifile) { + return false; + } - std::vector<MultiViewChannelName> channels; - GetChannelsInMultiPartFile(*data->ifile, channels); + Box2i dw = data->ifile->header(0).dataWindow(); + data->width = *width = dw.max.x - dw.min.x + 1; + data->height = *height = dw.max.y - dw.min.y + 1; - for (const MultiViewChannelName &channel : channels) { - IMB_exr_add_channel( - data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false); + if (parse_channels) { + /* Parse channels into view/layer/pass. */ + if (!imb_exr_multilayer_parse_channels_from_file(data)) { + return false; + } + } + else { + /* Read view and channels without parsing. */ + imb_exr_get_views(*data->ifile, *data->multiView); - echan = (ExrChannel *)data->channels.last; - echan->m->name = channel.name; - echan->m->view = channel.view; - echan->m->part_number = channel.part_number; - echan->m->internal_name = channel.internal_name; - } + std::vector<MultiViewChannelName> channels; + GetChannelsInMultiPartFile(*data->ifile, channels); + + for (const MultiViewChannelName &channel : channels) { + IMB_exr_add_channel( + data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false); - return 1; + echan = (ExrChannel *)data->channels.last; + echan->m->name = channel.name; + echan->m->view = channel.view; + echan->m->part_number = channel.part_number; + echan->m->internal_name = channel.internal_name; } } - return 0; + + return true; } /* still clumsy name handling, layers/channels can be ordered as list in list later */ @@ -1112,7 +1127,7 @@ void IMB_exr_write_channels(void *handle) } for (echan = (ExrChannel *)data->channels.first; echan; echan = echan->next) { - /* Writing starts from last scanline, stride negative. */ + /* Writing starts from last scan-line, stride negative. */ if (echan->use_half_float) { float *rect = echan->rect; half *cur = current_rect_half; @@ -1254,7 +1269,7 @@ void IMB_exr_read_channels(void *handle) if (!flip) { /* Inverse correct first pixel for data-window coordinates. */ rect -= echan->xstride * (dw.min.x - dw.min.y * data->width); - /* move to last scanline to flip to Blender convention */ + /* Move to last scan-line to flip to Blender convention. */ rect += echan->xstride * (data->height - 1) * data->width; ystride = -ystride; } @@ -1524,25 +1539,8 @@ static ExrPass *imb_exr_get_pass(ListBase *lb, char *passname) return pass; } -/* creates channels, makes a hierarchy and assigns memory to channels */ -static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, - MultiPartInputFile &file, - int width, - int height) +static bool imb_exr_multilayer_parse_channels_from_file(ExrHandle *data) { - ExrLayer *lay; - ExrPass *pass; - ExrChannel *echan; - ExrHandle *data = (ExrHandle *)IMB_exr_get_handle(); - int a; - char layname[EXR_TOT_MAXNAME], passname[EXR_TOT_MAXNAME]; - - data->ifile_stream = &file_stream; - data->ifile = &file; - - data->width = width; - data->height = height; - std::vector<MultiViewChannelName> channels; GetChannelsInMultiPartFile(*data->ifile, channels); @@ -1552,7 +1550,7 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, IMB_exr_add_channel( data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false); - echan = (ExrChannel *)data->channels.last; + ExrChannel *echan = (ExrChannel *)data->channels.last; echan->m->name = channel.name; echan->m->view = channel.view; echan->m->part_number = channel.part_number; @@ -1561,7 +1559,9 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, /* now try to sort out how to assign memory to the channels */ /* first build hierarchical layer list */ - for (echan = (ExrChannel *)data->channels.first; echan; echan = echan->next) { + ExrChannel *echan = (ExrChannel *)data->channels.first; + for (; echan; echan = echan->next) { + char layname[EXR_TOT_MAXNAME], passname[EXR_TOT_MAXNAME]; if (imb_exr_split_channel_name(echan, layname, passname)) { const char *view = echan->m->view.c_str(); @@ -1591,21 +1591,20 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, } if (echan) { printf("error, too many channels in one pass: %s\n", echan->m->name.c_str()); - IMB_exr_close(data); - return nullptr; + return false; } /* with some heuristics, try to merge the channels in buffers */ - for (lay = (ExrLayer *)data->layers.first; lay; lay = lay->next) { - for (pass = (ExrPass *)lay->passes.first; pass; pass = pass->next) { + for (ExrLayer *lay = (ExrLayer *)data->layers.first; lay; lay = lay->next) { + for (ExrPass *pass = (ExrPass *)lay->passes.first; pass; pass = pass->next) { if (pass->totchan) { - pass->rect = (float *)MEM_callocN(width * height * pass->totchan * sizeof(float), - "pass rect"); + pass->rect = (float *)MEM_callocN( + data->width * data->height * pass->totchan * sizeof(float), "pass rect"); if (pass->totchan == 1) { - echan = pass->chan[0]; + ExrChannel *echan = pass->chan[0]; echan->rect = pass->rect; echan->xstride = 1; - echan->ystride = width; + echan->ystride = data->width; pass->chan_id[0] = echan->chan_id; } else { @@ -1634,20 +1633,20 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, lookup[(unsigned int)'V'] = 1; lookup[(unsigned int)'A'] = 2; } - for (a = 0; a < pass->totchan; a++) { + for (int a = 0; a < pass->totchan; a++) { echan = pass->chan[a]; echan->rect = pass->rect + lookup[(unsigned int)echan->chan_id]; echan->xstride = pass->totchan; - echan->ystride = width * pass->totchan; + echan->ystride = data->width * pass->totchan; pass->chan_id[(unsigned int)lookup[(unsigned int)echan->chan_id]] = echan->chan_id; } } else { /* unknown */ - for (a = 0; a < pass->totchan; a++) { - echan = pass->chan[a]; + for (int a = 0; a < pass->totchan; a++) { + ExrChannel *echan = pass->chan[a]; echan->rect = pass->rect + a; echan->xstride = pass->totchan; - echan->ystride = width * pass->totchan; + echan->ystride = data->width * pass->totchan; pass->chan_id[a] = echan->chan_id; } } @@ -1656,6 +1655,28 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, } } + return true; +} + +/* creates channels, makes a hierarchy and assigns memory to channels */ +static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, + MultiPartInputFile &file, + int width, + int height) +{ + ExrHandle *data = (ExrHandle *)IMB_exr_get_handle(); + + data->ifile_stream = &file_stream; + data->ifile = &file; + + data->width = width; + data->height = height; + + if (!imb_exr_multilayer_parse_channels_from_file(data)) { + IMB_exr_close(data); + return nullptr; + } + return data; } @@ -1988,7 +2009,7 @@ struct ImBuf *imb_load_openexr(const unsigned char *mem, /* Inverse correct first pixel for data-window * coordinates (- dw.min.y because of y flip). */ first = ibuf->rect_float - 4 * (dw.min.x - dw.min.y * width); - /* but, since we read y-flipped (negative y stride) we move to last scanline */ + /* But, since we read y-flipped (negative y stride) we move to last scan-line. */ first += 4 * (height - 1) * width; if (num_rgb_channels > 0) { diff --git a/source/blender/imbuf/intern/openexr/openexr_multi.h b/source/blender/imbuf/intern/openexr/openexr_multi.h index 556717ad618..82a5d161ded 100644 --- a/source/blender/imbuf/intern/openexr/openexr_multi.h +++ b/source/blender/imbuf/intern/openexr/openexr_multi.h @@ -50,13 +50,14 @@ void IMB_exr_add_channel(void *handle, float *rect, bool use_half_float); -int IMB_exr_begin_read(void *handle, const char *filename, int *width, int *height); -int IMB_exr_begin_write(void *handle, - const char *filename, - int width, - int height, - int compress, - const struct StampData *stamp); +bool IMB_exr_begin_read( + void *handle, const char *filename, int *width, int *height, const bool parse_channels); +bool IMB_exr_begin_write(void *handle, + const char *filename, + int width, + int height, + int compress, + const struct StampData *stamp); void IMB_exrtile_begin_write( void *handle, const char *filename, int mipmap, int width, int height, int tilex, int tiley); diff --git a/source/blender/imbuf/intern/openexr/openexr_stub.cpp b/source/blender/imbuf/intern/openexr/openexr_stub.cpp index 51bc2094053..9b4d6178613 100644 --- a/source/blender/imbuf/intern/openexr/openexr_stub.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_stub.cpp @@ -43,21 +43,22 @@ void IMB_exr_add_channel(void * /*handle*/, { } -int IMB_exr_begin_read(void * /*handle*/, - const char * /*filename*/, - int * /*width*/, - int * /*height*/) +bool IMB_exr_begin_read(void * /*handle*/, + const char * /*filename*/, + int * /*width*/, + int * /*height*/, + const bool /*add_channels*/) { - return 0; + return false; } -int IMB_exr_begin_write(void * /*handle*/, - const char * /*filename*/, - int /*width*/, - int /*height*/, - int /*compress*/, - const struct StampData * /*stamp*/) +bool IMB_exr_begin_write(void * /*handle*/, + const char * /*filename*/, + int /*width*/, + int /*height*/, + int /*compress*/, + const struct StampData * /*stamp*/) { - return 0; + return false; } void IMB_exrtile_begin_write(void * /*handle*/, const char * /*filename*/, diff --git a/source/blender/imbuf/intern/radiance_hdr.c b/source/blender/imbuf/intern/radiance_hdr.c index 94b2a62aa26..7f4e4dd31df 100644 --- a/source/blender/imbuf/intern/radiance_hdr.c +++ b/source/blender/imbuf/intern/radiance_hdr.c @@ -294,7 +294,7 @@ struct ImBuf *imb_loadhdr(const unsigned char *mem, break; } for (x = 0; x < width; x++) { - /* convert to ldr */ + /* Convert to LDR. */ RGBE2FLOAT(sline[x], fcol); *rect_float++ = fcol[RED]; *rect_float++ = fcol[GRN]; @@ -328,7 +328,7 @@ static int fwritecolrs( rgbe_scan = (RGBE *)MEM_mallocN(sizeof(RGBE) * width, "radhdr_write_tmpscan"); - /* convert scanline */ + /* Convert scan-line. */ for (size_t i = 0, j = 0; i < width; i++) { if (fpscan) { fcol[RED] = fpscan[j]; diff --git a/source/blender/imbuf/intern/readimage.c b/source/blender/imbuf/intern/readimage.c index 50210650f05..c75bdfa375c 100644 --- a/source/blender/imbuf/intern/readimage.c +++ b/source/blender/imbuf/intern/readimage.c @@ -126,7 +126,7 @@ ImBuf *IMB_ibImageFromMemory(const unsigned char *mem, } if ((flags & IB_test) == 0) { - fprintf(stderr, "%s: unknown fileformat (%s)\n", __func__, descr); + fprintf(stderr, "%s: unknown file-format (%s)\n", __func__, descr); } return NULL; diff --git a/source/blender/imbuf/intern/rotate.c b/source/blender/imbuf/intern/rotate.c index c2fc2190ce5..83dc29aa107 100644 --- a/source/blender/imbuf/intern/rotate.c +++ b/source/blender/imbuf/intern/rotate.c @@ -69,7 +69,7 @@ void IMB_flipy(struct ImBuf *ibuf) topf = ibuf->rect_float; bottomf = topf + 4 * ((y - 1) * x); - linef = MEM_mallocN(4 * x * sizeof(float), "linebuff"); + linef = MEM_mallocN(4 * x * sizeof(float), "linebuf"); y >>= 1; diff --git a/source/blender/imbuf/intern/targa.c b/source/blender/imbuf/intern/targa.c index 8ed0b8b535c..333e29e6d97 100644 --- a/source/blender/imbuf/intern/targa.c +++ b/source/blender/imbuf/intern/targa.c @@ -192,7 +192,7 @@ static bool makebody_tga(ImBuf *ibuf, FILE *file, int (*out)(unsigned int, FILE else { while (*rect++ == this) { /* seek for first different byte */ if (--bytes == 0) { - break; /* oor end of line */ + break; /* Or end of line. */ } } rect--; @@ -470,7 +470,7 @@ static void decodetarga(struct ImBuf *ibuf, const unsigned char *mem, size_t mem if (psize & 2) { if (psize & 1) { - /* order = bgra */ + /* Order = BGRA. */ cp[0] = mem[3]; cp[1] = mem[0]; cp[2] = mem[1]; @@ -512,7 +512,7 @@ static void decodetarga(struct ImBuf *ibuf, const unsigned char *mem, size_t mem while (count > 0) { if (psize & 2) { if (psize & 1) { - /* order = bgra */ + /* Order = BGRA. */ cp[0] = mem[3]; cp[1] = mem[0]; cp[2] = mem[1]; @@ -589,7 +589,7 @@ static void ldtarga(struct ImBuf *ibuf, const unsigned char *mem, size_t mem_siz if (psize & 2) { if (psize & 1) { - /* order = bgra */ + /* Order = BGRA. */ cp[0] = mem[3]; cp[1] = mem[0]; cp[2] = mem[1]; diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc index e3ed897458d..087d60f8896 100644 --- a/source/blender/io/alembic/intern/abc_customdata.cc +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -247,13 +247,13 @@ void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig & coords[vertex_idx].setValue(orco_yup[0], orco_yup[1], orco_yup[2]); } - if (!config.abc_ocro.valid()) { + if (!config.abc_orco.valid()) { /* Create the Alembic property and keep a reference so future frames can reuse it. */ - config.abc_ocro = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1); + config.abc_orco = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1); } OV3fGeomParam::Sample sample(coords, kVertexScope); - config.abc_ocro.set(sample); + config.abc_orco.set(sample); } void write_custom_data(const OCompoundProperty &prop, diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index 4fba6a2f0f7..03e6f697f0c 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -79,8 +79,8 @@ struct CDStreamConfig { * UV map is kept alive by the Alembic mesh sample itself. */ std::map<std::string, Alembic::AbcGeom::OV2fGeomParam> abc_uv_maps; - /* OCRO coordinates, aka Generated Coordinates. */ - Alembic::AbcGeom::OV3fGeomParam abc_ocro; + /* ORCO coordinates, aka Generated Coordinates. */ + Alembic::AbcGeom::OV3fGeomParam abc_orco; CDStreamConfig() : mloop(NULL), @@ -122,12 +122,6 @@ void read_custom_data(const std::string &iobject_full_name, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss); -void read_velocity(const Alembic::Abc::ICompoundProperty &prop, - const Alembic::Abc::PropertyHeader *prop_header, - const Alembic::Abc::ISampleSelector &selector, - const CDStreamConfig &config, - const char *velocity_name, - const float velocity_scale); typedef enum { ABC_UV_SCOPE_NONE, ABC_UV_SCOPE_LOOP, diff --git a/source/blender/io/alembic/intern/abc_reader_camera.h b/source/blender/io/alembic/intern/abc_reader_camera.h index 408e9623970..ca8dee80c9d 100644 --- a/source/blender/io/alembic/intern/abc_reader_camera.h +++ b/source/blender/io/alembic/intern/abc_reader_camera.h @@ -23,18 +23,18 @@ namespace blender::io::alembic { -class AbcCameraReader : public AbcObjectReader { +class AbcCameraReader final : public AbcObjectReader { Alembic::AbcGeom::ICameraSchema m_schema; public: AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; }; } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h index 11b23c8a8cf..df5d68d7850 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.h +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -31,24 +31,24 @@ struct Curve; namespace blender::io::alembic { -class AbcCurveReader : public AbcObjectReader { +class AbcCurveReader final : public AbcObjectReader { Alembic::AbcGeom::ICurvesSchema m_curves_schema; public: AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, const int read_flag, const char *velocity_name, const float velocity_scale, - const char **err_str); + const char **err_str) override; void read_curve_sample(Curve *cu, const Alembic::AbcGeom::ICurvesSchema &schema, diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h index d9f89cc8085..2e34ca8ded0 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.h +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -26,7 +26,7 @@ struct Mesh; namespace blender::io::alembic { -class AbcMeshReader : public AbcObjectReader { +class AbcMeshReader final : public AbcObjectReader { Alembic::AbcGeom::IPolyMeshSchema m_schema; CDStreamConfig m_mesh_data; @@ -60,7 +60,7 @@ class AbcMeshReader : public AbcObjectReader { std::map<std::string, int> &r_mat_map); }; -class AbcSubDReader : public AbcObjectReader { +class AbcSubDReader final : public AbcObjectReader { Alembic::AbcGeom::ISubDSchema m_schema; CDStreamConfig m_mesh_data; @@ -68,17 +68,17 @@ class AbcSubDReader : public AbcObjectReader { public: AbcSubDReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + const char **err_str) const override; + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, const int read_flag, const char *velocity_name, const float velocity_scale, - const char **err_str); + const char **err_str) override; }; void read_mverts(MVert *mverts, diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc index 25567aa8c24..4492d1e1ca0 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.cc +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc @@ -71,6 +71,26 @@ bool AbcNurbsReader::valid() const return true; } +bool AbcNurbsReader::accepts_object_type( + const Alembic::AbcCoreAbstract::v12::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::INuPatch::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to NURBS when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_CURVE) { + *err_str = "Object type mismatch, Alembic object path points to NURBS."; + return false; + } + + return true; +} + static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots) { if (!knots || knots->size() < 2) { diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.h b/source/blender/io/alembic/intern/abc_reader_nurbs.h index e8be2efba9f..66e68cf6942 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.h +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.h @@ -23,15 +23,19 @@ namespace blender::io::alembic { -class AbcNurbsReader : public AbcObjectReader { +class AbcNurbsReader final : public AbcObjectReader { std::vector<std::pair<Alembic::AbcGeom::INuPatchSchema, Alembic::Abc::IObject>> m_schemas; public: AbcNurbsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const override; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; private: void getNurbsPatches(const Alembic::Abc::IObject &obj); diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc index 3aeacbd14fe..75ed18f57f2 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.cc +++ b/source/blender/io/alembic/intern/abc_reader_points.cc @@ -82,7 +82,7 @@ bool AbcPointsReader::accepts_object_type( void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) { Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, nullptr); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, "", 0.0f, nullptr); if (read_mesh != mesh) { BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); @@ -127,6 +127,8 @@ void read_points_sample(const IPointsSchema &schema, struct Mesh *AbcPointsReader::read_mesh(struct Mesh *existing_mesh, const ISampleSelector &sample_sel, int read_flag, + const char * /*velocity_name*/, + const float /*velocity_scale*/, const char **err_str) { IPointsSchema::Sample sample; diff --git a/source/blender/io/alembic/intern/abc_reader_points.h b/source/blender/io/alembic/intern/abc_reader_points.h index aed66699c75..105d1276f7a 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.h +++ b/source/blender/io/alembic/intern/abc_reader_points.h @@ -27,24 +27,26 @@ namespace blender::io::alembic { -class AbcPointsReader : public AbcObjectReader { +class AbcPointsReader final : public AbcObjectReader { Alembic::AbcGeom::IPointsSchema m_schema; Alembic::AbcGeom::IPointsSchema::Sample m_sample; public: AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, int read_flag, - const char **err_str); + const char *velocity_name, + const float velocity_scale, + const char **err_str) override; }; void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema, diff --git a/source/blender/io/alembic/intern/abc_reader_transform.h b/source/blender/io/alembic/intern/abc_reader_transform.h index e515560912f..6810cc214b7 100644 --- a/source/blender/io/alembic/intern/abc_reader_transform.h +++ b/source/blender/io/alembic/intern/abc_reader_transform.h @@ -25,18 +25,18 @@ namespace blender::io::alembic { -class AbcEmptyReader : public AbcObjectReader { +class AbcEmptyReader final : public AbcObjectReader { Alembic::AbcGeom::IXformSchema m_schema; public: AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; }; } // namespace blender::io::alembic diff --git a/source/blender/makesdna/DNA_asset_types.h b/source/blender/makesdna/DNA_asset_types.h index 2975915eccd..f5bdad3e79e 100644 --- a/source/blender/makesdna/DNA_asset_types.h +++ b/source/blender/makesdna/DNA_asset_types.h @@ -22,6 +22,7 @@ #include "DNA_defs.h" #include "DNA_listBase.h" +#include "DNA_uuid_types.h" #ifdef __cplusplus extern "C" { @@ -58,6 +59,19 @@ typedef struct AssetMetaData { /** Custom asset meta-data. Cannot store pointers to IDs (#STRUCT_NO_DATABLOCK_IDPROPERTIES)! */ struct IDProperty *properties; + /** + * Asset Catalog identifier. Should not contain spaces. + * Mapped to a path in the asset catalog hierarchy by an #AssetCatalogService. + * Use #BKE_asset_metadata_catalog_id_set() to ensure a valid ID is set. + */ + struct bUUID catalog_id; + /** + * Short name of the asset's catalog. This is for debugging purposes only, to allow (partial) + * reconstruction of asset catalogs in the unfortunate case that the mapping from catalog UUID to + * catalog path is lost. The catalog's simple name is copied to #catalog_simple_name whenever + * #catalog_id is updated. */ + char catalog_simple_name[64]; /* MAX_NAME */ + /** Optional description of this asset for display in the UI. Dynamic length. */ char *description; /** User defined tags for this asset. The asset manager uses these for filtering, but how they diff --git a/source/blender/makesdna/DNA_cloth_types.h b/source/blender/makesdna/DNA_cloth_types.h index e2eea5e5422..49b2862032f 100644 --- a/source/blender/makesdna/DNA_cloth_types.h +++ b/source/blender/makesdna/DNA_cloth_types.h @@ -42,7 +42,7 @@ extern "C" { */ typedef struct ClothSimSettings { - /** UNUSED atm. */ + /** UNUSED. */ struct LinkNode *cache; /** See SB. */ float mingoal; diff --git a/source/blender/makesdna/DNA_constraint_types.h b/source/blender/makesdna/DNA_constraint_types.h index 4b4c24a7a4e..28756395f7d 100644 --- a/source/blender/makesdna/DNA_constraint_types.h +++ b/source/blender/makesdna/DNA_constraint_types.h @@ -81,8 +81,8 @@ typedef struct bConstraint { /** Local influence ipo or driver */ struct Ipo *ipo DNA_DEPRECATED; - /* below are readonly fields that are set at runtime - * by the solver for use in the GE (only IK atm) */ + /* Below are read-only fields that are set at runtime + * by the solver for use in the GE (only IK at the moment). */ /** Residual error on constraint expressed in blender unit. */ float lin_error; /** Residual error on constraint expressed in radiant. */ diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index 6acea8da15f..36bdd4915ff 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -84,7 +84,8 @@ typedef struct CustomData { * MUST be >= CD_NUMTYPES, but we can't use a define here. * Correct size is ensured in CustomData_update_typemap assert(). */ - int typemap[51]; + int typemap[52]; + char _pad[4]; /** Number of layers, size of layers array. */ int totlayer, maxlayer; /** In editmode, total size of all data layers. */ @@ -166,7 +167,9 @@ typedef enum CustomDataType { CD_PROP_BOOL = 50, - CD_NUMTYPES = 51, + CD_HAIRLENGTH = 51, + + CD_NUMTYPES = 52, } CustomDataType; /* Bits for CustomDataMask */ @@ -220,6 +223,8 @@ typedef enum CustomDataType { #define CD_MASK_PROP_FLOAT2 (1ULL << CD_PROP_FLOAT2) #define CD_MASK_PROP_BOOL (1ULL << CD_PROP_BOOL) +#define CD_MASK_HAIRLENGTH (1ULL << CD_HAIRLENGTH) + /** Multires loop data. */ #define CD_MASK_MULTIRES_GRIDS (CD_MASK_MDISPS | CD_GRID_PAINT_MASK) diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index 2a3c6f4e3db..11299ae9717 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -318,7 +318,7 @@ .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_ALLOW_CLIPPING_BOUNDARIES | LRT_USE_CREASE_ON_SHARP_EDGES, \ .angle_splitting_threshold = DEG2RAD(60.0f), \ .chaining_image_threshold = 0.001f, \ - .overscan = 0.1f,\ + .chain_smooth_tolerance = 0.2f,\ } #define _DNA_DEFAULT_LengthGpencilModifierData \ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index 8d967a38808..ea5c81761c6 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -1040,7 +1040,11 @@ typedef struct LineartGpencilModifierData { /** `0..PI` angle, for splitting strokes at sharp points. */ float angle_splitting_threshold; - /* Doubles as geometry threshold when geometry space chaining is enabled */ + /** Strength for smoothing jagged chains. */ + float chain_smooth_tolerance; + int _pad1; + + /* CPU mode */ float chaining_image_threshold; /* Ported from SceneLineArt flags. */ diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 68bd2961f23..0f570f8603d 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -49,6 +49,8 @@ struct MDeformVert; #define GPENCIL_MIN_FILL_FAC 0.05f #define GPENCIL_MAX_FILL_FAC 8.0f +#define GPENCIL_MAX_THICKNESS 5000 + /* ***************************************** */ /* GP Stroke Points */ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 31daa778b03..631db64ddd3 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -848,7 +848,7 @@ typedef struct CollisionModifierData { struct MVert *x; /** Position at the end of the frame. */ struct MVert *xnew; - /** Unused atm, but was discussed during sprint. */ + /** Unused at the moment, but was discussed during sprint. */ struct MVert *xold; /** New position at the actual inter-frame step. */ struct MVert *current_xnew; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 18545666796..ea87cef1118 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -120,7 +120,10 @@ typedef struct bNodeSocket { /* XXX deprecated, kept for forward compatibility */ short stack_type DNA_DEPRECATED; char display_shape; - char _pad[1]; + + /* #AttributeDomain used when the geometry nodes modifier creates an attribute for a group + * output. */ + char attribute_domain; /* Runtime-only cache of the number of input links, for multi-input sockets. */ short total_inputs; @@ -445,6 +448,7 @@ typedef struct bNodeLink { #define NODE_LINK_TEST (1 << 2) /* free test flag, undefined */ #define NODE_LINK_TEMP_HIGHLIGHT (1 << 3) /* Link is highlighted for picking. */ #define NODE_LINK_MUTED (1 << 4) /* Link is muted. */ +#define NODE_LINK_DRAGGED (1 << 5) /* Node link is being dragged by the user. */ /* tree->edit_quality/tree->render_quality */ #define NTREE_QUALITY_HIGH 0 @@ -459,6 +463,16 @@ typedef struct bNodeLink { #define NTREE_CHUNKSIZE_512 512 #define NTREE_CHUNKSIZE_1024 1024 +/** Workaround to forward-declare C++ type in C header. */ +#ifdef __cplusplus +namespace blender::nodes { +struct FieldInferencingInterface; +} +using FieldInferencingInterfaceHandle = blender::nodes::FieldInferencingInterface; +#else +typedef struct FieldInferencingInterfaceHandle FieldInferencingInterfaceHandle; +#endif + /* the basis for a Node tree, all links and nodes reside internal here */ /* only re-usable node trees are in the library though, * materials and textures allocate own tree struct */ @@ -481,6 +495,8 @@ typedef struct bNodeTree { float view_center[2]; ListBase nodes, links; + /** Information about how inputs and outputs of the node group interact with fields. */ + FieldInferencingInterfaceHandle *field_inferencing_interface; /** Set init on fileread. */ int type, init; @@ -575,6 +591,9 @@ typedef enum eNodeTreeUpdate { NTREE_UPDATE_NODES = (1 << 1), /* nodes or sockets have been added or removed */ NTREE_UPDATE_GROUP_IN = (1 << 4), /* group inputs have changed */ NTREE_UPDATE_GROUP_OUT = (1 << 5), /* group outputs have changed */ + /* The field interface has changed. So e.g. an output that was always a field before is not + * anymore. This implies that the field type inferencing has to be done again. */ + NTREE_UPDATE_FIELD_INFERENCING = (1 << 6), /* group has changed (generic flag including all other group flags) */ NTREE_UPDATE_GROUP = (NTREE_UPDATE_GROUP_IN | NTREE_UPDATE_GROUP_OUT), } eNodeTreeUpdate; @@ -836,7 +855,7 @@ typedef struct NodeVertexCol { char name[64]; } NodeVertexCol; -/* qdn: Defocus blur node */ +/** Defocus blur node. */ typedef struct NodeDefocus { char bktype, _pad0, preview, gamco; short samples, no_zbuf; @@ -852,7 +871,7 @@ typedef struct NodeScriptDict { void *node; } NodeScriptDict; -/* qdn: glare node */ +/** glare node. */ typedef struct NodeGlare { char quality, type, iter; /* XXX angle is only kept for backward/forward compatibility, @@ -863,14 +882,14 @@ typedef struct NodeGlare { char _pad1[4]; } NodeGlare; -/* qdn: tonemap node */ +/** Tonemap node. */ typedef struct NodeTonemap { float key, offset, gamma; float f, m, a, c; int type; } NodeTonemap; -/* qdn: lens distortion node */ +/** Lens distortion node. */ typedef struct NodeLensDist { short jit, proj, fit; char _pad[2]; @@ -1222,6 +1241,11 @@ typedef struct NodeAttributeMix { uint8_t input_type_b; } NodeAttributeMix; +typedef struct NodeRandomValue { + /* CustomDataType. */ + uint8_t data_type; +} NodeRandomValue; + typedef struct NodeAttributeRandomize { /* CustomDataType. */ uint8_t data_type; @@ -1338,6 +1362,11 @@ typedef struct NodeGeometryAttributeProximity { uint8_t target_geometry_element; } NodeGeometryAttributeProximity; +typedef struct NodeGeometryProximity { + /* GeometryNodeProximityTargetType. */ + uint8_t target_element; +} NodeGeometryProximity; + typedef struct NodeGeometryVolumeToMesh { /* VolumeToMeshResolutionMode */ uint8_t resolution_mode; @@ -1487,6 +1516,11 @@ typedef struct NodeGeometryCurveFill { uint8_t mode; } NodeGeometryCurveFill; +typedef struct NodeGeometryMeshToPoints { + /* GeometryNodeMeshToPointsMode */ + uint8_t mode; +} NodeGeometryMeshToPoints; + typedef struct NodeGeometryAttributeCapture { /* CustomDataType. */ int8_t data_type; @@ -1494,6 +1528,16 @@ typedef struct NodeGeometryAttributeCapture { int8_t domain; } NodeGeometryAttributeCapture; +typedef struct NodeGeometryStringToCurves { + /* GeometryNodeStringToCurvesOverflowMode */ + uint8_t overflow; + /* GeometryNodeStringToCurvesAlignXMode */ + uint8_t align_x; + /* GeometryNodeStringToCurvesAlignYMode */ + uint8_t align_y; + char _pad[1]; +} NodeGeometryStringToCurves; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -1907,6 +1951,12 @@ typedef enum GeometryNodeAttributeProximityTargetType { GEO_NODE_PROXIMITY_TARGET_FACES = 2, } GeometryNodeAttributeProximityTargetType; +typedef enum GeometryNodeProximityTargetType { + GEO_NODE_PROX_TARGET_POINTS = 0, + GEO_NODE_PROX_TARGET_EDGES = 1, + GEO_NODE_PROX_TARGET_FACES = 2, +} GeometryNodeProximityTargetType; + typedef enum GeometryNodeBooleanOperation { GEO_NODE_BOOLEAN_INTERSECT = 0, GEO_NODE_BOOLEAN_UNION = 1, @@ -1972,11 +2022,21 @@ typedef enum GeometryNodePointDistributeMode { GEO_NODE_POINT_DISTRIBUTE_POISSON = 1, } GeometryNodePointDistributeMode; +typedef enum GeometryNodeDistributePointsOnFacesMode { + GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM = 0, + GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON = 1, +} GeometryNodeDistributePointsOnFacesMode; + typedef enum GeometryNodeRotatePointsType { GEO_NODE_POINT_ROTATE_TYPE_EULER = 0, GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE = 1, } GeometryNodeRotatePointsType; +typedef enum FunctionNodeRotatePointsType { + FN_NODE_ROTATE_EULER_TYPE_EULER = 0, + FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE = 1, +} FunctionNodeRotatePointsType; + typedef enum GeometryNodeAttributeVectorRotateMode { GEO_NODE_VECTOR_ROTATE_TYPE_AXIS = 0, GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X = 1, @@ -1997,6 +2057,11 @@ typedef enum GeometryNodeRotatePointsSpace { GEO_NODE_POINT_ROTATE_SPACE_POINT = 1, } GeometryNodeRotatePointsSpace; +typedef enum FunctionNodeRotateEulerSpace { + FN_NODE_ROTATE_EULER_SPACE_OBJECT = 0, + FN_NODE_ROTATE_EULER_SPACE_POINT = 1, +} FunctionNodeRotateEulerSpace; + typedef enum GeometryNodeAlignRotationToVectorAxis { GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X = 0, GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_Y = 1, @@ -2085,6 +2150,35 @@ typedef enum GeometryNodeCurveFillMode { GEO_NODE_CURVE_FILL_MODE_NGONS = 1, } GeometryNodeCurveFillMode; +typedef enum GeometryNodeMeshToPointsMode { + GEO_NODE_MESH_TO_POINTS_VERTICES = 0, + GEO_NODE_MESH_TO_POINTS_EDGES = 1, + GEO_NODE_MESH_TO_POINTS_FACES = 2, + GEO_NODE_MESH_TO_POINTS_CORNERS = 3, +} GeometryNodeMeshToPointsMode; + +typedef enum GeometryNodeStringToCurvesOverflowMode { + GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW = 0, + GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT = 1, + GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE = 2, +} GeometryNodeStringToCurvesOverflowMode; + +typedef enum GeometryNodeStringToCurvesAlignXMode { + GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT = 0, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_CENTER = 1, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_RIGHT = 2, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_JUSTIFY = 3, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_FLUSH = 4, +} GeometryNodeStringToCurvesAlignXMode; + +typedef enum GeometryNodeStringToCurvesAlignYMode { + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE = 0, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP = 1, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_MIDDLE = 2, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM_BASELINE = 3, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM = 4, +} GeometryNodeStringToCurvesAlignYMode; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index b28c3ac2b85..9a5c51825e1 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1463,14 +1463,15 @@ typedef struct ToolSettings { char edge_mode_live_unwrap; - char _pad1[1]; - /* Transform */ char transform_pivot_point; char transform_flag; - char snap_mode, snap_node_mode; + char snap_mode; + char snap_node_mode; char snap_uv_mode; char snap_flag; + /** UV equivalent of `snap_flag`, limited to: #SCE_SNAP_ABS_GRID. */ + char snap_uv_flag; char snap_target; char snap_transform_mode_flag; diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 25330acd486..a71f86eae9f 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -79,9 +79,13 @@ typedef struct StripTransform { } StripTransform; typedef struct StripColorBalance { + int method; float lift[3]; float gamma[3]; float gain[3]; + float slope[3]; + float offset[3]; + float power[3]; int flag; char _pad[4]; /* float exposure; */ @@ -232,15 +236,21 @@ typedef struct Sequence { int blend_mode; float blend_opacity; + /* Tag color showed if `SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG` is set. */ + int8_t color_tag; + + char alpha_mode; + char _pad4[2]; + + int cache_flag; + /* is sfra needed anymore? - it looks like its only used in one place */ /** Starting frame according to the timeline of the scene. */ int sfra; - char alpha_mode; - char _pad[2]; - /* Multiview */ char views_format; + char _pad[3]; struct Stereo3dFormat *stereo3d_format; struct IDProperty *prop; @@ -248,9 +258,6 @@ typedef struct Sequence { /* modifiers */ ListBase modifiers; - int cache_flag; - int _pad2[3]; - SequenceRuntime runtime; } Sequence; @@ -431,6 +438,11 @@ typedef struct ColorBalanceModifierData { float color_multiply; } ColorBalanceModifierData; +enum { + SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN = 0, + SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER = 1, +}; + typedef struct CurvesModifierData { SequenceModifierData modifier; @@ -486,7 +498,7 @@ typedef struct SequencerScopes { struct ImBuf *histogram_ibuf; } SequencerScopes; -#define MAXSEQ 32 +#define MAXSEQ 128 #define SELECT 1 @@ -568,6 +580,9 @@ enum { #define SEQ_COLOR_BALANCE_INVERSE_GAIN 1 #define SEQ_COLOR_BALANCE_INVERSE_GAMMA 2 #define SEQ_COLOR_BALANCE_INVERSE_LIFT 4 +#define SEQ_COLOR_BALANCE_INVERSE_SLOPE 8 +#define SEQ_COLOR_BALANCE_INVERSE_OFFSET 16 +#define SEQ_COLOR_BALANCE_INVERSE_POWER 32 /* !!! has to be same as IMB_imbuf.h IMB_PROXY_... and IMB_TC_... */ @@ -727,6 +742,22 @@ enum { SEQ_CACHE_STORE_THUMBNAIL = (1 << 12), }; +/* Sequence->color_tag. */ +typedef enum SequenceColorTag { + SEQUENCE_COLOR_NONE = -1, + SEQUENCE_COLOR_01, + SEQUENCE_COLOR_02, + SEQUENCE_COLOR_03, + SEQUENCE_COLOR_04, + SEQUENCE_COLOR_05, + SEQUENCE_COLOR_06, + SEQUENCE_COLOR_07, + SEQUENCE_COLOR_08, + SEQUENCE_COLOR_09, + + SEQUENCE_COLOR_TOT, +} SequenceColorTag; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index e849039fa93..aa74e7712c0 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -599,6 +599,7 @@ typedef struct SequencerTimelineOverlay { typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag { SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1), SEQ_TIMELINE_SHOW_THUMBNAILS = (1 << 2), + SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG = (1 << 3), /* use Sequence->color_tag */ SEQ_TIMELINE_SHOW_FCURVES = (1 << 5), SEQ_TIMELINE_ALL_WAVEFORMS = (1 << 7), /* draw all waveforms */ SEQ_TIMELINE_NO_WAVEFORMS = (1 << 8), /* draw no waveforms */ @@ -805,14 +806,25 @@ typedef struct FileAssetSelectParams { FileSelectParams base_params; AssetLibraryReference asset_library_ref; + short asset_catalog_visibility; /* eFileSel_Params_AssetCatalogVisibility */ + char _pad[6]; + /** If #asset_catalog_visibility is #FILE_SHOW_ASSETS_FROM_CATALOG, this sets the ID of the + * catalog to show. */ + bUUID catalog_id; short import_type; /* eFileAssetImportType */ - char _pad[6]; + char _pad2[6]; } FileAssetSelectParams; typedef enum eFileAssetImportType { + /** Regular data-block linking. */ FILE_ASSET_IMPORT_LINK = 0, + /** Regular data-block appending (basically linking + "Make Local"). */ FILE_ASSET_IMPORT_APPEND = 1, + /** Append data-block with the #BLO_LIBLINK_APPEND_LOCAL_ID_REUSE flag enabled. Some typically + * heavy data dependencies (e.g. the image data-blocks of a material, the mesh of an object) may + * be reused from an earlier append. */ + FILE_ASSET_IMPORT_APPEND_REUSE = 2, } eFileAssetImportType; /** @@ -965,7 +977,10 @@ enum eFileDetails { typedef enum eFileSelectType { FILE_LOADLIB = 1, FILE_MAIN = 2, + /** Load assets from #Main. */ FILE_MAIN_ASSET = 3, + /** Load assets of an asset library containing external files. */ + FILE_ASSET_LIBRARY = 4, FILE_UNIX = 8, FILE_BLENDER = 8, /* don't display relative paths */ @@ -984,23 +999,31 @@ typedef enum eFileSel_Action { * (WM and BLO code area, see #eBLOLibLinkFlags in BLO_readfile.h). */ typedef enum eFileSel_Params_Flag { - FILE_APPEND_SET_FAKEUSER = (1 << 0), + FILE_PARAMS_FLAG_UNUSED_1 = (1 << 0), FILE_RELPATH = (1 << 1), FILE_LINK = (1 << 2), FILE_HIDE_DOT = (1 << 3), FILE_AUTOSELECT = (1 << 4), FILE_ACTIVE_COLLECTION = (1 << 5), - FILE_APPEND_RECURSIVE = (1 << 6), + FILE_PARAMS_FLAG_UNUSED_2 = (1 << 6), FILE_DIRSEL_ONLY = (1 << 7), FILE_FILTER = (1 << 8), - FILE_OBDATA_INSTANCE = (1 << 9), - FILE_COLLECTION_INSTANCE = (1 << 10), + FILE_PARAMS_FLAG_UNUSED_3 = (1 << 9), + FILE_PARAMS_FLAG_UNUSED_4 = (1 << 10), FILE_SORT_INVERT = (1 << 11), FILE_HIDE_TOOL_PROPS = (1 << 12), FILE_CHECK_EXISTING = (1 << 13), FILE_ASSETS_ONLY = (1 << 14), + /** Enables filtering by asset catalog. */ + FILE_FILTER_ASSET_CATALOG = (1 << 15), } eFileSel_Params_Flag; +typedef enum eFileSel_Params_AssetCatalogVisibility { + FILE_SHOW_ASSETS_ALL_CATALOGS, + FILE_SHOW_ASSETS_FROM_CATALOG, + FILE_SHOW_ASSETS_WITHOUT_CATALOG, +} eFileSel_Params_AssetCatalogVisibility; + /* sfile->params->rename_flag */ /* NOTE: short flag. Defined as bitflags, but currently only used as exclusive status markers... */ typedef enum eFileSel_Params_RenameFlag { @@ -1175,13 +1198,10 @@ typedef struct SpaceImage { char mode_prev; char pin; - char _pad1; - /** - * The currently active tile of the image when tile is enabled, - * is kept in sync with the active faces tile. - */ - short curtile; - short lock; + + char pixel_snap_mode; + + char lock; /** UV draw type. */ char dt_uv; /** Sticky selection type. */ @@ -1189,14 +1209,19 @@ typedef struct SpaceImage { char dt_uvstretch; char around; - int flag; + char _pad1[3]; - char pixel_snap_mode; - char _pad2[7]; + int flag; float uv_opacity; int tile_grid_shape[2]; + /** + * UV editor custom-grid. Value of `N` will produce `NxN` grid. + * Use when #SI_CUSTOM_GRID is set. + */ + int custom_grid_subdiv; + char _pad3[4]; MaskSpaceInfo mask_info; SpaceImageOverlay overlay; @@ -1252,6 +1277,7 @@ typedef enum eSpaceImage_Flag { SI_FLAG_UNUSED_7 = (1 << 7), /* cleared */ SI_FLAG_UNUSED_8 = (1 << 8), /* cleared */ SI_COORDFLOATS = (1 << 9), + SI_FLAG_UNUSED_10 = (1 << 10), SI_LIVE_UNWRAP = (1 << 11), SI_USE_ALPHA = (1 << 12), @@ -1263,7 +1289,7 @@ typedef enum eSpaceImage_Flag { SI_FULLWINDOW = (1 << 16), SI_FLAG_UNUSED_17 = (1 << 17), - SI_FLAG_UNUSED_18 = (1 << 18), /* cleared */ + SI_CUSTOM_GRID = (1 << 18), /** * This means that the image is drawn until it reaches the view edge, @@ -1289,6 +1315,9 @@ typedef enum eSpaceImageOverlay_Flag { SI_OVERLAY_SHOW_OVERLAYS = (1 << 0), } eSpaceImageOverlay_Flag; +/** Keep in sync with `STEPS_LEN` in `grid_frag.glsl`. */ +#define SI_GRID_STEPS_LEN 8 + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/makesdna/DNA_tracking_types.h b/source/blender/makesdna/DNA_tracking_types.h index fb2d985d353..0e313183300 100644 --- a/source/blender/makesdna/DNA_tracking_types.h +++ b/source/blender/makesdna/DNA_tracking_types.h @@ -398,6 +398,8 @@ typedef struct MovieTrackingDopesheetChannel { int *segments; /** Longest segment length and total number of tracked frames. */ int max_segment, total_frames; + /** These numbers are valid only if tot_segment > 0. */ + int first_not_disabled_marker_framenr, last_not_disabled_marker_framenr; } MovieTrackingDopesheetChannel; typedef struct MovieTrackingDopesheetCoverageSegment { @@ -592,6 +594,8 @@ enum { TRACKING_DOPE_SORT_LONGEST = 1, TRACKING_DOPE_SORT_TOTAL = 2, TRACKING_DOPE_SORT_AVERAGE_ERROR = 3, + TRACKING_DOPE_SORT_START = 4, + TRACKING_DOPE_SORT_END = 5, }; /* MovieTrackingDopesheet->flag */ diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 4f86201ced2..291f6de5ba2 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -458,6 +458,10 @@ typedef struct ThemeCollectionColor { unsigned char color[4]; } ThemeCollectionColor; +typedef struct ThemeStripColor { + unsigned char color[4]; +} ThemeStripColor; + /** * A theme. * @@ -500,8 +504,10 @@ typedef struct bTheme { /* See COLLECTION_COLOR_TOT for the number of collection colors. */ ThemeCollectionColor collection_color[8]; + /* See SEQUENCE_COLOR_TOT for the total number of strip colors. */ + ThemeStripColor strip_color[9]; + int active_theme_area; - char _pad0[4]; } bTheme; #define UI_THEMESPACE_START(btheme) \ @@ -635,7 +641,9 @@ typedef struct UserDef_Experimental { /* Debug options, always available. */ char use_undo_legacy; char no_override_auto_resync; + char no_proxy_to_override_conversion; char use_cycles_debug; + char use_geometry_nodes_legacy; char SANITIZE_AFTER_HERE; /* The following options are automatically sanitized (set to 0) * when the release cycle is not alpha. */ @@ -646,8 +654,7 @@ typedef struct UserDef_Experimental { char use_sculpt_tools_tilt; char use_extended_asset_browser; char use_override_templates; - char use_geometry_nodes_fields; - char _pad[4]; + char _pad[3]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesdna/DNA_uuid_types.h b/source/blender/makesdna/DNA_uuid_types.h index fa0a78f074b..dcebfed6be7 100644 --- a/source/blender/makesdna/DNA_uuid_types.h +++ b/source/blender/makesdna/DNA_uuid_types.h @@ -40,6 +40,12 @@ typedef struct bUUID { uint8_t node[6]; } bUUID; +/** + * Memory required for a string representation of a UUID according to RFC4122. + * This is 36 characters for the string + a trailing zero byte. + */ +#define UUID_STRING_LEN 37 + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index ce53e3390e1..188f933dba5 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -558,6 +558,7 @@ extern StructRNA RNA_ShaderFxWave; extern StructRNA RNA_ShaderNode; extern StructRNA RNA_ShaderNodeCameraData; extern StructRNA RNA_ShaderNodeCombineRGB; +extern StructRNA RNA_ShaderNodeFloatCurve; extern StructRNA RNA_ShaderNodeGamma; extern StructRNA RNA_ShaderNodeHueSaturation; extern StructRNA RNA_ShaderNodeInvert; diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index c8f44262020..03d371be1f7 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -211,6 +211,7 @@ DEF_ENUM(rna_enum_attribute_domain_items) DEF_ENUM(rna_enum_attribute_domain_with_auto_items) DEF_ENUM(rna_enum_collection_color_items) +DEF_ENUM(rna_enum_strip_color_items) DEF_ENUM(rna_enum_subdivision_uv_smooth_items) DEF_ENUM(rna_enum_subdivision_boundary_smooth_items) diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index fceb6d045c3..f1980eed811 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -2206,6 +2206,7 @@ void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop) rna_property_update(C, CTX_data_main(C), CTX_data_scene(C), ptr, prop); } +/* NOTE: `scene` pointer may be NULL. */ void RNA_property_update_main(Main *bmain, Scene *scene, PointerRNA *ptr, PropertyRNA *prop) { rna_property_update(NULL, bmain, scene, ptr, prop); diff --git a/source/blender/makesrna/intern/rna_access_compare_override.c b/source/blender/makesrna/intern/rna_access_compare_override.c index f8a36c1b2e6..be8972dbff3 100644 --- a/source/blender/makesrna/intern/rna_access_compare_override.c +++ b/source/blender/makesrna/intern/rna_access_compare_override.c @@ -614,20 +614,25 @@ static bool rna_property_override_operation_apply(Main *bmain, } /* get and set the default values as appropriate for the various types */ - return override_apply(bmain, - ptr_dst, - ptr_src, - ptr_storage, - prop_dst, - prop_src, - prop_storage, - len_dst, - len_src, - len_storage, - ptr_item_dst, - ptr_item_src, - ptr_item_storage, - opop); + const bool sucess = override_apply(bmain, + ptr_dst, + ptr_src, + ptr_storage, + prop_dst, + prop_src, + prop_storage, + len_dst, + len_src, + len_storage, + ptr_item_dst, + ptr_item_src, + ptr_item_storage, + opop); + if (sucess) { + RNA_property_update_main(bmain, NULL, ptr_dst, prop_dst); + } + + return sucess; } /** diff --git a/source/blender/makesrna/intern/rna_asset.c b/source/blender/makesrna/intern/rna_asset.c index 1e583f4ca52..80824df1bc8 100644 --- a/source/blender/makesrna/intern/rna_asset.c +++ b/source/blender/makesrna/intern/rna_asset.c @@ -32,19 +32,67 @@ #ifdef RNA_RUNTIME # include "BKE_asset.h" +# include "BKE_asset_library.h" +# include "BKE_context.h" # include "BKE_idprop.h" # include "BLI_listbase.h" +# include "BLI_uuid.h" # include "ED_asset.h" +# include "ED_fileselect.h" # include "RNA_access.h" -static AssetTag *rna_AssetMetaData_tag_new(AssetMetaData *asset_data, - ReportList *reports, - const char *name, - bool skip_if_exists) +static bool rna_AssetMetaData_editable_from_owner_id(const ID *owner_id, + const AssetMetaData *asset_data, + const char **r_info) { + if (owner_id && asset_data && (owner_id->asset_data == asset_data)) { + return true; + } + + if (r_info) { + *r_info = + "Asset metadata from external asset libraries can't be edited, only assets stored in the " + "current file can"; + } + return false; +} + +int rna_AssetMetaData_editable(PointerRNA *ptr, const char **r_info) +{ + AssetMetaData *asset_data = ptr->data; + + return rna_AssetMetaData_editable_from_owner_id(ptr->owner_id, asset_data, r_info) ? + PROP_EDITABLE : + 0; +} + +static int rna_AssetTag_editable(PointerRNA *ptr, const char **r_info) +{ + AssetTag *asset_tag = ptr->data; + ID *owner_id = ptr->owner_id; + if (owner_id && owner_id->asset_data) { + BLI_assert_msg(BLI_findindex(&owner_id->asset_data->tags, asset_tag) != -1, + "The owner of the asset tag pointer is not the asset ID containing the tag"); + UNUSED_VARS_NDEBUG(asset_tag); + } + + return rna_AssetMetaData_editable_from_owner_id(ptr->owner_id, owner_id->asset_data, r_info) ? + PROP_EDITABLE : + 0; +} + +static AssetTag *rna_AssetMetaData_tag_new( + ID *id, AssetMetaData *asset_data, ReportList *reports, const char *name, bool skip_if_exists) +{ + const char *disabled_info = NULL; + if (!rna_AssetMetaData_editable_from_owner_id(id, asset_data, &disabled_info)) { + BKE_report(reports, RPT_WARNING, disabled_info); + return NULL; + } + AssetTag *tag = NULL; if (skip_if_exists) { @@ -64,10 +112,17 @@ static AssetTag *rna_AssetMetaData_tag_new(AssetMetaData *asset_data, return tag; } -static void rna_AssetMetaData_tag_remove(AssetMetaData *asset_data, +static void rna_AssetMetaData_tag_remove(ID *id, + AssetMetaData *asset_data, ReportList *reports, PointerRNA *tag_ptr) { + const char *disabled_info = NULL; + if (!rna_AssetMetaData_editable_from_owner_id(id, asset_data, &disabled_info)) { + BKE_report(reports, RPT_WARNING, disabled_info); + return; + } + AssetTag *tag = tag_ptr->data; if (BLI_findindex(&asset_data->tags, tag) == -1) { BKE_reportf(reports, RPT_ERROR, "Tag '%s' not found in given asset", tag->name); @@ -126,6 +181,40 @@ static void rna_AssetMetaData_active_tag_range( *max = *softmax = MAX2(asset_data->tot_tags - 1, 0); } +static void rna_AssetMetaData_catalog_id_get(PointerRNA *ptr, char *value) +{ + const AssetMetaData *asset_data = ptr->data; + BLI_uuid_format(value, asset_data->catalog_id); +} + +static int rna_AssetMetaData_catalog_id_length(PointerRNA *UNUSED(ptr)) +{ + return UUID_STRING_LEN - 1; +} + +static void rna_AssetMetaData_catalog_id_set(PointerRNA *ptr, const char *value) +{ + AssetMetaData *asset_data = ptr->data; + bUUID new_uuid; + + if (value[0] == '\0') { + BKE_asset_metadata_catalog_id_clear(asset_data); + return; + } + + if (!BLI_uuid_parse_string(&new_uuid, value)) { + // TODO(Sybren): raise ValueError exception once that's possible from an RNA setter. + printf("UUID %s not formatted correctly, ignoring new value\n", value); + return; + } + + /* This just sets the new UUID and clears the catalog simple name. The actual + * catalog simple name will be updated by some update function, as it + * needs the asset library from the context. */ + /* TODO(Sybren): write that update function. */ + BKE_asset_metadata_catalog_id_set(asset_data, new_uuid, ""); +} + static PointerRNA rna_AssetHandle_file_data_get(PointerRNA *ptr) { AssetHandle *asset_handle = ptr->data; @@ -185,6 +274,7 @@ static void rna_def_asset_tag(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Asset Tag", "User defined tag (name token)"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_editable_func(prop, "rna_AssetTag_editable"); RNA_def_property_string_maxlength(prop, MAX_NAME); RNA_def_property_ui_text(prop, "Name", "The identifier that makes up this tag"); RNA_def_struct_name_property(srna, prop); @@ -205,7 +295,7 @@ static void rna_def_asset_tags_api(BlenderRNA *brna, PropertyRNA *cprop) /* Tag collection */ func = RNA_def_function(srna, "new", "rna_AssetMetaData_tag_new"); RNA_def_function_ui_description(func, "Add a new tag to this asset"); - RNA_def_function_flag(func, FUNC_USE_REPORTS); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS); parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_boolean(func, @@ -219,7 +309,7 @@ static void rna_def_asset_tags_api(BlenderRNA *brna, PropertyRNA *cprop) func = RNA_def_function(srna, "remove", "rna_AssetMetaData_tag_remove"); RNA_def_function_ui_description(func, "Remove an existing tag from this asset"); - RNA_def_function_flag(func, FUNC_USE_REPORTS); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS); /* tag to remove */ parm = RNA_def_pointer(func, "tag", "AssetTag", "", "Removed tag"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); @@ -239,6 +329,7 @@ static void rna_def_asset_data(BlenderRNA *brna) RNA_def_struct_flag(srna, STRUCT_NO_DATABLOCK_IDPROPERTIES); /* Mandatory! */ prop = RNA_def_property(srna, "description", PROP_STRING, PROP_NONE); + RNA_def_property_editable_func(prop, "rna_AssetMetaData_editable"); RNA_def_property_string_funcs(prop, "rna_AssetMetaData_description_get", "rna_AssetMetaData_description_length", @@ -248,7 +339,7 @@ static void rna_def_asset_data(BlenderRNA *brna) prop = RNA_def_property(srna, "tags", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "AssetTag"); - RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_editable_func(prop, "rna_AssetMetaData_editable"); RNA_def_property_ui_text(prop, "Tags", "Custom tags (name tokens) for the asset, used for filtering and " @@ -258,6 +349,24 @@ static void rna_def_asset_data(BlenderRNA *brna) prop = RNA_def_property(srna, "active_tag", PROP_INT, PROP_NONE); RNA_def_property_int_funcs(prop, NULL, NULL, "rna_AssetMetaData_active_tag_range"); RNA_def_property_ui_text(prop, "Active Tag", "Index of the tag set for editing"); + + prop = RNA_def_property(srna, "catalog_id", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, + "rna_AssetMetaData_catalog_id_get", + "rna_AssetMetaData_catalog_id_length", + "rna_AssetMetaData_catalog_id_set"); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, + "Catalog UUID", + "Identifier for the asset's catalog, used by Blender to look up the " + "asset's catalog path. Must be a UUID according to RFC4122"); + + prop = RNA_def_property(srna, "catalog_simple_name", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, + "Catalog Simple Name", + "Simple name of the asset's catalog, for debugging and " + "data recovery purposes"); } static void rna_def_asset_handle_api(StructRNA *srna) diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 25caa411979..1d3b8cd9f9c 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -734,6 +734,12 @@ static void rna_Brush_icon_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Poi WM_main_add_notifier(NC_BRUSH | NA_EDITED, br); } +static bool rna_Brush_imagetype_poll(PointerRNA *UNUSED(ptr), PointerRNA value) +{ + Image *image = (Image *)value.owner_id; + return image->type != IMA_TYPE_R_RESULT && image->type != IMA_TYPE_COMPOSITE; +} + static void rna_TextureSlot_brush_angle_update(bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); @@ -3434,6 +3440,7 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Clone Image", "Image for clone tool"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, "rna_Brush_update"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_Brush_imagetype_poll"); prop = RNA_def_property(srna, "clone_alpha", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "clone.alpha"); diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 6480d16ccd2..675cff3e58c 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -26,6 +26,7 @@ #include "DNA_brush_types.h" #include "DNA_cachefile_types.h" #include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" #include "DNA_mesh_types.h" #include "DNA_object_force_types.h" #include "DNA_object_types.h" @@ -59,6 +60,12 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { {0, "", 0, N_("Modify"), ""}, + {eGpencilModifierType_Texture, + "GP_TEXTURE", + ICON_MOD_UVPROJECT, + "Texture Mapping", + "Change stroke uv texture values"}, + {eGpencilModifierType_Time, "GP_TIME", ICON_MOD_TIME, "Time Offset", "Offset keyframes"}, {eGpencilModifierType_WeightAngle, "GP_WEIGHT_ANGLE", ICON_MOD_VERTEX_WEIGHT, @@ -143,7 +150,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_THICKNESS, "Thickness", "Change stroke thickness"}, - {eGpencilModifierType_Time, "GP_TIME", ICON_MOD_TIME, "Time Offset", "Offset keyframes"}, {0, "", 0, N_("Color"), ""}, {eGpencilModifierType_Color, "GP_COLOR", @@ -155,11 +161,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_OPACITY, "Opacity", "Opacity of the strokes"}, - {eGpencilModifierType_Texture, - "GP_TEXTURE", - ICON_TEXTURE, - "Texture Mapping", - "Change stroke uv texture values"}, {eGpencilModifierType_Tint, "GP_TINT", ICON_MOD_TINT, "Tint", "Tint strokes with new color"}, {0, NULL, 0, NULL, NULL}, }; @@ -2685,7 +2686,7 @@ static void rna_def_modifier_gpenciltexture(BlenderRNA *brna) RNA_def_struct_ui_text( srna, "Texture Modifier", "Transform stroke texture coordinates Modifier"); RNA_def_struct_sdna(srna, "TextureGpencilModifierData"); - RNA_def_struct_ui_icon(srna, ICON_TEXTURE); + RNA_def_struct_ui_icon(srna, ICON_MOD_UVPROJECT); RNA_define_lib_overridable(true); @@ -2864,10 +2865,11 @@ static void rna_def_modifier_gpencilweight_proximity(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); - prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_NONE); + prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "dist_start"); - RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); - RNA_def_property_ui_text(prop, "Lowest", "Start value for distance calculation"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Lowest", "Distance mapping to 0.0 weight"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "minimum_weight", PROP_FLOAT, PROP_FACTOR); @@ -2875,10 +2877,11 @@ static void rna_def_modifier_gpencilweight_proximity(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Minimum", "Minimum value for vertex weight"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); - prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_NONE); + prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "dist_end"); - RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); - RNA_def_property_ui_text(prop, "Highest", "Max value for distance calculation"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Highest", "Distance mapping to 1.0 weight"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); @@ -3123,6 +3126,14 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) RNA_def_property_range(prop, 0.0f, DEG2RAD(180.0f)); RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "smooth_tolerance", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "chain_smooth_tolerance"); + RNA_def_property_ui_text( + prop, "Smooth Tolerance", "Strength of smoothing applied on jagged chains"); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.05f, 4); + RNA_def_property_range(prop, 0.0f, 30.0f); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "use_remove_doubles", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_REMOVE_DOUBLES); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index 4a013dc9bd7..2f42e521b52 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -161,7 +161,9 @@ static void rna_ImageUser_update(Main *bmain, Scene *scene, PointerRNA *ptr) ImageUser *iuser = ptr->data; ID *id = ptr->owner_id; - BKE_image_user_frame_calc(NULL, iuser, scene->r.cfra); + if (scene != NULL) { + BKE_image_user_frame_calc(NULL, iuser, scene->r.cfra); + } if (id) { if (GS(id->name) == ID_NT) { diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index 0bb76fd933a..fd6664ff0de 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -267,6 +267,7 @@ void rna_def_mtex_common(struct BlenderRNA *brna, void rna_def_texpaint_slots(struct BlenderRNA *brna, struct StructRNA *srna); void rna_def_view_layer_common(struct BlenderRNA *brna, struct StructRNA *srna, const bool scene); +int rna_AssetMetaData_editable(struct PointerRNA *ptr, const char **r_info); PropertyRNA *rna_def_asset_library_reference_common(struct StructRNA *srna, const char *get, const char *set); diff --git a/source/blender/makesrna/intern/rna_internal_types.h b/source/blender/makesrna/intern/rna_internal_types.h index 22a9b9d930a..29df7a53c44 100644 --- a/source/blender/makesrna/intern/rna_internal_types.h +++ b/source/blender/makesrna/intern/rna_internal_types.h @@ -44,11 +44,20 @@ typedef struct IDProperty IDProperty; /* Function Callbacks */ -typedef void (*UpdateFunc)(struct Main *main, struct Scene *scene, struct PointerRNA *ptr); +/** Update callback for an RNA property. + * + * \note This is NOT called automatically when writing into the property, it needs to be called + * manually (through #RNA_property_update or #RNA_property_update_main) when needed. + * + * \param bmain: the Main data-base to which `ptr` data belongs. + * \param active_scene: The current active scene (may be NULL in some cases). + * \param ptr: The RNA pointer data to update. */ +typedef void (*UpdateFunc)(struct Main *bmain, struct Scene *active_scene, struct PointerRNA *ptr); typedef void (*ContextPropUpdateFunc)(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop); typedef void (*ContextUpdateFunc)(struct bContext *C, struct PointerRNA *ptr); + typedef int (*EditableFunc)(struct PointerRNA *ptr, const char **r_info); typedef int (*ItemEditableFunc)(struct PointerRNA *ptr, int index); typedef struct IDProperty **(*IDPropertiesFunc)(struct PointerRNA *ptr); diff --git a/source/blender/makesrna/intern/rna_material_api.c b/source/blender/makesrna/intern/rna_material_api.c index f5b13fdbc81..5f89664458c 100644 --- a/source/blender/makesrna/intern/rna_material_api.c +++ b/source/blender/makesrna/intern/rna_material_api.c @@ -38,8 +38,8 @@ void RNA_api_material(StructRNA *UNUSED(srna)) { - /* FunctionRNA *func; */ - /* PropertyRNA *parm; */ + // FunctionRNA *func; + // PropertyRNA *parm; } #endif diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c index 17c7b331c88..d0711f28a6e 100644 --- a/source/blender/makesrna/intern/rna_nla.c +++ b/source/blender/makesrna/intern/rna_nla.c @@ -751,14 +751,14 @@ static void rna_def_nlastrip(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Influence", "Amount the strip contributes to the current result"); /* XXX: Update temporarily disabled so that the property can be edited at all! - * Even autokey only applies after the curves have been re-evaluated, + * Even auto-key only applies after the curves have been re-evaluated, * causing the unkeyed values to be lost. */ RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL); prop = RNA_def_property(srna, "strip_time", PROP_FLOAT, PROP_TIME); RNA_def_property_ui_text(prop, "Strip Time", "Frame of referenced Action to evaluate"); /* XXX: Update temporarily disabled so that the property can be edited at all! - * Even autokey only applies after the curves have been re-evaluated, + * Even auto-key only applies after the curves have been re-evaluated, * causing the unkeyed values to be lost. */ RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index b631e76c094..df1be412bc4 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -2092,6 +2092,19 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeRandom_type_itemf( return itemf_function_check(rna_enum_attribute_type_items, attribute_random_type_supported); } +static bool random_value_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_BOOL, CD_PROP_INT32); +} +static const EnumPropertyItem *rna_FunctionNodeRandomValue_type_itemf(bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_attribute_type_items, random_value_type_supported); +} + static const EnumPropertyItem *rna_GeometryNodeAttributeRandomize_operation_itemf( bContext *UNUSED(C), PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { @@ -3729,7 +3742,7 @@ static void rna_Node_image_layer_update(Main *bmain, Scene *scene, PointerRNA *p rna_Node_update(bmain, scene, ptr); - if (scene->nodetree != NULL) { + if (scene != NULL && scene->nodetree != NULL) { ntreeCompositUpdateRLayers(scene->nodetree); } } @@ -3901,7 +3914,7 @@ static const EnumPropertyItem *rna_Node_view_layer_itemf(bContext *UNUSED(C), static void rna_Node_view_layer_update(Main *bmain, Scene *scene, PointerRNA *ptr) { rna_Node_update(bmain, scene, ptr); - if (scene->nodetree != NULL) { + if (scene != NULL && scene->nodetree != NULL) { ntreeCompositUpdateRLayers(scene->nodetree); } } @@ -4336,7 +4349,7 @@ static void rna_ShaderNodeScript_update(Main *bmain, Scene *scene, PointerRNA *p { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNode *node = (bNode *)ptr->data; - RenderEngineType *engine_type = RE_engines_find(scene->r.engine); + RenderEngineType *engine_type = (scene != NULL) ? RE_engines_find(scene->r.engine) : NULL; if (engine_type && engine_type->update_script_node) { /* auto update node */ @@ -4887,6 +4900,17 @@ static void def_vector_curve(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_float_curve(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "mapping", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "storage"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Mapping", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_time(StructRNA *srna) { PropertyRNA *prop; @@ -6532,11 +6556,11 @@ static void def_cmp_levels(StructRNA *srna) PropertyRNA *prop; static const EnumPropertyItem channel_items[] = { - {1, "COMBINED_RGB", 0, "C", "Combined RGB"}, - {2, "RED", 0, "R", "Red Channel"}, - {3, "GREEN", 0, "G", "Green Channel"}, - {4, "BLUE", 0, "B", "Blue Channel"}, - {5, "LUMINANCE", 0, "L", "Luminance Channel"}, + {1, "COMBINED_RGB", 0, "Combined", "Combined RGB"}, + {2, "RED", 0, "Red", "Red Channel"}, + {3, "GREEN", 0, "Green", "Green Channel"}, + {4, "BLUE", 0, "Blue", "Blue Channel"}, + {5, "LUMINANCE", 0, "Luminance", "Luminance Channel"}, {0, NULL, 0, NULL, NULL}, }; @@ -9168,6 +9192,21 @@ static void def_geo_subdivision_surface(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_fn_random_value(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeRandomValue", "storage"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "data_type"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_FunctionNodeRandomValue_type_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_attribute_randomize(StructRNA *srna) { PropertyRNA *prop; @@ -9479,6 +9518,33 @@ static void def_geo_point_distribute(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_distribute_points_on_faces(StructRNA *srna) +{ + PropertyRNA *prop; + + static const EnumPropertyItem rna_node_geometry_distribute_points_on_faces_mode_items[] = { + {GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM, + "RANDOM", + 0, + "Random", + "Distribute points randomly on the surface"}, + {GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON, + "POISSON", + 0, + "Poisson Disk", + "Distribute the points randomly on the surface while taking a minimum distance between " + "points into account"}, + {0, NULL, 0, NULL, NULL}, + }; + + prop = RNA_def_property(srna, "distribute_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_node_geometry_distribute_points_on_faces_mode_items); + RNA_def_property_enum_default(prop, GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); + RNA_def_property_ui_text(prop, "Distribution Method", "Method to use for scattering points"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_attribute_color_ramp(StructRNA *srna) { PropertyRNA *prop; @@ -9601,6 +9667,23 @@ static void def_geo_curve_set_handles(StructRNA *srna) prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_node_geometry_curve_handle_side_items); RNA_def_property_ui_text(prop, "Mode", "Whether to update left and right handles"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + +static void def_geo_legacy_curve_set_handles(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryCurveSetHandles", "storage"); + + prop = RNA_def_property(srna, "handle_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "handle_type"); + RNA_def_property_enum_items(prop, rna_node_geometry_curve_handle_type_items); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_curve_handle_side_items); + RNA_def_property_ui_text(prop, "Mode", "Whether to update left and right handles"); RNA_def_property_flag(prop, PROP_ENUM_FLAG); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } @@ -9735,6 +9818,51 @@ static void def_geo_point_rotate(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_fn_rotate_euler(StructRNA *srna) +{ + static const EnumPropertyItem type_items[] = { + {FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE, + "AXIS_ANGLE", + ICON_NONE, + "Axis Angle", + "Rotate around an axis by an angle"}, + {FN_NODE_ROTATE_EULER_TYPE_EULER, + "EULER", + ICON_NONE, + "Euler", + "Rotate around the X, Y, and Z axes"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem space_items[] = { + {FN_NODE_ROTATE_EULER_SPACE_OBJECT, + "OBJECT", + ICON_NONE, + "Object", + "Rotate the input rotation in the local space of the object"}, + {FN_NODE_ROTATE_EULER_SPACE_POINT, + "POINT", + ICON_NONE, + "Point", + "Rotate the input rotation in its local space"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, type_items); + RNA_def_property_ui_text(prop, "Type", "Method used to describe the rotation"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "space", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, space_items); + RNA_def_property_ui_text(prop, "Space", "Base orientation of the points"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_geo_align_rotation_to_vector(StructRNA *srna) { static const EnumPropertyItem axis_items[] = { @@ -9917,7 +10045,7 @@ static void def_geo_collection_info(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } -static void def_geo_attribute_proximity(StructRNA *srna) +static void def_geo_legacy_attribute_proximity(StructRNA *srna) { static const EnumPropertyItem target_geometry_element[] = { {GEO_NODE_PROXIMITY_TARGET_POINTS, @@ -9950,6 +10078,39 @@ static void def_geo_attribute_proximity(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_proximity(StructRNA *srna) +{ + static const EnumPropertyItem target_element_items[] = { + {GEO_NODE_PROX_TARGET_POINTS, + "POINTS", + ICON_NONE, + "Points", + "Calculate the proximity to the target's points (faster than the other modes)"}, + {GEO_NODE_PROX_TARGET_EDGES, + "EDGES", + ICON_NONE, + "Edges", + "Calculate the proximity to the target's edges"}, + {GEO_NODE_PROX_TARGET_FACES, + "FACES", + ICON_NONE, + "Faces", + "Calculate the proximity to the target's faces"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryProximity", "storage"); + + prop = RNA_def_property(srna, "target_element", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, target_element_items); + RNA_def_property_enum_default(prop, GEO_NODE_PROX_TARGET_FACES); + RNA_def_property_ui_text( + prop, "Target Geometry", "Element of the target geometry to calculate the distance from"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_volume_to_mesh(StructRNA *srna) { PropertyRNA *prop; @@ -10036,7 +10197,7 @@ static void def_geo_mesh_cylinder(StructRNA *srna) prop = RNA_def_property(srna, "fill_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_node_geometry_mesh_circle_fill_type_items); RNA_def_property_ui_text(prop, "Fill Type", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_geo_mesh_cone(StructRNA *srna) @@ -10048,7 +10209,7 @@ static void def_geo_mesh_cone(StructRNA *srna) prop = RNA_def_property(srna, "fill_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_node_geometry_mesh_circle_fill_type_items); RNA_def_property_ui_text(prop, "Fill Type", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_geo_mesh_line(StructRNA *srna) @@ -10181,7 +10342,7 @@ static void def_geo_curve_resample(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } -static void def_geo_curve_subdivide(StructRNA *srna) +static void def_geo_legacy_curve_subdivide(StructRNA *srna) { PropertyRNA *prop; @@ -10251,6 +10412,42 @@ static void def_geo_curve_to_points(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_mesh_to_points(StructRNA *srna) +{ + PropertyRNA *prop; + + static EnumPropertyItem mode_items[] = { + {GEO_NODE_MESH_TO_POINTS_VERTICES, + "VERTICES", + 0, + "Vertices", + "Create a point in the point cloud for each selected vertex"}, + {GEO_NODE_MESH_TO_POINTS_EDGES, + "EDGES", + 0, + "Edges", + "Create a point in the point cloud for each selected edge"}, + {GEO_NODE_MESH_TO_POINTS_FACES, + "FACES", + 0, + "Faces", + "Create a point in the point cloud for each selected face"}, + {GEO_NODE_MESH_TO_POINTS_CORNERS, + "CORNERS", + 0, + "Corners", + "Create a point in the point cloud for each selected face corner"}, + {0, NULL, 0, NULL, NULL}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryMeshToPoints", "storage"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mode_items); + RNA_def_property_ui_text(prop, "Mode", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_geo_curve_trim(StructRNA *srna) { PropertyRNA *prop; @@ -10398,6 +10595,120 @@ static void def_geo_attribute_capture(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_string_to_curves(StructRNA *srna) +{ + static const EnumPropertyItem rna_node_geometry_string_to_curves_overflow_items[] = { + {GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW, + "OVERFLOW", + ICON_NONE, + "Overflow", + "Let the text use more space than the specified height"}, + {GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT, + "SCALE_TO_FIT", + ICON_NONE, + "Scale To Fit", + "Scale the text size to fit inside the width and height"}, + {GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE, + "TRUNCATE", + ICON_NONE, + "Truncate", + "Only output curves that fit within the width and height. Output the remainder to the " + "\"Remainder\" output"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem rna_node_geometry_string_to_curves_align_x_items[] = { + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT, + "LEFT", + ICON_ALIGN_LEFT, + "Left", + "Align text to the left"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_CENTER, + "CENTER", + ICON_ALIGN_CENTER, + "Center", + "Align text to the center"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_RIGHT, + "RIGHT", + ICON_ALIGN_RIGHT, + "Right", + "Align text to the right"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_JUSTIFY, + "JUSTIFY", + ICON_ALIGN_JUSTIFY, + "Justify", + "Align text to the left and the right"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_FLUSH, + "FLUSH", + ICON_ALIGN_FLUSH, + "Flush", + "Align text to the left and the right, with equal character spacing"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem rna_node_geometry_string_to_curves_align_y_items[] = { + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE, + "TOP_BASELINE", + ICON_ALIGN_TOP, + "Top Baseline", + "Align text to the top baseline"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP, + "TOP", + ICON_ALIGN_TOP, + "Top", + "Align text to the top"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_MIDDLE, + "MIDDLE", + ICON_ALIGN_MIDDLE, + "Middle", + "Align text to the middle"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM_BASELINE, + "BOTTOM_BASELINE", + ICON_ALIGN_BOTTOM, + "Bottom Baseline", + "Align text to the bottom baseline"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM, + "BOTTOM", + ICON_ALIGN_BOTTOM, + "Bottom", + "Align text to the bottom"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + prop = RNA_def_property(srna, "font", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "id"); + RNA_def_property_struct_type(prop, "VectorFont"); + RNA_def_property_ui_text(prop, "Font", "Font of the text. Falls back to the UI font by default"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + RNA_def_struct_sdna_from(srna, "NodeGeometryStringToCurves", "storage"); + + prop = RNA_def_property(srna, "overflow", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "overflow"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_overflow_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); + RNA_def_property_ui_text(prop, "Overflow", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "align_x", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "align_x"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_align_x_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT); + RNA_def_property_ui_text(prop, "Align X", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "align_y", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "align_y"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_align_y_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE); + RNA_def_property_ui_text(prop, "Align Y", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) @@ -10664,6 +10975,14 @@ static void rna_def_node_socket_interface(BlenderRNA *brna) prop, "Hide Value", "Hide the socket input value even when the socket is not connected"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); + prop = RNA_def_property(srna, "attribute_domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_ui_text( + prop, + "Attribute Domain", + "Attribute domain used by the geometry nodes modifier to create an attribute output"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); + /* registration */ prop = RNA_def_property(srna, "bl_socket_idname", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, NULL, "typeinfo->idname"); diff --git a/source/blender/makesrna/intern/rna_object_force.c b/source/blender/makesrna/intern/rna_object_force.c index 98d59bf3a1a..7f997109920 100644 --- a/source/blender/makesrna/intern/rna_object_force.c +++ b/source/blender/makesrna/intern/rna_object_force.c @@ -1056,8 +1056,8 @@ static void rna_def_ptcache_point_caches(BlenderRNA *brna, PropertyRNA *cprop) StructRNA *srna; PropertyRNA *prop; - /* FunctionRNA *func; */ - /* PropertyRNA *parm; */ + // FunctionRNA *func; + // PropertyRNA *parm; RNA_def_property_srna(cprop, "PointCaches"); srna = RNA_def_struct(brna, "PointCaches", NULL); diff --git a/source/blender/makesrna/intern/rna_rigidbody.c b/source/blender/makesrna/intern/rna_rigidbody.c index 0b56a73efa2..c51931d0d1a 100644 --- a/source/blender/makesrna/intern/rna_rigidbody.c +++ b/source/blender/makesrna/intern/rna_rigidbody.c @@ -215,9 +215,10 @@ static void rna_RigidBodyWorld_constraints_collection_update(Main *bmain, static void rna_RigidBodyOb_reset(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) { - RigidBodyWorld *rbw = scene->rigidbody_world; - - BKE_rigidbody_cache_reset(rbw); + if (scene != NULL) { + RigidBodyWorld *rbw = scene->rigidbody_world; + BKE_rigidbody_cache_reset(rbw); + } } static void rna_RigidBodyOb_shape_update(Main *bmain, Scene *scene, PointerRNA *ptr) diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 80fc13faab4..5b46dfa8210 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -676,7 +676,9 @@ static void rna_ToolSettings_snap_mode_set(struct PointerRNA *ptr, int value) /* Grease Pencil update cache */ static void rna_GPencil_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) { - ED_gpencil_tag_scene_gpencil(scene); + if (scene != NULL) { + ED_gpencil_tag_scene_gpencil(scene); + } } static void rna_Gpencil_extend_selection(bContext *C, PointerRNA *UNUSED(ptr)) @@ -848,8 +850,9 @@ static void rna_Scene_camera_update(Main *bmain, Scene *UNUSED(scene_unused), Po DEG_relations_tag_update(bmain); } -static void rna_Scene_fps_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_fps_update(Main *bmain, Scene *UNUSED(active_scene), PointerRNA *ptr) { + Scene *scene = (Scene *)ptr->owner_id; DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_FPS | ID_RECALC_SEQUENCER_STRIPS); /* NOTE: Tag via dependency graph will take care of all the updates ion the evaluated domain, * however, changes in FPS actually modifies an original skip length, @@ -857,9 +860,9 @@ static void rna_Scene_fps_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(p SEQ_sound_update_length(bmain, scene); } -static void rna_Scene_listener_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_listener_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_LISTENER); + DEG_id_tag_update(ptr->owner_id, ID_RECALC_AUDIO_LISTENER); } static void rna_Scene_volume_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) @@ -885,8 +888,11 @@ static const char *rna_Scene_statistics_string_get(Scene *scene, return ED_info_statistics_string(bmain, scene, view_layer); } -static void rna_Scene_framelen_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_framelen_update(Main *UNUSED(bmain), + Scene *UNUSED(active_scene), + PointerRNA *ptr) { + Scene *scene = (Scene *)ptr->owner_id; scene->r.framelen = (float)scene->r.framapto / (float)scene->r.images; } @@ -1940,9 +1946,9 @@ static void rna_Scene_use_audio_set(PointerRNA *ptr, bool value) } } -static void rna_Scene_use_audio_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_use_audio_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_MUTE); + DEG_id_tag_update(ptr->owner_id, ID_RECALC_AUDIO_MUTE); } static int rna_Scene_sync_mode_get(PointerRNA *ptr) @@ -2199,9 +2205,9 @@ static void rna_SceneCamera_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Po } } -static void rna_SceneSequencer_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_SceneSequencer_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - SEQ_cache_cleanup(scene); + SEQ_cache_cleanup((Scene *)ptr->owner_id); } static char *rna_ToolSettings_path(PointerRNA *UNUSED(ptr)) @@ -3156,6 +3162,14 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Snap UV Element", "Type of element to snap to"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + prop = RNA_def_property(srna, "use_snap_uv_grid_absolute", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "snap_uv_flag", SCE_SNAP_ABS_GRID); + RNA_def_property_ui_text( + prop, + "Absolute Grid Snap", + "Absolute grid alignment while translating (based on the pivot center)"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + prop = RNA_def_property(srna, "snap_target", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "snap_target"); RNA_def_property_enum_items(prop, rna_enum_snap_target_items); diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 1da08448c5b..a2db1536f55 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -130,7 +130,9 @@ const EnumPropertyItem rna_enum_symmetrize_direction_items[] = { static void rna_GPencil_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) { /* mark all grease pencil datablocks of the scene */ - ED_gpencil_tag_scene_gpencil(scene); + if (scene != NULL) { + ED_gpencil_tag_scene_gpencil(scene); + } } const EnumPropertyItem rna_enum_particle_edit_disconnected_hair_brush_items[] = { @@ -501,6 +503,12 @@ static void rna_ImaPaint_stencil_update(bContext *C, PointerRNA *UNUSED(ptr)) } } +static bool rna_ImaPaint_imagetype_poll(PointerRNA *UNUSED(ptr), PointerRNA value) +{ + Image *image = (Image *)value.owner_id; + return image->type != IMA_TYPE_R_RESULT && image->type != IMA_TYPE_COMPOSITE; +} + static void rna_ImaPaint_canvas_update(bContext *C, PointerRNA *UNUSED(ptr)) { Main *bmain = CTX_data_main(C); @@ -1033,17 +1041,20 @@ static void rna_def_image_paint(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE | PROP_CONTEXT_UPDATE); RNA_def_property_ui_text(prop, "Stencil Image", "Image used as stencil"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_ImaPaint_stencil_update"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_ImaPaint_imagetype_poll"); prop = RNA_def_property(srna, "canvas", PROP_POINTER, PROP_NONE); RNA_def_property_flag(prop, PROP_EDITABLE | PROP_CONTEXT_UPDATE); RNA_def_property_ui_text(prop, "Canvas", "Image used as canvas"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_ImaPaint_canvas_update"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_ImaPaint_imagetype_poll"); prop = RNA_def_property(srna, "clone_image", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "clone"); RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Clone Image", "Image used as clone source"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_ImaPaint_imagetype_poll"); prop = RNA_def_property(srna, "stencil_color", PROP_FLOAT, PROP_COLOR_GAMMA); RNA_def_property_range(prop, 0.0, 1.0); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index b713ffb68b4..e519740259c 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -81,6 +81,20 @@ const EnumPropertyItem rna_enum_sequence_modifier_type_items[] = { {0, NULL, 0, NULL, NULL}, }; +const EnumPropertyItem rna_enum_strip_color_items[] = { + {SEQUENCE_COLOR_NONE, "NONE", ICON_X, "None", "Assign no color tag to the collection"}, + {SEQUENCE_COLOR_01, "COLOR_01", ICON_SEQUENCE_COLOR_01, "Color 01", ""}, + {SEQUENCE_COLOR_02, "COLOR_02", ICON_SEQUENCE_COLOR_02, "Color 02", ""}, + {SEQUENCE_COLOR_03, "COLOR_03", ICON_SEQUENCE_COLOR_03, "Color 03", ""}, + {SEQUENCE_COLOR_04, "COLOR_04", ICON_SEQUENCE_COLOR_04, "Color 04", ""}, + {SEQUENCE_COLOR_05, "COLOR_05", ICON_SEQUENCE_COLOR_05, "Color 05", ""}, + {SEQUENCE_COLOR_06, "COLOR_06", ICON_SEQUENCE_COLOR_06, "Color 06", ""}, + {SEQUENCE_COLOR_07, "COLOR_07", ICON_SEQUENCE_COLOR_07, "Color 07", ""}, + {SEQUENCE_COLOR_08, "COLOR_08", ICON_SEQUENCE_COLOR_08, "Color 08", ""}, + {SEQUENCE_COLOR_09, "COLOR_09", ICON_SEQUENCE_COLOR_09, "Color 09", ""}, + {0, NULL, 0, NULL, NULL}, +}; + #ifdef RNA_RUNTIME # include "BKE_global.h" @@ -840,9 +854,9 @@ static int rna_Sequence_proxy_filepath_length(PointerRNA *ptr) return strlen(path); } -static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); + DEG_id_tag_update(ptr->owner_id, ID_RECALC_SEQUENCER_STRIPS); } static void rna_Sequence_pan_range( @@ -940,8 +954,9 @@ static void rna_Sequence_filepath_update(Main *bmain, Scene *UNUSED(scene), Poin rna_Sequence_invalidate_raw_update(bmain, scene, ptr); } -static void rna_Sequence_sound_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Sequence_sound_update(Main *bmain, Scene *UNUSED(active_scene), PointerRNA *ptr) { + Scene *scene = (Scene *)ptr->owner_id; DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO); DEG_relations_tag_update(bmain); } @@ -999,6 +1014,18 @@ static void rna_Sequence_opacity_set(PointerRNA *ptr, float value) seq->blend_opacity = value * 100.0f; } +static int rna_Sequence_color_tag_get(PointerRNA *ptr) +{ + Sequence *seq = (Sequence *)(ptr->data); + return seq->color_tag; +} + +static void rna_Sequence_color_tag_set(PointerRNA *ptr, int value) +{ + Sequence *seq = (Sequence *)(ptr->data); + seq->color_tag = value; +} + static bool colbalance_seq_cmp_fn(Sequence *seq, void *arg_pt) { SequenceSearchData *data = arg_pt; @@ -1564,12 +1591,28 @@ static void rna_def_color_balance(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + static const EnumPropertyItem method_items[] = { + {SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN, "LIFT_GAMMA_GAIN", 0, "Lift/Gamma/Gain", ""}, + {SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER, + "OFFSET_POWER_SLOPE", + 0, + "Offset/Power/Slope (ASC-CDL)", + "ASC-CDL standard color correction"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "SequenceColorBalanceData", NULL); RNA_def_struct_ui_text(srna, "Sequence Color Balance Data", "Color balance parameters for a sequence strip and its modifiers"); RNA_def_struct_sdna(srna, "StripColorBalance"); + prop = RNA_def_property(srna, "correction_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "method"); + RNA_def_property_enum_items(prop, method_items); + RNA_def_property_ui_text(prop, "Correction Method", ""); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + prop = RNA_def_property(srna, "lift", PROP_FLOAT, PROP_COLOR_GAMMA); RNA_def_property_ui_text(prop, "Lift", "Color balance lift (shadows)"); RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); @@ -1588,14 +1631,22 @@ static void rna_def_color_balance(BlenderRNA *brna) RNA_def_property_float_default(prop, 1.0f); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); - prop = RNA_def_property(srna, "invert_gain", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAIN); - RNA_def_property_ui_text(prop, "Inverse Gain", "Invert the gain color`"); + prop = RNA_def_property(srna, "slope", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_ui_text(prop, "Slope", "Correction for highlights"); + RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); + RNA_def_property_float_default(prop, 1.0f); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); - prop = RNA_def_property(srna, "invert_gamma", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAMMA); - RNA_def_property_ui_text(prop, "Inverse Gamma", "Invert the gamma color"); + prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_ui_text(prop, "Offset", "Correction for entire tonal range"); + RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); + RNA_def_property_float_default(prop, 1.0f); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "power", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_ui_text(prop, "Power", "Correction for midtones"); + RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); + RNA_def_property_float_default(prop, 1.0f); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); prop = RNA_def_property(srna, "invert_lift", PROP_BOOLEAN, PROP_NONE); @@ -1603,6 +1654,31 @@ static void rna_def_color_balance(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Inverse Lift", "Invert the lift color"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + prop = RNA_def_property(srna, "invert_gamma", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAMMA); + RNA_def_property_ui_text(prop, "Inverse Gamma", "Invert the gamma color"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_gain", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAIN); + RNA_def_property_ui_text(prop, "Inverse Gain", "Invert the gain color`"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_slope", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_SLOPE); + RNA_def_property_ui_text(prop, "Inverse Slope", "Invert the slope color`"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_offset", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_OFFSET); + RNA_def_property_ui_text(prop, "Inverse Offset", "Invert the offset color"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_power", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_POWER); + RNA_def_property_ui_text(prop, "Inverse Power", "Invert the power color"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + /* not yet used */ # if 0 prop = RNA_def_property(srna, "exposure", PROP_FLOAT, PROP_NONE); @@ -1937,6 +2013,14 @@ static void rna_def_sequence(BlenderRNA *brna) RNA_def_property_update( prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_preprocessed_update"); + prop = RNA_def_property(srna, "color_tag", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "color_tag"); + RNA_def_property_enum_funcs( + prop, "rna_Sequence_color_tag_get", "rna_Sequence_color_tag_set", NULL); + RNA_def_property_enum_items(prop, rna_enum_strip_color_items); + RNA_def_property_ui_text(prop, "Strip Color", "Color tag for a strip"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, NULL); + /* modifiers */ prop = RNA_def_property(srna, "modifiers", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "SequenceModifier"); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index a05cef7a1cd..652d2545e67 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -39,6 +39,7 @@ #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_uuid.h" #include "DNA_action_types.h" #include "DNA_gpencil_types.h" @@ -856,6 +857,14 @@ static void rna_Space_view2d_sync_set(PointerRNA *ptr, bool value) ARegion *region; area = rna_area_from_space(ptr); /* can be NULL */ + if ((area != NULL) && !UI_view2d_area_supports_sync(area)) { + BKE_reportf(NULL, + RPT_ERROR, + "'show_locked_time' is not supported for the '%s' editor", + area->type->name); + return; + } + region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); if (region) { View2D *v2d = ®ion->v2d; @@ -906,7 +915,7 @@ static void rna_GPencil_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *UN static void rna_SpaceView3D_camera_update(Main *bmain, Scene *scene, PointerRNA *ptr) { View3D *v3d = (View3D *)(ptr->data); - if (v3d->scenelock) { + if (v3d->scenelock && scene != NULL) { wmWindowManager *wm = bmain->wm.first; scene->camera = v3d->camera; @@ -1540,7 +1549,9 @@ static PointerRNA rna_SpaceImageEditor_uvedit_get(PointerRNA *ptr) static void rna_SpaceImageEditor_mode_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(ptr)) { - ED_space_image_paint_update(bmain, bmain->wm.first, scene); + if (scene != NULL) { + ED_space_image_paint_update(bmain, bmain->wm.first, scene); + } } static void rna_SpaceImageEditor_show_stereo_set(PointerRNA *ptr, int value) @@ -2604,16 +2615,38 @@ static void rna_FileAssetSelectParams_asset_library_set(PointerRNA *ptr, int val params->asset_library_ref = ED_asset_library_reference_from_enum_value(value); } -static void rna_FileAssetSelectParams_asset_category_set(PointerRNA *ptr, uint64_t value) +static PointerRNA rna_FileBrowser_FileSelectEntry_asset_data_get(PointerRNA *ptr) { - FileSelectParams *params = ptr->data; - params->filter_id = value; + const FileDirEntry *entry = ptr->data; + + /* Note that the owning ID of the RNA pointer (`ptr->owner_id`) has to be set carefully: + * Local IDs (`entry->id`) own their asset metadata themselves. Asset metadata from other blend + * files are owned by the file browser (`entry`). Only if this is set correctly, we can tell from + * the metadata RNA pointer if the metadata is stored locally and can thus be edited or not. */ + + if (entry->id) { + PointerRNA id_ptr; + RNA_id_pointer_create(entry->id, &id_ptr); + return rna_pointer_inherit_refine(&id_ptr, &RNA_AssetMetaData, entry->asset_data); + } + + return rna_pointer_inherit_refine(ptr, &RNA_AssetMetaData, entry->asset_data); } -static uint64_t rna_FileAssetSelectParams_asset_category_get(PointerRNA *ptr) +static int rna_FileBrowser_FileSelectEntry_name_editable(PointerRNA *ptr, const char **r_info) { - FileSelectParams *params = ptr->data; - return params->filter_id; + const FileDirEntry *entry = ptr->data; + + /* This actually always returns 0 (the name is never editable) but we want to get a disabled + * message returned to `r_info` in some cases. */ + + if (entry->asset_data) { + PointerRNA asset_data_ptr = rna_FileBrowser_FileSelectEntry_asset_data_get(ptr); + /* Get disabled hint from asset metadata polling. */ + rna_AssetMetaData_editable(&asset_data_ptr, r_info); + } + + return 0; } static void rna_FileBrowser_FileSelectEntry_name_get(PointerRNA *ptr, char *value) @@ -2672,12 +2705,6 @@ static int rna_FileBrowser_FileSelectEntry_preview_icon_id_get(PointerRNA *ptr) return ED_file_icon(entry); } -static PointerRNA rna_FileBrowser_FileSelectEntry_asset_data_get(PointerRNA *ptr) -{ - const FileDirEntry *entry = ptr->data; - return rna_pointer_inherit_refine(ptr, &RNA_AssetMetaData, entry->asset_data); -} - static StructRNA *rna_FileBrowser_params_typef(PointerRNA *ptr) { SpaceFile *sfile = ptr->data; @@ -3153,6 +3180,17 @@ static void rna_SpaceSpreadsheet_context_path_guess(SpaceSpreadsheet *sspreadshe WM_main_add_notifier(NC_SPACE | ND_SPACE_SPREADSHEET, NULL); } +static void rna_FileAssetSelectParams_catalog_id_get(PointerRNA *ptr, char *value) +{ + const FileAssetSelectParams *params = ptr->data; + BLI_uuid_format(value, params->catalog_id); +} + +static int rna_FileAssetSelectParams_catalog_id_length(PointerRNA *UNUSED(ptr)) +{ + return UUID_STRING_LEN - 1; +} + #else static const EnumPropertyItem dt_uv_items[] = { @@ -3443,6 +3481,19 @@ static void rna_def_space_image_uv(BlenderRNA *brna) prop, "Tile Grid Shape", "How many tiles will be shown in the background"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); + prop = RNA_def_property(srna, "use_custom_grid", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SI_CUSTOM_GRID); + RNA_def_property_boolean_default(prop, true); + RNA_def_property_ui_text(prop, "Custom Grid", "Use a grid with a user-defined number of steps"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); + + prop = RNA_def_property(srna, "custom_grid_subdivisions", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "custom_grid_subdiv"); + RNA_def_property_range(prop, 1, 5000); + RNA_def_property_ui_text( + prop, "Dynamic Grid Size", "Number of grid units in UV space that make one UV Unit"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); + prop = RNA_def_property(srna, "uv_opacity", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "uv_opacity"); RNA_def_property_range(prop, 0.0f, 1.0f); @@ -5449,6 +5500,12 @@ static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_THUMBNAILS); RNA_def_property_ui_text(prop, "Show Thumbnails", "Show strip thumbnails"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_strip_tag_color", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG); + RNA_def_property_ui_text( + prop, "Show Color Tags", "Display the strip color tags in the sequencer"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); } static void rna_def_space_sequencer(BlenderRNA *brna) @@ -6260,12 +6317,13 @@ static void rna_def_fileselect_entry(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "File Select Entry", "A file viewable in the File Browser"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_editable_func(prop, "rna_FileBrowser_FileSelectEntry_name_editable"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_string_funcs(prop, "rna_FileBrowser_FileSelectEntry_name_get", "rna_FileBrowser_FileSelectEntry_name_length", NULL); RNA_def_property_ui_text(prop, "Name", ""); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_struct_name_property(srna, prop); prop = RNA_def_property(srna, "relative_path", PROP_STRING, PROP_NONE); @@ -6537,47 +6595,6 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; - /* XXX copied from rna_enum_id_type_filter_items. */ - static const EnumPropertyItem asset_category_items[] = { - {FILTER_ID_SCE, "SCENES", ICON_SCENE_DATA, "Scenes", "Show scenes"}, - {FILTER_ID_AC, "ANIMATIONS", ICON_ANIM_DATA, "Animations", "Show animation data"}, - {FILTER_ID_OB | FILTER_ID_GR, - "OBJECTS_AND_COLLECTIONS", - ICON_GROUP, - "Objects & Collections", - "Show objects and collections"}, - {FILTER_ID_AR | FILTER_ID_CU | FILTER_ID_LT | FILTER_ID_MB | FILTER_ID_ME - /* XXX avoid warning */ - // | FILTER_ID_HA | FILTER_ID_PT | FILTER_ID_VO - , - "GEOMETRY", - ICON_MESH_DATA, - "Geometry", - "Show meshes, curves, lattice, armatures and metaballs data"}, - {FILTER_ID_LS | FILTER_ID_MA | FILTER_ID_NT | FILTER_ID_TE, - "SHADING", - ICON_MATERIAL_DATA, - "Shading", - "Show materials, nodetrees, textures and Freestyle's linestyles"}, - {FILTER_ID_IM | FILTER_ID_MC | FILTER_ID_MSK | FILTER_ID_SO, - "IMAGES_AND_SOUNDS", - ICON_IMAGE_DATA, - "Images & Sounds", - "Show images, movie clips, sounds and masks"}, - {FILTER_ID_CA | FILTER_ID_LA | FILTER_ID_LP | FILTER_ID_SPK | FILTER_ID_WO, - "ENVIRONMENTS", - ICON_WORLD_DATA, - "Environment", - "Show worlds, lights, cameras and speakers"}, - {FILTER_ID_BR | FILTER_ID_GD | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_TXT | - FILTER_ID_VF | FILTER_ID_CF | FILTER_ID_WS, - "MISC", - ICON_GREASEPENCIL, - "Miscellaneous", - "Show other data types"}, - {0, NULL, 0, NULL, NULL}, - }; - static const EnumPropertyItem asset_import_type_items[] = { {FILE_ASSET_IMPORT_LINK, "LINK", 0, "Link", "Import the assets as linked data-block"}, {FILE_ASSET_IMPORT_APPEND, @@ -6585,6 +6602,14 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) 0, "Append", "Import the assets as copied data-block, with no link to the original asset data-block"}, + {FILE_ASSET_IMPORT_APPEND_REUSE, + "APPEND_REUSE", + 0, + "Append (Reuse Data)", + "Import the assets as copied data-block while avoiding multiple copies of nested, " + "typically heavy data. For example the textures of a material asset, or the mesh of an " + "object asset, don't have to be copied every time this asset is imported. The instances of " + "the asset share the data instead"}, {0, NULL, 0, NULL, NULL}, }; @@ -6598,14 +6623,13 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Asset Library", ""); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); - prop = RNA_def_property(srna, "asset_category", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, asset_category_items); - RNA_def_property_enum_funcs(prop, - "rna_FileAssetSelectParams_asset_category_get", - "rna_FileAssetSelectParams_asset_category_set", - NULL); - RNA_def_property_ui_text(prop, "Asset Category", "Determine which kind of assets to display"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_LIST, NULL); + prop = RNA_def_property(srna, "catalog_id", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, + "rna_FileAssetSelectParams_catalog_id_get", + "rna_FileAssetSelectParams_catalog_id_length", + NULL); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Catalog UUID", "The UUID of the catalog shown in the browser"); prop = RNA_def_property(srna, "import_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, asset_import_type_items); diff --git a/source/blender/makesrna/intern/rna_tracking.c b/source/blender/makesrna/intern/rna_tracking.c index 336359a9dc0..c81588aa8b5 100644 --- a/source/blender/makesrna/intern/rna_tracking.c +++ b/source/blender/makesrna/intern/rna_tracking.c @@ -2437,6 +2437,8 @@ static void rna_def_trackingDopesheet(BlenderRNA *brna) 0, "Average Error", "Sort channels by average reprojection error of tracks after solve"}, + {TRACKING_DOPE_SORT_START, "START", 0, "Start Frame", "Sort channels by first frame number"}, + {TRACKING_DOPE_SORT_END, "END", 0, "End Frame", "Sort channels by last frame number"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 563c6ea35e0..ccfc1222d4c 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -3680,6 +3680,23 @@ static void rna_def_userdef_theme_collection_color(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); } +static void rna_def_userdef_theme_strip_color(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "ThemeStripColor", NULL); + RNA_def_struct_sdna(srna, "ThemeStripColor"); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "Theme Strip Color", "Theme settings for strip colors"); + + prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_float_sdna(prop, NULL, "color"); + RNA_def_property_array(prop, 3); + RNA_def_property_ui_text(prop, "Color", "Strip Color"); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); +} + static void rna_def_userdef_theme_space_clip(BlenderRNA *brna) { StructRNA *srna; @@ -4029,6 +4046,12 @@ static void rna_def_userdef_themes(BlenderRNA *brna) RNA_def_property_collection_sdna(prop, NULL, "collection_color", ""); RNA_def_property_struct_type(prop, "ThemeCollectionColor"); RNA_def_property_ui_text(prop, "Collection Color", ""); + + prop = RNA_def_property(srna, "strip_color", PROP_COLLECTION, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_collection_sdna(prop, NULL, "strip_color", ""); + RNA_def_property_struct_type(prop, "ThemeStripColor"); + RNA_def_property_ui_text(prop, "Strip Color", ""); } static void rna_def_userdef_addon(BlenderRNA *brna) @@ -4272,6 +4295,7 @@ static void rna_def_userdef_dothemes(BlenderRNA *brna) rna_def_userdef_theme_space_spreadsheet(brna); rna_def_userdef_theme_colorset(brna); rna_def_userdef_theme_collection_color(brna); + rna_def_userdef_theme_strip_color(brna); rna_def_userdef_themes(brna); } @@ -6285,6 +6309,13 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) "Enable library overrides automatic resync detection and process on file load. Disable when " "dealing with older .blend files that need manual Resync (Enforce) handling"); + prop = RNA_def_property(srna, "proxy_to_override_auto_conversion", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "no_proxy_to_override_conversion", 1); + RNA_def_property_ui_text( + prop, + "Proxy to Override Auto Conversion", + "Enable automatic conversion of proxies to library overrides on file load"); + prop = RNA_def_property(srna, "use_new_point_cloud_type", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "use_new_point_cloud_type", 1); RNA_def_property_ui_text( @@ -6328,9 +6359,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Override Templates", "Enable library override template in the python API"); - prop = RNA_def_property(srna, "use_geometry_nodes_fields", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_fields", 1); - RNA_def_property_ui_text(prop, "Geometry Nodes Fields", "Enable field nodes in geometry nodes"); + prop = RNA_def_property(srna, "use_geometry_nodes_legacy", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_legacy", 1); + RNA_def_property_ui_text( + prop, "Geometry Nodes Legacy", "Enable legacy geometry nodes in the menu"); } static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop) diff --git a/source/blender/makesrna/intern/rna_volume.c b/source/blender/makesrna/intern/rna_volume.c index 8ed53c9f70f..f6b8b55688c 100644 --- a/source/blender/makesrna/intern/rna_volume.c +++ b/source/blender/makesrna/intern/rna_volume.c @@ -41,6 +41,16 @@ # include "WM_api.h" # include "WM_types.h" +static char *rna_VolumeRender_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("render"); +} + +static char *rna_VolumeDisplay_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("display"); +} + /* Updates */ static void rna_Volume_update_display(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) @@ -371,6 +381,7 @@ static void rna_def_volume_display(BlenderRNA *brna) srna = RNA_def_struct(brna, "VolumeDisplay", NULL); RNA_def_struct_ui_text(srna, "Volume Display", "Volume object display settings for 3D viewport"); RNA_def_struct_sdna(srna, "VolumeDisplay"); + RNA_def_struct_path_func(srna, "rna_VolumeDisplay_path"); prop = RNA_def_property(srna, "density", PROP_FLOAT, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); @@ -477,6 +488,7 @@ static void rna_def_volume_render(BlenderRNA *brna) srna = RNA_def_struct(brna, "VolumeRender", NULL); RNA_def_struct_ui_text(srna, "Volume Render", "Volume object render settings"); RNA_def_struct_sdna(srna, "VolumeRender"); + RNA_def_struct_path_func(srna, "rna_VolumeRender_path"); static const EnumPropertyItem space_items[] = { {VOLUME_SPACE_OBJECT, diff --git a/source/blender/modifiers/intern/MOD_cloth.c b/source/blender/modifiers/intern/MOD_cloth.c index fa2f70e1a9c..cf0658d4b39 100644 --- a/source/blender/modifiers/intern/MOD_cloth.c +++ b/source/blender/modifiers/intern/MOD_cloth.c @@ -194,7 +194,7 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla } BKE_ptcache_free_list(&tclmd->ptcaches); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* Share the cache with the original object's modifier. */ tclmd->modifier.flag |= eModifierFlag_SharedCaches; tclmd->ptcaches = clmd->ptcaches; diff --git a/source/blender/modifiers/intern/MOD_datatransfer.c b/source/blender/modifiers/intern/MOD_datatransfer.c index e2b2cc58d48..34bb93cbbbc 100644 --- a/source/blender/modifiers/intern/MOD_datatransfer.c +++ b/source/blender/modifiers/intern/MOD_datatransfer.c @@ -163,7 +163,6 @@ static bool isDisabled(const struct Scene *UNUSED(scene), return !dtmd->ob_source || dtmd->ob_source->type != OB_MESH; } -#define HIGH_POLY_WARNING 10000 #define DT_TYPES_AFFECT_MESH \ (DT_TYPE_BWEIGHT_VERT | DT_TYPE_BWEIGHT_EDGE | DT_TYPE_CREASE | DT_TYPE_SHARP_EDGE | \ DT_TYPE_LNOR | DT_TYPE_SHARP_FACE) @@ -239,13 +238,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * BKE_modifier_set_error( ctx->object, (ModifierData *)dtmd, "Enable 'Auto Smooth' in Object Data Properties"); } - else if (result->totvert > HIGH_POLY_WARNING || - ((Mesh *)(ob_source->data))->totvert > HIGH_POLY_WARNING) { - BKE_modifier_set_error( - ctx->object, - md, - "Source or destination object has a high polygon count, computation might be slow"); - } return result; } @@ -473,7 +465,6 @@ static void panelRegister(ARegionType *region_type) region_type, "advanced", "Topology Mapping", NULL, advanced_panel_draw, panel_type); } -#undef HIGH_POLY_WARNING #undef DT_TYPES_AFFECT_MESH ModifierTypeInfo modifierType_DataTransfer = { diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 8c02c83d479..d294491d51c 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -88,6 +88,7 @@ #include "NOD_derived_node_tree.hh" #include "NOD_geometry.h" #include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_node_declaration.hh" #include "FN_field.hh" #include "FN_multi_function.hh" @@ -103,9 +104,13 @@ using blender::Span; using blender::StringRef; using blender::StringRefNull; using blender::Vector; +using blender::bke::OutputAttribute; +using blender::fn::GField; using blender::fn::GMutablePointer; using blender::fn::GPointer; +using blender::nodes::FieldInferencingInterface; using blender::nodes::GeoNodeExecParams; +using blender::nodes::InputSocketFieldType; using blender::threading::EnumerableThreadSpecific; using namespace blender::fn::multi_function_types; using namespace blender::nodes::derived_node_tree_types; @@ -306,6 +311,17 @@ static bool socket_type_has_attribute_toggle(const bNodeSocket &socket) return ELEM(socket.type, SOCK_FLOAT, SOCK_VECTOR, SOCK_BOOLEAN, SOCK_RGBA, SOCK_INT); } +/** + * \return Whether using an attribute to input values of this type is supported, and the node + * group's input for this socket accepts a field rather than just single values. + */ +static bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index) +{ + BLI_assert(node_tree.field_inferencing_interface != nullptr); + const FieldInferencingInterface &field_interface = *node_tree.field_inferencing_interface; + return field_interface.inputs[socket_index] != InputSocketFieldType::None; +} + static IDProperty *id_property_create_from_socket(const bNodeSocket &socket) { switch (socket.type) { @@ -531,7 +547,8 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings"); } - LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { + int socket_index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &nmd->node_group->inputs, socket_index) { IDProperty *new_prop = id_property_create_from_socket(*socket); if (new_prop == nullptr) { /* Out of the set of supported input sockets, only @@ -562,7 +579,7 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) } } - if (socket_type_has_attribute_toggle(*socket)) { + if (input_has_attribute_toggle(*nmd->node_group, socket_index)) { const std::string use_attribute_id = socket->identifier + use_attribute_suffix; const std::string attribute_name_id = socket->identifier + attribute_name_suffix; @@ -589,6 +606,35 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) } } + LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) { + if (!socket_type_has_attribute_toggle(*socket)) { + continue; + } + + const std::string idprop_name = socket->identifier + attribute_name_suffix; + IDProperty *new_prop = IDP_NewString("", idprop_name.c_str(), MAX_NAME); + if (socket->description[0] != '\0') { + IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop); + ui_data->description = BLI_strdup(socket->description); + } + IDP_AddToGroup(nmd->settings.properties, new_prop); + + if (old_properties != nullptr) { + IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, idprop_name.c_str()); + if (old_prop != nullptr) { + /* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only + * want to replace the values). So release it temporarily and replace it after. */ + IDPropertyUIData *ui_data = new_prop->ui_data; + new_prop->ui_data = nullptr; + IDP_CopyPropertyContent(new_prop, old_prop); + if (new_prop->ui_data != nullptr) { + IDP_ui_data_free(new_prop); + } + new_prop->ui_data = ui_data; + } + } + } + if (old_properties != nullptr) { IDP_FreeProperty(old_properties); } @@ -624,37 +670,39 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) } static void initialize_group_input(NodesModifierData &nmd, - const bNodeSocket &socket, + const OutputSocketRef &socket, void *r_value) { + const bNodeSocketType &socket_type = *socket.typeinfo(); + const bNodeSocket &bsocket = *socket.bsocket(); if (nmd.settings.properties == nullptr) { - socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); + socket_type.get_geometry_nodes_cpp_value(bsocket, r_value); return; } const IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties, - socket.identifier); + socket.identifier().c_str()); if (property == nullptr) { - socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); + socket_type.get_geometry_nodes_cpp_value(bsocket, r_value); return; } - if (!id_property_type_matches_socket(socket, *property)) { - socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); + if (!id_property_type_matches_socket(bsocket, *property)) { + socket_type.get_geometry_nodes_cpp_value(bsocket, r_value); return; } - if (!socket_type_has_attribute_toggle(socket)) { + if (!input_has_attribute_toggle(*nmd.node_group, socket.index())) { init_socket_cpp_value_from_property( - *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + *property, static_cast<eNodeSocketDatatype>(bsocket.type), r_value); return; } const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup( - nmd.settings.properties, (socket.identifier + use_attribute_suffix).c_str()); + nmd.settings.properties, (socket.identifier() + use_attribute_suffix).c_str()); const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup( - nmd.settings.properties, (socket.identifier + attribute_name_suffix).c_str()); + nmd.settings.properties, (socket.identifier() + attribute_name_suffix).c_str()); if (property_use_attribute == nullptr || property_attribute_name == nullptr) { init_socket_cpp_value_from_property( - *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + *property, static_cast<eNodeSocketDatatype>(bsocket.type), r_value); return; } @@ -662,12 +710,12 @@ static void initialize_group_input(NodesModifierData &nmd, if (use_attribute) { const StringRef attribute_name{IDP_String(property_attribute_name)}; auto attribute_input = std::make_shared<blender::bke::AttributeFieldInput>( - attribute_name, *socket.typeinfo->get_base_cpp_type()); + attribute_name, *socket_type.get_base_cpp_type()); new (r_value) blender::fn::GField(std::move(attribute_input), 0); } else { init_socket_cpp_value_from_property( - *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + *property, static_cast<eNodeSocketDatatype>(bsocket.type), r_value); } } @@ -778,14 +826,78 @@ static void clear_runtime_data(NodesModifierData *nmd) } } +static void store_field_on_geometry_component(GeometryComponent &component, + const StringRef attribute_name, + AttributeDomain domain, + const GField &field) +{ + /* If the attribute name corresponds to a built-in attribute, use the domain of the built-in + * attribute instead. */ + if (component.attribute_is_builtin(attribute_name)) { + component.attribute_try_create_builtin(attribute_name, AttributeInitDefault()); + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_name); + if (meta_data.has_value()) { + domain = meta_data->domain; + } + else { + return; + } + } + const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(field.cpp_type()); + OutputAttribute attribute = component.attribute_try_get_for_output_only( + attribute_name, domain, data_type); + if (attribute) { + /* In the future we could also evaluate all output fields at once. */ + const int domain_size = component.attribute_domain_size(domain); + blender::bke::GeometryComponentFieldContext field_context{component, domain}; + blender::fn::FieldEvaluator field_evaluator{field_context, domain_size}; + field_evaluator.add_with_destination(field, attribute.varray()); + field_evaluator.evaluate(); + attribute.save(); + } +} + +static void store_output_value_in_geometry(GeometrySet &geometry_set, + NodesModifierData *nmd, + const InputSocketRef &socket, + const GPointer value) +{ + if (!socket_type_has_attribute_toggle(*socket.bsocket())) { + return; + } + const std::string prop_name = socket.identifier() + attribute_name_suffix; + const IDProperty *prop = IDP_GetPropertyFromGroup(nmd->settings.properties, prop_name.c_str()); + if (prop == nullptr) { + return; + } + const StringRefNull attribute_name = IDP_String(prop); + if (attribute_name.is_empty()) { + return; + } + const GField &field = *(const GField *)value.get(); + const bNodeSocket *interface_socket = (bNodeSocket *)BLI_findlink(&nmd->node_group->outputs, + socket.index()); + const AttributeDomain domain = (AttributeDomain)interface_socket->attribute_domain; + if (geometry_set.has_mesh()) { + MeshComponent &component = geometry_set.get_component_for_write<MeshComponent>(); + store_field_on_geometry_component(component, attribute_name, domain, field); + } + if (geometry_set.has_pointcloud()) { + PointCloudComponent &component = geometry_set.get_component_for_write<PointCloudComponent>(); + store_field_on_geometry_component(component, attribute_name, domain, field); + } + if (geometry_set.has_curve()) { + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + store_field_on_geometry_component(component, attribute_name, domain, field); + } +} + /** * Evaluate a node group to compute the output geometry. - * Currently, this uses a fairly basic and inefficient algorithm that might compute things more - * often than necessary. It's going to be replaced soon. */ static GeometrySet compute_geometry(const DerivedNodeTree &tree, Span<const NodeRef *> group_input_nodes, - const InputSocketRef &socket_to_compute, + const NodeRef &output_node, GeometrySet input_geometry_set, NodesModifierData *nmd, const ModifierEvalContext *ctx) @@ -819,7 +931,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, for (const OutputSocketRef *socket : remaining_input_sockets) { const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type(); void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); - initialize_group_input(*nmd, *socket->bsocket(), value_in); + initialize_group_input(*nmd, *socket, value_in); group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); } } @@ -828,7 +940,9 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, input_geometry_set.clear(); Vector<DInputSocket> group_outputs; - group_outputs.append({root_context, &socket_to_compute}); + for (const InputSocketRef *socket_ref : output_node.inputs().drop_back(1)) { + group_outputs.append({root_context, socket_ref}); + } std::optional<geo_log::GeoLogger> geo_logger; @@ -856,9 +970,15 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, nmd_orig->runtime_eval_log = new geo_log::ModifierLog(*geo_logger); } - BLI_assert(eval_params.r_output_values.size() == 1); - GMutablePointer result = eval_params.r_output_values[0]; - return result.relocate_out<GeometrySet>(); + GeometrySet output_geometry_set = eval_params.r_output_values[0].relocate_out<GeometrySet>(); + + for (const InputSocketRef *socket : output_node.inputs().drop_front(1).drop_back(1)) { + GMutablePointer socket_value = eval_params.r_output_values[socket->index()]; + store_output_value_in_geometry(output_geometry_set, nmd, *socket, socket_value); + socket_value.destruct(); + } + + return output_geometry_set; } /** @@ -928,24 +1048,23 @@ static void modifyGeometry(ModifierData *md, const NodeTreeRef &root_tree_ref = tree.root_context().tree(); Span<const NodeRef *> input_nodes = root_tree_ref.nodes_by_type("NodeGroupInput"); Span<const NodeRef *> output_nodes = root_tree_ref.nodes_by_type("NodeGroupOutput"); - if (output_nodes.size() != 1) { return; } - Span<const InputSocketRef *> group_outputs = output_nodes[0]->inputs().drop_back(1); - - if (group_outputs.size() == 0) { + const NodeRef &output_node = *output_nodes[0]; + Span<const InputSocketRef *> group_outputs = output_node.inputs().drop_back(1); + if (group_outputs.is_empty()) { return; } - const InputSocketRef *group_output = group_outputs[0]; - if (group_output->idname() != "NodeSocketGeometry") { + const InputSocketRef *first_output_socket = group_outputs[0]; + if (first_output_socket->idname() != "NodeSocketGeometry") { return; } geometry_set = compute_geometry( - tree, input_nodes, *group_outputs[0], std::move(geometry_set), nmd, ctx); + tree, input_nodes, output_node, std::move(geometry_set), nmd, ctx); } static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) @@ -981,7 +1100,8 @@ static void draw_property_for_socket(uiLayout *layout, NodesModifierData *nmd, PointerRNA *bmain_ptr, PointerRNA *md_ptr, - const bNodeSocket &socket) + const bNodeSocket &socket, + const int socket_index) { /* The property should be created in #MOD_nodes_update_interface with the correct type. */ IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, socket.identifier); @@ -1026,8 +1146,7 @@ static void draw_property_for_socket(uiLayout *layout, break; } default: { - if (socket_type_has_attribute_toggle(socket) && - USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) { + if (input_has_attribute_toggle(*nmd->node_group, socket_index)) { const std::string rna_path_use_attribute = "[\"" + std::string(socket_id_esc) + use_attribute_suffix + "\"]"; const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) + @@ -1060,6 +1179,18 @@ static void draw_property_for_socket(uiLayout *layout, } } +static void draw_property_for_output_socket(uiLayout *layout, + PointerRNA *md_ptr, + const bNodeSocket &socket) +{ + char socket_id_esc[sizeof(socket.identifier) * 2]; + BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); + const std::string rna_path_attribute_name = "[\"" + StringRef(socket_id_esc) + + attribute_name_suffix + "\"]"; + + uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE); +} + static void panel_draw(const bContext *C, Panel *panel) { uiLayout *layout = panel->layout; @@ -1086,8 +1217,9 @@ static void panel_draw(const bContext *C, Panel *panel) PointerRNA bmain_ptr; RNA_main_pointer_create(bmain, &bmain_ptr); - LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { - draw_property_for_socket(layout, nmd, &bmain_ptr, ptr, *socket); + int socket_index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &nmd->node_group->inputs, socket_index) { + draw_property_for_socket(layout, nmd, &bmain_ptr, ptr, *socket, socket_index); } } @@ -1107,7 +1239,7 @@ static void panel_draw(const bContext *C, Panel *panel) }); } - if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields) && has_legacy_node) { + if (has_legacy_node) { uiLayout *row = uiLayoutRow(layout, false); uiItemL(row, IFACE_("Node tree has legacy node"), ICON_ERROR); uiLayout *sub = uiLayoutRow(row, false); @@ -1118,9 +1250,34 @@ static void panel_draw(const bContext *C, Panel *panel) modifier_panel_end(layout, ptr); } +static void output_attribute_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr); + NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, true); + + if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) { + LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) { + if (socket_type_has_attribute_toggle(*socket)) { + draw_property_for_output_socket(layout, ptr, *socket); + } + } + } +} + static void panelRegister(ARegionType *region_type) { - modifier_panel_register(region_type, eModifierType_Nodes, panel_draw); + PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Nodes, panel_draw); + modifier_subpanel_register(region_type, + "output_attributes", + N_("Output Attributes"), + nullptr, + output_attribute_panel_draw, + panel_type); } static void blendWrite(BlendWriter *writer, const ModifierData *md) diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index e50c07ce6f2..c5213dc304b 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -328,13 +328,24 @@ static void get_socket_value(const SocketRef &socket, void *r_value) * more complex defaults (other than just single values) in their socket declarations. */ if (bsocket.flag & SOCK_HIDE_VALUE) { const bNode &bnode = *socket.bnode(); - if (bsocket.type == SOCK_VECTOR && - ELEM(bnode.type, GEO_NODE_SET_POSITION, SH_NODE_TEX_NOISE)) { - new (r_value) Field<float3>( - std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>())); - return; + if (bsocket.type == SOCK_VECTOR) { + if (ELEM(bnode.type, + GEO_NODE_SET_POSITION, + SH_NODE_TEX_NOISE, + GEO_NODE_MESH_TO_POINTS, + GEO_NODE_PROXIMITY)) { + new (r_value) Field<float3>(bke::AttributeFieldInput::Create<float3>("position")); + return; + } + } + else if (bsocket.type == SOCK_INT) { + if (ELEM(bnode.type, FN_NODE_RANDOM_VALUE, GEO_NODE_INSTANCE_ON_POINTS)) { + new (r_value) Field<int>(std::make_shared<fn::IndexFieldInput>()); + return; + } } } + const bNodeSocketType *typeinfo = socket.typeinfo(); typeinfo->get_geometry_nodes_cpp_value(*socket.bsocket(), r_value); } @@ -870,11 +881,9 @@ class GeometryNodesEvaluator { NodeParamsProvider params_provider{*this, node, node_state}; GeoNodeExecParams params{params_provider}; - if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) { - if (node->idname().find("Legacy") != StringRef::not_found) { - params.error_message_add(geo_log::NodeWarningType::Legacy, - TIP_("Legacy node will be removed before Blender 4.0")); - } + if (node->idname().find("Legacy") != StringRef::not_found) { + params.error_message_add(geo_log::NodeWarningType::Legacy, + TIP_("Legacy node will be removed before Blender 4.0")); } bnode.typeinfo->geometry_node_execute(params); } @@ -883,6 +892,14 @@ class GeometryNodesEvaluator { const MultiFunction &fn, NodeState &node_state) { + if (node->idname().find("Legacy") != StringRef::not_found) { + /* Create geometry nodes params just for creating an error message. */ + NodeParamsProvider params_provider{*this, node, node_state}; + GeoNodeExecParams params{params_provider}; + params.error_message_add(geo_log::NodeWarningType::Legacy, + TIP_("Legacy node will be removed before Blender 4.0")); + } + LinearAllocator<> &allocator = local_allocators_.local(); /* Prepare the inputs for the multi function. */ diff --git a/source/blender/modifiers/intern/MOD_solidify_extrude.c b/source/blender/modifiers/intern/MOD_solidify_extrude.c index 8f9aa86e561..54a508ff5e2 100644 --- a/source/blender/modifiers/intern/MOD_solidify_extrude.c +++ b/source/blender/modifiers/intern/MOD_solidify_extrude.c @@ -1043,8 +1043,8 @@ Mesh *MOD_solidify_extrude_modifyMesh(ModifierData *md, const ModifierEvalContex #define SOLIDIFY_SIDE_NORMALS #ifdef SOLIDIFY_SIDE_NORMALS - /* Note that, due to the code setting cd_dirty_vert a few lines above, - * do_side_normals is always false. - Sybren */ + /* NOTE(@sybren): due to the code setting cd_dirty_vert a few lines above, + * do_side_normals is always false. */ const bool do_side_normals = !(result->runtime.cd_dirty_vert & CD_MASK_NORMAL); /* annoying to allocate these since we only need the edge verts, */ float(*edge_vert_nos)[3] = do_side_normals ? diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index e6af3ecafbc..903a30dd383 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -45,137 +45,159 @@ set(INC set(SRC - composite/nodes/node_composite_alphaOver.c - composite/nodes/node_composite_antialiasing.c - composite/nodes/node_composite_bilateralblur.c - composite/nodes/node_composite_blur.c - composite/nodes/node_composite_bokehblur.c - composite/nodes/node_composite_bokehimage.c - composite/nodes/node_composite_boxmask.c - composite/nodes/node_composite_brightness.c - composite/nodes/node_composite_channelMatte.c - composite/nodes/node_composite_chromaMatte.c - composite/nodes/node_composite_colorMatte.c - composite/nodes/node_composite_colorSpill.c - composite/nodes/node_composite_colorbalance.c - composite/nodes/node_composite_colorcorrection.c - composite/nodes/node_composite_common.c - composite/nodes/node_composite_composite.c - composite/nodes/node_composite_cornerpin.c - composite/nodes/node_composite_crop.c + composite/nodes/node_composite_alphaOver.cc + composite/nodes/node_composite_antialiasing.cc + composite/nodes/node_composite_bilateralblur.cc + composite/nodes/node_composite_blur.cc + composite/nodes/node_composite_bokehblur.cc + composite/nodes/node_composite_bokehimage.cc + composite/nodes/node_composite_boxmask.cc + composite/nodes/node_composite_brightness.cc + composite/nodes/node_composite_channelMatte.cc + composite/nodes/node_composite_chromaMatte.cc + composite/nodes/node_composite_colorMatte.cc + composite/nodes/node_composite_colorSpill.cc + composite/nodes/node_composite_colorbalance.cc + composite/nodes/node_composite_colorcorrection.cc + composite/nodes/node_composite_common.cc + composite/nodes/node_composite_composite.cc + composite/nodes/node_composite_cornerpin.cc + composite/nodes/node_composite_crop.cc composite/nodes/node_composite_cryptomatte.cc - composite/nodes/node_composite_curves.c - composite/nodes/node_composite_defocus.c - composite/nodes/node_composite_denoise.c - composite/nodes/node_composite_despeckle.c - composite/nodes/node_composite_diffMatte.c - composite/nodes/node_composite_dilate.c - composite/nodes/node_composite_directionalblur.c - composite/nodes/node_composite_displace.c - composite/nodes/node_composite_distanceMatte.c - composite/nodes/node_composite_doubleEdgeMask.c - composite/nodes/node_composite_ellipsemask.c - composite/nodes/node_composite_exposure.c - composite/nodes/node_composite_filter.c - composite/nodes/node_composite_flip.c - composite/nodes/node_composite_gamma.c - composite/nodes/node_composite_glare.c - composite/nodes/node_composite_hueSatVal.c - composite/nodes/node_composite_huecorrect.c - composite/nodes/node_composite_idMask.c - composite/nodes/node_composite_image.c - composite/nodes/node_composite_inpaint.c - composite/nodes/node_composite_invert.c - composite/nodes/node_composite_keying.c - composite/nodes/node_composite_keyingscreen.c - composite/nodes/node_composite_lensdist.c - composite/nodes/node_composite_levels.c - composite/nodes/node_composite_lummaMatte.c - composite/nodes/node_composite_mapRange.c - composite/nodes/node_composite_mapUV.c - composite/nodes/node_composite_mapValue.c - composite/nodes/node_composite_mask.c - composite/nodes/node_composite_math.c - composite/nodes/node_composite_mixrgb.c - composite/nodes/node_composite_movieclip.c - composite/nodes/node_composite_moviedistortion.c - composite/nodes/node_composite_normal.c - composite/nodes/node_composite_normalize.c - composite/nodes/node_composite_outputFile.c - composite/nodes/node_composite_pixelate.c - composite/nodes/node_composite_planetrackdeform.c - composite/nodes/node_composite_posterize.c - composite/nodes/node_composite_premulkey.c - composite/nodes/node_composite_rgb.c - composite/nodes/node_composite_rotate.c - composite/nodes/node_composite_scale.c - composite/nodes/node_composite_sepcombHSVA.c - composite/nodes/node_composite_sepcombRGBA.c - composite/nodes/node_composite_sepcombYCCA.c - composite/nodes/node_composite_sepcombYUVA.c - composite/nodes/node_composite_setalpha.c - composite/nodes/node_composite_splitViewer.c - composite/nodes/node_composite_stabilize2d.c - composite/nodes/node_composite_sunbeams.c - composite/nodes/node_composite_switch.c - composite/nodes/node_composite_switchview.c - composite/nodes/node_composite_texture.c - composite/nodes/node_composite_tonemap.c - composite/nodes/node_composite_trackpos.c - composite/nodes/node_composite_transform.c - composite/nodes/node_composite_translate.c - composite/nodes/node_composite_valToRgb.c - composite/nodes/node_composite_value.c - composite/nodes/node_composite_vecBlur.c - composite/nodes/node_composite_viewer.c - composite/nodes/node_composite_zcombine.c + composite/nodes/node_composite_curves.cc + composite/nodes/node_composite_defocus.cc + composite/nodes/node_composite_denoise.cc + composite/nodes/node_composite_despeckle.cc + composite/nodes/node_composite_diffMatte.cc + composite/nodes/node_composite_dilate.cc + composite/nodes/node_composite_directionalblur.cc + composite/nodes/node_composite_displace.cc + composite/nodes/node_composite_distanceMatte.cc + composite/nodes/node_composite_doubleEdgeMask.cc + composite/nodes/node_composite_ellipsemask.cc + composite/nodes/node_composite_exposure.cc + composite/nodes/node_composite_filter.cc + composite/nodes/node_composite_flip.cc + composite/nodes/node_composite_gamma.cc + composite/nodes/node_composite_glare.cc + composite/nodes/node_composite_hueSatVal.cc + composite/nodes/node_composite_huecorrect.cc + composite/nodes/node_composite_idMask.cc + composite/nodes/node_composite_image.cc + composite/nodes/node_composite_inpaint.cc + composite/nodes/node_composite_invert.cc + composite/nodes/node_composite_keying.cc + composite/nodes/node_composite_keyingscreen.cc + composite/nodes/node_composite_lensdist.cc + composite/nodes/node_composite_levels.cc + composite/nodes/node_composite_lummaMatte.cc + composite/nodes/node_composite_mapRange.cc + composite/nodes/node_composite_mapUV.cc + composite/nodes/node_composite_mapValue.cc + composite/nodes/node_composite_mask.cc + composite/nodes/node_composite_math.cc + composite/nodes/node_composite_mixrgb.cc + composite/nodes/node_composite_movieclip.cc + composite/nodes/node_composite_moviedistortion.cc + composite/nodes/node_composite_normal.cc + composite/nodes/node_composite_normalize.cc + composite/nodes/node_composite_outputFile.cc + composite/nodes/node_composite_pixelate.cc + composite/nodes/node_composite_planetrackdeform.cc + composite/nodes/node_composite_posterize.cc + composite/nodes/node_composite_premulkey.cc + composite/nodes/node_composite_rgb.cc + composite/nodes/node_composite_rotate.cc + composite/nodes/node_composite_scale.cc + composite/nodes/node_composite_sepcombHSVA.cc + composite/nodes/node_composite_sepcombRGBA.cc + composite/nodes/node_composite_sepcombYCCA.cc + composite/nodes/node_composite_sepcombYUVA.cc + composite/nodes/node_composite_setalpha.cc + composite/nodes/node_composite_splitViewer.cc + composite/nodes/node_composite_stabilize2d.cc + composite/nodes/node_composite_sunbeams.cc + composite/nodes/node_composite_switch.cc + composite/nodes/node_composite_switchview.cc + composite/nodes/node_composite_texture.cc + composite/nodes/node_composite_tonemap.cc + composite/nodes/node_composite_trackpos.cc + composite/nodes/node_composite_transform.cc + composite/nodes/node_composite_translate.cc + composite/nodes/node_composite_valToRgb.cc + composite/nodes/node_composite_value.cc + composite/nodes/node_composite_vecBlur.cc + composite/nodes/node_composite_viewer.cc + composite/nodes/node_composite_zcombine.cc - composite/node_composite_tree.c - composite/node_composite_util.c + composite/node_composite_tree.cc + composite/node_composite_util.cc + + function/nodes/legacy/node_fn_random_float.cc function/nodes/node_fn_boolean_math.cc function/nodes/node_fn_float_compare.cc function/nodes/node_fn_float_to_int.cc + function/nodes/node_fn_input_special_characters.cc function/nodes/node_fn_input_string.cc function/nodes/node_fn_input_vector.cc - function/nodes/node_fn_random_float.cc + function/nodes/node_fn_random_value.cc + function/nodes/node_fn_rotate_euler.cc function/nodes/node_fn_string_length.cc function/nodes/node_fn_string_substring.cc function/nodes/node_fn_value_to_string.cc function/node_function_util.cc + geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc + geometry/nodes/legacy/node_geo_attribute_clamp.cc + geometry/nodes/legacy/node_geo_attribute_color_ramp.cc + geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc + geometry/nodes/legacy/node_geo_attribute_compare.cc + geometry/nodes/legacy/node_geo_attribute_convert.cc + geometry/nodes/legacy/node_geo_attribute_curve_map.cc + geometry/nodes/legacy/node_geo_attribute_fill.cc + geometry/nodes/legacy/node_geo_attribute_map_range.cc + geometry/nodes/legacy/node_geo_attribute_math.cc + geometry/nodes/legacy/node_geo_attribute_mix.cc + geometry/nodes/legacy/node_geo_attribute_proximity.cc + geometry/nodes/legacy/node_geo_attribute_randomize.cc + geometry/nodes/legacy/node_geo_attribute_sample_texture.cc + geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc + geometry/nodes/legacy/node_geo_attribute_transfer.cc + geometry/nodes/legacy/node_geo_attribute_vector_math.cc + geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc + geometry/nodes/legacy/node_geo_curve_endpoints.cc + geometry/nodes/legacy/node_geo_curve_reverse.cc + geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc + geometry/nodes/legacy/node_geo_curve_set_handles.cc + geometry/nodes/legacy/node_geo_curve_spline_type.cc + geometry/nodes/legacy/node_geo_curve_subdivide.cc + geometry/nodes/legacy/node_geo_curve_to_points.cc + geometry/nodes/legacy/node_geo_delete_geometry.cc + geometry/nodes/legacy/node_geo_edge_split.cc geometry/nodes/legacy/node_geo_material_assign.cc + geometry/nodes/legacy/node_geo_mesh_to_curve.cc + geometry/nodes/legacy/node_geo_point_distribute.cc + geometry/nodes/legacy/node_geo_point_instance.cc + geometry/nodes/legacy/node_geo_point_rotate.cc + geometry/nodes/legacy/node_geo_point_scale.cc + geometry/nodes/legacy/node_geo_point_separate.cc + geometry/nodes/legacy/node_geo_point_translate.cc + geometry/nodes/legacy/node_geo_points_to_volume.cc + geometry/nodes/legacy/node_geo_raycast.cc geometry/nodes/legacy/node_geo_select_by_material.cc + geometry/nodes/legacy/node_geo_subdivision_surface.cc - geometry/nodes/node_geo_align_rotation_to_vector.cc geometry/nodes/node_geo_attribute_capture.cc - geometry/nodes/node_geo_attribute_clamp.cc - geometry/nodes/node_geo_attribute_color_ramp.cc - geometry/nodes/node_geo_attribute_combine_xyz.cc - geometry/nodes/node_geo_attribute_compare.cc - geometry/nodes/node_geo_attribute_convert.cc - geometry/nodes/node_geo_attribute_curve_map.cc - geometry/nodes/node_geo_attribute_fill.cc - geometry/nodes/node_geo_attribute_map_range.cc - geometry/nodes/node_geo_attribute_math.cc - geometry/nodes/node_geo_attribute_mix.cc - geometry/nodes/node_geo_attribute_proximity.cc - geometry/nodes/node_geo_attribute_randomize.cc geometry/nodes/node_geo_attribute_remove.cc - geometry/nodes/node_geo_attribute_sample_texture.cc - geometry/nodes/node_geo_attribute_separate_xyz.cc geometry/nodes/node_geo_attribute_statistic.cc - geometry/nodes/node_geo_attribute_transfer.cc - geometry/nodes/node_geo_attribute_vector_math.cc - geometry/nodes/node_geo_attribute_vector_rotate.cc geometry/nodes/node_geo_boolean.cc geometry/nodes/node_geo_bounding_box.cc geometry/nodes/node_geo_collection_info.cc geometry/nodes/node_geo_common.cc geometry/nodes/node_geo_convex_hull.cc - geometry/nodes/node_geo_curve_sample.cc - geometry/nodes/node_geo_curve_endpoints.cc geometry/nodes/node_geo_curve_fill.cc + geometry/nodes/node_geo_curve_fillet.cc geometry/nodes/node_geo_curve_length.cc geometry/nodes/node_geo_curve_parameter.cc geometry/nodes/node_geo_curve_primitive_bezier_segment.cc @@ -187,21 +209,20 @@ set(SRC geometry/nodes/node_geo_curve_primitive_star.cc geometry/nodes/node_geo_curve_resample.cc geometry/nodes/node_geo_curve_reverse.cc - geometry/nodes/node_geo_curve_select_by_handle_type.cc + geometry/nodes/node_geo_curve_sample.cc geometry/nodes/node_geo_curve_set_handles.cc geometry/nodes/node_geo_curve_spline_type.cc geometry/nodes/node_geo_curve_subdivide.cc - geometry/nodes/node_geo_curve_fillet.cc geometry/nodes/node_geo_curve_to_mesh.cc - geometry/nodes/node_geo_curve_to_points.cc geometry/nodes/node_geo_curve_trim.cc - geometry/nodes/node_geo_delete_geometry.cc - geometry/nodes/node_geo_edge_split.cc + geometry/nodes/node_geo_distribute_points_on_faces.cc + geometry/nodes/node_geo_input_index.cc geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_input_normal.cc geometry/nodes/node_geo_input_position.cc + geometry/nodes/node_geo_input_spline_length.cc geometry/nodes/node_geo_input_tangent.cc - geometry/nodes/node_geo_input_index.cc + geometry/nodes/node_geo_instance_on_points.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc geometry/nodes/node_geo_material_assign.cc @@ -216,26 +237,21 @@ set(SRC geometry/nodes/node_geo_mesh_primitive_line.cc geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc geometry/nodes/node_geo_mesh_subdivide.cc - geometry/nodes/node_geo_mesh_to_curve.cc + geometry/nodes/node_geo_mesh_to_points.cc geometry/nodes/node_geo_object_info.cc - geometry/nodes/node_geo_point_distribute.cc - geometry/nodes/node_geo_point_instance.cc - geometry/nodes/node_geo_point_rotate.cc - geometry/nodes/node_geo_point_scale.cc - geometry/nodes/node_geo_point_separate.cc - geometry/nodes/node_geo_point_translate.cc - geometry/nodes/node_geo_points_to_volume.cc - geometry/nodes/node_geo_raycast.cc + geometry/nodes/node_geo_points_to_vertices.cc + geometry/nodes/node_geo_proximity.cc geometry/nodes/node_geo_realize_instances.cc geometry/nodes/node_geo_separate_components.cc geometry/nodes/node_geo_set_position.cc geometry/nodes/node_geo_string_join.cc - geometry/nodes/node_geo_subdivision_surface.cc + geometry/nodes/node_geo_string_to_curves.cc geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc geometry/nodes/node_geo_triangulate.cc geometry/nodes/node_geo_viewer.cc geometry/nodes/node_geo_volume_to_mesh.cc + geometry/node_geometry_exec.cc geometry/node_geometry_tree.cc geometry/node_geometry_util.cc @@ -362,18 +378,18 @@ set(SRC intern/derived_node_tree.cc intern/geometry_nodes_eval_log.cc intern/math_functions.cc - intern/node_common.c + intern/node_common.cc + intern/node_declaration.cc intern/node_exec.cc intern/node_geometry_exec.cc intern/node_multi_function.cc - intern/node_declaration.cc - intern/node_socket_declarations.cc intern/node_socket.cc + intern/node_socket_declarations.cc intern/node_tree_ref.cc intern/node_util.c intern/type_conversions.cc - composite/node_composite_util.h + composite/node_composite_util.hh function/node_function_util.hh shader/node_shader_util.h geometry/node_geometry_util.hh @@ -389,10 +405,10 @@ set(SRC NOD_math_functions.hh NOD_multi_function.hh NOD_node_declaration.hh - NOD_socket_declarations.hh NOD_node_tree_ref.hh NOD_shader.h NOD_socket.h + NOD_socket_declarations.hh NOD_static_types.h NOD_texture.h NOD_type_conversions.hh diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h index 2cbbd31c97a..d243577f68d 100644 --- a/source/blender/nodes/NOD_composite.h +++ b/source/blender/nodes/NOD_composite.h @@ -145,7 +145,7 @@ void node_cmp_rlayers_register_pass(struct bNodeTree *ntree, struct Scene *scene, struct ViewLayer *view_layer, const char *name, - int type); + eNodeSocketDatatype type); const char *node_cmp_rlayers_sock_to_pass(int sock_index); void register_node_type_cmp_custom_group(bNodeType *ntype); diff --git a/source/blender/nodes/NOD_function.h b/source/blender/nodes/NOD_function.h index a67458418f2..450e999bea4 100644 --- a/source/blender/nodes/NOD_function.h +++ b/source/blender/nodes/NOD_function.h @@ -20,12 +20,16 @@ extern "C" { #endif +void register_node_type_fn_legacy_random_float(void); + void register_node_type_fn_boolean_math(void); void register_node_type_fn_float_compare(void); void register_node_type_fn_float_to_int(void); +void register_node_type_fn_input_special_characters(void); void register_node_type_fn_input_string(void); void register_node_type_fn_input_vector(void); -void register_node_type_fn_random_float(void); +void register_node_type_fn_random_value(void); +void register_node_type_fn_rotate_euler(void); void register_node_type_fn_string_length(void); void register_node_type_fn_string_substring(void); void register_node_type_fn_value_to_string(void); diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 47bc54132eb..bb90c7b6c51 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -29,10 +29,17 @@ void register_node_tree_type_geo(void); void register_node_type_geo_group(void); void register_node_type_geo_custom_group(bNodeType *ntype); +void register_node_type_geo_legacy_curve_set_handles(void); +void register_node_type_geo_legacy_attribute_proximity(void); +void register_node_type_geo_legacy_attribute_randomize(void); void register_node_type_geo_legacy_material_assign(void); void register_node_type_geo_legacy_select_by_material(void); +void register_node_type_geo_legacy_curve_spline_type(void); +void register_node_type_geo_legacy_curve_reverse(void); +void register_node_type_geo_legacy_curve_subdivide(void); void register_node_type_geo_align_rotation_to_vector(void); +void register_node_type_geo_attribute_capture(void); void register_node_type_geo_attribute_clamp(void); void register_node_type_geo_attribute_color_ramp(void); void register_node_type_geo_attribute_combine_xyz(void); @@ -40,12 +47,9 @@ void register_node_type_geo_attribute_compare(void); void register_node_type_geo_attribute_convert(void); void register_node_type_geo_attribute_curve_map(void); void register_node_type_geo_attribute_fill(void); -void register_node_type_geo_attribute_capture(void); void register_node_type_geo_attribute_map_range(void); void register_node_type_geo_attribute_math(void); void register_node_type_geo_attribute_mix(void); -void register_node_type_geo_attribute_proximity(void); -void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_attribute_separate_xyz(void); void register_node_type_geo_attribute_statistic(void); @@ -58,9 +62,9 @@ void register_node_type_geo_collection_info(void); void register_node_type_geo_convex_hull(void); void register_node_type_geo_curve_endpoints(void); void register_node_type_geo_curve_fill(void); +void register_node_type_geo_curve_fillet(void); void register_node_type_geo_curve_length(void); void register_node_type_geo_curve_parameter(void); -void register_node_type_geo_curve_sample(void); void register_node_type_geo_curve_primitive_bezier_segment(void); void register_node_type_geo_curve_primitive_circle(void); void register_node_type_geo_curve_primitive_line(void); @@ -70,20 +74,23 @@ void register_node_type_geo_curve_primitive_spiral(void); void register_node_type_geo_curve_primitive_star(void); void register_node_type_geo_curve_resample(void); void register_node_type_geo_curve_reverse(void); +void register_node_type_geo_curve_sample(void); void register_node_type_geo_curve_set_handles(void); void register_node_type_geo_curve_spline_type(void); void register_node_type_geo_curve_subdivide(void); -void register_node_type_geo_curve_fillet(void); void register_node_type_geo_curve_to_mesh(void); void register_node_type_geo_curve_to_points(void); void register_node_type_geo_curve_trim(void); void register_node_type_geo_delete_geometry(void); +void register_node_type_geo_distribute_points_on_faces(void); void register_node_type_geo_edge_split(void); void register_node_type_geo_input_index(void); void register_node_type_geo_input_material(void); void register_node_type_geo_input_normal(void); void register_node_type_geo_input_position(void); +void register_node_type_geo_input_spline_length(void); void register_node_type_geo_input_tangent(void); +void register_node_type_geo_instance_on_points(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); void register_node_type_geo_material_assign(void); @@ -99,6 +106,7 @@ void register_node_type_geo_mesh_primitive_line(void); void register_node_type_geo_mesh_primitive_uv_sphere(void); void register_node_type_geo_mesh_subdivide(void); void register_node_type_geo_mesh_to_curve(void); +void register_node_type_geo_mesh_to_points(void); void register_node_type_geo_object_info(void); void register_node_type_geo_point_distribute(void); void register_node_type_geo_point_instance(void); @@ -106,7 +114,9 @@ void register_node_type_geo_point_rotate(void); void register_node_type_geo_point_scale(void); void register_node_type_geo_point_separate(void); void register_node_type_geo_point_translate(void); +void register_node_type_geo_points_to_vertices(void); void register_node_type_geo_points_to_volume(void); +void register_node_type_geo_proximity(void); void register_node_type_geo_raycast(void); void register_node_type_geo_realize_instances(void); void register_node_type_geo_sample_texture(void); @@ -114,6 +124,7 @@ void register_node_type_geo_select_by_handle_type(void); void register_node_type_geo_separate_components(void); void register_node_type_geo_set_position(void); void register_node_type_geo_string_join(void); +void register_node_type_geo_string_to_curves(void); void register_node_type_geo_subdivision_surface(void); void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 6ce3d0f2ab5..962e1c3c48f 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -46,6 +46,8 @@ using bke::WeakAnonymousAttributeID; using bke::WriteAttributeLookup; using fn::CPPType; using fn::Field; +using fn::FieldContext; +using fn::FieldEvaluator; using fn::FieldInput; using fn::FieldOperation; using fn::GField; diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index 8e9e72bf4c8..07d4e05cda8 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -27,6 +27,109 @@ namespace blender::nodes { class NodeDeclarationBuilder; +enum class InputSocketFieldType { + /** The input is required to be a single value. */ + None, + /** The input can be a field. */ + IsSupported, + /** The input can be a field and is a field implicitly if nothing is connected. */ + Implicit, +}; + +enum class OutputSocketFieldType { + /** The output is always a single value. */ + None, + /** The output is always a field, independent of the inputs. */ + FieldSource, + /** If any input is a field, this output will be a field as well. */ + DependentField, + /** If any of a subset of inputs is a field, this out will be a field as well. + * The subset is defined by the vector of indices. */ + PartiallyDependent, +}; + +/** + * Contains information about how a node output's field state depends on inputs of the same node. + */ +class OutputFieldDependency { + private: + OutputSocketFieldType type_ = OutputSocketFieldType::None; + Vector<int> linked_input_indices_; + + public: + static OutputFieldDependency ForFieldSource() + { + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::FieldSource; + return field_dependency; + } + + static OutputFieldDependency ForDataSource() + { + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::None; + return field_dependency; + } + + static OutputFieldDependency ForPartiallyDependentField(Vector<int> indices) + { + OutputFieldDependency field_dependency; + if (indices.is_empty()) { + field_dependency.type_ = OutputSocketFieldType::None; + } + else { + field_dependency.type_ = OutputSocketFieldType::PartiallyDependent; + field_dependency.linked_input_indices_ = std::move(indices); + } + return field_dependency; + } + + static OutputFieldDependency ForDependentField() + { + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::DependentField; + return field_dependency; + } + + OutputSocketFieldType field_type() const + { + return type_; + } + + Span<int> linked_input_indices() const + { + return linked_input_indices_; + } + + friend bool operator==(const OutputFieldDependency &a, const OutputFieldDependency &b) + { + return a.type_ == b.type_ && a.linked_input_indices_ == b.linked_input_indices_; + } + + friend bool operator!=(const OutputFieldDependency &a, const OutputFieldDependency &b) + { + return !(a == b); + } +}; + +/** + * Information about how a node interacts with fields. + */ +struct FieldInferencingInterface { + Vector<InputSocketFieldType> inputs; + Vector<OutputFieldDependency> outputs; + + friend bool operator==(const FieldInferencingInterface &a, const FieldInferencingInterface &b) + { + return a.inputs == b.inputs && a.outputs == b.outputs; + } + + friend bool operator!=(const FieldInferencingInterface &a, const FieldInferencingInterface &b) + { + return !(a == b); + } +}; + /** * Describes a single input or output socket. This is subclassed for different socket types. */ @@ -34,11 +137,15 @@ class SocketDeclaration { protected: std::string name_; std::string identifier_; + std::string description_; bool hide_label_ = false; bool hide_value_ = false; bool is_multi_input_ = false; bool no_mute_links_ = false; + InputSocketFieldType input_field_type_ = InputSocketFieldType::None; + OutputFieldDependency output_field_dependency_; + friend NodeDeclarationBuilder; template<typename SocketDecl> friend class SocketDeclarationBuilder; @@ -50,8 +157,12 @@ class SocketDeclaration { virtual bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const; StringRefNull name() const; + StringRefNull description() const; StringRefNull identifier() const; + InputSocketFieldType input_field_type() const; + const OutputFieldDependency &output_field_dependency() const; + protected: void set_common_flags(bNodeSocket &socket) const; bool matches_common_data(const bNodeSocket &socket) const; @@ -95,11 +206,52 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder { return *(Self *)this; } + Self &description(std::string value = "") + { + decl_->description_ = std::move(value); + return *(Self *)this; + } Self &no_muted_links(bool value = true) { decl_->no_mute_links_ = value; return *(Self *)this; } + + /** The input socket allows passing in a field. */ + Self &supports_field() + { + decl_->input_field_type_ = InputSocketFieldType::IsSupported; + return *(Self *)this; + } + + /** The input supports a field and is a field by default when nothing is connected. */ + Self &implicit_field() + { + this->hide_value(); + decl_->input_field_type_ = InputSocketFieldType::Implicit; + return *(Self *)this; + } + + /** The output is always a field, regardless of any inputs. */ + Self &field_source() + { + decl_->output_field_dependency_ = OutputFieldDependency::ForFieldSource(); + return *(Self *)this; + } + + /** The output is a field if any of the inputs is a field. */ + Self &dependent_field() + { + decl_->output_field_dependency_ = OutputFieldDependency::ForDependentField(); + return *(Self *)this; + } + + /** The output is a field if any of the inputs with indices in the given list is a field. */ + Self &dependent_field(Vector<int> input_dependencies) + { + decl_->output_field_dependency_ = OutputFieldDependency::ForPartiallyDependentField( + std::move(input_dependencies)); + } }; using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>; @@ -108,6 +260,7 @@ class NodeDeclaration { private: Vector<SocketDeclarationPtr> inputs_; Vector<SocketDeclarationPtr> outputs_; + bool is_function_node_ = false; friend NodeDeclarationBuilder; @@ -118,6 +271,11 @@ class NodeDeclaration { Span<SocketDeclarationPtr> inputs() const; Span<SocketDeclarationPtr> outputs() const; + bool is_function_node() const + { + return is_function_node_; + } + MEM_CXX_CLASS_ALLOC_FUNCS("NodeDeclaration") }; @@ -129,6 +287,15 @@ class NodeDeclarationBuilder { public: NodeDeclarationBuilder(NodeDeclaration &declaration); + /** + * All inputs support fields, and all outputs are fields if any of the inputs is a field. + * Calling field status definitions on each socket is unnecessary. + */ + void is_function_node(bool value = true) + { + declaration_.is_function_node_ = value; + } + template<typename DeclType> typename DeclType::Builder &add_input(StringRef name, StringRef identifier = ""); template<typename DeclType> @@ -155,6 +322,20 @@ inline StringRefNull SocketDeclaration::identifier() const return identifier_; } +inline StringRefNull SocketDeclaration::description() const +{ + return description_; +} +inline InputSocketFieldType SocketDeclaration::input_field_type() const +{ + return input_field_type_; +} + +inline const OutputFieldDependency &SocketDeclaration::output_field_dependency() const +{ + return output_field_dependency_; +} + /* -------------------------------------------------------------------- * NodeDeclarationBuilder inline methods. */ diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh index 4f2565cbbaf..1da42fb6425 100644 --- a/source/blender/nodes/NOD_node_tree_ref.hh +++ b/source/blender/nodes/NOD_node_tree_ref.hh @@ -182,6 +182,7 @@ class NodeRef : NonCopyable, NonMovable { Span<const InputSocketRef *> inputs() const; Span<const OutputSocketRef *> outputs() const; Span<const InternalLinkRef *> internal_links() const; + Span<const SocketRef *> sockets(eNodeSocketInOut in_out) const; const InputSocketRef &input(int index) const; const OutputSocketRef &output(int index) const; @@ -189,6 +190,10 @@ class NodeRef : NonCopyable, NonMovable { const InputSocketRef &input_by_identifier(StringRef identifier) const; const OutputSocketRef &output_by_identifier(StringRef identifier) const; + bool any_input_is_directly_linked() const; + bool any_output_is_directly_linked() const; + bool any_socket_is_directly_linked(eNodeSocketInOut in_out) const; + bNode *bnode() const; bNodeTree *btree() const; @@ -196,6 +201,7 @@ class NodeRef : NonCopyable, NonMovable { StringRefNull idname() const; StringRefNull name() const; bNodeType *typeinfo() const; + const NodeDeclaration *declaration() const; int id() const; @@ -272,6 +278,13 @@ class NodeTreeRef : NonCopyable, NonMovable { bool has_link_cycles() const; bool has_undefined_nodes_or_sockets() const; + enum class ToposortDirection { + LeftToRight, + RightToLeft, + }; + + Vector<const NodeRef *> toposort(ToposortDirection direction) const; + bNodeTree *btree() const; StringRefNull name() const; @@ -496,6 +509,12 @@ inline Span<const OutputSocketRef *> NodeRef::outputs() const return outputs_; } +inline Span<const SocketRef *> NodeRef::sockets(const eNodeSocketInOut in_out) const +{ + return in_out == SOCK_IN ? inputs_.as_span().cast<const SocketRef *>() : + outputs_.as_span().cast<const SocketRef *>(); +} + inline Span<const InternalLinkRef *> NodeRef::internal_links() const { return internal_links_; @@ -553,6 +572,13 @@ inline bNodeType *NodeRef::typeinfo() const return bnode_->typeinfo; } +/* Returns a pointer because not all nodes have declarations currently. */ +inline const NodeDeclaration *NodeRef::declaration() const +{ + nodeDeclarationEnsure(this->tree().btree(), bnode_); + return bnode_->declaration; +} + inline int NodeRef::id() const { return id_; diff --git a/source/blender/nodes/NOD_shader.h b/source/blender/nodes/NOD_shader.h index 2911e0fbea6..76c174201e8 100644 --- a/source/blender/nodes/NOD_shader.h +++ b/source/blender/nodes/NOD_shader.h @@ -49,6 +49,7 @@ void register_node_type_sh_normal(void); void register_node_type_sh_gamma(void); void register_node_type_sh_brightcontrast(void); void register_node_type_sh_mapping(void); +void register_node_type_sh_curve_float(void); void register_node_type_sh_curve_vec(void); void register_node_type_sh_curve_rgb(void); void register_node_type_sh_map_range(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index ab673d814bb..c13ab199691 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -132,6 +132,7 @@ DefNode(ShaderNode, SH_NODE_VECTOR_DISPLACEMENT,def_sh_vector_displacement," DefNode(ShaderNode, SH_NODE_TEX_IES, def_sh_tex_ies, "TEX_IES", TexIES, "IES Texture", "" ) DefNode(ShaderNode, SH_NODE_TEX_WHITE_NOISE, def_sh_tex_white_noise, "TEX_WHITE_NOISE", TexWhiteNoise, "White Noise", "" ) DefNode(ShaderNode, SH_NODE_OUTPUT_AOV, def_sh_output_aov, "OUTPUT_AOV", OutputAOV, "AOV Output", "" ) +DefNode(ShaderNode, SH_NODE_CURVE_FLOAT, def_float_curve, "CURVE_FLOAT", FloatCurve, "Float Curve", "" ) DefNode(CompositorNode, CMP_NODE_VIEWER, def_cmp_viewer, "VIEWER", Viewer, "Viewer", "" ) DefNode(CompositorNode, CMP_NODE_RGB, 0, "RGB", RGB, "RGB", "" ) @@ -262,18 +263,22 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_NOISE, 0, "TEX_NO DefNode(TextureNode, TEX_NODE_PROC+TEX_STUCCI, 0, "TEX_STUCCI", TexStucci, "Stucci", "" ) DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DISTNOISE", TexDistNoise, "Distorted Noise", "" ) +DefNode(FunctionNode, FN_NODE_LEGACY_RANDOM_FLOAT, 0, "LEGACY_RANDOM_FLOAT", LegacyRandomFloat, "Random Float", "") + DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "") DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", FloatCompare, "Float Compare", "") DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "") +DefNode(FunctionNode, FN_NODE_INPUT_SPECIAL_CHARACTERS, 0, "INPUT_SPECIAL_CHARACTERS", InputSpecialCharacters, "Special Characters", "") DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "") DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") -DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") -DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "") +DefNode(FunctionNode, FN_NODE_RANDOM_VALUE, def_fn_random_value, "RANDOM_VALUE", RandomValue, "Random Value", "") +DefNode(FunctionNode, FN_NODE_ROTATE_EULER, def_fn_rotate_euler, "ROTATE_EULER", RotateEuler, "Rotate Euler", "") DefNode(FunctionNode, FN_NODE_STRING_LENGTH, 0, "STRING_LENGTH", StringLength, "String Length", "") DefNode(FunctionNode, FN_NODE_STRING_SUBSTRING, 0, "STRING_SUBSTRING", StringSubstring, "String Substring", "") +DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "") -DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "LEGACY_ATTRIBUTE_COLOR_RAMP", LegacyAttributeColorRamp, "Attribute Color Ramp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "LEGACY_ATTRIBUTE_COMBINE_XYZ", LegacyAttributeCombineXYZ, "Attribute Combine XYZ", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "LEGACY_ATTRIBUTE_COMPARE", LegacyAttributeCompare, "Attribute Compare", "") @@ -283,18 +288,22 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_FILL, def_geo_attribute_fill, "L DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "LEGACY_ATTRIBUTE_MAP_RANGE", LegacyAttributeMapRange, "Attribute Map Range", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MATH, def_geo_attribute_math, "LEGACY_ATTRIBUTE_MATH", LegacyAttributeMath, "Attribute Math", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MIX, def_geo_attribute_mix, "LEGACY_ATTRIBUTE_MIX", LegacyAttributeMix, "Attribute Mix", "") -DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "LEGACY_ATTRIBUTE_PROXIMITY", LegacyAttributeProximity, "Attribute Proximity", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, def_geo_legacy_attribute_proximity, "LEGACY_ATTRIBUTE_PROXIMITY", LegacyAttributeProximity, "Attribute Proximity", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "LEGACY_ATTRIBUTE_RANDOMIZE", LegacyAttributeRandomize, "Attribute Randomize", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, 0, "LEGACY_ATTRIBUTE_SAMPLE_TEXTURE", LegacyAttributeSampleTexture, "Attribute Sample Texture", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "LEGACY_ATTRIBUTE_SEPARATE_XYZ", LegacyAttributeSeparateXYZ, "Attribute Separate XYZ", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "LEGACY_ATTRIBUTE_TRANSFER", LegacyAttributeTransfer, "Attribute Transfer", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "LEGACY_ATTRIBUTE_VECTOR_MATH", LegacyAttributeVectorMath, "Attribute Vector Math", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_ENDPOINTS, 0, "LEGACY_CURVE_ENDPOINTS", LegacyCurveEndpoints, "Curve Endpoints", "") DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_REVERSE, 0, "LEGACY_CURVE_REVERSE", LegacyCurveReverse, "Curve Reverse", "") DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "LEGACY_CURVE_SELECT_HANDLES", LegacyCurveSelectHandles, "Select by Handle Type", "") -DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SET_HANDLES, def_geo_curve_set_handles, "LEGACY_CURVE_SET_HANDLES", LegacyCurveSetHandles, "Set Handle Type", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SET_HANDLES, def_geo_legacy_curve_set_handles, "LEGACY_CURVE_SET_HANDLES", LegacyCurveSetHandles, "Set Handle Type", "") DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "LEGACY_CURVE_SPLINE_TYPE", LegacyCurveSplineType, "Set Spline Type", "") -DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "LEGACY_CURVE_SUBDIVIDE", LegacyCurveSubdivide, "Curve Subdivide", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, def_geo_legacy_curve_subdivide, "LEGACY_CURVE_SUBDIVIDE", LegacyCurveSubdivide, "Curve Subdivide", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_TO_POINTS, def_geo_curve_to_points, "LEGACY_CURVE_TO_POINTS", LegacyCurveToPoints, "Curve to Points", "") DefNode(GeometryNode, GEO_NODE_LEGACY_DELETE_GEOMETRY, 0, "LEGACY_DELETE_GEOMETRY", LegacyDeleteGeometry, "Delete Geometry", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_EDGE_SPLIT, 0, "LEGACY_EDGE_SPLIT", LegacyEdgeSplit, "Edge Split", "") DefNode(GeometryNode, GEO_NODE_LEGACY_MATERIAL_ASSIGN, 0, "LEGACY_MATERIAL_ASSIGN", LegacyMaterialAssign, "Material Assign", "") DefNode(GeometryNode, GEO_NODE_LEGACY_MESH_TO_CURVE, 0, "LEGACY_MESH_TO_CURVE", LegacyMeshToCurve, "Mesh to Curve", "") DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_DISTRIBUTE, def_geo_point_distribute, "LEGACY_POINT_DISTRIBUTE", LegacyPointDistribute, "Point Distribute", "") @@ -306,18 +315,17 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_TRANSLATE, def_geo_point_translate, DefNode(GeometryNode, GEO_NODE_LEGACY_POINTS_TO_VOLUME, def_geo_points_to_volume, "LEGACY_POINTS_TO_VOLUME", LegacyPointsToVolume, "Points to Volume", "") DefNode(GeometryNode, GEO_NODE_LEGACY_RAYCAST, def_geo_raycast, "LEGACY_RAYCAST", LegacyRaycast, "Raycast", "") DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_MATERIAL", LegacySelectByMaterial, "Select by Material", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "LEGACY_SUBDIVISION_SURFACE", LegacySubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "") DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE, def_geo_curve_sample, "CURVE_SAMPLE", CurveSample, "Curve Sample", "") -DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINTS, 0, "CURVE_ENDPOINTS", CurveEndpoints, "Curve Endpoints", "") DefNode(GeometryNode, GEO_NODE_CURVE_FILL, def_geo_curve_fill, "CURVE_FILL", CurveFill, "Curve Fill", "") +DefNode(GeometryNode, GEO_NODE_CURVE_FILLET, def_geo_curve_fillet, "CURVE_FILLET", CurveFillet, "Curve Fillet", "") DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "") DefNode(GeometryNode, GEO_NODE_CURVE_PARAMETER, 0, "CURVE_PARAMETER", CurveParameter, "Curve Parameter", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "") @@ -328,16 +336,21 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, def_geo_curve_prim DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRAL", CurveSpiral, "Curve Spiral", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "") DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "") -DefNode(GeometryNode, GEO_NODE_CURVE_FILLET, def_geo_curve_fillet, "CURVE_FILLET", CurveFillet, "Curve Fillet", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") +DefNode(GeometryNode, GEO_NODE_CURVE_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE, def_geo_curve_sample, "CURVE_SAMPLE", CurveSample, "Curve Sample", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, 0, "CURVE_SUBDIVIDE", CurveSubdivide, "Curve Subdivide", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") -DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "") -DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "") DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "") DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "") DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "") +DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_LENGTH, 0, "SPLINE_LENGTH", SplineLength, "Spline Length", "") DefNode(GeometryNode, GEO_NODE_INPUT_TANGENT, 0, "INPUT_TANGENT", InputTangent, "Curve Tangent", "") +DefNode(GeometryNode, GEO_NODE_INSTANCE_ON_POINTS, 0, "INSTANCE_ON_POINTS", InstanceOnPoints, "Instance on Points", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") @@ -352,12 +365,15 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Mesh Line", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") DefNode(GeometryNode, GEO_NODE_MESH_SUBDIVIDE, 0, "MESH_SUBDIVIDE", MeshSubdivide, "Mesh Subdivide", "") +DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "") DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "") +DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proximity, "Geometry Proximity", "") DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "String Join", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") +DefNode(GeometryNode, GEO_NODE_STRING_TO_CURVES, def_geo_string_to_curves, "STRING_TO_CURVES", StringToCurves, "String to Curves", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") diff --git a/source/blender/nodes/composite/node_composite_tree.c b/source/blender/nodes/composite/node_composite_tree.cc index cc657d6f91d..a596a85b748 100644 --- a/source/blender/nodes/composite/node_composite_tree.c +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -21,7 +21,7 @@ * \ingroup nodes */ -#include <stdio.h> +#include <cstdio> #include "DNA_color_types.h" #include "DNA_node_types.h" @@ -41,7 +41,7 @@ #include "RNA_access.h" #include "NOD_composite.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" #ifdef WITH_COMPOSITOR # include "COM_compositor.h" @@ -55,7 +55,7 @@ static void composite_get_from_context(const bContext *C, { Scene *scene = CTX_data_scene(C); - *r_from = NULL; + *r_from = nullptr; *r_id = &scene->id; *r_ntree = scene->nodetree; } @@ -77,19 +77,16 @@ static void foreach_nodeclass(Scene *UNUSED(scene), void *calldata, bNodeClassCa static void free_node_cache(bNodeTree *UNUSED(ntree), bNode *node) { - bNodeSocket *sock; - - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (sock->cache) { - sock->cache = NULL; + sock->cache = nullptr; } } } static void free_cache(bNodeTree *ntree) { - bNode *node; - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { free_node_cache(ntree, node); } } @@ -98,16 +95,16 @@ static void free_cache(bNodeTree *ntree) static void localize(bNodeTree *localtree, bNodeTree *ntree) { - bNode *node = ntree->nodes.first; - bNode *local_node = localtree->nodes.first; - while (node != NULL) { + bNode *node = (bNode *)ntree->nodes.first; + bNode *local_node = (bNode *)localtree->nodes.first; + while (node != nullptr) { /* Ensure new user input gets handled ok. */ node->need_exec = 0; local_node->original = node; /* move over the compbufs */ - /* right after ntreeCopyTree() oldsock pointers are valid */ + /* right after #ntreeCopyTree() `oldsock` pointers are valid */ if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) { if (node->id) { @@ -115,16 +112,16 @@ static void localize(bNodeTree *localtree, bNodeTree *ntree) local_node->id = (ID *)node->id; } else { - local_node->id = NULL; + local_node->id = nullptr; } } } - bNodeSocket *output_sock = node->outputs.first; - bNodeSocket *local_output_sock = local_node->outputs.first; - while (output_sock != NULL) { + bNodeSocket *output_sock = (bNodeSocket *)node->outputs.first; + bNodeSocket *local_output_sock = (bNodeSocket *)local_node->outputs.first; + while (output_sock != nullptr) { local_output_sock->cache = output_sock->cache; - output_sock->cache = NULL; + output_sock->cache = nullptr; /* This is actually link to original: someone was just lazy enough and tried to save few * bytes in the cost of readability. */ local_output_sock->new_sock = output_sock; @@ -151,7 +148,7 @@ static void local_merge(Main *bmain, bNodeTree *localtree, bNodeTree *ntree) /* move over the compbufs and previews */ BKE_node_preview_merge_tree(ntree, localtree, true); - for (lnode = localtree->nodes.first; lnode; lnode = lnode->next) { + for (lnode = (bNode *)localtree->nodes.first; lnode; lnode = lnode->next) { if (ntreeNodeExists(ntree, lnode->new_node)) { if (ELEM(lnode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) { if (lnode->id && (lnode->flag & NODE_DO_OUTPUT)) { @@ -165,18 +162,19 @@ static void local_merge(Main *bmain, bNodeTree *localtree, bNodeTree *ntree) * copied back to original node */ if (lnode->storage) { if (lnode->new_node->storage) { - BKE_tracking_distortion_free(lnode->new_node->storage); + BKE_tracking_distortion_free((MovieDistortion *)lnode->new_node->storage); } - lnode->new_node->storage = BKE_tracking_distortion_copy(lnode->storage); + lnode->new_node->storage = BKE_tracking_distortion_copy( + (MovieDistortion *)lnode->storage); } } - for (lsock = lnode->outputs.first; lsock; lsock = lsock->next) { + for (lsock = (bNodeSocket *)lnode->outputs.first; lsock; lsock = lsock->next) { if (ntreeOutputExists(lnode->new_node, lsock->new_sock)) { lsock->new_sock->cache = lsock->cache; - lsock->cache = NULL; - lsock->new_sock = NULL; + lsock->cache = nullptr; + lsock->new_sock = nullptr; } } } @@ -216,13 +214,13 @@ bNodeTreeType *ntreeType_Composite; void register_node_tree_type_cmp(void) { - bNodeTreeType *tt = ntreeType_Composite = MEM_callocN(sizeof(bNodeTreeType), - "compositor node tree type"); + bNodeTreeType *tt = ntreeType_Composite = (bNodeTreeType *)MEM_callocN( + sizeof(bNodeTreeType), "compositor node tree type"); tt->type = NTREE_COMPOSIT; strcpy(tt->idname, "CompositorNodeTree"); strcpy(tt->ui_name, N_("Compositor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Compositing nodes")); tt->free_cache = free_cache; @@ -241,9 +239,6 @@ void register_node_tree_type_cmp(void) ntreeTypeAdd(tt); } -extern void *COM_linker_hack; /* Quiet warning. */ -void *COM_linker_hack = NULL; - void ntreeCompositExecTree(Scene *scene, bNodeTree *ntree, RenderData *rd, @@ -276,13 +271,11 @@ void ntreeCompositExecTree(Scene *scene, */ void ntreeCompositUpdateRLayers(bNodeTree *ntree) { - bNode *node; - - if (ntree == NULL) { + if (ntree == nullptr) { return; } - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->type == CMP_NODE_R_LAYERS) { node_cmp_rlayers_outputs(ntree, node); } @@ -295,13 +288,11 @@ void ntreeCompositRegisterPass(bNodeTree *ntree, const char *name, eNodeSocketDatatype type) { - bNode *node; - - if (ntree == NULL) { + if (ntree == nullptr) { return; } - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->type == CMP_NODE_R_LAYERS) { node_cmp_rlayers_register_pass(ntree, node, scene, view_layer, name, type); } @@ -317,15 +308,14 @@ void ntreeCompositTagRender(Scene *scene) * This is still rather weak though, * ideally render struct would store own main AND original G_MAIN. */ - for (Scene *sce_iter = G_MAIN->scenes.first; sce_iter; sce_iter = sce_iter->id.next) { + for (Scene *sce_iter = (Scene *)G_MAIN->scenes.first; sce_iter; + sce_iter = (Scene *)sce_iter->id.next) { if (sce_iter->nodetree) { - bNode *node; - - for (node = sce_iter->nodetree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &sce_iter->nodetree->nodes) { if (node->id == (ID *)scene || node->type == CMP_NODE_COMPOSITE) { nodeUpdate(sce_iter->nodetree, node); } - else if (node->type == CMP_NODE_TEXTURE) /* uses scene sizex/sizey */ { + else if (node->type == CMP_NODE_TEXTURE) /* uses scene size_x/size_y */ { nodeUpdate(sce_iter->nodetree, node); } } @@ -336,13 +326,11 @@ void ntreeCompositTagRender(Scene *scene) /* XXX after render animation system gets a refresh, this call allows composite to end clean */ void ntreeCompositClearTags(bNodeTree *ntree) { - bNode *node; - - if (ntree == NULL) { + if (ntree == nullptr) { return; } - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { node->need_exec = 0; if (node->type == NODE_GROUP) { ntreeCompositClearTags((bNodeTree *)node->id); diff --git a/source/blender/nodes/composite/node_composite_util.c b/source/blender/nodes/composite/node_composite_util.cc index 6cc17d8c272..86aaec61bc3 100644 --- a/source/blender/nodes/composite/node_composite_util.c +++ b/source/blender/nodes/composite/node_composite_util.cc @@ -21,7 +21,7 @@ * \ingroup nodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" bool cmp_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree, @@ -36,11 +36,10 @@ bool cmp_node_poll_default(bNodeType *UNUSED(ntype), void cmp_node_update_default(bNodeTree *UNUSED(ntree), bNode *node) { - bNodeSocket *sock; - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (sock->cache) { // free_compbuf(sock->cache); - // sock->cache = NULL; + // sock->cache = nullptr; } } node->need_exec = 1; diff --git a/source/blender/nodes/composite/node_composite_util.h b/source/blender/nodes/composite/node_composite_util.hh index 4fcccbb79f0..6fd82ffc93f 100644 --- a/source/blender/nodes/composite/node_composite_util.h +++ b/source/blender/nodes/composite/node_composite_util.hh @@ -47,20 +47,13 @@ /* only for forward declarations */ #include "NOD_composite.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include "NOD_socket_declarations.hh" #define CMP_SCALE_MAX 12000 bool cmp_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree, - const char **r_disabled_info); + const char **r_disabled_hint); void cmp_node_update_default(struct bNodeTree *ntree, struct bNode *node); void cmp_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); - -#ifdef __cplusplus -} -#endif diff --git a/source/blender/nodes/composite/nodes/node_composite_alphaOver.c b/source/blender/nodes/composite/nodes/node_composite_alphaOver.cc index 7a08bd51575..6210d946bc7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_alphaOver.c +++ b/source/blender/nodes/composite/nodes/node_composite_alphaOver.cc @@ -21,19 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** ALPHAOVER ******************** */ -static bNodeSocketTemplate cmp_node_alphaover_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_alphaover_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_alphaover_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>("Image", "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_alphaover_init(bNodeTree *UNUSED(ntree), bNode *node) { @@ -45,7 +47,7 @@ void register_node_type_cmp_alphaover(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_ALPHAOVER, "Alpha Over", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_alphaover_in, cmp_node_alphaover_out); + ntype.declare = blender::nodes::cmp_node_alphaover_declare; node_type_init(&ntype, node_alphaover_init); node_type_storage( &ntype, "NodeTwoFloats", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_antialiasing.c b/source/blender/nodes/composite/nodes/node_composite_antialiasing.cc index a5906c31093..23e63b9a53b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_antialiasing.c +++ b/source/blender/nodes/composite/nodes/node_composite_antialiasing.cc @@ -23,7 +23,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Anti-Aliasing (SMAA 1x) ******************** */ @@ -34,7 +34,8 @@ static bNodeSocketTemplate cmp_node_antialiasing_out[] = {{SOCK_RGBA, N_("Image" static void node_composit_init_antialiasing(bNodeTree *UNUSED(ntree), bNode *node) { - NodeAntiAliasingData *data = MEM_callocN(sizeof(NodeAntiAliasingData), "node antialiasing data"); + NodeAntiAliasingData *data = (NodeAntiAliasingData *)MEM_callocN(sizeof(NodeAntiAliasingData), + "node antialiasing data"); data->threshold = CMP_DEFAULT_SMAA_THRESHOLD; data->contrast_limit = CMP_DEFAULT_SMAA_CONTRAST_LIMIT; diff --git a/source/blender/nodes/composite/nodes/node_composite_bilateralblur.c b/source/blender/nodes/composite/nodes/node_composite_bilateralblur.cc index 270a137280c..3e724d17a10 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bilateralblur.c +++ b/source/blender/nodes/composite/nodes/node_composite_bilateralblur.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** BILATERALBLUR ******************** */ static bNodeSocketTemplate cmp_node_bilateralblur_in[] = { @@ -36,8 +36,8 @@ static bNodeSocketTemplate cmp_node_bilateralblur_out[] = { static void node_composit_init_bilateralblur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBilateralBlurData *nbbd = MEM_callocN(sizeof(NodeBilateralBlurData), - "node bilateral blur data"); + NodeBilateralBlurData *nbbd = (NodeBilateralBlurData *)MEM_callocN(sizeof(NodeBilateralBlurData), + "node bilateral blur data"); node->storage = nbbd; nbbd->iter = 1; nbbd->sigma_color = 0.3; diff --git a/source/blender/nodes/composite/nodes/node_composite_blur.c b/source/blender/nodes/composite/nodes/node_composite_blur.cc index 92379f4552b..c5c0c21929e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_blur.c +++ b/source/blender/nodes/composite/nodes/node_composite_blur.cc @@ -22,7 +22,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** BLUR ******************** */ static bNodeSocketTemplate cmp_node_blur_in[] = { @@ -33,7 +33,7 @@ static bNodeSocketTemplate cmp_node_blur_out[] = {{SOCK_RGBA, N_("Image")}, {-1, static void node_composit_init_blur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBlurData *data = MEM_callocN(sizeof(NodeBlurData), "node blur data"); + NodeBlurData *data = (NodeBlurData *)MEM_callocN(sizeof(NodeBlurData), "node blur data"); data->filtertype = R_FILTER_GAUSS; node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehblur.c b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc index d724a83e5a2..f130a642e20 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehblur.c +++ b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc @@ -22,7 +22,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** BLUR ******************** */ static bNodeSocketTemplate cmp_node_bokehblur_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehimage.c b/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc index 744aba417be..3a4bf94d256 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehimage.c +++ b/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc @@ -21,18 +21,22 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** Bokeh image Tools ******************** */ -static bNodeSocketTemplate cmp_node_bokehimage_out[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_bokehimage_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_bokehimage(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBokehImage *data = MEM_callocN(sizeof(NodeBokehImage), "NodeBokehImage"); + NodeBokehImage *data = (NodeBokehImage *)MEM_callocN(sizeof(NodeBokehImage), "NodeBokehImage"); data->angle = 0.0f; data->flaps = 5; data->rounding = 0.0f; @@ -46,7 +50,7 @@ void register_node_type_cmp_bokehimage(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_BOKEHIMAGE, "Bokeh Image", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, cmp_node_bokehimage_out); + ntype.declare = blender::nodes::cmp_node_bokehimage_declare; node_type_init(&ntype, node_composit_init_bokehimage); node_type_storage( &ntype, "NodeBokehImage", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_boxmask.c b/source/blender/nodes/composite/nodes/node_composite_boxmask.cc index e646b9a9adf..cdf96065f97 100644 --- a/source/blender/nodes/composite/nodes/node_composite_boxmask.c +++ b/source/blender/nodes/composite/nodes/node_composite_boxmask.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** SCALAR MATH ******************** */ static bNodeSocketTemplate cmp_node_boxmask_in[] = { @@ -34,7 +34,7 @@ static bNodeSocketTemplate cmp_node_boxmask_out[] = { static void node_composit_init_boxmask(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBoxMask *data = MEM_callocN(sizeof(NodeBoxMask), "NodeBoxMask"); + NodeBoxMask *data = (NodeBoxMask *)MEM_callocN(sizeof(NodeBoxMask), "NodeBoxMask"); data->x = 0.5; data->y = 0.5; data->width = 0.2; diff --git a/source/blender/nodes/composite/nodes/node_composite_brightness.c b/source/blender/nodes/composite/nodes/node_composite_brightness.cc index 5beecb55665..ad4b09c69d0 100644 --- a/source/blender/nodes/composite/nodes/node_composite_brightness.c +++ b/source/blender/nodes/composite/nodes/node_composite_brightness.cc @@ -21,20 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -/* **************** Brigh and contrsast ******************** */ +/* **************** Bright and Contrast ******************** */ -static bNodeSocketTemplate cmp_node_brightcontrast_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Bright"), 0.0f, 0.0f, 0.0f, 0.0f, -100.0f, 100.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Contrast"), 0.0f, 0.0f, 0.0f, 0.0f, -100.0f, 100.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_brightcontrast_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_brightcontrast_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Bright").min(-100.0f).max(100.0f); + b.add_input<decl::Float>("Contrast").min(-100.0f).max(100.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_brightcontrast(bNodeTree *UNUSED(ntree), bNode *node) { @@ -46,7 +47,7 @@ void register_node_type_cmp_brightcontrast(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_BRIGHTCONTRAST, "Bright/Contrast", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_brightcontrast_in, cmp_node_brightcontrast_out); + ntype.declare = blender::nodes::cmp_node_brightcontrast_declare; node_type_init(&ntype, node_composit_init_brightcontrast); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_channelMatte.c b/source/blender/nodes/composite/nodes/node_composite_channelMatte.cc index 9912c10b368..e211bc45b17 100644 --- a/source/blender/nodes/composite/nodes/node_composite_channelMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_channelMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Channel Matte Node ********************************* */ static bNodeSocketTemplate cmp_node_channel_matte_in[] = { @@ -37,7 +37,7 @@ static bNodeSocketTemplate cmp_node_channel_matte_out[] = { static void node_composit_init_channel_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = 1.0f; c->t2 = 0.0f; diff --git a/source/blender/nodes/composite/nodes/node_composite_chromaMatte.c b/source/blender/nodes/composite/nodes/node_composite_chromaMatte.cc index 705566df35a..990778160df 100644 --- a/source/blender/nodes/composite/nodes/node_composite_chromaMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_chromaMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Chroma Key ********************************************************** */ static bNodeSocketTemplate cmp_node_chroma_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_chroma_out[] = { static void node_composit_init_chroma_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = DEG2RADF(30.0f); c->t2 = DEG2RADF(10.0f); diff --git a/source/blender/nodes/composite/nodes/node_composite_colorMatte.c b/source/blender/nodes/composite/nodes/node_composite_colorMatte.cc index f5cf7bcbf22..fc9a0075b14 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Color Key ********************************************************** */ static bNodeSocketTemplate cmp_node_color_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_color_out[] = { static void node_composit_init_color_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node color"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node color"); node->storage = c; c->t1 = 0.01f; c->t2 = 0.1f; diff --git a/source/blender/nodes/composite/nodes/node_composite_colorSpill.c b/source/blender/nodes/composite/nodes/node_composite_colorSpill.cc index 8ff4bcdced3..7bdc2e8289e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorSpill.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorSpill.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Color Spill Suppression ********************************* */ static bNodeSocketTemplate cmp_node_color_spill_in[] = { @@ -37,7 +37,7 @@ static bNodeSocketTemplate cmp_node_color_spill_out[] = { static void node_composit_init_color_spill(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorspill *ncs = MEM_callocN(sizeof(NodeColorspill), "node colorspill"); + NodeColorspill *ncs = (NodeColorspill *)MEM_callocN(sizeof(NodeColorspill), "node colorspill"); node->storage = ncs; node->custom1 = 2; /* green channel */ node->custom2 = 0; /* simple limit algorithm */ diff --git a/source/blender/nodes/composite/nodes/node_composite_colorbalance.c b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc index 0525229697a..440e37fe741 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorbalance.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc @@ -21,19 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Color Balance ********************************* */ -static bNodeSocketTemplate cmp_node_colorbalance_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_colorbalance_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_colorbalance_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes /* Sync functions update formula parameters for other modes, such that the result is comparable. * Note that the results are not exactly the same due to differences in color handling @@ -43,10 +44,9 @@ static bNodeSocketTemplate cmp_node_colorbalance_out[] = { void ntreeCompositColorBalanceSyncFromLGG(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorBalance *n = node->storage; - int c; + NodeColorBalance *n = (NodeColorBalance *)node->storage; - for (c = 0; c < 3; c++) { + for (int c = 0; c < 3; c++) { n->slope[c] = (2.0f - n->lift[c]) * n->gain[c]; n->offset[c] = (n->lift[c] - 1.0f) * n->gain[c]; n->power[c] = (n->gamma[c] != 0.0f) ? 1.0f / n->gamma[c] : 1000000.0f; @@ -55,10 +55,9 @@ void ntreeCompositColorBalanceSyncFromLGG(bNodeTree *UNUSED(ntree), bNode *node) void ntreeCompositColorBalanceSyncFromCDL(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorBalance *n = node->storage; - int c; + NodeColorBalance *n = (NodeColorBalance *)node->storage; - for (c = 0; c < 3; c++) { + for (int c = 0; c < 3; c++) { float d = n->slope[c] + n->offset[c]; n->lift[c] = (d != 0.0f ? n->slope[c] + 2.0f * n->offset[c] / d : 0.0f); n->gain[c] = d; @@ -68,7 +67,8 @@ void ntreeCompositColorBalanceSyncFromCDL(bNodeTree *UNUSED(ntree), bNode *node) static void node_composit_init_colorbalance(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorBalance *n = node->storage = MEM_callocN(sizeof(NodeColorBalance), "node colorbalance"); + NodeColorBalance *n = (NodeColorBalance *)MEM_callocN(sizeof(NodeColorBalance), + "node colorbalance"); n->lift[0] = n->lift[1] = n->lift[2] = 1.0f; n->gamma[0] = n->gamma[1] = n->gamma[2] = 1.0f; @@ -77,6 +77,7 @@ static void node_composit_init_colorbalance(bNodeTree *UNUSED(ntree), bNode *nod n->slope[0] = n->slope[1] = n->slope[2] = 1.0f; n->offset[0] = n->offset[1] = n->offset[2] = 0.0f; n->power[0] = n->power[1] = n->power[2] = 1.0f; + node->storage = n; } void register_node_type_cmp_colorbalance(void) @@ -84,7 +85,7 @@ void register_node_type_cmp_colorbalance(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COLORBALANCE, "Color Balance", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_colorbalance_in, cmp_node_colorbalance_out); + ntype.declare = blender::nodes::cmp_node_colorbalance_declare; node_type_size(&ntype, 400, 200, 400); node_type_init(&ntype, node_composit_init_colorbalance); node_type_storage( diff --git a/source/blender/nodes/composite/nodes/node_composite_colorcorrection.c b/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc index 45d39f8be8d..0682c66f1e8 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorcorrection.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc @@ -21,24 +21,25 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -/* ******************* Color Balance ********************************* */ -static bNodeSocketTemplate cmp_node_colorcorrection_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Mask"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; +/* ******************* Color Correction ********************************* */ -static bNodeSocketTemplate cmp_node_colorcorrection_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_colorcorrection_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Mask").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_colorcorrection(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorCorrection *n = node->storage = MEM_callocN(sizeof(NodeColorCorrection), - "node colorcorrection"); + NodeColorCorrection *n = (NodeColorCorrection *)MEM_callocN(sizeof(NodeColorCorrection), + "node colorcorrection"); n->startmidtones = 0.2f; n->endmidtones = 0.7f; n->master.contrast = 1.0f; @@ -62,6 +63,7 @@ static void node_composit_init_colorcorrection(bNodeTree *UNUSED(ntree), bNode * n->highlights.lift = 0.0f; n->highlights.saturation = 1.0f; node->custom1 = 7; // red + green + blue enabled + node->storage = n; } void register_node_type_cmp_colorcorrection(void) @@ -69,7 +71,7 @@ void register_node_type_cmp_colorcorrection(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COLORCORRECTION, "Color Correction", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_colorcorrection_in, cmp_node_colorcorrection_out); + ntype.declare = blender::nodes::cmp_node_colorcorrection_declare; node_type_size(&ntype, 400, 200, 600); node_type_init(&ntype, node_composit_init_colorcorrection); node_type_storage( diff --git a/source/blender/nodes/composite/nodes/node_composite_common.c b/source/blender/nodes/composite/nodes/node_composite_common.cc index 61abc80fe93..fecf6795ef7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_common.c +++ b/source/blender/nodes/composite/nodes/node_composite_common.cc @@ -26,7 +26,7 @@ #include "NOD_common.h" #include "node_common.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_node.h" @@ -46,10 +46,10 @@ void register_node_type_cmp_group(void) ntype.insert_link = node_insert_link_default; ntype.update_internal_links = node_update_internal_links_default; ntype.rna_ext.srna = RNA_struct_find("CompositorNodeGroup"); - BLI_assert(ntype.rna_ext.srna != NULL); + BLI_assert(ntype.rna_ext.srna != nullptr); RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype); - node_type_socket_templates(&ntype, NULL, NULL); + node_type_socket_templates(&ntype, nullptr, nullptr); node_type_size(&ntype, 140, 60, 400); node_type_label(&ntype, node_group_label); node_type_group_update(&ntype, node_group_update); @@ -60,13 +60,13 @@ void register_node_type_cmp_group(void) void register_node_type_cmp_custom_group(bNodeType *ntype) { /* These methods can be overridden but need a default implementation otherwise. */ - if (ntype->poll == NULL) { + if (ntype->poll == nullptr) { ntype->poll = cmp_node_poll_default; } - if (ntype->insert_link == NULL) { + if (ntype->insert_link == nullptr) { ntype->insert_link = node_insert_link_default; } - if (ntype->update_internal_links == NULL) { + if (ntype->update_internal_links == nullptr) { ntype->update_internal_links = node_update_internal_links_default; } } diff --git a/source/blender/nodes/composite/nodes/node_composite_composite.c b/source/blender/nodes/composite/nodes/node_composite_composite.cc index dee2ce6b3ec..170fecb251c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_composite.c +++ b/source/blender/nodes/composite/nodes/node_composite_composite.cc @@ -21,25 +21,30 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** COMPOSITE ******************** */ -static bNodeSocketTemplate cmp_node_composite_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_FLOAT, N_("Alpha"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Z"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_composite_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_input<decl::Float>("Alpha").default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(1.0f); +} + +} // namespace blender::nodes void register_node_type_cmp_composite(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMPOSITE, "Composite", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_composite_in, NULL); + ntype.declare = blender::nodes::cmp_node_composite_declare; /* Do not allow muting for this node. */ - node_type_internal_links(&ntype, NULL); + node_type_internal_links(&ntype, nullptr); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_cornerpin.c b/source/blender/nodes/composite/nodes/node_composite_cornerpin.cc index 135120c45aa..b5ca1fb015e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cornerpin.c +++ b/source/blender/nodes/composite/nodes/node_composite_cornerpin.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate inputs[] = { {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, diff --git a/source/blender/nodes/composite/nodes/node_composite_crop.c b/source/blender/nodes/composite/nodes/node_composite_crop.cc index 868df5367c4..f07dba8a74b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_crop.c +++ b/source/blender/nodes/composite/nodes/node_composite_crop.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Crop ******************** */ @@ -36,7 +36,7 @@ static bNodeSocketTemplate cmp_node_crop_out[] = { static void node_composit_init_crop(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTwoXYs *nxy = MEM_callocN(sizeof(NodeTwoXYs), "node xy data"); + NodeTwoXYs *nxy = (NodeTwoXYs *)MEM_callocN(sizeof(NodeTwoXYs), "node xy data"); node->storage = nxy; nxy->x1 = 0; nxy->x2 = 0; diff --git a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc index 51dd73a86af..6657267b016 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BLI_assert.h" #include "BLI_dynstr.h" diff --git a/source/blender/nodes/composite/nodes/node_composite_curves.c b/source/blender/nodes/composite/nodes/node_composite_curves.cc index 470540d3337..88d96e1ca4a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_curves.c +++ b/source/blender/nodes/composite/nodes/node_composite_curves.cc @@ -21,16 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** CURVE Time ******************** */ -/* custom1 = sfra, custom2 = efra */ -static bNodeSocketTemplate cmp_node_time_out[] = { - {SOCK_FLOAT, N_("Fac")}, - {-1, ""}, -}; +namespace blender::nodes { +static void cmp_node_time_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Fac"); +} + +} // namespace blender::nodes + +/* custom1 = start_frame, custom2 = end_frame */ static void node_composit_init_curves_time(bNodeTree *UNUSED(ntree), bNode *node) { node->custom1 = 1; @@ -43,7 +47,7 @@ void register_node_type_cmp_curve_time(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TIME, "Time", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_time_out); + ntype.declare = blender::nodes::cmp_node_time_declare; node_type_size(&ntype, 140, 100, 320); node_type_init(&ntype, node_composit_init_curves_time); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); @@ -81,18 +85,19 @@ void register_node_type_cmp_curve_vec(void) } /* **************** CURVE RGB ******************** */ -static bNodeSocketTemplate cmp_node_curve_rgb_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_RGBA, N_("Black Level"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_RGBA, N_("White Level"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_curve_rgb_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_rgbcurves_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(-1.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>("Black Level").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_input<decl::Color>("White Level").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_curve_rgb(bNodeTree *UNUSED(ntree), bNode *node) { @@ -104,7 +109,7 @@ void register_node_type_cmp_curve_rgb(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_curve_rgb_in, cmp_node_curve_rgb_out); + ntype.declare = blender::nodes::cmp_node_rgbcurves_declare; node_type_size(&ntype, 200, 140, 320); node_type_init(&ntype, node_composit_init_curve_rgb); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); diff --git a/source/blender/nodes/composite/nodes/node_composite_defocus.c b/source/blender/nodes/composite/nodes/node_composite_defocus.cc index 3803f450f49..1103aff4366 100644 --- a/source/blender/nodes/composite/nodes/node_composite_defocus.c +++ b/source/blender/nodes/composite/nodes/node_composite_defocus.cc @@ -21,11 +21,11 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -#include <limits.h> +#include <climits> -/* ************ qdn: Defocus node ****************** */ +/* ************ Defocus Node ****************** */ static bNodeSocketTemplate cmp_node_defocus_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {SOCK_FLOAT, N_("Z"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, @@ -38,8 +38,8 @@ static bNodeSocketTemplate cmp_node_defocus_out[] = { static void node_composit_init_defocus(bNodeTree *UNUSED(ntree), bNode *node) { - /* qdn: defocus node */ - NodeDefocus *nbd = MEM_callocN(sizeof(NodeDefocus), "node defocus data"); + /* defocus node */ + NodeDefocus *nbd = (NodeDefocus *)MEM_callocN(sizeof(NodeDefocus), "node defocus data"); nbd->bktype = 0; nbd->rotation = 0.0f; nbd->preview = 1; diff --git a/source/blender/nodes/composite/nodes/node_composite_denoise.c b/source/blender/nodes/composite/nodes/node_composite_denoise.cc index e2c7c7b995f..ec085794462 100644 --- a/source/blender/nodes/composite/nodes/node_composite_denoise.c +++ b/source/blender/nodes/composite/nodes/node_composite_denoise.cc @@ -23,7 +23,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_denoise_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f}, @@ -34,7 +34,7 @@ static bNodeSocketTemplate cmp_node_denoise_out[] = {{SOCK_RGBA, N_("Image")}, { static void node_composit_init_denonise(bNodeTree *UNUSED(ntree), bNode *node) { - NodeDenoise *ndg = MEM_callocN(sizeof(NodeDenoise), "node denoise data"); + NodeDenoise *ndg = (NodeDenoise *)MEM_callocN(sizeof(NodeDenoise), "node denoise data"); ndg->hdr = true; ndg->prefilter = CMP_NODE_DENOISE_PREFILTER_ACCURATE; node->storage = ndg; diff --git a/source/blender/nodes/composite/nodes/node_composite_despeckle.c b/source/blender/nodes/composite/nodes/node_composite_despeckle.cc index 18567ee2006..52d91dabeb1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_despeckle.c +++ b/source/blender/nodes/composite/nodes/node_composite_despeckle.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** FILTER ******************** */ static bNodeSocketTemplate cmp_node_despeckle_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_diffMatte.c b/source/blender/nodes/composite/nodes/node_composite_diffMatte.cc index 7871a9e8b04..1e1a48381b7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_diffMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_diffMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* channel Difference Matte ********************************* */ static bNodeSocketTemplate cmp_node_diff_matte_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_diff_matte_out[] = { static void node_composit_init_diff_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = 0.1f; c->t2 = 0.1f; diff --git a/source/blender/nodes/composite/nodes/node_composite_dilate.c b/source/blender/nodes/composite/nodes/node_composite_dilate.cc index 12f1f229258..57884a299da 100644 --- a/source/blender/nodes/composite/nodes/node_composite_dilate.c +++ b/source/blender/nodes/composite/nodes/node_composite_dilate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Dilate/Erode ******************** */ @@ -31,7 +31,8 @@ static bNodeSocketTemplate cmp_node_dilateerode_out[] = {{SOCK_FLOAT, N_("Mask") static void node_composit_init_dilateerode(bNodeTree *UNUSED(ntree), bNode *node) { - NodeDilateErode *data = MEM_callocN(sizeof(NodeDilateErode), "NodeDilateErode"); + NodeDilateErode *data = (NodeDilateErode *)MEM_callocN(sizeof(NodeDilateErode), + "NodeDilateErode"); data->falloff = PROP_SMOOTH; node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_directionalblur.c b/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc index 6dd60526edf..d9f82ba5009 100644 --- a/source/blender/nodes/composite/nodes/node_composite_directionalblur.c +++ b/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_dblur_in[] = {{SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}}; @@ -30,7 +30,7 @@ static bNodeSocketTemplate cmp_node_dblur_out[] = {{SOCK_RGBA, N_("Image")}, {-1 static void node_composit_init_dblur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeDBlurData *ndbd = MEM_callocN(sizeof(NodeDBlurData), "node dblur data"); + NodeDBlurData *ndbd = (NodeDBlurData *)MEM_callocN(sizeof(NodeDBlurData), "node dblur data"); node->storage = ndbd; ndbd->iter = 1; ndbd->center_x = 0.5; diff --git a/source/blender/nodes/composite/nodes/node_composite_displace.c b/source/blender/nodes/composite/nodes/node_composite_displace.cc index 819a4f29b3a..b1ed7f05794 100644 --- a/source/blender/nodes/composite/nodes/node_composite_displace.c +++ b/source/blender/nodes/composite/nodes/node_composite_displace.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Displace ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_distanceMatte.c b/source/blender/nodes/composite/nodes/node_composite_distanceMatte.cc index 3e659fe662b..3f8767ecd08 100644 --- a/source/blender/nodes/composite/nodes/node_composite_distanceMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_distanceMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* channel Distance Matte ********************************* */ static bNodeSocketTemplate cmp_node_distance_matte_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_distance_matte_out[] = { static void node_composit_init_distance_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->channel = 1; c->t1 = 0.1f; diff --git a/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.c b/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.cc index 6f68b187775..7c9a48efc2d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.c +++ b/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.cc @@ -20,7 +20,7 @@ /** \file * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Double Edge Mask ******************** */ static bNodeSocketTemplate cmp_node_doubleedgemask_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_ellipsemask.c b/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc index d5e1d519a1c..67196fb0d35 100644 --- a/source/blender/nodes/composite/nodes/node_composite_ellipsemask.c +++ b/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** SCALAR MATH ******************** */ static bNodeSocketTemplate cmp_node_ellipsemask_in[] = { @@ -34,7 +34,8 @@ static bNodeSocketTemplate cmp_node_ellipsemask_out[] = { static void node_composit_init_ellipsemask(bNodeTree *UNUSED(ntree), bNode *node) { - NodeEllipseMask *data = MEM_callocN(sizeof(NodeEllipseMask), "NodeEllipseMask"); + NodeEllipseMask *data = (NodeEllipseMask *)MEM_callocN(sizeof(NodeEllipseMask), + "NodeEllipseMask"); data->x = 0.5; data->y = 0.5; data->width = 0.2; diff --git a/source/blender/nodes/composite/nodes/node_composite_exposure.c b/source/blender/nodes/composite/nodes/node_composite_exposure.cc index bd27e4a3d76..fd959376afe 100644 --- a/source/blender/nodes/composite/nodes/node_composite_exposure.c +++ b/source/blender/nodes/composite/nodes/node_composite_exposure.cc @@ -21,26 +21,27 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Exposure ******************** */ -static bNodeSocketTemplate cmp_node_exposure_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Exposure"), 0.0f, 0.0f, 0.0f, 0.0f, -10.0f, 10.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_exposure_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_exposure_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Exposure").min(-10.0f).max(10.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_exposure(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_EXPOSURE, "Exposure", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_exposure_in, cmp_node_exposure_out); + ntype.declare = blender::nodes::cmp_node_exposure_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_filter.c b/source/blender/nodes/composite/nodes/node_composite_filter.cc index d0ad090ece4..f07619877f4 100644 --- a/source/blender/nodes/composite/nodes/node_composite_filter.c +++ b/source/blender/nodes/composite/nodes/node_composite_filter.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** FILTER ******************** */ static bNodeSocketTemplate cmp_node_filter_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_flip.c b/source/blender/nodes/composite/nodes/node_composite_flip.cc index 91a91bb5f5f..42aa3141f5c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_flip.c +++ b/source/blender/nodes/composite/nodes/node_composite_flip.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Flip ******************** */ static bNodeSocketTemplate cmp_node_flip_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_gamma.c b/source/blender/nodes/composite/nodes/node_composite_gamma.cc index ddcaf691fd2..a29a001688a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_gamma.c +++ b/source/blender/nodes/composite/nodes/node_composite_gamma.cc @@ -21,26 +21,28 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Gamma Tools ******************** */ -static bNodeSocketTemplate cmp_node_gamma_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Gamma"), 1.0f, 0.0f, 0.0f, 0.0f, 0.001f, 10.0f, PROP_UNSIGNED}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_gamma_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_gamma_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Gamma").default_value(1.0f).min(0.001f).max(10.0f).subtype( + PROP_UNSIGNED); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_gamma(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_GAMMA, "Gamma", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_gamma_in, cmp_node_gamma_out); + ntype.declare = blender::nodes::cmp_node_gamma_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_glare.c b/source/blender/nodes/composite/nodes/node_composite_glare.cc index a792fcc86cd..8a2fd1e1584 100644 --- a/source/blender/nodes/composite/nodes/node_composite_glare.c +++ b/source/blender/nodes/composite/nodes/node_composite_glare.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_glare_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, @@ -34,7 +34,7 @@ static bNodeSocketTemplate cmp_node_glare_out[] = { static void node_composit_init_glare(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGlare *ndg = MEM_callocN(sizeof(NodeGlare), "node glare data"); + NodeGlare *ndg = (NodeGlare *)MEM_callocN(sizeof(NodeGlare), "node glare data"); ndg->quality = 1; ndg->type = 2; ndg->iter = 3; diff --git a/source/blender/nodes/composite/nodes/node_composite_hueSatVal.c b/source/blender/nodes/composite/nodes/node_composite_hueSatVal.cc index 494b6136a6e..07746918a94 100644 --- a/source/blender/nodes/composite/nodes/node_composite_hueSatVal.c +++ b/source/blender/nodes/composite/nodes/node_composite_hueSatVal.cc @@ -21,28 +21,34 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Hue Saturation ******************** */ -static bNodeSocketTemplate cmp_node_hue_sat_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Hue"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Saturation"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Value"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_hue_sat_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_huesatval_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Hue").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Saturation") + .default_value(1.0f) + .min(0.0f) + .max(2.0f) + .subtype(PROP_FACTOR); + b.add_input<decl::Float>("Value").default_value(1.0f).min(0.0f).max(2.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_hue_sat(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_HUE_SAT, "Hue Saturation Value", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_hue_sat_in, cmp_node_hue_sat_out); + ntype.declare = blender::nodes::cmp_node_huesatval_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_huecorrect.c b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc index 6a5c918d9ae..39014896a7b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_huecorrect.c +++ b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc @@ -21,27 +21,28 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -static bNodeSocketTemplate cmp_node_huecorrect_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; +namespace blender::nodes { -static bNodeSocketTemplate cmp_node_huecorrect_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +static void cmp_node_huecorrect_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_huecorrect(bNodeTree *UNUSED(ntree), bNode *node) { - CurveMapping *cumapping = node->storage = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - int c; + node->storage = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + + CurveMapping *cumapping = (CurveMapping *)node->storage; cumapping->preset = CURVE_PRESET_MID9; - for (c = 0; c < 3; c++) { + for (int c = 0; c < 3; c++) { CurveMap *cuma = &cumapping->cm[c]; BKE_curvemap_reset(cuma, &cumapping->clipr, cumapping->preset, CURVEMAP_SLOPE_POSITIVE); } @@ -55,7 +56,7 @@ void register_node_type_cmp_huecorrect(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_HUECORRECT, "Hue Correct", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_huecorrect_in, cmp_node_huecorrect_out); + ntype.declare = blender::nodes::cmp_node_huecorrect_declare; node_type_size(&ntype, 320, 140, 500); node_type_init(&ntype, node_composit_init_huecorrect); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); diff --git a/source/blender/nodes/composite/nodes/node_composite_idMask.c b/source/blender/nodes/composite/nodes/node_composite_idMask.cc index 84563e7560b..de011dd6274 100644 --- a/source/blender/nodes/composite/nodes/node_composite_idMask.c +++ b/source/blender/nodes/composite/nodes/node_composite_idMask.cc @@ -21,25 +21,26 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** ID Mask ******************** */ -static bNodeSocketTemplate cmp_node_idmask_in[] = { - {SOCK_FLOAT, N_("ID value"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_idmask_out[] = { - {SOCK_FLOAT, N_("Alpha")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_idmask_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("ID value").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Float>("Alpha"); +} + +} // namespace blender::nodes void register_node_type_cmp_idmask(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_ID_MASK, "ID Mask", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_idmask_in, cmp_node_idmask_out); + ntype.declare = blender::nodes::cmp_node_idmask_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_image.c b/source/blender/nodes/composite/nodes/node_composite_image.cc index a56dfea9dbf..3cef3a7625f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.c +++ b/source/blender/nodes/composite/nodes/node_composite_image.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BLI_linklist.h" #include "BLI_utildefines.h" @@ -84,16 +84,17 @@ static void cmp_node_image_add_pass_output(bNodeTree *ntree, LinkNodePair *available_sockets, int *prev_index) { - bNodeSocket *sock = BLI_findstring(&node->outputs, name, offsetof(bNodeSocket, name)); + bNodeSocket *sock = (bNodeSocket *)BLI_findstring( + &node->outputs, name, offsetof(bNodeSocket, name)); /* Replace if types don't match. */ if (sock && sock->type != type) { nodeRemoveSocket(ntree, node, sock); - sock = NULL; + sock = nullptr; } /* Create socket if it doesn't exist yet. */ - if (sock == NULL) { + if (sock == nullptr) { if (rres_index >= 0) { sock = node_add_socket_from_template( ntree, node, &cmp_node_rlayers_out[rres_index], SOCK_OUT); @@ -102,18 +103,19 @@ static void cmp_node_image_add_pass_output(bNodeTree *ntree, sock = nodeAddStaticSocket(ntree, node, SOCK_OUT, type, PROP_NONE, name, name); } /* extra socket info */ - NodeImageLayer *sockdata = MEM_callocN(sizeof(NodeImageLayer), "node image layer"); + NodeImageLayer *sockdata = (NodeImageLayer *)MEM_callocN(sizeof(NodeImageLayer), + "node image layer"); sock->storage = sockdata; } - NodeImageLayer *sockdata = sock->storage; + NodeImageLayer *sockdata = (NodeImageLayer *)sock->storage; if (sockdata) { BLI_strncpy(sockdata->pass_name, passname, sizeof(sockdata->pass_name)); } /* Reorder sockets according to order that passes are added. */ const int after_index = (*prev_index)++; - bNodeSocket *after_sock = BLI_findlink(&node->outputs, after_index); + bNodeSocket *after_sock = (bNodeSocket *)BLI_findlink(&node->outputs, after_index); BLI_remlink(&node->outputs, sock); BLI_insertlinkafter(&node->outputs, after_sock, sock); @@ -128,8 +130,8 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, ImBuf *ibuf; int prev_index = -1; if (ima) { - ImageUser *iuser = node->storage; - ImageUser load_iuser = {NULL}; + ImageUser *iuser = (ImageUser *)node->storage; + ImageUser load_iuser = {nullptr}; int offset = BKE_image_sequence_guess_offset(ima); /* It is possible that image user in this node is not @@ -138,21 +140,19 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, * * So we manually construct image user to be sure first * image from sequence (that one which is set as filename - * for image datablock) is used for sockets detection - */ + * for image data-block) is used for sockets detection. */ load_iuser.ok = 1; load_iuser.framenr = offset; /* make sure ima->type is correct */ - ibuf = BKE_image_acquire_ibuf(ima, &load_iuser, NULL); + ibuf = BKE_image_acquire_ibuf(ima, &load_iuser, nullptr); if (ima->rr) { - RenderLayer *rl = BLI_findlink(&ima->rr->layers, iuser->layer); + RenderLayer *rl = (RenderLayer *)BLI_findlink(&ima->rr->layers, iuser->layer); if (rl) { - RenderPass *rpass; - for (rpass = rl->passes.first; rpass; rpass = rpass->next) { - int type; + LISTBASE_FOREACH (RenderPass *, rpass, &rl->passes) { + eNodeSocketDatatype type; if (rpass->channels == 1) { type = SOCK_FLOAT; } @@ -182,7 +182,7 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, &prev_index); } } - BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(ima, ibuf, nullptr); return; } } @@ -219,14 +219,14 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, available_sockets, &prev_index); } - BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(ima, ibuf, nullptr); } } -typedef struct RLayerUpdateData { +struct RLayerUpdateData { LinkNodePair *available_sockets; int prev_index; -} RLayerUpdateData; +}; void node_cmp_rlayers_register_pass(bNodeTree *ntree, bNode *node, @@ -235,13 +235,13 @@ void node_cmp_rlayers_register_pass(bNodeTree *ntree, const char *name, eNodeSocketDatatype type) { - RLayerUpdateData *data = node->storage; + RLayerUpdateData *data = (RLayerUpdateData *)node->storage; - if (scene == NULL || view_layer == NULL || data == NULL || node->id != (ID *)scene) { + if (scene == nullptr || view_layer == nullptr || data == nullptr || node->id != (ID *)scene) { return; } - ViewLayer *node_view_layer = BLI_findlink(&scene->view_layers, node->custom1); + ViewLayer *node_view_layer = (ViewLayer *)BLI_findlink(&scene->view_layers, node->custom1); if (node_view_layer != view_layer) { return; } @@ -281,7 +281,7 @@ static void cmp_node_rlayer_create_outputs_cb(void *UNUSED(userdata), * unless we want to register that for every other temp Main we could generate??? */ ntreeCompositRegisterPass(scene->nodetree, scene, view_layer, name, type); - for (Scene *sce = G_MAIN->scenes.first; sce; sce = sce->id.next) { + for (Scene *sce = (Scene *)G_MAIN->scenes.first; sce; sce = (Scene *)sce->id.next) { if (sce->nodetree && sce != scene) { ntreeCompositRegisterPass(sce->nodetree, scene, view_layer, name, type); } @@ -297,16 +297,17 @@ static void cmp_node_rlayer_create_outputs(bNodeTree *ntree, if (scene) { RenderEngineType *engine_type = RE_engines_find(scene->r.engine); if (engine_type && engine_type->update_render_passes) { - ViewLayer *view_layer = BLI_findlink(&scene->view_layers, node->custom1); + ViewLayer *view_layer = (ViewLayer *)BLI_findlink(&scene->view_layers, node->custom1); if (view_layer) { - RLayerUpdateData *data = MEM_mallocN(sizeof(RLayerUpdateData), "render layer update data"); + RLayerUpdateData *data = (RLayerUpdateData *)MEM_mallocN(sizeof(RLayerUpdateData), + "render layer update data"); data->available_sockets = available_sockets; data->prev_index = -1; node->storage = data; RenderEngine *engine = RE_engine_create(engine_type); RE_engine_update_render_passes( - engine, scene, view_layer, cmp_node_rlayer_create_outputs_cb, NULL); + engine, scene, view_layer, cmp_node_rlayer_create_outputs_cb, nullptr); RE_engine_free(engine); if ((scene->r.mode & R_EDGE_FRS) && @@ -315,7 +316,7 @@ static void cmp_node_rlayer_create_outputs(bNodeTree *ntree, } MEM_freeN(data); - node->storage = NULL; + node->storage = nullptr; return; } @@ -348,8 +349,7 @@ static void cmp_node_rlayer_create_outputs(bNodeTree *ntree, static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rlayer) { bNodeSocket *sock, *sock_next; - LinkNodePair available_sockets = {NULL, NULL}; - int sock_index; + LinkNodePair available_sockets = {nullptr, nullptr}; /* XXX make callback */ if (rlayer) { @@ -369,15 +369,15 @@ static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rl * the first 31 passes to belong to a specific pass type. * So, we keep those 31 always allocated before the others as well, * even if they have no links attached. */ - sock_index = 0; - for (sock = node->outputs.first; sock; sock = sock_next, sock_index++) { + int sock_index = 0; + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock_next, sock_index++) { sock_next = sock->next; if (BLI_linklist_index(available_sockets.list, sock) >= 0) { sock->flag &= ~(SOCK_UNAVAIL | SOCK_HIDDEN); } else { bNodeLink *link; - for (link = ntree->links.first; link; link = link->next) { + for (link = (bNodeLink *)ntree->links.first; link; link = link->next) { if (link->fromsock == sock) { break; } @@ -392,7 +392,7 @@ static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rl } } - BLI_linklist_free(available_sockets.list, NULL); + BLI_linklist_free(available_sockets.list, nullptr); } static void cmp_node_image_update(bNodeTree *ntree, bNode *node) @@ -407,7 +407,7 @@ static void cmp_node_image_update(bNodeTree *ntree, bNode *node) static void node_composit_init_image(bNodeTree *ntree, bNode *node) { - ImageUser *iuser = MEM_callocN(sizeof(ImageUser), "node image user"); + ImageUser *iuser = (ImageUser *)MEM_callocN(sizeof(ImageUser), "node image user"); node->storage = iuser; iuser->frames = 1; iuser->sfra = 1; @@ -420,10 +420,8 @@ static void node_composit_init_image(bNodeTree *ntree, bNode *node) static void node_composit_free_image(bNode *node) { - bNodeSocket *sock; - /* free extra socket info */ - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { MEM_freeN(sock->storage); } @@ -436,9 +434,9 @@ static void node_composit_copy_image(bNodeTree *UNUSED(dest_ntree), { dest_node->storage = MEM_dupallocN(src_node->storage); - const bNodeSocket *src_output_sock = src_node->outputs.first; - bNodeSocket *dest_output_sock = dest_node->outputs.first; - while (dest_output_sock != NULL) { + const bNodeSocket *src_output_sock = (bNodeSocket *)src_node->outputs.first; + bNodeSocket *dest_output_sock = (bNodeSocket *)dest_node->outputs.first; + while (dest_output_sock != nullptr) { dest_output_sock->storage = MEM_dupallocN(src_output_sock->storage); src_output_sock = src_output_sock->next; @@ -469,7 +467,7 @@ void node_cmp_rlayers_outputs(bNodeTree *ntree, bNode *node) const char *node_cmp_rlayers_sock_to_pass(int sock_index) { if (sock_index >= NUM_LEGACY_SOCKETS) { - return NULL; + return nullptr; } const char *name = cmp_node_rlayers_out[sock_index].name; /* Exception for alpha, which is derived from Combined. */ @@ -479,14 +477,16 @@ const char *node_cmp_rlayers_sock_to_pass(int sock_index) static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; int sock_index = 0; node->id = &scene->id; id_us_plus(node->id); - for (bNodeSocket *sock = node->outputs.first; sock; sock = sock->next, sock_index++) { - NodeImageLayer *sockdata = MEM_callocN(sizeof(NodeImageLayer), "node image layer"); + for (bNodeSocket *sock = (bNodeSocket *)node->outputs.first; sock; + sock = sock->next, sock_index++) { + NodeImageLayer *sockdata = (NodeImageLayer *)MEM_callocN(sizeof(NodeImageLayer), + "node image layer"); sock->storage = sockdata; BLI_strncpy(sockdata->pass_name, @@ -510,13 +510,13 @@ static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype), * Render layers node can only be used in local scene->nodetree, * since it directly links to the scene. */ - for (scene = G.main->scenes.first; scene; scene = scene->id.next) { + for (scene = (Scene *)G.main->scenes.first; scene; scene = (Scene *)scene->id.next) { if (scene->nodetree == ntree) { break; } } - if (scene == NULL) { + if (scene == nullptr) { *r_disabled_hint = "The node tree must be the compositing node tree of any scene in the file"; return false; } @@ -525,10 +525,8 @@ static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype), static void node_composit_free_rlayers(bNode *node) { - bNodeSocket *sock; - /* free extra socket info */ - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (sock->storage) { MEM_freeN(sock->storage); } @@ -540,9 +538,9 @@ static void node_composit_copy_rlayers(bNodeTree *UNUSED(dest_ntree), const bNode *src_node) { /* copy extra socket info */ - const bNodeSocket *src_output_sock = src_node->outputs.first; - bNodeSocket *dest_output_sock = dest_node->outputs.first; - while (dest_output_sock != NULL) { + const bNodeSocket *src_output_sock = (bNodeSocket *)src_node->outputs.first; + bNodeSocket *dest_output_sock = (bNodeSocket *)dest_node->outputs.first; + while (dest_output_sock != nullptr) { dest_output_sock->storage = MEM_dupallocN(src_output_sock->storage); src_output_sock = src_output_sock->next; @@ -562,10 +560,10 @@ void register_node_type_cmp_rlayers(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_R_LAYERS, "Render Layers", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, cmp_node_rlayers_out); + node_type_socket_templates(&ntype, nullptr, cmp_node_rlayers_out); ntype.initfunc_api = node_composit_init_rlayers; ntype.poll = node_composit_poll_rlayers; - node_type_storage(&ntype, NULL, node_composit_free_rlayers, node_composit_copy_rlayers); + node_type_storage(&ntype, nullptr, node_composit_free_rlayers, node_composit_copy_rlayers); node_type_update(&ntype, cmp_node_rlayers_update); node_type_init(&ntype, node_cmp_rlayers_outputs); node_type_size_preset(&ntype, NODE_SIZE_LARGE); diff --git a/source/blender/nodes/composite/nodes/node_composite_inpaint.c b/source/blender/nodes/composite/nodes/node_composite_inpaint.cc index 6c02bca9b39..d0ff97a2ca0 100644 --- a/source/blender/nodes/composite/nodes/node_composite_inpaint.c +++ b/source/blender/nodes/composite/nodes/node_composite_inpaint.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Inpaint/ ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_invert.c b/source/blender/nodes/composite/nodes/node_composite_invert.cc index d229261f208..57b7ed36ccd 100644 --- a/source/blender/nodes/composite/nodes/node_composite_invert.c +++ b/source/blender/nodes/composite/nodes/node_composite_invert.cc @@ -21,15 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** INVERT ******************** */ -static bNodeSocketTemplate cmp_node_invert_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Color"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}}; -static bNodeSocketTemplate cmp_node_invert_out[] = {{SOCK_RGBA, N_("Color")}, {-1, ""}}; +namespace blender::nodes { + +static void cmp_node_invert_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Color").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Color"); +} + +} // namespace blender::nodes static void node_composit_init_invert(bNodeTree *UNUSED(ntree), bNode *node) { @@ -42,7 +47,7 @@ void register_node_type_cmp_invert(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_INVERT, "Invert", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_invert_in, cmp_node_invert_out); + ntype.declare = blender::nodes::cmp_node_invert_declare; node_type_init(&ntype, node_composit_init_invert); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_keying.c b/source/blender/nodes/composite/nodes/node_composite_keying.cc index 73e86a21ebe..d5547161069 100644 --- a/source/blender/nodes/composite/nodes/node_composite_keying.c +++ b/source/blender/nodes/composite/nodes/node_composite_keying.cc @@ -27,7 +27,7 @@ #include "BLI_math_base.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Translate ******************** */ @@ -48,9 +48,7 @@ static bNodeSocketTemplate cmp_node_keying_out[] = { static void node_composit_init_keying(bNodeTree *UNUSED(ntree), bNode *node) { - NodeKeyingData *data; - - data = MEM_callocN(sizeof(NodeKeyingData), "node keying data"); + NodeKeyingData *data = (NodeKeyingData *)MEM_callocN(sizeof(NodeKeyingData), "node keying data"); data->screen_balance = 0.5f; data->despill_balance = 0.5f; @@ -59,7 +57,6 @@ static void node_composit_init_keying(bNodeTree *UNUSED(ntree), bNode *node) data->edge_kernel_tolerance = 0.1f; data->clip_black = 0.0f; data->clip_white = 1.0f; - node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.c b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc index e5e97cd72e4..c976a92b92d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.c +++ b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc @@ -26,7 +26,7 @@ #include "BLI_math_base.h" #include "BLI_math_color.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Translate ******************** */ @@ -37,10 +37,8 @@ static bNodeSocketTemplate cmp_node_keyingscreen_out[] = { static void node_composit_init_keyingscreen(bNodeTree *UNUSED(ntree), bNode *node) { - NodeKeyingScreenData *data; - - data = MEM_callocN(sizeof(NodeKeyingScreenData), "node keyingscreen data"); - + NodeKeyingScreenData *data = (NodeKeyingScreenData *)MEM_callocN(sizeof(NodeKeyingScreenData), + "node keyingscreen data"); node->storage = data; } @@ -49,7 +47,7 @@ void register_node_type_cmp_keyingscreen(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_KEYINGSCREEN, "Keying Screen", NODE_CLASS_MATTE, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_keyingscreen_out); + node_type_socket_templates(&ntype, nullptr, cmp_node_keyingscreen_out); node_type_init(&ntype, node_composit_init_keyingscreen); node_type_storage( &ntype, "NodeKeyingScreenData", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_lensdist.c b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc index ce8c8c00e24..2a8dc035792 100644 --- a/source/blender/nodes/composite/nodes/node_composite_lensdist.c +++ b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_lensdist_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, @@ -36,7 +36,7 @@ static bNodeSocketTemplate cmp_node_lensdist_out[] = { static void node_composit_init_lensdist(bNodeTree *UNUSED(ntree), bNode *node) { - NodeLensDist *nld = MEM_callocN(sizeof(NodeLensDist), "node lensdist data"); + NodeLensDist *nld = (NodeLensDist *)MEM_callocN(sizeof(NodeLensDist), "node lensdist data"); nld->jit = nld->proj = nld->fit = 0; node->storage = nld; } diff --git a/source/blender/nodes/composite/nodes/node_composite_levels.c b/source/blender/nodes/composite/nodes/node_composite_levels.cc index 7c70ccf412a..aaab8dcc874 100644 --- a/source/blender/nodes/composite/nodes/node_composite_levels.c +++ b/source/blender/nodes/composite/nodes/node_composite_levels.cc @@ -21,19 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** LEVELS ******************** */ -static bNodeSocketTemplate cmp_node_view_levels_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_view_levels_out[] = { - {SOCK_FLOAT, N_("Mean")}, - {SOCK_FLOAT, N_("Std Dev")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_levels_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_output<decl::Float>("Mean"); + b.add_output<decl::Float>("Std Dev"); +} + +} // namespace blender::nodes static void node_composit_init_view_levels(bNodeTree *UNUSED(ntree), bNode *node) { @@ -45,7 +46,7 @@ void register_node_type_cmp_view_levels(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VIEW_LEVELS, "Levels", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_view_levels_in, cmp_node_view_levels_out); + ntype.declare = blender::nodes::cmp_node_levels_declare; node_type_init(&ntype, node_composit_init_view_levels); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_lummaMatte.c b/source/blender/nodes/composite/nodes/node_composite_lummaMatte.cc index cb0f59c2f4a..600406d22b9 100644 --- a/source/blender/nodes/composite/nodes/node_composite_lummaMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_lummaMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Luma Matte Node ********************************* */ static bNodeSocketTemplate cmp_node_luma_matte_in[] = { @@ -37,7 +37,7 @@ static bNodeSocketTemplate cmp_node_luma_matte_out[] = { static void node_composit_init_luma_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = 1.0f; c->t2 = 0.0f; diff --git a/source/blender/nodes/composite/nodes/node_composite_mapRange.c b/source/blender/nodes/composite/nodes/node_composite_mapRange.cc index cd95e73ba5c..808ad538e55 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mapRange.c +++ b/source/blender/nodes/composite/nodes/node_composite_mapRange.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** MAP VALUE ******************** */ static bNodeSocketTemplate cmp_node_map_range_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_mapUV.c b/source/blender/nodes/composite/nodes/node_composite_mapUV.cc index e88a303e449..99032bd042e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mapUV.c +++ b/source/blender/nodes/composite/nodes/node_composite_mapUV.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Map UV ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_mapValue.c b/source/blender/nodes/composite/nodes/node_composite_mapValue.cc index c93807c3801..25c00c2ba13 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mapValue.c +++ b/source/blender/nodes/composite/nodes/node_composite_mapValue.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** MAP VALUE ******************** */ static bNodeSocketTemplate cmp_node_map_value_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_mask.c b/source/blender/nodes/composite/nodes/node_composite_mask.cc index e6a5df6c24b..8b415bb8b63 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mask.c +++ b/source/blender/nodes/composite/nodes/node_composite_mask.cc @@ -23,15 +23,22 @@ #include "DNA_mask_types.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" -/* **************** Translate ******************** */ +/* **************** Mask ******************** */ -static bNodeSocketTemplate cmp_node_mask_out[] = {{SOCK_FLOAT, "Mask"}, {-1, ""}}; +namespace blender::nodes { + +static void cmp_node_mask_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Mask"); +} + +} // namespace blender::nodes static void node_composit_init_mask(bNodeTree *UNUSED(ntree), bNode *node) { - NodeMask *data = MEM_callocN(sizeof(NodeMask), "NodeMask"); + NodeMask *data = (NodeMask *)MEM_callocN(sizeof(NodeMask), "NodeMask"); data->size_x = data->size_y = 256; node->storage = data; @@ -41,7 +48,7 @@ static void node_composit_init_mask(bNodeTree *UNUSED(ntree), bNode *node) static void node_mask_label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int maxlen) { - if (node->id != NULL) { + if (node->id != nullptr) { BLI_strncpy(label, node->id->name + 2, maxlen); } else { @@ -54,7 +61,7 @@ void register_node_type_cmp_mask(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MASK, "Mask", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_mask_out); + ntype.declare = blender::nodes::cmp_node_mask_declare; node_type_init(&ntype, node_composit_init_mask); node_type_label(&ntype, node_mask_label); diff --git a/source/blender/nodes/composite/nodes/node_composite_math.c b/source/blender/nodes/composite/nodes/node_composite_math.cc index 2191c6bcdc3..a9859425e28 100644 --- a/source/blender/nodes/composite/nodes/node_composite_math.c +++ b/source/blender/nodes/composite/nodes/node_composite_math.cc @@ -21,23 +21,28 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SCALAR MATH ******************** */ -static bNodeSocketTemplate cmp_node_math_in[] = { - {SOCK_FLOAT, N_("Value"), 0.5f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Value"), 0.5f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Value"), 0.0f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, - {-1, ""}}; -static bNodeSocketTemplate cmp_node_math_out[] = {{SOCK_FLOAT, N_("Value")}, {-1, ""}}; +namespace blender::nodes { + +static void cmp_node_math_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Value").default_value(0.5f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>("Value", "Value_001").default_value(0.5f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>("Value", "Value_002").default_value(0.5f).min(-10000.0f).max(10000.0f); + b.add_output<decl::Float>("Value"); +} + +} // namespace blender::nodes void register_node_type_cmp_math(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MATH, "Math", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_math_in, cmp_node_math_out); + ntype.declare = blender::nodes::cmp_node_math_declare; node_type_label(&ntype, node_math_label); node_type_update(&ntype, node_math_update); diff --git a/source/blender/nodes/composite/nodes/node_composite_mixrgb.c b/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc index 9d3751c7da3..4f2a9c2717c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mixrgb.c +++ b/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc @@ -21,19 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** MIX RGB ******************** */ -static bNodeSocketTemplate cmp_node_mix_rgb_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_mix_rgb_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_mixrgb_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>("Image", "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes /* custom1 = mix type */ void register_node_type_cmp_mix_rgb(void) @@ -41,7 +43,7 @@ void register_node_type_cmp_mix_rgb(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MIX_RGB, "Mix", NODE_CLASS_OP_COLOR, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_mix_rgb_in, cmp_node_mix_rgb_out); + ntype.declare = blender::nodes::cmp_node_mixrgb_declare; node_type_label(&ntype, node_blend_label); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_movieclip.c b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc index 4f5aef05425..ae91212f811 100644 --- a/source/blender/nodes/composite/nodes/node_composite_movieclip.c +++ b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc @@ -21,26 +21,31 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_context.h" #include "BKE_lib_id.h" -static bNodeSocketTemplate cmp_node_movieclip_out[] = { - {SOCK_RGBA, N_("Image")}, - {SOCK_FLOAT, N_("Alpha")}, - {SOCK_FLOAT, N_("Offset X")}, - {SOCK_FLOAT, N_("Offset Y")}, - {SOCK_FLOAT, N_("Scale")}, - {SOCK_FLOAT, N_("Angle")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_movieclip_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Color>("Image"); + b.add_output<decl::Float>("Alpha"); + b.add_output<decl::Float>("Offset X"); + b.add_output<decl::Float>("Offset Y"); + b.add_output<decl::Float>("Scale"); + b.add_output<decl::Float>("Angle"); +} + +} // namespace blender::nodes static void init(const bContext *C, PointerRNA *ptr) { - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; Scene *scene = CTX_data_scene(C); - MovieClipUser *user = MEM_callocN(sizeof(MovieClipUser), "node movie clip user"); + MovieClipUser *user = (MovieClipUser *)MEM_callocN(sizeof(MovieClipUser), + "node movie clip user"); node->id = (ID *)scene->clip; id_us_plus(node->id); @@ -53,7 +58,7 @@ void register_node_type_cmp_movieclip(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MOVIECLIP, "Movie Clip", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, cmp_node_movieclip_out); + ntype.declare = blender::nodes::cmp_node_movieclip_declare; ntype.initfunc_api = init; node_type_storage( &ntype, "MovieClipUser", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_moviedistortion.c b/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc index 7e30d004b45..2bac30cc152 100644 --- a/source/blender/nodes/composite/nodes/node_composite_moviedistortion.c +++ b/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_context.h" #include "BKE_lib_id.h" @@ -50,7 +50,7 @@ static void label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int maxlen static void init(const bContext *C, PointerRNA *ptr) { - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; Scene *scene = CTX_data_scene(C); node->id = (ID *)scene->clip; @@ -60,16 +60,16 @@ static void init(const bContext *C, PointerRNA *ptr) static void storage_free(bNode *node) { if (node->storage) { - BKE_tracking_distortion_free(node->storage); + BKE_tracking_distortion_free((MovieDistortion *)node->storage); } - node->storage = NULL; + node->storage = nullptr; } static void storage_copy(bNodeTree *UNUSED(dest_ntree), bNode *dest_node, const bNode *src_node) { if (src_node->storage) { - dest_node->storage = BKE_tracking_distortion_copy(src_node->storage); + dest_node->storage = BKE_tracking_distortion_copy((MovieDistortion *)src_node->storage); } } @@ -82,7 +82,7 @@ void register_node_type_cmp_moviedistortion(void) node_type_label(&ntype, label); ntype.initfunc_api = init; - node_type_storage(&ntype, NULL, storage_free, storage_copy); + node_type_storage(&ntype, nullptr, storage_free, storage_copy); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_normal.c b/source/blender/nodes/composite/nodes/node_composite_normal.cc index 91300e66339..7531025daa5 100644 --- a/source/blender/nodes/composite/nodes/node_composite_normal.c +++ b/source/blender/nodes/composite/nodes/node_composite_normal.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** NORMAL ******************** */ static bNodeSocketTemplate cmp_node_normal_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_normalize.c b/source/blender/nodes/composite/nodes/node_composite_normalize.cc index 26f2abc745f..7cc54e4eed6 100644 --- a/source/blender/nodes/composite/nodes/node_composite_normalize.c +++ b/source/blender/nodes/composite/nodes/node_composite_normalize.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** NORMALIZE single channel, useful for Z buffer ******************** */ static bNodeSocketTemplate cmp_node_normalize_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_outputFile.c b/source/blender/nodes/composite/nodes/node_composite_outputFile.cc index c10edd8d5ad..a372d2f7419 100644 --- a/source/blender/nodes/composite/nodes/node_composite_outputFile.c +++ b/source/blender/nodes/composite/nodes/node_composite_outputFile.cc @@ -23,13 +23,13 @@ #include "BLI_string_utils.h" #include "BLI_utildefines.h" -#include <string.h> +#include <cstring> #include "BKE_context.h" #include "RNA_access.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "intern/openexr/openexr_multi.h" @@ -38,14 +38,15 @@ /* find unique path */ static bool unique_path_unique_check(void *arg, const char *name) { - struct { + struct Args { ListBase *lb; bNodeSocket *sock; - } *data = arg; - bNodeSocket *sock; - for (sock = data->lb->first; sock; sock = sock->next) { + }; + Args *data = (Args *)arg; + + LISTBASE_FOREACH (bNodeSocket *, sock, data->lb) { if (sock != data->sock) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; if (STREQ(sockdata->path, name)) { return true; } @@ -67,11 +68,11 @@ void ntreeCompositOutputFileUniquePath(ListBase *list, data.sock = sock; /* See if we are given an empty string */ - if (ELEM(NULL, sock, defname)) { + if (ELEM(nullptr, sock, defname)) { return; } - sockdata = sock->storage; + sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_uniquename_cb( unique_path_unique_check, &data, defname, delim, sockdata->path, sizeof(sockdata->path)); } @@ -79,14 +80,15 @@ void ntreeCompositOutputFileUniquePath(ListBase *list, /* find unique EXR layer */ static bool unique_layer_unique_check(void *arg, const char *name) { - struct { + struct Args { ListBase *lb; bNodeSocket *sock; - } *data = arg; - bNodeSocket *sock; - for (sock = data->lb->first; sock; sock = sock->next) { + }; + Args *data = (Args *)arg; + + LISTBASE_FOREACH (bNodeSocket *, sock, data->lb) { if (sock != data->sock) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; if (STREQ(sockdata->layer, name)) { return true; } @@ -99,7 +101,6 @@ void ntreeCompositOutputFileUniqueLayer(ListBase *list, const char defname[], char delim) { - NodeImageMultiFileSocket *sockdata; struct { ListBase *lb; bNodeSocket *sock; @@ -108,11 +109,11 @@ void ntreeCompositOutputFileUniqueLayer(ListBase *list, data.sock = sock; /* See if we are given an empty string */ - if (ELEM(NULL, sock, defname)) { + if (ELEM(nullptr, sock, defname)) { return; } - sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_uniquename_cb( unique_layer_unique_check, &data, defname, delim, sockdata->layer, sizeof(sockdata->layer)); } @@ -122,12 +123,13 @@ bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree, const char *name, ImageFormatData *im_format) { - NodeImageMultiFile *nimf = node->storage; - bNodeSocket *sock = nodeAddStaticSocket(ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, NULL, name); + NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage; + bNodeSocket *sock = nodeAddStaticSocket( + ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, nullptr, name); /* create format data for the input socket */ - NodeImageMultiFileSocket *sockdata = MEM_callocN(sizeof(NodeImageMultiFileSocket), - "socket image format"); + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)MEM_callocN( + sizeof(NodeImageMultiFileSocket), "socket image format"); sock->storage = sockdata; BLI_strncpy_utf8(sockdata->path, name, sizeof(sockdata->path)); @@ -155,8 +157,8 @@ bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree, int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node) { - NodeImageMultiFile *nimf = node->storage; - bNodeSocket *sock = BLI_findlink(&node->inputs, nimf->active_input); + NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage; + bNodeSocket *sock = (bNodeSocket *)BLI_findlink(&node->inputs, nimf->active_input); int totinputs = BLI_listbase_count(&node->inputs); if (!sock) { @@ -176,14 +178,14 @@ int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node) void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_strncpy_utf8(sockdata->path, name, sizeof(sockdata->path)); ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_'); } void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_strncpy_utf8(sockdata->layer, name, sizeof(sockdata->layer)); ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_'); } @@ -193,9 +195,10 @@ static void init_output_file(const bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = ptr->data; - NodeImageMultiFile *nimf = MEM_callocN(sizeof(NodeImageMultiFile), "node image multi file"); - ImageFormatData *format = NULL; + bNode *node = (bNode *)ptr->data; + NodeImageMultiFile *nimf = (NodeImageMultiFile *)MEM_callocN(sizeof(NodeImageMultiFile), + "node image multi file"); + ImageFormatData *format = nullptr; node->storage = nimf; if (scene) { @@ -219,10 +222,8 @@ static void init_output_file(const bContext *C, PointerRNA *ptr) static void free_output_file(bNode *node) { - bNodeSocket *sock; - /* free storage data in sockets */ - for (sock = node->inputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { MEM_freeN(sock->storage); } @@ -238,37 +239,35 @@ static void copy_output_file(bNodeTree *UNUSED(dest_ntree), dest_node->storage = MEM_dupallocN(src_node->storage); /* duplicate storage data in sockets */ - for (src_sock = src_node->inputs.first, dest_sock = dest_node->inputs.first; + for (src_sock = (bNodeSocket *)src_node->inputs.first, + dest_sock = (bNodeSocket *)dest_node->inputs.first; src_sock && dest_sock; - src_sock = src_sock->next, dest_sock = dest_sock->next) { + src_sock = src_sock->next, dest_sock = (bNodeSocket *)dest_sock->next) { dest_sock->storage = MEM_dupallocN(src_sock->storage); } } static void update_output_file(bNodeTree *ntree, bNode *node) { - bNodeSocket *sock, *sock_next; PointerRNA ptr; /* XXX fix for T36706: remove invalid sockets added with bpy API. * This is not ideal, but prevents crashes from missing storage. * FileOutput node needs a redesign to support this properly. */ - for (sock = node->inputs.first; sock; sock = sock_next) { - sock_next = sock->next; - if (sock->storage == NULL) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { + if (sock->storage == nullptr) { nodeRemoveSocket(ntree, node, sock); } } - for (sock = node->outputs.first; sock; sock = sock_next) { - sock_next = sock->next; + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { nodeRemoveSocket(ntree, node, sock); } cmp_node_update_default(ntree, node); /* automatically update the socket type based on linked input */ - for (sock = node->inputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { if (sock->link) { RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, sock, &ptr); RNA_enum_set(&ptr, "type", sock->link->fromsock->type); @@ -281,7 +280,7 @@ void register_node_type_cmp_output_file(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_OUTPUT_FILE, "File Output", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, NULL); + node_type_socket_templates(&ntype, nullptr, nullptr); ntype.initfunc_api = init_output_file; node_type_storage(&ntype, "NodeImageMultiFile", free_output_file, copy_output_file); node_type_update(&ntype, update_output_file); diff --git a/source/blender/nodes/composite/nodes/node_composite_pixelate.c b/source/blender/nodes/composite/nodes/node_composite_pixelate.cc index 6e8a28df76f..19975c21a0b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_pixelate.c +++ b/source/blender/nodes/composite/nodes/node_composite_pixelate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Pixelate ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.c b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc index ab5db41e5b5..e122b710b7b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.c +++ b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_planetrackdeform_in[] = { {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, {-1, ""}}; @@ -34,8 +34,8 @@ static bNodeSocketTemplate cmp_node_planetrackdeform_out[] = { static void init(bNodeTree *UNUSED(ntree), bNode *node) { - NodePlaneTrackDeformData *data = MEM_callocN(sizeof(NodePlaneTrackDeformData), - "node plane track deform data"); + NodePlaneTrackDeformData *data = (NodePlaneTrackDeformData *)MEM_callocN( + sizeof(NodePlaneTrackDeformData), "node plane track deform data"); data->motion_blur_samples = 16; data->motion_blur_shutter = 0.5f; node->storage = data; diff --git a/source/blender/nodes/composite/nodes/node_composite_posterize.c b/source/blender/nodes/composite/nodes/node_composite_posterize.cc index 5093e581cdc..45a98e68b4b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_posterize.c +++ b/source/blender/nodes/composite/nodes/node_composite_posterize.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Posterize ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_premulkey.c b/source/blender/nodes/composite/nodes/node_composite_premulkey.cc index be76bbf01cf..e557854c611 100644 --- a/source/blender/nodes/composite/nodes/node_composite_premulkey.c +++ b/source/blender/nodes/composite/nodes/node_composite_premulkey.cc @@ -21,25 +21,26 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Premul and Key Alpha Convert ******************** */ -static bNodeSocketTemplate cmp_node_premulkey_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_premulkey_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_premulkey_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_premulkey(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_PREMULKEY, "Alpha Convert", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_premulkey_in, cmp_node_premulkey_out); + ntype.declare = blender::nodes::cmp_node_premulkey_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_rgb.c b/source/blender/nodes/composite/nodes/node_composite_rgb.cc index dae63f7a702..332e56e26b1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_rgb.c +++ b/source/blender/nodes/composite/nodes/node_composite_rgb.cc @@ -21,20 +21,25 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** RGB ******************** */ -static bNodeSocketTemplate cmp_node_rgb_out[] = { - {SOCK_RGBA, N_("RGBA"), 0.5f, 0.5f, 0.5f, 1.0f}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_rgb_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Color>("RGBA").default_value({0.5f, 0.5f, 0.5f, 1.0f}); +} + +} // namespace blender::nodes void register_node_type_cmp_rgb(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_RGB, "RGB", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_rgb_out); + ntype.declare = blender::nodes::cmp_node_rgb_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_rotate.c b/source/blender/nodes/composite/nodes/node_composite_rotate.cc index 7dd39d5eaa1..d28b35ec9fb 100644 --- a/source/blender/nodes/composite/nodes/node_composite_rotate.c +++ b/source/blender/nodes/composite/nodes/node_composite_rotate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Rotate ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_scale.c b/source/blender/nodes/composite/nodes/node_composite_scale.cc index 963832de03a..3972fc0d949 100644 --- a/source/blender/nodes/composite/nodes/node_composite_scale.c +++ b/source/blender/nodes/composite/nodes/node_composite_scale.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Scale ******************** */ @@ -38,7 +38,7 @@ static void node_composite_update_scale(bNodeTree *UNUSED(ntree), bNode *node) bool use_xy_scale = ELEM(node->custom1, CMP_SCALE_RELATIVE, CMP_SCALE_ABSOLUTE); /* Only show X/Y scale factor inputs for modes using them! */ - for (sock = node->inputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { if (STR_ELEM(sock->name, "X", "Y")) { if (use_xy_scale) { sock->flag &= ~SOCK_UNAVAIL; diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.cc index 001b197e23a..aa719a99b36 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.cc @@ -21,50 +21,53 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE HSVA ******************** */ -static bNodeSocketTemplate cmp_node_sephsva_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_sephsva_out[] = { - {SOCK_FLOAT, N_("H")}, - {SOCK_FLOAT, N_("S")}, - {SOCK_FLOAT, N_("V")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_sephsva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("H"); + b.add_output<decl::Float>("S"); + b.add_output<decl::Float>("V"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes void register_node_type_cmp_sephsva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPHSVA, "Separate HSVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_sephsva_in, cmp_node_sephsva_out); - + ntype.declare = blender::nodes::cmp_node_sephsva_declare; nodeRegisterType(&ntype); } /* **************** COMBINE HSVA ******************** */ -static bNodeSocketTemplate cmp_node_combhsva_in[] = { - {SOCK_FLOAT, N_("H"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("S"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("V"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combhsva_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combhsva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("H").min(0.0f).max(1.0f); + b.add_input<decl::Float>("S").min(0.0f).max(1.0f); + b.add_input<decl::Float>("V").min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_combhsva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBHSVA, "Combine HSVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combhsva_in, cmp_node_combhsva_out); + ntype.declare = blender::nodes::cmp_node_combhsva_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.cc index e08f27db254..b29af1359f5 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.cc @@ -21,50 +21,53 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE RGBA ******************** */ -static bNodeSocketTemplate cmp_node_seprgba_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_seprgba_out[] = { - {SOCK_FLOAT, N_("R")}, - {SOCK_FLOAT, N_("G")}, - {SOCK_FLOAT, N_("B")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_seprgba_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("R"); + b.add_output<decl::Float>("G"); + b.add_output<decl::Float>("B"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes void register_node_type_cmp_seprgba(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPRGBA, "Separate RGBA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_seprgba_in, cmp_node_seprgba_out); + ntype.declare = blender::nodes::cmp_node_seprgba_declare; nodeRegisterType(&ntype); } /* **************** COMBINE RGBA ******************** */ -static bNodeSocketTemplate cmp_node_combrgba_in[] = { - {SOCK_FLOAT, N_("R"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("G"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("B"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combrgba_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combrgba_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("R").min(0.0f).max(1.0f); + b.add_input<decl::Float>("G").min(0.0f).max(1.0f); + b.add_input<decl::Float>("B").min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_combrgba(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBRGBA, "Combine RGBA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combrgba_in, cmp_node_combrgba_out); + ntype.declare = blender::nodes::cmp_node_combrgba_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.cc index b3884296600..526d6b4eb5b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.cc @@ -21,18 +21,22 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE YCCA ******************** */ -static bNodeSocketTemplate cmp_node_sepycca_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}}; -static bNodeSocketTemplate cmp_node_sepycca_out[] = { - {SOCK_FLOAT, N_("Y")}, - {SOCK_FLOAT, N_("Cb")}, - {SOCK_FLOAT, N_("Cr")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_sepycca_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("Y"); + b.add_output<decl::Float>("Cb"); + b.add_output<decl::Float>("Cr"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes static void node_composit_init_mode_sepycca(bNodeTree *UNUSED(ntree), bNode *node) { @@ -44,24 +48,26 @@ void register_node_type_cmp_sepycca(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPYCCA, "Separate YCbCrA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_sepycca_in, cmp_node_sepycca_out); + ntype.declare = blender::nodes::cmp_node_sepycca_declare; node_type_init(&ntype, node_composit_init_mode_sepycca); nodeRegisterType(&ntype); } /* **************** COMBINE YCCA ******************** */ -static bNodeSocketTemplate cmp_node_combycca_in[] = { - {SOCK_FLOAT, N_("Y"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Cb"), 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Cr"), 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combycca_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combycca_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Y").min(0.0f).max(1.0f); + b.add_input<decl::Float>("Cb").default_value(0.5f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("Cr").default_value(0.5f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_mode_combycca(bNodeTree *UNUSED(ntree), bNode *node) { @@ -73,7 +79,7 @@ void register_node_type_cmp_combycca(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBYCCA, "Combine YCbCrA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combycca_in, cmp_node_combycca_out); + ntype.declare = blender::nodes::cmp_node_combycca_declare; node_type_init(&ntype, node_composit_init_mode_combycca); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.cc index 4da79ec7981..4619b0c97f1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.cc @@ -21,48 +21,54 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE YUVA ******************** */ -static bNodeSocketTemplate cmp_node_sepyuva_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}}; -static bNodeSocketTemplate cmp_node_sepyuva_out[] = { - {SOCK_FLOAT, N_("Y")}, - {SOCK_FLOAT, N_("U")}, - {SOCK_FLOAT, N_("V")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_sepyuva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("Y"); + b.add_output<decl::Float>("U"); + b.add_output<decl::Float>("V"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes void register_node_type_cmp_sepyuva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPYUVA, "Separate YUVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_sepyuva_in, cmp_node_sepyuva_out); + ntype.declare = blender::nodes::cmp_node_sepyuva_declare; nodeRegisterType(&ntype); } /* **************** COMBINE YUVA ******************** */ -static bNodeSocketTemplate cmp_node_combyuva_in[] = { - {SOCK_FLOAT, N_("Y"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("U"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("V"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combyuva_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combyuva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Y").min(0.0f).max(1.0f); + b.add_input<decl::Float>("U").min(0.0f).max(1.0f); + b.add_input<decl::Float>("V").min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_combyuva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBYUVA, "Combine YUVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combyuva_in, cmp_node_combyuva_out); + ntype.declare = blender::nodes::cmp_node_combyuva_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_setalpha.c b/source/blender/nodes/composite/nodes/node_composite_setalpha.cc index 1b44cc011e9..07a7ffcb426 100644 --- a/source/blender/nodes/composite/nodes/node_composite_setalpha.c +++ b/source/blender/nodes/composite/nodes/node_composite_setalpha.cc @@ -21,22 +21,24 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SET ALPHA ******************** */ -static bNodeSocketTemplate cmp_node_setalpha_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_FLOAT, N_("Alpha"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_setalpha_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_setalpha_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Alpha").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_setalpha(bNodeTree *UNUSED(ntree), bNode *node) { - NodeSetAlpha *settings = MEM_callocN(sizeof(NodeSetAlpha), __func__); + NodeSetAlpha *settings = (NodeSetAlpha *)MEM_callocN(sizeof(NodeSetAlpha), __func__); node->storage = settings; settings->mode = CMP_NODE_SETALPHA_MODE_APPLY; } @@ -46,7 +48,7 @@ void register_node_type_cmp_setalpha(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SETALPHA, "Set Alpha", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_setalpha_in, cmp_node_setalpha_out); + ntype.declare = blender::nodes::cmp_node_setalpha_declare; node_type_init(&ntype, node_composit_init_setalpha); node_type_storage( &ntype, "NodeSetAlpha", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_splitViewer.c b/source/blender/nodes/composite/nodes/node_composite_splitViewer.cc index 8afb3fd4841..f64abe87116 100644 --- a/source/blender/nodes/composite/nodes/node_composite_splitViewer.c +++ b/source/blender/nodes/composite/nodes/node_composite_splitViewer.cc @@ -21,21 +21,26 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_global.h" #include "BKE_image.h" /* **************** SPLIT VIEWER ******************** */ -static bNodeSocketTemplate cmp_node_splitviewer_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_splitviewer_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image"); + b.add_input<decl::Color>("Image", "Image_001"); +} + +} // namespace blender::nodes static void node_composit_init_splitviewer(bNodeTree *UNUSED(ntree), bNode *node) { - ImageUser *iuser = MEM_callocN(sizeof(ImageUser), "node image user"); + ImageUser *iuser = (ImageUser *)MEM_callocN(sizeof(ImageUser), "node image user"); node->storage = iuser; iuser->sfra = 1; iuser->ok = 1; @@ -50,12 +55,12 @@ void register_node_type_cmp_splitviewer(void) cmp_node_type_base( &ntype, CMP_NODE_SPLITVIEWER, "Split Viewer", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_splitviewer_in, NULL); + ntype.declare = blender::nodes::cmp_node_splitviewer_declare; node_type_init(&ntype, node_composit_init_splitviewer); node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage); /* Do not allow muting for this node. */ - node_type_internal_links(&ntype, NULL); + node_type_internal_links(&ntype, nullptr); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_stabilize2d.c b/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc index b89f245c542..e5ce2e8ceb9 100644 --- a/source/blender/nodes/composite/nodes/node_composite_stabilize2d.c +++ b/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_context.h" #include "BKE_lib_id.h" @@ -40,7 +40,7 @@ static bNodeSocketTemplate cmp_node_stabilize2d_out[] = { static void init(const bContext *C, PointerRNA *ptr) { - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; Scene *scene = CTX_data_scene(C); node->id = (ID *)scene->clip; diff --git a/source/blender/nodes/composite/nodes/node_composite_sunbeams.c b/source/blender/nodes/composite/nodes/node_composite_sunbeams.cc index 84ab2d30d34..73907d2e27f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sunbeams.c +++ b/source/blender/nodes/composite/nodes/node_composite_sunbeams.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate inputs[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, @@ -34,11 +34,10 @@ static bNodeSocketTemplate outputs[] = { static void init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeSunBeams *data = MEM_callocN(sizeof(NodeSunBeams), "sun beams node"); + NodeSunBeams *data = (NodeSunBeams *)MEM_callocN(sizeof(NodeSunBeams), "sun beams node"); data->source[0] = 0.5f; data->source[1] = 0.5f; - node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_switch.c b/source/blender/nodes/composite/nodes/node_composite_switch.cc index efbb3390e06..71226a6da0b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_switch.c +++ b/source/blender/nodes/composite/nodes/node_composite_switch.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** MIX RGB ******************** */ static bNodeSocketTemplate cmp_node_switch_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_switchview.c b/source/blender/nodes/composite/nodes/node_composite_switchview.cc index b09d5119bc4..a61712f7f8d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_switchview.c +++ b/source/blender/nodes/composite/nodes/node_composite_switchview.cc @@ -25,7 +25,7 @@ #include "BKE_context.h" #include "BKE_lib_id.h" -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** SWITCH VIEW ******************** */ static bNodeSocketTemplate cmp_node_switch_view_out[] = { @@ -37,26 +37,23 @@ static bNodeSocket *ntreeCompositSwitchViewAddSocket(bNodeTree *ntree, bNode *node, const char *name) { - bNodeSocket *sock = nodeAddStaticSocket(ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, NULL, name); + bNodeSocket *sock = nodeAddStaticSocket( + ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, nullptr, name); return sock; } static void cmp_node_switch_view_sanitycheck(bNodeTree *ntree, bNode *node) { - bNodeSocket *sock; - if (!BLI_listbase_is_empty(&node->inputs)) { return; } - sock = ntreeCompositSwitchViewAddSocket(ntree, node, "No View"); + bNodeSocket *sock = ntreeCompositSwitchViewAddSocket(ntree, node, "No View"); sock->flag |= SOCK_HIDDEN; } static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) { - bNodeSocket *sock; - SceneRenderView *srv; Scene *scene = (Scene *)node->id; /* only update when called from the operator button */ @@ -64,7 +61,7 @@ static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) return; } - if (scene == NULL) { + if (scene == nullptr) { nodeRemoveAllSockets(ntree, node); /* make sure there is always one socket */ cmp_node_switch_view_sanitycheck(ntree, node); @@ -72,11 +69,12 @@ static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) } /* remove the views that were removed */ - sock = node->inputs.last; + bNodeSocket *sock = (bNodeSocket *)node->inputs.last; while (sock) { - srv = BLI_findstring(&scene->r.views, sock->name, offsetof(SceneRenderView, name)); + SceneRenderView *srv = (SceneRenderView *)BLI_findstring( + &scene->r.views, sock->name, offsetof(SceneRenderView, name)); - if (srv == NULL) { + if (srv == nullptr) { bNodeSocket *sock_del = sock; sock = sock->prev; nodeRemoveSocket(ntree, node, sock_del); @@ -94,10 +92,10 @@ static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) } /* add the new views */ - for (srv = scene->r.views.first; srv; srv = srv->next) { - sock = BLI_findstring(&node->inputs, srv->name, offsetof(bNodeSocket, name)); + LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) { + sock = (bNodeSocket *)BLI_findstring(&node->inputs, srv->name, offsetof(bNodeSocket, name)); - if (sock == NULL) { + if (sock == nullptr) { sock = ntreeCompositSwitchViewAddSocket(ntree, node, srv->name); } @@ -117,10 +115,7 @@ static void init_switch_view(const bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = ptr->data; - SceneRenderView *srv; - bNodeSocket *sock; - int nr; + bNode *node = (bNode *)ptr->data; /* store scene for updates */ node->id = (ID *)scene; @@ -129,8 +124,8 @@ static void init_switch_view(const bContext *C, PointerRNA *ptr) if (scene) { RenderData *rd = &scene->r; - for (nr = 0, srv = rd->views.first; srv; srv = srv->next, nr++) { - sock = ntreeCompositSwitchViewAddSocket(ntree, node, srv->name); + LISTBASE_FOREACH (SceneRenderView *, srv, &rd->views) { + bNodeSocket *sock = ntreeCompositSwitchViewAddSocket(ntree, node, srv->name); if (srv->viewflag & SCE_VIEW_DISABLE) { sock->flag |= SOCK_HIDDEN; @@ -147,7 +142,7 @@ void register_node_type_cmp_switch_view(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SWITCH_VIEW, "Switch View", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_switch_view_out); + node_type_socket_templates(&ntype, nullptr, cmp_node_switch_view_out); ntype.initfunc_api = init_switch_view; diff --git a/source/blender/nodes/composite/nodes/node_composite_texture.c b/source/blender/nodes/composite/nodes/node_composite_texture.cc index 50be05fe5a6..eff008b4b41 100644 --- a/source/blender/nodes/composite/nodes/node_composite_texture.c +++ b/source/blender/nodes/composite/nodes/node_composite_texture.cc @@ -21,26 +21,32 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** TEXTURE ******************** */ -static bNodeSocketTemplate cmp_node_texture_in[] = { - {SOCK_VECTOR, N_("Offset"), 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 2.0f, PROP_TRANSLATION}, - {SOCK_VECTOR, N_("Scale"), 1.0f, 1.0f, 1.0f, 1.0f, -10.0f, 10.0f, PROP_XYZ}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_texture_out[] = { - {SOCK_FLOAT, N_("Value")}, - {SOCK_RGBA, N_("Color")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_texture_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Vector>("Offset").min(-2.0f).max(2.0f).subtype(PROP_TRANSLATION); + b.add_input<decl::Vector>("Scale") + .default_value({1.0f, 1.0f, 1.0f}) + .min(-10.0f) + .max(10.0f) + .subtype(PROP_XYZ); + b.add_output<decl::Float>("Value"); + b.add_output<decl::Color>("Color"); +} + +} // namespace blender::nodes void register_node_type_cmp_texture(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TEXTURE, "Texture", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_texture_in, cmp_node_texture_out); + ntype.declare = blender::nodes::cmp_node_texture_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_tonemap.c b/source/blender/nodes/composite/nodes/node_composite_tonemap.cc index 5fc86c997f5..85fd240ce2e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_tonemap.c +++ b/source/blender/nodes/composite/nodes/node_composite_tonemap.cc @@ -21,20 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -static bNodeSocketTemplate cmp_node_tonemap_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_tonemap_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_tonemap_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_tonemap(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTonemap *ntm = MEM_callocN(sizeof(NodeTonemap), "node tonemap data"); + NodeTonemap *ntm = (NodeTonemap *)MEM_callocN(sizeof(NodeTonemap), "node tonemap data"); ntm->type = 1; ntm->key = 0.18; ntm->offset = 1; @@ -53,7 +54,7 @@ void register_node_type_cmp_tonemap(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TONEMAP, "Tonemap", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_tonemap_in, cmp_node_tonemap_out); + ntype.declare = blender::nodes::cmp_node_tonemap_declare; node_type_init(&ntype, node_composit_init_tonemap); node_type_storage(&ntype, "NodeTonemap", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_trackpos.c b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc index d59ce9b8b7a..cb5c9468daa 100644 --- a/source/blender/nodes/composite/nodes/node_composite_trackpos.c +++ b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc @@ -21,18 +21,23 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -static bNodeSocketTemplate cmp_node_trackpos_out[] = { - {SOCK_FLOAT, N_("X")}, - {SOCK_FLOAT, N_("Y")}, - {SOCK_VECTOR, N_("Speed"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_VELOCITY}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_trackpos_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("X"); + b.add_output<decl::Float>("Y"); + b.add_output<decl::Vector>("Speed").subtype(PROP_VELOCITY); +} + +} // namespace blender::nodes static void init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTrackPosData *data = MEM_callocN(sizeof(NodeTrackPosData), "node track position data"); + NodeTrackPosData *data = (NodeTrackPosData *)MEM_callocN(sizeof(NodeTrackPosData), + "node track position data"); node->storage = data; } @@ -42,7 +47,7 @@ void register_node_type_cmp_trackpos(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TRACKPOS, "Track Position", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_trackpos_out); + ntype.declare = blender::nodes::cmp_node_trackpos_declare; node_type_init(&ntype, init); node_type_storage( &ntype, "NodeTrackPosData", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_transform.c b/source/blender/nodes/composite/nodes/node_composite_transform.cc index be526c1059c..1695101cdbf 100644 --- a/source/blender/nodes/composite/nodes/node_composite_transform.c +++ b/source/blender/nodes/composite/nodes/node_composite_transform.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Transform ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_translate.c b/source/blender/nodes/composite/nodes/node_composite_translate.cc index 43337ec6f7e..0ee8a41a5ea 100644 --- a/source/blender/nodes/composite/nodes/node_composite_translate.c +++ b/source/blender/nodes/composite/nodes/node_composite_translate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Translate ******************** */ @@ -38,7 +38,8 @@ static bNodeSocketTemplate cmp_node_translate_out[] = { static void node_composit_init_translate(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTranslateData *data = MEM_callocN(sizeof(NodeTranslateData), "node translate data"); + NodeTranslateData *data = (NodeTranslateData *)MEM_callocN(sizeof(NodeTranslateData), + "node translate data"); node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_valToRgb.c b/source/blender/nodes/composite/nodes/node_composite_valToRgb.cc index ed6dbfa2bf3..ba98ee12f30 100644 --- a/source/blender/nodes/composite/nodes/node_composite_valToRgb.c +++ b/source/blender/nodes/composite/nodes/node_composite_valToRgb.cc @@ -21,18 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** VALTORGB ******************** */ -static bNodeSocketTemplate cmp_node_valtorgb_in[] = { - {SOCK_FLOAT, N_("Fac"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_valtorgb_out[] = { - {SOCK_RGBA, N_("Image")}, - {SOCK_FLOAT, N_("Alpha")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_valtorgb_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_output<decl::Color>("Image"); + b.add_output<decl::Color>("Alpha"); +} + +} // namespace blender::nodes static void node_composit_init_valtorgb(bNodeTree *UNUSED(ntree), bNode *node) { @@ -44,7 +46,7 @@ void register_node_type_cmp_valtorgb(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VALTORGB, "ColorRamp", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_valtorgb_in, cmp_node_valtorgb_out); + ntype.declare = blender::nodes::cmp_node_valtorgb_declare; node_type_size(&ntype, 240, 200, 320); node_type_init(&ntype, node_composit_init_valtorgb); node_type_storage(&ntype, "ColorBand", node_free_standard_storage, node_copy_standard_storage); @@ -53,21 +55,23 @@ void register_node_type_cmp_valtorgb(void) } /* **************** RGBTOBW ******************** */ -static bNodeSocketTemplate cmp_node_rgbtobw_in[] = { - {SOCK_RGBA, N_("Image"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_rgbtobw_out[] = { - {SOCK_FLOAT, N_("Val"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_rgbtobw_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.8f, 0.8f, 0.8f, 1.0f}); + b.add_output<decl::Color>("Val"); +} + +} // namespace blender::nodes void register_node_type_cmp_rgbtobw(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_RGBTOBW, "RGB to BW", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_rgbtobw_in, cmp_node_rgbtobw_out); + ntype.declare = blender::nodes::cmp_node_rgbtobw_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_value.c b/source/blender/nodes/composite/nodes/node_composite_value.cc index 2ede2cb8c83..5459801bcc7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_value.c +++ b/source/blender/nodes/composite/nodes/node_composite_value.cc @@ -21,20 +21,25 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** VALUE ******************** */ -static bNodeSocketTemplate cmp_node_value_out[] = { - {SOCK_FLOAT, N_("Value"), 0.5f, 0, 0, 0, -FLT_MAX, FLT_MAX, PROP_NONE}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_value_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Value").default_value(0.5f); +} + +} // namespace blender::nodes void register_node_type_cmp_value(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VALUE, "Value", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_value_out); + ntype.declare = blender::nodes::cmp_node_value_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_vecBlur.c b/source/blender/nodes/composite/nodes/node_composite_vecBlur.cc index 7005ea42afe..ce6ba659609 100644 --- a/source/blender/nodes/composite/nodes/node_composite_vecBlur.c +++ b/source/blender/nodes/composite/nodes/node_composite_vecBlur.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** VECTOR BLUR ******************** */ static bNodeSocketTemplate cmp_node_vecblur_in[] = { @@ -33,13 +33,13 @@ static bNodeSocketTemplate cmp_node_vecblur_out[] = {{SOCK_RGBA, N_("Image")}, { static void node_composit_init_vecblur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBlurData *nbd = MEM_callocN(sizeof(NodeBlurData), "node blur data"); + NodeBlurData *nbd = (NodeBlurData *)MEM_callocN(sizeof(NodeBlurData), "node blur data"); node->storage = nbd; nbd->samples = 32; nbd->fac = 1.0f; } -/* custom1: iterations, custom2: maxspeed (0 = nolimit) */ +/* custom1: iterations, custom2: max_speed (0 = no_limit). */ void register_node_type_cmp_vecblur(void) { static bNodeType ntype; diff --git a/source/blender/nodes/composite/nodes/node_composite_viewer.c b/source/blender/nodes/composite/nodes/node_composite_viewer.cc index b5f74d5c937..7234d4d8eb2 100644 --- a/source/blender/nodes/composite/nodes/node_composite_viewer.c +++ b/source/blender/nodes/composite/nodes/node_composite_viewer.cc @@ -21,21 +21,27 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_global.h" #include "BKE_image.h" /* **************** VIEWER ******************** */ -static bNodeSocketTemplate cmp_node_viewer_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_FLOAT, N_("Alpha"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Z"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}}; + +namespace blender::nodes { + +static void cmp_node_viewer_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_input<decl::Float>("Alpha").default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(1.0f); +} + +} // namespace blender::nodes static void node_composit_init_viewer(bNodeTree *UNUSED(ntree), bNode *node) { - ImageUser *iuser = MEM_callocN(sizeof(ImageUser), "node image user"); + ImageUser *iuser = (ImageUser *)MEM_callocN(sizeof(ImageUser), "node image user"); node->storage = iuser; iuser->sfra = 1; iuser->ok = 1; @@ -50,11 +56,11 @@ void register_node_type_cmp_viewer(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VIEWER, "Viewer", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_viewer_in, NULL); + ntype.declare = blender::nodes::cmp_node_viewer_declare; node_type_init(&ntype, node_composit_init_viewer); node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage); - node_type_internal_links(&ntype, NULL); + node_type_internal_links(&ntype, nullptr); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_zcombine.c b/source/blender/nodes/composite/nodes/node_composite_zcombine.cc index 5041b16c303..79e4d449159 100644 --- a/source/blender/nodes/composite/nodes/node_composite_zcombine.c +++ b/source/blender/nodes/composite/nodes/node_composite_zcombine.cc @@ -21,29 +21,31 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Z COMBINE ******************** */ -/* lazy coder NOTE: node->custom2 is abused to send signal. */ -static bNodeSocketTemplate cmp_node_zcombine_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Z"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 10000.0f, PROP_NONE}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Z"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 10000.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_zcombine_out[] = { - {SOCK_RGBA, N_("Image")}, - {SOCK_FLOAT, N_("Z")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_zcombine_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(10000.0f); + b.add_input<decl::Color>("Image", "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Z", "Z_001").default_value(1.0f).min(0.0f).max(10000.0f); + b.add_output<decl::Color>("Image"); + b.add_output<decl::Float>("Z"); +} + +} // namespace blender::nodes + +/* lazy coder NOTE: node->custom2 is abused to send signal. */ void register_node_type_cmp_zcombine(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_ZCOMBINE, "Z Combine", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_zcombine_in, cmp_node_zcombine_out); + ntype.declare = blender::nodes::cmp_node_zcombine_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/function/nodes/node_fn_random_float.cc b/source/blender/nodes/function/nodes/legacy/node_fn_random_float.cc index 1bd39aacdca..7f6f554ba93 100644 --- a/source/blender/nodes/function/nodes/node_fn_random_float.cc +++ b/source/blender/nodes/function/nodes/legacy/node_fn_random_float.cc @@ -20,8 +20,9 @@ namespace blender::nodes { -static void fn_node_random_float_declare(NodeDeclarationBuilder &b) +static void fn_node_legacy_random_float_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Min").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f); b.add_input<decl::Int>("Seed").min(-10000).max(10000); @@ -67,19 +68,19 @@ class RandomFloatFunction : public blender::fn::MultiFunction { } }; -static void fn_node_random_float_build_multi_function( +static void fn_node_legacy_random_float_build_multi_function( blender::nodes::NodeMultiFunctionBuilder &builder) { static RandomFloatFunction fn; builder.set_matching_fn(fn); } -void register_node_type_fn_random_float() +void register_node_type_fn_legacy_random_float() { static bNodeType ntype; - fn_node_type_base(&ntype, FN_NODE_RANDOM_FLOAT, "Random Float", 0, 0); - ntype.declare = blender::nodes::fn_node_random_float_declare; - ntype.build_multi_function = fn_node_random_float_build_multi_function; + fn_node_type_base(&ntype, FN_NODE_LEGACY_RANDOM_FLOAT, "Random Float", 0, 0); + ntype.declare = blender::nodes::fn_node_legacy_random_float_declare; + ntype.build_multi_function = fn_node_legacy_random_float_build_multi_function; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc index b71ee092de6..d10490bb2ee 100644 --- a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc +++ b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc @@ -28,6 +28,7 @@ namespace blender::nodes { static void fn_node_boolean_math_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Bool>("Boolean", "Boolean"); b.add_input<decl::Bool>("Boolean", "Boolean_001"); b.add_output<decl::Bool>("Boolean"); diff --git a/source/blender/nodes/function/nodes/node_fn_float_compare.cc b/source/blender/nodes/function/nodes/node_fn_float_compare.cc index 4f4830afabc..9736c52e895 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_compare.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_compare.cc @@ -30,6 +30,7 @@ namespace blender::nodes { static void fn_node_float_compare_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("A").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("B").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Epsilon").default_value(0.001f).min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc index e59c78d2c04..8bb5dafff8a 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc @@ -29,6 +29,7 @@ namespace blender::nodes { static void fn_node_float_to_int_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Float"); b.add_output<decl::Int>("Integer"); }; diff --git a/source/blender/nodes/function/nodes/node_fn_input_special_characters.cc b/source/blender/nodes/function/nodes/node_fn_input_special_characters.cc new file mode 100644 index 00000000000..11c64d3f694 --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_input_special_characters.cc @@ -0,0 +1,74 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_function_util.hh" + +namespace blender::nodes { + +static void fn_node_input_special_characters_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::String>("Line Break"); + b.add_output<decl::String>("Tab"); +}; + +class MF_SpecialCharacters : public fn::MultiFunction { + public: + MF_SpecialCharacters() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Special Characters"}; + signature.single_output<std::string>("Line Break"); + signature.single_output<std::string>("Tab"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + MutableSpan<std::string> lb = params.uninitialized_single_output<std::string>(0, "Line Break"); + MutableSpan<std::string> tab = params.uninitialized_single_output<std::string>(1, "Tab"); + + for (const int i : mask) { + new (&lb[i]) std::string("\n"); + new (&tab[i]) std::string("\t"); + } + } +}; + +static void fn_node_input_special_characters_build_multi_function( + NodeMultiFunctionBuilder &builder) +{ + static MF_SpecialCharacters special_characters_fn; + builder.set_matching_fn(special_characters_fn); +} + +} // namespace blender::nodes + +void register_node_type_fn_input_special_characters() +{ + static bNodeType ntype; + + fn_node_type_base( + &ntype, FN_NODE_INPUT_SPECIAL_CHARACTERS, "Special Characters", NODE_CLASS_INPUT, 0); + ntype.declare = blender::nodes::fn_node_input_special_characters_declare; + ntype.build_multi_function = + blender::nodes::fn_node_input_special_characters_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_input_string.cc b/source/blender/nodes/function/nodes/node_fn_input_string.cc index 4a8e898fb9b..704ae9d900c 100644 --- a/source/blender/nodes/function/nodes/node_fn_input_string.cc +++ b/source/blender/nodes/function/nodes/node_fn_input_string.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void fn_node_input_string_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_output<decl::String>("String"); }; diff --git a/source/blender/nodes/function/nodes/node_fn_random_value.cc b/source/blender/nodes/function/nodes/node_fn_random_value.cc new file mode 100644 index 00000000000..53ca77aab0c --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_random_value.cc @@ -0,0 +1,299 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// #include "BLI_hash.h" +#include "BLI_noise.hh" + +#include "node_function_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes { + +static void fn_node_random_value_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Vector>("Min").supports_field(); + b.add_input<decl::Vector>("Max").default_value({1.0f, 1.0f, 1.0f}).supports_field(); + b.add_input<decl::Float>("Min", "Min_001").supports_field(); + b.add_input<decl::Float>("Max", "Max_001").default_value(1.0f).supports_field(); + b.add_input<decl::Int>("Min", "Min_002").min(-100000).max(100000).supports_field(); + b.add_input<decl::Int>("Max", "Max_002") + .default_value(100) + .min(-100000) + .max(100000) + .supports_field(); + b.add_input<decl::Float>("Probability") + .min(0.0f) + .max(1.0f) + .default_value(0.5f) + .subtype(PROP_FACTOR) + .supports_field(); + b.add_input<decl::Int>("ID").implicit_field(); + b.add_input<decl::Int>("Seed").default_value(0).min(-10000).max(10000).supports_field(); + + b.add_output<decl::Vector>("Value").dependent_field(); + b.add_output<decl::Float>("Value", "Value_001").dependent_field(); + b.add_output<decl::Int>("Value", "Value_002").dependent_field(); + b.add_output<decl::Bool>("Value", "Value_003").dependent_field(); +} + +static void fn_node_random_value_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); +} + +static void fn_node_random_value_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeRandomValue *data = (NodeRandomValue *)MEM_callocN(sizeof(NodeRandomValue), __func__); + data->data_type = CD_PROP_FLOAT; + node->storage = data; +} + +static void fn_node_random_value_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeRandomValue &storage = *(const NodeRandomValue *)node->storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + bNodeSocket *sock_min_vector = (bNodeSocket *)node->inputs.first; + bNodeSocket *sock_max_vector = sock_min_vector->next; + bNodeSocket *sock_min_float = sock_max_vector->next; + bNodeSocket *sock_max_float = sock_min_float->next; + bNodeSocket *sock_min_int = sock_max_float->next; + bNodeSocket *sock_max_int = sock_min_int->next; + bNodeSocket *sock_probability = sock_max_int->next; + + bNodeSocket *sock_out_vector = (bNodeSocket *)node->outputs.first; + bNodeSocket *sock_out_float = sock_out_vector->next; + bNodeSocket *sock_out_int = sock_out_float->next; + bNodeSocket *sock_out_bool = sock_out_int->next; + + nodeSetSocketAvailability(sock_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_min_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_max_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_probability, data_type == CD_PROP_BOOL); + + nodeSetSocketAvailability(sock_out_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_out_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_out_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_out_bool, data_type == CD_PROP_BOOL); +} + +class RandomVectorFunction : public fn::MultiFunction { + public: + RandomVectorFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<float3>("Min"); + signature.single_input<float3>("Max"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<float3>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &min_values = params.readonly_single_input<float3>(0, "Min"); + const VArray<float3> &max_values = params.readonly_single_input<float3>(1, "Max"); + const VArray<int> &ids = params.readonly_single_input<int>(2, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed"); + MutableSpan<float3> values = params.uninitialized_single_output<float3>(4, "Value"); + + for (int64_t i : mask) { + const float3 min_value = min_values[i]; + const float3 max_value = max_values[i]; + const int seed = seeds[i]; + const int id = ids[i]; + + const float x = noise::hash_to_float(seed, id, 0); + const float y = noise::hash_to_float(seed, id, 1); + const float z = noise::hash_to_float(seed, id, 2); + + values[i] = float3(x, y, z) * (max_value - min_value) + min_value; + } + } +}; + +class RandomFloatFunction : public fn::MultiFunction { + public: + RandomFloatFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<float>("Min"); + signature.single_input<float>("Max"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<float>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float> &min_values = params.readonly_single_input<float>(0, "Min"); + const VArray<float> &max_values = params.readonly_single_input<float>(1, "Max"); + const VArray<int> &ids = params.readonly_single_input<int>(2, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed"); + MutableSpan<float> values = params.uninitialized_single_output<float>(4, "Value"); + + for (int64_t i : mask) { + const float min_value = min_values[i]; + const float max_value = max_values[i]; + const int seed = seeds[i]; + const int id = ids[i]; + + const float value = noise::hash_to_float(seed, id); + values[i] = value * (max_value - min_value) + min_value; + } + } +}; + +class RandomIntFunction : public fn::MultiFunction { + public: + RandomIntFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<int>("Min"); + signature.single_input<int>("Max"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<int>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<int> &min_values = params.readonly_single_input<int>(0, "Min"); + const VArray<int> &max_values = params.readonly_single_input<int>(1, "Max"); + const VArray<int> &ids = params.readonly_single_input<int>(2, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed"); + MutableSpan<int> values = params.uninitialized_single_output<int>(4, "Value"); + + for (int64_t i : mask) { + const float min_value = min_values[i]; + const float max_value = max_values[i]; + const int seed = seeds[i]; + const int id = ids[i]; + + const float value = noise::hash_to_float(id, seed); + values[i] = round_fl_to_int(value * (max_value - min_value) + min_value); + } + } +}; + +class RandomBoolFunction : public fn::MultiFunction { + public: + RandomBoolFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<float>("Probability"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<bool>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float> &probabilities = params.readonly_single_input<float>(0, "Probability"); + const VArray<int> &ids = params.readonly_single_input<int>(1, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(2, "Seed"); + MutableSpan<bool> values = params.uninitialized_single_output<bool>(3, "Value"); + + for (int64_t i : mask) { + const int seed = seeds[i]; + const int id = ids[i]; + const float probability = probabilities[i]; + values[i] = noise::hash_to_float(id, seed) <= probability; + } + } +}; + +static void fn_node_random_value_build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const NodeRandomValue &storage = *(const NodeRandomValue *)builder.node().storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + switch (data_type) { + case CD_PROP_FLOAT3: { + static RandomVectorFunction fn; + builder.set_matching_fn(fn); + break; + } + case CD_PROP_FLOAT: { + static RandomFloatFunction fn; + builder.set_matching_fn(fn); + break; + } + case CD_PROP_INT32: { + static RandomIntFunction fn; + builder.set_matching_fn(fn); + break; + } + case CD_PROP_BOOL: { + static RandomBoolFunction fn; + builder.set_matching_fn(fn); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } +} + +} // namespace blender::nodes + +void register_node_type_fn_random_value() +{ + static bNodeType ntype; + fn_node_type_base(&ntype, FN_NODE_RANDOM_VALUE, "Random Value", NODE_CLASS_CONVERTER, 0); + node_type_init(&ntype, blender::nodes::fn_node_random_value_init); + node_type_update(&ntype, blender::nodes::fn_node_random_value_update); + ntype.draw_buttons = blender::nodes::fn_node_random_value_layout; + ntype.declare = blender::nodes::fn_node_random_value_declare; + ntype.build_multi_function = blender::nodes::fn_node_random_value_build_multi_function; + node_type_storage( + &ntype, "NodeRandomValue", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_rotate_euler.cc b/source/blender/nodes/function/nodes/node_fn_rotate_euler.cc new file mode 100644 index 00000000000..cbae1648663 --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_rotate_euler.cc @@ -0,0 +1,138 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "RNA_enum_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_function_util.hh" + +namespace blender::nodes { +static void fn_node_rotate_euler_declare(NodeDeclarationBuilder &b) +{ + b.is_function_node(); + b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER).hide_value(); + b.add_input<decl::Vector>("Rotate By").subtype(PROP_EULER); + b.add_input<decl::Vector>("Axis").default_value({0.0, 0.0, 1.0}).subtype(PROP_XYZ); + b.add_input<decl::Float>("Angle").subtype(PROP_ANGLE); + b.add_output<decl::Vector>("Rotation"); +}; + +static void fn_node_rotate_euler_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *rotate_by_socket = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 1)); + bNodeSocket *axis_socket = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 2)); + bNodeSocket *angle_socket = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 3)); + + nodeSetSocketAvailability(rotate_by_socket, + ELEM(node->custom1, FN_NODE_ROTATE_EULER_TYPE_EULER)); + nodeSetSocketAvailability(axis_socket, + ELEM(node->custom1, FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE)); + nodeSetSocketAvailability(angle_socket, + ELEM(node->custom1, FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE)); +} + +static void fn_node_rotate_euler_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "type", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "space", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static const fn::MultiFunction *get_multi_function(bNode &bnode) +{ + static fn::CustomMF_SI_SI_SO<float3, float3, float3> obj_euler_rot{ + "Rotate Euler by Euler/Object", [](const float3 &input, const float3 &rotation) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + eul_to_mat3(rot_mat, rotation); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, rot_mat, input_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, float3> obj_AA_rot{ + "Rotate Euler by AxisAngle/Object", + [](const float3 &input, const float3 &axis, float angle) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + axis_angle_to_mat3(rot_mat, axis, angle); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, rot_mat, input_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + static fn::CustomMF_SI_SI_SO<float3, float3, float3> point_euler_rot{ + "Rotate Euler by Euler/Point", [](const float3 &input, const float3 &rotation) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + eul_to_mat3(rot_mat, rotation); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, input_mat, rot_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, float3> point_AA_rot{ + "Rotate Euler by AxisAngle/Point", [](const float3 &input, const float3 &axis, float angle) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + axis_angle_to_mat3(rot_mat, axis, angle); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, input_mat, rot_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + short type = bnode.custom1; + short space = bnode.custom2; + if (type == FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE) { + return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ? &obj_AA_rot : &point_AA_rot; + } + if (type == FN_NODE_ROTATE_EULER_TYPE_EULER) { + return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ? &obj_euler_rot : &point_euler_rot; + } + BLI_assert_unreachable(); + return nullptr; +} + +static void fn_node_rotate_euler_build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const fn::MultiFunction *fn = get_multi_function(builder.node()); + builder.set_matching_fn(fn); +} + +} // namespace blender::nodes + +void register_node_type_fn_rotate_euler() +{ + static bNodeType ntype; + fn_node_type_base(&ntype, FN_NODE_ROTATE_EULER, "Rotate Euler", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_rotate_euler_declare; + ntype.draw_buttons = blender::nodes::fn_node_rotate_euler_layout; + node_type_update(&ntype, blender::nodes::fn_node_rotate_euler_update); + ntype.build_multi_function = blender::nodes::fn_node_rotate_euler_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_string_length.cc b/source/blender/nodes/function/nodes/node_fn_string_length.cc index a0f85dfd2bf..89038629c3c 100644 --- a/source/blender/nodes/function/nodes/node_fn_string_length.cc +++ b/source/blender/nodes/function/nodes/node_fn_string_length.cc @@ -24,6 +24,7 @@ namespace blender::nodes { static void fn_node_string_length_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::String>("String"); b.add_output<decl::Int>("Length"); }; diff --git a/source/blender/nodes/function/nodes/node_fn_string_substring.cc b/source/blender/nodes/function/nodes/node_fn_string_substring.cc index 55a01093ae9..b91171923d6 100644 --- a/source/blender/nodes/function/nodes/node_fn_string_substring.cc +++ b/source/blender/nodes/function/nodes/node_fn_string_substring.cc @@ -22,6 +22,7 @@ namespace blender::nodes { static void fn_node_string_substring_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::String>("String"); b.add_input<decl::Int>("Position"); b.add_input<decl::Int>("Length").min(0); diff --git a/source/blender/nodes/function/nodes/node_fn_value_to_string.cc b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc index c1e6373cb6d..56206af2eb2 100644 --- a/source/blender/nodes/function/nodes/node_fn_value_to_string.cc +++ b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc @@ -21,6 +21,7 @@ namespace blender::nodes { static void fn_node_value_to_string_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value"); b.add_input<decl::Int>("Decimals").min(0); b.add_output<decl::String>("String"); diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index d6b23c38ee4..20b610a4db9 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -119,7 +119,7 @@ void register_node_tree_type_geo(void) tt->type = NTREE_GEOMETRY; strcpy(tt->idname, "GeometryNodeTree"); strcpy(tt->ui_name, N_("Geometry Node Editor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Geometry nodes")); tt->rna_ext.srna = &RNA_GeometryNodeTree; tt->update = geometry_node_tree_update; diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 015ac0de002..5896b5bd6cc 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -65,7 +65,9 @@ Mesh *create_grid_mesh(const int verts_x, Mesh *create_cylinder_or_cone_mesh(const float radius_top, const float radius_bottom, const float depth, - const int verts_num, + const int circle_segments, + const int side_segments, + const int fill_segments, const GeometryNodeMeshCircleFillType fill_type); Mesh *create_cuboid_mesh(float3 size, int verts_x, int verts_y, int verts_z); diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc index d0bb906e8af..d0bb906e8af 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_clamp.cc index 97070184609..2e931a2da98 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_clamp.cc @@ -269,7 +269,7 @@ void register_node_type_geo_attribute_clamp() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_clamp_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_clamp_update); ntype.declare = blender::nodes::geo_node_attribute_clamp_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_color_ramp.cc index aa054af3acd..aa054af3acd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_color_ramp.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc index 569d5a824ca..569d5a824ca 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_compare.cc index 0b9708dae14..0b9708dae14 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_compare.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_convert.cc index a2382aa9d25..a2382aa9d25 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_convert.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_curve_map.cc index b9621b4ae92..b9621b4ae92 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_curve_map.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_fill.cc index 3c50ae5c837..3c50ae5c837 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_fill.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_map_range.cc index 0ea3bbe1e45..0ea3bbe1e45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_map_range.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_math.cc index efa09215b45..efa09215b45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_math.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_mix.cc index 74e05cb997d..74e05cb997d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_mix.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_proximity.cc index 0cf411343cf..6120118f611 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_proximity.cc @@ -232,7 +232,7 @@ static void geo_node_attribute_proximity_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_attribute_proximity() +void register_node_type_geo_legacy_attribute_proximity() { static bNodeType ntype; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_randomize.cc index 60b9910399c..2e6ba456725 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_randomize.cc @@ -25,7 +25,7 @@ namespace blender::nodes { -static void geo_node_attribute_randomize_declare(NodeDeclarationBuilder &b) +static void geo_node_legacy_attribute_randomize_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); b.add_input<decl::String>("Attribute"); @@ -39,15 +39,15 @@ static void geo_node_attribute_randomize_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Geometry"); } -static void geo_node_attribute_random_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void geo_node_legacy_attribute_random_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) { uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); } -static void geo_node_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *node) +static void geo_node_legacy_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *node) { NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN( sizeof(NodeAttributeRandomize), __func__); @@ -57,7 +57,7 @@ static void geo_node_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *no node->storage = data; } -static void geo_node_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) +static void geo_node_legacy_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) { bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); bNodeSocket *sock_max_vector = sock_min_vector->next; @@ -280,7 +280,7 @@ static void randomize_attribute_on_component(GeometryComponent &component, attribute.save(); } -static void geo_node_random_attribute_exec(GeoNodeExecParams params) +static void geo_node_legacy_random_attribute_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); const std::string attribute_name = params.get_input<std::string>("Attribute"); @@ -326,18 +326,18 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_attribute_randomize() +void register_node_type_geo_legacy_attribute_randomize() { static bNodeType ntype; geo_node_type_base( &ntype, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_node_attribute_randomize_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_randomize_update); + node_type_init(&ntype, blender::nodes::geo_node_legacy_attribute_randomize_init); + node_type_update(&ntype, blender::nodes::geo_node_legacy_attribute_randomize_update); - ntype.declare = blender::nodes::geo_node_attribute_randomize_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_random_layout; + ntype.declare = blender::nodes::geo_node_legacy_attribute_randomize_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_random_attribute_exec; + ntype.draw_buttons = blender::nodes::geo_node_legacy_attribute_random_layout; node_type_storage( &ntype, "NodeAttributeRandomize", node_free_standard_storage, node_copy_standard_storage); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_sample_texture.cc index 52f97475941..52f97475941 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_sample_texture.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc index de0090406c6..de0090406c6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc index 874350cd714..f187ee39b94 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc @@ -404,7 +404,7 @@ static void transfer_attribute_nearest(const GeometrySet &src_geometry, data_type); for (const int i : IndexRange(tot_samples)) { if (pointcloud_distances_sq[i] < mesh_distances_sq[i]) { - /* Pointcloud point is closer. */ + /* Point-cloud point is closer. */ const int index = pointcloud_indices[i]; pointcloud_src_attribute.varray->get(index, buffer); dst_attribute->set_by_relocate(i, buffer); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_math.cc index 59903050f88..59903050f88 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_math.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc index adaa4de3029..0c515fa63fb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc @@ -332,7 +332,7 @@ void register_node_type_geo_attribute_vector_rotate() static bNodeType ntype; geo_node_type_base(&ntype, - GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, + GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE, "Attribute Vector Rotate", NODE_CLASS_ATTRIBUTE, 0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_endpoints.cc index 7853c5aa04a..65d22eca39c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_endpoints.cc @@ -212,7 +212,8 @@ void register_node_type_geo_curve_endpoints() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_ENDPOINTS, "Curve Endpoints", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_ENDPOINTS, "Curve Endpoints", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_endpoints_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_endpoints_exec; diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc new file mode 100644 index 00000000000..d1c81333c30 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc @@ -0,0 +1,71 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Curve"); +} + +static void geo_node_curve_reverse_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + /* Retrieve data for write access so we can avoid new allocations for the reversed data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + const std::string selection_name = params.extract_input<std::string>("Selection"); + GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( + selection_name, ATTR_DOMAIN_CURVE, true); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + if (selection[i]) { + splines[i]->reverse(); + } + } + }); + + params.set_output("Curve", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_reverse() +{ + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_reverse_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc index dfcae2e65b0..dfcae2e65b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc new file mode 100644 index 00000000000..339029336d9 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc @@ -0,0 +1,144 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_set_handles_decalre(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Curve"); +} + +static void geo_node_curve_set_handles_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "handle_type", 0, "", ICON_NONE); +} + +static void geo_node_curve_set_handles_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSetHandles *data = (NodeGeometryCurveSetHandles *)MEM_callocN( + sizeof(NodeGeometryCurveSetHandles), __func__); + + data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; + data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; + node->storage = data; +} + +static BezierSpline::HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type) +{ + switch (type) { + case GEO_NODE_CURVE_HANDLE_AUTO: + return BezierSpline::HandleType::Auto; + case GEO_NODE_CURVE_HANDLE_ALIGN: + return BezierSpline::HandleType::Align; + case GEO_NODE_CURVE_HANDLE_FREE: + return BezierSpline::HandleType::Free; + case GEO_NODE_CURVE_HANDLE_VECTOR: + return BezierSpline::HandleType::Vector; + } + BLI_assert_unreachable(); + return BezierSpline::HandleType::Auto; +} + +static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSetHandles *node_storage = + (NodeGeometryCurveSetHandles *)params.node().storage; + const GeometryNodeCurveHandleType type = (GeometryNodeCurveHandleType)node_storage->handle_type; + const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)node_storage->mode; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + /* Retrieve data for write access so we can avoid new allocations for the handles data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + const std::string selection_name = params.extract_input<std::string>("Selection"); + GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( + selection_name, ATTR_DOMAIN_POINT, true); + + const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); + int point_index = 0; + bool has_bezier_spline = false; + for (SplinePtr &spline : splines) { + if (spline->type() != Spline::Type::Bezier) { + point_index += spline->positions().size(); + continue; + } + + BezierSpline &bezier_spline = static_cast<BezierSpline &>(*spline); + if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) { + /* In this case the automatically calculated handle types need to be "baked", because + * they're possibly changing from a type that is calculated automatically to a type that + * is positioned manually. */ + bezier_spline.ensure_auto_handles(); + } + has_bezier_spline = true; + for (int i_point : IndexRange(bezier_spline.size())) { + if (selection[point_index]) { + if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { + bezier_spline.handle_types_left()[i_point] = new_handle_type; + } + if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) { + bezier_spline.handle_types_right()[i_point] = new_handle_type; + } + } + point_index++; + } + bezier_spline.mark_cache_invalid(); + } + + if (!has_bezier_spline) { + params.error_message_add(NodeWarningType::Info, TIP_("No Bezier splines in input curve")); + } + + params.set_output("Curve", geometry_set); +} +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_set_handles() +{ + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_set_handles_decalre; + ntype.geometry_node_execute = blender::nodes::geo_node_curve_set_handles_exec; + node_type_init(&ntype, blender::nodes::geo_node_curve_set_handles_init); + node_type_storage(&ntype, + "NodeGeometryCurveSetHandles", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_curve_set_handles_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc new file mode 100644 index 00000000000..44522e990d9 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc @@ -0,0 +1,302 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_spline.hh" + +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_legacy_curve_spline_type_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Curve"); +} + +static void geo_node_legacy_curve_spline_type_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "spline_type", 0, "", ICON_NONE); +} + +static void geo_node_legacy_curve_spline_type_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSplineType *data = (NodeGeometryCurveSplineType *)MEM_callocN( + sizeof(NodeGeometryCurveSplineType), __func__); + + data->spline_type = GEO_NODE_SPLINE_TYPE_POLY; + node->storage = data; +} + +template<class T> +static void scale_input_assign(const Span<T> input, + const int scale, + const int offset, + const MutableSpan<T> r_output) +{ + for (const int i : IndexRange(r_output.size())) { + r_output[i] = input[i * scale + offset]; + } +} + +template<class T> +static void scale_output_assign(const Span<T> input, + const int scale, + const int offset, + const MutableSpan<T> &r_output) +{ + for (const int i : IndexRange(input.size())) { + r_output[i * scale + offset] = input[i]; + } +} + +template<typename CopyFn> +static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn) +{ + input_spline.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id); + BLI_assert(src); + if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) { + BLI_assert_unreachable(); + return false; + } + std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id); + if (!dst) { + BLI_assert_unreachable(); + return false; + } + + copy_fn(*src, *dst); + + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr convert_to_poly_spline(const Spline &input) +{ + std::unique_ptr<PolySpline> output = std::make_unique<PolySpline>(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr poly_to_nurbs(const Spline &input) +{ + std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + Spline::copy_base_settings(input, *output); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes = input.attributes; + return output; +} + +static SplinePtr bezier_to_nurbs(const Spline &input) +{ + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(input); + std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>(); + output->resize(input.size() * 3); + + scale_output_assign(bezier_spline.handle_positions_left(), 3, 0, output->positions()); + scale_output_assign(input.radii(), 3, 0, output->radii()); + scale_output_assign(input.tilts(), 3, 0, output->tilts()); + + scale_output_assign(bezier_spline.positions(), 3, 1, output->positions()); + scale_output_assign(input.radii(), 3, 1, output->radii()); + scale_output_assign(input.tilts(), 3, 1, output->tilts()); + + scale_output_assign(bezier_spline.handle_positions_right(), 3, 2, output->positions()); + scale_output_assign(input.radii(), 3, 2, output->radii()); + scale_output_assign(input.tilts(), 3, 2, output->tilts()); + + Spline::copy_base_settings(input, *output); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + output->set_cyclic(input.is_cyclic()); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes.reallocate(output->size()); + copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + scale_output_assign<T>(src.typed<T>(), 3, 0, dst.typed<T>()); + scale_output_assign<T>(src.typed<T>(), 3, 1, dst.typed<T>()); + scale_output_assign<T>(src.typed<T>(), 3, 2, dst.typed<T>()); + }); + }); + return output; +} + +static SplinePtr poly_to_bezier(const Spline &input) +{ + std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>(); + output->resize(input.size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->handle_types_left().fill(BezierSpline::HandleType::Vector); + output->handle_types_right().fill(BezierSpline::HandleType::Vector); + output->set_resolution(12); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr nurbs_to_bezier(const Spline &input) +{ + const NURBSpline &nurbs_spline = static_cast<const NURBSpline &>(input); + std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>(); + output->resize(input.size() / 3); + scale_input_assign<float3>(input.positions(), 3, 1, output->positions()); + scale_input_assign<float3>(input.positions(), 3, 0, output->handle_positions_left()); + scale_input_assign<float3>(input.positions(), 3, 2, output->handle_positions_right()); + scale_input_assign<float>(input.radii(), 3, 2, output->radii()); + scale_input_assign<float>(input.tilts(), 3, 2, output->tilts()); + output->handle_types_left().fill(BezierSpline::HandleType::Align); + output->handle_types_right().fill(BezierSpline::HandleType::Align); + output->set_resolution(nurbs_spline.resolution()); + Spline::copy_base_settings(input, *output); + output->attributes.reallocate(output->size()); + copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + scale_input_assign<T>(src.typed<T>(), 3, 1, dst.typed<T>()); + }); + }); + return output; +} + +static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params) +{ + switch (input.type()) { + case Spline::Type::Bezier: + return input.copy(); + case Spline::Type::Poly: + return poly_to_bezier(input); + case Spline::Type::NURBS: + if (input.size() < 6) { + params.error_message_add( + NodeWarningType::Info, + TIP_("NURBS must have minimum of 6 points for Bezier Conversion")); + return input.copy(); + } + else { + if (input.size() % 3 != 0) { + params.error_message_add(NodeWarningType::Info, + TIP_("NURBS must have multiples of 3 points for full Bezier " + "conversion, curve truncated")); + } + return nurbs_to_bezier(input); + } + } + BLI_assert_unreachable(); + return {}; +} + +static SplinePtr convert_to_nurbs(const Spline &input) +{ + switch (input.type()) { + case Spline::Type::NURBS: + return input.copy(); + case Spline::Type::Bezier: + return bezier_to_nurbs(input); + case Spline::Type::Poly: + return poly_to_nurbs(input); + } + BLI_assert_unreachable(); + return {}; +} + +static void geo_node_legacy_curve_spline_type_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSplineType *storage = + (const NodeGeometryCurveSplineType *)params.node().storage; + const GeometryNodeSplineType output_type = (const GeometryNodeSplineType)storage->spline_type; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + const CurveEval &curve = *curve_component->get_for_read(); + + const std::string selection_name = params.extract_input<std::string>("Selection"); + GVArray_Typed<bool> selection = curve_component->attribute_get_for_read( + selection_name, ATTR_DOMAIN_CURVE, true); + + std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); + for (const int i : curve.splines().index_range()) { + if (selection[i]) { + switch (output_type) { + case GEO_NODE_SPLINE_TYPE_POLY: + new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i])); + break; + case GEO_NODE_SPLINE_TYPE_BEZIER: + new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params)); + break; + case GEO_NODE_SPLINE_TYPE_NURBS: + new_curve->add_spline(convert_to_nurbs(*curve.splines()[i])); + break; + } + } + else { + new_curve->add_spline(curve.splines()[i]->copy()); + } + } + + new_curve->attributes = curve.attributes; + params.set_output("Curve", GeometrySet::create_with_curve(new_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_spline_type() +{ + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_legacy_curve_spline_type_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_curve_spline_type_exec; + node_type_init(&ntype, blender::nodes::geo_node_legacy_curve_spline_type_init); + node_type_storage(&ntype, + "NodeGeometryCurveSplineType", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_legacy_curve_spline_type_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc new file mode 100644 index 00000000000..f32a68bc042 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc @@ -0,0 +1,392 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_For_Span; +using blender::fn::GVArray_Typed; + +namespace blender::nodes { + +static void geo_node_curve_subdivide_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::String>("Cuts"); + b.add_input<decl::Int>("Cuts", "Cuts_001").default_value(1).min(0).max(1000); + b.add_output<decl::Geometry>("Geometry"); +} + +static void geo_node_curve_subdivide_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "cuts_type", 0, IFACE_("Cuts"), ICON_NONE); +} + +static void geo_node_curve_subdivide_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSubdivide *data = (NodeGeometryCurveSubdivide *)MEM_callocN( + sizeof(NodeGeometryCurveSubdivide), __func__); + + data->cuts_type = GEO_NODE_ATTRIBUTE_INPUT_INTEGER; + node->storage = data; +} + +static void geo_node_curve_subdivide_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; + + update_attribute_input_socket_availabilities( + *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); +} + +static Array<int> get_subdivided_offsets(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + Array<int> offsets(spline.segments_size() + 1); + int offset = 0; + for (const int i : IndexRange(spline.segments_size())) { + offsets[i] = offset; + offset = offset + std::max(cuts[spline_offset + i], 0) + 1; + } + offsets.last() = offset; + return offsets; +} + +template<typename T> +static void subdivide_attribute(Span<T> src, + const Span<int> offsets, + const bool is_cyclic, + MutableSpan<T> dst) +{ + const int src_size = src.size(); + threading::parallel_for(IndexRange(src_size - 1), 1024, [&](IndexRange range) { + for (const int i : range) { + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src[i]; + const float factor_delta = 1.0f / (cuts + 1.0f); + for (const int cut : IndexRange(cuts)) { + const float factor = (cut + 1) * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src[i], src[i + 1]); + } + } + }); + + if (is_cyclic) { + const int i = src_size - 1; + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src.last(); + const float factor_delta = 1.0f / (cuts + 1.0f); + for (const int cut : IndexRange(cuts)) { + const float factor = (cut + 1) * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src.last(), src.first()); + } + } + else { + dst.last() = src.last(); + } +} + +/** + * In order to generate a Bezier spline with the same shape as the input spline, apply the + * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the + * previous result point's right handle and the left handle at the end of the segment. + * + * \note Non-vector segments in the result spline are given free handles. This could possibly be + * improved with another pass that sets handles to aligned where possible, but currently that does + * not provide much benefit for the increased complexity. + */ +static void subdivide_bezier_segment(const BezierSpline &src, + const int index, + const int offset, + const int result_size, + Span<float3> src_positions, + Span<float3> src_handles_left, + Span<float3> src_handles_right, + MutableSpan<float3> dst_positions, + MutableSpan<float3> dst_handles_left, + MutableSpan<float3> dst_handles_right, + MutableSpan<BezierSpline::HandleType> dst_type_left, + MutableSpan<BezierSpline::HandleType> dst_type_right) +{ + const bool is_last_cyclic_segment = index == (src.size() - 1); + const int next_index = is_last_cyclic_segment ? 0 : index + 1; + + /* The first point in the segment is always copied. */ + dst_positions[offset] = src_positions[index]; + + if (src.segment_is_vector(index)) { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Vector; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Vector); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Vector); + + const float factor_delta = 1.0f / result_size; + for (const int cut : IndexRange(result_size)) { + const float factor = cut * factor_delta; + dst_positions[offset + cut] = attribute_math::mix2( + factor, src_positions[index], src_positions[next_index]); + } + } + else { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Free; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Free); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Free); + + const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_size; + + /* Create a Bezier segment to update iteratively for every subdivision + * and references to the meaningful values for ease of use. */ + BezierSpline temp; + temp.resize(2); + float3 &segment_start = temp.positions().first(); + float3 &segment_end = temp.positions().last(); + float3 &handle_prev = temp.handle_positions_right().first(); + float3 &handle_next = temp.handle_positions_left().last(); + segment_start = src_positions[index]; + segment_end = src_positions[next_index]; + handle_prev = src_handles_right[index]; + handle_next = src_handles_left[next_index]; + + for (const int cut : IndexRange(result_size - 1)) { + const float parameter = 1.0f / (result_size - cut); + const BezierSpline::InsertResult insert = temp.calculate_segment_insertion(0, 1, parameter); + + /* Copy relevant temporary data to the result. */ + dst_handles_right[offset + cut] = insert.handle_prev; + dst_handles_left[offset + cut + 1] = insert.left_handle; + dst_positions[offset + cut + 1] = insert.position; + + /* Update the segment to prepare it for the next subdivision. */ + segment_start = insert.position; + handle_prev = insert.right_handle; + handle_next = insert.handle_next; + } + + /* Copy the handles for the last segment from the temporary spline. */ + dst_handles_right[offset + result_size - 1] = handle_prev; + dst_handles_left[i_segment_last] = handle_next; + } +} + +static void subdivide_bezier_spline(const BezierSpline &src, + const Span<int> offsets, + BezierSpline &dst) +{ + Span<float3> src_positions = src.positions(); + Span<float3> src_handles_left = src.handle_positions_left(); + Span<float3> src_handles_right = src.handle_positions_right(); + MutableSpan<float3> dst_positions = dst.positions(); + MutableSpan<float3> dst_handles_left = dst.handle_positions_left(); + MutableSpan<float3> dst_handles_right = dst.handle_positions_right(); + MutableSpan<BezierSpline::HandleType> dst_type_left = dst.handle_types_left(); + MutableSpan<BezierSpline::HandleType> dst_type_right = dst.handle_types_right(); + + threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) { + for (const int i : range) { + subdivide_bezier_segment(src, + i, + offsets[i], + offsets[i + 1] - offsets[i], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + }); + + if (src.is_cyclic()) { + const int i_last = src.size() - 1; + subdivide_bezier_segment(src, + i_last, + offsets[i_last], + offsets.last() - offsets[i_last], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + else { + dst_positions.last() = src_positions.last(); + } +} + +static void subdivide_builtin_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + subdivide_attribute<float>(src_spline.radii(), offsets, is_cyclic, dst_spline.radii()); + subdivide_attribute<float>(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts()); + switch (src_spline.type()) { + case Spline::Type::Poly: { + const PolySpline &src = static_cast<const PolySpline &>(src_spline); + PolySpline &dst = static_cast<PolySpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + break; + } + case Spline::Type::Bezier: { + const BezierSpline &src = static_cast<const BezierSpline &>(src_spline); + BezierSpline &dst = static_cast<BezierSpline &>(dst_spline); + subdivide_bezier_spline(src, offsets, dst); + dst.mark_cache_invalid(); + break; + } + case Spline::Type::NURBS: { + const NURBSpline &src = static_cast<const NURBSpline &>(src_spline); + NURBSpline &dst = static_cast<NURBSpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + subdivide_attribute<float>(src.weights(), offsets, is_cyclic, dst.weights()); + break; + } + } +} + +static void subdivide_dynamic_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + src_spline.attributes.foreach_attribute( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = src_spline.attributes.get_for_read(attribute_id); + BLI_assert(src); + + if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) { + /* Since the source spline of the same type had the attribute, adding it should work. */ + BLI_assert_unreachable(); + } + + std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(attribute_id); + BLI_assert(dst); + + attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) { + using T = decltype(dummy); + subdivide_attribute<T>(src->typed<T>(), offsets, is_cyclic, dst->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr subdivide_spline(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + /* Since we expect to access each value many times, it should be worth it to make sure the + * attribute is a real span (especially considering the note below). Using the offset at each + * point facilitates subdividing in parallel later. */ + Array<int> offsets = get_subdivided_offsets(spline, cuts, spline_offset); + const int result_size = offsets.last() + int(!spline.is_cyclic()); + SplinePtr new_spline = spline.copy_only_settings(); + new_spline->resize(result_size); + subdivide_builtin_attributes(spline, offsets, *new_spline); + subdivide_dynamic_attributes(spline, offsets, *new_spline); + return new_spline; +} + +/** + * \note Passing the virtual array for the entire spline is possibly quite inefficient here when + * the attribute was on the point domain and stored separately for each spline already, and it + * prevents some other optimizations like skipping splines with a single attribute value of < 1. + * However, it allows the node to access builtin attribute easily, so it the makes most sense this + * way until the attribute API is refactored. + */ +static std::unique_ptr<CurveEval> subdivide_curve(const CurveEval &input_curve, + const VArray<int> &cuts) +{ + const Array<int> control_point_offsets = input_curve.control_point_offsets(); + const Span<SplinePtr> input_splines = input_curve.splines(); + + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + output_curve->resize(input_splines.size()); + output_curve->attributes = input_curve.attributes; + MutableSpan<SplinePtr> output_splines = output_curve->splines(); + + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + output_splines[i] = subdivide_spline(*input_splines[i], cuts, control_point_offsets[i]); + } + }); + + return output_curve; +} + +static void geo_node_subdivide_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + if (!geometry_set.has_curve()) { + params.set_output("Geometry", geometry_set); + return; + } + + const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); + GVArray_Typed<int> cuts = params.get_input_attribute<int>( + "Cuts", component, ATTR_DOMAIN_POINT, 0); + if (cuts->is_single() && cuts->get_internal_single() < 1) { + params.set_output("Geometry", geometry_set); + return; + } + + std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), *cuts); + + params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_subdivide() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_subdivide_declare; + ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout; + node_type_storage(&ntype, + "NodeGeometryCurveSubdivide", + node_free_standard_storage, + node_copy_standard_storage); + node_type_init(&ntype, blender::nodes::geo_node_curve_subdivide_init); + node_type_update(&ntype, blender::nodes::geo_node_curve_subdivide_update); + ntype.geometry_node_execute = blender::nodes::geo_node_subdivide_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_to_points.cc index 1e66b340f5c..0c435d69991 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_to_points.cc @@ -358,7 +358,8 @@ void register_node_type_geo_curve_to_points() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_to_points_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_points_exec; ntype.draw_buttons = blender::nodes::geo_node_curve_to_points_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_delete_geometry.cc index 1e2f652cd78..1e2f652cd78 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_delete_geometry.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_edge_split.cc index 867fecea251..2ea6516996d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_edge_split.cc @@ -82,7 +82,7 @@ void register_node_type_geo_edge_split() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_EDGE_SPLIT, "Edge Split", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_EDGE_SPLIT, "Edge Split", NODE_CLASS_GEOMETRY, 0); ntype.geometry_node_execute = blender::nodes::geo_node_edge_split_exec; ntype.declare = blender::nodes::geo_node_edge_split_declare; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc index 11349dc7d42..11349dc7d42 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc index 04b4003daed..f95b0da86ed 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc @@ -36,7 +36,6 @@ #include "node_geometry_util.hh" -using blender::bke::AttributeKind; using blender::bke::GeometryInstanceGroup; namespace blender::nodes { diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc index fb45c22ced4..fb45c22ced4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_rotate.cc index 60c82360007..60c82360007 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_rotate.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_scale.cc index 99adce149e9..99adce149e9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_scale.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_separate.cc index 48b6676c1dd..48b6676c1dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_separate.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_translate.cc index f2fce45c57b..f2fce45c57b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_translate.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_points_to_volume.cc index d920c8de9f0..d920c8de9f0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_points_to_volume.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc index 401a478f04c..401a478f04c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_subdivision_surface.cc index 4541bf3569f..07d3f89bdb7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_subdivision_surface.cc @@ -133,7 +133,7 @@ void register_node_type_geo_subdivision_surface() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_SUBDIVISION_SURFACE, "Subdivision Surface", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, "Subdivision Surface", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_subdivision_surface_declare; ntype.geometry_node_execute = blender::nodes::geo_node_subdivision_surface_exec; ntype.draw_buttons = blender::nodes::geo_node_subdivision_surface_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index c8a33205de4..43fb00a482c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -26,18 +26,18 @@ namespace blender::nodes { static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Vector>("Value"); - b.add_input<decl::Float>("Value", "Value_001"); - b.add_input<decl::Color>("Value", "Value_002"); - b.add_input<decl::Bool>("Value", "Value_003"); - b.add_input<decl::Int>("Value", "Value_004"); + b.add_input<decl::Vector>("Value").supports_field(); + b.add_input<decl::Float>("Value", "Value_001").supports_field(); + b.add_input<decl::Color>("Value", "Value_002").supports_field(); + b.add_input<decl::Bool>("Value", "Value_003").supports_field(); + b.add_input<decl::Int>("Value", "Value_004").supports_field(); b.add_output<decl::Geometry>("Geometry"); - b.add_output<decl::Vector>("Attribute"); - b.add_output<decl::Float>("Attribute", "Attribute_001"); - b.add_output<decl::Color>("Attribute", "Attribute_002"); - b.add_output<decl::Bool>("Attribute", "Attribute_003"); - b.add_output<decl::Int>("Attribute", "Attribute_004"); + b.add_output<decl::Vector>("Attribute").field_source(); + b.add_output<decl::Float>("Attribute", "Attribute_001").field_source(); + b.add_output<decl::Color>("Attribute", "Attribute_002").field_source(); + b.add_output<decl::Bool>("Attribute", "Attribute_003").field_source(); + b.add_output<decl::Int>("Attribute", "Attribute_004").field_source(); } static void geo_node_attribute_capture_layout(uiLayout *layout, @@ -117,8 +117,6 @@ static void geo_node_attribute_capture_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - const bNode &node = params.node(); const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *) node.storage; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc index 21a9a338857..f93ef6f1db3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -47,8 +47,6 @@ static void geo_node_attribute_remove_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); Vector<std::string> attribute_names = params.extract_multi_input<std::string>("Attribute"); - geometry_set = geometry_set_realize_instances(geometry_set); - if (geometry_set.has<MeshComponent>()) { remove_attribute( geometry_set.get_component_for_write<MeshComponent>(), params, attribute_names); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc index 5001034518c..1b7d2fe28a1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -29,8 +29,8 @@ namespace blender::nodes { static void geo_node_attribute_statistic_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Float>("Attribute").hide_value(); - b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value(); + b.add_input<decl::Float>("Attribute").hide_value().supports_field(); + b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value().supports_field(); b.add_output<decl::Float>("Mean"); b.add_output<decl::Float>("Median"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index 2a1c43a89fe..21b425c0ed4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -83,10 +83,14 @@ static void geo_node_boolean_exec(GeoNodeExecParams params) GeometrySet set_a; if (operation == GEO_NODE_BOOLEAN_DIFFERENCE) { set_a = params.extract_input<GeometrySet>("Geometry 1"); + if (set_a.has_instances()) { + params.error_message_add( + NodeWarningType::Info, + TIP_("Instances are not supported for the first geometry input, and will not be used")); + } /* Note that it technically wouldn't be necessary to realize the instances for the first * geometry input, but the boolean code expects the first shape for the difference operation * to be a single mesh. */ - set_a = geometry_set_realize_instances(set_a); const Mesh *mesh_in_a = set_a.get_mesh_for_read(); if (mesh_in_a != nullptr) { meshes.append(mesh_in_a); diff --git a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc index f4c295b06fb..d03221703f0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -21,6 +21,8 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "BKE_collection.h" + #include "node_geometry_util.hh" namespace blender::nodes { @@ -28,6 +30,12 @@ namespace blender::nodes { static void geo_node_collection_info_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Collection>("Collection").hide_label(); + b.add_input<decl::Bool>("Separate Children") + .description("Output each child of the collection as a separate instance"); + b.add_input<decl::Bool>("Reset Children") + .description( + "Reset the transforms of every child instance in the output. Only used when Separate " + "Children is enabled"); b.add_output<decl::Geometry>("Geometry"); } @@ -57,23 +65,66 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) const bNode &bnode = params.node(); NodeGeometryCollectionInfo *node_storage = (NodeGeometryCollectionInfo *)bnode.storage; - const bool transform_space_relative = (node_storage->transform_space == - GEO_NODE_TRANSFORM_SPACE_RELATIVE); + const bool use_relative_transform = (node_storage->transform_space == + GEO_NODE_TRANSFORM_SPACE_RELATIVE); InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); - float transform_mat[4][4]; - unit_m4(transform_mat); const Object *self_object = params.self_object(); - if (transform_space_relative) { - copy_v3_v3(transform_mat[3], collection->instance_offset); - - mul_m4_m4_pre(transform_mat, self_object->imat); + const bool separate_children = params.get_input<bool>("Separate Children"); + if (separate_children) { + const bool reset_children = params.get_input<bool>("Reset Children"); + Vector<Collection *> children_collections; + LISTBASE_FOREACH (CollectionChild *, collection_child, &collection->children) { + children_collections.append(collection_child->collection); + } + Vector<Object *> children_objects; + LISTBASE_FOREACH (CollectionObject *, collection_object, &collection->gobject) { + children_objects.append(collection_object->ob); + } + + instances.reserve(children_collections.size() + children_objects.size()); + + for (Collection *child_collection : children_collections) { + float4x4 transform = float4x4::identity(); + if (!reset_children) { + add_v3_v3(transform.values[3], child_collection->instance_offset); + if (use_relative_transform) { + mul_m4_m4_pre(transform.values, self_object->imat); + } + else { + sub_v3_v3(transform.values[3], collection->instance_offset); + } + } + const int handle = instances.add_reference(*child_collection); + instances.add_instance(handle, transform); + } + for (Object *child_object : children_objects) { + const int handle = instances.add_reference(*child_object); + float4x4 transform = float4x4::identity(); + if (!reset_children) { + if (use_relative_transform) { + transform = self_object->imat; + } + else { + sub_v3_v3(transform.values[3], collection->instance_offset); + } + mul_m4_m4_post(transform.values, child_object->obmat); + } + instances.add_instance(handle, transform); + } + } + else { + float4x4 transform = float4x4::identity(); + if (use_relative_transform) { + copy_v3_v3(transform.values[3], collection->instance_offset); + mul_m4_m4_pre(transform.values, self_object->imat); + } + + const int handle = instances.add_reference(*collection); + instances.add_instance(handle, transform); } - - const int handle = instances.add_reference(*collection); - instances.add_instance(handle, transform_mat, -1); params.set_output("Geometry", geometry_set_out); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc index f1e10e3d276..4377d32210d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -227,9 +227,13 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) return hull_from_bullet(geometry_set.get_mesh_for_read(), positions); } +/* Since only positions are read from the instances, this can be used as an internal optimization + * to avoid the cost of realizing instances before the node. But disable this for now, since + * re-enabling that optimization will be a separate step. */ +# if 0 static void read_positions(const GeometryComponent &component, - Span<float4x4> transforms, - Vector<float3> *r_coords) + Span<float4x4> transforms, + Vector<float3> *r_coords) { GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); @@ -265,6 +269,31 @@ static void read_curve_positions(const CurveEval &curve, } } +static Mesh *convex_hull_from_instances(const GeometrySet &geometry_set) +{ + Vector<GeometryInstanceGroup> set_groups; + bke::geometry_set_gather_instances(geometry_set, set_groups); + + Vector<float3> coords; + + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + Span<float4x4> transforms = set_group.transforms; + + if (set.has_pointcloud()) { + read_positions(*set.get_component_for_read<PointCloudComponent>(), transforms, &coords); + } + if (set.has_mesh()) { + read_positions(*set.get_component_for_read<MeshComponent>(), transforms, &coords); + } + if (set.has_curve()) { + read_curve_positions(*set.get_curve_for_read(), transforms, &coords); + } + } + return hull_from_bullet(nullptr, coords); +} +# endif + #endif /* WITH_BULLET */ static void geo_node_convex_hull_exec(GeoNodeExecParams params) @@ -272,33 +301,14 @@ static void geo_node_convex_hull_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); #ifdef WITH_BULLET - Mesh *mesh = nullptr; - if (geometry_set.has_instances()) { - Vector<GeometryInstanceGroup> set_groups; - bke::geometry_set_gather_instances(geometry_set, set_groups); - - Vector<float3> coords; - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - Span<float4x4> transforms = set_group.transforms; + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + Mesh *mesh = compute_hull(geometry_set); + geometry_set.replace_mesh(mesh); + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + }); - if (set.has_pointcloud()) { - read_positions(*set.get_component_for_read<PointCloudComponent>(), transforms, &coords); - } - if (set.has_mesh()) { - read_positions(*set.get_component_for_read<MeshComponent>(), transforms, &coords); - } - if (set.has_curve()) { - read_curve_positions(*set.get_curve_for_read(), transforms, &coords); - } - } - mesh = hull_from_bullet(nullptr, coords); - } - else { - mesh = compute_hull(geometry_set); - } - params.set_output("Convex Hull", GeometrySet::create_with_mesh(mesh)); + params.set_output("Convex Hull", std::move(geometry_set)); #else params.error_message_add(NodeWarningType::Error, TIP_("Disabled, Blender was compiled without Bullet")); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc index 8de2975f9b0..c30741cf786 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc @@ -154,23 +154,8 @@ static void geo_node_curve_fill_exec(GeoNodeExecParams params) const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage; const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode; - if (geometry_set.has_instances()) { - InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); - instances.ensure_geometry_instances(); - - threading::parallel_for(IndexRange(instances.references_amount()), 16, [&](IndexRange range) { - for (int i : range) { - GeometrySet &geometry_set = instances.geometry_set_from_reference(i); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - curve_fill_calculate(geometry_set, mode); - } - }); - - params.set_output("Mesh", std::move(geometry_set)); - return; - } - - curve_fill_calculate(geometry_set, mode); + geometry_set.modify_geometry_sets( + [&](GeometrySet &geometry_set) { curve_fill_calculate(geometry_set, mode); }); params.set_output("Mesh", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index 830cfcc8331..67ce20efd9d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -563,19 +563,16 @@ static std::unique_ptr<CurveEval> fillet_curve(const CurveEval &input_curve, return output_curve; } -static void geo_node_fillet_exec(GeoNodeExecParams params) +static void calculate_curve_fillet(GeometrySet &geometry_set, + const GeometryNodeCurveFilletMode mode, + const Field<float> &radius_field, + const std::optional<Field<int>> &count_field, + const bool limit_radius) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); return; } - NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)params.node().storage; - const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode; FilletParam fillet_param; fillet_param.mode = mode; @@ -584,19 +581,16 @@ static void geo_node_fillet_exec(GeoNodeExecParams params) const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); fn::FieldEvaluator field_evaluator{field_context, domain_size}; - Field<float> radius_field = params.extract_input<Field<float>>("Radius"); - field_evaluator.add(std::move(radius_field)); + field_evaluator.add(radius_field); if (mode == GEO_NODE_CURVE_FILLET_POLY) { - Field<int> count_field = params.extract_input<Field<int>>("Count"); - field_evaluator.add(std::move(count_field)); + field_evaluator.add(*count_field); } field_evaluator.evaluate(); fillet_param.radii = &field_evaluator.get_evaluated<float>(0); if (fillet_param.radii->is_single() && fillet_param.radii->get_internal_single() < 0.0f) { - params.set_output("Geometry", geometry_set); return; } @@ -604,13 +598,36 @@ static void geo_node_fillet_exec(GeoNodeExecParams params) fillet_param.counts = &field_evaluator.get_evaluated<int>(1); } - fillet_param.limit_radius = params.extract_input<bool>("Limit Radius"); + fillet_param.limit_radius = limit_radius; const CurveEval &input_curve = *geometry_set.get_curve_for_read(); std::unique_ptr<CurveEval> output_curve = fillet_curve(input_curve, fillet_param); - params.set_output("Curve", GeometrySet::create_with_curve(output_curve.release())); + geometry_set.replace_curve(output_curve.release()); +} + +static void geo_node_fillet_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + + NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)params.node().storage; + const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode; + + Field<float> radius_field = params.extract_input<Field<float>>("Radius"); + const bool limit_radius = params.extract_input<bool>("Limit Radius"); + + std::optional<Field<int>> count_field; + if (mode == GEO_NODE_CURVE_FILLET_POLY) { + count_field.emplace(params.extract_input<Field<int>>("Count")); + } + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + calculate_curve_fillet(geometry_set, mode, radius_field, count_field, limit_radius); + }); + + params.set_output("Curve", std::move(geometry_set)); } + } // namespace blender::nodes void register_node_type_geo_curve_fillet() diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc index 8fe054633a1..ac7df35bb72 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc @@ -28,7 +28,6 @@ static void geo_node_curve_length_declare(NodeDeclarationBuilder &b) static void geo_node_curve_length_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); - curve_set = bke::geometry_set_realize_instances(curve_set); if (!curve_set.has_curve()) { params.set_output("Length", 0.0f); return; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc index 2cde198e679..90853387ec7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc @@ -24,7 +24,7 @@ namespace blender::nodes { static void geo_node_curve_parameter_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Float>("Factor"); + b.add_output<decl::Float>("Factor").field_source(); } /** diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc index 0803d43e5c3..7292fafc8b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc @@ -43,26 +43,18 @@ static std::unique_ptr<CurveEval> create_spiral_curve(const float rotations, const int totalpoints = std::max(int(resolution * rotations), 1); const float delta_radius = (end_radius - start_radius) / (float)totalpoints; - float radius = start_radius; const float delta_height = height / (float)totalpoints; - const float delta_theta = (M_PI * 2 * rotations) / (float)totalpoints; - float theta = 0.0f; + const float delta_theta = (M_PI * 2 * rotations) / (float)totalpoints * + (direction ? 1.0f : -1.0f); for (const int i : IndexRange(totalpoints + 1)) { + const float theta = i * delta_theta; + const float radius = start_radius + i * delta_radius; const float x = radius * cos(theta); const float y = radius * sin(theta); const float z = delta_height * i; spline->add_point(float3(x, y, z), 1.0f, 0.0f); - - radius += delta_radius; - - if (direction) { - theta += delta_theta; - } - else { - theta -= delta_theta; - } } spline->attributes.reallocate(spline->size()); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index 208525f17f6..e5be9b7a6f4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -35,8 +35,9 @@ namespace blender::nodes { static void geo_node_curve_resample_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Int>("Count").default_value(10).min(1).max(100000); - b.add_input<decl::Float>("Length").default_value(0.1f).min(0.001f).subtype(PROP_DISTANCE); + b.add_input<decl::Int>("Count").default_value(10).min(1).max(100000).supports_field(); + b.add_input<decl::Float>("Length").default_value(0.1f).min(0.001f).supports_field().subtype( + PROP_DISTANCE); b.add_output<decl::Geometry>("Geometry"); } @@ -68,8 +69,8 @@ static void geo_node_curve_resample_update(bNodeTree *UNUSED(ntree), bNode *node struct SampleModeParam { GeometryNodeCurveResampleMode mode; - std::optional<float> length; - std::optional<int> count; + std::optional<Field<float>> length; + std::optional<Field<int>> count; }; static SplinePtr resample_spline(const Spline &src, const int count) @@ -163,28 +164,44 @@ static SplinePtr resample_spline_evaluated(const Spline &src) return dst; } -static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, +static std::unique_ptr<CurveEval> resample_curve(const CurveComponent *component, const SampleModeParam &mode_param) { - Span<SplinePtr> input_splines = input_curve.splines(); + const CurveEval *input_curve = component->get_for_read(); + GeometryComponentFieldContext field_context{*component, ATTR_DOMAIN_CURVE}; + const int domain_size = component->attribute_domain_size(ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + + Span<SplinePtr> input_splines = input_curve->splines(); std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); output_curve->resize(input_splines.size()); MutableSpan<SplinePtr> output_splines = output_curve->splines(); if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { + evaluator.add(*mode_param.count); + evaluator.evaluate(); + const VArray<int> &cuts = evaluator.get_evaluated<int>(0); + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { BLI_assert(mode_param.count); - output_splines[i] = resample_spline(*input_splines[i], *mode_param.count); + output_splines[i] = resample_spline(*input_splines[i], std::max(cuts[i], 1)); } }); } else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { + evaluator.add(*mode_param.length); + evaluator.evaluate(); + const VArray<float> &lengths = evaluator.get_evaluated<float>(0); + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { - const float length = input_splines[i]->length(); - const int count = std::max(int(length / *mode_param.length) + 1, 1); + /* Don't allow asymptotic count increase for low resolution values. */ + const float divide_length = std::max(lengths[i], 0.0001f); + const float spline_length = input_splines[i]->length(); + const int count = std::max(int(spline_length / divide_length) + 1, 1); output_splines[i] = resample_spline(*input_splines[i], count); } }); @@ -197,29 +214,35 @@ static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, }); } - output_curve->attributes = input_curve.attributes; + output_curve->attributes = input_curve->attributes; return output_curve; } -static void geo_node_resample_exec(GeoNodeExecParams params) +static void geometry_set_curve_resample(GeometrySet &geometry_set, + const SampleModeParam &mode_param) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Geometry", GeometrySet()); return; } - const CurveEval &input_curve = *geometry_set.get_curve_for_read(); + std::unique_ptr<CurveEval> output_curve = resample_curve( + geometry_set.get_component_for_read<CurveComponent>(), mode_param); + + geometry_set.replace_curve(output_curve.release()); +} + +static void geo_node_resample_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)params.node().storage; const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; + SampleModeParam mode_param; mode_param.mode = mode; if (mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { - const int count = params.extract_input<int>("Count"); + Field<int> count = params.extract_input<Field<int>>("Count"); if (count < 1) { params.set_output("Geometry", GeometrySet()); return; @@ -227,14 +250,14 @@ static void geo_node_resample_exec(GeoNodeExecParams params) mode_param.count.emplace(count); } else if (mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { - /* Don't allow asymptotic count increase for low resolution values. */ - const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f); + Field<int> resolution = params.extract_input<Field<int>>("Length"); mode_param.length.emplace(resolution); } - std::unique_ptr<CurveEval> output_curve = resample_curve(input_curve, mode_param); + geometry_set.modify_geometry_sets( + [&](GeometrySet &geometry_set) { geometry_set_curve_resample(geometry_set, mode_param); }); - params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); + params.set_output("Geometry", std::move(geometry_set)); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc index 32bcbe2c608..b644faabedb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -25,37 +25,39 @@ namespace blender::nodes { static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Curve"); } static void geo_node_curve_reverse_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); - return; - } - /* Retrieve data for write access so we can avoid new allocations for the reversed data. */ - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - CurveEval &curve = *curve_component.get_for_write(); - MutableSpan<SplinePtr> splines = curve.splines(); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; + } + + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); - const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( - selection_name, ATTR_DOMAIN_CURVE, true); + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - if (selection[i]) { - splines[i]->reverse(); + CurveEval &curve = *component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + threading::parallel_for(selection.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + splines[selection[i]]->reverse(); } - } + }); }); - params.set_output("Curve", geometry_set); + params.set_output("Curve", std::move(geometry_set)); } } // namespace blender::nodes @@ -63,8 +65,7 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) void register_node_type_geo_curve_reverse() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_reverse_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc index ac0cd510ffa..1266f525861 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -28,12 +28,12 @@ namespace blender::nodes { static void geo_node_curve_sample_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR).supports_field(); + b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE).supports_field(); - b.add_output<decl::Vector>("Position"); - b.add_output<decl::Vector>("Tangent"); - b.add_output<decl::Vector>("Normal"); + b.add_output<decl::Vector>("Position").dependent_field(); + b.add_output<decl::Vector>("Tangent").dependent_field(); + b.add_output<decl::Vector>("Normal").dependent_field(); } static void geo_node_curve_sample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc index 31c13134f79..9e7ac60c29d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc @@ -23,10 +23,10 @@ namespace blender::nodes { -static void geo_node_curve_set_handles_decalre(NodeDeclarationBuilder &b) +static void geo_node_curve_set_handles_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Curve"); } @@ -44,7 +44,7 @@ static void geo_node_curve_set_handles_init(bNodeTree *UNUSED(tree), bNode *node sizeof(NodeGeometryCurveSetHandles), __func__); data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; - data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; + data->mode = GEO_NODE_CURVE_HANDLE_LEFT; node->storage = data; } @@ -72,57 +72,63 @@ static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)node_storage->mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); - return; - } - - /* Retrieve data for write access so we can avoid new allocations for the handles data. */ - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - CurveEval &curve = *curve_component.get_for_write(); - MutableSpan<SplinePtr> splines = curve.splines(); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); - const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( - selection_name, ATTR_DOMAIN_POINT, true); - - const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); - int point_index = 0; bool has_bezier_spline = false; - for (SplinePtr &spline : splines) { - if (spline->type() != Spline::Type::Bezier) { - point_index += spline->positions().size(); - continue; + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; } - BezierSpline &bezier_spline = static_cast<BezierSpline &>(*spline); - if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) { - /* In this case the automatically calculated handle types need to be "baked", because - * they're possibly changing from a type that is calculated automatically to a type that - * is positioned manually. */ - bezier_spline.ensure_auto_handles(); - } - has_bezier_spline = true; - for (int i_point : IndexRange(bezier_spline.size())) { - if (selection[point_index]) { - if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { - bezier_spline.handle_types_left()[i_point] = new_handle_type; - } - if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) { - bezier_spline.handle_types_right()[i_point] = new_handle_type; + /* Retrieve data for write access so we can avoid new allocations for the handles data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + GeometryComponentFieldContext field_context{curve_component, ATTR_DOMAIN_POINT}; + const int domain_size = curve_component.attribute_domain_size(ATTR_DOMAIN_POINT); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); + int point_index = 0; + + for (SplinePtr &spline : splines) { + if (spline->type() != Spline::Type::Bezier) { + point_index += spline->positions().size(); + continue; + } + + has_bezier_spline = true; + BezierSpline &bezier_spline = static_cast<BezierSpline &>(*spline); + if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) { + /* In this case the automatically calculated handle types need to be "baked", because + * they're possibly changing from a type that is calculated automatically to a type that + * is positioned manually. */ + bezier_spline.ensure_auto_handles(); + } + + for (int i_point : IndexRange(bezier_spline.size())) { + if (selection[point_index]) { + if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { + bezier_spline.handle_types_left()[i_point] = new_handle_type; + } + if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) { + bezier_spline.handle_types_right()[i_point] = new_handle_type; + } } + point_index++; } - point_index++; + bezier_spline.mark_cache_invalid(); } - bezier_spline.mark_cache_invalid(); - } - + }); if (!has_bezier_spline) { params.error_message_add(NodeWarningType::Info, TIP_("No Bezier splines in input curve")); } - - params.set_output("Curve", geometry_set); + params.set_output("Curve", std::move(geometry_set)); } } // namespace blender::nodes @@ -130,8 +136,8 @@ void register_node_type_geo_curve_set_handles() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_set_handles_decalre; + &ntype, GEO_NODE_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_set_handles_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_set_handles_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_set_handles_init); node_type_storage(&ntype, diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc index 0ef107fd8a4..ec72154db13 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -28,7 +28,7 @@ namespace blender::nodes { static void geo_node_curve_spline_type_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Curve"); } @@ -245,41 +245,47 @@ static void geo_node_curve_spline_type_exec(GeoNodeExecParams params) const GeometryNodeSplineType output_type = (const GeometryNodeSplineType)storage->spline_type; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); - return; - } + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); - const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); - const CurveEval &curve = *curve_component->get_for_read(); - - const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component->attribute_get_for_read( - selection_name, ATTR_DOMAIN_CURVE, true); - - std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); - for (const int i : curve.splines().index_range()) { - if (selection[i]) { - switch (output_type) { - case GEO_NODE_SPLINE_TYPE_POLY: - new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i])); - break; - case GEO_NODE_SPLINE_TYPE_BEZIER: - new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params)); - break; - case GEO_NODE_SPLINE_TYPE_NURBS: - new_curve->add_spline(convert_to_nurbs(*curve.splines()[i])); - break; - } + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; } - else { - new_curve->add_spline(curve.splines()[i]->copy()); + + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + const CurveEval &curve = *curve_component->get_for_read(); + GeometryComponentFieldContext field_context{*curve_component, ATTR_DOMAIN_CURVE}; + const int domain_size = curve_component->attribute_domain_size(ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); + for (const int i : curve.splines().index_range()) { + if (selection[i]) { + switch (output_type) { + case GEO_NODE_SPLINE_TYPE_POLY: + new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i])); + break; + case GEO_NODE_SPLINE_TYPE_BEZIER: + new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params)); + break; + case GEO_NODE_SPLINE_TYPE_NURBS: + new_curve->add_spline(convert_to_nurbs(*curve.splines()[i])); + break; + } + } + else { + new_curve->add_spline(curve.splines()[i]->copy()); + } } - } + new_curve->attributes = curve.attributes; + geometry_set.replace_curve(new_curve.release()); + }); - new_curve->attributes = curve.attributes; - params.set_output("Curve", GeometrySet::create_with_curve(new_curve.release())); + params.set_output("Curve", std::move(geometry_set)); } } // namespace blender::nodes @@ -288,7 +294,7 @@ void register_node_type_geo_curve_spline_type() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_spline_type_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_spline_type_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_spline_type_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc index 0522f2b8981..34997c66cbb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -34,35 +34,10 @@ namespace blender::nodes { static void geo_node_curve_subdivide_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Cuts"); - b.add_input<decl::Int>("Cuts", "Cuts_001").default_value(1).min(0).max(1000); + b.add_input<decl::Int>("Cuts").default_value(1).min(0).max(1000).supports_field(); b.add_output<decl::Geometry>("Geometry"); } -static void geo_node_curve_subdivide_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) -{ - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "cuts_type", 0, IFACE_("Cuts"), ICON_NONE); -} - -static void geo_node_curve_subdivide_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryCurveSubdivide *data = (NodeGeometryCurveSubdivide *)MEM_callocN( - sizeof(NodeGeometryCurveSubdivide), __func__); - - data->cuts_type = GEO_NODE_ATTRIBUTE_INPUT_INTEGER; - node->storage = data; -} - -static void geo_node_curve_subdivide_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; - - update_attribute_input_socket_availabilities( - *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); -} - static Array<int> get_subdivided_offsets(const Spline &spline, const VArray<int> &cuts, const int spline_offset) @@ -350,25 +325,30 @@ static std::unique_ptr<CurveEval> subdivide_curve(const CurveEval &input_curve, static void geo_node_subdivide_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + Field<int> cuts_field = params.extract_input<Field<int>>("Cuts"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; + } - if (!geometry_set.has_curve()) { - params.set_output("Geometry", geometry_set); - return; - } + const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); - const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); - GVArray_Typed<int> cuts = params.get_input_attribute<int>( - "Cuts", component, ATTR_DOMAIN_POINT, 0); - if (cuts->is_single() && cuts->get_internal_single() < 1) { - params.set_output("Geometry", geometry_set); - return; - } + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(cuts_field); + evaluator.evaluate(); + const VArray<int> &cuts = evaluator.get_evaluated<int>(0); - std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), *cuts); + if (cuts.is_single() && cuts.get_internal_single() < 1) { + return; + } - params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); + std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), cuts); + geometry_set.replace_curve(output_curve.release()); + }); + params.set_output("Geometry", geometry_set); } } // namespace blender::nodes @@ -377,16 +357,8 @@ void register_node_type_geo_curve_subdivide() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_subdivide_declare; - ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout; - node_type_storage(&ntype, - "NodeGeometryCurveSubdivide", - node_free_standard_storage, - node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_curve_subdivide_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_subdivide_update); ntype.geometry_node_execute = blender::nodes::geo_node_subdivide_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index 89ba635ff4b..00451946af9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -32,39 +32,52 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Mesh"); } +static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set) +{ + const CurveEval *profile_curve = profile_set.get_curve_for_read(); + + if (profile_curve == nullptr) { + Mesh *mesh = bke::curve_to_wire_mesh(*geometry_set.get_curve_for_read()); + geometry_set.replace_mesh(mesh); + } + else { + Mesh *mesh = bke::curve_to_mesh_sweep(*geometry_set.get_curve_for_read(), *profile_curve); + geometry_set.replace_mesh(mesh); + } +} + static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve"); - curve_set = bke::geometry_set_realize_instances(curve_set); - profile_set = bke::geometry_set_realize_instances(profile_set); + if (profile_set.has_instances()) { + params.error_message_add(NodeWarningType::Error, + TIP_("Instances are not supported in the profile input")); + params.set_output("Mesh", GeometrySet()); + return; + } - /* NOTE: Theoretically an "is empty" check would be more correct for errors. */ - if (profile_set.has_mesh() && !profile_set.has_curve()) { + if (!profile_set.has_curve() && !profile_set.is_empty()) { params.error_message_add(NodeWarningType::Warning, - TIP_("No curve data available in profile input")); + TIP_("No curve data available in the profile input")); } - if (!curve_set.has_curve()) { - if (curve_set.has_mesh()) { - params.error_message_add(NodeWarningType::Warning, - TIP_("No curve data available in curve input")); + bool has_curve = false; + curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_curve()) { + has_curve = true; + geometry_set_curve_to_mesh(geometry_set, profile_set); } - params.set_output("Mesh", GeometrySet()); - return; - } - - const CurveEval *profile_curve = profile_set.get_curve_for_read(); + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + }); - if (profile_curve == nullptr) { - Mesh *mesh = bke::curve_to_wire_mesh(*curve_set.get_curve_for_read()); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); - } - else { - Mesh *mesh = bke::curve_to_mesh_sweep(*curve_set.get_curve_for_read(), *profile_curve); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + if (!has_curve && !curve_set.is_empty()) { + params.error_message_add(NodeWarningType::Warning, + TIP_("No curve data available in curve input")); } + + params.set_output("Mesh", std::move(curve_set)); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 2b6d25b6bf3..97043980899 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -320,28 +320,18 @@ static void trim_bezier_spline(Spline &spline, bezier_spline.resize(size); } -static void geo_node_curve_trim_exec(GeoNodeExecParams params) +static void geometry_set_curve_trim(GeometrySet &geometry_set, + const GeometryNodeCurveSampleMode mode, + const float start, + const float end) { - const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)params.node().storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; - - GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); if (!geometry_set.has_curve()) { - params.set_output("Curve", std::move(geometry_set)); return; } - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - CurveEval &curve = *curve_component.get_for_write(); + CurveEval &curve = *geometry_set.get_curve_for_write(); MutableSpan<SplinePtr> splines = curve.splines(); - const float start = mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? - params.extract_input<float>("Start") : - params.extract_input<float>("Start_001"); - const float end = mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? params.extract_input<float>("End") : - params.extract_input<float>("End_001"); - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { Spline &spline = *splines[i]; @@ -382,6 +372,29 @@ static void geo_node_curve_trim_exec(GeoNodeExecParams params) splines[i]->mark_cache_invalid(); } }); +} + +static void geo_node_curve_trim_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + + if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { + const float start = params.extract_input<float>("Start"); + const float end = params.extract_input<float>("End"); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + geometry_set_curve_trim(geometry_set, mode, start, end); + }); + } + else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + const float start = params.extract_input<float>("Start_001"); + const float end = params.extract_input<float>("End_001"); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + geometry_set_curve_trim(geometry_set, mode, start, end); + }); + } params.set_output("Curve", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc new file mode 100644 index 00000000000..1a4c5d84dbf --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -0,0 +1,594 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_kdtree.h" +#include "BLI_noise.hh" +#include "BLI_rand.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" +#include "BKE_pointcloud.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::bke::GeometryInstanceGroup; + +namespace blender::nodes { + +static void geo_node_point_distribute_points_on_faces_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Float>("Distance Min").min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Density Max").default_value(10.0f).min(0.0f); + b.add_input<decl::Float>("Density").default_value(10.0f).supports_field(); + b.add_input<decl::Float>("Density Factor") + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .supports_field(); + b.add_input<decl::Int>("Seed"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); + + b.add_output<decl::Geometry>("Points"); + b.add_output<decl::Vector>("Normal").field_source(); + b.add_output<decl::Vector>("Rotation").subtype(PROP_EULER).field_source(); + b.add_output<decl::Int>("Stable ID").field_source(); +} + +static void geo_node_point_distribute_points_on_faces_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE); +} + +static void node_point_distribute_points_on_faces_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_distance_min = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *sock_density_max = (bNodeSocket *)sock_distance_min->next; + bNodeSocket *sock_density = sock_density_max->next; + bNodeSocket *sock_density_factor = sock_density->next; + nodeSetSocketAvailability(sock_distance_min, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); + nodeSetSocketAvailability(sock_density_max, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); + nodeSetSocketAvailability(sock_density, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); + nodeSetSocketAvailability(sock_density_factor, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); +} + +/** + * Use an arbitrary choice of axes for a usable rotation attribute directly out of this node. + */ +static float3 normal_to_euler_rotation(const float3 normal) +{ + float quat[4]; + vec_to_quat(quat, normal, OB_NEGZ, OB_POSY); + float3 rotation; + quat_to_eul(rotation, quat); + return rotation; +} + +static void sample_mesh_surface(const Mesh &mesh, + const float base_density, + const Span<float> density_factors, + const int seed, + Vector<float3> &r_positions, + Vector<float3> &r_bary_coords, + Vector<int> &r_looptri_indices) +{ + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int looptri_index : looptris.index_range()) { + const MLoopTri &looptri = looptris[looptri_index]; + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + const int v0_index = mesh.mloop[v0_loop].v; + const int v1_index = mesh.mloop[v1_loop].v; + const int v2_index = mesh.mloop[v2_loop].v; + const float3 v0_pos = float3(mesh.mvert[v0_index].co); + const float3 v1_pos = float3(mesh.mvert[v1_index].co); + const float3 v2_pos = float3(mesh.mvert[v2_index].co); + + float looptri_density_factor = 1.0f; + if (!density_factors.is_empty()) { + const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); + looptri_density_factor = (v0_density_factor + v1_density_factor + v2_density_factor) / 3.0f; + } + const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); + + const int looptri_seed = noise::hash(looptri_index, seed); + RandomNumberGenerator looptri_rng(looptri_seed); + + const float points_amount_fl = area * base_density * looptri_density_factor; + const float add_point_probability = fractf(points_amount_fl); + const bool add_point = add_point_probability > looptri_rng.get_float(); + const int point_amount = (int)points_amount_fl + (int)add_point; + + for (int i = 0; i < point_amount; i++) { + const float3 bary_coord = looptri_rng.get_barycentric_coordinates(); + float3 point_pos; + interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coord); + r_positions.append(point_pos); + r_bary_coords.append(bary_coord); + r_looptri_indices.append(looptri_index); + } + } +} + +BLI_NOINLINE static KDTree_3d *build_kdtree(Span<float3> positions) +{ + KDTree_3d *kdtree = BLI_kdtree_3d_new(positions.size()); + + int i_point = 0; + for (const float3 position : positions) { + BLI_kdtree_3d_insert(kdtree, i_point, position); + i_point++; + } + + BLI_kdtree_3d_balance(kdtree); + return kdtree; +} + +BLI_NOINLINE static void update_elimination_mask_for_close_points( + Span<float3> positions, const float minimum_distance, MutableSpan<bool> elimination_mask) +{ + if (minimum_distance <= 0.0f) { + return; + } + + KDTree_3d *kdtree = build_kdtree(positions); + + for (const int i : positions.index_range()) { + if (elimination_mask[i]) { + continue; + } + + struct CallbackData { + int index; + MutableSpan<bool> elimination_mask; + } callback_data = {i, elimination_mask}; + + BLI_kdtree_3d_range_search_cb( + kdtree, + positions[i], + minimum_distance, + [](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) { + CallbackData &callback_data = *static_cast<CallbackData *>(user_data); + if (index != callback_data.index) { + callback_data.elimination_mask[index] = true; + } + return true; + }, + &callback_data); + } + + BLI_kdtree_3d_free(kdtree); +} + +BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( + const Mesh &mesh, + const Span<float> density_factors, + const Span<float3> bary_coords, + const Span<int> looptri_indices, + const MutableSpan<bool> elimination_mask) +{ + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + for (const int i : bary_coords.index_range()) { + if (elimination_mask[i]) { + continue; + } + + const MLoopTri &looptri = looptris[looptri_indices[i]]; + const float3 bary_coord = bary_coords[i]; + + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + + const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); + + const float probablity = v0_density_factor * bary_coord.x + v1_density_factor * bary_coord.y + + v2_density_factor * bary_coord.z; + + const float hash = noise::hash_float_to_float(bary_coord); + if (hash > probablity) { + elimination_mask[i] = true; + } + } +} + +BLI_NOINLINE static void eliminate_points_based_on_mask(const Span<bool> elimination_mask, + Vector<float3> &positions, + Vector<float3> &bary_coords, + Vector<int> &looptri_indices) +{ + for (int i = positions.size() - 1; i >= 0; i--) { + if (elimination_mask[i]) { + positions.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + } + } +} + +BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, + const Span<float3> bary_coords, + const Span<int> looptri_indices, + const AttributeDomain source_domain, + const GVArray &source_data, + GMutableSpan output_data) +{ + switch (source_domain) { + case ATTR_DOMAIN_POINT: { + bke::mesh_surface_sample::sample_point_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_CORNER: { + bke::mesh_surface_sample::sample_corner_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_FACE: { + bke::mesh_surface_sample::sample_face_attribute( + mesh, looptri_indices, source_data, output_data); + break; + } + default: { + /* Not supported currently. */ + return; + } + } +} + +BLI_NOINLINE static void propagate_existing_attributes( + const MeshComponent &mesh_component, + const Map<AttributeIDRef, AttributeKind> &attributes, + GeometryComponent &point_component, + const Span<float3> bary_coords, + const Span<int> looptri_indices) +{ + const Mesh &mesh = *mesh_component.get_for_read(); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType output_data_type = entry.value.data_type; + /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ + OutputAttribute attribute_out = point_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, output_data_type); + if (!attribute_out) { + continue; + } + + GMutableSpan out_span = attribute_out.as_span(); + + std::optional<AttributeMetaData> attribute_info = point_component.attribute_get_meta_data( + attribute_id); + if (!attribute_info) { + continue; + } + + const AttributeDomain source_domain = attribute_info->domain; + GVArrayPtr source_attribute = mesh_component.attribute_get_for_read( + attribute_id, source_domain, output_data_type, nullptr); + if (!source_attribute) { + continue; + } + + interpolate_attribute( + mesh, bary_coords, looptri_indices, source_domain, *source_attribute, out_span); + + attribute_out.save(); + } +} + +namespace { +struct AttributeOutputs { + StrongAnonymousAttributeID normal_id; + StrongAnonymousAttributeID rotation_id; + StrongAnonymousAttributeID stable_id_id; +}; +} // namespace + +BLI_NOINLINE static void compute_attribute_outputs(const MeshComponent &mesh_component, + PointCloudComponent &point_component, + const Span<float3> bary_coords, + const Span<int> looptri_indices, + const AttributeOutputs &attribute_outputs) +{ + std::optional<OutputAttribute_Typed<int>> id_attribute; + std::optional<OutputAttribute_Typed<float3>> normal_attribute; + std::optional<OutputAttribute_Typed<float3>> rotation_attribute; + + MutableSpan<int> ids; + MutableSpan<float3> normals; + MutableSpan<float3> rotations; + + if (attribute_outputs.stable_id_id) { + id_attribute.emplace(point_component.attribute_try_get_for_output_only<int>( + attribute_outputs.stable_id_id.get(), ATTR_DOMAIN_POINT)); + ids = id_attribute->as_span(); + } + if (attribute_outputs.normal_id) { + normal_attribute.emplace(point_component.attribute_try_get_for_output_only<float3>( + attribute_outputs.normal_id.get(), ATTR_DOMAIN_POINT)); + normals = normal_attribute->as_span(); + } + if (attribute_outputs.rotation_id) { + rotation_attribute.emplace(point_component.attribute_try_get_for_output_only<float3>( + attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT)); + rotations = rotation_attribute->as_span(); + } + + const Mesh &mesh = *mesh_component.get_for_read(); + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int i : bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const float3 &bary_coord = bary_coords[i]; + + const int v0_index = mesh.mloop[looptri.tri[0]].v; + const int v1_index = mesh.mloop[looptri.tri[1]].v; + const int v2_index = mesh.mloop[looptri.tri[2]].v; + const float3 v0_pos = float3(mesh.mvert[v0_index].co); + const float3 v1_pos = float3(mesh.mvert[v1_index].co); + const float3 v2_pos = float3(mesh.mvert[v2_index].co); + + if (!ids.is_empty()) { + ids[i] = noise::hash(noise::hash_float(bary_coord), looptri_index); + } + float3 normal; + if (!normals.is_empty() || !rotations.is_empty()) { + normal_tri_v3(normal, v0_pos, v1_pos, v2_pos); + } + if (!normals.is_empty()) { + normals[i] = normal; + } + if (!rotations.is_empty()) { + rotations[i] = normal_to_euler_rotation(normal); + } + } + + if (id_attribute) { + id_attribute->save(); + } + if (normal_attribute) { + normal_attribute->save(); + } + if (rotation_attribute) { + rotation_attribute->save(); + } +} + +static Array<float> calc_full_density_factors_with_selection(const MeshComponent &component, + const Field<float> &density_field, + const Field<bool> &selection_field) +{ + const AttributeDomain attribute_domain = ATTR_DOMAIN_CORNER; + GeometryComponentFieldContext field_context{component, attribute_domain}; + const int domain_size = component.attribute_domain_size(attribute_domain); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection_mask = selection_evaluator.get_evaluated_as_mask(0); + + Array<float> densities(domain_size, 0.0f); + + fn::FieldEvaluator density_evaluator{field_context, &selection_mask}; + density_evaluator.add_with_destination(density_field, densities.as_mutable_span()); + density_evaluator.evaluate(); + return densities; +} + +static void distribute_points_random(const MeshComponent &component, + const Field<float> &density_field, + const Field<bool> &selection_field, + const int seed, + Vector<float3> &positions, + Vector<float3> &bary_coords, + Vector<int> &looptri_indices) +{ + const Array<float> densities = calc_full_density_factors_with_selection( + component, density_field, selection_field); + const Mesh &mesh = *component.get_for_read(); + sample_mesh_surface(mesh, 1.0f, densities, seed, positions, bary_coords, looptri_indices); +} + +static void distribute_points_poisson_disk(const MeshComponent &mesh_component, + const float minimum_distance, + const float max_density, + const Field<float> &density_factor_field, + const Field<bool> &selection_field, + const int seed, + Vector<float3> &positions, + Vector<float3> &bary_coords, + Vector<int> &looptri_indices) +{ + const Mesh &mesh = *mesh_component.get_for_read(); + sample_mesh_surface(mesh, max_density, {}, seed, positions, bary_coords, looptri_indices); + + Array<bool> elimination_mask(positions.size(), false); + update_elimination_mask_for_close_points(positions, minimum_distance, elimination_mask); + + const Array<float> density_factors = calc_full_density_factors_with_selection( + mesh_component, density_factor_field, selection_field); + + update_elimination_mask_based_on_density_factors( + mesh, density_factors, bary_coords, looptri_indices, elimination_mask.as_mutable_span()); + + eliminate_points_based_on_mask( + elimination_mask.as_span(), positions, bary_coords, looptri_indices); +} + +static void point_distribution_calculate(GeometrySet &geometry_set, + const Field<bool> selection_field, + const GeometryNodeDistributePointsOnFacesMode method, + const int seed, + const AttributeOutputs &attribute_outputs, + const GeoNodeExecParams ¶ms) +{ + if (!geometry_set.has_mesh()) { + return; + } + + const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>(); + + Vector<float3> positions; + Vector<float3> bary_coords; + Vector<int> looptri_indices; + + switch (method) { + case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM: { + const Field<float> density_field = params.get_input<Field<float>>("Density"); + distribute_points_random(mesh_component, + density_field, + selection_field, + seed, + positions, + bary_coords, + looptri_indices); + break; + } + case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON: { + const float minimum_distance = params.get_input<float>("Distance Min"); + const float density_max = params.get_input<float>("Density Max"); + const Field<float> density_factors_field = params.get_input<Field<float>>("Density Factor"); + distribute_points_poisson_disk(mesh_component, + minimum_distance, + density_max, + density_factors_field, + selection_field, + seed, + positions, + bary_coords, + looptri_indices); + break; + } + } + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size()); + memcpy(pointcloud->co, positions.data(), sizeof(float3) * positions.size()); + uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); + geometry_set.replace_pointcloud(pointcloud); + + PointCloudComponent &point_component = + geometry_set.get_component_for_write<PointCloudComponent>(); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + + /* Position is set separately. */ + attributes.remove("position"); + + propagate_existing_attributes( + mesh_component, attributes, point_component, bary_coords, looptri_indices); + + compute_attribute_outputs( + mesh_component, point_component, bary_coords, looptri_indices, attribute_outputs); +} + +static void geo_node_point_distribute_points_on_faces_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + const GeometryNodeDistributePointsOnFacesMode method = + static_cast<GeometryNodeDistributePointsOnFacesMode>(params.node().custom1); + + const int seed = params.get_input<int>("Seed") * 5383843; + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + AttributeOutputs attribute_outputs; + if (params.output_is_required("Normal")) { + attribute_outputs.normal_id = StrongAnonymousAttributeID("normal"); + } + if (params.output_is_required("Rotation")) { + attribute_outputs.rotation_id = StrongAnonymousAttributeID("rotation"); + } + if (params.output_is_required("Stable ID")) { + attribute_outputs.stable_id_id = StrongAnonymousAttributeID("stable id"); + } + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + point_distribution_calculate( + geometry_set, selection_field, method, seed, attribute_outputs, params); + /* Keep instances because the original geometry set may contain instances that are processed as + * well. */ + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); + }); + + params.set_output("Points", std::move(geometry_set)); + + if (attribute_outputs.normal_id) { + params.set_output( + "Normal", + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.normal_id))); + } + if (attribute_outputs.rotation_id) { + params.set_output( + "Rotation", + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.rotation_id))); + } + if (attribute_outputs.stable_id_id) { + params.set_output( + "Stable ID", + AnonymousAttributeFieldInput::Create<int>(std::move(attribute_outputs.stable_id_id))); + } +} + +} // namespace blender::nodes + +void register_node_type_geo_distribute_points_on_faces() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, + GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, + "Distribute Points on Faces", + NODE_CLASS_GEOMETRY, + 0); + node_type_update(&ntype, blender::nodes::node_point_distribute_points_on_faces_update); + node_type_size(&ntype, 170, 100, 320); + ntype.declare = blender::nodes::geo_node_point_distribute_points_on_faces_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_points_on_faces_exec; + ntype.draw_buttons = blender::nodes::geo_node_point_distribute_points_on_faces_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc index c52ff3d448e..7fcbaf429dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc @@ -20,30 +20,12 @@ namespace blender::nodes { static void geo_node_input_index_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Int>("Index"); + b.add_output<decl::Int>("Index").field_source(); } -class IndexFieldInput final : public fn::FieldInput { - public: - IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") - { - } - - const GVArray *get_varray_for_context(const fn::FieldContext &UNUSED(context), - IndexMask mask, - ResourceScope &scope) const final - { - /* TODO: Investigate a similar method to IndexRange::as_span() */ - auto index_func = [](int i) { return i; }; - return &scope.construct< - fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>( - mask.min_array_size(), mask.min_array_size(), index_func); - } -}; - static void geo_node_input_index_exec(GeoNodeExecParams params) { - Field<int> index_field{std::make_shared<IndexFieldInput>()}; + Field<int> index_field{std::make_shared<fn::IndexFieldInput>()}; params.set_output("Index", std::move(index_field)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc index f92086acdf0..5a2495afb9e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -28,7 +28,7 @@ namespace blender::nodes { static void geo_node_input_normal_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Normal"); + b.add_output<decl::Vector>("Normal").field_source(); } static GVArrayPtr mesh_face_normals(const Mesh &mesh, diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc index 3f3457a3acb..44874259e20 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc @@ -20,13 +20,12 @@ namespace blender::nodes { static void geo_node_input_position_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Position"); + b.add_output<decl::Vector>("Position").field_source(); } static void geo_node_input_position_exec(GeoNodeExecParams params) { - Field<float3> position_field{ - std::make_shared<AttributeFieldInput>("position", CPPType::get<float3>())}; + Field<float3> position_field{AttributeFieldInput::Create<float3>("position")}; params.set_output("Position", std::move(position_field)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc new file mode 100644 index 00000000000..b5f3e1b0c28 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc @@ -0,0 +1,109 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +#include "BKE_spline.hh" + +namespace blender::nodes { + +static void geo_node_input_spline_length_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Length").field_source(); +} + +static const GVArray *construct_spline_length_gvarray(const CurveComponent &component, + const AttributeDomain domain, + ResourceScope &scope) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + Span<SplinePtr> splines = curve->splines(); + auto length_fn = [splines](int i) { return splines[i]->length(); }; + + if (domain == ATTR_DOMAIN_CURVE) { + return &scope.construct< + fn::GVArray_For_EmbeddedVArray<float, VArray_For_Func<float, decltype(length_fn)>>>( + splines.size(), splines.size(), length_fn); + } + if (domain == ATTR_DOMAIN_POINT) { + GVArrayPtr length = std::make_unique< + fn::GVArray_For_EmbeddedVArray<float, VArray_For_Func<float, decltype(length_fn)>>>( + splines.size(), splines.size(), length_fn); + return scope + .add_value(component.attribute_try_adapt_domain( + std::move(length), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT)) + .get(); + } + + return nullptr; +} + +class SplineLengthFieldInput final : public fn::FieldInput { + public: + SplineLengthFieldInput() : fn::FieldInput(CPPType::get<float>(), "Spline Length") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask UNUSED(mask), + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_spline_length_gvarray(curve_component, domain, scope); + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 3549623580; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const SplineLengthFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_input_spline_length_exec(GeoNodeExecParams params) +{ + Field<float> length_field{std::make_shared<SplineLengthFieldInput>()}; + params.set_output("Length", std::move(length_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_spline_length() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_SPLINE_LENGTH, "Spline Length", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_spline_length_exec; + ntype.declare = blender::nodes::geo_node_input_spline_length_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc index 68788709f1e..d690642373a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc @@ -24,7 +24,7 @@ namespace blender::nodes { static void geo_node_input_tangent_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Tangent"); + b.add_output<decl::Vector>("Tangent").field_source(); } static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents) diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc new file mode 100644 index 00000000000..8c0c0763be8 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -0,0 +1,207 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_collection_types.h" + +#include "BLI_hash.h" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_instance_on_points_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Points").description("Points to instance on"); + b.add_input<decl::Geometry>("Instance").description("Geometry that is instanced on the points"); + b.add_input<decl::Bool>("Pick Instance") + .supports_field() + .description("Place different instances on different points"); + b.add_input<decl::Int>("Instance Index") + .implicit_field() + .description( + "Index of the instance that used for each point. This is only used when Pick Instances " + "is on. By default the point index is used"); + b.add_input<decl::Vector>("Rotation") + .subtype(PROP_EULER) + .supports_field() + .description("Rotation of the instances"); + b.add_input<decl::Vector>("Scale") + .default_value({1.0f, 1.0f, 1.0f}) + .supports_field() + .description("Scale of the instances"); + b.add_input<decl::Int>("Stable ID") + .supports_field() + .description( + "ID for every instance that is used to identify it over time even when the number of " + "instances changes. Used for example for motion blur"); + + b.add_output<decl::Geometry>("Instances"); +} + +static void add_instances_from_component(InstancesComponent &dst_component, + const GeometryComponent &src_component, + const GeometrySet &instance, + const GeoNodeExecParams ¶ms) +{ + const AttributeDomain domain = ATTR_DOMAIN_POINT; + const int domain_size = src_component.attribute_domain_size(domain); + + /* The initial size of the component might be non-zero when this function is called for multiple + * component types. */ + const int start_len = dst_component.instances_amount(); + dst_component.resize(start_len + domain_size); + MutableSpan<int> dst_handles = dst_component.instance_reference_handles().slice(start_len, + domain_size); + MutableSpan<float4x4> dst_transforms = dst_component.instance_transforms().slice(start_len, + domain_size); + MutableSpan<int> dst_stable_ids = dst_component.instance_ids().slice(start_len, domain_size); + + GeometryComponentFieldContext field_context{src_component, domain}; + FieldEvaluator field_evaluator{field_context, domain_size}; + + const VArray<bool> *pick_instance = nullptr; + const VArray<int> *indices = nullptr; + const VArray<float3> *rotations = nullptr; + const VArray<float3> *scales = nullptr; + field_evaluator.add(params.get_input<Field<bool>>("Pick Instance"), &pick_instance); + field_evaluator.add(params.get_input<Field<int>>("Instance Index"), &indices); + field_evaluator.add(params.get_input<Field<float3>>("Rotation"), &rotations); + field_evaluator.add(params.get_input<Field<float3>>("Scale"), &scales); + field_evaluator.add_with_destination(params.get_input<Field<int>>("Stable ID"), dst_stable_ids); + field_evaluator.evaluate(); + + GVArray_Typed<float3> positions = src_component.attribute_get_for_read<float3>( + "position", domain, {0, 0, 0}); + + const InstancesComponent *src_instances = instance.get_component_for_read<InstancesComponent>(); + + /* Maps handles from the source instances to handles on the new instance. */ + Array<int> handle_mapping; + /* Only fill #handle_mapping when it may be used below. */ + if (src_instances != nullptr && + (!pick_instance->is_single() || pick_instance->get_internal_single())) { + Span<InstanceReference> src_references = src_instances->references(); + handle_mapping.reinitialize(src_references.size()); + for (const int src_instance_handle : src_references.index_range()) { + const InstanceReference &reference = src_references[src_instance_handle]; + const int dst_instance_handle = dst_component.add_reference(reference); + handle_mapping[src_instance_handle] = dst_instance_handle; + } + } + + const int full_instance_handle = dst_component.add_reference(instance); + /* Add this reference last, because it is the most likely one to be removed later on. */ + const int empty_reference_handle = dst_component.add_reference(InstanceReference()); + + threading::parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + /* Compute base transform for every instances. */ + float4x4 &dst_transform = dst_transforms[i]; + dst_transform = float4x4::from_loc_eul_scale( + positions[i], rotations->get(i), scales->get(i)); + + /* Reference that will be used by this new instance. */ + int dst_handle = empty_reference_handle; + + const bool use_individual_instance = pick_instance->get(i); + if (use_individual_instance) { + if (src_instances != nullptr) { + const int src_instances_amount = src_instances->instances_amount(); + const int original_index = indices->get(i); + /* Use #mod_i instead of `%` to get the desirable wrap around behavior where -1 refers to + * the last element. */ + const int index = mod_i(original_index, std::max(src_instances_amount, 1)); + if (index < src_instances_amount) { + /* Get the reference to the source instance. */ + const int src_handle = src_instances->instance_reference_handles()[index]; + dst_handle = handle_mapping[src_handle]; + + /* Take transforms of the source instance into account. */ + mul_m4_m4_post(dst_transform.values, + src_instances->instance_transforms()[index].values); + } + } + } + else { + /* Use entire source geometry as instance. */ + dst_handle = full_instance_handle; + } + /* Set properties of new instance. */ + dst_handles[i] = dst_handle; + } + }); + + if (pick_instance->is_single()) { + if (pick_instance->get_internal_single()) { + if (instance.has_realized_data()) { + params.error_message_add( + NodeWarningType::Info, + TIP_("Realized geometry is not used when pick instances is true")); + } + } + } +} + +static void geo_node_instance_on_points_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); + GeometrySet instance = params.get_input<GeometrySet>("Instance"); + instance.ensure_owns_direct_data(); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + + if (geometry_set.has<MeshComponent>()) { + add_instances_from_component( + instances, *geometry_set.get_component_for_read<MeshComponent>(), instance, params); + geometry_set.remove(GEO_COMPONENT_TYPE_MESH); + } + if (geometry_set.has<PointCloudComponent>()) { + add_instances_from_component(instances, + *geometry_set.get_component_for_read<PointCloudComponent>(), + instance, + params); + geometry_set.remove(GEO_COMPONENT_TYPE_POINT_CLOUD); + } + if (geometry_set.has<CurveComponent>()) { + add_instances_from_component( + instances, *geometry_set.get_component_for_read<CurveComponent>(), instance, params); + geometry_set.remove(GEO_COMPONENT_TYPE_CURVE); + } + /* Unused references may have been added above. Remove those now so that other nodes don't + * process them needlessly. */ + instances.remove_unused_references(); + }); + + params.set_output("Instances", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_instance_on_points() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_INSTANCE_ON_POINTS, "Instance on Points", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_instance_on_points_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_instance_on_points_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 93643298f92..3e9b615f478 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -437,7 +437,7 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge /* Retrieve attribute info before moving the splines out of the input components. */ const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info( {(const GeometryComponent **)src_components.data(), src_components.size()}, - {"position", "radius", "tilt", "cyclic", "resolution"}); + {"position", "radius", "tilt", "handle_left", "handle_right", "cyclic", "resolution"}); CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); CurveEval *dst_curve = new CurveEval(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc index 43818947272..780994996ae 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc @@ -30,7 +30,7 @@ static void geo_node_material_assign_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); b.add_input<decl::Material>("Material").hide_label(); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value(); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Geometry"); } @@ -64,23 +64,22 @@ static void geo_node_material_assign_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh *mesh = mesh_component.get_for_write(); + if (mesh != nullptr) { + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; - if (geometry_set.has<MeshComponent>()) { - MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); - Mesh *mesh = mesh_component.get_for_write(); - if (mesh != nullptr) { + fn::FieldEvaluator selection_evaluator{field_context, mesh->totpoly}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; - - fn::FieldEvaluator selection_evaluator{field_context, mesh->totpoly}; - selection_evaluator.add(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - - assign_material_to_faces(*mesh, selection, material); + assign_material_to_faces(*mesh, selection, material); + } } - } + }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc index a9c3bfc6ce0..a917434fa00 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc @@ -40,11 +40,9 @@ static void geo_node_material_replace_exec(GeoNodeExecParams params) Material *new_material = params.extract_input<Material *>("New"); GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); - if (geometry_set.has<MeshComponent>()) { - MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); - Mesh *mesh = mesh_component.get_for_write(); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + Mesh *mesh = geometry_set.get_mesh_for_write(); if (mesh != nullptr) { for (const int i : IndexRange(mesh->totcol)) { if (mesh->mat[i] == old_material) { @@ -52,7 +50,7 @@ static void geo_node_material_replace_exec(GeoNodeExecParams params) } } } - } + }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc index 22c24e34314..9d4533b9bda 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc @@ -31,7 +31,7 @@ namespace blender::nodes { static void geo_node_material_selection_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Material>("Material").hide_label(true); - b.add_output<decl::Bool>("Selection"); + b.add_output<decl::Bool>("Selection").field_source(); } static void select_mesh_by_material(const Mesh &mesh, @@ -100,13 +100,16 @@ class MaterialSelectionFieldInput final : public fn::FieldInput { uint64_t hash() const override { - /* Some random constant hash. */ - return 91619626; + return get_default_hash(material_); } bool is_equal_to(const fn::FieldNode &other) const override { - return dynamic_cast<const MaterialSelectionFieldInput *>(&other) != nullptr; + if (const MaterialSelectionFieldInput *other_material_selection = + dynamic_cast<const MaterialSelectionFieldInput *>(&other)) { + return material_ == other_material_selection->material_; + } + return false; } }; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index 0d58476fc58..059e6e8680c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -29,13 +29,38 @@ namespace blender::nodes { static void geo_node_mesh_primitive_cone_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Vertices").default_value(32).min(3); + b.add_input<decl::Int>("Vertices").default_value(32).min(3).max(512); + b.add_input<decl::Int>("Side Segments").default_value(1).min(1).max(512); + b.add_input<decl::Int>("Fill Segments").default_value(1).min(1).max(512); b.add_input<decl::Float>("Radius Top").min(0.0f).subtype(PROP_DISTANCE); b.add_input<decl::Float>("Radius Bottom").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); b.add_input<decl::Float>("Depth").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); b.add_output<decl::Geometry>("Geometry"); } +static void geo_node_mesh_primitive_cone_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryMeshCone *node_storage = (NodeGeometryMeshCone *)MEM_callocN( + sizeof(NodeGeometryMeshCone), __func__); + + node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + + node->storage = node_storage; +} + +static void geo_node_mesh_primitive_cone_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *vertices_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *rings_socket = vertices_socket->next; + bNodeSocket *fill_subdiv_socket = rings_socket->next; + + const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node->storage; + const GeometryNodeMeshCircleFillType fill_type = + static_cast<const GeometryNodeMeshCircleFillType>(storage.fill_type); + const bool has_fill = fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; + nodeSetSocketAvailability(fill_subdiv_socket, has_fill); +} + static void geo_node_mesh_primitive_cone_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) @@ -45,493 +70,632 @@ static void geo_node_mesh_primitive_cone_layout(uiLayout *layout, uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); } -static void geo_node_mesh_primitive_cone_init(bNodeTree *UNUSED(ntree), bNode *node) +struct ConeConfig { + float radius_top; + float radius_bottom; + float height; + int circle_segments; + int side_segments; + int fill_segments; + GeometryNodeMeshCircleFillType fill_type; + + bool top_is_point; + bool bottom_is_point; + /* The cone tip and a triangle fan filling are topologically identical. + * This simplifies the logic in some cases. */ + bool top_has_center_vert; + bool bottom_has_center_vert; + + /* Helpful quantities. */ + int tot_quad_rings; + int tot_edge_rings; + int tot_verts; + int tot_edges; + + /* Helpful vertex indices. */ + int first_vert; + int first_ring_verts_start; + int last_ring_verts_start; + int last_vert; + + /* Helpful edge indices. */ + int first_ring_edges_start; + int last_ring_edges_start; + int last_fan_edges_start; + int last_edge; + + ConeConfig(float radius_top, + float radius_bottom, + float depth, + int circle_segments, + int side_segments, + int fill_segments, + GeometryNodeMeshCircleFillType fill_type) + : radius_top(radius_top), + radius_bottom(radius_bottom), + height(0.5f * depth), + circle_segments(circle_segments), + side_segments(side_segments), + fill_segments(fill_segments), + fill_type(fill_type) + { + this->top_is_point = this->radius_top == 0.0f; + this->bottom_is_point = this->radius_bottom == 0.0f; + this->top_has_center_vert = this->top_is_point || + this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN; + this->bottom_has_center_vert = this->bottom_is_point || + this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN; + + this->tot_quad_rings = this->calculate_total_quad_rings(); + this->tot_edge_rings = this->calculate_total_edge_rings(); + this->tot_verts = this->calculate_total_verts(); + this->tot_edges = this->calculate_total_edges(); + + this->first_vert = 0; + this->first_ring_verts_start = this->top_has_center_vert ? 1 : first_vert; + this->last_vert = this->tot_verts - 1; + this->last_ring_verts_start = this->last_vert - this->circle_segments; + + this->first_ring_edges_start = this->top_has_center_vert ? this->circle_segments : 0; + this->last_ring_edges_start = this->first_ring_edges_start + + this->tot_quad_rings * this->circle_segments * 2; + this->last_fan_edges_start = this->tot_edges - this->circle_segments; + this->last_edge = this->tot_edges - 1; + } + + private: + int calculate_total_quad_rings(); + int calculate_total_edge_rings(); + int calculate_total_verts(); + int calculate_total_edges(); + + public: + int get_tot_corners() const; + int get_tot_faces() const; +}; + +int ConeConfig::calculate_total_quad_rings() { - NodeGeometryMeshCone *node_storage = (NodeGeometryMeshCone *)MEM_callocN( - sizeof(NodeGeometryMeshCone), __func__); + if (top_is_point && bottom_is_point) { + return 0; + } - node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + int quad_rings = 0; - node->storage = node_storage; + if (!top_is_point) { + quad_rings += fill_segments - 1; + } + + quad_rings += (!top_is_point && !bottom_is_point) ? side_segments : (side_segments - 1); + + if (!bottom_is_point) { + quad_rings += fill_segments - 1; + } + + return quad_rings; } -static int vert_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_edge_rings() { - int vert_total = 0; + if (top_is_point && bottom_is_point) { + return 0; + } + + int edge_rings = 0; + if (!top_is_point) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } + edge_rings += fill_segments; } - else { + + edge_rings += side_segments - 1; + + if (!bottom_is_point) { + edge_rings += fill_segments; + } + + return edge_rings; +} + +int ConeConfig::calculate_total_verts() +{ + if (top_is_point && bottom_is_point) { + return side_segments + 1; + } + + int vert_total = 0; + + if (top_has_center_vert) { vert_total++; } + + if (!top_is_point) { + vert_total += circle_segments * fill_segments; + } + + vert_total += circle_segments * (side_segments - 1); + if (!bottom_is_point) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } + vert_total += circle_segments * fill_segments; } - else { + + if (bottom_has_center_vert) { vert_total++; } return vert_total; } -static int edge_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_edges() { if (top_is_point && bottom_is_point) { - return 1; + return side_segments; } int edge_total = 0; - if (!top_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + if (top_has_center_vert) { + edge_total += circle_segments; } - edge_total += verts_num; + edge_total += circle_segments * (tot_quad_rings * 2 + 1); - if (!bottom_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + if (bottom_has_center_vert) { + edge_total += circle_segments; } return edge_total; } -static int corner_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::get_tot_corners() const { if (top_is_point && bottom_is_point) { return 0; } int corner_total = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } - } - if (!top_is_point && !bottom_is_point) { - corner_total += verts_num * 4; + if (top_has_center_vert) { + corner_total += (circle_segments * 3); } - else { - corner_total += verts_num * 3; + else if (!top_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + corner_total += circle_segments; } - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } + corner_total += tot_quad_rings * (circle_segments * 4); + + if (bottom_has_center_vert) { + corner_total += (circle_segments * 3); + } + else if (!bottom_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + corner_total += circle_segments; } return corner_total; } -static int face_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::get_tot_faces() const { if (top_is_point && bottom_is_point) { return 0; } int face_total = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + if (top_has_center_vert) { + face_total += circle_segments; + } + else if (!top_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + face_total++; } - face_total += verts_num; + face_total += tot_quad_rings * circle_segments; - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + if (bottom_has_center_vert) { + face_total += circle_segments; + } + else if (!bottom_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + face_total++; } return face_total; } -static void calculate_uvs(Mesh *mesh, - const bool top_is_point, - const bool bottom_is_point, - const int verts_num, - const GeometryNodeMeshCircleFillType fill_type) +static void calculate_cone_vertices(const MutableSpan<MVert> &verts, const ConeConfig &config) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttribute_Typed<float2> uv_attribute = - mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); - MutableSpan<float2> uvs = uv_attribute.as_span(); - - Array<float2> circle(verts_num); + Array<float2> circle(config.circle_segments); + const float angle_delta = 2.0f * (M_PI / static_cast<float>(config.circle_segments)); float angle = 0.0f; - const float angle_delta = 2.0f * M_PI / static_cast<float>(verts_num); - for (const int i : IndexRange(verts_num)) { - circle[i].x = std::cos(angle) * 0.225f + 0.25f; - circle[i].y = std::sin(angle) * 0.225f + 0.25f; + for (const int i : IndexRange(config.circle_segments)) { + circle[i].x = std::cos(angle); + circle[i].y = std::sin(angle); angle += angle_delta; } - int loop_index = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i]; - } - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i]; - uvs[loop_index++] = circle[(i + 1) % verts_num]; - uvs[loop_index++] = float2(0.25f, 0.25f); - } - } - } + int vert_index = 0; - /* Create side corners and faces. */ - if (!top_is_point && !bottom_is_point) { - const float bottom = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f; - /* Quads connect the top and bottom. */ - for (const int i : IndexRange(verts_num)) { - const float vert = static_cast<float>(i); - uvs[loop_index++] = float2(vert / verts_num, bottom); - uvs[loop_index++] = float2(vert / verts_num, 1.0f); - uvs[loop_index++] = float2((vert + 1.0f) / verts_num, 1.0f); - uvs[loop_index++] = float2((vert + 1.0f) / verts_num, bottom); - } + /* Top cone tip or triangle fan center. */ + if (config.top_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, config.height); } - else { - /* Triangles connect the top and bottom section. */ - if (!top_is_point) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f); - uvs[loop_index++] = float2(0.75f, 0.25f); - uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f); - } - } - else { - BLI_assert(!bottom_is_point); - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i]; - uvs[loop_index++] = circle[(i + 1) % verts_num]; - uvs[loop_index++] = float2(0.25f, 0.25f); + + /* Top fill including the outer edge of the fill. */ + if (!config.top_is_point) { + const float top_fill_radius_delta = config.radius_top / + static_cast<float>(config.fill_segments); + for (const int i : IndexRange(config.fill_segments)) { + const float top_fill_radius = top_fill_radius_delta * (i + 1); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * top_fill_radius; + const float y = circle[j].y * top_fill_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, config.height); } } } - /* Create bottom corners and faces. */ - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - for (const int i : IndexRange(verts_num)) { - /* Go backwards because of reversed face normal. */ - uvs[loop_index++] = circle[verts_num - 1 - i] + float2(0.5f, 0.0f); - } + /* Rings along the side. */ + const float side_radius_delta = (config.radius_bottom - config.radius_top) / + static_cast<float>(config.side_segments); + const float height_delta = 2.0f * config.height / static_cast<float>(config.side_segments); + for (const int i : IndexRange(config.side_segments - 1)) { + const float ring_radius = config.radius_top + (side_radius_delta * (i + 1)); + const float ring_height = config.height - (height_delta * (i + 1)); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * ring_radius; + const float y = circle[j].y * ring_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, ring_height); } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f); - uvs[loop_index++] = float2(0.75f, 0.25f); - uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f); + } + + /* Bottom fill including the outer edge of the fill. */ + if (!config.bottom_is_point) { + const float bottom_fill_radius_delta = config.radius_bottom / + static_cast<float>(config.fill_segments); + for (const int i : IndexRange(config.fill_segments)) { + const float bottom_fill_radius = config.radius_bottom - (i * bottom_fill_radius_delta); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * bottom_fill_radius; + const float y = circle[j].y * bottom_fill_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, -config.height); } } } - uv_attribute.save(); + /* Bottom cone tip or triangle fan center. */ + if (config.bottom_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, -config.height); + } } -Mesh *create_cylinder_or_cone_mesh(const float radius_top, - const float radius_bottom, - const float depth, - const int verts_num, - const GeometryNodeMeshCircleFillType fill_type) +static void calculate_cone_edges(const MutableSpan<MEdge> &edges, const ConeConfig &config) { - const bool top_is_point = radius_top == 0.0f; - const bool bottom_is_point = radius_bottom == 0.0f; - const float height = depth * 0.5f; - /* Handle the case of a line / single point before everything else to avoid - * the need to check for it later. */ - if (top_is_point && bottom_is_point) { - const bool single_vertex = height == 0.0f; - Mesh *mesh = BKE_mesh_new_nomain(single_vertex ? 1 : 2, single_vertex ? 0 : 1, 0, 0, 0); - copy_v3_v3(mesh->mvert[0].co, float3(0.0f, 0.0f, height)); - if (single_vertex) { - const short up[3] = {0, 0, SHRT_MAX}; - copy_v3_v3_short(mesh->mvert[0].no, up); - return mesh; - } - copy_v3_v3(mesh->mvert[1].co, float3(0.0f, 0.0f, -height)); - mesh->medge[0].v1 = 0; - mesh->medge[0].v2 = 1; - mesh->medge[0].flag |= ME_LOOSEEDGE; - BKE_mesh_normals_tag_dirty(mesh); - return mesh; - } - - Mesh *mesh = BKE_mesh_new_nomain( - vert_total(fill_type, verts_num, top_is_point, bottom_is_point), - edge_total(fill_type, verts_num, top_is_point, bottom_is_point), - 0, - corner_total(fill_type, verts_num, top_is_point, bottom_is_point), - face_total(fill_type, verts_num, top_is_point, bottom_is_point)); - BKE_id_material_eval_ensure_default_slot(&mesh->id); - MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; - MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; - MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; - MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; - - /* Calculate vertex positions. */ - const int top_verts_start = 0; - const int bottom_verts_start = top_verts_start + (!top_is_point ? verts_num : 1); - const float angle_delta = 2.0f * (M_PI / static_cast<float>(verts_num)); - for (const int i : IndexRange(verts_num)) { - const float angle = i * angle_delta; - const float x = std::cos(angle); - const float y = std::sin(angle); - if (!top_is_point) { - copy_v3_v3(verts[top_verts_start + i].co, float3(x * radius_top, y * radius_top, height)); - } - if (!bottom_is_point) { - copy_v3_v3(verts[bottom_verts_start + i].co, - float3(x * radius_bottom, y * radius_bottom, -height)); + int edge_index = 0; + + /* Edges for top cone tip or triangle fan */ + if (config.top_has_center_vert) { + for (const int i : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.first_vert; + edge.v2 = config.first_ring_verts_start + i; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } - if (top_is_point) { - copy_v3_v3(verts[top_verts_start].co, float3(0.0f, 0.0f, height)); - } - if (bottom_is_point) { - copy_v3_v3(verts[bottom_verts_start].co, float3(0.0f, 0.0f, -height)); - } - /* Add center vertices for the triangle fans at the end. */ - const int top_center_vert_index = bottom_verts_start + (bottom_is_point ? 1 : verts_num); - const int bottom_center_vert_index = top_center_vert_index + (top_is_point ? 0 : 1); - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - if (!top_is_point) { - copy_v3_v3(verts[top_center_vert_index].co, float3(0.0f, 0.0f, height)); + /* Rings and connecting edges between the rings. */ + for (const int i : IndexRange(config.tot_edge_rings)) { + const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments); + const int next_ring_vert_start = this_ring_vert_start + config.circle_segments; + /* Edge rings. */ + for (const int j : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = this_ring_vert_start + ((j + 1) % config.circle_segments); + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (!bottom_is_point) { - copy_v3_v3(verts[bottom_center_vert_index].co, float3(0.0f, 0.0f, -height)); + if (i == config.tot_edge_rings - 1) { + /* There is one fewer ring of connecting edges. */ + break; } - } - - /* Create top edges. */ - const int top_edges_start = 0; - const int top_fan_edges_start = (!top_is_point && - fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ? - top_edges_start + verts_num : - top_edges_start; - if (!top_is_point) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[top_edges_start + i]; - edge.v1 = top_verts_start + i; - edge.v2 = top_verts_start + (i + 1) % verts_num; + /* Connecting edges. */ + for (const int j : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = next_ring_vert_start + j; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[top_fan_edges_start + i]; - edge.v1 = top_center_vert_index; - edge.v2 = top_verts_start + i; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } } - /* Create connecting edges. */ - const int connecting_edges_start = top_fan_edges_start + (!top_is_point ? verts_num : 0); - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[connecting_edges_start + i]; - edge.v1 = top_verts_start + (!top_is_point ? i : 0); - edge.v2 = bottom_verts_start + (!bottom_is_point ? i : 0); - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - - /* Create bottom edges. */ - const int bottom_edges_start = connecting_edges_start + verts_num; - const int bottom_fan_edges_start = (!bottom_is_point && - fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ? - bottom_edges_start + verts_num : - bottom_edges_start; - if (!bottom_is_point) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[bottom_edges_start + i]; - edge.v1 = bottom_verts_start + i; - edge.v2 = bottom_verts_start + (i + 1) % verts_num; + /* Edges for bottom triangle fan or tip. */ + if (config.bottom_has_center_vert) { + for (const int i : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.last_ring_verts_start + i; + edge.v2 = config.last_vert; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[bottom_fan_edges_start + i]; - edge.v1 = bottom_center_vert_index; - edge.v2 = bottom_verts_start + i; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } } +} - /* Create top corners and faces. */ +static void calculate_cone_faces(const MutableSpan<MLoop> &loops, + const MutableSpan<MPoly> &polys, + const ConeConfig &config) +{ int loop_index = 0; int poly_index = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + + if (config.top_has_center_vert) { + /* Top cone tip or center triangle fan in the fill. */ + const int top_center_vert = 0; + const int top_fan_edges_start = 0; + + for (const int i : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = verts_num; + poly.totloop = 3; - for (const int i : IndexRange(verts_num)) { - MLoop &loop = loops[loop_index++]; - loop.v = top_verts_start + i; - loop.e = top_edges_start + i; - } + MLoop &loop_a = loops[loop_index++]; + loop_a.v = config.first_ring_verts_start + i; + loop_a.e = config.first_ring_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = config.first_ring_verts_start + ((i + 1) % config.circle_segments); + loop_b.e = top_fan_edges_start + ((i + 1) % config.circle_segments); + MLoop &loop_c = loops[loop_index++]; + loop_c.v = top_center_vert; + loop_c.e = top_fan_edges_start + i; } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { + } + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; + for (const int i : IndexRange(config.circle_segments)) { + MLoop &loop = loops[loop_index++]; + loop.v = i; + loop.e = i; + } + } + + /* Quads connect one edge ring to the next one. */ + if (config.tot_quad_rings > 0) { + for (const int i : IndexRange(config.tot_quad_rings)) { + const int this_ring_vert_start = config.first_ring_verts_start + + (i * config.circle_segments); + const int next_ring_vert_start = this_ring_vert_start + config.circle_segments; + + const int this_ring_edges_start = config.first_ring_edges_start + + (i * 2 * config.circle_segments); + const int next_ring_edges_start = this_ring_edges_start + (2 * config.circle_segments); + const int ring_connections_start = this_ring_edges_start + config.circle_segments; + + for (const int j : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 3; + poly.totloop = 4; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = top_edges_start + i; + loop_a.v = this_ring_vert_start + j; + loop_a.e = ring_connections_start + j; MLoop &loop_b = loops[loop_index++]; - loop_b.v = top_verts_start + (i + 1) % verts_num; - loop_b.e = top_fan_edges_start + (i + 1) % verts_num; + loop_b.v = next_ring_vert_start + j; + loop_b.e = next_ring_edges_start + j; MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_center_vert_index; - loop_c.e = top_fan_edges_start + i; + loop_c.v = next_ring_vert_start + ((j + 1) % config.circle_segments); + loop_c.e = ring_connections_start + ((j + 1) % config.circle_segments); + MLoop &loop_d = loops[loop_index++]; + loop_d.v = this_ring_vert_start + ((j + 1) % config.circle_segments); + loop_d.e = this_ring_edges_start + j; } } } - /* Create side corners and faces. */ - if (!top_is_point && !bottom_is_point) { - /* Quads connect the top and bottom. */ - for (const int i : IndexRange(verts_num)) { + if (config.bottom_has_center_vert) { + /* Bottom cone tip or center triangle fan in the fill. */ + for (const int i : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 4; + poly.totloop = 3; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = connecting_edges_start + i; + loop_a.v = config.last_ring_verts_start + i; + loop_a.e = config.last_fan_edges_start + i; MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start + i; - loop_b.e = bottom_edges_start + i; + loop_b.v = config.last_vert; + loop_b.e = config.last_fan_edges_start + (i + 1) % config.circle_segments; MLoop &loop_c = loops[loop_index++]; - loop_c.v = bottom_verts_start + (i + 1) % verts_num; - loop_c.e = connecting_edges_start + (i + 1) % verts_num; - MLoop &loop_d = loops[loop_index++]; - loop_d.v = top_verts_start + (i + 1) % verts_num; - loop_d.e = top_edges_start + i; + loop_c.v = config.last_ring_verts_start + (i + 1) % config.circle_segments; + loop_c.e = config.last_ring_edges_start + i; } } - else { - /* Triangles connect the top and bottom section. */ - if (!top_is_point) { - for (const int i : IndexRange(verts_num)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 3; + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; - MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = connecting_edges_start + i; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start; - loop_b.e = connecting_edges_start + (i + 1) % verts_num; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_verts_start + (i + 1) % verts_num; - loop_c.e = top_edges_start + i; - } + for (const int i : IndexRange(config.circle_segments)) { + /* Go backwards to reverse surface normal. */ + MLoop &loop = loops[loop_index++]; + loop.v = config.last_vert - i; + loop.e = config.last_edge - ((i + 1) % config.circle_segments); } - else { - BLI_assert(!bottom_is_point); - for (const int i : IndexRange(verts_num)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 3; + } +} - MLoop &loop_a = loops[loop_index++]; - loop_a.v = bottom_verts_start + i; - loop_a.e = bottom_edges_start + i; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start + (i + 1) % verts_num; - loop_b.e = connecting_edges_start + (i + 1) % verts_num; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_verts_start; - loop_c.e = connecting_edges_start + i; +/** + * If the top is the cone tip or has a fill, it is unwrapped into a circle in the + * lower left quadrant of the UV. + * Likewise, if the bottom is the cone tip or has a fill, it is unwrapped into a circle + * in the lower right quadrant of the UV. + * If the mesh is a truncated cone or a cylinder, the side faces are unwrapped into + * a rectangle that fills the top half of the UV (or the entire UV, if there are no fills). + */ +static void calculate_cone_uvs(Mesh *mesh, const ConeConfig &config) +{ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); + + Array<float2> circle(config.circle_segments); + float angle = 0.0f; + const float angle_delta = 2.0f * M_PI / static_cast<float>(config.circle_segments); + for (const int i : IndexRange(config.circle_segments)) { + circle[i].x = std::cos(angle) * 0.225f; + circle[i].y = std::sin(angle) * 0.225f; + angle += angle_delta; + } + + int loop_index = 0; + + /* Left circle of the UV representing the top fill or top cone tip. */ + if (config.top_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) { + const float2 center_left(0.25f, 0.25f); + const float radius_factor_delta = 1.0f / (config.top_is_point ? + static_cast<float>(config.side_segments) : + static_cast<float>(config.fill_segments)); + const int left_circle_segment_count = config.top_is_point ? config.side_segments : + config.fill_segments; + + if (config.top_has_center_vert) { + /* Cone tip itself or triangle fan center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_left; + uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] + + center_left; + uvs[loop_index++] = center_left; + } + } + else if (!config.top_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* N-gon at the center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_left; + } + } + /* The rest of the top fill is made out of quad rings. */ + for (const int i : IndexRange(1, left_circle_segment_count - 1)) { + const float inner_radius_factor = i * radius_factor_delta; + const float outer_radius_factor = (i + 1) * radius_factor_delta; + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = inner_radius_factor * circle[j] + center_left; + uvs[loop_index++] = outer_radius_factor * circle[j] + center_left; + uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] + + center_left; + uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] + + center_left; } } } - /* Create bottom corners and faces. */ - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = verts_num; + if (!config.top_is_point && !config.bottom_is_point) { + /* Mesh is a truncated cone or cylinder. The sides are unwrapped into a rectangle. */ + const float bottom = (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f; + const float x_delta = 1.0f / static_cast<float>(config.circle_segments); + const float y_delta = (1.0f - bottom) / static_cast<float>(config.side_segments); - for (const int i : IndexRange(verts_num)) { - /* Go backwards to reverse surface normal. */ - MLoop &loop = loops[loop_index++]; - loop.v = bottom_verts_start + verts_num - 1 - i; - loop.e = bottom_edges_start + verts_num - 1 - (i + 1) % verts_num; + for (const int i : IndexRange(config.side_segments)) { + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = float2(j * x_delta, i * y_delta + bottom); + uvs[loop_index++] = float2(j * x_delta, (i + 1) * y_delta + bottom); + uvs[loop_index++] = float2((j + 1) * x_delta, (i + 1) * y_delta + bottom); + uvs[loop_index++] = float2((j + 1) * x_delta, i * y_delta + bottom); } } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 3; + } - MLoop &loop_a = loops[loop_index++]; - loop_a.v = bottom_verts_start + i; - loop_a.e = bottom_fan_edges_start + i; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_center_vert_index; - loop_b.e = bottom_fan_edges_start + (i + 1) % verts_num; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = bottom_verts_start + (i + 1) % verts_num; - loop_c.e = bottom_edges_start + i; + /* Right circle of the UV representing the bottom fill or bottom cone tip. */ + if (config.bottom_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) { + const float2 center_right(0.75f, 0.25f); + const float radius_factor_delta = 1.0f / (config.bottom_is_point ? + static_cast<float>(config.side_segments) : + static_cast<float>(config.fill_segments)); + const int right_circle_segment_count = config.bottom_is_point ? config.side_segments : + config.fill_segments; + + /* The bottom circle has to be created outside in to match the loop order. */ + for (const int i : IndexRange(right_circle_segment_count - 1)) { + const float outer_radius_factor = 1.0f - i * radius_factor_delta; + const float inner_radius_factor = 1.0f - (i + 1) * radius_factor_delta; + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = outer_radius_factor * circle[j] + center_right; + uvs[loop_index++] = inner_radius_factor * circle[j] + center_right; + uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] + + center_right; + uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] + + center_right; + } + } + + if (config.bottom_has_center_vert) { + /* Cone tip itself or triangle fan center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_right; + uvs[loop_index++] = center_right; + uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] + + center_right; + } + } + else if (!config.bottom_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* N-gon at the center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + /* Go backwards because of reversed face normal. */ + uvs[loop_index++] = radius_factor_delta * circle[config.circle_segments - 1 - i] + + center_right; } } } + uv_attribute.save(); +} + +static Mesh *create_vertex_mesh() +{ + /* Returns a mesh with a single vertex at the origin. */ + Mesh *mesh = BKE_mesh_new_nomain(1, 0, 0, 0, 0); + copy_v3_fl3(mesh->mvert[0].co, 0.0f, 0.0f, 0.0f); + const short up[3] = {0, 0, SHRT_MAX}; + copy_v3_v3_short(mesh->mvert[0].no, up); + return mesh; +} + +Mesh *create_cylinder_or_cone_mesh(const float radius_top, + const float radius_bottom, + const float depth, + const int circle_segments, + const int side_segments, + const int fill_segments, + const GeometryNodeMeshCircleFillType fill_type) +{ + const ConeConfig config( + radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type); + + /* Handle the case of a line / single point before everything else to avoid + * the need to check for it later. */ + if (config.top_is_point && config.bottom_is_point) { + if (config.height == 0.0f) { + return create_vertex_mesh(); + } + const float z_delta = -2.0f * config.height / static_cast<float>(config.side_segments); + const float3 start(0.0f, 0.0f, config.height); + const float3 delta(0.0f, 0.0f, z_delta); + return create_line_mesh(start, delta, config.tot_verts); + } + + Mesh *mesh = BKE_mesh_new_nomain( + config.tot_verts, config.tot_edges, 0, config.get_tot_corners(), config.get_tot_faces()); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + + calculate_cone_vertices(verts, config); + calculate_cone_edges(edges, config); + calculate_cone_faces(loops, polys, config); + calculate_cone_uvs(mesh, config); + BKE_mesh_normals_tag_dirty(mesh); - calculate_uvs(mesh, top_is_point, bottom_is_point, verts_num, fill_type); + calculate_cone_uvs(mesh, config); return mesh; } @@ -540,23 +704,37 @@ static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params) { const bNode &node = params.node(); const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node.storage; - const GeometryNodeMeshCircleFillType fill_type = (const GeometryNodeMeshCircleFillType) storage.fill_type; - const int verts_num = params.extract_input<int>("Vertices"); - if (verts_num < 3) { + const int circle_segments = params.extract_input<int>("Vertices"); + if (circle_segments < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); params.set_output("Geometry", GeometrySet()); return; } + const int side_segments = params.extract_input<int>("Side Segments"); + if (side_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Side Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + + const bool no_fill = fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE; + const int fill_segments = no_fill ? 1 : params.extract_input<int>("Fill Segments"); + if (fill_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Fill Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + const float radius_top = params.extract_input<float>("Radius Top"); const float radius_bottom = params.extract_input<float>("Radius Bottom"); const float depth = params.extract_input<float>("Depth"); Mesh *mesh = create_cylinder_or_cone_mesh( - radius_top, radius_bottom, depth, verts_num, fill_type); + radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type); /* Transform the mesh so that the base of the cone is at the origin. */ BKE_mesh_translate(mesh, float3(0.0f, 0.0f, depth * 0.5f), false); @@ -572,6 +750,7 @@ void register_node_type_geo_mesh_primitive_cone() geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CONE, "Cone", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_cone_init); + node_type_update(&ntype, blender::nodes::geo_node_mesh_primitive_cone_update); node_type_storage( &ntype, "NodeGeometryMeshCone", node_free_standard_storage, node_copy_standard_storage); ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_cone_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc index ed701c921ca..287ea896ade 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc @@ -29,9 +29,31 @@ namespace blender::nodes { static void geo_node_mesh_primitive_cylinder_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Vertices").default_value(32).min(3).max(4096); - b.add_input<decl::Float>("Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Depth").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Int>("Vertices") + .default_value(32) + .min(3) + .max(512) + .description("The number of vertices around the circumference"); + b.add_input<decl::Int>("Side Segments") + .default_value(1) + .min(1) + .max(512) + .description("The number of segments along the side"); + b.add_input<decl::Int>("Fill Segments") + .default_value(1) + .min(1) + .max(512) + .description("The number of concentric segments of the fill"); + b.add_input<decl::Float>("Radius") + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description("The radius of the cylinder"); + b.add_input<decl::Float>("Depth") + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description("The height of the cylinder on the Z axis"); b.add_output<decl::Geometry>("Geometry"); } @@ -54,6 +76,19 @@ static void geo_node_mesh_primitive_cylinder_init(bNodeTree *UNUSED(ntree), bNod node->storage = node_storage; } +static void geo_node_mesh_primitive_cylinder_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *vertices_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *rings_socket = vertices_socket->next; + bNodeSocket *fill_subdiv_socket = rings_socket->next; + + const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node->storage; + const GeometryNodeMeshCircleFillType fill_type = + static_cast<const GeometryNodeMeshCircleFillType>(storage.fill_type); + const bool has_fill = fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; + nodeSetSocketAvailability(fill_subdiv_socket, has_fill); +} + static void geo_node_mesh_primitive_cylinder_exec(GeoNodeExecParams params) { const bNode &node = params.node(); @@ -64,15 +99,31 @@ static void geo_node_mesh_primitive_cylinder_exec(GeoNodeExecParams params) const float radius = params.extract_input<float>("Radius"); const float depth = params.extract_input<float>("Depth"); - const int verts_num = params.extract_input<int>("Vertices"); - if (verts_num < 3) { + const int circle_segments = params.extract_input<int>("Vertices"); + if (circle_segments < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); params.set_output("Geometry", GeometrySet()); return; } + const int side_segments = params.extract_input<int>("Side Segments"); + if (side_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Side Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + + const bool no_fill = fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE; + const int fill_segments = no_fill ? 1 : params.extract_input<int>("Fill Segments"); + if (fill_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Fill Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + /* The cylinder is a special case of the cone mesh where the top and bottom radius are equal. */ - Mesh *mesh = create_cylinder_or_cone_mesh(radius, radius, depth, verts_num, fill_type); + Mesh *mesh = create_cylinder_or_cone_mesh( + radius, radius, depth, circle_segments, side_segments, fill_segments, fill_type); params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); } @@ -84,6 +135,7 @@ void register_node_type_geo_mesh_primitive_cylinder() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CYLINDER, "Cylinder", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_cylinder_init); + node_type_update(&ntype, blender::nodes::geo_node_mesh_primitive_cylinder_update); node_type_storage( &ntype, "NodeGeometryMeshCylinder", node_free_standard_storage, node_copy_standard_storage); ntype.declare = blender::nodes::geo_node_mesh_primitive_cylinder_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc index 9ca74fed9a7..c436f5bd480 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc @@ -32,28 +32,9 @@ static void geo_node_mesh_subdivide_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Geometry"); } -static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) +static void geometry_set_mesh_subdivide(GeometrySet &geometry_set, const int level) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_mesh()) { - params.set_output("Geometry", geometry_set); - return; - } - -#ifndef WITH_OPENSUBDIV - params.error_message_add(NodeWarningType::Error, - TIP_("Disabled, Blender was compiled without OpenSubdiv")); - params.set_output("Geometry", std::move(geometry_set)); - return; -#endif - - /* See CCGSUBSURF_LEVEL_MAX for max limit. */ - const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 11); - - if (subdiv_level == 0) { - params.set_output("Geometry", std::move(geometry_set)); return; } @@ -61,7 +42,7 @@ static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) /* Initialize mesh settings. */ SubdivToMeshSettings mesh_settings; - mesh_settings.resolution = (1 << subdiv_level) + 1; + mesh_settings.resolution = (1 << level) + 1; mesh_settings.use_optimal_display = false; /* Initialize subdivision settings. */ @@ -79,7 +60,6 @@ static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) /* In case of bad topology, skip to input mesh. */ if (subdiv == nullptr) { - params.set_output("Geometry", std::move(geometry_set)); return; } @@ -90,6 +70,29 @@ static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) mesh_component.replace(mesh_out); BKE_subdiv_free(subdiv); +} + +static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + +#ifndef WITH_OPENSUBDIV + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without OpenSubdiv")); + params.set_output("Geometry", std::move(geometry_set)); + return; +#endif + + /* See CCGSUBSURF_LEVEL_MAX for max limit. */ + const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 11); + + if (subdiv_level == 0) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + geometry_set.modify_geometry_sets( + [&](GeometrySet &geometry_set) { geometry_set_mesh_subdivide(geometry_set, subdiv_level); }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc new file mode 100644 index 00000000000..6863f685eae --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -0,0 +1,189 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_pointcloud.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::Array; + +namespace blender::nodes { + +static void geo_node_mesh_to_points_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Mesh"); + b.add_input<decl::Vector>("Position").implicit_field(); + b.add_input<decl::Float>("Radius") + .default_value(0.05f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field(); + b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); + b.add_output<decl::Geometry>("Points"); +} + +static void geo_node_mesh_to_points_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void geo_node_mesh_to_points_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryMeshToPoints *data = (NodeGeometryMeshToPoints *)MEM_callocN( + sizeof(NodeGeometryMeshToPoints), __func__); + data->mode = GEO_NODE_MESH_TO_POINTS_VERTICES; + node->storage = data; +} + +template<typename T> +static void copy_attribute_to_points(const VArray<T> &src, + const IndexMask mask, + MutableSpan<T> dst) +{ + for (const int i : mask.index_range()) { + dst[i] = src[mask[i]]; + } +} + +static void geometry_set_mesh_to_points(GeometrySet &geometry_set, + Field<float3> &position_field, + Field<float> &radius_field, + Field<bool> &selection_field, + const AttributeDomain domain) +{ + const MeshComponent *mesh_component = geometry_set.get_component_for_read<MeshComponent>(); + if (mesh_component == nullptr) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + GeometryComponentFieldContext field_context{*mesh_component, domain}; + const int domain_size = mesh_component->attribute_domain_size(domain); + if (domain_size == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); + uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); + geometry_set.replace_pointcloud(pointcloud); + PointCloudComponent &point_component = + geometry_set.get_component_for_write<PointCloudComponent>(); + + /* Evaluating directly into the point cloud doesn't work because we are not using the full + * "min_array_size" array but compressing the selected elements into the final array with no + * gaps. */ + fn::FieldEvaluator evaluator{field_context, &selection}; + evaluator.add(position_field); + evaluator.add(radius_field); + evaluator.evaluate(); + copy_attribute_to_points(evaluator.get_evaluated<float3>(0), + selection, + {(float3 *)pointcloud->co, pointcloud->totpoint}); + copy_attribute_to_points( + evaluator.get_evaluated<float>(1), selection, {pointcloud->radius, pointcloud->totpoint}); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + attributes.remove("position"); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType data_type = entry.value.data_type; + GVArrayPtr src = mesh_component->attribute_get_for_read(attribute_id, domain, data_type); + OutputAttribute dst = point_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> src_typed{*src}; + copy_attribute_to_points(*src_typed, selection, dst.as_span().typed<T>()); + }); + dst.save(); + } + } + + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); +} + +static void geo_node_mesh_to_points_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + Field<float3> position = params.extract_input<Field<float3>>("Position"); + Field<float> radius = params.extract_input<Field<float>>("Radius"); + Field<bool> selection = params.extract_input<Field<bool>>("Selection"); + + /* Use another multi-function operation to make sure the input radius is greater than zero. + * TODO: Use mutable multi-function once that is supported. */ + static fn::CustomMF_SI_SO<float, float> max_zero_fn( + __func__, [](float value) { return std::max(0.0f, value); }); + auto max_zero_op = std::make_shared<FieldOperation>( + FieldOperation(max_zero_fn, {std::move(radius)})); + Field<float> positive_radius(std::move(max_zero_op), 0); + + const NodeGeometryMeshToPoints &storage = + *(const NodeGeometryMeshToPoints *)params.node().storage; + const GeometryNodeMeshToPointsMode mode = (GeometryNodeMeshToPointsMode)storage.mode; + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + switch (mode) { + case GEO_NODE_MESH_TO_POINTS_VERTICES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_POINT); + break; + case GEO_NODE_MESH_TO_POINTS_EDGES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_EDGE); + break; + case GEO_NODE_MESH_TO_POINTS_FACES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_FACE); + break; + case GEO_NODE_MESH_TO_POINTS_CORNERS: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_CORNER); + break; + } + }); + + params.set_output("Points", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_mesh_to_points() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MESH_TO_POINTS, "Mesh to Points", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_mesh_to_points_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_points_exec; + node_type_init(&ntype, blender::nodes::geo_node_mesh_to_points_init); + ntype.draw_buttons = blender::nodes::geo_node_mesh_to_points_layout; + node_type_storage( + &ntype, "NodeGeometryMeshToPoints", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc new file mode 100644 index 00000000000..afd0ced6360 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -0,0 +1,118 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +using blender::Array; + +namespace blender::nodes { + +static void geo_node_points_to_vertices_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Points"); + b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); + b.add_output<decl::Geometry>("Mesh"); +} + +template<typename T> +static void copy_attribute_to_vertices(const Span<T> src, const IndexMask mask, MutableSpan<T> dst) +{ + for (const int i : mask.index_range()) { + dst[i] = src[mask[i]]; + } +} + +/* One improvement would be to move the attribute arrays directly to the mesh when possible. */ +static void geometry_set_points_to_vertices(GeometrySet &geometry_set, + Field<bool> &selection_field) +{ + const PointCloudComponent *point_component = + geometry_set.get_component_for_read<PointCloudComponent>(); + if (point_component == nullptr) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + GeometryComponentFieldContext field_context{*point_component, ATTR_DOMAIN_POINT}; + const int domain_size = point_component->attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_MESH, false, attributes); + + Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0, 0); + geometry_set.replace_mesh(mesh); + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType data_type = entry.value.data_type; + GVArrayPtr src = point_component->attribute_get_for_read( + attribute_id, ATTR_DOMAIN_POINT, data_type); + OutputAttribute dst = mesh_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> src_typed{*src}; + VArray_Span<T> src_typed_span{*src_typed}; + copy_attribute_to_vertices(src_typed_span, selection, dst.as_span().typed<T>()); + }); + dst.save(); + } + } + + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); +} + +static void geo_node_points_to_vertices_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + geometry_set_points_to_vertices(geometry_set, selection_field); + }); + + params.set_output("Mesh", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_points_to_vertices() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_POINTS_TO_VERTICES, "Points to Vertices", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_points_to_vertices_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_points_to_vertices_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_proximity.cc new file mode 100644 index 00000000000..7062deff2f1 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_proximity.cc @@ -0,0 +1,235 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_geometry_set.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_proximity_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Target"); + b.add_input<decl::Vector>("Source Position").implicit_field(); + b.add_output<decl::Vector>("Position").dependent_field(); + b.add_output<decl::Float>("Distance").dependent_field(); +} + +static void geo_node_proximity_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "target_element", 0, "", ICON_NONE); +} + +static void geo_proximity_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryProximity *node_storage = (NodeGeometryProximity *)MEM_callocN( + sizeof(NodeGeometryProximity), __func__); + node_storage->target_element = GEO_NODE_PROX_TARGET_FACES; + node->storage = node_storage; +} + +static void calculate_mesh_proximity(const VArray<float3> &positions, + const IndexMask mask, + const Mesh &mesh, + const GeometryNodeProximityTargetType type, + const MutableSpan<float> r_distances, + const MutableSpan<float3> r_locations) +{ + BVHTreeFromMesh bvh_data; + switch (type) { + case GEO_NODE_PROX_TARGET_POINTS: + BKE_bvhtree_from_mesh_get(&bvh_data, &mesh, BVHTREE_FROM_VERTS, 2); + break; + case GEO_NODE_PROX_TARGET_EDGES: + BKE_bvhtree_from_mesh_get(&bvh_data, &mesh, BVHTREE_FROM_EDGES, 2); + break; + case GEO_NODE_PROX_TARGET_FACES: + BKE_bvhtree_from_mesh_get(&bvh_data, &mesh, BVHTREE_FROM_LOOPTRI, 2); + break; + } + + if (bvh_data.tree == nullptr) { + return; + } + + threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) { + BVHTreeNearest nearest; + copy_v3_fl(nearest.co, FLT_MAX); + nearest.index = -1; + + for (int i : range) { + const int index = mask[i]; + /* Use the distance to the last found point as upper bound to speedup the bvh lookup. */ + nearest.dist_sq = float3::distance_squared(nearest.co, positions[index]); + + BLI_bvhtree_find_nearest( + bvh_data.tree, positions[index], &nearest, bvh_data.nearest_callback, &bvh_data); + + if (nearest.dist_sq < r_distances[index]) { + r_distances[index] = nearest.dist_sq; + if (!r_locations.is_empty()) { + r_locations[index] = nearest.co; + } + } + } + }); + + free_bvhtree_from_mesh(&bvh_data); +} + +static void calculate_pointcloud_proximity(const VArray<float3> &positions, + const IndexMask mask, + const PointCloud &pointcloud, + const MutableSpan<float> r_distances, + const MutableSpan<float3> r_locations) +{ + BVHTreeFromPointCloud bvh_data; + BKE_bvhtree_from_pointcloud_get(&bvh_data, &pointcloud, 2); + if (bvh_data.tree == nullptr) { + return; + } + + threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) { + BVHTreeNearest nearest; + copy_v3_fl(nearest.co, FLT_MAX); + nearest.index = -1; + + for (int i : range) { + const int index = mask[i]; + /* Use the distance to the closest point in the mesh to speedup the pointcloud bvh lookup. + * This is ok because we only need to find the closest point in the pointcloud if it's + * closer than the mesh. */ + nearest.dist_sq = r_distances[index]; + + BLI_bvhtree_find_nearest( + bvh_data.tree, positions[index], &nearest, bvh_data.nearest_callback, &bvh_data); + + if (nearest.dist_sq < r_distances[index]) { + r_distances[index] = nearest.dist_sq; + if (!r_locations.is_empty()) { + r_locations[index] = nearest.co; + } + } + } + }); + + free_bvhtree_from_pointcloud(&bvh_data); +} + +class ProximityFunction : public fn::MultiFunction { + private: + GeometrySet target_; + GeometryNodeProximityTargetType type_; + + public: + ProximityFunction(GeometrySet target, GeometryNodeProximityTargetType type) + : target_(std::move(target)), type_(type) + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Geometry Proximity"}; + signature.single_input<float3>("Source Position"); + signature.single_output<float3>("Position"); + signature.single_output<float>("Distance"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &src_positions = params.readonly_single_input<float3>(0, + "Source Position"); + MutableSpan<float3> positions = params.uninitialized_single_output_if_required<float3>( + 1, "Position"); + /* Make sure there is a distance array, used for finding the smaller distance when there are + * multiple components. Theoretically it would be possible to avoid using the distance array + * when there is only one component. However, this only adds an allocation and a single float + * comparison per vertex, so it's likely not worth it. */ + MutableSpan<float> distances = params.uninitialized_single_output<float>(2, "Distance"); + + distances.fill(FLT_MAX); + + if (target_.has_mesh()) { + calculate_mesh_proximity( + src_positions, mask, *target_.get_mesh_for_read(), type_, distances, positions); + } + + if (target_.has_pointcloud() && type_ == GEO_NODE_PROX_TARGET_POINTS) { + calculate_pointcloud_proximity( + src_positions, mask, *target_.get_pointcloud_for_read(), distances, positions); + } + + if (params.single_output_is_required(2, "Distance")) { + threading::parallel_for(mask.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + const int j = mask[i]; + distances[j] = std::sqrt(distances[j]); + } + }); + } + } +}; + +static void geo_node_proximity_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_target = params.extract_input<GeometrySet>("Target"); + + if (!geometry_set_target.has_mesh() && !geometry_set_target.has_pointcloud()) { + params.set_output("Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Distance", fn::make_constant_field<float>({0.0f})); + return; + } + + const NodeGeometryProximity &storage = *(const NodeGeometryProximity *)params.node().storage; + Field<float3> position_field = params.extract_input<Field<float3>>("Source Position"); + + auto proximity_fn = std::make_unique<ProximityFunction>( + std::move(geometry_set_target), + static_cast<GeometryNodeProximityTargetType>(storage.target_element)); + auto proximity_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(proximity_fn), {std::move(position_field)})); + + params.set_output("Position", Field<float3>(proximity_op, 0)); + params.set_output("Distance", Field<float>(proximity_op, 1)); +} + +} // namespace blender::nodes + +void register_node_type_geo_proximity() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_PROXIMITY, "Geometry Proximity", NODE_CLASS_GEOMETRY, 0); + node_type_init(&ntype, blender::nodes::geo_proximity_init); + node_type_storage( + &ntype, "NodeGeometryProximity", node_free_standard_storage, node_copy_standard_storage); + ntype.declare = blender::nodes::geo_node_proximity_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_proximity_exec; + ntype.draw_buttons = blender::nodes::geo_node_proximity_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc index c63e4ec49d9..dafd10cee2d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc @@ -25,20 +25,18 @@ static void geo_node_join_geometry_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Point Cloud"); b.add_output<decl::Geometry>("Curve"); b.add_output<decl::Geometry>("Volume"); + b.add_output<decl::Geometry>("Instances"); } static void geo_node_separate_components_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - /* Note that it will be possible to skip realizing instances here when instancing - * geometry directly is supported by creating corresponding geometry instances. */ - geometry_set = bke::geometry_set_realize_instances(geometry_set); - GeometrySet meshes; GeometrySet point_clouds; GeometrySet volumes; GeometrySet curves; + GeometrySet instances; if (geometry_set.has<MeshComponent>()) { meshes.add(*geometry_set.get_component_for_read<MeshComponent>()); @@ -52,11 +50,15 @@ static void geo_node_separate_components_exec(GeoNodeExecParams params) if (geometry_set.has<VolumeComponent>()) { volumes.add(*geometry_set.get_component_for_read<VolumeComponent>()); } + if (geometry_set.has<InstancesComponent>()) { + instances.add(*geometry_set.get_component_for_read<InstancesComponent>()); + } params.set_output("Mesh", meshes); params.set_output("Point Cloud", point_clouds); params.set_output("Curve", curves); params.set_output("Volume", volumes); + params.set_output("Instances", instances); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc index c5e10b788ac..8caf961fc04 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc @@ -23,8 +23,8 @@ namespace blender::nodes { static void geo_node_set_position_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Vector>("Position").hide_value(); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value(); + b.add_input<decl::Vector>("Position").implicit_field(); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Geometry"); } @@ -34,6 +34,9 @@ static void set_position_in_component(GeometryComponent &component, { GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + return; + } fn::FieldEvaluator selection_evaluator{field_context, domain_size}; selection_evaluator.add(selection_field); diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc new file mode 100644 index 00000000000..5e2f03806c3 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc @@ -0,0 +1,306 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_curve_types.h" +#include "DNA_vfont_types.h" + +#include "BKE_curve.h" +#include "BKE_font.h" +#include "BKE_spline.hh" + +#include "BLI_hash.h" +#include "BLI_string_utf8.h" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_string_to_curves_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("String"); + b.add_input<decl::Float>("Size").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Character Spacing") + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Word Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Line Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Text Box Width").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Text Box Height").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>("Curves"); + b.add_output<decl::String>("Remainder"); +} + +static void geo_node_string_to_curves_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiTemplateID(layout, + C, + ptr, + "font", + nullptr, + "FONT_OT_open", + "FONT_OT_unlink", + UI_TEMPLATE_ID_FILTER_ALL, + false, + nullptr); + uiItemR(layout, ptr, "overflow", 0, "", ICON_NONE); + uiItemR(layout, ptr, "align_x", 0, "", ICON_NONE); + uiItemR(layout, ptr, "align_y", 0, "", ICON_NONE); +} + +static void geo_node_string_to_curves_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryStringToCurves *data = (NodeGeometryStringToCurves *)MEM_callocN( + sizeof(NodeGeometryStringToCurves), __func__); + + data->overflow = GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW; + data->align_x = GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT; + data->align_y = GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE; + node->storage = data; + node->id = (ID *)BKE_vfont_builtin_get(); +} + +static void geo_node_string_to_curves_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryStringToCurves *storage = (const NodeGeometryStringToCurves *)node->storage; + const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) + storage->overflow; + bNodeSocket *socket_remainder = ((bNodeSocket *)node->outputs.first)->next; + nodeSetSocketAvailability(socket_remainder, overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE); + + bNodeSocket *height_socket = (bNodeSocket *)node->inputs.last; + bNodeSocket *width_socket = height_socket->prev; + nodeSetSocketAvailability(height_socket, overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); + node_sock_label(width_socket, + overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ? N_("Max Width") : + N_("Text Box Width")); +} + +struct TextLayout { + /* Position of each character. */ + Vector<float2> positions; + + /* The text that fit into the text box, with newline character sequences replaced. */ + std::string text; + + /* The text that didn't fit into the text box in 'Truncate' mode. May be empty. */ + std::string truncated_text; + + /* Font size could be modified if in 'Scale to fit'-mode. */ + float final_font_size; +}; + +static TextLayout get_text_layout(GeoNodeExecParams ¶ms) +{ + TextLayout layout; + layout.text = params.extract_input<std::string>("String"); + if (layout.text.empty()) { + return {}; + } + + const NodeGeometryStringToCurves &storage = + *(const NodeGeometryStringToCurves *)params.node().storage; + const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) + storage.overflow; + const GeometryNodeStringToCurvesAlignXMode align_x = (GeometryNodeStringToCurvesAlignXMode) + storage.align_x; + const GeometryNodeStringToCurvesAlignYMode align_y = (GeometryNodeStringToCurvesAlignYMode) + storage.align_y; + + const float font_size = std::max(params.extract_input<float>("Size"), 0.0f); + const float char_spacing = params.extract_input<float>("Character Spacing"); + const float word_spacing = params.extract_input<float>("Word Spacing"); + const float line_spacing = params.extract_input<float>("Line Spacing"); + const float textbox_w = params.extract_input<float>("Text Box Width"); + const float textbox_h = overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ? + 0.0f : + params.extract_input<float>("Text Box Height"); + VFont *vfont = (VFont *)params.node().id; + + Curve cu = {nullptr}; + cu.type = OB_FONT; + /* Set defaults */ + cu.resolu = 12; + cu.smallcaps_scale = 0.75f; + cu.wordspace = 1.0f; + /* Set values from inputs */ + cu.spacemode = align_x; + cu.align_y = align_y; + cu.fsize = font_size; + cu.spacing = char_spacing; + cu.wordspace = word_spacing; + cu.linedist = line_spacing; + cu.vfont = vfont; + cu.overflow = overflow; + cu.tb = (TextBox *)MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), __func__); + cu.tb->w = textbox_w; + cu.tb->h = textbox_h; + cu.totbox = 1; + size_t len_bytes; + size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes); + cu.len_char32 = len_chars; + cu.len = len_bytes; + cu.pos = len_chars; + /* The reason for the additional character here is unknown, but reflects other code elsewhere. */ + cu.str = (char *)MEM_mallocN(len_bytes + sizeof(char32_t), __func__); + cu.strinfo = (CharInfo *)MEM_callocN((len_chars + 1) * sizeof(CharInfo), __func__); + BLI_strncpy(cu.str, layout.text.c_str(), len_bytes + 1); + + struct CharTrans *chartransdata = nullptr; + int text_len; + bool text_free; + const char32_t *r_text = nullptr; + /* Mode FO_DUPLI used because it doesn't create curve splines. */ + BKE_vfont_to_curve_ex( + nullptr, &cu, FO_DUPLI, nullptr, &r_text, &text_len, &text_free, &chartransdata); + + if (text_free) { + MEM_freeN((void *)r_text); + } + + Span<CharInfo> info{cu.strinfo, text_len}; + layout.final_font_size = cu.fsize_realtime; + layout.positions.reserve(text_len); + + for (const int i : IndexRange(text_len)) { + CharTrans &ct = chartransdata[i]; + layout.positions.append(float2(ct.xof, ct.yof) * layout.final_font_size); + + if ((info[i].flag & CU_CHINFO_OVERFLOW) && (cu.overflow == CU_OVERFLOW_TRUNCATE)) { + const int offset = BLI_str_utf8_offset_from_index(layout.text.c_str(), i + 1); + layout.truncated_text = layout.text.substr(offset); + layout.text = layout.text.substr(0, offset); + break; + } + } + + MEM_SAFE_FREE(chartransdata); + MEM_SAFE_FREE(cu.str); + MEM_SAFE_FREE(cu.strinfo); + MEM_SAFE_FREE(cu.tb); + + return layout; +} + +/* Returns a mapping of UTF-32 character code to instance handle. */ +static Map<int, int> create_curve_instances(GeoNodeExecParams ¶ms, + const float fontsize, + const Span<char32_t> charcodes, + InstancesComponent &instance_component) +{ + VFont *vfont = (VFont *)params.node().id; + Map<int, int> handles; + + for (int i : charcodes.index_range()) { + if (handles.contains(charcodes[i])) { + continue; + } + Curve cu = {nullptr}; + cu.type = OB_FONT; + cu.resolu = 12; + cu.vfont = vfont; + CharInfo charinfo = {0}; + charinfo.mat_nr = 1; + + BKE_vfont_build_char(&cu, &cu.nurb, charcodes[i], &charinfo, 0, 0, 0, i, 1); + std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve(cu); + BKE_nurbList_free(&cu.nurb); + float4x4 size_matrix = float4x4::identity(); + size_matrix.apply_scale(fontsize); + curve_eval->transform(size_matrix); + + GeometrySet geometry_set_curve = GeometrySet::create_with_curve(curve_eval.release()); + handles.add_new(charcodes[i], instance_component.add_reference(std::move(geometry_set_curve))); + } + return handles; +} + +static void add_instances_from_handles(InstancesComponent &instances, + const Map<int, int> &char_handles, + const Span<char32_t> charcodes, + const Span<float2> positions) +{ + instances.resize(positions.size()); + MutableSpan<int> handles = instances.instance_reference_handles(); + MutableSpan<float4x4> transforms = instances.instance_transforms(); + MutableSpan<int> instance_ids = instances.instance_ids(); + + threading::parallel_for(IndexRange(positions.size()), 256, [&](IndexRange range) { + for (const int i : range) { + handles[i] = char_handles.lookup(charcodes[i]); + transforms[i] = float4x4::from_location({positions[i].x, positions[i].y, 0}); + instance_ids[i] = i; + } + }); +} + +static void geo_node_string_to_curves_exec(GeoNodeExecParams params) +{ + TextLayout layout = get_text_layout(params); + + const NodeGeometryStringToCurves &storage = + *(const NodeGeometryStringToCurves *)params.node().storage; + if (storage.overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE) { + params.set_output("Remainder", std::move(layout.truncated_text)); + } + + if (layout.positions.size() == 0) { + params.set_output("Curves", GeometrySet()); + return; + } + + /* Convert UTF-8 encoded string to UTF-32. */ + size_t len_bytes; + size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes); + Array<char32_t> char_codes(len_chars + 1); + BLI_str_utf8_as_utf32(char_codes.data(), layout.text.c_str(), len_chars + 1); + + /* Create and add instances. */ + GeometrySet geometry_set_out; + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + Map<int, int> char_handles = create_curve_instances( + params, layout.final_font_size, char_codes, instances); + add_instances_from_handles(instances, char_handles, char_codes, layout.positions); + + params.set_output("Curves", std::move(geometry_set_out)); +} + +} // namespace blender::nodes + +void register_node_type_geo_string_to_curves() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_STRING_TO_CURVES, "String to Curves", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_string_to_curves_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_string_to_curves_exec; + node_type_init(&ntype, blender::nodes::geo_node_string_to_curves_init); + node_type_update(&ntype, blender::nodes::geo_node_string_to_curves_update); + node_type_size(&ntype, 190, 120, 700); + node_type_storage(&ntype, + "NodeGeometryStringToCurves", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_string_to_curves_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc index ca857c4d2e3..05df927fb39 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -19,24 +19,37 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +#include "FN_multi_function_signature.hh" + namespace blender::nodes { static void geo_node_switch_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Bool>("Switch"); - - b.add_input<decl::Float>("False"); - b.add_input<decl::Float>("True"); - b.add_input<decl::Int>("False", "False_001").min(-100000).max(100000); - b.add_input<decl::Int>("True", "True_001").min(-100000).max(100000); - b.add_input<decl::Bool>("False", "False_002"); - b.add_input<decl::Bool>("True", "True_002"); - b.add_input<decl::Vector>("False", "False_003"); - b.add_input<decl::Vector>("True", "True_003"); - b.add_input<decl::Color>("False", "False_004").default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::Color>("True", "True_004").default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::String>("False", "False_005"); - b.add_input<decl::String>("True", "True_005"); + b.add_input<decl::Bool>("Switch").default_value(false).supports_field(); + b.add_input<decl::Bool>("Switch", "Switch_001").default_value(false); + + b.add_input<decl::Float>("False").supports_field(); + b.add_input<decl::Float>("True").supports_field(); + b.add_input<decl::Int>("False", "False_001").min(-100000).max(100000).supports_field(); + b.add_input<decl::Int>("True", "True_001").min(-100000).max(100000).supports_field(); + b.add_input<decl::Bool>("False", "False_002").default_value(false).hide_value().supports_field(); + b.add_input<decl::Bool>("True", "True_002").default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>("False", "False_003").supports_field(); + b.add_input<decl::Vector>("True", "True_003").supports_field(); + b.add_input<decl::Color>("False", "False_004") + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .supports_field(); + b.add_input<decl::Color>("True", "True_004") + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .supports_field(); + b.add_input<decl::String>("False", "False_005").supports_field(); + b.add_input<decl::String>("True", "True_005").supports_field(); + b.add_input<decl::Geometry>("False", "False_006"); b.add_input<decl::Geometry>("True", "True_006"); b.add_input<decl::Object>("False", "False_007"); @@ -48,12 +61,12 @@ static void geo_node_switch_declare(NodeDeclarationBuilder &b) b.add_input<decl::Material>("False", "False_010"); b.add_input<decl::Material>("True", "True_010"); - b.add_output<decl::Float>("Output"); - b.add_output<decl::Int>("Output", "Output_001"); - b.add_output<decl::Bool>("Output", "Output_002"); - b.add_output<decl::Vector>("Output", "Output_003"); - b.add_output<decl::Color>("Output", "Output_004"); - b.add_output<decl::String>("Output", "Output_005"); + b.add_output<decl::Float>("Output").dependent_field(); + b.add_output<decl::Int>("Output", "Output_001").dependent_field(); + b.add_output<decl::Bool>("Output", "Output_002").dependent_field(); + b.add_output<decl::Vector>("Output", "Output_003").dependent_field(); + b.add_output<decl::Color>("Output", "Output_004").dependent_field(); + b.add_output<decl::String>("Output", "Output_005").dependent_field(); b.add_output<decl::Geometry>("Output", "Output_006"); b.add_output<decl::Object>("Output", "Output_007"); b.add_output<decl::Collection>("Output", "Output_008"); @@ -77,91 +90,168 @@ static void geo_node_switch_update(bNodeTree *UNUSED(ntree), bNode *node) { NodeSwitch *node_storage = (NodeSwitch *)node->storage; int index = 0; - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - nodeSetSocketAvailability( - socket, index == 0 || socket->type == (eNodeSocketDatatype)node_storage->input_type); - index++; + bNodeSocket *field_switch = (bNodeSocket *)node->inputs.first; + bNodeSocket *non_field_switch = (bNodeSocket *)field_switch->next; + + const bool fields_type = ELEM((eNodeSocketDatatype)node_storage->input_type, + SOCK_FLOAT, + SOCK_INT, + SOCK_BOOLEAN, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_STRING); + + nodeSetSocketAvailability(field_switch, fields_type); + nodeSetSocketAvailability(non_field_switch, !fields_type); + + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &node->inputs, index) { + if (index <= 1) { + continue; + } + nodeSetSocketAvailability(socket, + socket->type == (eNodeSocketDatatype)node_storage->input_type); } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { nodeSetSocketAvailability(socket, socket->type == (eNodeSocketDatatype)node_storage->input_type); } } -template<typename T> -static void output_input(GeoNodeExecParams ¶ms, - const bool input, - const StringRef input_suffix, - const StringRef output_identifier) +template<typename T> class SwitchFieldsFunction : public fn::MultiFunction { + public: + SwitchFieldsFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Switch"}; + signature.single_input<bool>("Switch"); + signature.single_input<T>("False"); + signature.single_input<T>("True"); + signature.single_output<T>("Output"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<bool> &switches = params.readonly_single_input<bool>(0, "Switch"); + const VArray<T> &falses = params.readonly_single_input<T>(1, "False"); + const VArray<T> &trues = params.readonly_single_input<T>(2, "True"); + MutableSpan<T> values = params.uninitialized_single_output_if_required<T>(3, "Output"); + for (int64_t i : mask) { + new (&values[i]) T(switches[i] ? trues[i] : falses[i]); + } + } +}; + +template<typename T> void switch_fields(GeoNodeExecParams ¶ms, const StringRef suffix) +{ + if (params.lazy_require_input("Switch")) { + return; + } + + const std::string name_false = "False" + suffix; + const std::string name_true = "True" + suffix; + const std::string name_output = "Output" + suffix; + + /* TODO: Allow for Laziness when the switch field is constant. */ + const bool require_false = params.lazy_require_input(name_false); + const bool require_true = params.lazy_require_input(name_true); + if (require_false | require_true) { + return; + } + + Field<bool> switches_field = params.extract_input<Field<bool>>("Switch"); + Field<T> falses_field = params.extract_input<Field<T>>(name_false); + Field<T> trues_field = params.extract_input<Field<T>>(name_true); + + auto switch_fn = std::make_unique<SwitchFieldsFunction<T>>(); + auto switch_op = std::make_shared<FieldOperation>(FieldOperation( + std::move(switch_fn), + {std::move(switches_field), std::move(falses_field), std::move(trues_field)})); + + params.set_output(name_output, Field<T>(switch_op, 0)); +} + +template<typename T> void switch_no_fields(GeoNodeExecParams ¶ms, const StringRef suffix) { - const std::string name_a = "False" + input_suffix; - const std::string name_b = "True" + input_suffix; - if (input) { - params.set_input_unused(name_a); - if (params.lazy_require_input(name_b)) { + if (params.lazy_require_input("Switch_001")) { + return; + } + bool switch_value = params.get_input<bool>("Switch_001"); + + const std::string name_false = "False" + suffix; + const std::string name_true = "True" + suffix; + const std::string name_output = "Output" + suffix; + + if (switch_value) { + params.set_input_unused(name_false); + if (params.lazy_require_input(name_true)) { return; } - params.set_output(output_identifier, params.extract_input<T>(name_b)); + params.set_output(name_output, params.extract_input<T>(name_true)); } else { - params.set_input_unused(name_b); - if (params.lazy_require_input(name_a)) { + params.set_input_unused(name_true); + if (params.lazy_require_input(name_false)) { return; } - params.set_output(output_identifier, params.extract_input<T>(name_a)); + params.set_output(name_output, params.extract_input<T>(name_false)); } } static void geo_node_switch_exec(GeoNodeExecParams params) { - if (params.lazy_require_input("Switch")) { - return; - } const NodeSwitch &storage = *(const NodeSwitch *)params.node().storage; - const bool input = params.get_input<bool>("Switch"); - switch ((eNodeSocketDatatype)storage.input_type) { + const eNodeSocketDatatype data_type = static_cast<eNodeSocketDatatype>(storage.input_type); + + switch (data_type) { + case SOCK_FLOAT: { - output_input<float>(params, input, "", "Output"); + switch_fields<float>(params, ""); break; } case SOCK_INT: { - output_input<int>(params, input, "_001", "Output_001"); + switch_fields<int>(params, "_001"); break; } case SOCK_BOOLEAN: { - output_input<bool>(params, input, "_002", "Output_002"); + switch_fields<bool>(params, "_002"); break; } case SOCK_VECTOR: { - output_input<float3>(params, input, "_003", "Output_003"); + switch_fields<float3>(params, "_003"); break; } case SOCK_RGBA: { - output_input<ColorGeometry4f>(params, input, "_004", "Output_004"); + switch_fields<ColorGeometry4f>(params, "_004"); break; } case SOCK_STRING: { - output_input<std::string>(params, input, "_005", "Output_005"); + switch_fields<std::string>(params, "_005"); break; } case SOCK_GEOMETRY: { - output_input<GeometrySet>(params, input, "_006", "Output_006"); + switch_no_fields<GeometrySet>(params, "_006"); break; } case SOCK_OBJECT: { - output_input<Object *>(params, input, "_007", "Output_007"); + switch_no_fields<Object *>(params, "_007"); break; } case SOCK_COLLECTION: { - output_input<Collection *>(params, input, "_008", "Output_008"); + switch_no_fields<Collection *>(params, "_008"); break; } case SOCK_TEXTURE: { - output_input<Tex *>(params, input, "_009", "Output_009"); + switch_no_fields<Tex *>(params, "_009"); break; } case SOCK_MATERIAL: { - output_input<Material *>(params, input, "_010", "Output_010"); + switch_no_fields<Material *>(params, "_010"); break; } default: diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc index 8886c7194db..7ef0913622c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -58,14 +58,14 @@ static void geo_node_triangulate_exec(GeoNodeExecParams params) GeometryNodeTriangulateNGons ngon_method = static_cast<GeometryNodeTriangulateNGons>( params.node().custom2); - geometry_set = geometry_set_realize_instances(geometry_set); - - /* #triangulate_mesh might modify the input mesh currently. */ - Mesh *mesh_in = geometry_set.get_mesh_for_write(); - if (mesh_in != nullptr) { - Mesh *mesh_out = triangulate_mesh(mesh_in, quad_method, ngon_method, min_vertices, 0); - geometry_set.replace_mesh(mesh_out); - } + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + /* #triangulate_mesh might modify the input mesh currently. */ + Mesh *mesh_in = geometry_set.get_mesh_for_write(); + if (mesh_in != nullptr) { + Mesh *mesh_out = triangulate_mesh(mesh_in, quad_method, ngon_method, min_vertices, 0); + geometry_set.replace_mesh(mesh_out); + } + }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc index 3b3b643d0ae..fa9bf09d8d9 100644 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc @@ -159,15 +159,22 @@ const SocketLog *NodeLog::lookup_socket_log(const bNode &node, const bNodeSocket GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry) { - bke::geometry_set_instances_attribute_foreach( - geometry_set, - [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + static std::array all_component_types = {GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES, + GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_VOLUME}; + geometry_set.attribute_foreach( + all_component_types, + true, + [&](const bke::AttributeIDRef &attribute_id, + const AttributeMetaData &meta_data, + const GeometryComponent &UNUSED(component)) { if (attribute_id.is_named()) { this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); } - return true; - }, - 8); + }); + for (const GeometryComponent *component : geometry_set.get_components_for_read()) { component_types_.append(component->type()); switch (component->type()) { diff --git a/source/blender/nodes/intern/node_common.c b/source/blender/nodes/intern/node_common.cc index b8c89d1db37..f5e6e7640ad 100644 --- a/source/blender/nodes/intern/node_common.c +++ b/source/blender/nodes/intern/node_common.cc @@ -21,12 +21,16 @@ * \ingroup nodes */ -#include <stddef.h> -#include <string.h> +#include <cstddef> +#include <cstring> #include "DNA_node_types.h" #include "BLI_listbase.h" +#include "BLI_map.hh" +#include "BLI_multi_value_map.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -42,10 +46,10 @@ #include "node_common.h" #include "node_util.h" -enum { - REFINE_FORWARD = 1 << 0, - REFINE_BACKWARD = 1 << 1, -}; +using blender::Map; +using blender::MultiValueMap; +using blender::Set; +using blender::Stack; /* -------------------------------------------------------------------- */ /** \name Node Group @@ -53,24 +57,22 @@ enum { bNodeSocket *node_group_find_input_socket(bNode *groupnode, const char *identifier) { - bNodeSocket *sock; - for (sock = groupnode->inputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &groupnode->inputs) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } bNodeSocket *node_group_find_output_socket(bNode *groupnode, const char *identifier) { - bNodeSocket *sock; - for (sock = groupnode->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &groupnode->outputs) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } /* groups display their internal tree name as label */ @@ -95,13 +97,12 @@ bool node_group_poll_instance(bNode *node, bNodeTree *nodetree, const char **dis bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_disabled_hint) { - bNode *node; bool valid = true; /* unspecified node group, generally allowed * (if anything, should be avoided on operator level) */ - if (grouptree == NULL) { + if (grouptree == nullptr) { return true; } @@ -110,7 +111,7 @@ bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_dis return false; } - for (node = grouptree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &grouptree->nodes) { if (node->typeinfo->poll_instance && !node->typeinfo->poll_instance(node, nodetree, r_disabled_hint)) { valid = false; @@ -121,12 +122,15 @@ bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_dis } /* used for both group nodes and interface nodes */ -static bNodeSocket *group_verify_socket( - bNodeTree *ntree, bNode *gnode, bNodeSocket *iosock, ListBase *verify_lb, int in_out) +static bNodeSocket *group_verify_socket(bNodeTree *ntree, + bNode *gnode, + bNodeSocket *iosock, + ListBase *verify_lb, + eNodeSocketInOut in_out) { bNodeSocket *sock; - for (sock = verify_lb->first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)verify_lb->first; sock; sock = sock->next) { if (STREQ(sock->identifier, iosock->identifier)) { break; } @@ -163,29 +167,32 @@ static bNodeSocket *group_verify_socket( } /* used for both group nodes and interface nodes */ -static void group_verify_socket_list( - bNodeTree *ntree, bNode *gnode, ListBase *iosock_lb, ListBase *verify_lb, int in_out) +static void group_verify_socket_list(bNodeTree *ntree, + bNode *gnode, + ListBase *iosock_lb, + ListBase *verify_lb, + eNodeSocketInOut in_out) { - bNodeSocket *iosock, *sock, *nextsock; + bNodeSocket *sock, *nextsock; /* step by step compare */ - iosock = iosock_lb->first; + bNodeSocket *iosock = (bNodeSocket *)iosock_lb->first; for (; iosock; iosock = iosock->next) { /* abusing new_sock pointer for verification here! only used inside this function */ iosock->new_sock = group_verify_socket(ntree, gnode, iosock, verify_lb, in_out); } /* leftovers are removed */ - for (sock = verify_lb->first; sock; sock = nextsock) { + for (sock = (bNodeSocket *)verify_lb->first; sock; sock = nextsock) { nextsock = sock->next; nodeRemoveSocket(ntree, gnode, sock); } /* and we put back the verified sockets */ - iosock = iosock_lb->first; + iosock = (bNodeSocket *)iosock_lb->first; for (; iosock; iosock = iosock->next) { if (iosock->new_sock) { BLI_addtail(verify_lb, iosock->new_sock); - iosock->new_sock = NULL; + iosock->new_sock = nullptr; } } } @@ -194,11 +201,11 @@ static void group_verify_socket_list( void node_group_update(struct bNodeTree *ntree, struct bNode *node) { /* check inputs and outputs, and remove or insert them */ - if (node->id == NULL) { + if (node->id == nullptr) { nodeRemoveAllSockets(ntree, node); } else if ((ID_IS_LINKED(node->id) && (node->id->tag & LIB_TAG_MISSING))) { - /* Missing datablock, leave sockets unchanged so that when it comes back + /* Missing data-block, leave sockets unchanged so that when it comes back * the links remain valid. */ } else { @@ -227,7 +234,7 @@ static void node_frame_init(bNodeTree *UNUSED(ntree), bNode *node) void register_node_type_frame(void) { /* frame type is used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "frame node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "frame node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_FRAME, "Frame", NODE_CLASS_LAYOUT, NODE_BACKGROUND); @@ -254,11 +261,11 @@ static void node_reroute_update_internal_links(bNodeTree *ntree, bNode *node) return; } - link = MEM_callocN(sizeof(bNodeLink), "internal node link"); + link = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "internal node link"); link->fromnode = node; - link->fromsock = node->inputs.first; + link->fromsock = (bNodeSocket *)node->inputs.first; link->tonode = node; - link->tosock = node->outputs.first; + link->tosock = (bNodeSocket *)node->outputs.first; /* internal link is always valid */ link->flag |= NODE_LINK_VALID; BLI_addtail(&node->internal_links, link); @@ -276,7 +283,7 @@ static void node_reroute_init(bNodeTree *ntree, bNode *node) void register_node_type_reroute(void) { /* frame type is used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "frame node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "frame node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_REROUTE, "Reroute", NODE_CLASS_LAYOUT, 0); @@ -286,76 +293,37 @@ void register_node_type_reroute(void) nodeRegisterType(ntype); } -static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node, int flag) +static void propagate_reroute_type_from_start_socket( + bNodeSocket *start_socket, + const MultiValueMap<bNodeSocket *, bNodeLink *> &links_map, + Map<bNode *, const bNodeSocketType *> &r_reroute_types) { - bNodeSocket *input = node->inputs.first; - bNodeSocket *output = node->outputs.first; - bNodeLink *link; - int type = SOCK_FLOAT; - const char *type_idname = nodeStaticSocketType(type, PROP_NONE); - - /* XXX it would be a little bit more efficient to restrict actual updates - * to reroute nodes connected to an updated node, but there's no reliable flag - * to indicate updated nodes (node->update is not set on linking). - */ - - node->done = 1; - - /* recursive update */ - for (link = ntree->links.first; link; link = link->next) { - bNode *fromnode = link->fromnode; - bNode *tonode = link->tonode; - if (!tonode || !fromnode) { - continue; + Stack<bNode *> nodes_to_check; + for (bNodeLink *link : links_map.lookup(start_socket)) { + if (link->tonode->type == NODE_REROUTE) { + nodes_to_check.push(link->tonode); } - if (nodeLinkIsHidden(link)) { - continue; - } - - if (flag & REFINE_FORWARD) { - if (tonode == node && fromnode->type == NODE_REROUTE && !fromnode->done) { - node_reroute_inherit_type_recursive(ntree, fromnode, REFINE_FORWARD); - } + if (link->fromnode->type == NODE_REROUTE) { + nodes_to_check.push(link->fromnode); } - if (flag & REFINE_BACKWARD) { - if (fromnode == node && tonode->type == NODE_REROUTE && !tonode->done) { - node_reroute_inherit_type_recursive(ntree, tonode, REFINE_BACKWARD); - } - } - } - - /* determine socket type from unambiguous input/output connection if possible */ - if (nodeSocketLinkLimit(input) == 1 && input->link) { - type = input->link->fromsock->type; - type_idname = nodeStaticSocketType(type, PROP_NONE); - } - else if (nodeSocketLinkLimit(output) == 1 && output->link) { - type = output->link->tosock->type; - type_idname = nodeStaticSocketType(type, PROP_NONE); } - - if (input->type != type) { - bNodeSocket *ninput = nodeAddSocket(ntree, node, SOCK_IN, type_idname, "input", "Input"); - for (link = ntree->links.first; link; link = link->next) { - if (link->tosock == input) { - link->tosock = ninput; - ninput->link = link; + const bNodeSocketType *current_type = start_socket->typeinfo; + while (!nodes_to_check.is_empty()) { + bNode *reroute_node = nodes_to_check.pop(); + BLI_assert(reroute_node->type == NODE_REROUTE); + if (r_reroute_types.add(reroute_node, current_type)) { + for (bNodeLink *link : links_map.lookup((bNodeSocket *)reroute_node->inputs.first)) { + if (link->fromnode->type == NODE_REROUTE) { + nodes_to_check.push(link->fromnode); + } } - } - nodeRemoveSocket(ntree, node, input); - } - - if (output->type != type) { - bNodeSocket *noutput = nodeAddSocket(ntree, node, SOCK_OUT, type_idname, "output", "Output"); - for (link = ntree->links.first; link; link = link->next) { - if (link->fromsock == output) { - link->fromsock = noutput; + for (bNodeLink *link : links_map.lookup((bNodeSocket *)reroute_node->outputs.first)) { + if (link->tonode->type == NODE_REROUTE) { + nodes_to_check.push(link->tonode); + } } } - nodeRemoveSocket(ntree, node, output); } - - nodeUpdateInternalLinks(ntree, node); } /* Global update function for Reroute node types. @@ -363,16 +331,58 @@ static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node, i */ void ntree_update_reroute_nodes(bNodeTree *ntree) { - bNode *node; + /* Contains nodes that are linked to at least one reroute node. */ + Set<bNode *> nodes_linked_with_reroutes; + /* Contains all links that are linked to at least one reroute node. */ + MultiValueMap<bNodeSocket *, bNodeLink *> links_map; + /* Build acceleration data structures for the algorithm below. */ + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (link->fromsock == nullptr || link->tosock == nullptr) { + continue; + } + if (link->fromnode->type != NODE_REROUTE && link->tonode->type != NODE_REROUTE) { + continue; + } + if (link->fromnode->type != NODE_REROUTE) { + nodes_linked_with_reroutes.add(link->fromnode); + } + if (link->tonode->type != NODE_REROUTE) { + nodes_linked_with_reroutes.add(link->tonode); + } + links_map.add(link->fromsock, link); + links_map.add(link->tosock, link); + } + + /* Will contain the socket type for every linked reroute node. */ + Map<bNode *, const bNodeSocketType *> reroute_types; + + /* Propagate socket types from left to right. */ + for (bNode *start_node : nodes_linked_with_reroutes) { + LISTBASE_FOREACH (bNodeSocket *, output_socket, &start_node->outputs) { + propagate_reroute_type_from_start_socket(output_socket, links_map, reroute_types); + } + } - /* clear tags */ - for (node = ntree->nodes.first; node; node = node->next) { - node->done = 0; + /* Propagate socket types from right to left. This affects reroute nodes that haven't been + * changed in the the loop above. */ + for (bNode *start_node : nodes_linked_with_reroutes) { + LISTBASE_FOREACH (bNodeSocket *, input_socket, &start_node->inputs) { + propagate_reroute_type_from_start_socket(input_socket, links_map, reroute_types); + } } - for (node = ntree->nodes.first; node; node = node->next) { - if (node->type == NODE_REROUTE && !node->done) { - node_reroute_inherit_type_recursive(ntree, node, REFINE_FORWARD | REFINE_BACKWARD); + /* Actually update reroute nodes with changed types. */ + for (const auto &item : reroute_types.items()) { + bNode *reroute_node = item.key; + const bNodeSocketType *socket_type = item.value; + bNodeSocket *input_socket = (bNodeSocket *)reroute_node->inputs.first; + bNodeSocket *output_socket = (bNodeSocket *)reroute_node->outputs.first; + + if (input_socket->typeinfo != socket_type) { + nodeModifySocketType(ntree, reroute_node, input_socket, socket_type->idname); + } + if (output_socket->typeinfo != socket_type) { + nodeModifySocketType(ntree, reroute_node, output_socket, socket_type->idname); } } } @@ -393,7 +403,7 @@ static bool node_is_connected_to_output_recursive(bNodeTree *ntree, bNode *node) } /* test all connected nodes, first positive find is sufficient to return true */ - for (link = ntree->links.first; link; link = link->next) { + for (link = (bNodeLink *)ntree->links.first; link; link = link->next) { if (link->fromnode == node) { if (node_is_connected_to_output_recursive(ntree, link->tonode)) { return true; @@ -408,7 +418,7 @@ bool BKE_node_is_connected_to_output(bNodeTree *ntree, bNode *node) bNode *tnode; /* clear flags */ - for (tnode = ntree->nodes.first; tnode; tnode = tnode->next) { + for (tnode = (bNode *)ntree->nodes.first; tnode; tnode = tnode->next) { tnode->done = 0; } @@ -419,9 +429,9 @@ void BKE_node_tree_unlink_id(ID *id, struct bNodeTree *ntree) { bNode *node; - for (node = ntree->nodes.first; node; node = node->next) { + for (node = (bNode *)ntree->nodes.first; node; node = node->next) { if (node->id == id) { - node->id = NULL; + node->id = nullptr; } } } @@ -440,17 +450,17 @@ static void node_group_input_init(bNodeTree *ntree, bNode *node) bNodeSocket *node_group_input_find_socket(bNode *node, const char *identifier) { bNodeSocket *sock; - for (sock = node->outputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } void node_group_input_update(bNodeTree *ntree, bNode *node) { - bNodeSocket *extsock = node->outputs.last; + bNodeSocket *extsock = (bNodeSocket *)node->outputs.last; bNodeLink *link, *linknext, *exposelink; /* Adding a tree socket and verifying will remove the extension socket! * This list caches the existing links from the extension socket @@ -460,14 +470,14 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) /* find links from the extension socket and store them */ BLI_listbase_clear(&tmplinks); - for (link = ntree->links.first; link; link = linknext) { + for (link = (bNodeLink *)ntree->links.first; link; link = linknext) { linknext = link->next; if (nodeLinkIsHidden(link)) { continue; } if (link->fromsock == extsock) { - bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link"); + bNodeLink *tlink = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "temporary link"); *tlink = *link; BLI_addtail(&tmplinks, tlink); @@ -476,8 +486,8 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) } /* find valid link to expose */ - exposelink = NULL; - for (link = tmplinks.first; link; link = link->next) { + exposelink = nullptr; + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { /* XXX Multiple sockets can be connected to the extension socket at once, * in that case the arbitrary first link determines name and type. * This could be improved by choosing the "best" type among all links, @@ -498,7 +508,7 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) newsock = node_group_input_find_socket(node, gsock->identifier); /* redirect links from the extension socket */ - for (link = tmplinks.first; link; link = link->next) { + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { nodeAddLink(ntree, node, newsock, link->tonode, link->tosock); } } @@ -518,7 +528,7 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) void register_node_type_group_input(void) { /* used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_GROUP_INPUT, "Group Input", NODE_CLASS_INTERFACE, 0); @@ -537,17 +547,17 @@ static void node_group_output_init(bNodeTree *ntree, bNode *node) bNodeSocket *node_group_output_find_socket(bNode *node, const char *identifier) { bNodeSocket *sock; - for (sock = node->inputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } void node_group_output_update(bNodeTree *ntree, bNode *node) { - bNodeSocket *extsock = node->inputs.last; + bNodeSocket *extsock = (bNodeSocket *)node->inputs.last; bNodeLink *link, *linknext, *exposelink; /* Adding a tree socket and verifying will remove the extension socket! * This list caches the existing links to the extension socket @@ -557,14 +567,14 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) /* find links to the extension socket and store them */ BLI_listbase_clear(&tmplinks); - for (link = ntree->links.first; link; link = linknext) { + for (link = (bNodeLink *)ntree->links.first; link; link = linknext) { linknext = link->next; if (nodeLinkIsHidden(link)) { continue; } if (link->tosock == extsock) { - bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link"); + bNodeLink *tlink = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "temporary link"); *tlink = *link; BLI_addtail(&tmplinks, tlink); @@ -573,8 +583,8 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) } /* find valid link to expose */ - exposelink = NULL; - for (link = tmplinks.first; link; link = link->next) { + exposelink = nullptr; + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { /* XXX Multiple sockets can be connected to the extension socket at once, * in that case the arbitrary first link determines name and type. * This could be improved by choosing the "best" type among all links, @@ -596,7 +606,7 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) newsock = node_group_output_find_socket(node, gsock->identifier); /* redirect links to the extension socket */ - for (link = tmplinks.first; link; link = link->next) { + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { nodeAddLink(ntree, link->fromnode, link->fromsock, node, newsock); } } @@ -616,7 +626,7 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) void register_node_type_group_output(void) { /* used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_GROUP_OUTPUT, "Group Output", NODE_CLASS_INTERFACE, 0); diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index 31260f95242..4334f1b5030 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -872,7 +872,7 @@ static bNodeSocketType *make_socket_type_material() void register_standard_node_socket_types(void) { - /* draw callbacks are set in drawnode.c to avoid bad-level calls */ + /* Draw callbacks are set in `drawnode.c` to avoid bad-level calls. */ nodeRegisterSocketType(make_socket_type_float(PROP_NONE)); nodeRegisterSocketType(make_socket_type_float(PROP_UNSIGNED)); diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index 641d02af902..43c7fbd2599 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -19,6 +19,7 @@ #include "NOD_node_tree_ref.hh" #include "BLI_dot_export.hh" +#include "BLI_stack.hh" namespace blender::nodes { @@ -473,6 +474,108 @@ bool NodeTreeRef::has_undefined_nodes_or_sockets() const return false; } +bool NodeRef::any_input_is_directly_linked() const +{ + for (const SocketRef *socket : inputs_) { + if (!socket->directly_linked_sockets().is_empty()) { + return true; + } + } + return false; +} + +bool NodeRef::any_output_is_directly_linked() const +{ + for (const SocketRef *socket : outputs_) { + if (!socket->directly_linked_sockets().is_empty()) { + return true; + } + } + return false; +} + +bool NodeRef::any_socket_is_directly_linked(eNodeSocketInOut in_out) const +{ + if (in_out == SOCK_IN) { + return this->any_input_is_directly_linked(); + } + return this->any_output_is_directly_linked(); +} + +/** + * Sort nodes topologically from left to right or right to left. + * In the future the result if this could be cached on #NodeTreeRef. + */ +Vector<const NodeRef *> NodeTreeRef::toposort(const ToposortDirection direction) const +{ + struct Item { + const NodeRef *node; + /* Index of the next socket that is checked in the depth-first search. */ + int socket_index = 0; + /* Link index in the next socket that is checked in the depth-first search. */ + int link_index = 0; + }; + + Vector<const NodeRef *> toposort; + toposort.reserve(nodes_by_id_.size()); + Array<bool> node_is_done_by_id(nodes_by_id_.size(), false); + Stack<Item> nodes_to_check; + + for (const NodeRef *start_node : nodes_by_id_) { + if (node_is_done_by_id[start_node->id()]) { + /* Ignore nodes that are done already. */ + continue; + } + if (start_node->any_socket_is_directly_linked( + direction == ToposortDirection::LeftToRight ? SOCK_OUT : SOCK_IN)) { + /* Ignore non-start nodes. */ + continue; + } + + /* Do a depth-first search to sort nodes topologically. */ + nodes_to_check.push({start_node}); + while (!nodes_to_check.is_empty()) { + Item &item = nodes_to_check.peek(); + const NodeRef &node = *item.node; + const Span<const SocketRef *> sockets = node.sockets( + direction == ToposortDirection::LeftToRight ? SOCK_IN : SOCK_OUT); + + while (true) { + if (item.socket_index == sockets.size()) { + /* All sockets have already been visited. */ + break; + } + const SocketRef &socket = *sockets[item.socket_index]; + const Span<const SocketRef *> linked_sockets = socket.directly_linked_sockets(); + if (item.link_index == linked_sockets.size()) { + /* All links connected to this socket have already been visited. */ + item.socket_index++; + item.link_index = 0; + continue; + } + const SocketRef &linked_socket = *linked_sockets[item.link_index]; + const NodeRef &linked_node = linked_socket.node(); + if (node_is_done_by_id[linked_node.id()]) { + /* The linked node has already been visited. */ + item.link_index++; + continue; + } + nodes_to_check.push({&linked_node}); + break; + } + + /* If no other element has been pushed, the current node can be pushed to the sorted list. */ + if (&item == &nodes_to_check.peek()) { + node_is_done_by_id[node.id()] = true; + toposort.append(&node); + nodes_to_check.pop(); + } + } + } + + return toposort; +} + std::string NodeTreeRef::to_dot() const { dot::DirectedGraph digraph; diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index c3a675fcd20..83ee0c2f411 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -201,7 +201,7 @@ void register_node_tree_type_sh(void) tt->type = NTREE_SHADER; strcpy(tt->idname, "ShaderNodeTree"); strcpy(tt->ui_name, N_("Shader Editor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Shader nodes")); tt->foreach_nodeclass = foreach_nodeclass; diff --git a/source/blender/nodes/shader/nodes/node_shader_brightness.c b/source/blender/nodes/shader/nodes/node_shader_brightness.c index d8f560277f2..4f375c666de 100644 --- a/source/blender/nodes/shader/nodes/node_shader_brightness.c +++ b/source/blender/nodes/shader/nodes/node_shader_brightness.c @@ -19,7 +19,7 @@ #include "node_shader_util.h" -/* **************** Brigh and contrsast ******************** */ +/* **************** Bright and contrast ******************** */ static bNodeSocketTemplate sh_node_brightcontrast_in[] = { {SOCK_RGBA, N_("Color"), 1.0f, 1.0f, 1.0f, 1.0f}, diff --git a/source/blender/nodes/shader/nodes/node_shader_clamp.cc b/source/blender/nodes/shader/nodes/node_shader_clamp.cc index 31d8f8ef15c..e8d4239937f 100644 --- a/source/blender/nodes/shader/nodes/node_shader_clamp.cc +++ b/source/blender/nodes/shader/nodes/node_shader_clamp.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_clamp_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value").min(0.0f).max(1.0f).default_value(1.0f); b.add_input<decl::Float>("Min").default_value(0.0f).min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_curves.cc b/source/blender/nodes/shader/nodes/node_shader_curves.cc index e4ada06133e..8657d9e517d 100644 --- a/source/blender/nodes/shader/nodes/node_shader_curves.cc +++ b/source/blender/nodes/shader/nodes/node_shader_curves.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_curve_vec_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Fac").min(0.0f).max(1.0f).default_value(1.0f).subtype(PROP_FACTOR); b.add_input<decl::Vector>("Vector").min(-1.0f).max(1.0f); b.add_output<decl::Vector>("Vector"); @@ -342,3 +343,142 @@ void register_node_type_sh_curve_rgb(void) nodeRegisterType(&ntype); } + +/* **************** CURVE FLOAT ******************** */ + +namespace blender::nodes { + +static void sh_node_curve_float_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).default_value(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Value").default_value(1.0f); + b.add_output<decl::Float>("Value"); +}; + +} // namespace blender::nodes + +static void node_shader_exec_curve_float(void *UNUSED(data), + int UNUSED(thread), + bNode *node, + bNodeExecData *UNUSED(execdata), + bNodeStack **in, + bNodeStack **out) +{ + float value; + float fac; + + nodestack_get_vec(&fac, SOCK_FLOAT, in[0]); + nodestack_get_vec(&value, SOCK_FLOAT, in[1]); + out[0]->vec[0] = BKE_curvemapping_evaluateF((CurveMapping *)node->storage, 0, value); + if (fac != 1.0f) { + out[0]->vec[0] = (1.0f - fac) * value + fac * out[0]->vec[0]; + } +} + +static void node_shader_init_curve_float(bNodeTree *UNUSED(ntree), bNode *node) +{ + node->storage = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); +} + +static int gpu_shader_curve_float(GPUMaterial *mat, + bNode *node, + bNodeExecData *UNUSED(execdata), + GPUNodeStack *in, + GPUNodeStack *out) +{ + float *array, layer; + int size; + + CurveMapping *cumap = (CurveMapping *)node->storage; + + BKE_curvemapping_table_F(cumap, &array, &size); + GPUNodeLink *tex = GPU_color_band(mat, size, array, &layer); + + float ext_xyz[4]; + float range_x; + + const CurveMap *cm = &cumap->cm[0]; + ext_xyz[0] = cm->mintable; + ext_xyz[2] = cm->maxtable; + range_x = 1.0f / max_ff(1e-8f, cm->maxtable - cm->mintable); + /* Compute extrapolation gradients. */ + if ((cumap->flag & CUMA_EXTEND_EXTRAPOLATE) != 0) { + ext_xyz[1] = (cm->ext_in[0] != 0.0f) ? (cm->ext_in[1] / (cm->ext_in[0] * range_x)) : 1e8f; + ext_xyz[3] = (cm->ext_out[0] != 0.0f) ? (cm->ext_out[1] / (cm->ext_out[0] * range_x)) : 1e8f; + } + else { + ext_xyz[1] = 0.0f; + ext_xyz[3] = 0.0f; + } + return GPU_stack_link(mat, + node, + "curve_float", + in, + out, + tex, + GPU_constant(&layer), + GPU_uniform(&range_x), + GPU_uniform(ext_xyz)); +} + +class CurveFloatFunction : public blender::fn::MultiFunction { + private: + const CurveMapping &cumap_; + + public: + CurveFloatFunction(const CurveMapping &cumap) : cumap_(cumap) + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve Float"}; + signature.single_input<float>("Factor"); + signature.single_input<float>("Value"); + signature.single_output<float>("Value"); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &fac = params.readonly_single_input<float>(0, "Factor"); + const blender::VArray<float> &val_in = params.readonly_single_input<float>(1, "Value"); + blender::MutableSpan<float> val_out = params.uninitialized_single_output<float>(2, "Value"); + + for (int64_t i : mask) { + val_out[i] = BKE_curvemapping_evaluateF(&cumap_, 0, val_in[i]); + if (fac[i] != 1.0f) { + val_out[i] = (1.0f - fac[i]) * val_in[i] + fac[i] * val_out[i]; + } + } + } +}; + +static void sh_node_curve_float_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + bNode &bnode = builder.node(); + CurveMapping *cumap = (CurveMapping *)bnode.storage; + BKE_curvemapping_init(cumap); + builder.construct_and_set_matching_fn<CurveFloatFunction>(*cumap); +} + +void register_node_type_sh_curve_float(void) +{ + static bNodeType ntype; + + sh_fn_node_type_base(&ntype, SH_NODE_CURVE_FLOAT, "Float Curve", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::sh_node_curve_float_declare; + node_type_init(&ntype, node_shader_init_curve_float); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); + node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_float); + node_type_gpu(&ntype, gpu_shader_curve_float); + ntype.build_multi_function = sh_node_curve_float_build_multi_function; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/shader/nodes/node_shader_hair_info.c b/source/blender/nodes/shader/nodes/node_shader_hair_info.c index 843185befb6..c721fb9c77a 100644 --- a/source/blender/nodes/shader/nodes/node_shader_hair_info.c +++ b/source/blender/nodes/shader/nodes/node_shader_hair_info.c @@ -22,6 +22,7 @@ static bNodeSocketTemplate outputs[] = { {SOCK_FLOAT, N_("Is Strand"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_FLOAT, N_("Intercept"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + {SOCK_FLOAT, N_("Length"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_FLOAT, N_("Thickness"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_VECTOR, N_("Tangent Normal"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, // { SOCK_FLOAT, 0, N_("Fade"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, @@ -35,7 +36,11 @@ static int node_shader_gpu_hair_info(GPUMaterial *mat, GPUNodeStack *in, GPUNodeStack *out) { - return GPU_stack_link(mat, node, "node_hair_info", in, out); + /* Length: don't request length if not needed. */ + static const float zero = 0; + GPUNodeLink *length_link = (!out[2].hasoutput) ? GPU_constant(&zero) : + GPU_attribute(mat, CD_HAIRLENGTH, ""); + return GPU_stack_link(mat, node, "node_hair_info", in, out, length_link); } /* node type definition */ diff --git a/source/blender/nodes/shader/nodes/node_shader_map_range.cc b/source/blender/nodes/shader/nodes/node_shader_map_range.cc index a5f9a24a728..5ea194ddc83 100644 --- a/source/blender/nodes/shader/nodes/node_shader_map_range.cc +++ b/source/blender/nodes/shader/nodes/node_shader_map_range.cc @@ -29,6 +29,7 @@ namespace blender::nodes { static void sh_node_map_range_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value").min(-10000.0f).max(10000.0f).default_value(1.0f); b.add_input<decl::Float>("From Min").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("From Max").min(-10000.0f).max(10000.0f).default_value(1.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_math.cc b/source/blender/nodes/shader/nodes/node_shader_math.cc index 80a27b8e6a1..96d1be49c04 100644 --- a/source/blender/nodes/shader/nodes/node_shader_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_math.cc @@ -31,6 +31,7 @@ namespace blender::nodes { static void sh_node_math_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value").default_value(0.5f).min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Value", "Value_001").default_value(0.5f).min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Value", "Value_002").default_value(0.5f).min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc b/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc index 860cc260d5d..d4d02e80ada 100644 --- a/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_mix_rgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); b.add_input<decl::Color>("Color1").default_value({0.5f, 0.5f, 0.5f, 1.0f}); b.add_input<decl::Color>("Color2").default_value({0.5f, 0.5f, 0.5f, 1.0f}); diff --git a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc index 63be399366f..24c5dcf7ba3 100644 --- a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc +++ b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_seprgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Color>("Image").default_value({0.8f, 0.8f, 0.8f, 1.0f}); b.add_output<decl::Float>("R"); b.add_output<decl::Float>("G"); @@ -119,6 +120,7 @@ namespace blender::nodes { static void sh_node_combrgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("R").min(0.0f).max(1.0f); b.add_input<decl::Float>("G").min(0.0f).max(1.0f); b.add_input<decl::Float>("B").min(0.0f).max(1.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc b/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc index b4b3c48482f..8ca8fc19521 100644 --- a/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc +++ b/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_sepxyz_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f); b.add_output<decl::Float>("X"); b.add_output<decl::Float>("Y"); @@ -103,6 +104,7 @@ namespace blender::nodes { static void sh_node_combxyz_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("X").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Y").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Z").min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc index d33d92f25fd..23f150d8135 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void sh_node_tex_musgrave_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").hide_value(); b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f); b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc index 1ae6b3a616c..6ffc8979815 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc @@ -25,7 +25,8 @@ namespace blender::nodes { static void sh_node_tex_noise_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Vector>("Vector").hide_value(); + b.is_function_node(); + b.add_input<decl::Vector>("Vector").implicit_field(); b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f); b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f); b.add_input<decl::Float>("Detail").min(0.0f).max(16.0f).default_value(2.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc index cea7af247c1..e12e5724e8e 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void sh_node_tex_voronoi_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").hide_value(); b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f); b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc index bae16e10120..03543e5f7fe 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void sh_node_tex_white_noise_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("W").min(-10000.0f).max(10000.0f); b.add_output<decl::Float>("Value"); diff --git a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc index 1870caffbb1..d4d08be5d49 100644 --- a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc @@ -33,6 +33,7 @@ namespace blender::nodes { static void sh_node_valtorgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); b.add_output<decl::Color>("Color"); b.add_output<decl::Float>("Alpha"); diff --git a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc index 5b24e8bb72d..f49ff06cef1 100644 --- a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc @@ -29,6 +29,7 @@ namespace blender::nodes { static void sh_node_vector_math_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f); b.add_input<decl::Vector>("Vector", "Vector_001").min(-10000.0f).max(10000.0f); b.add_input<decl::Vector>("Vector", "Vector_002").min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc b/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc index e9fd6c4f31e..c9b26fa5199 100644 --- a/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc +++ b/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc @@ -27,12 +27,13 @@ namespace blender::nodes { static void sh_node_vector_rotate_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(0.0f).max(1.0f).hide_value(); - b.add_input<decl::Vector>("Vector"); + b.add_input<decl::Vector>("Center"); b.add_input<decl::Vector>("Axis").min(-1.0f).max(1.0f).default_value({0.0f, 0.0f, 1.0f}); b.add_input<decl::Float>("Angle").subtype(PROP_ANGLE); b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER); - b.add_output<decl::Vector>("Value"); + b.add_output<decl::Vector>("Vector"); }; } // namespace blender::nodes diff --git a/source/blender/nodes/texture/node_texture_tree.c b/source/blender/nodes/texture/node_texture_tree.c index 7452007639c..14597050524 100644 --- a/source/blender/nodes/texture/node_texture_tree.c +++ b/source/blender/nodes/texture/node_texture_tree.c @@ -169,7 +169,7 @@ void register_node_tree_type_tex(void) tt->type = NTREE_TEXTURE; strcpy(tt->idname, "TextureNodeTree"); strcpy(tt->ui_name, N_("Texture Node Editor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Texture nodes")); tt->foreach_nodeclass = foreach_nodeclass; diff --git a/source/blender/nodes/texture/nodes/node_texture_curves.c b/source/blender/nodes/texture/nodes/node_texture_curves.c index 70f7e731720..f61e3f36db5 100644 --- a/source/blender/nodes/texture/nodes/node_texture_curves.c +++ b/source/blender/nodes/texture/nodes/node_texture_curves.c @@ -26,7 +26,7 @@ /* **************** CURVE Time ******************** */ -/* custom1 = sfra, custom2 = efra */ +/* custom1 = start-frame, custom2 = end-frame. */ static bNodeSocketTemplate time_outputs[] = {{SOCK_FLOAT, N_("Value")}, {-1, ""}}; static void time_colorfn( diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c index e5e601e0eb6..cbebe4746e9 100644 --- a/source/blender/python/bmesh/bmesh_py_types.c +++ b/source/blender/python/bmesh/bmesh_py_types.c @@ -1950,7 +1950,7 @@ static PyObject *bpy_bmface_calc_tangent_edge_diagonal(BPy_BMFace *self) PyDoc_STRVAR(bpy_bmface_calc_tangent_vert_diagonal_doc, ".. method:: calc_tangent_vert_diagonal()\n" "\n" - " Return face tangent based on the two most distent vertices.\n" + " Return face tangent based on the two most distant vertices.\n" "\n" " :return: a normalized vector.\n" " :rtype: :class:`mathutils.Vector`\n"); @@ -3464,7 +3464,7 @@ PyDoc_STRVAR(bpy_bmelemseq_doc, ":class:`BMVert`, :class:`BMEdge`, :class:`BMFace`, :class:`BMLoop`.\n" "\n" "When accessed via :class:`BMesh.verts`, :class:`BMesh.edges`, :class:`BMesh.faces`\n" - "there are also functions to create/remomove items.\n"); + "there are also functions to create/remove items.\n"); PyDoc_STRVAR(bpy_bmiter_doc, "Internal BMesh type for looping over verts/faces/edges,\n" "used for iterating over :class:`BMElemSeq` types.\n"); diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 7a93a076621..95affa9dba9 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -246,7 +246,7 @@ void BPY_modules_update(void) #if 0 /* slow, this runs all the time poll, draw etc 100's of time a sec. */ PyObject *mod = PyImport_ImportModuleLevel("bpy", NULL, NULL, NULL, 0); PyModule_AddObject(mod, "data", BPY_rna_module()); - PyModule_AddObject(mod, "types", BPY_rna_types()); /* atm this does not need updating */ + PyModule_AddObject(mod, "types", BPY_rna_types()); /* This does not need updating. */ #endif /* refreshes the main struct */ diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c index ac624d365fa..6d384ed9358 100644 --- a/source/blender/python/intern/bpy_props.c +++ b/source/blender/python/intern/bpy_props.c @@ -3750,7 +3750,7 @@ PyDoc_STRVAR( " (e.g. returned by :class:`bpy.types.UILayout.icon`)\n" " :number: Unique value used as the identifier for this item (stored in file data).\n" " Use when the identifier may need to change. If the *ENUM_FLAG* option is used,\n" - " the values are bitmasks and should be powers of two.\n" + " the values are bit-masks and should be powers of two.\n" "\n" " When an item only contains 4 items they define ``(identifier, name, description, " "number)``.\n" diff --git a/source/blender/python/mathutils/mathutils_geometry.c b/source/blender/python/mathutils/mathutils_geometry.c index 5868c76b28f..d47b59d0c76 100644 --- a/source/blender/python/mathutils/mathutils_geometry.c +++ b/source/blender/python/mathutils/mathutils_geometry.c @@ -1210,7 +1210,7 @@ PyDoc_STRVAR(M_Geometry_tessellate_polygon_doc, "\n" " :arg veclist_list: list of polylines\n" " :rtype: list\n"); -/* PolyFill function, uses Blenders scanfill to fill multiple poly lines */ +/* PolyFill function, uses Blenders scan-fill to fill multiple poly lines. */ static PyObject *M_Geometry_tessellate_polygon(PyObject *UNUSED(self), PyObject *polyLineSeq) { PyObject *tri_list; /* Return this list of tri's */ diff --git a/source/blender/render/RE_pipeline.h b/source/blender/render/RE_pipeline.h index 3237772dd80..0d2d93ae026 100644 --- a/source/blender/render/RE_pipeline.h +++ b/source/blender/render/RE_pipeline.h @@ -233,7 +233,8 @@ void RE_create_render_pass(struct RenderResult *rr, int channels, const char *chan_id, const char *layername, - const char *viewname); + const char *viewname, + const bool allocate); /* obligatory initialize call, disprect is optional */ void RE_InitState(struct Render *re, diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c index 389b821ca35..790c46dad0f 100644 --- a/source/blender/render/intern/engine.c +++ b/source/blender/render/intern/engine.c @@ -207,11 +207,10 @@ static RenderResult *render_result_from_bake(RenderEngine *engine, int x, int y, /* Add render passes. */ RenderPass *result_pass = render_layer_add_pass( - rr, rl, engine->bake.depth, RE_PASSNAME_COMBINED, "", "RGBA"); - RenderPass *primitive_pass = render_layer_add_pass(rr, rl, 4, "BakePrimitive", "", "RGBA"); - RenderPass *differential_pass = render_layer_add_pass(rr, rl, 4, "BakeDifferential", "", "RGBA"); - - render_result_passes_allocated_ensure(rr); + rr, rl, engine->bake.depth, RE_PASSNAME_COMBINED, "", "RGBA", true); + RenderPass *primitive_pass = render_layer_add_pass(rr, rl, 4, "BakePrimitive", "", "RGBA", true); + RenderPass *differential_pass = render_layer_add_pass( + rr, rl, 4, "BakeDifferential", "", "RGBA", true); /* Fill render passes from bake pixel array, to be read by the render engine. */ for (int ty = 0; ty < h; ty++) { @@ -414,7 +413,7 @@ void RE_engine_add_pass(RenderEngine *engine, return; } - RE_create_render_pass(re->result, name, channels, chan_id, layername, NULL); + RE_create_render_pass(re->result, name, channels, chan_id, layername, NULL, false); } void RE_engine_end_result( diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c index 72ff920561d..7c5259a1c5c 100644 --- a/source/blender/render/intern/pipeline.c +++ b/source/blender/render/intern/pipeline.c @@ -1015,10 +1015,10 @@ static void render_result_uncrop(Render *re) render_result_disprect_to_full_resolution(re); rres = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); - render_result_passes_allocated_ensure(rres); rres->stamp_data = BKE_stamp_data_copy(re->result->stamp_data); render_result_clone_passes(re, rres, NULL); + render_result_passes_allocated_ensure(rres); render_result_merge(rres, re->result); render_result_free(re->result); @@ -2817,7 +2817,7 @@ RenderPass *RE_create_gp_pass(RenderResult *rr, const char *layername, const cha BLI_freelinkN(&rl->passes, rp); } /* create a totally new pass */ - return render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, viewname, "RGBA"); + return render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, viewname, "RGBA", true); } bool RE_allow_render_generic_object(Object *ob) diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c index c308147fc5b..0681bcd9aa5 100644 --- a/source/blender/render/intern/render_result.c +++ b/source/blender/render/intern/render_result.c @@ -213,12 +213,37 @@ static void set_pass_full_name( /********************************** New **************************************/ +static void render_layer_allocate_pass(RenderResult *rr, RenderPass *rp) +{ + if (rp->rect != NULL) { + return; + } + + const size_t rectsize = ((size_t)rr->rectx) * rr->recty * rp->channels; + rp->rect = MEM_callocN(sizeof(float) * rectsize, rp->name); + + if (STREQ(rp->name, RE_PASSNAME_VECTOR)) { + /* initialize to max speed */ + float *rect = rp->rect; + for (int x = rectsize - 1; x >= 0; x--) { + rect[x] = PASS_VECTOR_MAX; + } + } + else if (STREQ(rp->name, RE_PASSNAME_Z)) { + float *rect = rp->rect; + for (int x = rectsize - 1; x >= 0; x--) { + rect[x] = 10e10; + } + } +} + RenderPass *render_layer_add_pass(RenderResult *rr, RenderLayer *rl, int channels, const char *name, const char *viewname, - const char *chan_id) + const char *chan_id, + const bool allocate) { const int view_id = BLI_findstringindex(&rr->views, viewname, offsetof(RenderView, name)); RenderPass *rpass = MEM_callocN(sizeof(RenderPass), name); @@ -250,8 +275,13 @@ RenderPass *render_layer_add_pass(RenderResult *rr, BLI_addtail(&rl->passes, rpass); - /* The result contains non-allocated pass now, so tag it as such. */ - rr->passes_allocated = false; + if (allocate) { + render_layer_allocate_pass(rr, rpass); + } + else { + /* The result contains non-allocated pass now, so tag it as such. */ + rr->passes_allocated = false; + } return rpass; } @@ -323,14 +353,14 @@ RenderResult *render_result_new(Render *re, #define RENDER_LAYER_ADD_PASS_SAFE(rr, rl, channels, name, viewname, chan_id) \ do { \ - if (render_layer_add_pass(rr, rl, channels, name, viewname, chan_id) == NULL) { \ + if (render_layer_add_pass(rr, rl, channels, name, viewname, chan_id, false) == NULL) { \ render_result_free(rr); \ return NULL; \ } \ } while (false) /* A renderlayer should always have a Combined pass. */ - render_layer_add_pass(rr, rl, 4, "Combined", view, "RGBA"); + render_layer_add_pass(rr, rl, 4, "Combined", view, "RGBA", false); if (view_layer->passflag & SCE_PASS_Z) { RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 1, RE_PASSNAME_Z, view, "Z"); @@ -427,7 +457,7 @@ RenderResult *render_result_new(Render *re, } /* a renderlayer should always have a Combined pass */ - render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA"); + render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA", false); } /* NOTE: this has to be in sync with `scene.c`. */ @@ -453,26 +483,7 @@ void render_result_passes_allocated_ensure(RenderResult *rr) continue; } - if (rp->rect != NULL) { - continue; - } - - const size_t rectsize = ((size_t)rr->rectx) * rr->recty * rp->channels; - rp->rect = MEM_callocN(sizeof(float) * rectsize, rp->name); - - if (STREQ(rp->name, RE_PASSNAME_VECTOR)) { - /* initialize to max speed */ - float *rect = rp->rect; - for (int x = rectsize - 1; x >= 0; x--) { - rect[x] = PASS_VECTOR_MAX; - } - } - else if (STREQ(rp->name, RE_PASSNAME_Z)) { - float *rect = rp->rect; - for (int x = rectsize - 1; x >= 0; x--) { - rect[x] = 10e10; - } - } + render_layer_allocate_pass(rr, rp); } } @@ -501,7 +512,7 @@ void render_result_clone_passes(Render *re, RenderResult *rr, const char *viewna &rl->passes, main_rp->fullname, offsetof(RenderPass, fullname)); if (!rp) { render_layer_add_pass( - rr, rl, main_rp->channels, main_rp->name, main_rp->view, main_rp->chan_id); + rr, rl, main_rp->channels, main_rp->name, main_rp->view, main_rp->chan_id, false); } } } @@ -512,7 +523,8 @@ void RE_create_render_pass(RenderResult *rr, int channels, const char *chan_id, const char *layername, - const char *viewname) + const char *viewname, + const bool allocate) { RenderLayer *rl; RenderPass *rp; @@ -542,7 +554,7 @@ void RE_create_render_pass(RenderResult *rr, } if (!rp) { - render_layer_add_pass(rr, rl, channels, name, view, chan_id); + render_layer_add_pass(rr, rl, channels, name, view, chan_id, allocate); } } } @@ -1083,7 +1095,7 @@ int render_result_exr_file_read_path(RenderResult *rr, void *exrhandle = IMB_exr_get_handle(); int rectx, recty; - if (IMB_exr_begin_read(exrhandle, filepath, &rectx, &recty) == 0) { + if (!IMB_exr_begin_read(exrhandle, filepath, &rectx, &recty, false)) { printf("failed being read %s\n", filepath); IMB_exr_close(exrhandle); return 0; @@ -1175,20 +1187,32 @@ void render_result_exr_file_cache_write(Render *re) /* For cache, makes exact copy of render result */ bool render_result_exr_file_cache_read(Render *re) { - char str[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = ""; + /* File path to cache. */ + char filepath[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = ""; char *root = U.render_cachedir; + render_result_exr_file_cache_path(re->scene, root, filepath); - RE_FreeRenderResult(re->result); - re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); + printf("read exr cache file: %s\n", filepath); - /* First try cache. */ - render_result_exr_file_cache_path(re->scene, root, str); + /* Try opening the file. */ + void *exrhandle = IMB_exr_get_handle(); + int rectx, recty; - printf("read exr cache file: %s\n", str); - if (!render_result_exr_file_read_path(re->result, NULL, str)) { - printf("cannot read: %s\n", str); + if (!IMB_exr_begin_read(exrhandle, filepath, &rectx, &recty, true)) { + printf("cannot read: %s\n", filepath); + IMB_exr_close(exrhandle); return false; } + + /* Read file contents into render result. */ + const char *colorspace = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_SCENE_LINEAR); + RE_FreeRenderResult(re->result); + + IMB_exr_read_channels(exrhandle); + re->result = render_result_new_from_exr(exrhandle, colorspace, false, rectx, recty); + + IMB_exr_close(exrhandle); + return true; } diff --git a/source/blender/render/intern/render_result.h b/source/blender/render/intern/render_result.h index 4145bb3b8ab..34b8143869c 100644 --- a/source/blender/render/intern/render_result.h +++ b/source/blender/render/intern/render_result.h @@ -83,7 +83,8 @@ struct RenderPass *render_layer_add_pass(struct RenderResult *rr, int channels, const char *name, const char *viewname, - const char *chan_id); + const char *chan_id, + const bool allocate); int render_result_exr_file_read_path(struct RenderResult *rr, struct RenderLayer *rl_single, diff --git a/source/blender/render/intern/texture_image.c b/source/blender/render/intern/texture_image.c index 62aee564626..edfa284242c 100644 --- a/source/blender/render/intern/texture_image.c +++ b/source/blender/render/intern/texture_image.c @@ -1958,13 +1958,11 @@ int imagewraposa(Tex *tex, } if (texres->nor && (tex->imaflag & TEX_NORMALMAP)) { - /* qdn: normal from color - * The invert of the red channel is to make - * the normal map compliant with the outside world. - * It needs to be done because in Blender - * the normal used in the renderer points inward. It is generated - * this way in calc_vertexnormals(). Should this ever change - * this negate must be removed. */ + /* Normal from color: + * The invert of the red channel is to make the normal map compliant with the outside world. + * It needs to be done because in Blender the normal used in the renderer points inward. + * It is generated this way in #calc_vertexnormals(). + * Should this ever change this negate must be removed. */ texres->nor[0] = -2.0f * (texres->tr - 0.5f); texres->nor[1] = 2.0f * (texres->tg - 0.5f); texres->nor[2] = 2.0f * (texres->tb - 0.5f); diff --git a/source/blender/sequencer/SEQ_sequencer.h b/source/blender/sequencer/SEQ_sequencer.h index 7e733817630..1b8982da0d2 100644 --- a/source/blender/sequencer/SEQ_sequencer.h +++ b/source/blender/sequencer/SEQ_sequencer.h @@ -89,6 +89,7 @@ void SEQ_sequence_base_dupli_recursive(const struct Scene *scene_src, const struct ListBase *seqbase, int dupe_flag, const int flag); +bool SEQ_valid_strip_channel(struct Sequence *seq); /* Read and Write functions for .blend file data */ void SEQ_blend_write(struct BlendWriter *writer, struct ListBase *seqbase); diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 4448db013fe..427a8835879 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -3154,6 +3154,7 @@ void seq_effect_speed_rebuild_map(Scene *scene, Sequence *seq) float target_frame = 0; for (int frame_index = 1; frame_index < effect_strip_length; frame_index++) { target_frame += evaluate_fcurve(fcu, seq->startdisp + frame_index); + CLAMP(target_frame, 0, seq->seq1->len); v->frameMap[frame_index] = target_frame; } } diff --git a/source/blender/sequencer/intern/modifier.c b/source/blender/sequencer/intern/modifier.c index 07d09f4ae17..1a63f4c4655 100644 --- a/source/blender/sequencer/intern/modifier.c +++ b/source/blender/sequencer/intern/modifier.c @@ -216,7 +216,7 @@ static void modifier_apply_threaded(ImBuf *ibuf, /** \name Color Balance Modifier * \{ */ -static StripColorBalance calc_cb(StripColorBalance *cb_) +static StripColorBalance calc_cb_lgg(StripColorBalance *cb_) { StripColorBalance cb = *cb_; int c; @@ -262,8 +262,52 @@ static StripColorBalance calc_cb(StripColorBalance *cb_) return cb; } +static StripColorBalance calc_cb_sop(StripColorBalance *cb_) +{ + StripColorBalance cb = *cb_; + int c; + + for (c = 0; c < 3; c++) { + if (cb.flag & SEQ_COLOR_BALANCE_INVERSE_SLOPE) { + if (cb.slope[c] != 0.0f) { + cb.slope[c] = 1.0f / cb.slope[c]; + } + else { + cb.slope[c] = 1000000; + } + } + + if (cb.flag & SEQ_COLOR_BALANCE_INVERSE_OFFSET) { + cb.offset[c] = -1.0f * (cb.offset[c] - 1.0f); + } + else { + cb.offset[c] = cb.offset[c] - 1.0f; + } + + if (!(cb.flag & SEQ_COLOR_BALANCE_INVERSE_POWER)) { + if (cb.power[c] != 0.0f) { + cb.power[c] = 1.0f / cb.power[c]; + } + else { + cb.power[c] = 1000000; + } + } + } + + return cb; +} + +static StripColorBalance calc_cb(StripColorBalance *cb_) +{ + if (cb_->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + return calc_cb_lgg(cb_); + } + /* `cb_->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER`. */ + return calc_cb_sop(cb_); +} + /* NOTE: lift is actually 2-lift. */ -MINLINE float color_balance_fl( +MINLINE float color_balance_fl_lgg( float in, const float lift, const float gain, const float gamma, const float mul) { float x = (((in - 1.0f) * lift) + 1.0f) * gain; @@ -278,12 +322,40 @@ MINLINE float color_balance_fl( return x; } -static void make_cb_table_float(float lift, float gain, float gamma, float *table, float mul) +MINLINE float color_balance_fl_sop(float in, + const float slope, + const float offset, + const float power, + const float pivot, + float mul) { - int y; + float x = in * slope + offset; + + /* prevent NaN */ + if (x < 0.0f) { + x = 0.0f; + } - for (y = 0; y < 256; y++) { - float v = color_balance_fl((float)y * (1.0f / 255.0f), lift, gain, gamma, mul); + x = powf(x / pivot, power) * pivot; + x *= mul; + CLAMP(x, FLT_MIN, FLT_MAX); + return x; +} + +static void make_cb_table_float_lgg(float lift, float gain, float gamma, float *table, float mul) +{ + for (int y = 0; y < 256; y++) { + float v = color_balance_fl_lgg((float)y * (1.0f / 255.0f), lift, gain, gamma, mul); + + table[y] = v; + } +} + +static void make_cb_table_float_sop( + float slope, float offset, float power, float pivot, float *table, float mul) +{ + for (int y = 0; y < 256; y++) { + float v = color_balance_fl_sop((float)y * (1.0f / 255.0f), slope, offset, power, pivot, mul); table[y] = v; } @@ -310,7 +382,13 @@ static void color_balance_byte_byte(StripColorBalance *cb_, straight_uchar_to_premul_float(p, cp); for (c = 0; c < 3; c++) { - float t = color_balance_fl(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + float t; + if (cb.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + t = color_balance_fl_lgg(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + } + else { + t = color_balance_fl_sop(p[c], cb.slope[c], cb.offset[c], cb.power[c], 1.0, mul); + } if (m) { float m_normal = (float)m[c] / 255.0f; @@ -352,7 +430,12 @@ static void color_balance_byte_float(StripColorBalance *cb_, cb = calc_cb(cb_); for (c = 0; c < 3; c++) { - make_cb_table_float(cb.lift[c], cb.gain[c], cb.gamma[c], cb_tab[c], mul); + if (cb.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + make_cb_table_float_lgg(cb.lift[c], cb.gain[c], cb.gamma[c], cb_tab[c], mul); + } + else { + make_cb_table_float_sop(cb.slope[c], cb.offset[c], cb.power[c], 1.0, cb_tab[c], mul); + } } for (i = 0; i < 256; i++) { @@ -397,7 +480,13 @@ static void color_balance_float_float(StripColorBalance *cb_, while (p < e) { int c; for (c = 0; c < 3; c++) { - float t = color_balance_fl(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + float t; + if (cb_->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + t = color_balance_fl_lgg(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + } + else { + t = color_balance_fl_sop(p[c], cb.slope[c], cb.offset[c], cb.power[c], 1.0, mul); + } if (m) { p[c] = p[c] * (1.0f - m[c]) + t * m[c]; @@ -507,11 +596,15 @@ static void colorBalance_init_data(SequenceModifierData *smd) int c; cbmd->color_multiply = 1.0f; + cbmd->color_balance.method = 0; for (c = 0; c < 3; c++) { cbmd->color_balance.lift[c] = 1.0f; cbmd->color_balance.gamma[c] = 1.0f; cbmd->color_balance.gain[c] = 1.0f; + cbmd->color_balance.slope[c] = 1.0f; + cbmd->color_balance.offset[c] = 1.0f; + cbmd->color_balance.power[c] = 1.0f; } } diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index 382bd51aae1..3478c2d4f97 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -144,6 +144,8 @@ Sequence *SEQ_sequence_alloc(ListBase *lb, int timeline_frame, int machine, int seq->strip = seq_strip_alloc(type); seq->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Sequence Stereo Format"); + seq->color_tag = SEQUENCE_COLOR_NONE; + SEQ_relations_session_uuid_generate(seq); return seq; @@ -636,6 +638,18 @@ void SEQ_sequence_base_dupli_recursive(const Scene *scene_src, seq_new_fix_links_recursive(seq); } } + +bool SEQ_valid_strip_channel(Sequence *seq) +{ + if (seq->machine < 1) { + return false; + } + if (seq->machine > MAXSEQ) { + return false; + } + return true; +} + /* r_prefix + [" + escaped_name + "] + \0 */ #define SEQ_RNAPATH_MAXSTR ((30 + 2 + (SEQ_NAME_MAXSTR * 2) + 2) + 1) diff --git a/source/blender/sequencer/intern/strip_relations.c b/source/blender/sequencer/intern/strip_relations.c index 46fdd2c3d14..9822bfe38f9 100644 --- a/source/blender/sequencer/intern/strip_relations.c +++ b/source/blender/sequencer/intern/strip_relations.c @@ -526,4 +526,4 @@ struct Sequence *SEQ_find_metastrip_by_sequence(ListBase *seqbase, Sequence *met } return NULL; -}
\ No newline at end of file +} diff --git a/source/blender/sequencer/intern/strip_transform.c b/source/blender/sequencer/intern/strip_transform.c index d5ff455c694..54ca4ef487f 100644 --- a/source/blender/sequencer/intern/strip_transform.c +++ b/source/blender/sequencer/intern/strip_transform.c @@ -278,7 +278,7 @@ bool SEQ_transform_seqbase_shuffle_ex(ListBase *seqbasep, SEQ_time_update_sequence(evil_scene, test); } - if ((test->machine < 1) || (test->machine > MAXSEQ)) { + if (!SEQ_valid_strip_channel(test)) { /* Blender 2.4x would remove the strip. * nicer to move it to the end */ diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 987fef570cb..cf90179f129 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -208,14 +208,16 @@ struct ID *WM_file_link_datablock(struct Main *bmain, struct View3D *v3d, const char *filepath, const short id_code, - const char *id_name); + const char *id_name, + int flag); struct ID *WM_file_append_datablock(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct View3D *v3d, const char *filepath, const short id_code, - const char *id_name); + const char *id_name, + int flag); void WM_lib_reload(struct Library *lib, struct bContext *C, struct ReportList *reports); /* mouse cursors */ @@ -906,6 +908,7 @@ int WM_event_modifier_flag(const struct wmEvent *event); bool WM_event_is_modal_tweak_exit(const struct wmEvent *event, int tweak_event); bool WM_event_is_last_mousemove(const struct wmEvent *event); bool WM_event_is_mouse_drag(const struct wmEvent *event); +bool WM_event_is_mouse_drag_or_press(const wmEvent *event); int WM_event_drag_threshold(const struct wmEvent *event); bool WM_event_drag_test(const struct wmEvent *event, const int prev_xy[2]); diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 1b6e9bd2c1d..2f98834703c 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -998,6 +998,13 @@ typedef struct wmDragAsset { const char *path; int id_type; int import_type; /* eFileAssetImportType */ + + /* FIXME: This is temporary evil solution to get scene/view-layer/etc in the copy callback of the + * #wmDropBox. + * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its + * copy callback. + * */ + struct bContext *evil_C; } wmDragAsset; typedef char *(*WMDropboxTooltipFunc)(struct bContext *, diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c index 6585349c83c..c5a89e3ad9f 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.c +++ b/source/blender/windowmanager/intern/wm_dragdrop.c @@ -42,6 +42,9 @@ #include "BKE_global.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" +#include "BKE_main.h" + +#include "BLO_readfile.h" #include "GPU_shader.h" #include "GPU_state.h" @@ -390,11 +393,43 @@ static ID *wm_drag_asset_id_import(wmDragAsset *asset_drag) const char *name = asset_drag->name; ID_Type idtype = asset_drag->id_type; + /* FIXME: Link/Append should happens in the operator called at the end of drop process, not from + * here. */ + + Main *bmain = CTX_data_main(asset_drag->evil_C); + Scene *scene = CTX_data_scene(asset_drag->evil_C); + ViewLayer *view_layer = CTX_data_view_layer(asset_drag->evil_C); + View3D *view3d = CTX_wm_view3d(asset_drag->evil_C); + switch ((eFileAssetImportType)asset_drag->import_type) { case FILE_ASSET_IMPORT_LINK: - return WM_file_link_datablock(G_MAIN, NULL, NULL, NULL, asset_drag->path, idtype, name); + return WM_file_link_datablock(bmain, + scene, + view_layer, + view3d, + asset_drag->path, + idtype, + name, + FILE_ACTIVE_COLLECTION); case FILE_ASSET_IMPORT_APPEND: - return WM_file_append_datablock(G_MAIN, NULL, NULL, NULL, asset_drag->path, idtype, name); + return WM_file_append_datablock(bmain, + scene, + view_layer, + view3d, + asset_drag->path, + idtype, + name, + BLO_LIBLINK_APPEND_RECURSIVE | FILE_ACTIVE_COLLECTION); + case FILE_ASSET_IMPORT_APPEND_REUSE: + return WM_file_append_datablock(G_MAIN, + scene, + view_layer, + view3d, + asset_drag->path, + idtype, + name, + BLO_LIBLINK_APPEND_RECURSIVE | FILE_ACTIVE_COLLECTION | + BLO_LIBLINK_APPEND_LOCAL_ID_REUSE); } BLI_assert_unreachable(); diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c index 562a1f83473..364aab9482a 100644 --- a/source/blender/windowmanager/intern/wm_event_query.c +++ b/source/blender/windowmanager/intern/wm_event_query.c @@ -270,6 +270,12 @@ bool WM_event_is_mouse_drag(const wmEvent *event) return ISTWEAK(event->type) || (ISMOUSE_BUTTON(event->type) && (event->val == KM_CLICK_DRAG)); } +bool WM_event_is_mouse_drag_or_press(const wmEvent *event) +{ + return WM_event_is_mouse_drag(event) || + (ISMOUSE_BUTTON(event->type) && (event->val == KM_PRESS)); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 3b92d858c71..f025fd43b49 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -3046,7 +3046,7 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers /* Other drop custom types allowed. */ if (event->custom == EVT_DATA_DRAGDROP) { ListBase *lb = (ListBase *)event->customdata; - LISTBASE_FOREACH (wmDrag *, drag, lb) { + LISTBASE_FOREACH_MUTABLE (wmDrag *, drag, lb) { if (drop->poll(C, drag, event)) { /* Optionally copy drag information to operator properties. Don't call it if the * operator fails anyway, it might do more than just set properties (e.g. @@ -3057,7 +3057,8 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers /* Pass single matched wmDrag onto the operator. */ BLI_remlink(lb, drag); - ListBase single_lb = {drag, drag}; + ListBase single_lb = {0}; + BLI_addtail(&single_lb, drag); event->customdata = &single_lb; int op_retval = wm_operator_call_internal( diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index 126f8abc69a..24a3b58fc26 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -869,6 +869,28 @@ static void file_read_reports_finalize(BlendFileReadReport *bf_reports) duration_lib_override_recursive_resync_seconds); } + if (bf_reports->count.linked_proxies != 0 || + bf_reports->count.proxies_to_lib_overrides_success != 0 || + bf_reports->count.proxies_to_lib_overrides_failures != 0) { + BKE_reportf(bf_reports->reports, + RPT_WARNING, + "Proxies are deprecated (%d proxies were automatically converted to library " + "overrides, %d proxies could not be converted and %d linked proxies were kept " + "untouched). If you need to keep proxies for the time being, please disable the " + "`Proxy to Override Auto Conversion` in Experimental user preferences", + bf_reports->count.proxies_to_lib_overrides_success, + bf_reports->count.proxies_to_lib_overrides_failures, + bf_reports->count.linked_proxies); + } + + if (bf_reports->count.vse_strips_skipped != 0) { + BKE_reportf(bf_reports->reports, + RPT_ERROR, + "%d sequence strips were not read because they were in a channel larger than %d", + bf_reports->count.vse_strips_skipped, + MAXSEQ); + } + BLI_linklist_free(bf_reports->resynced_lib_overrides_libraries, NULL); bf_reports->resynced_lib_overrides_libraries = NULL; } diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index 09567eca17f..a73bea31669 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -127,10 +127,10 @@ static int wm_link_append_invoke(bContext *C, wmOperator *op, const wmEvent *UNU return OPERATOR_RUNNING_MODAL; } -static short wm_link_append_flag(wmOperator *op) +static int wm_link_append_flag(wmOperator *op) { PropertyRNA *prop; - short flag = 0; + int flag = 0; if (RNA_boolean_get(op->ptr, "autoselect")) { flag |= FILE_AUTOSELECT; @@ -147,17 +147,20 @@ static short wm_link_append_flag(wmOperator *op) } else { if (RNA_boolean_get(op->ptr, "use_recursive")) { - flag |= FILE_APPEND_RECURSIVE; + flag |= BLO_LIBLINK_APPEND_RECURSIVE; } if (RNA_boolean_get(op->ptr, "set_fake")) { - flag |= FILE_APPEND_SET_FAKEUSER; + flag |= BLO_LIBLINK_APPEND_SET_FAKEUSER; + } + if (RNA_boolean_get(op->ptr, "do_reuse_local_id")) { + flag |= BLO_LIBLINK_APPEND_LOCAL_ID_REUSE; } } if (RNA_boolean_get(op->ptr, "instance_collections")) { - flag |= FILE_COLLECTION_INSTANCE; + flag |= BLO_LIBLINK_COLLECTION_INSTANCE; } if (RNA_boolean_get(op->ptr, "instance_object_data")) { - flag |= FILE_OBDATA_INSTANCE; + flag |= BLO_LIBLINK_OBDATA_INSTANCE; } return flag; @@ -396,7 +399,7 @@ static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, LinkNode *itemlink; Collection *active_collection = NULL; - const bool do_obdata = (lapp_data->flag & FILE_OBDATA_INSTANCE) != 0; + const bool do_obdata = (lapp_data->flag & BLO_LIBLINK_OBDATA_INSTANCE) != 0; const bool object_set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0; /* Do NOT make base active here! screws up GUI stuff, @@ -472,7 +475,7 @@ static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, /* In case user requested instantiation of collections as empties, we do so for the one they * explicitly selected (originally directly linked IDs). */ - if ((lapp_data->flag & FILE_COLLECTION_INSTANCE) != 0 && + if ((lapp_data->flag & BLO_LIBLINK_COLLECTION_INSTANCE) != 0 && (item->append_tag & WM_APPEND_TAG_INDIRECT) == 0) { /* BKE_object_add(...) messes with the selection. */ Object *ob = BKE_object_add_only_object(bmain, OB_EMPTY, collection->id.name + 2); @@ -573,6 +576,8 @@ static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, ob, object_set_selected, object_set_active, view_layer, v3d); copy_v3_v3(ob->loc, scene->cursor.location); + + id->tag &= ~LIB_TAG_DOIT; } } @@ -592,6 +597,11 @@ static int foreach_libblock_append_callback(LibraryIDLinkCallbackData *cb_data) } if (!BKE_idtype_idcode_is_linkable(GS(id->name))) { + /* While we do not want to add non-linkable ID (shape keys...) to the list of linked items, + * unfortunately they can use fully linkable valid IDs too, like actions. Those need to be + * processed, so we need to recursively deal with them here. */ + BKE_library_foreach_ID_link( + cb_data->bmain, id, foreach_libblock_append_callback, data, IDWALK_NOP); return IDWALK_RET_NOP; } @@ -626,8 +636,9 @@ static void wm_append_do(WMLinkAppendData *lapp_data, { BLI_assert((lapp_data->flag & FILE_LINK) == 0); - const bool do_recursive = (lapp_data->flag & FILE_APPEND_RECURSIVE) != 0; - const bool set_fakeuser = (lapp_data->flag & FILE_APPEND_SET_FAKEUSER) != 0; + const bool do_recursive = (lapp_data->flag & BLO_LIBLINK_APPEND_RECURSIVE) != 0; + const bool set_fakeuser = (lapp_data->flag & BLO_LIBLINK_APPEND_SET_FAKEUSER) != 0; + const bool do_reuse_local_id = (lapp_data->flag & BLO_LIBLINK_APPEND_LOCAL_ID_REUSE) != 0; LinkNode *itemlink; @@ -642,7 +653,6 @@ static void wm_append_do(WMLinkAppendData *lapp_data, BLI_ghash_insert(lapp_data->new_id_to_item, id, item); } - const bool do_reuse_existing_id = false; lapp_data->library_weak_reference_mapping = BKE_main_library_weak_reference_create(bmain); /* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect @@ -656,8 +666,8 @@ static void wm_append_do(WMLinkAppendData *lapp_data, } BLI_assert(item->customdata == NULL); - /* Clear tag previously used to mark IDs needing post-processing (instantiation of loose - * objects etc.). */ + /* In Append case linked IDs should never be marked as needing post-processing (instantiation + * of loose objects etc.). */ BLI_assert((id->tag & LIB_TAG_DOIT) == 0); ID *existing_local_id = BKE_idtype_idcode_append_is_reusable(GS(id->name)) ? @@ -674,10 +684,7 @@ static void wm_append_do(WMLinkAppendData *lapp_data, CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name); item->append_action = WM_APPEND_ACT_KEEP_LINKED; } - /* Only re-use existing local ID for indirectly linked data, the ID explicitely selected by the - * user we always fully append. */ - else if (do_reuse_existing_id && existing_local_id != NULL && - (item->append_tag & WM_APPEND_TAG_INDIRECT) != 0) { + else if (do_reuse_local_id && existing_local_id != NULL) { CLOG_INFO(&LOG, 3, "Appended ID '%s' as a matching local one, re-using it...", id->name); item->append_action = WM_APPEND_ACT_REUSE_LOCAL; item->customdata = existing_local_id; @@ -806,6 +813,27 @@ static void wm_append_do(WMLinkAppendData *lapp_data, BKE_libblock_relink_to_newid_new(bmain, id); } + /* Remove linked IDs when a local existing data has been reused instead. */ + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + + if (item->append_action != WM_APPEND_ACT_REUSE_LOCAL) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(ID_IS_LINKED(id)); + BLI_assert(id->newid != NULL); + + id->tag |= LIB_TAG_DOIT; + item->new_id = id->newid; + } + BKE_id_multi_tagged_delete(bmain); + /* Instantiate newly created (duplicated) IDs as needed. */ wm_append_loose_data_instantiate(lapp_data, bmain, scene, view_layer, v3d); @@ -1057,7 +1085,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - short flag = wm_link_append_flag(op); + int flag = wm_link_append_flag(op); const bool do_append = (flag & FILE_LINK) == 0; /* sanity checks for flag */ @@ -1066,12 +1094,10 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) RPT_WARNING, "Scene '%s' is linked, instantiation of objects is disabled", scene->id.name + 2); - flag &= ~(FILE_COLLECTION_INSTANCE | FILE_OBDATA_INSTANCE); + flag &= ~(BLO_LIBLINK_COLLECTION_INSTANCE | BLO_LIBLINK_OBDATA_INSTANCE); scene = NULL; } - /* We need to add nothing from #eBLOLibLinkFlags to flag here. */ - /* from here down, no error returns */ if (view_layer && RNA_boolean_get(op->ptr, "autoselect")) { @@ -1201,14 +1227,25 @@ static void wm_link_append_properties_common(wmOperatorType *ot, bool is_link) prop = RNA_def_boolean( ot->srna, "link", is_link, "Link", "Link the objects or data-blocks rather than appending"); RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); + + prop = RNA_def_boolean( + ot->srna, + "do_reuse_local_id", + false, + "Re-Use Local Data", + "Try to re-use previously matching appended data-blocks instead of appending a new copy"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); + prop = RNA_def_boolean(ot->srna, "autoselect", true, "Select", "Select new objects"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "active_collection", true, "Active Collection", "Put new objects on the active collection"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean( ot->srna, "instance_collections", @@ -1299,13 +1336,14 @@ static ID *wm_file_link_append_datablock_ex(Main *bmain, const char *filepath, const short id_code, const char *id_name, - const bool do_append) + const int flag) { + const bool do_append = (flag & FILE_LINK) == 0; /* Tag everything so we can make local only the new datablock. */ BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); /* Define working data, with just the one item we want to link. */ - WMLinkAppendData *lapp_data = wm_link_append_data_new(do_append ? FILE_APPEND_RECURSIVE : 0); + WMLinkAppendData *lapp_data = wm_link_append_data_new(flag); wm_link_append_data_library_add(lapp_data, filepath); WMLinkAppendDataItem *item = wm_link_append_data_item_add(lapp_data, id_name, id_code, NULL); @@ -1314,13 +1352,13 @@ static ID *wm_file_link_append_datablock_ex(Main *bmain, /* Link datablock. */ wm_link_do(lapp_data, NULL, bmain, scene, view_layer, v3d); - /* Get linked datablock and free working data. */ - ID *id = item->new_id; - if (do_append) { wm_append_do(lapp_data, NULL, bmain, scene, view_layer, v3d); } + /* Get linked datablock and free working data. */ + ID *id = item->new_id; + wm_link_append_data_free(lapp_data); BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); @@ -1338,10 +1376,12 @@ ID *WM_file_link_datablock(Main *bmain, View3D *v3d, const char *filepath, const short id_code, - const char *id_name) + const char *id_name, + int flag) { + flag |= FILE_LINK; return wm_file_link_append_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, false); + bmain, scene, view_layer, v3d, filepath, id_code, id_name, flag); } /* @@ -1354,10 +1394,12 @@ ID *WM_file_append_datablock(Main *bmain, View3D *v3d, const char *filepath, const short id_code, - const char *id_name) + const char *id_name, + int flag) { + BLI_assert((flag & FILE_LINK) == 0); ID *id = wm_file_link_append_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, true); + bmain, scene, view_layer, v3d, filepath, id_code, id_name, flag); return id; } diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c index 06d5c16108f..b087bd9d668 100644 --- a/source/blender/windowmanager/intern/wm_gesture_ops.c +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -184,7 +184,8 @@ int WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input"); + const bool wait_for_input = !WM_event_is_mouse_drag_or_press(event) && + RNA_boolean_get(op->ptr, "wait_for_input"); if (wait_for_input) { op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_CROSS_RECT); @@ -438,7 +439,8 @@ static void gesture_circle_apply(bContext *C, wmOperator *op); int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); - const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input"); + const bool wait_for_input = !WM_event_is_mouse_drag_or_press(event) && + RNA_boolean_get(op->ptr, "wait_for_input"); op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_CIRCLE); wmGesture *gesture = op->customdata; @@ -1009,7 +1011,7 @@ int WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *e op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_STRAIGHTLINE); - if (ISTWEAK(event->type)) { + if (WM_event_is_mouse_drag_or_press(event)) { wmGesture *gesture = op->customdata; gesture->is_active = true; } diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index a8d2e000108..ad04416613a 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -373,13 +373,6 @@ void WM_init(bContext *C, int argc, const char **argv) BLI_strncpy(G.lib, BKE_main_blendfile_path_from_global(), sizeof(G.lib)); -#ifdef WITH_COMPOSITOR - if (1) { - extern void *COM_linker_hack; - COM_linker_hack = COM_execute; - } -#endif - wm_homefile_read_post(C, params_file_read_post); } diff --git a/source/creator/creator_signals.c b/source/creator/creator_signals.c index 29e12a96fe1..5604fb4c58d 100644 --- a/source/creator/creator_signals.c +++ b/source/creator/creator_signals.c @@ -79,7 +79,7 @@ static void sig_handle_fpe(int UNUSED(sig)) } # endif -/* handling ctrl-c event in console */ +/* Handling `Ctrl-C` event in the console. */ # if !defined(WITH_HEADLESS) static void sig_handle_blender_esc(int sig) { diff --git a/source/tools b/source/tools -Subproject 723b24841df1ed8478949bca20c73878fab00dc +Subproject 01f51a0e551ab730f0934dc6488613690ac4bf8 |