diff options
author | Cian Jinks <cjinks99@gmail.com> | 2021-09-21 02:14:59 +0300 |
---|---|---|
committer | Cian Jinks <cjinks99@gmail.com> | 2021-09-21 02:14:59 +0300 |
commit | 5db0bb87019139a5d3270108e242d2a6bcb1e652 (patch) | |
tree | fdcd7b8d531779106e1562b54f64f292dfb30b04 | |
parent | e232de687e5e8574380651acdc2976d955c0f8dd (diff) | |
parent | 13a4bccdb196770b4f357c2a3c312bd4629ecb36 (diff) |
Merge branch 'master' into soc-2021-knife-tools
594 files changed, 22429 insertions, 6093 deletions
diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake index e3183fe5b7f..cb4d196d43f 100644 --- a/build_files/cmake/platform/platform_win32.cmake +++ b/build_files/cmake/platform/platform_win32.cmake @@ -259,7 +259,7 @@ if(NOT DEFINED LIBDIR) else() message(FATAL_ERROR "32 bit compiler detected, blender no longer provides pre-build libraries for 32 bit windows, please set the LIBDIR cmake variable to your own library folder") endif() - if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.29.30130) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.30.30423) message(STATUS "Visual Studio 2022 detected.") set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_BASE}_vc15) elseif(MSVC_VERSION GREATER 1919) diff --git a/build_files/utils/make_update.py b/build_files/utils/make_update.py index b901fa56f52..30ef090efbb 100755 --- a/build_files/utils/make_update.py +++ b/build_files/utils/make_update.py @@ -200,15 +200,20 @@ def submodules_update(args, release_version, branch): if msg: skip_msg += submodule_path + " skipped: " + msg + "\n" else: - if make_utils.git_branch(args.git_command) != submodule_branch: - call([args.git_command, "fetch", "origin"]) - call([args.git_command, "checkout", submodule_branch]) - call([args.git_command, "pull", "--rebase", "origin", submodule_branch]) - # If we cannot find the specified branch for this submodule, fallback to default one (aka master). - if make_utils.git_branch(args.git_command) != submodule_branch: - call([args.git_command, "fetch", "origin"]) - call([args.git_command, "checkout", submodule_branch_fallback]) - call([args.git_command, "pull", "--rebase", "origin", submodule_branch_fallback]) + # Find a matching branch that exists. + call([args.git_command, "fetch", "origin"]) + if make_utils.git_branch_exists(args.git_command, submodule_branch): + pass + elif make_utils.git_branch_exists(args.git_command, submodule_branch_fallback): + submodule_branch = submodule_branch_fallback + else: + submodule_branch = None + + # Switch to branch and pull. + if submodule_branch: + if make_utils.git_branch(args.git_command) != submodule_branch: + call([args.git_command, "checkout", submodule_branch]) + call([args.git_command, "pull", "--rebase", "origin", submodule_branch]) finally: os.chdir(cwd) @@ -222,6 +227,10 @@ if __name__ == "__main__": # Test if we are building a specific release version. branch = make_utils.git_branch(args.git_command) + if branch == 'HEAD': + sys.stderr.write('Blender git repository is in detached HEAD state, must be in a branch\n') + sys.exit(1) + tag = make_utils.git_tag(args.git_command) release_version = make_utils.git_branch_release_version(branch, tag) diff --git a/build_files/utils/make_utils.py b/build_files/utils/make_utils.py index 7cd23baad68..9def0059ceb 100755 --- a/build_files/utils/make_utils.py +++ b/build_files/utils/make_utils.py @@ -8,14 +8,19 @@ import subprocess import sys -def call(cmd, exit_on_error=True): - print(" ".join(cmd)) +def call(cmd, exit_on_error=True, silent=False): + if not silent: + print(" ".join(cmd)) # Flush to ensure correct order output on Windows. sys.stdout.flush() sys.stderr.flush() - retcode = subprocess.call(cmd) + if silent: + retcode = subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + else: + retcode = subprocess.call(cmd) + if exit_on_error and retcode != 0: sys.exit(retcode) return retcode @@ -38,6 +43,11 @@ def check_output(cmd, exit_on_error=True): return output.strip() +def git_branch_exists(git_command, branch): + return call([git_command, "rev-parse", "--verify", branch], exit_on_error=False, silent=True) == 0 or \ + call([git_command, "rev-parse", "--verify", "remotes/origin/" + branch], exit_on_error=False, silent=True) == 0 + + def git_branch(git_command): # Get current branch name. try: @@ -70,7 +80,7 @@ def git_branch_release_version(branch, tag): return release_version -def svn_libraries_base_url(release_version, branch): +def svn_libraries_base_url(release_version, branch=None): if release_version: svn_branch = "tags/blender-" + release_version + "-release" elif branch: diff --git a/build_files/windows/icons.cmd b/build_files/windows/icons.cmd index 473a40885a8..d51b27d8953 100644 --- a/build_files/windows/icons.cmd +++ b/build_files/windows/icons.cmd @@ -1,4 +1,4 @@ -if EXIST %PYTHON% ( +if EXIST "%PYTHON%" ( goto detect_python_done ) diff --git a/doc/python_api/examples/aud.py b/doc/python_api/examples/aud.py index a52258c1a45..0eb27647671 100644 --- a/doc/python_api/examples/aud.py +++ b/doc/python_api/examples/aud.py @@ -14,7 +14,7 @@ sound = aud.Sound('music.ogg') # play the audio, this return a handle to control play/pause handle = device.play(sound) # if the audio is not too big and will be used often you can buffer it -sound_buffered = aud.Sound.buffer(sound) +sound_buffered = aud.Sound.cache(sound) handle_buffered = device.play(sound_buffered) # stop the sounds (otherwise they play until their ends) diff --git a/doc/python_api/requirements.txt b/doc/python_api/requirements.txt index b5a9d15bf7b..51440046430 100644 --- a/doc/python_api/requirements.txt +++ b/doc/python_api/requirements.txt @@ -10,4 +10,4 @@ requests==2.26.0 # Only needed to match the theme used for the official documentation. # Without this theme, the default theme will be used. -sphinx_rtd_theme==1.0.0rc1 +sphinx_rtd_theme==1.0.0 diff --git a/extern/audaspace/CMakeLists.txt b/extern/audaspace/CMakeLists.txt index 1599c03cbad..552ff749512 100644 --- a/extern/audaspace/CMakeLists.txt +++ b/extern/audaspace/CMakeLists.txt @@ -152,6 +152,7 @@ set(PUBLIC_HDR include/devices/ThreadedDevice.h include/Exception.h include/file/File.h + include/file/FileInfo.h include/file/FileManager.h include/file/FileWriter.h include/file/IFileInput.h @@ -960,7 +961,10 @@ endif() if(BUILD_DEMOS) include_directories(${INCLUDE}) - set(DEMOS audaplay audaconvert audaremap signalgen randsounds dynamicmusic playbackmanager) + set(DEMOS audainfo audaplay audaconvert audaremap signalgen randsounds dynamicmusic playbackmanager) + + add_executable(audainfo demos/audainfo.cpp) + target_link_libraries(audainfo audaspace) add_executable(audaplay demos/audaplay.cpp) target_link_libraries(audaplay audaspace) diff --git a/extern/audaspace/bindings/C/AUD_PlaybackManager.h b/extern/audaspace/bindings/C/AUD_PlaybackManager.h index 0fa8171599d..a2f5134602a 100644 --- a/extern/audaspace/bindings/C/AUD_PlaybackManager.h +++ b/extern/audaspace/bindings/C/AUD_PlaybackManager.h @@ -39,7 +39,7 @@ extern AUD_API void AUD_PlaybackManager_free(AUD_PlaybackManager* manager); * Plays a sound through the playback manager, adding it into a category. * \param manager The PlaybackManager object. * \param sound The sound to be played. -* \param catKey The key of the category into which the sound will be added. If it doesn't exist a new one will be creatd. +* \param catKey The key of the category into which the sound will be added. If it doesn't exist a new one will be created. */ extern AUD_API void AUD_PlaybackManager_play(AUD_PlaybackManager* manager, AUD_Sound* sound, unsigned int catKey); diff --git a/extern/audaspace/bindings/C/AUD_Sound.cpp b/extern/audaspace/bindings/C/AUD_Sound.cpp index 8c99ce2341f..aa246b9a047 100644 --- a/extern/audaspace/bindings/C/AUD_Sound.cpp +++ b/extern/audaspace/bindings/C/AUD_Sound.cpp @@ -94,6 +94,36 @@ AUD_API int AUD_Sound_getLength(AUD_Sound* sound) return (*sound)->createReader()->getLength(); } +AUD_API int AUD_Sound_getFileStreams(AUD_Sound* sound, AUD_StreamInfo **stream_infos) +{ + assert(sound); + + std::shared_ptr<File> file = std::dynamic_pointer_cast<File>(*sound); + + if(file) + { + auto streams = file->queryStreams(); + + size_t size = sizeof(AUD_StreamInfo) * streams.size(); + + if(!size) + { + *stream_infos = nullptr; + return 0; + } + + *stream_infos = reinterpret_cast<AUD_StreamInfo*>(std::malloc(size)); + std::memcpy(*stream_infos, streams.data(), size); + + return streams.size(); + } + else + { + *stream_infos = nullptr; + return 0; + } +} + AUD_API sample_t* AUD_Sound_data(AUD_Sound* sound, int* length, AUD_Specs* specs) { assert(sound); @@ -252,6 +282,12 @@ AUD_API AUD_Sound* AUD_Sound_bufferFile(unsigned char* buffer, int size) return new AUD_Sound(new File(buffer, size)); } +AUD_API AUD_Sound* AUD_Sound_bufferFileStream(unsigned char* buffer, int size, int stream) +{ + assert(buffer); + return new AUD_Sound(new File(buffer, size, stream)); +} + AUD_API AUD_Sound* AUD_Sound_cache(AUD_Sound* sound) { assert(sound); @@ -272,6 +308,12 @@ AUD_API AUD_Sound* AUD_Sound_file(const char* filename) return new AUD_Sound(new File(filename)); } +AUD_API AUD_Sound* AUD_Sound_fileStream(const char* filename, int stream) +{ + assert(filename); + return new AUD_Sound(new File(filename, stream)); +} + AUD_API AUD_Sound* AUD_Sound_sawtooth(float frequency, AUD_SampleRate rate) { return new AUD_Sound(new Sawtooth(frequency, rate)); diff --git a/extern/audaspace/bindings/C/AUD_Sound.h b/extern/audaspace/bindings/C/AUD_Sound.h index 53172616781..fc73a31e15c 100644 --- a/extern/audaspace/bindings/C/AUD_Sound.h +++ b/extern/audaspace/bindings/C/AUD_Sound.h @@ -36,7 +36,15 @@ extern AUD_API AUD_Specs AUD_Sound_getSpecs(AUD_Sound* sound); * \return The length of the sound in samples. * \note This function creates a reader from the sound and deletes it again. */ -extern AUD_API int AUD_getLength(AUD_Sound* sound); +extern AUD_API int AUD_Sound_getLength(AUD_Sound* sound); + +/** + * Retrieves the stream infos of a sound file. + * \param sound The sound to retrieve from which must be a file sound. + * \param infos A pointer to a AUD_StreamInfo array that will be allocated and must afterwards be freed by the caller. + * \return The number of items in the infos array. + */ +extern AUD_API int AUD_Sound_getFileStreams(AUD_Sound* sound, AUD_StreamInfo** stream_infos); /** * Reads a sound's samples into memory. @@ -90,6 +98,15 @@ extern AUD_API AUD_Sound* AUD_Sound_buffer(sample_t* data, int length, AUD_Specs extern AUD_API AUD_Sound* AUD_Sound_bufferFile(unsigned char* buffer, int size); /** + * Loads a sound file from a memory buffer. + * \param buffer The buffer which contains the sound file. + * \param size The size of the buffer. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. + * \return A handle of the sound file. + */ +extern AUD_API AUD_Sound* AUD_Sound_bufferFileStream(unsigned char* buffer, int size, int stream); + +/** * Caches a sound into a memory buffer. * \param sound The sound to cache. * \return A handle of the cached sound. @@ -104,6 +121,14 @@ extern AUD_API AUD_Sound* AUD_Sound_cache(AUD_Sound* sound); extern AUD_API AUD_Sound* AUD_Sound_file(const char* filename); /** + * Loads a sound file. + * \param filename The filename of the sound file. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. + * \return A handle of the sound file. + */ +extern AUD_API AUD_Sound* AUD_Sound_fileStream(const char* filename, int stream); + +/** * Creates a sawtooth sound. * \param frequency The frequency of the generated sawtooth sound. * \param rate The sample rate of the sawtooth sound. diff --git a/extern/audaspace/bindings/C/AUD_Special.cpp b/extern/audaspace/bindings/C/AUD_Special.cpp index 5cc33525d1d..1ce25dcd41c 100644 --- a/extern/audaspace/bindings/C/AUD_Special.cpp +++ b/extern/audaspace/bindings/C/AUD_Special.cpp @@ -86,7 +86,6 @@ AUD_API AUD_SoundInfo AUD_getInfo(AUD_Sound* sound) info.specs.channels = AUD_CHANNELS_INVALID; info.specs.rate = AUD_RATE_INVALID; info.length = 0.0f; - info.start_offset = 0.0f; try { @@ -96,7 +95,6 @@ AUD_API AUD_SoundInfo AUD_getInfo(AUD_Sound* sound) { info.specs = convSpecToC(reader->getSpecs()); info.length = reader->getLength() / (float) info.specs.rate; - info.start_offset = reader->getStartOffset(); } } catch(Exception&) @@ -109,7 +107,7 @@ AUD_API AUD_SoundInfo AUD_getInfo(AUD_Sound* sound) AUD_API float* AUD_readSoundBuffer(const char* filename, float low, float high, float attack, float release, float threshold, int accumulate, int additive, int square, - float sthreshold, double samplerate, int* length) + float sthreshold, double samplerate, int* length, int stream) { Buffer buffer; DeviceSpecs specs; @@ -117,7 +115,7 @@ AUD_API float* AUD_readSoundBuffer(const char* filename, float low, float high, specs.rate = (SampleRate)samplerate; std::shared_ptr<ISound> sound; - std::shared_ptr<ISound> file = std::shared_ptr<ISound>(new File(filename)); + std::shared_ptr<ISound> file = std::shared_ptr<ISound>(new File(filename, stream)); int position = 0; @@ -247,7 +245,7 @@ AUD_API int AUD_readSound(AUD_Sound* sound, float* buffer, int length, int sampl buffer[i * 3] = min; buffer[i * 3 + 1] = max; - buffer[i * 3 + 2] = sqrt(power / len); // RMS + buffer[i * 3 + 2] = std::sqrt(power / len); if(overallmax < max) overallmax = max; diff --git a/extern/audaspace/bindings/C/AUD_Special.h b/extern/audaspace/bindings/C/AUD_Special.h index ce51fa2e04e..2f5d13c6fd9 100644 --- a/extern/audaspace/bindings/C/AUD_Special.h +++ b/extern/audaspace/bindings/C/AUD_Special.h @@ -37,7 +37,7 @@ extern AUD_API float* AUD_readSoundBuffer(const char* filename, float low, float float attack, float release, float threshold, int accumulate, int additive, int square, float sthreshold, double samplerate, - int* length); + int* length, int stream); /** * Pauses a playing sound after a specific amount of time. diff --git a/extern/audaspace/bindings/C/AUD_Types.h b/extern/audaspace/bindings/C/AUD_Types.h index c6a96d30d3f..0f95366bc27 100644 --- a/extern/audaspace/bindings/C/AUD_Types.h +++ b/extern/audaspace/bindings/C/AUD_Types.h @@ -176,5 +176,17 @@ typedef struct { AUD_Specs specs; float length; - double start_offset; } AUD_SoundInfo; + +/// Specification of a sound source. +typedef struct +{ + /// Start time in seconds. + double start; + + /// Duration in seconds. May be estimated or 0 if unknown. + double duration; + + /// Audio data parameters. + AUD_DeviceSpecs specs; +} AUD_StreamInfo; diff --git a/extern/audaspace/bindings/python/PySound.cpp b/extern/audaspace/bindings/python/PySound.cpp index 33628307249..2236057e7d2 100644 --- a/extern/audaspace/bindings/python/PySound.cpp +++ b/extern/audaspace/bindings/python/PySound.cpp @@ -89,10 +89,11 @@ Sound_new(PyTypeObject* type, PyObject* args, PyObject* kwds) self = (Sound*)type->tp_alloc(type, 0); if(self != nullptr) { - static const char* kwlist[] = {"filename", nullptr}; + static const char* kwlist[] = {"filename", "stream", nullptr}; const char* filename = nullptr; + int stream = 0; - if(!PyArg_ParseTupleAndKeywords(args, kwds, "s:Sound", const_cast<char**>(kwlist), &filename)) + if(!PyArg_ParseTupleAndKeywords(args, kwds, "s|i:Sound", const_cast<char**>(kwlist), &filename, &stream)) { Py_DECREF(self); return nullptr; @@ -100,7 +101,7 @@ Sound_new(PyTypeObject* type, PyObject* args, PyObject* kwds) try { - self->sound = new std::shared_ptr<ISound>(new File(filename)); + self->sound = new std::shared_ptr<ISound>(new File(filename, stream)); } catch(Exception& e) { @@ -407,8 +408,9 @@ static PyObject * Sound_file(PyTypeObject* type, PyObject* args) { const char* filename = nullptr; + int stream = 0; - if(!PyArg_ParseTuple(args, "s:file", &filename)) + if(!PyArg_ParseTuple(args, "s|i:file", &filename, &stream)) return nullptr; Sound* self; @@ -418,7 +420,7 @@ Sound_file(PyTypeObject* type, PyObject* args) { try { - self->sound = new std::shared_ptr<ISound>(new File(filename)); + self->sound = new std::shared_ptr<ISound>(new File(filename, stream)); } catch(Exception& e) { diff --git a/extern/audaspace/include/IReader.h b/extern/audaspace/include/IReader.h index f6070b0f23b..c29900ca579 100644 --- a/extern/audaspace/include/IReader.h +++ b/extern/audaspace/include/IReader.h @@ -71,12 +71,6 @@ public: virtual int getPosition() const=0; /** - * Returns the start offset the sound should have to line up with related sources. - * \return The required start offset in seconds. - */ - virtual double getStartOffset() const { return 0.0;} - - /** * Returns the specification of the reader. * \return The Specs structure. */ diff --git a/extern/audaspace/include/file/File.h b/extern/audaspace/include/file/File.h index 24745a757e8..ac490acba38 100644 --- a/extern/audaspace/include/file/File.h +++ b/extern/audaspace/include/file/File.h @@ -23,9 +23,11 @@ */ #include "ISound.h" +#include "FileInfo.h" #include <string> #include <memory> +#include <vector> AUD_NAMESPACE_BEGIN @@ -48,6 +50,14 @@ private: */ std::shared_ptr<Buffer> m_buffer; + /** + * The index of the stream within the file if it contains multiple. + * The first audio stream in the file has index 0 and the index increments by one + * for every other audio stream in the file. Other types of streams in the file + * do not count. + */ + int m_stream; + // delete copy constructor and operator= File(const File&) = delete; File& operator=(const File&) = delete; @@ -57,16 +67,25 @@ public: * Creates a new sound. * The file is read from the file system using the given path. * \param filename The sound file path. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. */ - File(std::string filename); + File(std::string filename, int stream = 0); /** * Creates a new sound. * The file is read from memory using the supplied buffer. * \param buffer The buffer to read from. * \param size The size of the buffer. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. + */ + File(const data_t* buffer, int size, int stream = 0); + + /** + * Queries the streams of the file. + * \return A vector with as many streams as there are in the file. + * \exception Exception Thrown if the file specified cannot be read. */ - File(const data_t* buffer, int size); + std::vector<StreamInfo> queryStreams(); virtual std::shared_ptr<IReader> createReader(); }; diff --git a/extern/audaspace/include/file/FileInfo.h b/extern/audaspace/include/file/FileInfo.h new file mode 100644 index 00000000000..53ba99a5f67 --- /dev/null +++ b/extern/audaspace/include/file/FileInfo.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright 2009-2016 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#pragma once + +/** + * @file FileInfo.h + * @ingroup file + * The FileInfo data structures. + */ + +#include "respec/Specification.h" + +AUD_NAMESPACE_BEGIN + +/// Specification of a sound source. +struct StreamInfo +{ + /// Start time in seconds. + double start; + + /// Duration in seconds. May be estimated or 0 if unknown. + double duration; + + /// Audio data parameters. + DeviceSpecs specs; +}; + +AUD_NAMESPACE_END diff --git a/extern/audaspace/include/file/FileManager.h b/extern/audaspace/include/file/FileManager.h index 56708607ea6..e19eef65b1c 100644 --- a/extern/audaspace/include/file/FileManager.h +++ b/extern/audaspace/include/file/FileManager.h @@ -22,12 +22,14 @@ * The FileManager class. */ +#include "FileInfo.h" #include "respec/Specification.h" #include "IWriter.h" #include <list> #include <memory> #include <string> +#include <vector> AUD_NAMESPACE_BEGIN @@ -66,18 +68,36 @@ public: /** * Creates a file reader for the given filename if a registed IFileInput is able to read it. * @param filename The path to the file. + * @param stream The index of the audio stream within the file if it contains multiple audio streams. * @return The reader created. * @exception Exception If no file input can read the file an exception is thrown. */ - static std::shared_ptr<IReader> createReader(std::string filename); + static std::shared_ptr<IReader> createReader(std::string filename, int stream = 0); /** * Creates a file reader for the given buffer if a registed IFileInput is able to read it. * @param buffer The buffer to read the file from. + * @param stream The index of the audio stream within the file if it contains multiple audio streams. * @return The reader created. * @exception Exception If no file input can read the file an exception is thrown. */ - static std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer); + static std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer, int stream = 0); + + /** + * Queries the streams of a sound file. + * \param filename Path to the file to be read. + * \return A vector with as many streams as there are in the file. + * \exception Exception Thrown if the file specified cannot be read. + */ + static std::vector<StreamInfo> queryStreams(std::string filename); + + /** + * Queries the streams of a sound file. + * \param buffer The in-memory file buffer. + * \return A vector with as many streams as there are in the file. + * \exception Exception Thrown if the file specified cannot be read. + */ + static std::vector<StreamInfo> queryStreams(std::shared_ptr<Buffer> buffer); /** * Creates a file writer that writes a sound to the given file path. diff --git a/extern/audaspace/include/file/IFileInput.h b/extern/audaspace/include/file/IFileInput.h index 64074910d13..4a3fe446852 100644 --- a/extern/audaspace/include/file/IFileInput.h +++ b/extern/audaspace/include/file/IFileInput.h @@ -23,9 +23,11 @@ */ #include "Audaspace.h" +#include "FileInfo.h" #include <memory> #include <string> +#include <vector> AUD_NAMESPACE_BEGIN @@ -48,18 +50,36 @@ public: /** * Creates a reader for a file to be read. * \param filename Path to the file to be read. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. * \return The reader that reads the file. * \exception Exception Thrown if the file specified cannot be read. */ - virtual std::shared_ptr<IReader> createReader(std::string filename)=0; + virtual std::shared_ptr<IReader> createReader(std::string filename, int stream = 0)=0; /** * Creates a reader for a file to be read from memory. * \param buffer The in-memory file buffer. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. * \return The reader that reads the file. * \exception Exception Thrown if the file specified cannot be read. */ - virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer)=0; + virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer, int stream = 0)=0; + + /** + * Queries the streams of a sound file. + * \param filename Path to the file to be read. + * \return A vector with as many streams as there are in the file. + * \exception Exception Thrown if the file specified cannot be read. + */ + virtual std::vector<StreamInfo> queryStreams(std::string filename)=0; + + /** + * Queries the streams of a sound file. + * \param buffer The in-memory file buffer. + * \return A vector with as many streams as there are in the file. + * \exception Exception Thrown if the file specified cannot be read. + */ + virtual std::vector<StreamInfo> queryStreams(std::shared_ptr<Buffer> buffer)=0; }; AUD_NAMESPACE_END diff --git a/extern/audaspace/include/fx/VolumeReader.h b/extern/audaspace/include/fx/VolumeReader.h index f7169f4c78b..13b6845e931 100644 --- a/extern/audaspace/include/fx/VolumeReader.h +++ b/extern/audaspace/include/fx/VolumeReader.h @@ -67,4 +67,4 @@ public: virtual void read(int& length, bool& eos, sample_t* buffer); }; -AUD_NAMESPACE_END +AUD_NAMESPACE_END
\ No newline at end of file diff --git a/extern/audaspace/plugins/ffmpeg/FFMPEG.cpp b/extern/audaspace/plugins/ffmpeg/FFMPEG.cpp index 3ffe963b2b9..07c0fee691a 100644 --- a/extern/audaspace/plugins/ffmpeg/FFMPEG.cpp +++ b/extern/audaspace/plugins/ffmpeg/FFMPEG.cpp @@ -35,14 +35,24 @@ void FFMPEG::registerPlugin() FileManager::registerOutput(plugin); } -std::shared_ptr<IReader> FFMPEG::createReader(std::string filename) +std::shared_ptr<IReader> FFMPEG::createReader(std::string filename, int stream) { - return std::shared_ptr<IReader>(new FFMPEGReader(filename)); + return std::shared_ptr<IReader>(new FFMPEGReader(filename, stream)); } -std::shared_ptr<IReader> FFMPEG::createReader(std::shared_ptr<Buffer> buffer) +std::shared_ptr<IReader> FFMPEG::createReader(std::shared_ptr<Buffer> buffer, int stream) { - return std::shared_ptr<IReader>(new FFMPEGReader(buffer)); + return std::shared_ptr<IReader>(new FFMPEGReader(buffer, stream)); +} + +std::vector<StreamInfo> FFMPEG::queryStreams(std::string filename) +{ + return FFMPEGReader(filename).queryStreams(); +} + +std::vector<StreamInfo> FFMPEG::queryStreams(std::shared_ptr<Buffer> buffer) +{ + return FFMPEGReader(buffer).queryStreams(); } std::shared_ptr<IWriter> FFMPEG::createWriter(std::string filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate) diff --git a/extern/audaspace/plugins/ffmpeg/FFMPEG.h b/extern/audaspace/plugins/ffmpeg/FFMPEG.h index 108ba547e0f..fb40ba05573 100644 --- a/extern/audaspace/plugins/ffmpeg/FFMPEG.h +++ b/extern/audaspace/plugins/ffmpeg/FFMPEG.h @@ -52,8 +52,10 @@ public: */ static void registerPlugin(); - virtual std::shared_ptr<IReader> createReader(std::string filename); - virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer); + virtual std::shared_ptr<IReader> createReader(std::string filename, int stream = 0); + virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer, int stream = 0); + virtual std::vector<StreamInfo> queryStreams(std::string filename); + virtual std::vector<StreamInfo> queryStreams(std::shared_ptr<Buffer> buffer); virtual std::shared_ptr<IWriter> createWriter(std::string filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate); }; diff --git a/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp b/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp index afdc7fcfcc6..de3ca099696 100644 --- a/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp +++ b/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp @@ -31,6 +31,25 @@ AUD_NAMESPACE_BEGIN #define FFMPEG_OLD_CODE #endif +SampleFormat FFMPEGReader::convertSampleFormat(AVSampleFormat format) +{ + switch(av_get_packed_sample_fmt(format)) + { + case AV_SAMPLE_FMT_U8: + return FORMAT_U8; + case AV_SAMPLE_FMT_S16: + return FORMAT_S16; + case AV_SAMPLE_FMT_S32: + return FORMAT_S32; + case AV_SAMPLE_FMT_FLT: + return FORMAT_FLOAT32; + case AV_SAMPLE_FMT_DBL: + return FORMAT_FLOAT64; + default: + AUD_THROW(FileException, "FFMPEG sample format unknown."); + } +} + int FFMPEGReader::decode(AVPacket& packet, Buffer& buffer) { int buf_size = buffer.getSize(); @@ -68,7 +87,7 @@ int FFMPEGReader::decode(AVPacket& packet, Buffer& buffer) for(int i = 0; i < m_frame->nb_samples; i++) { std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos + ((m_codecCtx->channels * i) + channel) * single_size, - m_frame->data[channel] + i * single_size, single_size); + m_frame->data[channel] + i * single_size, single_size); } } } @@ -109,7 +128,7 @@ int FFMPEGReader::decode(AVPacket& packet, Buffer& buffer) for(int i = 0; i < m_frame->nb_samples; i++) { std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos + ((m_codecCtx->channels * i) + channel) * single_size, - m_frame->data[channel] + i * single_size, single_size); + m_frame->data[channel] + i * single_size, single_size); } } } @@ -123,13 +142,10 @@ int FFMPEGReader::decode(AVPacket& packet, Buffer& buffer) return buf_pos; } -void FFMPEGReader::init() +void FFMPEGReader::init(int stream) { m_position = 0; - m_start_offset = 0.0f; m_pkgbuf_left = 0; - m_st_time = 0; - m_duration = 0; if(avformat_find_stream_info(m_formatCtx, nullptr) < 0) AUD_THROW(FileException, "File couldn't be read, ffmpeg couldn't find the stream info."); @@ -137,43 +153,22 @@ void FFMPEGReader::init() // find audio stream and codec m_stream = -1; - double dur_sec = 0; - for(unsigned int i = 0; i < m_formatCtx->nb_streams; i++) { #ifdef FFMPEG_OLD_CODE - if(m_formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) + if((m_formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) #else - if(m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + if((m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) #endif + && (m_stream < 0)) { - AVStream *audio_stream = m_formatCtx->streams[i]; - double audio_timebase = av_q2d(audio_stream->time_base); - - if (audio_stream->start_time != AV_NOPTS_VALUE) + if(stream == 0) { - m_st_time = audio_stream->start_time; - } - - int64_t ctx_start_time = 0; - if (m_formatCtx->start_time != AV_NOPTS_VALUE) { - ctx_start_time = m_formatCtx->start_time; - } - - m_start_offset = m_st_time * audio_timebase - (double)ctx_start_time / AV_TIME_BASE; - - if(audio_stream->duration != AV_NOPTS_VALUE) - { - dur_sec = audio_stream->duration * audio_timebase; + m_stream=i; + break; } else - { - /* If the audio starts after the stream start time, subract this from the total duration. */ - dur_sec = (double)m_formatCtx->duration / AV_TIME_BASE - m_start_offset; - } - - m_stream=i; - break; + stream--; } } @@ -242,10 +237,9 @@ void FFMPEGReader::init() } m_specs.rate = (SampleRate) m_codecCtx->sample_rate; - m_duration = lround(dur_sec * m_codecCtx->sample_rate); } -FFMPEGReader::FFMPEGReader(std::string filename) : +FFMPEGReader::FFMPEGReader(std::string filename, int stream) : m_pkgbuf(), m_formatCtx(nullptr), m_codecCtx(nullptr), @@ -259,7 +253,7 @@ FFMPEGReader::FFMPEGReader(std::string filename) : try { - init(); + init(stream); } catch(Exception&) { @@ -268,7 +262,7 @@ FFMPEGReader::FFMPEGReader(std::string filename) : } } -FFMPEGReader::FFMPEGReader(std::shared_ptr<Buffer> buffer) : +FFMPEGReader::FFMPEGReader(std::shared_ptr<Buffer> buffer, int stream) : m_pkgbuf(), m_codecCtx(nullptr), m_frame(nullptr), @@ -295,7 +289,7 @@ FFMPEGReader::FFMPEGReader(std::shared_ptr<Buffer> buffer) : try { - init(); + init(stream); } catch(Exception&) { @@ -318,6 +312,51 @@ FFMPEGReader::~FFMPEGReader() avformat_close_input(&m_formatCtx); } +std::vector<StreamInfo> FFMPEGReader::queryStreams() +{ + std::vector<StreamInfo> result; + + for(unsigned int i = 0; i < m_formatCtx->nb_streams; i++) + { +#ifdef FFMPEG_OLD_CODE + if(m_formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) +#else + if(m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) +#endif + { + StreamInfo info; + + double time_base = av_q2d(m_formatCtx->streams[i]->time_base); + + if(m_formatCtx->streams[i]->start_time != AV_NOPTS_VALUE) + info.start = m_formatCtx->streams[i]->start_time * time_base; + else + info.start = 0; + + if(m_formatCtx->streams[i]->duration != AV_NOPTS_VALUE) + info.duration = m_formatCtx->streams[i]->duration * time_base; + else if(m_formatCtx->duration != AV_NOPTS_VALUE) + info.duration = double(m_formatCtx->duration) / AV_TIME_BASE - info.start; + else + info.duration = 0; + +#ifdef FFMPEG_OLD_CODE + info.specs.channels = Channels(m_formatCtx->streams[i]->codec->channels); + info.specs.rate = m_formatCtx->streams[i]->codec->sample_rate; + info.specs.format = convertSampleFormat(m_formatCtx->streams[i]->codec->sample_fmt); +#else + info.specs.channels = Channels(m_formatCtx->streams[i]->codecpar->channels); + info.specs.rate = m_formatCtx->streams[i]->codecpar->sample_rate; + info.specs.format = convertSampleFormat(AVSampleFormat(m_formatCtx->streams[i]->codecpar->format)); +#endif + + result.emplace_back(info); + } + } + + return result; +} + int FFMPEGReader::read_packet(void* opaque, uint8_t* buf, int buf_size) { FFMPEGReader* reader = reinterpret_cast<FFMPEGReader*>(opaque); @@ -368,18 +407,16 @@ void FFMPEGReader::seek(int position) { if(position >= 0) { - double pts_time_base = - av_q2d(m_formatCtx->streams[m_stream]->time_base); + double pts_time_base = av_q2d(m_formatCtx->streams[m_stream]->time_base); - uint64_t seek_pts = (((uint64_t)position) / ((uint64_t)m_specs.rate)) / pts_time_base; + uint64_t st_time = m_formatCtx->streams[m_stream]->start_time; + uint64_t seek_pos = (uint64_t)(position / (pts_time_base * m_specs.rate)); - if(m_st_time != AV_NOPTS_VALUE) { - seek_pts += m_st_time; - } + if(st_time != AV_NOPTS_VALUE) + seek_pos += st_time; // a value < 0 tells us that seeking failed - if(av_seek_frame(m_formatCtx, m_stream, seek_pts, - AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY) >= 0) + if(av_seek_frame(m_formatCtx, m_stream, seek_pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY) >= 0) { avcodec_flush_buffers(m_codecCtx); m_position = position; @@ -400,7 +437,7 @@ void FFMPEGReader::seek(int position) if(packet.pts != AV_NOPTS_VALUE) { // calculate real position, and read to frame! - m_position = (packet.pts - m_st_time) * pts_time_base * m_specs.rate; + m_position = (packet.pts - (st_time != AV_NOPTS_VALUE ? st_time : 0)) * pts_time_base * m_specs.rate; if(m_position < position) { @@ -430,8 +467,25 @@ void FFMPEGReader::seek(int position) int FFMPEGReader::getLength() const { + auto stream = m_formatCtx->streams[m_stream]; + + double time_base = av_q2d(stream->time_base); + double duration; + + if(stream->duration != AV_NOPTS_VALUE) + duration = stream->duration * time_base; + else if(m_formatCtx->duration != AV_NOPTS_VALUE) + { + duration = float(m_formatCtx->duration) / AV_TIME_BASE; + + if(stream->start_time != AV_NOPTS_VALUE) + duration -= stream->start_time * time_base; + } + else + duration = -1; + // return approximated remaning size - return m_duration - m_position; + return (int)(duration * m_codecCtx->sample_rate) - m_position; } int FFMPEGReader::getPosition() const @@ -439,11 +493,6 @@ int FFMPEGReader::getPosition() const return m_position; } -double FFMPEGReader::getStartOffset() const -{ - return m_start_offset; -} - Specs FFMPEGReader::getSpecs() const { return m_specs.specs; @@ -480,13 +529,11 @@ void FFMPEGReader::read(int& length, bool& eos, sample_t* buffer) // decode the package pkgbuf_pos = decode(packet, m_pkgbuf); - if (packet.pts >= m_st_time) { - // copy to output buffer - data_size = std::min(pkgbuf_pos, left * sample_size); - m_convert((data_t*) buf, (data_t*) m_pkgbuf.getBuffer(), data_size / AUD_FORMAT_SIZE(m_specs.format)); - buf += data_size / AUD_FORMAT_SIZE(m_specs.format); - left -= data_size / sample_size; - } + // copy to output buffer + data_size = std::min(pkgbuf_pos, left * sample_size); + m_convert((data_t*) buf, (data_t*) m_pkgbuf.getBuffer(), data_size / AUD_FORMAT_SIZE(m_specs.format)); + buf += data_size / AUD_FORMAT_SIZE(m_specs.format); + left -= data_size / sample_size; } av_packet_unref(&packet); } diff --git a/extern/audaspace/plugins/ffmpeg/FFMPEGReader.h b/extern/audaspace/plugins/ffmpeg/FFMPEGReader.h index d613457c220..70f13911eca 100644 --- a/extern/audaspace/plugins/ffmpeg/FFMPEGReader.h +++ b/extern/audaspace/plugins/ffmpeg/FFMPEGReader.h @@ -29,9 +29,11 @@ #include "respec/ConverterFunctions.h" #include "IReader.h" #include "util/Buffer.h" +#include "file/FileInfo.h" #include <string> #include <memory> +#include <vector> struct AVCodecContext; extern "C" { @@ -55,22 +57,6 @@ private: int m_position; /** - * The start offset in seconds relative to the media container start time. - * IE how much the sound should be delayed to be kept in sync with the rest of the containter streams. - */ - double m_start_offset; - - /** - * The start time pts of the stream. All packets before this timestamp shouldn't be played back (only decoded). - */ - int64_t m_st_time; - - /** - * The duration of the audio stream in samples. - */ - int64_t m_duration; - - /** * The specification of the audio data. */ DeviceSpecs m_specs; @@ -136,6 +122,13 @@ private: bool m_tointerleave; /** + * Converts an ffmpeg sample format to an audaspace one. + * \param format The AVSampleFormat sample format. + * \return The sample format as SampleFormat. + */ + AUD_LOCAL static SampleFormat convertSampleFormat(AVSampleFormat format); + + /** * Decodes a packet into the given buffer. * \param packet The AVPacket to decode. * \param buffer The target buffer. @@ -145,8 +138,9 @@ private: /** * Initializes the object. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. */ - AUD_LOCAL void init(); + AUD_LOCAL void init(int stream); // delete copy constructor and operator= FFMPEGReader(const FFMPEGReader&) = delete; @@ -156,18 +150,20 @@ public: /** * Creates a new reader. * \param filename The path to the file to be read. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. * \exception Exception Thrown if the file specified does not exist or * cannot be read with ffmpeg. */ - FFMPEGReader(std::string filename); + FFMPEGReader(std::string filename, int stream = 0); /** * Creates a new reader. * \param buffer The buffer to read from. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. * \exception Exception Thrown if the buffer specified cannot be read * with ffmpeg. */ - FFMPEGReader(std::shared_ptr<Buffer> buffer); + FFMPEGReader(std::shared_ptr<Buffer> buffer, int stream = 0); /** * Destroys the reader and closes the file. @@ -175,6 +171,13 @@ public: virtual ~FFMPEGReader(); /** + * Queries the streams of a sound file. + * \return A vector with as many streams as there are in the file. + * \exception Exception Thrown if the file specified cannot be read. + */ + virtual std::vector<StreamInfo> queryStreams(); + + /** * Reads data to a memory buffer. * This function is used for avio only. * @param opaque The FFMPEGReader. @@ -198,7 +201,6 @@ public: virtual void seek(int position); virtual int getLength() const; virtual int getPosition() const; - virtual double getStartOffset() const; virtual Specs getSpecs() const; virtual void read(int& length, bool& eos, sample_t* buffer); }; diff --git a/extern/audaspace/plugins/libsndfile/SndFile.cpp b/extern/audaspace/plugins/libsndfile/SndFile.cpp index ba4ff24ad68..39335de9a1a 100644 --- a/extern/audaspace/plugins/libsndfile/SndFile.cpp +++ b/extern/audaspace/plugins/libsndfile/SndFile.cpp @@ -32,16 +32,26 @@ void SndFile::registerPlugin() FileManager::registerOutput(plugin); } -std::shared_ptr<IReader> SndFile::createReader(std::string filename) +std::shared_ptr<IReader> SndFile::createReader(std::string filename, int stream) { return std::shared_ptr<IReader>(new SndFileReader(filename)); } -std::shared_ptr<IReader> SndFile::createReader(std::shared_ptr<Buffer> buffer) +std::shared_ptr<IReader> SndFile::createReader(std::shared_ptr<Buffer> buffer, int stream) { return std::shared_ptr<IReader>(new SndFileReader(buffer)); } +std::vector<StreamInfo> SndFile::queryStreams(std::string filename) +{ + return SndFileReader(filename).queryStreams(); +} + +std::vector<StreamInfo> SndFile::queryStreams(std::shared_ptr<Buffer> buffer) +{ + return SndFileReader(buffer).queryStreams(); +} + std::shared_ptr<IWriter> SndFile::createWriter(std::string filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate) { return std::shared_ptr<IWriter>(new SndFileWriter(filename, specs, format, codec, bitrate)); diff --git a/extern/audaspace/plugins/libsndfile/SndFile.h b/extern/audaspace/plugins/libsndfile/SndFile.h index 61afed1d564..10a7391180f 100644 --- a/extern/audaspace/plugins/libsndfile/SndFile.h +++ b/extern/audaspace/plugins/libsndfile/SndFile.h @@ -52,8 +52,10 @@ public: */ static void registerPlugin(); - virtual std::shared_ptr<IReader> createReader(std::string filename); - virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer); + virtual std::shared_ptr<IReader> createReader(std::string filename, int stream = 0); + virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer, int stream = 0); + virtual std::vector<StreamInfo> queryStreams(std::string filename); + virtual std::vector<StreamInfo> queryStreams(std::shared_ptr<Buffer> buffer); virtual std::shared_ptr<IWriter> createWriter(std::string filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate); }; diff --git a/extern/audaspace/plugins/libsndfile/SndFileReader.cpp b/extern/audaspace/plugins/libsndfile/SndFileReader.cpp index d2d89814c07..21c733d8117 100644 --- a/extern/audaspace/plugins/libsndfile/SndFileReader.cpp +++ b/extern/audaspace/plugins/libsndfile/SndFileReader.cpp @@ -118,6 +118,21 @@ SndFileReader::~SndFileReader() sf_close(m_sndfile); } +std::vector<StreamInfo> SndFileReader::queryStreams() +{ + std::vector<StreamInfo> result; + + StreamInfo info; + info.start = 0; + info.duration = double(getLength()) / m_specs.rate; + info.specs.specs = m_specs; + info.specs.format = FORMAT_FLOAT32; + + result.emplace_back(info); + + return result; +} + bool SndFileReader::isSeekable() const { return m_seekable; diff --git a/extern/audaspace/plugins/libsndfile/SndFileReader.h b/extern/audaspace/plugins/libsndfile/SndFileReader.h index 081c29c686c..b4158d9091a 100644 --- a/extern/audaspace/plugins/libsndfile/SndFileReader.h +++ b/extern/audaspace/plugins/libsndfile/SndFileReader.h @@ -28,9 +28,12 @@ * The SndFileReader class. */ +#include "file/FileInfo.h" + #include <string> #include <sndfile.h> #include <memory> +#include <vector> AUD_NAMESPACE_BEGIN @@ -96,6 +99,7 @@ public: /** * Creates a new reader. * \param filename The path to the file to be read. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. * \exception Exception Thrown if the file specified does not exist or * cannot be read with libsndfile. */ @@ -104,6 +108,7 @@ public: /** * Creates a new reader. * \param buffer The buffer to read from. + * \param stream The index of the audio stream within the file if it contains multiple audio streams. * \exception Exception Thrown if the buffer specified cannot be read * with libsndfile. */ @@ -114,6 +119,13 @@ public: */ virtual ~SndFileReader(); + /** + * Queries the streams of a sound file. + * \return A vector with as many streams as there are in the file. + * \exception Exception Thrown if the file specified cannot be read. + */ + virtual std::vector<StreamInfo> queryStreams(); + virtual bool isSeekable() const; virtual void seek(int position); virtual int getLength() const; diff --git a/extern/audaspace/src/file/File.cpp b/extern/audaspace/src/file/File.cpp index 0cdecb03657..5d4bae482d6 100644 --- a/extern/audaspace/src/file/File.cpp +++ b/extern/audaspace/src/file/File.cpp @@ -23,23 +23,31 @@ AUD_NAMESPACE_BEGIN -File::File(std::string filename) : - m_filename(filename) +File::File(std::string filename, int stream) : + m_filename(filename), m_stream(stream) { } -File::File(const data_t* buffer, int size) : - m_buffer(new Buffer(size)) +File::File(const data_t* buffer, int size, int stream) : + m_buffer(new Buffer(size)), m_stream(stream) { std::memcpy(m_buffer->getBuffer(), buffer, size); } +std::vector<StreamInfo> File::queryStreams() +{ + if(m_buffer.get()) + return FileManager::queryStreams(m_buffer); + else + return FileManager::queryStreams(m_filename); +} + std::shared_ptr<IReader> File::createReader() { if(m_buffer.get()) - return FileManager::createReader(m_buffer); + return FileManager::createReader(m_buffer, m_stream); else - return FileManager::createReader(m_filename); + return FileManager::createReader(m_filename, m_stream); } AUD_NAMESPACE_END diff --git a/extern/audaspace/src/file/FileManager.cpp b/extern/audaspace/src/file/FileManager.cpp index f8ef8deb409..7cbc0318f8c 100644 --- a/extern/audaspace/src/file/FileManager.cpp +++ b/extern/audaspace/src/file/FileManager.cpp @@ -43,13 +43,13 @@ void FileManager::registerOutput(std::shared_ptr<aud::IFileOutput> output) outputs().push_back(output); } -std::shared_ptr<IReader> FileManager::createReader(std::string filename) +std::shared_ptr<IReader> FileManager::createReader(std::string filename, int stream) { for(std::shared_ptr<IFileInput> input : inputs()) { try { - return input->createReader(filename); + return input->createReader(filename, stream); } catch(Exception&) {} } @@ -57,13 +57,41 @@ std::shared_ptr<IReader> FileManager::createReader(std::string filename) AUD_THROW(FileException, "The file couldn't be read with any installed file reader."); } -std::shared_ptr<IReader> FileManager::createReader(std::shared_ptr<Buffer> buffer) +std::shared_ptr<IReader> FileManager::createReader(std::shared_ptr<Buffer> buffer, int stream) { for(std::shared_ptr<IFileInput> input : inputs()) { try { - return input->createReader(buffer); + return input->createReader(buffer, stream); + } + catch(Exception&) {} + } + + AUD_THROW(FileException, "The file couldn't be read with any installed file reader."); +} + +std::vector<StreamInfo> FileManager::queryStreams(std::string filename) +{ + for(std::shared_ptr<IFileInput> input : inputs()) + { + try + { + return input->queryStreams(filename); + } + catch(Exception&) {} + } + + AUD_THROW(FileException, "The file couldn't be read with any installed file reader."); +} + +std::vector<StreamInfo> FileManager::queryStreams(std::shared_ptr<Buffer> buffer) +{ + for(std::shared_ptr<IFileInput> input : inputs()) + { + try + { + return input->queryStreams(buffer); } catch(Exception&) {} } diff --git a/extern/audaspace/src/fx/VolumeReader.cpp b/extern/audaspace/src/fx/VolumeReader.cpp index 627acbac9ef..ac1d4882a87 100644 --- a/extern/audaspace/src/fx/VolumeReader.cpp +++ b/extern/audaspace/src/fx/VolumeReader.cpp @@ -57,4 +57,4 @@ void VolumeReader::read(int& length, bool& eos, sample_t* buffer) buffer[i] = buffer[i] * m_volumeStorage->getVolume(); } -AUD_NAMESPACE_END +AUD_NAMESPACE_END
\ No newline at end of file diff --git a/extern/mantaflow/UPDATE.sh b/extern/mantaflow/UPDATE.sh index aed4e2a9b71..1158ff13455 100644 --- a/extern/mantaflow/UPDATE.sh +++ b/extern/mantaflow/UPDATE.sh @@ -8,7 +8,7 @@ # YOUR INSTALLATION PATHS GO HERE: MANTA_INSTALLATION=/Users/sebbas/Developer/Mantaflow/mantaflowDevelop -BLENDER_INSTALLATION=/Users/sebbas/Developer/Blender/fluid-mantaflow +BLENDER_INSTALLATION=/Users/sebbas/Developer/Blender # Try to check out Mantaflow repository before building? CLEAN_REPOSITORY=0 diff --git a/extern/mantaflow/helper/pwrapper/pconvert.cpp b/extern/mantaflow/helper/pwrapper/pconvert.cpp index 7c66cdc7e72..5a7a32c5a73 100644 --- a/extern/mantaflow/helper/pwrapper/pconvert.cpp +++ b/extern/mantaflow/helper/pwrapper/pconvert.cpp @@ -28,11 +28,13 @@ extern PyTypeObject PbVec3Type; extern PyTypeObject PbVec4Type; struct PbVec3 { - PyObject_HEAD float data[3]; + PyObject_HEAD + float data[3]; }; struct PbVec4 { - PyObject_HEAD float data[4]; + PyObject_HEAD + float data[4]; }; PyObject *getPyNone() diff --git a/extern/mantaflow/helper/pwrapper/pvec3.cpp b/extern/mantaflow/helper/pwrapper/pvec3.cpp index 1dca44d5e5c..4d07a201cfe 100644 --- a/extern/mantaflow/helper/pwrapper/pvec3.cpp +++ b/extern/mantaflow/helper/pwrapper/pvec3.cpp @@ -25,7 +25,8 @@ namespace Manta { extern PyTypeObject PbVec3Type; struct PbVec3 { - PyObject_HEAD float data[3]; + PyObject_HEAD + float data[3]; }; static void PbVec3Dealloc(PbVec3 *self) @@ -293,7 +294,8 @@ inline PyObject *castPy(PyTypeObject *p) extern PyTypeObject PbVec4Type; struct PbVec4 { - PyObject_HEAD float data[4]; + PyObject_HEAD + float data[4]; }; static PyMethodDef PbVec4Methods[] = { diff --git a/extern/mantaflow/helper/pwrapper/registry.cpp b/extern/mantaflow/helper/pwrapper/registry.cpp index f88c2aa708e..5196c0409f8 100644 --- a/extern/mantaflow/helper/pwrapper/registry.cpp +++ b/extern/mantaflow/helper/pwrapper/registry.cpp @@ -76,7 +76,8 @@ struct ClassData { }; struct PbObject { - PyObject_HEAD Manta::PbClass *instance; + PyObject_HEAD + Manta::PbClass *instance; ClassData *classdef; }; diff --git a/extern/mantaflow/preprocessed/fastmarch.cpp b/extern/mantaflow/preprocessed/fastmarch.cpp index 956725e523c..31e43483b49 100644 --- a/extern/mantaflow/preprocessed/fastmarch.cpp +++ b/extern/mantaflow/preprocessed/fastmarch.cpp @@ -874,6 +874,136 @@ static const Vec3i nb[6] = {Vec3i(1, 0, 0), Vec3i(0, 0, 1), Vec3i(0, 0, -1)}; +struct knMarkSkipCells : public KernelBase { + knMarkSkipCells(Grid<Real> &phi, Grid<int> &tmp, bool inside) + : KernelBase(&phi, 1), phi(phi), tmp(tmp), inside(inside) + { + runMessage(); + run(); + } + inline void op(int i, int j, int k, Grid<Real> &phi, Grid<int> &tmp, bool inside) const + { + if (!inside && phi(i, j, k) < 0.) { + tmp(i, j, k) = 1; + } + if (inside && phi(i, j, k) > 0.) { + tmp(i, j, k) = 1; + } + } + inline Grid<Real> &getArg0() + { + return phi; + } + typedef Grid<Real> type0; + inline Grid<int> &getArg1() + { + return tmp; + } + typedef Grid<int> type1; + inline bool &getArg2() + { + return inside; + } + typedef bool type2; + void runMessage() + { + debMsg("Executing kernel knMarkSkipCells ", 3); + debMsg("Kernel range" + << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ", + 4); + }; + void operator()(const tbb::blocked_range<IndexInt> &__r) const + { + const int _maxX = maxX; + const int _maxY = maxY; + if (maxZ > 1) { + for (int k = __r.begin(); k != (int)__r.end(); k++) + for (int j = 1; j < _maxY; j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, phi, tmp, inside); + } + else { + const int k = 0; + for (int j = __r.begin(); j != (int)__r.end(); j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, phi, tmp, inside); + } + } + void run() + { + if (maxZ > 1) + tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this); + else + tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this); + } + Grid<Real> φ + Grid<int> &tmp; + bool inside; +}; + +struct knSetFirstLayer : public KernelBase { + knSetFirstLayer(Grid<int> &tmp, int dim) : KernelBase(&tmp, 1), tmp(tmp), dim(dim) + { + runMessage(); + run(); + } + inline void op(int i, int j, int k, Grid<int> &tmp, int dim) const + { + Vec3i p(i, j, k); + if (tmp(p)) + return; + for (int n = 0; n < 2 * dim; ++n) { + if (tmp(p + nb[n]) == 1) { + tmp(i, j, k) = 2; + break; + } + } + } + inline Grid<int> &getArg0() + { + return tmp; + } + typedef Grid<int> type0; + inline int &getArg1() + { + return dim; + } + typedef int type1; + void runMessage() + { + debMsg("Executing kernel knSetFirstLayer ", 3); + debMsg("Kernel range" + << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ", + 4); + }; + void operator()(const tbb::blocked_range<IndexInt> &__r) const + { + const int _maxX = maxX; + const int _maxY = maxY; + if (maxZ > 1) { + for (int k = __r.begin(); k != (int)__r.end(); k++) + for (int j = 1; j < _maxY; j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, tmp, dim); + } + else { + const int k = 0; + for (int j = __r.begin(); j != (int)__r.end(); j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, tmp, dim); + } + } + void run() + { + if (maxZ > 1) + tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this); + else + tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this); + } + Grid<int> &tmp; + int dim; +}; + template<class S> struct knExtrapolateLsSimple : public KernelBase { knExtrapolateLsSimple(Grid<S> &val, int distance, Grid<int> &tmp, const int d, S direction) : KernelBase(&val, 1), val(val), distance(distance), tmp(tmp), d(d), direction(direction) @@ -1043,39 +1173,12 @@ void extrapolateLsSimple(Grid<Real> &phi, int distance = 4, bool inside = false) tmp.clear(); const int dim = (phi.is3D() ? 3 : 2); - // by default, march outside - Real direction = 1.; - if (!inside) { - // mark all inside - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) < 0.) { - tmp(i, j, k) = 1; - } - } - } - else { - direction = -1.; - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) > 0.) { - tmp(i, j, k) = 1; - } - } - } + // by default, march outside (ie mark all inside to be skipped) + Real direction = (inside) ? -1. : 1.; + knMarkSkipCells(phi, tmp, inside); + // + first layer around - FOR_IJK_BND(phi, 1) - { - Vec3i p(i, j, k); - if (tmp(p)) - continue; - for (int n = 0; n < 2 * dim; ++n) { - if (tmp(p + nb[n]) == 1) { - tmp(i, j, k) = 2; - n = 2 * dim; - } - } - } + knSetFirstLayer(tmp, dim); // extrapolate for distance for (int d = 2; d < 1 + distance; ++d) { @@ -1126,37 +1229,12 @@ void extrapolateVec3Simple(Grid<Vec3> &vel, Grid<Real> &phi, int distance = 4, b tmp.clear(); const int dim = (vel.is3D() ? 3 : 2); - // mark initial cells, by default, march outside - if (!inside) { - // mark all inside - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) < 0.) { - tmp(i, j, k) = 1; - } - } - } - else { - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) > 0.) { - tmp(i, j, k) = 1; - } - } - } + // mark initial cells, by default, march outside (ie mark all inside to be skipped) + Real direction = (inside) ? -1. : 1.; + knMarkSkipCells(phi, tmp, inside); + // + first layer next to initial cells - FOR_IJK_BND(vel, 1) - { - Vec3i p(i, j, k); - if (tmp(p)) - continue; - for (int n = 0; n < 2 * dim; ++n) { - if (tmp(p + nb[n]) == 1) { - tmp(i, j, k) = 2; - n = 2 * dim; - } - } - } + knSetFirstLayer(tmp, dim); for (int d = 2; d < 1 + distance; ++d) { knExtrapolateLsSimple<Vec3>(vel, distance, tmp, d, Vec3(0.)); diff --git a/extern/mantaflow/preprocessed/gitinfo.h b/extern/mantaflow/preprocessed/gitinfo.h index 6bc92278a33..6d367b764af 100644 --- a/extern/mantaflow/preprocessed/gitinfo.h +++ b/extern/mantaflow/preprocessed/gitinfo.h @@ -1,3 +1,3 @@ -#define MANTA_GIT_VERSION "commit 8fbebe02459b7f72575872c20961f7cb757db408" +#define MANTA_GIT_VERSION "commit d5d9a6c28daa8f21426d7a285f48639c0d8fd13f" diff --git a/intern/cycles/blender/blender_curves.cpp b/intern/cycles/blender/blender_curves.cpp index 85d886fd850..6fe5ea41fff 100644 --- a/intern/cycles/blender/blender_curves.cpp +++ b/intern/cycles/blender/blender_curves.cpp @@ -526,8 +526,13 @@ bool BlenderSync::object_has_particle_hair(BL::Object b_ob) /* Old particle hair. */ void BlenderSync::sync_particle_hair( - Hair *hair, BL::Mesh &b_mesh, BL::Object &b_ob, bool motion, int motion_step) + Hair *hair, BL::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, int motion_step) { + if (!b_ob_info.is_real_object_data()) { + return; + } + BL::Object b_ob = b_ob_info.real_object; + /* obtain general settings */ if (b_ob.mode() == b_ob.mode_PARTICLE_EDIT || b_ob.mode() == b_ob.mode_EDIT) { return; @@ -788,10 +793,10 @@ static void export_hair_curves_motion(Hair *hair, BL::Hair b_hair, int motion_st } /* Hair object. */ -void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step) +void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step) { /* Convert Blender hair to Cycles curves. */ - BL::Hair b_hair(b_ob.data()); + BL::Hair b_hair(b_ob_info.object_data); if (motion) { export_hair_curves_motion(hair, b_hair, motion_step); } @@ -800,16 +805,16 @@ void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motio } } #else -void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step) +void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step) { (void)hair; - (void)b_ob; + (void)b_ob_info; (void)motion; (void)motion_step; } #endif -void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair) +void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Hair *hair) { /* make a copy of the shaders as the caller in the main thread still need them for syncing the * attributes */ @@ -819,19 +824,19 @@ void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *ha new_hair.set_used_shaders(used_shaders); if (view_layer.use_hair) { - if (b_ob.type() == BL::Object::type_HAIR) { + if (b_ob_info.object_data.is_a(&RNA_Hair)) { /* Hair object. */ - sync_hair(&new_hair, b_ob, false); + sync_hair(&new_hair, b_ob_info, false); } else { /* Particle hair. */ bool need_undeformed = new_hair.need_attribute(scene, ATTR_STD_GENERATED); BL::Mesh b_mesh = object_to_mesh( - b_data, b_ob, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE); + b_data, b_ob_info, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE); if (b_mesh) { - sync_particle_hair(&new_hair, b_mesh, b_ob, false); - free_object_to_mesh(b_data, b_ob, b_mesh); + sync_particle_hair(&new_hair, b_mesh, b_ob_info, false); + free_object_to_mesh(b_data, b_ob_info, b_mesh); } } } @@ -859,7 +864,7 @@ void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *ha } void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph, - BL::Object b_ob, + BObjectInfo &b_ob_info, Hair *hair, int motion_step) { @@ -869,18 +874,19 @@ void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph, } /* Export deformed coordinates. */ - if (ccl::BKE_object_is_deform_modified(b_ob, b_scene, preview)) { - if (b_ob.type() == BL::Object::type_HAIR) { + if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) { + if (b_ob_info.object_data.is_a(&RNA_Hair)) { /* Hair object. */ - sync_hair(hair, b_ob, true, motion_step); + sync_hair(hair, b_ob_info, true, motion_step); return; } else { /* Particle hair. */ - BL::Mesh b_mesh = object_to_mesh(b_data, b_ob, b_depsgraph, false, Mesh::SUBDIVISION_NONE); + BL::Mesh b_mesh = object_to_mesh( + b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE); if (b_mesh) { - sync_particle_hair(hair, b_mesh, b_ob, true, motion_step); - free_object_to_mesh(b_data, b_ob, b_mesh); + sync_particle_hair(hair, b_mesh, b_ob_info, true, motion_step); + free_object_to_mesh(b_data, b_ob_info, b_mesh); return; } } diff --git a/intern/cycles/blender/blender_geometry.cpp b/intern/cycles/blender/blender_geometry.cpp index a009018f357..b1de37dac10 100644 --- a/intern/cycles/blender/blender_geometry.cpp +++ b/intern/cycles/blender/blender_geometry.cpp @@ -29,13 +29,15 @@ CCL_NAMESPACE_BEGIN -static Geometry::Type determine_geom_type(BL::Object &b_ob, bool use_particle_hair) +static Geometry::Type determine_geom_type(BObjectInfo &b_ob_info, bool use_particle_hair) { - if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) { + if (b_ob_info.object_data.is_a(&RNA_Hair) || use_particle_hair) { return Geometry::HAIR; } - if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) { + if (b_ob_info.object_data.is_a(&RNA_Volume) || + (b_ob_info.object_data == b_ob_info.real_object.data() && + object_fluid_gas_domain_find(b_ob_info.real_object))) { return Geometry::VOLUME; } @@ -71,20 +73,17 @@ array<Node *> BlenderSync::find_used_shaders(BL::Object &b_ob) } Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, bool object_updated, bool use_particle_hair, TaskPool *task_pool) { /* Test if we can instance or if the object is modified. */ - BL::ID b_ob_data = b_ob.data(); - BL::ID b_key_id = (BKE_object_is_modified(b_ob)) ? b_ob_instance : b_ob_data; - Geometry::Type geom_type = determine_geom_type(b_ob, use_particle_hair); - GeometryKey key(b_key_id.ptr.data, geom_type); + Geometry::Type geom_type = determine_geom_type(b_ob_info, use_particle_hair); + GeometryKey key(b_ob_info.object_data, geom_type); /* Find shader indices. */ - array<Node *> used_shaders = find_used_shaders(b_ob); + array<Node *> used_shaders = find_used_shaders(b_ob_info.iter_object); /* Ensure we only sync instanced geometry once. */ Geometry *geom = geometry_map.find(key); @@ -111,7 +110,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, } else { /* Test if we need to update existing geometry. */ - sync = geometry_map.update(geom, b_key_id); + sync = geometry_map.update(geom, b_ob_info.object_data); } if (!sync) { @@ -144,7 +143,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, geometry_synced.insert(geom); - geom->name = ustring(b_ob_data.name().c_str()); + geom->name = ustring(b_ob_info.object_data.name().c_str()); /* Store the shaders immediately for the object attribute code. */ geom->set_used_shaders(used_shaders); @@ -153,19 +152,19 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, if (progress.get_cancel()) return; - progress.set_sync_status("Synchronizing object", b_ob.name()); + progress.set_sync_status("Synchronizing object", b_ob_info.real_object.name()); if (geom_type == Geometry::HAIR) { Hair *hair = static_cast<Hair *>(geom); - sync_hair(b_depsgraph, b_ob, hair); + sync_hair(b_depsgraph, b_ob_info, hair); } else if (geom_type == Geometry::VOLUME) { Volume *volume = static_cast<Volume *>(geom); - sync_volume(b_ob, volume); + sync_volume(b_ob_info, volume); } else { Mesh *mesh = static_cast<Mesh *>(geom); - sync_mesh(b_depsgraph, b_ob, mesh); + sync_mesh(b_depsgraph, b_ob_info, mesh); } }; @@ -181,7 +180,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, } void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph, - BL::Object &b_ob, + BObjectInfo &b_ob_info, Object *object, float motion_time, bool use_particle_hair, @@ -190,8 +189,10 @@ void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph, /* Ensure we only sync instanced geometry once. */ Geometry *geom = object->get_geometry(); - if (geometry_motion_synced.find(geom) != geometry_motion_synced.end()) + if (geometry_motion_synced.find(geom) != geometry_motion_synced.end() || + geometry_motion_attribute_synced.find(geom) != geometry_motion_attribute_synced.end()) { return; + } geometry_motion_synced.insert(geom); @@ -210,16 +211,17 @@ void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph, if (progress.get_cancel()) return; - if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) { + if (b_ob_info.object_data.is_a(&RNA_Hair) || use_particle_hair) { Hair *hair = static_cast<Hair *>(geom); - sync_hair_motion(b_depsgraph, b_ob, hair, motion_step); + sync_hair_motion(b_depsgraph, b_ob_info, hair, motion_step); } - else if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) { + else if (b_ob_info.object_data.is_a(&RNA_Volume) || + object_fluid_gas_domain_find(b_ob_info.real_object)) { /* No volume motion blur support yet. */ } else { Mesh *mesh = static_cast<Mesh *>(geom); - sync_mesh_motion(b_depsgraph, b_ob, mesh, motion_step); + sync_mesh_motion(b_depsgraph, b_ob_info, mesh, motion_step); } }; diff --git a/intern/cycles/blender/blender_light.cpp b/intern/cycles/blender/blender_light.cpp index 50cd9e3db5c..542028f4b2f 100644 --- a/intern/cycles/blender/blender_light.cpp +++ b/intern/cycles/blender/blender_light.cpp @@ -27,15 +27,14 @@ CCL_NAMESPACE_BEGIN void BlenderSync::sync_light(BL::Object &b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, int random_id, Transform &tfm, bool *use_portal) { /* test if we need to sync */ - ObjectKey key(b_parent, persistent_id, b_ob_instance, false); - BL::Light b_light(b_ob.data()); + ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, false); + BL::Light b_light(b_ob_info.object_data); Light *light = light_map.find(key); @@ -44,7 +43,7 @@ void BlenderSync::sync_light(BL::Object &b_parent, const bool tfm_updated = (light && light->get_tfm() != tfm); /* Update if either object or light data changed. */ - if (!light_map.add_or_update(&light, b_ob, b_parent, key) && !tfm_updated) { + if (!light_map.add_or_update(&light, b_ob_info.real_object, b_parent, key) && !tfm_updated) { Shader *shader; if (!shader_map.add_or_update(&shader, b_light)) { if (light->get_is_portal()) @@ -139,11 +138,11 @@ void BlenderSync::sync_light(BL::Object &b_parent, light->set_max_bounces(get_int(clight, "max_bounces")); - if (b_ob != b_ob_instance) { + if (b_ob_info.real_object != b_ob_info.iter_object) { light->set_random_id(random_id); } else { - light->set_random_id(hash_uint2(hash_string(b_ob.name().c_str()), 0)); + light->set_random_id(hash_uint2(hash_string(b_ob_info.real_object.name().c_str()), 0)); } if (light->get_light_type() == LIGHT_AREA) @@ -155,7 +154,7 @@ void BlenderSync::sync_light(BL::Object &b_parent, *use_portal = true; /* visibility */ - uint visibility = object_ray_visibility(b_ob); + uint visibility = object_ray_visibility(b_ob_info.real_object); light->set_use_diffuse((visibility & PATH_RAY_DIFFUSE) != 0); light->set_use_glossy((visibility & PATH_RAY_GLOSSY) != 0); light->set_use_transmission((visibility & PATH_RAY_TRANSMIT) != 0); diff --git a/intern/cycles/blender/blender_mesh.cpp b/intern/cycles/blender/blender_mesh.cpp index ebba6981502..7ec430eb7fe 100644 --- a/intern/cycles/blender/blender_mesh.cpp +++ b/intern/cycles/blender/blender_mesh.cpp @@ -347,16 +347,57 @@ static void fill_generic_attribute(BL::Mesh &b_mesh, } } -static void attr_create_generic(Scene *scene, Mesh *mesh, BL::Mesh &b_mesh, bool subdivision) +static void attr_create_motion(Mesh *mesh, BL::Attribute &b_attribute, const float motion_scale) +{ + if (!(b_attribute.domain() == BL::Attribute::domain_POINT) && + (b_attribute.data_type() == BL::Attribute::data_type_FLOAT_VECTOR)) { + return; + } + + BL::FloatVectorAttribute b_vector_attribute(b_attribute); + const int numverts = mesh->get_verts().size(); + + /* Find or add attribute */ + float3 *P = &mesh->get_verts()[0]; + Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); + + if (!attr_mP) { + attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); + } + + /* Only export previous and next frame, we don't have any in between data. */ + float motion_times[2] = {-1.0f, 1.0f}; + for (int step = 0; step < 2; step++) { + const float relative_time = motion_times[step] * 0.5f * motion_scale; + float3 *mP = attr_mP->data_float3() + step * numverts; + + for (int i = 0; i < numverts; i++) { + mP[i] = P[i] + get_float3(b_vector_attribute.data[i].vector()) * relative_time; + } + } +} + +static void attr_create_generic(Scene *scene, + Mesh *mesh, + BL::Mesh &b_mesh, + const bool subdivision, + const bool need_motion, + const float motion_scale) { if (subdivision) { /* TODO: Handle subdivision correctly. */ return; } AttributeSet &attributes = mesh->attributes; + static const ustring u_velocity("velocity"); for (BL::Attribute &b_attribute : b_mesh.attributes) { const ustring name{b_attribute.name().c_str()}; + + if (need_motion && name == u_velocity) { + attr_create_motion(mesh, b_attribute, motion_scale); + } + if (!mesh->need_attribute(scene, name)) { continue; } @@ -859,8 +900,10 @@ static void create_mesh(Scene *scene, Mesh *mesh, BL::Mesh &b_mesh, const array<Node *> &used_shaders, - bool subdivision = false, - bool subdivide_uvs = true) + const bool need_motion, + const float motion_scale, + const bool subdivision = false, + const bool subdivide_uvs = true) { /* count vertices and faces */ int numverts = b_mesh.vertices.length(); @@ -974,7 +1017,7 @@ static void create_mesh(Scene *scene, attr_create_vertex_color(scene, mesh, b_mesh, subdivision); attr_create_sculpt_vertex_color(scene, mesh, b_mesh, subdivision); attr_create_random_per_island(scene, mesh, b_mesh, subdivision); - attr_create_generic(scene, mesh, b_mesh, subdivision); + attr_create_generic(scene, mesh, b_mesh, subdivision, need_motion, motion_scale); if (subdivision) { attr_create_subd_uv_map(scene, mesh, b_mesh, subdivide_uvs); @@ -999,16 +1042,20 @@ static void create_mesh(Scene *scene, static void create_subd_mesh(Scene *scene, Mesh *mesh, - BL::Object &b_ob, + BObjectInfo &b_ob_info, BL::Mesh &b_mesh, const array<Node *> &used_shaders, + const bool need_motion, + const float motion_scale, float dicing_rate, int max_subdivisions) { + BL::Object b_ob = b_ob_info.real_object; + BL::SubsurfModifier subsurf_mod(b_ob.modifiers[b_ob.modifiers.length() - 1]); bool subdivide_uvs = subsurf_mod.uv_smooth() != BL::SubsurfModifier::uv_smooth_NONE; - create_mesh(scene, mesh, b_mesh, used_shaders, true, subdivide_uvs); + create_mesh(scene, mesh, b_mesh, used_shaders, need_motion, motion_scale, true, subdivide_uvs); /* export creases */ size_t num_creases = 0; @@ -1043,7 +1090,7 @@ static void create_subd_mesh(Scene *scene, * * NOTE: This code is run prior to object motion blur initialization. so can not access properties * set by `sync_object_motion_init()`. */ -static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene) +static bool mesh_need_motion_attribute(BObjectInfo &b_ob_info, Scene *scene) { const Scene::MotionType need_motion = scene->need_motion(); if (need_motion == Scene::MOTION_NONE) { @@ -1060,7 +1107,7 @@ static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene) * - Motion attribute expects non-zero time steps. * * Avoid adding motion attributes if the motion blur will enforce 0 motion steps. */ - PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles"); + PointerRNA cobject = RNA_pointer_get(&b_ob_info.real_object.ptr, "cycles"); const bool use_motion = get_boolean(cobject, "use_motion_blur"); if (!use_motion) { return false; @@ -1072,92 +1119,7 @@ static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene) return true; } -static void sync_mesh_cached_velocities(BL::Object &b_ob, Scene *scene, Mesh *mesh) -{ - if (!mesh_need_motion_attribute(b_ob, scene)) { - return; - } - - BL::MeshSequenceCacheModifier b_mesh_cache = object_mesh_cache_find(b_ob, true, nullptr); - - if (!b_mesh_cache) { - return; - } - - if (!MeshSequenceCacheModifier_read_velocity_get(&b_mesh_cache.ptr)) { - return; - } - - const size_t numverts = mesh->get_verts().size(); - - if (b_mesh_cache.vertex_velocities.length() != numverts) { - return; - } - - /* Find or add attribute */ - float3 *P = &mesh->get_verts()[0]; - Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); - - if (!attr_mP) { - attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); - } - - /* Only export previous and next frame, we don't have any in between data. */ - float motion_times[2] = {-1.0f, 1.0f}; - for (int step = 0; step < 2; step++) { - const float relative_time = motion_times[step] * scene->motion_shutter_time() * 0.5f; - float3 *mP = attr_mP->data_float3() + step * numverts; - - BL::MeshSequenceCacheModifier::vertex_velocities_iterator vvi; - int i = 0; - - for (b_mesh_cache.vertex_velocities.begin(vvi); vvi != b_mesh_cache.vertex_velocities.end(); - ++vvi, ++i) { - mP[i] = P[i] + get_float3(vvi->velocity()) * relative_time; - } - } -} - -static void sync_mesh_fluid_motion(BL::Object &b_ob, Scene *scene, Mesh *mesh) -{ - if (!mesh_need_motion_attribute(b_ob, scene)) { - return; - } - - BL::FluidDomainSettings b_fluid_domain = object_fluid_liquid_domain_find(b_ob); - - if (!b_fluid_domain) - return; - - /* If the mesh has modifiers following the fluid domain we can't export motion. */ - if (b_fluid_domain.mesh_vertices.length() != mesh->get_verts().size()) - return; - - /* Find or add attribute */ - float3 *P = &mesh->get_verts()[0]; - Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); - - if (!attr_mP) { - attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); - } - - /* Only export previous and next frame, we don't have any in between data. */ - float motion_times[2] = {-1.0f, 1.0f}; - for (int step = 0; step < 2; step++) { - float relative_time = motion_times[step] * scene->motion_shutter_time() * 0.5f; - float3 *mP = attr_mP->data_float3() + step * mesh->get_verts().size(); - - BL::FluidDomainSettings::mesh_vertices_iterator svi; - int i = 0; - - for (b_fluid_domain.mesh_vertices.begin(svi); svi != b_fluid_domain.mesh_vertices.end(); - ++svi, ++i) { - mP[i] = P[i] + get_float3(svi->velocity()) * relative_time; - } - } -} - -void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh) +void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Mesh *mesh) { /* make a copy of the shaders as the caller in the main thread still need them for syncing the * attributes */ @@ -1170,37 +1132,47 @@ void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *me /* Adaptive subdivision setup. Not for baking since that requires * exact mapping to the Blender mesh. */ if (!scene->bake_manager->get_baking()) { - new_mesh.set_subdivision_type(object_subdivision_type(b_ob, preview, experimental)); + new_mesh.set_subdivision_type( + object_subdivision_type(b_ob_info.real_object, preview, experimental)); } /* For some reason, meshes do not need this... */ bool need_undeformed = new_mesh.need_attribute(scene, ATTR_STD_GENERATED); BL::Mesh b_mesh = object_to_mesh( - b_data, b_ob, b_depsgraph, need_undeformed, new_mesh.get_subdivision_type()); + b_data, b_ob_info, b_depsgraph, need_undeformed, new_mesh.get_subdivision_type()); if (b_mesh) { + /* Motion blur attribute is relative to seconds, we need it relative to frames. */ + const bool need_motion = mesh_need_motion_attribute(b_ob_info, scene); + const float motion_scale = (need_motion) ? + scene->motion_shutter_time() / + (b_scene.render().fps() / b_scene.render().fps_base()) : + 0.0f; + /* Sync mesh itself. */ if (new_mesh.get_subdivision_type() != Mesh::SUBDIVISION_NONE) create_subd_mesh(scene, &new_mesh, - b_ob, + b_ob_info, b_mesh, new_mesh.get_used_shaders(), + need_motion, + motion_scale, dicing_rate, max_subdivisions); else - create_mesh(scene, &new_mesh, b_mesh, new_mesh.get_used_shaders(), false); - - free_object_to_mesh(b_data, b_ob, b_mesh); + create_mesh(scene, + &new_mesh, + b_mesh, + new_mesh.get_used_shaders(), + need_motion, + motion_scale, + false); + + free_object_to_mesh(b_data, b_ob_info, b_mesh); } } - /* cached velocities (e.g. from alembic archive) */ - sync_mesh_cached_velocities(b_ob, scene, &new_mesh); - - /* mesh fluid motion mantaflow */ - sync_mesh_fluid_motion(b_ob, scene, &new_mesh); - /* update original sockets */ mesh->clear_non_sockets(); @@ -1230,22 +1202,10 @@ void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *me } void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, - BL::Object b_ob, + BObjectInfo &b_ob_info, Mesh *mesh, int motion_step) { - /* Fluid motion blur already exported. */ - BL::FluidDomainSettings b_fluid_domain = object_fluid_liquid_domain_find(b_ob); - if (b_fluid_domain) { - return; - } - - /* Cached motion blur already exported. */ - BL::MeshSequenceCacheModifier mesh_cache = object_mesh_cache_find(b_ob, true, nullptr); - if (mesh_cache) { - return; - } - /* Skip if no vertices were exported. */ size_t numverts = mesh->get_verts().size(); if (numverts == 0) { @@ -1255,11 +1215,13 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, /* Skip objects without deforming modifiers. this is not totally reliable, * would need a more extensive check to see which objects are animated. */ BL::Mesh b_mesh(PointerRNA_NULL); - if (ccl::BKE_object_is_deform_modified(b_ob, b_scene, preview)) { + if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) { /* get derived mesh */ - b_mesh = object_to_mesh(b_data, b_ob, b_depsgraph, false, Mesh::SUBDIVISION_NONE); + b_mesh = object_to_mesh(b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE); } + const std::string ob_name = b_ob_info.real_object.name(); + /* TODO(sergey): Perform preliminary check for number of vertices. */ if (b_mesh) { /* Export deformed coordinates. */ @@ -1295,17 +1257,17 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, memcmp(mP, &mesh->get_verts()[0], sizeof(float3) * numverts) == 0) { /* no motion, remove attributes again */ if (b_mesh.vertices.length() != numverts) { - VLOG(1) << "Topology differs, disabling motion blur for object " << b_ob.name(); + VLOG(1) << "Topology differs, disabling motion blur for object " << ob_name; } else { - VLOG(1) << "No actual deformation motion for object " << b_ob.name(); + VLOG(1) << "No actual deformation motion for object " << ob_name; } mesh->attributes.remove(ATTR_STD_MOTION_VERTEX_POSITION); if (attr_mN) mesh->attributes.remove(ATTR_STD_MOTION_VERTEX_NORMAL); } else if (motion_step > 0) { - VLOG(1) << "Filling deformation motion for object " << b_ob.name(); + VLOG(1) << "Filling deformation motion for object " << ob_name; /* motion, fill up previous steps that we might have skipped because * they had no motion, but we need them anyway now */ float3 *P = &mesh->get_verts()[0]; @@ -1319,8 +1281,8 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, } else { if (b_mesh.vertices.length() != numverts) { - VLOG(1) << "Topology differs, discarding motion blur for object " << b_ob.name() - << " at time " << motion_step; + VLOG(1) << "Topology differs, discarding motion blur for object " << ob_name << " at time " + << motion_step; memcpy(mP, &mesh->get_verts()[0], sizeof(float3) * numverts); if (mN != NULL) { memcpy(mN, attr_N->data_float3(), sizeof(float3) * numverts); @@ -1328,7 +1290,7 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, } } - free_object_to_mesh(b_data, b_ob, b_mesh); + free_object_to_mesh(b_data, b_ob_info, b_mesh); return; } diff --git a/intern/cycles/blender/blender_object.cpp b/intern/cycles/blender/blender_object.cpp index 5d98b61b409..22d6edeb099 100644 --- a/intern/cycles/blender/blender_object.cpp +++ b/intern/cycles/blender/blender_object.cpp @@ -154,7 +154,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, const bool is_instance = b_instance.is_instance(); BL::Object b_ob = b_instance.object(); BL::Object b_parent = is_instance ? b_instance.parent() : b_instance.object(); - BL::Object b_ob_instance = is_instance ? b_instance.instance_object() : b_ob; + BObjectInfo b_ob_info{b_ob, is_instance ? b_instance.instance_object() : b_ob, b_ob.data()}; const bool motion = motion_time != 0.0f; /*const*/ Transform tfm = get_transform(b_ob.matrix_world()); int *persistent_id = NULL; @@ -178,8 +178,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, { sync_light(b_parent, persistent_id, - b_ob, - b_ob_instance, + b_ob_info, is_instance ? b_instance.random_id() : 0, tfm, use_portal); @@ -231,7 +230,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, TaskPool *object_geom_task_pool = (is_instance) ? NULL : geom_task_pool; /* key to lookup object */ - ObjectKey key(b_parent, persistent_id, b_ob_instance, use_particle_hair); + ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, use_particle_hair); Object *object; /* motion vector case */ @@ -249,12 +248,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, /* mesh deformation */ if (object->get_geometry()) - sync_geometry_motion(b_depsgraph, - b_ob_instance, - object, - motion_time, - use_particle_hair, - object_geom_task_pool); + sync_geometry_motion( + b_depsgraph, b_ob_info, object, motion_time, use_particle_hair, object_geom_task_pool); } return object; @@ -265,15 +260,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, (tfm != object->get_tfm()); /* mesh sync */ - /* b_ob is owned by the iterator and will go out of scope at the end of the block. - * b_ob_instance is the original object and will remain valid for deferred geometry - * sync. */ - Geometry *geometry = sync_geometry(b_depsgraph, - b_ob_instance, - b_ob_instance, - object_updated, - use_particle_hair, - object_geom_task_pool); + Geometry *geometry = sync_geometry( + b_depsgraph, b_ob_info, object_updated, use_particle_hair, object_geom_task_pool); object->set_geometry(geometry); /* special case not tracked by object update flags */ @@ -616,7 +604,7 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph, * only available in preview renders since currently do not have a good cache policy, the * data being loaded at once for all the frames. */ if (experimental && b_v3d) { - b_mesh_cache = object_mesh_cache_find(b_ob, false, &has_subdivision_modifier); + b_mesh_cache = object_mesh_cache_find(b_ob, &has_subdivision_modifier); use_procedural = b_mesh_cache && b_mesh_cache.cache_file().use_render_procedural(); } @@ -731,6 +719,14 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render, } } + /* Check which geometry already has motion blur so it can be skipped. */ + geometry_motion_attribute_synced.clear(); + for (Geometry *geom : scene->geometry) { + if (geom->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION)) { + geometry_motion_attribute_synced.insert(geom); + } + } + /* note iteration over motion_times set happens in sorted order */ foreach (float relative_time, motion_times) { /* center time is already handled. */ @@ -761,6 +757,8 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render, sync_objects(b_depsgraph, b_v3d, relative_time); } + geometry_motion_attribute_synced.clear(); + /* we need to set the python thread state again because this * function assumes it is being executed from python and will * try to save the thread state */ diff --git a/intern/cycles/blender/blender_sync.h b/intern/cycles/blender/blender_sync.h index 44322dda6b9..d25c0ce1bc3 100644 --- a/intern/cycles/blender/blender_sync.h +++ b/intern/cycles/blender/blender_sync.h @@ -23,6 +23,7 @@ #include "RNA_types.h" #include "blender/blender_id_map.h" +#include "blender/blender_util.h" #include "blender/blender_viewport.h" #include "render/scene.h" @@ -158,18 +159,24 @@ class BlenderSync { bool sync_object_attributes(BL::DepsgraphObjectInstance &b_instance, Object *object); /* Volume */ - void sync_volume(BL::Object &b_ob, Volume *volume); + void sync_volume(BObjectInfo &b_ob_info, Volume *volume); /* Mesh */ - void sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh); - void sync_mesh_motion(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh, int motion_step); + void sync_mesh(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Mesh *mesh); + void sync_mesh_motion(BL::Depsgraph b_depsgraph, + BObjectInfo &b_ob_info, + Mesh *mesh, + int motion_step); /* Hair */ - void sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair); - void sync_hair_motion(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair, int motion_step); - void sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step = 0); + void sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Hair *hair); + void sync_hair_motion(BL::Depsgraph b_depsgraph, + BObjectInfo &b_ob_info, + Hair *hair, + int motion_step); + void sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step = 0); void sync_particle_hair( - Hair *hair, BL::Mesh &b_mesh, BL::Object &b_ob, bool motion, int motion_step = 0); + Hair *hair, BL::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, int motion_step = 0); bool object_has_particle_hair(BL::Object b_ob); /* Camera */ @@ -178,14 +185,13 @@ class BlenderSync { /* Geometry */ Geometry *sync_geometry(BL::Depsgraph &b_depsgrpah, - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, bool object_updated, bool use_particle_hair, TaskPool *task_pool); void sync_geometry_motion(BL::Depsgraph &b_depsgraph, - BL::Object &b_ob, + BObjectInfo &b_ob_info, Object *object, float motion_time, bool use_particle_hair, @@ -194,8 +200,7 @@ class BlenderSync { /* Light */ void sync_light(BL::Object &b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, int random_id, Transform &tfm, bool *use_portal); @@ -231,6 +236,7 @@ class BlenderSync { id_map<ParticleSystemKey, ParticleSystem> particle_system_map; set<Geometry *> geometry_synced; set<Geometry *> geometry_motion_synced; + set<Geometry *> geometry_motion_attribute_synced; set<float> motion_times; void *world_map; bool world_recalc; diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h index f6824f31b7b..04008d77d89 100644 --- a/intern/cycles/blender/blender_util.h +++ b/intern/cycles/blender/blender_util.h @@ -40,6 +40,28 @@ float *BKE_image_get_float_pixels_for_frame(void *image, int frame, int tile); CCL_NAMESPACE_BEGIN +struct BObjectInfo { + /* Object directly provided by the depsgraph iterator. This object is only valid during one + * iteration and must not be accessed afterwards. Transforms and visibility should be checked on + * this object. */ + BL::Object iter_object; + + /* This object remains alive even after the object iterator is done. It corresponds to one + * original object. It is the object that owns the object data below. */ + BL::Object real_object; + + /* The object-data referenced by the iter object. This is still valid after the depsgraph + * iterator is done. It might have a different type compared to real_object.data(). */ + BL::ID object_data; + + /* True when the current geometry is the data of the referenced object. False when it is a + * geometry instance that does not have a 1-to-1 relationship with an object. */ + bool is_real_object_data() const + { + return const_cast<BL::Object &>(real_object).data() == object_data; + } +}; + typedef BL::ShaderNodeAttribute::attribute_type_enum BlenderAttributeType; BlenderAttributeType blender_attribute_name_split_type(ustring name, string *r_real_name); @@ -47,7 +69,7 @@ void python_thread_state_save(void **python_thread_state); void python_thread_state_restore(void **python_thread_state); static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, - BL::Object &object, + BObjectInfo &b_ob_info, BL::Depsgraph & /*depsgraph*/, bool /*calc_undeformed*/, Mesh::SubdivisionType subdivision_type) @@ -69,9 +91,9 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, #endif BL::Mesh mesh(PointerRNA_NULL); - if (object.type() == BL::Object::type_MESH) { + if (b_ob_info.object_data.is_a(&RNA_Mesh)) { /* TODO: calc_undeformed is not used. */ - mesh = BL::Mesh(object.data()); + mesh = BL::Mesh(b_ob_info.object_data); /* Make a copy to split faces if we use autosmooth, otherwise not needed. * Also in edit mode do we need to make a copy, to ensure data layers like @@ -79,12 +101,15 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, if (mesh.is_editmode() || (mesh.use_auto_smooth() && subdivision_type == Mesh::SUBDIVISION_NONE)) { BL::Depsgraph depsgraph(PointerRNA_NULL); - mesh = object.to_mesh(false, depsgraph); + assert(b_ob_info.is_real_object_data()); + mesh = b_ob_info.real_object.to_mesh(false, depsgraph); } } else { BL::Depsgraph depsgraph(PointerRNA_NULL); - mesh = object.to_mesh(false, depsgraph); + if (b_ob_info.is_real_object_data()) { + mesh = b_ob_info.real_object.to_mesh(false, depsgraph); + } } #if 0 @@ -108,10 +133,14 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, } static inline void free_object_to_mesh(BL::BlendData & /*data*/, - BL::Object &object, + BObjectInfo &b_ob_info, BL::Mesh &mesh) { + if (!b_ob_info.is_real_object_data()) { + return; + } /* Free mesh if we didn't just use the existing one. */ + BL::Object object = b_ob_info.real_object; if (object.data().ptr.data != mesh.ptr.data) { object.to_mesh_clear(); } @@ -219,9 +248,13 @@ static inline bool BKE_object_is_modified(BL::Object &self, BL::Scene &scene, bo return self.is_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : false; } -static inline bool BKE_object_is_deform_modified(BL::Object &self, BL::Scene &scene, bool preview) +static inline bool BKE_object_is_deform_modified(BObjectInfo &self, BL::Scene &scene, bool preview) { - return self.is_deform_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : false; + if (!self.is_real_object_data()) { + return false; + } + return self.real_object.is_deform_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : + false; } static inline int render_resolution_x(BL::RenderSettings &b_render) @@ -540,22 +573,6 @@ static inline bool object_use_deform_motion(BL::Object &b_parent, BL::Object &b_ return use_deform_motion; } -static inline BL::FluidDomainSettings object_fluid_liquid_domain_find(BL::Object &b_ob) -{ - for (BL::Modifier &b_mod : b_ob.modifiers) { - if (b_mod.is_a(&RNA_FluidModifier)) { - BL::FluidModifier b_mmd(b_mod); - - if (b_mmd.fluid_type() == BL::FluidModifier::fluid_type_DOMAIN && - b_mmd.domain_settings().domain_type() == BL::FluidDomainSettings::domain_type_LIQUID) { - return b_mmd.domain_settings(); - } - } - } - - return BL::FluidDomainSettings(PointerRNA_NULL); -} - static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b_ob) { for (BL::Modifier &b_mod : b_ob.modifiers) { @@ -573,7 +590,6 @@ static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b } static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b_ob, - bool check_velocity, bool *has_subdivision_modifier) { for (int i = b_ob.modifiers.length() - 1; i >= 0; --i) { @@ -581,13 +597,6 @@ static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b if (b_mod.type() == BL::Modifier::type_MESH_SEQUENCE_CACHE) { BL::MeshSequenceCacheModifier mesh_cache = BL::MeshSequenceCacheModifier(b_mod); - - if (check_velocity) { - if (!MeshSequenceCacheModifier_has_velocity_get(&mesh_cache.ptr)) { - return BL::MeshSequenceCacheModifier(PointerRNA_NULL); - } - } - return mesh_cache; } @@ -596,9 +605,7 @@ static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b continue; } - /* Only skip the subsurf modifier if we are not checking for the mesh sequence cache modifier - * for motion blur. */ - if (b_mod.type() == BL::Modifier::type_SUBSURF && !check_velocity) { + if (b_mod.type() == BL::Modifier::type_SUBSURF) { if (has_subdivision_modifier) { *has_subdivision_modifier = true; } diff --git a/intern/cycles/blender/blender_volume.cpp b/intern/cycles/blender/blender_volume.cpp index 772ab9f5c8a..0a5b19d7d4c 100644 --- a/intern/cycles/blender/blender_volume.cpp +++ b/intern/cycles/blender/blender_volume.cpp @@ -181,9 +181,12 @@ class BlenderSmokeLoader : public ImageLoader { AttributeStandard attribute; }; -static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Volume *volume, float frame) +static void sync_smoke_volume(Scene *scene, BObjectInfo &b_ob_info, Volume *volume, float frame) { - BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob); + if (!b_ob_info.is_real_object_data()) { + return; + } + BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob_info.real_object); if (!b_domain) { return; } @@ -206,7 +209,7 @@ static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Volume *volume, fl Attribute *attr = volume->attributes.add(std); - ImageLoader *loader = new BlenderSmokeLoader(b_ob, std); + ImageLoader *loader = new BlenderSmokeLoader(b_ob_info.real_object, std); ImageParams params; params.frame = frame; @@ -244,11 +247,11 @@ class BlenderVolumeLoader : public VDBImageLoader { }; static void sync_volume_object(BL::BlendData &b_data, - BL::Object &b_ob, + BObjectInfo &b_ob_info, Scene *scene, Volume *volume) { - BL::Volume b_volume(b_ob.data()); + BL::Volume b_volume(b_ob_info.object_data); b_volume.grids.load(b_data.ptr.data); BL::VolumeRender b_render(b_volume.render()); @@ -296,19 +299,19 @@ static void sync_volume_object(BL::BlendData &b_data, } } -void BlenderSync::sync_volume(BL::Object &b_ob, Volume *volume) +void BlenderSync::sync_volume(BObjectInfo &b_ob_info, Volume *volume) { volume->clear(true); if (view_layer.use_volumes) { - if (b_ob.type() == BL::Object::type_VOLUME) { + if (b_ob_info.object_data.is_a(&RNA_Volume)) { /* Volume object. Create only attributes, bounding mesh will then * be automatically generated later. */ - sync_volume_object(b_data, b_ob, scene, volume); + sync_volume_object(b_data, b_ob_info, scene, volume); } else { /* Smoke domain. */ - sync_smoke_volume(scene, b_ob, volume, b_scene.frame_current()); + sync_smoke_volume(scene, b_ob_info, volume, b_scene.frame_current()); } } diff --git a/intern/cycles/render/hair.h b/intern/cycles/render/hair.h index 1a8f422e8c4..e4451d70767 100644 --- a/intern/cycles/render/hair.h +++ b/intern/cycles/render/hair.h @@ -89,10 +89,10 @@ class Hair : public Geometry { float4 r_keys[4]) const; }; - NODE_SOCKET_API(array<float3>, curve_keys) - NODE_SOCKET_API(array<float>, curve_radius) - NODE_SOCKET_API(array<int>, curve_first_key) - NODE_SOCKET_API(array<int>, curve_shader) + NODE_SOCKET_API_ARRAY(array<float3>, curve_keys) + NODE_SOCKET_API_ARRAY(array<float>, curve_radius) + NODE_SOCKET_API_ARRAY(array<int>, curve_first_key) + NODE_SOCKET_API_ARRAY(array<int>, curve_shader) /* BVH */ size_t curvekey_offset; diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index 5290d68e75a..15aa4e047b5 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -410,38 +410,39 @@ void LightManager::device_update_distribution(Device *, } float trianglearea = totarea; - /* point lights */ - float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f; bool use_lamp_mis = false; - int light_index = 0; - foreach (Light *light, scene->lights) { - if (!light->is_enabled) - continue; - distribution[offset].totarea = totarea; - distribution[offset].prim = ~light_index; - distribution[offset].lamp.pad = 1.0f; - distribution[offset].lamp.size = light->size; - totarea += lightarea; + if (num_lights > 0) { + float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f; + foreach (Light *light, scene->lights) { + if (!light->is_enabled) + continue; - if (light->light_type == LIGHT_DISTANT) { - use_lamp_mis |= (light->angle > 0.0f && light->use_mis); - } - else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) { - use_lamp_mis |= (light->size > 0.0f && light->use_mis); - } - else if (light->light_type == LIGHT_AREA) { - use_lamp_mis |= light->use_mis; - } - else if (light->light_type == LIGHT_BACKGROUND) { - num_background_lights++; - background_mis |= light->use_mis; - } + distribution[offset].totarea = totarea; + distribution[offset].prim = ~light_index; + distribution[offset].lamp.pad = 1.0f; + distribution[offset].lamp.size = light->size; + totarea += lightarea; - light_index++; - offset++; + if (light->light_type == LIGHT_DISTANT) { + use_lamp_mis |= (light->angle > 0.0f && light->use_mis); + } + else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) { + use_lamp_mis |= (light->size > 0.0f && light->use_mis); + } + else if (light->light_type == LIGHT_AREA) { + use_lamp_mis |= light->use_mis; + } + else if (light->light_type == LIGHT_BACKGROUND) { + num_background_lights++; + background_mis |= light->use_mis; + } + + light_index++; + offset++; + } } /* normalize cumulative distribution functions */ diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h index 99cb0b779b8..3013e9b1866 100644 --- a/intern/cycles/render/nodes.h +++ b/intern/cycles/render/nodes.h @@ -128,7 +128,7 @@ class ImageTextureNode : public ImageSlotTextureNode { NODE_SOCKET_API(float, projection_blend) NODE_SOCKET_API(bool, animated) NODE_SOCKET_API(float3, vector) - NODE_SOCKET_API(array<int>, tiles) + NODE_SOCKET_API_ARRAY(array<int>, tiles) protected: void cull_tiles(Scene *scene, ShaderGraph *graph); @@ -1554,7 +1554,7 @@ class CurvesNode : public ShaderNode { return NODE_GROUP_LEVEL_3; } - NODE_SOCKET_API(array<float3>, curves) + NODE_SOCKET_API_ARRAY(array<float3>, curves) NODE_SOCKET_API(float, min_x) NODE_SOCKET_API(float, max_x) NODE_SOCKET_API(float, fac) @@ -1588,8 +1588,8 @@ class RGBRampNode : public ShaderNode { return NODE_GROUP_LEVEL_1; } - NODE_SOCKET_API(array<float3>, ramp) - NODE_SOCKET_API(array<float>, ramp_alpha) + NODE_SOCKET_API_ARRAY(array<float3>, ramp) + NODE_SOCKET_API_ARRAY(array<float>, ramp_alpha) NODE_SOCKET_API(float, fac) NODE_SOCKET_API(bool, interpolate) }; diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index e46f712cb64..221fa140f70 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -652,6 +652,11 @@ typedef struct { enum { GHOST_kXrContextDebug = (1 << 0), GHOST_kXrContextDebugTime = (1 << 1), +# ifdef WIN32 + /* Needed to avoid issues with the SteamVR OpenGL graphics binding (use DirectX fallback + instead). */ + GHOST_kXrContextGpuNVIDIA = (1 << 2), +# endif }; typedef struct { diff --git a/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp b/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp index 18adf948e3b..5b026eb1632 100644 --- a/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp +++ b/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp @@ -106,7 +106,7 @@ GHOST_TSuccess GHOST_DisplayManagerSDL::setCurrentDisplaySetting( * ftp://ftp.idsoftware.com/idstuff/source/q2source-3.21.zip * See linux/gl_glx.c:GLimp_SetMode * http://wiki.bzflag.org/BZFlag_Source - * See src/platform/SDLDisplay.cxx:SDLDisplay and createWindow + * See: `src/platform/SDLDisplay.cxx:SDLDisplay` and `createWindow`. */ SDL_DisplayMode mode; const int num_modes = SDL_GetNumDisplayModes(display); diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index a7498e9f91f..fe8fec052fe 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -20,6 +20,7 @@ * Abstraction for XR (VR, AR, MR, ..) access via OpenXR. */ +#include <algorithm> #include <cassert> #include <sstream> #include <string> @@ -98,7 +99,7 @@ void GHOST_XrContext::initialize(const GHOST_XrContextCreateInfo *create_info) storeInstanceProperties(); /* Multiple bindings may be enabled. Now that we know the runtime in use, settle for one. */ - m_gpu_binding_type = determineGraphicsBindingTypeToUse(graphics_binding_types); + m_gpu_binding_type = determineGraphicsBindingTypeToUse(graphics_binding_types, create_info); printInstanceInfo(); if (isDebugMode()) { @@ -135,7 +136,8 @@ void GHOST_XrContext::storeInstanceProperties() {"Monado(XRT) by Collabora et al", OPENXR_RUNTIME_MONADO}, {"Oculus", OPENXR_RUNTIME_OCULUS}, {"SteamVR/OpenXR", OPENXR_RUNTIME_STEAMVR}, - {"Windows Mixed Reality Runtime", OPENXR_RUNTIME_WMR}}; + {"Windows Mixed Reality Runtime", OPENXR_RUNTIME_WMR}, + {"Varjo OpenXR Runtime", OPENXR_RUNTIME_VARJO}}; decltype(runtime_map)::const_iterator runtime_map_iter; m_oxr->instance_properties.type = XR_TYPE_INSTANCE_PROPERTIES; @@ -415,6 +417,12 @@ void GHOST_XrContext::getExtensionsToEnable( try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME); try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME); + /* Varjo quad view extension. */ + try_ext.push_back(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME); + + /* Varjo foveated extension. */ + try_ext.push_back(XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME); + r_ext_names.reserve(try_ext.size() + graphics_binding_types.size()); /* Add graphics binding extensions (may be multiple ones, we'll settle for one to use later, once @@ -465,16 +473,20 @@ std::vector<GHOST_TXrGraphicsBinding> GHOST_XrContext::determineGraphicsBindingT } GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse( - const std::vector<GHOST_TXrGraphicsBinding> &enabled_types) + const std::vector<GHOST_TXrGraphicsBinding> &enabled_types, + const GHOST_XrContextCreateInfo *create_info) { /* Return the first working type. */ for (GHOST_TXrGraphicsBinding type : enabled_types) { #ifdef WIN32 - /* The SteamVR OpenGL backend fails currently. Disable it and allow falling back to the DirectX - * one. */ - if ((m_runtime_id == OPENXR_RUNTIME_STEAMVR) && (type == GHOST_kXrGraphicsOpenGL)) { + /* The SteamVR OpenGL backend currently fails for NVIDIA GPU's. Disable it and allow falling + * back to the DirectX one. */ + if ((m_runtime_id == OPENXR_RUNTIME_STEAMVR) && (type == GHOST_kXrGraphicsOpenGL) && + ((create_info->context_flag & GHOST_kXrContextGpuNVIDIA) != 0)) { continue; } +#else + ((void)create_info); #endif assert(type != GHOST_kXrGraphicsUnknown); @@ -613,4 +625,11 @@ bool GHOST_XrContext::isDebugTimeMode() const return m_debug_time; } +bool GHOST_XrContext::isExtensionEnabled(const char *ext) const +{ + bool contains = std::find(m_enabled_extensions.begin(), m_enabled_extensions.end(), ext) != + m_enabled_extensions.end(); + return contains; +} + /** \} */ /* Ghost Internal Accessors and Mutators */ diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h index f29d7349f7e..479b50e1537 100644 --- a/intern/ghost/intern/GHOST_XrContext.h +++ b/intern/ghost/intern/GHOST_XrContext.h @@ -51,6 +51,7 @@ enum GHOST_TXrOpenXRRuntimeID { OPENXR_RUNTIME_OCULUS, OPENXR_RUNTIME_STEAMVR, OPENXR_RUNTIME_WMR, /* Windows Mixed Reality */ + OPENXR_RUNTIME_VARJO, OPENXR_RUNTIME_UNKNOWN }; @@ -94,6 +95,8 @@ class GHOST_XrContext : public GHOST_IXrContext { bool isDebugMode() const; bool isDebugTimeMode() const; + bool isExtensionEnabled(const char *ext) const; + private: static GHOST_XrErrorHandlerFn s_error_handler; static void *s_error_handler_customdata; @@ -136,5 +139,6 @@ class GHOST_XrContext : public GHOST_IXrContext { std::vector<GHOST_TXrGraphicsBinding> determineGraphicsBindingTypesToEnable( const GHOST_XrContextCreateInfo *create_info); GHOST_TXrGraphicsBinding determineGraphicsBindingTypeToUse( - const std::vector<GHOST_TXrGraphicsBinding> &enabled_types); + const std::vector<GHOST_TXrGraphicsBinding> &enabled_types, + const GHOST_XrContextCreateInfo *create_info); }; diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp index a08b2d6045a..cd930c8328b 100644 --- a/intern/ghost/intern/GHOST_XrSession.cpp +++ b/intern/ghost/intern/GHOST_XrSession.cpp @@ -41,10 +41,13 @@ struct OpenXRSessionData { XrSession session = XR_NULL_HANDLE; XrSessionState session_state = XR_SESSION_STATE_UNKNOWN; - /* Only stereo rendering supported now. */ - const XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + /* Use stereo rendering by default. */ + XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + bool foveation_supported = false; + XrSpace reference_space; XrSpace view_space; + XrSpace combined_eye_space; std::vector<XrView> views; std::vector<GHOST_XrSwapchain> swapchains; @@ -58,6 +61,9 @@ struct GHOST_XrDrawInfo { std::chrono::high_resolution_clock::time_point frame_begin_time; /* Time previous frames took for rendering (in ms). */ std::list<double> last_frame_times; + + /* Whether foveation is active for the frame. */ + bool foveation_active; }; /* -------------------------------------------------------------------- */ @@ -82,6 +88,9 @@ GHOST_XrSession::~GHOST_XrSession() if (m_oxr->view_space != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySpace(m_oxr->view_space)); } + if (m_oxr->combined_eye_space != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroySpace(m_oxr->combined_eye_space)); + } if (m_oxr->session != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySession(m_oxr->session)); } @@ -189,6 +198,13 @@ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose & create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW; CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.view_space), "Failed to create view reference space."); + + /* Foveation reference spaces. */ + if (oxr.foveation_supported) { + create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO; + CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.combined_eye_space), + "Failed to create combined eye reference space."); + } } void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) @@ -292,9 +308,19 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( void GHOST_XrSession::prepareDrawing() { + assert(m_context->getInstance() != XR_NULL_HANDLE); + std::vector<XrViewConfigurationView> view_configs; uint32_t view_count; + /* Attempt to use quad view if supported. */ + if (m_context->isExtensionEnabled(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME)) { + m_oxr->view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO; + } + + m_oxr->foveation_supported = m_context->isExtensionEnabled( + XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME); + CHECK_XR( xrEnumerateViewConfigurationViews( m_context->getInstance(), m_oxr->system_id, m_oxr->view_type, 0, &view_count, nullptr), @@ -306,7 +332,36 @@ void GHOST_XrSession::prepareDrawing() view_configs.size(), &view_count, view_configs.data()), - "Failed to get count of view configurations."); + "Failed to get view configurations."); + + /* If foveated rendering is used, query the foveated views. */ + if (m_oxr->foveation_supported) { + std::vector<XrFoveatedViewConfigurationViewVARJO> request_foveated_config{ + view_count, {XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO, nullptr, XR_TRUE}}; + + auto foveated_views = std::vector<XrViewConfigurationView>(view_count, + {XR_TYPE_VIEW_CONFIGURATION_VIEW}); + + for (uint32_t i = 0; i < view_count; i++) { + foveated_views[i].next = &request_foveated_config[i]; + } + CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(), + m_oxr->system_id, + m_oxr->view_type, + view_configs.size(), + &view_count, + foveated_views.data()), + "Failed to get foveated view configurations."); + + /* Ensure swapchains have correct size even when foveation is being used. */ + for (uint32_t i = 0; i < view_count; i++) { + view_configs[i].recommendedImageRectWidth = std::max( + view_configs[i].recommendedImageRectWidth, foveated_views[i].recommendedImageRectWidth); + view_configs[i].recommendedImageRectHeight = std::max( + view_configs[i].recommendedImageRectHeight, + foveated_views[i].recommendedImageRectHeight); + } + } for (const XrViewConfigurationView &view_config : view_configs) { m_oxr->swapchains.emplace_back(*m_gpu_binding, m_oxr->session, view_config); @@ -327,6 +382,20 @@ void GHOST_XrSession::beginFrameDrawing() CHECK_XR(xrWaitFrame(m_oxr->session, &wait_info, &frame_state), "Failed to synchronize frame rates between Blender and the device."); + /* Check if we have foveation available for the current frame. */ + m_draw_info->foveation_active = false; + if (m_oxr->foveation_supported) { + XrSpaceLocation render_gaze_location{XR_TYPE_SPACE_LOCATION}; + CHECK_XR(xrLocateSpace(m_oxr->combined_eye_space, + m_oxr->view_space, + frame_state.predictedDisplayTime, + &render_gaze_location), + "Failed to locate combined eye space."); + + m_draw_info->foveation_active = (render_gaze_location.locationFlags & + XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0; + } + CHECK_XR(xrBeginFrame(m_oxr->session, &begin_info), "Failed to submit frame rendering start state."); @@ -442,6 +511,8 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer( std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata) { XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO}; + XrViewLocateFoveatedRenderingVARJO foveated_info{ + XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO, nullptr, true}; XrViewState view_state = {XR_TYPE_VIEW_STATE}; XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION}; XrSpaceLocation view_location{XR_TYPE_SPACE_LOCATION}; @@ -451,6 +522,10 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer( viewloc_info.displayTime = m_draw_info->frame_state.predictedDisplayTime; viewloc_info.space = m_oxr->reference_space; + if (m_draw_info->foveation_active) { + viewloc_info.next = &foveated_info; + } + CHECK_XR(xrLocateViews(m_oxr->session, &viewloc_info, &view_state, @@ -458,6 +533,7 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer( &view_count, m_oxr->views.data()), "Failed to query frame view and projection state."); + assert(m_oxr->swapchains.size() == view_count); CHECK_XR( diff --git a/release/datafiles/blender_icons.svg b/release/datafiles/blender_icons.svg index c3461fd1bea..70bd7dc8085 100644 --- a/release/datafiles/blender_icons.svg +++ b/release/datafiles/blender_icons.svg @@ -17321,6 +17321,58 @@ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" d="m 368.30892,141.58547 c -0.27613,4e-5 -0.49997,0.22388 -0.5,0.5 v 1.26473 h -4.76715 v 1.4911 h 4.76715 v 1.24417 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 0.63583,0.004 3.43318,-0.006 3.9995,-0.006 0.24106,0 0.46127,-0.0485 0.46127,-0.50967 4e-5,-0.85242 -8.9e-4,-2.98571 -8.9e-4,-3.95935 0,-0.30244 -0.19636,-0.51552 -0.46153,-0.51552 -0.82724,0 -3.36276,-0.009 -3.99823,-0.009 v 2e-5 z m 2.30359,-4.68113 -0.005,4.25868 c 0.48989,0.002 1.39549,0.005 1.88538,0.007 0.44541,0.0357 0.71675,0.47423 0.71675,0.85988 -6.6e-4,1.00616 -0.009,2.97018 -0.009,4.15122 0,0.46073 -0.24756,0.84994 -0.6533,0.84994 -0.48399,0.0143 -1.44986,-1.1e-4 -1.93405,-1.6e-4 v 3.87356 l -7.75691,-0.0669 v -14.00001 z" sodipodi:nodetypes="cccccccccccccccccccccccccc" /> + <g + transform="translate(230.76791,210.17135)" + style="display:inline;enable-background:new" + id="g4087_GP_lineart"> + <g + id="g4082"> + <path + sodipodi:nodetypes="cccccccccccccccccccc" + inkscape:connector-curvature="0" + id="path12456-6" + mask="none" + d="m 198.0253,98.27163 v 1.5 h 1 v -1.5 z m 0,2.5 v 2 h 1 v -2 z m 0,3 v 1.2793 l -2.58594,2.35156 0.67188,0.73828 2.60351,-2.36719 0.49027,-0.002 c 0.82475,0 0.82408,-1 0,-1 h -0.17972 v -1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + <path + id="path4185" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.10423;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 207.2397,99.568306 c -0.33768,-0.02992 -0.70751,0.105959 -1.01625,0.406518 l -0.51139,0.495896 c -0.13287,0.12942 -0.13287,0.34092 0,0.47035 l 2.04339,1.98784 c 0.13292,0.12938 0.3479,0.12938 0.48082,0 l 0.50922,-0.49802 c 0.3087,-0.30067 0.44811,-0.65869 0.41741,-0.98755 -0.0307,-0.32884 -0.20718,-0.60186 -0.41741,-0.80663 l -0.67969,-0.661886 c -0.21026,-0.204768 -0.48842,-0.37662 -0.8261,-0.406518 z m -2.31222,1.800554 c -0.0883,9.4e-4 -0.17353,0.0367 -0.23603,0.0979 l -4.25293,4.14168 c -0.0434,0.0426 -0.0749,0.095 -0.0896,0.15324 l -0.67969,2.65189 c -0.0614,0.24217 0.16235,0.46285 0.41088,0.40225 l 2.72308,-0.66402 c 0.0599,-0.0144 0.11363,-0.0428 0.15735,-0.0851 l 4.2551,-4.14382 c 0.13286,-0.12943 0.13286,-0.33881 0,-0.46825 l -2.0434,-1.98784 c -0.0651,-0.0634 -0.15267,-0.0994 -0.24478,-0.0979 z" /> + <path + id="path12458-7" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 198.52539,94.771484 c -0.1326,2.7e-5 -0.25978,0.05272 -0.35351,0.146485 l -3,3 c -0.0938,0.09376 -0.14646,0.220915 -0.14649,0.353515 v 9.999996 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 2.50158 c 0.72806,0 0.76638,-1.01916 0,-1 h -2.00158 v -8.999996 h 9 v 0.186392 c 0,0.766385 1,0.767345 1,0 v -0.47936 l 2,-2 v 0.907841 c 0,0.708905 1,0.709935 1,0 v -2.114873 c -3e-5,-0.276131 -0.22387,-0.499972 -0.5,-0.5 z m 0.20703,1 h 8.58594 l -2,2 h -8.58594 z" + sodipodi:nodetypes="ccccccsccccssccsscccccccc" /> + </g> + </g> + <g + transform="translate(167.42608,209.69482)" + style="display:inline;enable-background:new" + id="g7880_GP_lenght"> + <path + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 224.38607,100.78271 c -0.15574,0.005 -0.30353,0.0699 -0.41211,0.18164 l -2.05673,2.00254 c -0.62065,0.56444 0.28322,1.46831 0.84766,0.84765 l 2.05673,-2.00254 c 0.39088,-0.38144 0.1104,-1.04428 -0.43555,-1.02929 z" + id="path15289-7-6" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccc" /> + <path + sodipodi:nodetypes="ccccccccccc" + inkscape:connector-curvature="0" + id="path15289-7-6-5" + d="m 225.6621,95.349988 c -0.67621,-0.0096 -0.67621,1.009611 0,1 h 2.79493 c -1.0479,1.117288 -1.7641,1.668027 -2.82812,2.732043 -0.62065,0.56444 0.28321,1.468319 0.84765,0.847657 1.06063,-1.101282 1.59202,-1.777197 2.68554,-2.870716 v 2.791016 c -0.01,0.676162 1.00956,0.676162 1,0 v -4 c -3e-5,-0.276131 -0.22387,-0.499973 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + <path + sodipodi:nodetypes="ccccccccccc" + inkscape:connector-curvature="0" + id="path15289-7-6-5-2" + d="m 221.03217,109.33958 c 0.67621,0.01 0.67621,-1.00961 0,-1 h -2.79493 c 1.0479,-1.11729 1.7641,-1.66802 2.82812,-2.73204 0.62065,-0.56444 -0.28321,-1.46832 -0.84765,-0.84766 -1.06063,1.10128 -1.59202,1.7772 -2.68554,2.87072 v -2.79102 c 0.01,-0.67616 -1.00956,-0.67616 -1,0 v 4 c 3e-5,0.27613 0.22387,0.49998 0.5,0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + </g> + <path + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + d="m 417.92349,304.73964 c -0.7818,-0.0644 -0.86293,1.09626 -0.0796,1.1383 l 0.41758,0.0202 c 0.78182,0.0644 0.86296,-1.09626 0.0796,-1.13831 z m -7.87437,1.29265 c -0.65325,0.42724 0.0163,1.38626 0.65667,0.94062 l 0.34001,-0.23929 c 0.65327,-0.42727 -0.0163,-1.38631 -0.65662,-0.94061 z m 5.26412,-0.10772 c 0.785,-0.0185 0.73895,-1.18175 -0.0451,-1.14009 -0.6811,-0.0652 -1.43225,-0.0213 -2.22341,0.0851 -0.785,0.0185 -0.73896,1.18176 0.0451,1.14011 0.8585,-0.10954 1.60282,-0.14009 2.22342,-0.0852 z m -5.74172,5.34858 c -0.17789,-0.75187 -1.32618,-0.47161 -1.12597,0.27482 -0.008,0.72815 0.18352,1.43475 0.53595,2.12392 0.17789,0.75187 1.32617,0.47159 1.12598,-0.27483 -0.40688,-0.70818 -0.47775,-1.41605 -0.53596,-2.12391 z m 1.14987,4.81425 c 0.55238,0.5479 1.3799,-0.2833 0.81165,-0.81524 l -0.30437,-0.28193 c -0.55238,-0.54789 -1.37991,0.2833 -0.81163,0.81524 z m 2.55883,0.11471 c -0.78112,0.0716 -0.65484,1.22767 0.12391,1.13446 0.79706,0.0708 1.5429,0.0136 2.2124,-0.23372 0.7811,-0.0716 0.65482,-1.22768 -0.12391,-1.13445 -0.66955,0.35373 -1.42049,0.37687 -2.2124,0.23371 z m 4.35036,-1.24066 c 0.39775,-0.66505 -0.63058,-1.23994 -1.00859,-0.56384 l -0.19953,0.36135 c -0.39776,0.66506 0.63057,1.23995 1.00857,0.56383 z m -1.53457,-4.82813 c -0.44444,-0.63566 -1.409,0.0364 -0.94666,0.65956 0.53116,0.53126 0.99257,1.10609 1.28624,1.78569 0.44445,0.63565 1.40902,-0.0364 0.94667,-0.65956 -0.24301,-0.74231 -0.69323,-1.32054 -1.28625,-1.78569 z m -2.73483,-1.49223 c -0.72218,-0.30138 -1.16808,0.7761 -0.43732,1.05681 l 0.39025,0.14758 c 0.7222,0.30141 1.1681,-0.7761 0.43732,-1.0568 z m -7.60223,1.91562 c -0.52109,0.57678 0.37464,1.33651 0.87855,0.74515 l 0.26685,-0.31654 c 0.52111,-0.57679 -0.37465,-1.33654 -0.87854,-0.74516 z m 1.15912,7.09355 c -0.1906,-0.74845 -1.33363,-0.44917 -1.12109,0.29354 l 0.11543,0.39523 c 0.19062,0.74845 1.33365,0.44917 1.12109,-0.29354 z m -0.68592,-4.36328 c -0.0858,-0.76698 -1.25912,-0.62352 -1.15127,0.14077 -0.065,0.75431 -0.008,1.50847 0.28594,2.26232 0.0859,0.76696 1.25912,0.62352 1.15129,-0.14076 -0.28468,-0.81162 -0.29126,-1.53878 -0.28596,-2.26233 z m 1.97398,-4.7241 c -0.77314,0.13162 -0.55483,1.27463 0.21417,1.12135 0.7762,-0.30633 1.5005,-0.42412 2.18687,-0.40397 0.77313,-0.13163 0.55482,-1.27462 -0.21418,-1.12137 -0.74152,0.0229 -1.4733,0.13255 -2.18686,0.40399 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.15052;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:2.2;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + id="path4101-2-6-9-1_GP_dotdash" /> </g> <g inkscape:groupmode="layer" diff --git a/release/datafiles/blender_icons16/icon16_mod_dash.dat b/release/datafiles/blender_icons16/icon16_mod_dash.dat Binary files differnew file mode 100644 index 00000000000..a8419db8c16 --- /dev/null +++ b/release/datafiles/blender_icons16/icon16_mod_dash.dat diff --git a/release/datafiles/blender_icons16/icon16_mod_length.dat b/release/datafiles/blender_icons16/icon16_mod_length.dat Binary files differnew file mode 100644 index 00000000000..0e1e25fcd71 --- /dev/null +++ b/release/datafiles/blender_icons16/icon16_mod_length.dat diff --git a/release/datafiles/blender_icons16/icon16_mod_lineart.dat b/release/datafiles/blender_icons16/icon16_mod_lineart.dat Binary files differnew file mode 100644 index 00000000000..3478b14fdab --- /dev/null +++ b/release/datafiles/blender_icons16/icon16_mod_lineart.dat diff --git a/release/datafiles/blender_icons32/icon32_mod_dash.dat b/release/datafiles/blender_icons32/icon32_mod_dash.dat Binary files differnew file mode 100644 index 00000000000..cca56b0c9de --- /dev/null +++ b/release/datafiles/blender_icons32/icon32_mod_dash.dat diff --git a/release/datafiles/blender_icons32/icon32_mod_length.dat b/release/datafiles/blender_icons32/icon32_mod_length.dat Binary files differnew file mode 100644 index 00000000000..0d1cf1f33aa --- /dev/null +++ b/release/datafiles/blender_icons32/icon32_mod_length.dat diff --git a/release/datafiles/blender_icons32/icon32_mod_lineart.dat b/release/datafiles/blender_icons32/icon32_mod_lineart.dat Binary files differnew file mode 100644 index 00000000000..a8e9c976a9f --- /dev/null +++ b/release/datafiles/blender_icons32/icon32_mod_lineart.dat diff --git a/release/datafiles/locale b/release/datafiles/locale -Subproject 8a05b618f031582c006c6f62b9e60619ab3eef8 +Subproject 62e82958a760dad775d9b3387d7fb535fd6de4c diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index d51a82c482b..b82d78b927e 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -33,8 +33,8 @@ const UserDef U_default = { .versionfile = BLENDER_FILE_VERSION, .subversionfile = BLENDER_FILE_SUBVERSION, - .flag = (USER_AUTOSAVE | USER_TOOLTIPS | USER_SAVE_PREVIEWS | USER_RELPATHS | - USER_RELEASECONFIRM | USER_SCRIPT_AUTOEXEC_DISABLE | USER_NONEGFRAMES), + .flag = (USER_AUTOSAVE | USER_TOOLTIPS | USER_RELPATHS | USER_RELEASECONFIRM | + USER_SCRIPT_AUTOEXEC_DISABLE | USER_NONEGFRAMES), .dupflag = USER_DUP_MESH | USER_DUP_CURVE | USER_DUP_SURF | USER_DUP_FONT | USER_DUP_MBALL | USER_DUP_LAMP | USER_DUP_ARM | USER_DUP_ACT | USER_DUP_LIGHTPROBE | USER_DUP_GPENCIL, @@ -231,6 +231,7 @@ const UserDef U_default = { .collection_instance_empty_size = 1.0f, .statusbar_flag = STATUSBAR_SHOW_VERSION, + .file_preview_type = USER_FILE_PREVIEW_AUTO, .runtime = { diff --git a/release/scripts/addons b/release/scripts/addons -Subproject 67f1fbca1482d9d9362a4001332e785c3fd5d23 +Subproject 4475cbd11a636382d57571e0f5dfeff1f90bd6b diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib -Subproject ef6ef414d22c2578fad99327743b925ab640a99 +Subproject 788441f2930465bbfba8f0797b12dcef1d46694 diff --git a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py index 14fc81821c4..0293d7143ec 100644 --- a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py +++ b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py @@ -48,6 +48,7 @@ class SpellChecker: "equi", # equi-angular, etc. "fader", "globbing", + "gridded", "haptics", "hasn", # hasn't "hetero", @@ -64,12 +65,14 @@ class SpellChecker: "mplayer", "ons", # add-ons "pong", # ping pong + "resumable", "scalable", "shadeless", "shouldn", # shouldn't "smoothen", "spacings", "teleport", "teleporting", + "tangency", "vertices", "wasn", # wasn't @@ -173,11 +176,13 @@ class SpellChecker: "precalculate", "precomputing", "prefetch", + "preload", "premultiply", "premultiplied", "prepass", "prepend", - "preprocess", "preprocessing", "preprocessor", + "preprocess", "preprocessing", "preprocessor", "preprocessed", "preseek", + "preselect", "preselected", "promillage", "pushdown", "raytree", @@ -185,8 +190,10 @@ class SpellChecker: "realtime", "reinject", "reinjected", "rekey", + "relink", "remesh", "reprojection", "reproject", "reprojecting", + "resample", "resize", "restpose", "resync", "resynced", @@ -226,6 +233,7 @@ class SpellChecker: "todo", "tradeoff", "un", + "unadjust", "unadjusted", "unassociate", "unassociated", "unbake", "uncheck", @@ -388,6 +396,7 @@ class SpellChecker: "boid", "boids", "ceil", "compressibility", + "coplanar", "curvilinear", "equiangular", "equisolid", @@ -396,6 +405,7 @@ class SpellChecker: "gettext", "hashable", "hotspot", + "hydrostatic", "interocular", "intrinsics", "irradiance", @@ -495,6 +505,7 @@ class SpellChecker: "perlin", "phong", "pinlight", + "posterize", "qi", "radiosity", "raycast", "raycasting", diff --git a/release/scripts/modules/bl_keymap_utils/io.py b/release/scripts/modules/bl_keymap_utils/io.py index 96832cbd9c7..d8b68822feb 100644 --- a/release/scripts/modules/bl_keymap_utils/io.py +++ b/release/scripts/modules/bl_keymap_utils/io.py @@ -63,16 +63,11 @@ def kmi_args_as_data(kmi): if kmi.any: s.append("\"any\": True") else: - if kmi.shift: - s.append("\"shift\": True") - if kmi.ctrl: - s.append("\"ctrl\": True") - if kmi.alt: - s.append("\"alt\": True") - if kmi.oskey: - s.append("\"oskey\": True") - if kmi.key_modifier and kmi.key_modifier != 'NONE': - s.append(f"\"key_modifier\": '{kmi.key_modifier}'") + for attr in ("shift", "ctrl", "alt", "oskey"): + if mod := getattr(kmi, attr): + s.append(f"\"{attr:s}\": " + ("-1" if mod == -1 else "True")) + if (mod := kmi.key_modifier) and (mod != 'NONE'): + s.append(f"\"key_modifier\": '{mod:s}'") if kmi.repeat: if ( diff --git a/release/scripts/modules/rna_keymap_ui.py b/release/scripts/modules/rna_keymap_ui.py index b42539ac44a..08035d25481 100644 --- a/release/scripts/modules/rna_keymap_ui.py +++ b/release/scripts/modules/rna_keymap_ui.py @@ -199,10 +199,12 @@ def draw_kmi(display_keymaps, kc, km, kmi, layout, level): subrow = sub.row() subrow.scale_x = 0.75 subrow.prop(kmi, "any", toggle=True) - subrow.prop(kmi, "shift", toggle=True) - subrow.prop(kmi, "ctrl", toggle=True) - subrow.prop(kmi, "alt", toggle=True) - subrow.prop(kmi, "oskey", text="Cmd", toggle=True) + # Use `*_ui` properties as integers aren't practical. + subrow.prop(kmi, "shift_ui", toggle=True) + subrow.prop(kmi, "ctrl_ui", toggle=True) + subrow.prop(kmi, "alt_ui", toggle=True) + subrow.prop(kmi, "oskey_ui", text="Cmd", toggle=True) + subrow.prop(kmi, "key_modifier", text="", event=True) # Operator properties diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py index f9756811bde..0e3cb7e3cab 100644 --- a/release/scripts/modules/rna_manual_reference.py +++ b/release/scripts/modules/rna_manual_reference.py @@ -39,6 +39,7 @@ if LANG is not None: url_manual_prefix = url_manual_prefix.replace("manual/en", "manual/" + LANG) url_manual_mapping = ( + ("bpy.types.cyclesworldsettings.sample_mbpy.types.cyclesworldsettings.sample_map_resolutionbpy.types.cyclesworldsettings.sample_map_resolutionap_resolution*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-sample-mbpy-types-cyclesworldsettings-sample-map-resolutionbpy-types-cyclesworldsettings-sample-map-resolutionap-resolution"), ("bpy.types.movietrackingsettings.refine_intrinsics_tangential_distortion*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-tangential-distortion"), ("bpy.types.movietrackingsettings.refine_intrinsics_radial_distortion*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-radial-distortion"), ("bpy.types.fluiddomainsettings.sndparticle_potential_max_trappedair*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-trappedair"), @@ -46,18 +47,24 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.sndparticle_potential_max_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-wavecrest"), ("bpy.types.fluiddomainsettings.sndparticle_potential_min_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-wavecrest"), ("bpy.types.movietrackingsettings.refine_intrinsics_principal_point*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-principal-point"), + ("bpy.types.cyclesobjectsettings.shadow_terminator_geometry_offset*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-shadow-terminator-geometry-offset"), ("bpy.types.cyclesrenderlayersettings.denoising_optix_input_passes*", "render/layers/denoising.html#bpy-types-cyclesrenderlayersettings-denoising-optix-input-passes"), + ("bpy.types.sequencertoolsettings.use_snap_current_frame_to_strips*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-use-snap-current-frame-to-strips"), ("bpy.types.fluiddomainsettings.sndparticle_potential_max_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-energy"), ("bpy.types.fluiddomainsettings.sndparticle_potential_min_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-energy"), ("bpy.types.movietrackingsettings.refine_intrinsics_focal_length*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-focal-length"), ("bpy.types.rigidbodyconstraint.rigidbodyconstraint.use_breaking*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-rigidbodyconstraint-use-breaking"), ("bpy.types.cyclesrendersettings.preview_denoising_input_passes*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoising-input-passes"), + ("bpy.types.cyclesrendersettings.preview_denoising_start_sample*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoising-start-sample"), ("bpy.types.fluiddomainsettings.sndparticle_sampling_trappedair*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-sampling-trappedair"), ("bpy.types.fluiddomainsettings.sndparticle_sampling_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-sampling-wavecrest"), ("bpy.types.rigidbodyconstraint.use_override_solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-override-solver-iterations"), ("bpy.types.toolsettings.use_transform_correct_face_attributes*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-face-attributes"), + ("bpy.types.rendersettings.use_sequencer_override_scene_strip*", "video_editing/preview/sidebar.html#bpy-types-rendersettings-use-sequencer-override-scene-strip"), ("bpy.types.toolsettings.use_transform_correct_keep_connected*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-keep-connected"), ("bpy.types.fluiddomainsettings.sndparticle_potential_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-radius"), + ("bpy.types.cyclesrendersettings.film_transparent_roughness*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-roughness"), + ("bpy.types.cyclesrendersettings.sample_all_lights_indirect*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-sample-all-lights-indirect"), ("bpy.types.fluiddomainsettings.openvdb_cache_compress_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-openvdb-cache-compress-type"), ("bpy.types.fluiddomainsettings.sndparticle_bubble_buoyancy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-buoyancy"), ("bpy.types.fluiddomainsettings.sndparticle_combined_export*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-combined-export"), @@ -65,15 +72,27 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.vector_scale_with_magnitude*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-scale-with-magnitude"), ("bpy.types.movietrackingstabilization.use_2d_stabilization*", "movie_clip/tracking/clip/sidebar/stabilization/panel.html#bpy-types-movietrackingstabilization-use-2d-stabilization"), ("bpy.types.spacespreadsheet.display_context_path_collapsed*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-display-context-path-collapsed"), + ("bpy.types.toolsettings.annotation_stroke_placement_view2d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view2d"), + ("bpy.types.toolsettings.annotation_stroke_placement_view3d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view3d"), ("bpy.types.fluiddomainsettings.use_collision_border_front*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-front"), ("bpy.types.fluiddomainsettings.use_collision_border_right*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-right"), + ("bpy.types.cyclesmaterialsettings.use_transparent_shadow*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-use-transparent-shadow"), + ("bpy.types.cyclesobjectsettings.shadow_terminator_offset*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-shadow-terminator-offset"), ("bpy.types.cyclesobjectsettings.use_adaptive_subdivision*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-use-adaptive-subdivision"), + ("bpy.types.cyclesrendersettings.debug_use_spatial_splits*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-use-spatial-splits"), + ("bpy.types.cyclesrendersettings.light_sampling_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-light-sampling-threshold"), + ("bpy.types.cyclesrendersettings.preview_start_resolution*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-preview-start-resolution"), + ("bpy.types.cyclesrendersettings.rolling_shutter_duration*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-rolling-shutter-duration"), + ("bpy.types.cyclesrendersettings.sample_all_lights_direct*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-sample-all-lights-direct"), + ("bpy.types.cyclesrendersettings.volume_preview_step_rate*", "render/cycles/render_settings/volumes.html#bpy-types-cyclesrendersettings-volume-preview-step-rate"), ("bpy.types.fluiddomainsettings.sndparticle_update_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-update-radius"), ("bpy.types.fluiddomainsettings.use_collision_border_back*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-back"), ("bpy.types.fluiddomainsettings.use_collision_border_left*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-left"), ("bpy.types.rendersettings_simplify_gpencil_view_modifier*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-modifier"), ("bpy.types.brushgpencilsettings.use_settings_stabilizer*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-stabilizer"), ("bpy.types.colormanagedsequencercolorspacesettings.name*", "render/color_management.html#bpy-types-colormanagedsequencercolorspacesettings-name"), + ("bpy.types.cyclesrendersettings.max_transparent_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-max-transparent-bounces"), + ("bpy.types.cyclesrendersettings.min_transparent_bounces*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-min-transparent-bounces"), ("bpy.types.fluiddomainsettings.use_collision_border_top*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-top"), ("bpy.types.gpencilsculptsettings.use_multiframe_falloff*", "grease_pencil/multiframe.html#bpy-types-gpencilsculptsettings-use-multiframe-falloff"), ("bpy.types.movietrackingsettings.use_keyframe_selection*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-keyframe-selection"), @@ -81,44 +100,71 @@ url_manual_mapping = ( ("bpy.types.spaceoutliner.use_filter_lib_override_system*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-lib-override-system"), ("bpy.types.toolsettings.use_transform_pivot_point_align*", "scene_layout/object/tools/tool_settings.html#bpy-types-toolsettings-use-transform-pivot-point-align"), ("bpy.types.brush.show_multiplane_scrape_planes_preview*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-show-multiplane-scrape-planes-preview"), + ("bpy.types.cyclesmaterialsettings.volume_interpolation*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-interpolation"), + ("bpy.types.cyclesrendersettings.debug_optix_curves_api*", "render/cycles/render_settings/debug.html#bpy-types-cyclesrendersettings-debug-optix-curves-api"), + ("bpy.types.cyclesrendersettings.film_transparent_glass*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-glass"), ("bpy.types.cyclesrendersettings.offscreen_dicing_scale*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-offscreen-dicing-scale"), + ("bpy.types.cyclesrendersettings.use_progressive_refine*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-use-progressive-refine"), ("bpy.types.fluiddomainsettings.sndparticle_bubble_drag*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-drag"), - ("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/backbone_stretcher.html#bpy-types-linestylegeometrymodifier-backbonestretcher"), - ("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/sinus_displacement.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"), + ("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/view_layer/line_style/modifiers/geometry/backbone_stretcher.html#bpy-types-linestylegeometrymodifier-backbonestretcher"), + ("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/view_layer/line_style/modifiers/geometry/sinus_displacement.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"), + ("bpy.types.sequencertoolsettings.snap_to_current_frame*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-to-current-frame"), ("bpy.types.colormanageddisplaysettings.display_device*", "render/color_management.html#bpy-types-colormanageddisplaysettings-display-device"), ("bpy.types.colormanagedviewsettings.use_curve_mapping*", "render/color_management.html#bpy-types-colormanagedviewsettings-use-curve-mapping"), + ("bpy.types.cyclesrendersettings.sample_clamp_indirect*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-sample-clamp-indirect"), + ("bpy.types.cyclesrendersettings.use_adaptive_sampling*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-adaptive-sampling"), + ("bpy.types.cyclesrendersettings.use_preview_denoising*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-preview-denoising"), ("bpy.types.fluiddomainsettings.color_ramp_field_scale*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field-scale"), ("bpy.types.fluiddomainsettings.use_adaptive_timesteps*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-adaptive-timesteps"), ("bpy.types.fluiddomainsettings.use_dissolve_smoke_log*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-dissolve-smoke-log"), + ("bpy.types.freestylelineset.select_suggestive_contour*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-suggestive-contour"), + ("bpy.types.gpencillayer.annotation_onion_before_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-color"), + ("bpy.types.gpencillayer.annotation_onion_before_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-range"), + ("bpy.types.gpencillayer.use_annotation_onion_skinning*", "interface/annotate_tool.html#bpy-types-gpencillayer-use-annotation-onion-skinning"), ("bpy.types.greasepencil.use_adaptive_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-adaptive-curve-resolution"), - ("bpy.types.linestylegeometrymodifier_polygonalization*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/polygonization.html#bpy-types-linestylegeometrymodifier-polygonalization"), + ("bpy.types.linestylegeometrymodifier_polygonalization*", "render/freestyle/view_layer/line_style/modifiers/geometry/polygonization.html#bpy-types-linestylegeometrymodifier-polygonalization"), ("bpy.types.toolsettings.use_gpencil_automerge_strokes*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-automerge-strokes"), ("bpy.types.toolsettings.use_proportional_edit_objects*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-edit-objects"), ("bpy.ops.view3d.edit_mesh_extrude_move_shrink_fatten*", "modeling/meshes/editing/face/extrude_faces_normal.html#bpy-ops-view3d-edit-mesh-extrude-move-shrink-fatten"), + ("bpy.types.cyclesmaterialsettings.homogeneous_volume*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-homogeneous-volume"), + ("bpy.types.cyclesrendersettings.adaptive_min_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-adaptive-min-samples"), + ("bpy.types.cyclesrendersettings.debug_bvh_time_steps*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-bvh-time-steps"), ("bpy.types.cyclesrendersettings.distance_cull_margin*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-distance-cull-margin"), + ("bpy.types.cyclesrendersettings.motion_blur_position*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-motion-blur-position"), + ("bpy.types.cyclesrendersettings.rolling_shutter_type*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-rolling-shutter-type"), + ("bpy.types.cyclesrendersettings.transmission_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-transmission-bounces"), + ("bpy.types.cyclesrendersettings.transmission_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-transmission-samples"), ("bpy.types.fluiddomainsettings.display_interpolation*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-display-interpolation"), ("bpy.types.fluiddomainsettings.gridlines_cell_filter*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-cell-filter"), ("bpy.types.fluiddomainsettings.gridlines_color_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-color-field"), ("bpy.types.fluiddomainsettings.gridlines_lower_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-lower-bound"), ("bpy.types.fluiddomainsettings.gridlines_range_color*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-range-color"), ("bpy.types.fluiddomainsettings.gridlines_upper_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-upper-bound"), + ("bpy.types.freestylelineset.select_material_boundary*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-material-boundary"), + ("bpy.types.gpencillayer.annotation_onion_after_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-color"), + ("bpy.types.gpencillayer.annotation_onion_after_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-range"), ("bpy.types.materialgpencilstyle.use_fill_texture_mix*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-texture-mix"), ("bpy.types.rendersettings_simplify_gpencil_shader_fx*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-shader-fx"), ("bpy.types.rendersettings_simplify_gpencil_view_fill*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-fill"), - ("bpy.types.spacesequenceeditor.waveform_display_type*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-waveform-display-type"), + ("bpy.types.sequencertoolsettings.snap_to_hold_offset*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-to-hold-offset"), + ("bpy.types.spacesequenceeditor.waveform_display_type*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-waveform-display-type"), ("bpy.types.toolsettings.use_mesh_automerge_and_split*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge-and-split"), ("bpy.types.brush.cloth_constraint_softbody_strength*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-constraint-softbody-strength"), ("bpy.types.brush.elastic_deform_volume_preservation*", "sculpt_paint/sculpting/tools/elastic_deform.html#bpy-types-brush-elastic-deform-volume-preservation"), ("bpy.types.brushgpencilsettings.fill_simplify_level*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-simplify-level"), ("bpy.types.brushgpencilsettings.use_jitter_pressure*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-jitter-pressure"), ("bpy.types.brushgpencilsettings.use_settings_random*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-random"), + ("bpy.types.cyclesrendersettings.preview_dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-preview-dicing-rate"), + ("bpy.types.cyclesrendersettings.sample_clamp_direct*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-sample-clamp-direct"), + ("bpy.types.cyclesworldsettings.volume_interpolation*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-interpolation"), ("bpy.types.fluiddomainsettings.mesh_particle_radius*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-particle-radius"), ("bpy.types.fluiddomainsettings.sndparticle_boundary*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-boundary"), ("bpy.types.fluiddomainsettings.sndparticle_life_max*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-life-max"), ("bpy.types.fluiddomainsettings.sndparticle_life_min*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-life-min"), ("bpy.types.fluiddomainsettings.sys_particle_maximum*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-sys-particle-maximum"), ("bpy.types.fluiddomainsettings.use_bubble_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-use-bubble-particles"), - ("bpy.types.linestylegeometrymodifier_simplification*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/simplification.html#bpy-types-linestylegeometrymodifier-simplification"), + ("bpy.types.freestylelineset.select_external_contour*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-external-contour"), + ("bpy.types.linestylegeometrymodifier_simplification*", "render/freestyle/view_layer/line_style/modifiers/geometry/simplification.html#bpy-types-linestylegeometrymodifier-simplification"), ("bpy.types.materialgpencilstyle.use_overlap_strokes*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-overlap-strokes"), ("bpy.types.spacespreadsheet.geometry_component_type*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-geometry-component-type"), ("bpy.types.toolsettings.use_gpencil_weight_data_add*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-weight-data-add"), @@ -130,7 +176,14 @@ url_manual_mapping = ( ("bpy.types.brushgpencilsettings.use_default_eraser*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-use-default-eraser"), ("bpy.types.colormanagedsequencercolorspacesettings*", "render/color_management.html#bpy-types-colormanagedsequencercolorspacesettings"), ("bpy.types.colormanagedviewsettings.view_transform*", "render/color_management.html#bpy-types-colormanagedviewsettings-view-transform"), + ("bpy.types.cyclesmaterialsettings.volume_step_rate*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-step-rate"), + ("bpy.types.cyclesrendersettings.adaptive_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-adaptive-threshold"), ("bpy.types.cyclesrendersettings.camera_cull_margin*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-camera-cull-margin"), + ("bpy.types.cyclesrendersettings.debug_use_hair_bvh*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-use-hair-bvh"), + ("bpy.types.cyclesrendersettings.mesh_light_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-mesh-light-samples"), + ("bpy.types.cyclesrendersettings.preview_aa_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-aa-samples"), + ("bpy.types.cyclesrendersettings.subsurface_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-subsurface-samples"), + ("bpy.types.cyclesrendersettings.use_square_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-square-samples"), ("bpy.types.fluiddomainsettings.export_manta_script*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-export-manta-script"), ("bpy.types.fluiddomainsettings.fractions_threshold*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-fractions-threshold"), ("bpy.types.fluiddomainsettings.particle_band_width*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-band-width"), @@ -139,15 +192,20 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.use_resumable_cache*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-use-resumable-cache"), ("bpy.types.fluiddomainsettings.use_spray_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-use-spray-particles"), ("bpy.types.fluiddomainsettings.vector_display_type*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-display-type"), + ("bpy.types.freestylelineset.select_by_image_border*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-image-border"), + ("bpy.types.freestylesettings.kr_derivative_epsilon*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-kr-derivative-epsilon"), ("bpy.types.geometrynodecurveprimitivebeziersegment*", "modeling/geometry_nodes/curve_primitives/bezier_segment.html#bpy-types-geometrynodecurveprimitivebeziersegment"), - ("bpy.types.linestylegeometrymodifier_perlinnoise1d*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/perlin_noise_1d.html#bpy-types-linestylegeometrymodifier-perlinnoise1d"), - ("bpy.types.linestylegeometrymodifier_perlinnoise2d*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/perlin_noise_2d.html#bpy-types-linestylegeometrymodifier-perlinnoise2d"), + ("bpy.types.linestylegeometrymodifier_perlinnoise1d*", "render/freestyle/view_layer/line_style/modifiers/geometry/perlin_noise_1d.html#bpy-types-linestylegeometrymodifier-perlinnoise1d"), + ("bpy.types.linestylegeometrymodifier_perlinnoise2d*", "render/freestyle/view_layer/line_style/modifiers/geometry/perlin_noise_2d.html#bpy-types-linestylegeometrymodifier-perlinnoise2d"), ("bpy.types.materialgpencilstyle.use_stroke_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-stroke-holdout"), ("bpy.types.movietrackingsettings.use_tripod_solver*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-tripod-solver"), + ("bpy.types.rendersettings.simplify_child_particles*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-child-particles"), ("bpy.types.rendersettings.use_high_quality_normals*", "render/eevee/render_settings/performance.html#bpy-types-rendersettings-use-high-quality-normals"), + ("bpy.types.sequencertoolsettings.snap_ignore_muted*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-muted"), + ("bpy.types.sequencertoolsettings.snap_ignore_sound*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-sound"), ("bpy.types.spaceoutliner.use_filter_case_sensitive*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-case-sensitive"), ("bpy.types.spaceoutliner.use_filter_object_content*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-content"), - ("bpy.types.spacesequenceeditor.show_strip_duration*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-duration"), + ("bpy.types.spacesequenceeditor.show_strip_duration*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-duration"), ("bpy.types.toolsettings.use_proportional_connected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-connected"), ("bpy.types.toolsettings.use_proportional_projected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-projected"), ("bpy.types.view3doverlay.vertex_paint_mode_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-vertex-paint-mode-opacity"), @@ -156,8 +214,18 @@ url_manual_mapping = ( ("bpy.ops.gpencil.vertex_color_brightness_contrast*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-brightness-contrast"), ("bpy.ops.view3d.edit_mesh_extrude_individual_move*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-view3d-edit-mesh-extrude-individual-move"), ("bpy.ops.view3d.edit_mesh_extrude_manifold_normal*", "modeling/meshes/tools/extrude_manifold.html#bpy-ops-view3d-edit-mesh-extrude-manifold-normal"), + ("bpy.types.cyclescurverendersettings.subdivisions*", "render/cycles/render_settings/hair.html#bpy-types-cyclescurverendersettings-subdivisions"), + ("bpy.types.cyclesmaterialsettings.sample_as_light*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-sample-as-light"), + ("bpy.types.cyclesmaterialsettings.volume_sampling*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-sampling"), + ("bpy.types.cyclesobjectsettings.use_deform_motion*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-deform-motion"), + ("bpy.types.cyclesobjectsettings.use_distance_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-distance-cull"), ("bpy.types.cyclesrendersettings.ao_bounces_render*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces-render"), + ("bpy.types.cyclesrendersettings.min_light_bounces*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-min-light-bounces"), + ("bpy.types.cyclesrendersettings.pixel_filter_type*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-pixel-filter-type"), + ("bpy.types.cyclesrendersettings.use_animated_seed*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-animated-seed"), ("bpy.types.cyclesrendersettings.use_distance_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-distance-cull"), + ("bpy.types.cyclesrendersettings.use_layer_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-layer-samples"), + ("bpy.types.cyclesworldsettings.homogeneous_volume*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-homogeneous-volume"), ("bpy.types.fluiddomainsettings.cache_frame_offset*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-offset"), ("bpy.types.fluiddomainsettings.delete_in_obstacle*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-delete-in-obstacle"), ("bpy.types.fluiddomainsettings.fractions_distance*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-fractions-distance"), @@ -171,15 +239,16 @@ url_manual_mapping = ( ("bpy.types.fluideffectorsettings.surface_distance*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-surface-distance"), ("bpy.types.fluidflowsettings.density_vertex_group*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-density-vertex-group"), ("bpy.types.fluidflowsettings.use_initial_velocity*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-initial-velocity"), - ("bpy.types.linestylegeometrymodifier_guidinglines*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/guiding_lines.html#bpy-types-linestylegeometrymodifier-guidinglines"), - ("bpy.types.linestylegeometrymodifier_spatialnoise*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/spatial_noise.html#bpy-types-linestylegeometrymodifier-spatialnoise"), - ("bpy.types.linestylethicknessmodifier_calligraphy*", "render/freestyle/parameter_editor/line_style/modifiers/thickness/calligraphy.html#bpy-types-linestylethicknessmodifier-calligraphy"), + ("bpy.types.linestylegeometrymodifier_guidinglines*", "render/freestyle/view_layer/line_style/modifiers/geometry/guiding_lines.html#bpy-types-linestylegeometrymodifier-guidinglines"), + ("bpy.types.linestylegeometrymodifier_spatialnoise*", "render/freestyle/view_layer/line_style/modifiers/geometry/spatial_noise.html#bpy-types-linestylegeometrymodifier-spatialnoise"), + ("bpy.types.linestylethicknessmodifier_calligraphy*", "render/freestyle/view_layer/line_style/modifiers/thickness/calligraphy.html#bpy-types-linestylethicknessmodifier-calligraphy"), ("bpy.types.rendersettings_simplify_gpencil_onplay*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-onplay"), ("bpy.types.rigidbodyconstraint.breaking_threshold*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-breaking-threshold"), ("bpy.types.spaceclipeditor.use_manual_calibration*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-manual-calibration"), ("bpy.types.spacedopesheeteditor.show_pose_markers*", "animation/markers.html#bpy-types-spacedopesheeteditor-show-pose-markers"), ("bpy.types.spaceoutliner.use_filter_object_camera*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-camera"), ("bpy.types.spaceoutliner.use_filter_object_others*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-others"), + ("bpy.types.spacesequenceeditor.show_strip_overlay*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-overlay"), ("bpy.types.toolsettings.proportional_edit_falloff*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-proportional-edit-falloff"), ("bpy.types.toolsettings.use_edge_path_live_unwrap*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-edge-path-live-unwrap"), ("bpy.types.toolsettings.use_gpencil_draw_additive*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-draw-additive"), @@ -188,6 +257,10 @@ url_manual_mapping = ( ("bpy.types.view3doverlay.sculpt_mode_mask_opacity*", "sculpt_paint/sculpting/editing/mask.html#bpy-types-view3doverlay-sculpt-mode-mask-opacity"), ("bpy.ops.outliner.collection_indirect_only_clear*", "render/layers/introduction.html#bpy-ops-outliner-collection-indirect-only-clear"), ("bpy.types.cyclesrendersettings.max_subdivisions*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-max-subdivisions"), + ("bpy.types.cyclesrendersettings.preview_denoiser*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoiser"), + ("bpy.types.cyclesrendersettings.sampling_pattern*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-sampling-pattern"), + ("bpy.types.cyclesrendersettings.volume_max_steps*", "render/cycles/render_settings/volumes.html#bpy-types-cyclesrendersettings-volume-max-steps"), + ("bpy.types.cyclesrendersettings.volume_step_rate*", "render/cycles/render_settings/volumes.html#bpy-types-cyclesrendersettings-volume-step-rate"), ("bpy.types.editbone.bbone_handle_use_scale_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-scale-start"), ("bpy.types.fluiddomainsettings.cache_data_format*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-data-format"), ("bpy.types.fluiddomainsettings.cache_frame_start*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-start"), @@ -202,10 +275,13 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.vector_show_mac_y*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-show-mac-y"), ("bpy.types.fluiddomainsettings.vector_show_mac_z*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-show-mac-z"), ("bpy.types.fluideffectorsettings.velocity_factor*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-velocity-factor"), - ("bpy.types.linestyle*modifier_distancefromcamera*", "render/freestyle/parameter_editor/line_style/modifiers/color/distance_from_camera.html#bpy-types-linestyle-modifier-distancefromcamera"), - ("bpy.types.linestyle*modifier_distancefromobject*", "render/freestyle/parameter_editor/line_style/modifiers/color/distance_from_object.html#bpy-types-linestyle-modifier-distancefromobject"), - ("bpy.types.linestylegeometrymodifier_2dtransform*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/2d_transform.html#bpy-types-linestylegeometrymodifier-2dtransform"), - ("bpy.types.linestylegeometrymodifier_beziercurve*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/bezier_curve.html#bpy-types-linestylegeometrymodifier-beziercurve"), + ("bpy.types.freestylelineset.select_by_collection*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-collection"), + ("bpy.types.freestylelineset.select_by_edge_types*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-edge-types"), + ("bpy.types.freestylelineset.select_by_face_marks*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-face-marks"), + ("bpy.types.linestyle*modifier_distancefromcamera*", "render/freestyle/view_layer/line_style/modifiers/color/distance_from_camera.html#bpy-types-linestyle-modifier-distancefromcamera"), + ("bpy.types.linestyle*modifier_distancefromobject*", "render/freestyle/view_layer/line_style/modifiers/color/distance_from_object.html#bpy-types-linestyle-modifier-distancefromobject"), + ("bpy.types.linestylegeometrymodifier_2dtransform*", "render/freestyle/view_layer/line_style/modifiers/geometry/2d_transform.html#bpy-types-linestylegeometrymodifier-2dtransform"), + ("bpy.types.linestylegeometrymodifier_beziercurve*", "render/freestyle/view_layer/line_style/modifiers/geometry/bezier_curve.html#bpy-types-linestylegeometrymodifier-beziercurve"), ("bpy.types.materialgpencilstyle.use_fill_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-holdout"), ("bpy.types.particlesettings.use_parent_particles*", "physics/particles/emitter/render.html#bpy-types-particlesettings-use-parent-particles"), ("bpy.types.rigidbodyconstraint.solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-solver-iterations"), @@ -214,8 +290,8 @@ url_manual_mapping = ( ("bpy.types.spaceoutliner.use_filter_object_empty*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-empty"), ("bpy.types.spaceoutliner.use_filter_object_light*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-light"), ("bpy.types.spacesequenceeditor.proxy_render_size*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-proxy-render-size"), - ("bpy.types.spacesequenceeditor.show_strip_offset*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-offset"), - ("bpy.types.spacesequenceeditor.show_strip_source*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-source"), + ("bpy.types.spacesequenceeditor.show_strip_offset*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-offset"), + ("bpy.types.spacesequenceeditor.show_strip_source*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-source"), ("bpy.types.spacespreadsheetrowfilter.column_name*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-column-name"), ("bpy.types.toolsettings.gpencil_stroke_placement*", "grease_pencil/modes/draw/stroke_placement.html#bpy-types-toolsettings-gpencil-stroke-placement"), ("bpy.types.toolsettings.use_keyframe_cycle_aware*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-cycle-aware"), @@ -223,16 +299,28 @@ url_manual_mapping = ( ("bpy.types.viewlayer.use_pass_cryptomatte_object*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-object"), ("bpy.ops.armature.rigify_apply_selection_colors*", "addons/rigging/rigify/metarigs.html#bpy-ops-armature-rigify-apply-selection-colors"), ("bpy.types.brushgpencilsettings.fill_layer_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-layer-mode"), + ("bpy.types.cyclesobjectsettings.use_camera_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-camera-cull"), + ("bpy.types.cyclesobjectsettings.use_motion_blur*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-motion-blur"), + ("bpy.types.cyclesrendersettings.diffuse_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-diffuse-bounces"), + ("bpy.types.cyclesrendersettings.diffuse_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-diffuse-samples"), + ("bpy.types.cyclesrendersettings.preview_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-samples"), ("bpy.types.cyclesrendersettings.use_camera_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-camera-cull"), + ("bpy.types.cyclesworldsettings.volume_step_size*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-step-size"), ("bpy.types.editbone.bbone_handle_use_ease_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-ease-start"), ("bpy.types.fluiddomainsettings.color_ramp_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field"), ("bpy.types.fluiddomainsettings.guide_vel_factor*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-vel-factor"), ("bpy.types.fluideffectorsettings.use_plane_init*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-use-plane-init"), + ("bpy.types.freestylelineset.collection_negation*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-collection-negation"), + ("bpy.types.freestylelineset.face_mark_condition*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-face-mark-condition"), + ("bpy.types.freestylelineset.select_ridge_valley*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-ridge-valley"), + ("bpy.types.freestylelinestyle.material_boundary*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-material-boundary"), + ("bpy.types.freestylelinestyle.use_split_pattern*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-split-pattern"), + ("bpy.types.freestylesettings.use_view_map_cache*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-view-map-cache"), ("bpy.types.greasepencil.curve_edit_corner_angle*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-curve-edit-corner-angle"), - ("bpy.types.linestylegeometrymodifier_tipremover*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/tip_remover.html#bpy-types-linestylegeometrymodifier-tipremover"), + ("bpy.types.linestylegeometrymodifier_tipremover*", "render/freestyle/view_layer/line_style/modifiers/geometry/tip_remover.html#bpy-types-linestylegeometrymodifier-tipremover"), ("bpy.types.movieclipuser.use_render_undistorted*", "editors/clip/display/clip_display.html#bpy-types-movieclipuser-use-render-undistorted"), ("bpy.types.movietrackingcamera.distortion_model*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-distortion-model"), - ("bpy.types.rendersettings.resolution_percentage*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-percentage"), + ("bpy.types.rendersettings.resolution_percentage*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-percentage"), ("bpy.types.rendersettings_simplify_gpencil_tint*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-tint"), ("bpy.types.spaceoutliner.use_filter_object_mesh*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-mesh"), ("bpy.types.spaceoutliner.use_filter_view_layers*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-view-layers"), @@ -241,6 +329,7 @@ url_manual_mapping = ( ("bpy.types.toolsettings.use_snap_align_rotation*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-align-rotation"), ("bpy.types.viewlayer.use_pass_cryptomatte_asset*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-asset"), ("bpy.ops.outliner.collection_indirect_only_set*", "render/layers/introduction.html#bpy-ops-outliner-collection-indirect-only-set"), + ("bpy.ops.scene.freestyle_geometry_modifier_add*", "render/freestyle/view_layer/line_style/geometry.html#bpy-ops-scene-freestyle-geometry-modifier-add"), ("bpy.ops.sequencer.deinterlace_selected_movies*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-deinterlace-selected-movies"), ("bpy.types.bakesettings.use_selected_to_active*", "render/cycles/baking.html#bpy-types-bakesettings-use-selected-to-active"), ("bpy.types.brush.surface_smooth_current_vertex*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-current-vertex"), @@ -250,6 +339,13 @@ url_manual_mapping = ( ("bpy.types.brushgpencilsettings.fill_threshold*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-threshold"), ("bpy.types.clothsettings.vertex_group_pressure*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-vertex-group-pressure"), ("bpy.types.cyclesmaterialsettings.displacement*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-displacement"), + ("bpy.types.cyclesrendersettings.debug_bvh_type*", "render/cycles/render_settings/debug.html#bpy-types-cyclesrendersettings-debug-bvh-type"), + ("bpy.types.cyclesrendersettings.glossy_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-glossy-bounces"), + ("bpy.types.cyclesrendersettings.glossy_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-glossy-samples"), + ("bpy.types.cyclesrendersettings.volume_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-volume-bounces"), + ("bpy.types.cyclesrendersettings.volume_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-volume-samples"), + ("bpy.types.cyclesworldsettings.sampling_method*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-sampling-method"), + ("bpy.types.cyclesworldsettings.volume_sampling*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-sampling"), ("bpy.types.editbone.bbone_handle_use_scale_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-scale-end"), ("bpy.types.fluiddomainsettings.adapt_threshold*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-adapt-threshold"), ("bpy.types.fluiddomainsettings.cache_directory*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-directory"), @@ -264,9 +360,13 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.viscosity_value*", "physics/fluid/type/domain/liquid/viscosity.html#bpy-types-fluiddomainsettings-viscosity-value"), ("bpy.types.fluideffectorsettings.effector_type*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-effector-type"), ("bpy.types.fluidflowsettings.use_particle_size*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-particle-size"), - ("bpy.types.linestylegeometrymodifier_blueprint*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/blueprint.html#bpy-types-linestylegeometrymodifier-blueprint"), + ("bpy.types.freestylelineset.face_mark_negation*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-face-mark-negation"), + ("bpy.types.freestylelinestyle.integration_type*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-integration-type"), + ("bpy.types.freestylelinestyle.use_split_length*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-split-length"), + ("bpy.types.linestylegeometrymodifier_blueprint*", "render/freestyle/view_layer/line_style/modifiers/geometry/blueprint.html#bpy-types-linestylegeometrymodifier-blueprint"), ("bpy.types.materialgpencilstyle.alignment_mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-alignment-mode"), ("bpy.types.particlesettings.use_modifier_stack*", "physics/particles/emitter/emission.html#bpy-types-particlesettings-use-modifier-stack"), + ("bpy.types.rendersettings.sequencer_gl_preview*", "video_editing/preview/sidebar.html#bpy-types-rendersettings-sequencer-gl-preview"), ("bpy.types.rendersettings.simplify_subdivision*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-subdivision"), ("bpy.types.spacegrapheditor.show_extrapolation*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-show-extrapolation"), ("bpy.types.spaceoutliner.use_filter_collection*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-collection"), @@ -274,12 +374,13 @@ url_manual_mapping = ( ("bpy.types.spacesequenceeditor.show_annotation*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-annotation"), ("bpy.types.spacesequenceeditor.show_region_hud*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-region-hud"), ("bpy.types.spacesequenceeditor.show_safe_areas*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-safe-areas"), - ("bpy.types.spacesequenceeditor.show_strip_name*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-name"), + ("bpy.types.spacesequenceeditor.show_strip_name*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-name"), ("bpy.types.spacespreadsheet.show_only_selected*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-show-only-selected"), ("bpy.types.spacespreadsheetrowfilter.operation*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-operation"), ("bpy.types.spacespreadsheetrowfilter.threshold*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-threshold"), ("bpy.types.toolsettings.use_snap_grid_absolute*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-grid-absolute"), ("bpy.types.view3doverlay.show_face_orientation*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-face-orientation"), + ("bpy.types.worldlighting.use_ambient_occlusion*", "render/cycles/world_settings.html#bpy-types-worldlighting-use-ambient-occlusion"), ("bpy.ops.object.blenderkit_material_thumbnail*", "addons/3d_view/blenderkit.html#bpy-ops-object-blenderkit-material-thumbnail"), ("bpy.ops.object.multires_higher_levels_delete*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-higher-levels-delete"), ("bpy.ops.object.vertex_group_copy_to_selected*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-copy-to-selected"), @@ -287,8 +388,11 @@ url_manual_mapping = ( ("bpy.ops.view3d.edit_mesh_extrude_move_normal*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-view3d-edit-mesh-extrude-move-normal"), ("bpy.types.bakesettings.use_pass_transmission*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-transmission"), ("bpy.types.clothsettings.internal_compression*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-compression"), + ("bpy.types.cyclescamerasettings.panorama_type*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-panorama-type"), ("bpy.types.cyclesrendersettings.dicing_camera*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-camera"), + ("bpy.types.cyclesrendersettings.film_exposure*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-exposure"), ("bpy.types.cyclesrendersettings.texture_limit*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-texture-limit"), + ("bpy.types.cyclesrendersettings.use_denoising*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-denoising"), ("bpy.types.editbone.bbone_custom_handle_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-start"), ("bpy.types.editbone.bbone_handle_use_ease_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-ease-end"), ("bpy.types.fluiddomainsettings.additional_res*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-additional-res"), @@ -306,13 +410,21 @@ url_manual_mapping = ( ("bpy.types.fluideffectorsettings.use_effector*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-use-effector"), ("bpy.types.fluidflowsettings.surface_distance*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-surface-distance"), ("bpy.types.fluidflowsettings.texture_map_type*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-texture-map-type"), + ("bpy.types.freestylelineset.select_silhouette*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-silhouette"), + ("bpy.types.freestylelinestyle.texture_spacing*", "render/freestyle/view_layer/line_style/texture.html#bpy-types-freestylelinestyle-texture-spacing"), + ("bpy.types.freestylelinestyle.use_chain_count*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-chain-count"), + ("bpy.types.freestylelinestyle.use_dashed_line*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-dashed-line"), + ("bpy.types.freestylelinestyle.use_same_object*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-same-object"), ("bpy.types.geometrynodeattributesampletexture*", "modeling/geometry_nodes/attribute/attribute_sample_texture.html#bpy-types-geometrynodeattributesampletexture"), ("bpy.types.gpencilsculptguide.reference_point*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-reference-point"), ("bpy.types.greasepencil.edit_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-edit-curve-resolution"), - ("bpy.types.linestylegeometrymodifier_2doffset*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/2d_offset.html#bpy-types-linestylegeometrymodifier-2doffset"), - ("bpy.types.linestylegeometrymodifier_sampling*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/sampling.html#bpy-types-linestylegeometrymodifier-sampling"), + ("bpy.types.linestylegeometrymodifier_2doffset*", "render/freestyle/view_layer/line_style/modifiers/geometry/2d_offset.html#bpy-types-linestylegeometrymodifier-2doffset"), + ("bpy.types.linestylegeometrymodifier_sampling*", "render/freestyle/view_layer/line_style/modifiers/geometry/sampling.html#bpy-types-linestylegeometrymodifier-sampling"), ("bpy.types.nodesocketinterface*.default_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-default-value"), + ("bpy.types.rendersettings.line_thickness_mode*", "render/freestyle/render.html#bpy-types-rendersettings-line-thickness-mode"), + ("bpy.types.rendersettings.motion_blur_shutter*", "render/cycles/render_settings/motion_blur.html#bpy-types-rendersettings-motion-blur-shutter"), ("bpy.types.rendersettings.use_persistent_data*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-use-persistent-data"), + ("bpy.types.sequencertoolsettings.overlap_mode*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-overlap-mode"), ("bpy.types.spaceclipeditor.show_green_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-green-channel"), ("bpy.types.spaceoutliner.show_restrict_column*", "editors/outliner/interface.html#bpy-types-spaceoutliner-show-restrict-column"), ("bpy.types.spacespreadsheet.object_eval_state*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-object-eval-state"), @@ -322,7 +434,9 @@ url_manual_mapping = ( ("bpy.types.clothsettings.use_pressure_volume*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-use-pressure-volume"), ("bpy.types.clothsettings.vertex_group_intern*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-vertex-group-intern"), ("bpy.types.colormanagedviewsettings.exposure*", "render/color_management.html#bpy-types-colormanagedviewsettings-exposure"), - ("bpy.types.cyclesrendersettings.*dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-rate"), + ("bpy.types.cyclescamerasettings.fisheye_lens*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-fisheye-lens"), + ("bpy.types.cyclesobjectsettings.motion_steps*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-motion-steps"), + ("bpy.types.cyclesrendersettings.filter_width*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-filter-width"), ("bpy.types.fluiddomainsettings.cfl_condition*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-cfl-condition"), ("bpy.types.fluiddomainsettings.show_velocity*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-show-velocity"), ("bpy.types.fluiddomainsettings.timesteps_max*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-timesteps-max"), @@ -333,16 +447,19 @@ url_manual_mapping = ( ("bpy.types.fluidflowsettings.particle_system*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-particle-system"), ("bpy.types.fluidflowsettings.velocity_factor*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-velocity-factor"), ("bpy.types.fluidflowsettings.velocity_normal*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-velocity-normal"), + ("bpy.types.freestylelineset.select_edge_mark*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-edge-mark"), + ("bpy.types.freestylelinestyle.use_length_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-length-max"), + ("bpy.types.freestylelinestyle.use_length_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-length-min"), ("bpy.types.geometrynodealignrotationtovector*", "modeling/geometry_nodes/point/align_rotation_to_vector.html#bpy-types-geometrynodealignrotationtovector"), ("bpy.types.geometrynodeattributevectorrotate*", "modeling/geometry_nodes/attribute/attribute_vector_rotate.html#bpy-types-geometrynodeattributevectorrotate"), ("bpy.types.greasepencil.curve_edit_threshold*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-curve-edit-threshold"), ("bpy.types.materialgpencilstyle.stroke_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-stroke-style"), ("bpy.types.objectlineart.use_crease_override*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-use-crease-override"), ("bpy.types.rendersettings.preview_pixel_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-preview-pixel-size"), - ("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-crop-to-border"), + ("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-crop-to-border"), ("bpy.types.rendersettings.use_file_extension*", "render/output/properties/output.html#bpy-types-rendersettings-use-file-extension"), ("bpy.types.sculpt.constant_detail_resolution*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-constant-detail-resolution"), - ("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/annotation.html#bpy-types-spaceclipeditor-annotation-source"), + ("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/view.html#bpy-types-spaceclipeditor-annotation-source"), ("bpy.types.spaceclipeditor.show_blue_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-blue-channel"), ("bpy.types.spaceoutliner.use_filter_children*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-children"), ("bpy.types.spaceoutliner.use_filter_complete*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-complete"), @@ -357,10 +474,18 @@ url_manual_mapping = ( ("bpy.ops.object.constraint_add_with_targets*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-constraint-add-with-targets"), ("bpy.ops.object.material_slot_remove_unused*", "scene_layout/object/editing/cleanup.html#bpy-ops-object-material-slot-remove-unused"), ("bpy.ops.outliner.collection_disable_render*", "editors/outliner/editing.html#bpy-ops-outliner-collection-disable-render"), + ("bpy.ops.scene.freestyle_alpha_modifier_add*", "render/freestyle/view_layer/line_style/alpha.html#bpy-ops-scene-freestyle-alpha-modifier-add"), + ("bpy.ops.scene.freestyle_color_modifier_add*", "render/freestyle/view_layer/line_style/color.html#bpy-ops-scene-freestyle-color-modifier-add"), ("bpy.types.brush.cloth_simulation_area_type*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-simulation-area-type"), ("bpy.types.brushgpencilsettings.fill_factor*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-factor"), ("bpy.types.curve.bevel_factor_mapping_start*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-mapping-start"), + ("bpy.types.cyclescamerasettings.fisheye_fov*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-fisheye-fov"), + ("bpy.types.cyclesobjectsettings.ao_distance*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-ao-distance"), ("bpy.types.cyclesobjectsettings.dicing_rate*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-dicing-rate"), + ("bpy.types.cyclesrendersettings.blur_glossy*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-blur-glossy"), + ("bpy.types.cyclesrendersettings.dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-rate"), + ("bpy.types.cyclesrendersettings.max_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-max-bounces"), + ("bpy.types.cyclesrendersettings.progressive*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-progressive"), ("bpy.types.cyclesrendersettings.use_fast_gi*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-use-fast-gi"), ("bpy.types.editbone.bbone_custom_handle_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-end"), ("bpy.types.editbone.bbone_handle_type_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-start"), @@ -377,6 +502,10 @@ url_manual_mapping = ( ("bpy.types.fluidflowsettings.use_plane_init*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-plane-init"), ("bpy.types.fluidflowsettings.velocity_coord*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-velocity-coord"), ("bpy.types.fluidflowsettings.volume_density*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-volume-density"), + ("bpy.types.freestylelinestyle.use_angle_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-angle-max"), + ("bpy.types.freestylelinestyle.use_angle_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-angle-min"), + ("bpy.types.freestylesettings.as_render_pass*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-as-render-pass"), + ("bpy.types.freestylesettings.use_smoothness*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-smoothness"), ("bpy.types.geometrynodecurvequadraticbezier*", "modeling/geometry_nodes/curve_primitives/quadratic_bezier.html#bpy-types-geometrynodecurvequadraticbezier"), ("bpy.types.materialgpencilstyle.show_stroke*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-show-stroke"), ("bpy.types.movietrackingcamera.focal_length*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-focal-length"), @@ -388,7 +517,7 @@ url_manual_mapping = ( ("bpy.types.spaceclipeditor.show_red_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-red-channel"), ("bpy.types.spaceclipeditor.use_mute_footage*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-mute-footage"), ("bpy.types.spacesequenceeditor.overlay_type*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-overlay-type"), - ("bpy.types.spacesequenceeditor.show_fcurves*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-fcurves"), + ("bpy.types.spacesequenceeditor.show_fcurves*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-fcurves"), ("bpy.types.spaceuveditor.sticky_select_mode*", "editors/uv/selecting.html#bpy-types-spaceuveditor-sticky-select-mode"), ("bpy.types.spaceview3d.show_object_viewport*", "editors/3dview/display/visibility.html#bpy-types-spaceview3d-show-object-viewport"), ("bpy.types.view3doverlay.show_fade_inactive*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-fade-inactive"), @@ -396,6 +525,7 @@ url_manual_mapping = ( ("bpy.ops.clip.stabilize_2d_rotation_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-rotation-select"), ("bpy.ops.constraint.disable_keep_transform*", "animation/constraints/interface/common.html#bpy-ops-constraint-disable-keep-transform"), ("bpy.ops.gpencil.stroke_reset_vertex_color*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-stroke-reset-vertex-color"), + ("bpy.ops.object.modifier_apply_as_shapekey*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply-as-shapekey"), ("bpy.ops.object.vertex_group_normalize_all*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize-all"), ("bpy.ops.outliner.collection_color_tag_set*", "editors/outliner/editing.html#bpy-ops-outliner-collection-color-tag-set"), ("bpy.ops.outliner.collection_enable_render*", "editors/outliner/editing.html#bpy-ops-outliner-collection-enable-render"), @@ -406,7 +536,13 @@ url_manual_mapping = ( ("bpy.types.brush.disconnected_distance_max*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-disconnected-distance-max"), ("bpy.types.brush.surface_smooth_iterations*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-iterations"), ("bpy.types.brushgpencilsettings.pen_jitter*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-jitter"), + ("bpy.types.cyclescurverendersettings.shape*", "render/cycles/render_settings/hair.html#bpy-types-cyclescurverendersettings-shape"), + ("bpy.types.cyclesrendersettings.aa_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-aa-samples"), ("bpy.types.cyclesrendersettings.ao_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces"), + ("bpy.types.cyclesrendersettings.ao_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-ao-samples"), + ("bpy.types.cyclesrendersettings.tile_order*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-tile-order"), + ("bpy.types.cyclesvisibilitysettings.camera*", "render/cycles/world_settings.html#bpy-types-cyclesvisibilitysettings-camera"), + ("bpy.types.cyclesworldsettings.max_bounces*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-max-bounces"), ("bpy.types.fluiddomainsettings.domain_type*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-domain-type"), ("bpy.types.fluiddomainsettings.flame_smoke*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flame-smoke"), ("bpy.types.fluiddomainsettings.fluid_group*", "physics/fluid/type/domain/collections.html#bpy-types-fluiddomainsettings-fluid-group"), @@ -416,24 +552,29 @@ url_manual_mapping = ( ("bpy.types.fluideffectorsettings.subframes*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-subframes"), ("bpy.types.fluidflowsettings.flow_behavior*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-behavior"), ("bpy.types.fluidflowsettings.noise_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-noise-texture"), + ("bpy.types.freestylelineset.select_contour*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-contour"), + ("bpy.types.freestylelinestyle.split_length*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-split-length"), + ("bpy.types.freestylelinestyle.use_chaining*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-chaining"), + ("bpy.types.freestylesettings.sphere_radius*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-sphere-radius"), ("bpy.types.geometrynodeattributevectormath*", "modeling/geometry_nodes/attribute/attribute_vector_math.html#bpy-types-geometrynodeattributevectormath"), + ("bpy.types.gpencillayer.annotation_opacity*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-opacity"), ("bpy.types.gpencillayer.use_onion_skinning*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-onion-skinning"), ("bpy.types.gpencilsculptguide.use_snapping*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-use-snapping"), ("bpy.types.gpencilsculptsettings.lock_axis*", "grease_pencil/modes/draw/drawing_planes.html#bpy-types-gpencilsculptsettings-lock-axis"), ("bpy.types.imageformatsettings.file_format*", "render/output/properties/output.html#bpy-types-imageformatsettings-file-format"), ("bpy.types.imagepaint.use_backface_culling*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-use-backface-culling"), - ("bpy.types.linestyle*modifier_curvature_3d*", "render/freestyle/parameter_editor/line_style/modifiers/color/curvature_3d.html#bpy-types-linestyle-modifier-curvature-3d"), + ("bpy.types.linestyle*modifier_curvature_3d*", "render/freestyle/view_layer/line_style/modifiers/color/curvature_3d.html#bpy-types-linestyle-modifier-curvature-3d"), ("bpy.types.materialgpencilstyle.fill_color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-fill-color"), ("bpy.types.materialgpencilstyle.fill_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-fill-style"), ("bpy.types.materialgpencilstyle.mix_factor*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mix-factor"), ("bpy.types.materialgpencilstyle.pass_index*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-pass-index"), ("bpy.types.nodesocketinterface.description*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-description"), ("bpy.types.rendersettings.dither_intensity*", "render/output/properties/post_processing.html#bpy-types-rendersettings-dither-intensity"), + ("bpy.types.rendersettings.film_transparent*", "render/cycles/render_settings/film.html#bpy-types-rendersettings-film-transparent"), ("bpy.types.rendersettings.simplify_volumes*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-volumes"), ("bpy.types.rendersettings.use_render_cache*", "render/output/properties/output.html#bpy-types-rendersettings-use-render-cache"), ("bpy.types.rendersettings.use_save_buffers*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-use-save-buffers"), ("bpy.types.rendersettings.use_single_layer*", "render/layers/view_layer.html#bpy-types-rendersettings-use-single-layer"), - ("bpy.types.rendersettings_simplify_gpencil*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil"), ("bpy.types.sceneeevee.use_taa_reprojection*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-use-taa-reprojection"), ("bpy.types.sequenceeditor.use_overlay_lock*", "video_editing/preview/sidebar.html#bpy-types-sequenceeditor-use-overlay-lock"), ("bpy.types.spaceoutliner.use_filter_object*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object"), @@ -443,6 +584,7 @@ url_manual_mapping = ( ("bpy.types.toolsettings.gpencil_selectmode*", "grease_pencil/selecting.html#bpy-types-toolsettings-gpencil-selectmode"), ("bpy.types.toolsettings.use_auto_normalize*", "sculpt_paint/weight_paint/tool_settings/options.html#bpy-types-toolsettings-use-auto-normalize"), ("bpy.types.toolsettings.use_mesh_automerge*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge"), + ("bpy.types.toolsettings.use_snap_sequencer*", "video_editing/sequencer/editing.html#bpy-types-toolsettings-use-snap-sequencer"), ("bpy.types.toolsettings.use_snap_translate*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-translate"), ("bpy.types.toolsettings.use_uv_select_sync*", "editors/uv/selecting.html#bpy-types-toolsettings-use-uv-select-sync"), ("bpy.types.view3doverlay.wireframe_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-wireframe-opacity"), @@ -463,17 +605,22 @@ url_manual_mapping = ( ("bpy.types.colormanagedviewsettings.gamma*", "render/color_management.html#bpy-types-colormanagedviewsettings-gamma"), ("bpy.types.compositornodeplanetrackdeform*", "compositing/types/distort/plane_track_deform.html#bpy-types-compositornodeplanetrackdeform"), ("bpy.types.curve.bevel_factor_mapping_end*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-mapping-end"), + ("bpy.types.cyclescamerasettings.longitude*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-longitude"), ("bpy.types.editbone.bbone_handle_type_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-end"), ("bpy.types.editbone.use_endroll_as_inroll*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-endroll-as-inroll"), ("bpy.types.fluiddomainsettings.cache_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-type"), ("bpy.types.fluiddomainsettings.flip_ratio*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flip-ratio"), ("bpy.types.fluiddomainsettings.guide_beta*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-beta"), ("bpy.types.fluiddomainsettings.mesh_scale*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-scale"), - ("bpy.types.fluiddomainsettings.noise_type*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-noise-type"), ("bpy.types.fluiddomainsettings.slice_axis*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-slice-axis"), ("bpy.types.fluiddomainsettings.time_scale*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-time-scale"), ("bpy.types.fluidflowsettings.texture_size*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-texture-size"), ("bpy.types.fluidflowsettings.use_absolute*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-absolute"), + ("bpy.types.freestylelineset.select_border*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-border"), + ("bpy.types.freestylelineset.select_crease*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-crease"), + ("bpy.types.freestylelinestyle.chain_count*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-chain-count"), + ("bpy.types.freestylelinestyle.use_sorting*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-sorting"), + ("bpy.types.freestylesettings.crease_angle*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-crease-angle"), ("bpy.types.geometrynodeattributecolorramp*", "modeling/geometry_nodes/attribute/attribute_color_ramp.html#bpy-types-geometrynodeattributecolorramp"), ("bpy.types.geometrynodeattributeproximity*", "modeling/geometry_nodes/attribute/attribute_proximity.html#bpy-types-geometrynodeattributeproximity"), ("bpy.types.geometrynodeattributerandomize*", "modeling/geometry_nodes/attribute/attribute_randomize.html#bpy-types-geometrynodeattributerandomize"), @@ -481,9 +628,9 @@ url_manual_mapping = ( ("bpy.types.geometrynodeseparatecomponents*", "modeling/geometry_nodes/geometry/separate_components.html#bpy-types-geometrynodeseparatecomponents"), ("bpy.types.geometrynodesubdivisionsurface*", "modeling/geometry_nodes/mesh/subdivision_surface.html#bpy-types-geometrynodesubdivisionsurface"), ("bpy.types.imageformatsettings.color_mode*", "render/output/properties/output.html#bpy-types-imageformatsettings-color-mode"), - ("bpy.types.linestyle*modifier_alongstroke*", "render/freestyle/parameter_editor/line_style/modifiers/color/along_stroke.html#bpy-types-linestyle-modifier-alongstroke"), - ("bpy.types.linestyle*modifier_creaseangle*", "render/freestyle/parameter_editor/line_style/modifiers/color/crease_angle.html#bpy-types-linestyle-modifier-creaseangle"), - ("bpy.types.linestylecolormodifier_tangent*", "render/freestyle/parameter_editor/line_style/modifiers/color/tangent.html#bpy-types-linestylecolormodifier-tangent"), + ("bpy.types.linestyle*modifier_alongstroke*", "render/freestyle/view_layer/line_style/modifiers/color/along_stroke.html#bpy-types-linestyle-modifier-alongstroke"), + ("bpy.types.linestyle*modifier_creaseangle*", "render/freestyle/view_layer/line_style/modifiers/color/crease_angle.html#bpy-types-linestyle-modifier-creaseangle"), + ("bpy.types.linestylecolormodifier_tangent*", "render/freestyle/view_layer/line_style/modifiers/color/tangent.html#bpy-types-linestylecolormodifier-tangent"), ("bpy.types.materialgpencilstyle.mix_color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mix-color"), ("bpy.types.materialgpencilstyle.show_fill*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-show-fill"), ("bpy.types.mesh.use_mirror_vertex_group_x*", "sculpt_paint/weight_paint/tool_settings/symmetry.html#bpy-types-mesh-use-mirror-vertex-group-x"), @@ -495,6 +642,7 @@ url_manual_mapping = ( ("bpy.types.nodesocketinterface.hide_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-hide-value"), ("bpy.types.objectlineart.crease_threshold*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-crease-threshold"), ("bpy.types.rendersettings.use_compositing*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-compositing"), + ("bpy.types.rendersettings.use_motion_blur*", "render/cycles/render_settings/motion_blur.html#bpy-types-rendersettings-use-motion-blur"), ("bpy.types.rendersettings.use_placeholder*", "render/output/properties/output.html#bpy-types-rendersettings-use-placeholder"), ("bpy.types.shadernodesubsurfacescattering*", "render/shader_nodes/shader/sss.html#bpy-types-shadernodesubsurfacescattering"), ("bpy.types.spaceclipeditor.lock_selection*", "editors/clip/introduction.html#bpy-types-spaceclipeditor-lock-selection"), @@ -524,6 +672,9 @@ url_manual_mapping = ( ("bpy.types.colormanagedviewsettings.look*", "render/color_management.html#bpy-types-colormanagedviewsettings-look"), ("bpy.types.compositornodecolorcorrection*", "compositing/types/color/color_correction.html#bpy-types-compositornodecolorcorrection"), ("bpy.types.compositornodemoviedistortion*", "compositing/types/distort/movie_distortion.html#bpy-types-compositornodemoviedistortion"), + ("bpy.types.cyclescamerasettings.latitude*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-latitude"), + ("bpy.types.cyclesrendersettings.caustics*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-caustics"), + ("bpy.types.cyclesrendersettings.denoiser*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoiser"), ("bpy.types.editbone.use_inherit_rotation*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-inherit-rotation"), ("bpy.types.fluiddomainsettings.use_guide*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-use-guide"), ("bpy.types.fluiddomainsettings.use_noise*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-use-noise"), @@ -534,7 +685,12 @@ url_manual_mapping = ( ("bpy.types.fluidflowsettings.smoke_color*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-smoke-color"), ("bpy.types.fluidflowsettings.temperature*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-temperature"), ("bpy.types.fluidflowsettings.use_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-texture"), - ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"), + ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"), + ("bpy.types.freestylelinestyle.length_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-length-max"), + ("bpy.types.freestylelinestyle.length_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-length-min"), + ("bpy.types.freestylelinestyle.sort_order*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-sort-order"), + ("bpy.types.freestylelinestyle.split_dash*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-split-dash"), + ("bpy.types.freestylesettings.use_culling*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-culling"), ("bpy.types.geometrynodeattributecurvemap*", "modeling/geometry_nodes/attribute/attribute_curve_map.html#bpy-types-geometrynodeattributecurvemap"), ("bpy.types.geometrynodeattributemaprange*", "modeling/geometry_nodes/attribute/attribute_map_range.html#bpy-types-geometrynodeattributemaprange"), ("bpy.types.geometrynodeattributetransfer*", "modeling/geometry_nodes/attribute/attribute_transfer.html#bpy-types-geometrynodeattributetransfer"), @@ -543,13 +699,16 @@ url_manual_mapping = ( ("bpy.types.material.use_sss_translucency*", "render/eevee/materials/settings.html#bpy-types-material-use-sss-translucency"), ("bpy.types.movietrackingcamera.principal*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-principal"), ("bpy.types.object.use_camera_lock_parent*", "scene_layout/object/properties/relations.html#bpy-types-object-use-camera-lock-parent"), - ("bpy.types.rendersettings.pixel_aspect_x*", "render/output/properties/dimensions.html#bpy-types-rendersettings-pixel-aspect-x"), - ("bpy.types.rendersettings.pixel_aspect_y*", "render/output/properties/dimensions.html#bpy-types-rendersettings-pixel-aspect-y"), + ("bpy.types.object.visible_volume_scatter*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-volume-scatter"), + ("bpy.types.rendersettings.line_thickness*", "render/freestyle/render.html#bpy-types-rendersettings-line-thickness"), + ("bpy.types.rendersettings.pixel_aspect_x*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-x"), + ("bpy.types.rendersettings.pixel_aspect_y*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-y"), ("bpy.types.rigidbodyconstraint.use_limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-limit"), ("bpy.types.sceneeevee.taa_render_samples*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-taa-render-samples"), ("bpy.types.spaceoutliner.use_sync_select*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-sync-select"), ("bpy.types.spaceproperties.outliner_sync*", "editors/properties_editor.html#bpy-types-spaceproperties-outliner-sync"), ("bpy.types.spaceproperties.search_filter*", "editors/properties_editor.html#bpy-types-spaceproperties-search-filter"), + ("bpy.types.spacesequenceeditor.view_type*", "editors/video_sequencer/introduction.html#bpy-types-spacesequenceeditor-view-type"), ("bpy.types.spacetexteditor.margin_column*", "editors/text_editor.html#bpy-types-spacetexteditor-margin-column"), ("bpy.types.spacetexteditor.use_find_wrap*", "editors/text_editor.html#bpy-types-spacetexteditor-use-find-wrap"), ("bpy.types.spaceuveditor.pixel_snap_mode*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-pixel-snap-mode"), @@ -578,17 +737,23 @@ url_manual_mapping = ( ("bpy.types.brush.use_pose_lock_rotation*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-use-pose-lock-rotation"), ("bpy.types.compositornodebrightcontrast*", "compositing/types/color/bright_contrast.html#bpy-types-compositornodebrightcontrast"), ("bpy.types.compositornodedoubleedgemask*", "compositing/types/matte/double_edge_mask.html#bpy-types-compositornodedoubleedgemask"), + ("bpy.types.cyclesrendersettings.samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-samples"), ("bpy.types.fluiddomainsettings.clipping*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-clipping"), ("bpy.types.fluiddomainsettings.use_mesh*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-use-mesh"), + ("bpy.types.freestylelinestyle.angle_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-angle-max"), + ("bpy.types.freestylelinestyle.angle_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-angle-min"), + ("bpy.types.freestylelinestyle.split_gap*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-split-gap"), + ("bpy.types.freestylelinestyle.use_nodes*", "render/freestyle/view_layer/line_style/texture.html#bpy-types-freestylelinestyle-use-nodes"), ("bpy.types.geometrynodeattributecompare*", "modeling/geometry_nodes/attribute/attribute_compare.html#bpy-types-geometrynodeattributecompare"), ("bpy.types.geometrynodeattributeconvert*", "modeling/geometry_nodes/attribute/attribute_convert.html#bpy-types-geometrynodeattributeconvert"), ("bpy.types.geometrynodeselectbymaterial*", "modeling/geometry_nodes/material/select_by_material.html#bpy-types-geometrynodeselectbymaterial"), ("bpy.types.material.preview_render_type*", "render/materials/preview.html#bpy-types-material-preview-render-type"), ("bpy.types.materialgpencilstyle.pattern*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-pattern"), ("bpy.types.materialgpencilstyle.texture*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-texture"), + ("bpy.types.modifier.use_apply_on_spline*", "modeling/modifiers/introduction.html#bpy-types-modifier-use-apply-on-spline"), ("bpy.types.object.use_empty_image_alpha*", "modeling/empties.html#bpy-types-object-use-empty-image-alpha"), - ("bpy.types.rendersettings.frame_map_new*", "render/output/properties/dimensions.html#bpy-types-rendersettings-frame-map-new"), - ("bpy.types.rendersettings.frame_map_old*", "render/output/properties/dimensions.html#bpy-types-rendersettings-frame-map-old"), + ("bpy.types.rendersettings.frame_map_new*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-new"), + ("bpy.types.rendersettings.frame_map_old*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-old"), ("bpy.types.rendersettings.use_overwrite*", "render/output/properties/output.html#bpy-types-rendersettings-use-overwrite"), ("bpy.types.rendersettings.use_sequencer*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-sequencer"), ("bpy.types.sceneeevee.volumetric_shadow*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric-shadow"), @@ -613,6 +778,7 @@ url_manual_mapping = ( ("bpy.ops.mesh.primitive_ico_sphere_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-ico-sphere-add"), ("bpy.ops.object.gpencil_modifier_apply*", "grease_pencil/modifiers/introduction.html#bpy-ops-object-gpencil-modifier-apply"), ("bpy.ops.object.material_slot_deselect*", "render/materials/assignment.html#bpy-ops-object-material-slot-deselect"), + ("bpy.ops.object.modifier_move_to_index*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-move-to-index"), ("bpy.ops.object.multires_external_save*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-external-save"), ("bpy.ops.object.vertex_group_normalize*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize"), ("bpy.ops.object.visual_transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-visual-transform-apply"), @@ -636,20 +802,25 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.gravity*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-gravity"), ("bpy.types.fluidflowsettings.flow_type*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-type"), ("bpy.types.fluidflowsettings.subframes*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-subframes"), + ("bpy.types.freestylelineset.collection*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-collection"), + ("bpy.types.freestylelineset.visibility*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-visibility"), + ("bpy.types.freestylelinestyle.chaining*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-chaining"), + ("bpy.types.freestylelinestyle.sort_key*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-sort-key"), ("bpy.types.geometrynodeattributeremove*", "modeling/geometry_nodes/attribute/attribute_remove.html#bpy-types-geometrynodeattributeremove"), ("bpy.types.geometrynodematerialreplace*", "modeling/geometry_nodes/material/replace.html#bpy-types-geometrynodematerialreplace"), ("bpy.types.geometrynodepointdistribute*", "modeling/geometry_nodes/point/point_distribute.html#bpy-types-geometrynodepointdistribute"), ("bpy.types.gpencillayer.use_mask_layer*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-mask-layer"), ("bpy.types.greasepencil.use_curve_edit*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-curve-edit"), ("bpy.types.imagepaint.screen_grab_size*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-screen-grab-size"), - ("bpy.types.linestyle*modifier_material*", "render/freestyle/parameter_editor/line_style/modifiers/color/material.html#bpy-types-linestyle-modifier-material"), + ("bpy.types.linestyle*modifier_material*", "render/freestyle/view_layer/line_style/modifiers/color/material.html#bpy-types-linestyle-modifier-material"), ("bpy.types.movietrackingcamera.brown_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-k"), ("bpy.types.movietrackingcamera.brown_p*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-p"), + ("bpy.types.object.visible_transmission*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-transmission"), ("bpy.types.particlesettingstextureslot*", "physics/particles/texture_influence.html#bpy-types-particlesettingstextureslot"), ("bpy.types.posebone.ik_rotation_weight*", "animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html#bpy-types-posebone-ik-rotation-weight"), ("bpy.types.regionview3d.show_sync_view*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-show-sync-view"), - ("bpy.types.rendersettings.resolution_x*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-x"), - ("bpy.types.rendersettings.resolution_y*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-y"), + ("bpy.types.rendersettings.resolution_x*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-x"), + ("bpy.types.rendersettings.resolution_y*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-y"), ("bpy.types.rendersettings.threads_mode*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-threads-mode"), ("bpy.types.rigidbodyconstraint.enabled*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-enabled"), ("bpy.types.rigidbodyconstraint.object1*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-object1"), @@ -700,7 +871,7 @@ url_manual_mapping = ( ("bpy.types.compositornodekeyingscreen*", "compositing/types/matte/keying_screen.html#bpy-types-compositornodekeyingscreen"), ("bpy.types.dynamicpaintcanvassettings*", "physics/dynamic_paint/canvas.html#bpy-types-dynamicpaintcanvassettings"), ("bpy.types.fluidflowsettings.use_flow*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-flow"), - ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierfunctiongenerator"), + ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"), ("bpy.types.geometrynodeattributeclamp*", "modeling/geometry_nodes/attribute/attribute_clamp.html#bpy-types-geometrynodeattributeclamp"), ("bpy.types.geometrynodecollectioninfo*", "modeling/geometry_nodes/input/collection_info.html#bpy-types-geometrynodecollectioninfo"), ("bpy.types.geometrynodecurveendpoints*", "modeling/geometry_nodes/curve/curve_endpoints.html#bpy-types-geometrynodecurveendpoints"), @@ -710,6 +881,7 @@ url_manual_mapping = ( ("bpy.types.geometrynodepointstovolume*", "modeling/geometry_nodes/volume/points_to_volume.html#bpy-types-geometrynodepointstovolume"), ("bpy.types.geometrynodepointtranslate*", "modeling/geometry_nodes/point/point_translate.html#bpy-types-geometrynodepointtranslate"), ("bpy.types.greasepencil.use_multiedit*", "grease_pencil/multiframe.html#bpy-types-greasepencil-use-multiedit"), + ("bpy.types.keyframe.handle_right_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right-type"), ("bpy.types.materialgpencilstyle.color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-color"), ("bpy.types.movietrackingcamera.nuke_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-nuke-k"), ("bpy.types.movietrackingstabilization*", "movie_clip/tracking/clip/sidebar/stabilization/index.html#bpy-types-movietrackingstabilization"), @@ -728,6 +900,7 @@ url_manual_mapping = ( ("bpy.types.toolsettings.use_snap_self*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-self"), ("bpy.types.viewlayer.active_aov_index*", "render/layers/passes.html#bpy-types-viewlayer-active-aov-index"), ("bpy.ops.anim.channels_enable_toggle*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-enable-toggle"), + ("bpy.ops.constraint.copy_to_selected*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy-to-selected"), ("bpy.ops.gpencil.bake_mesh_animation*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-bake-mesh-animation"), ("bpy.ops.gpencil.select_vertex_color*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-vertex-color"), ("bpy.ops.gpencil.set_active_material*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-set-active-material"), @@ -747,6 +920,7 @@ url_manual_mapping = ( ("bpy.ops.outliner.collection_disable*", "editors/outliner/editing.html#bpy-ops-outliner-collection-disable"), ("bpy.ops.outliner.collection_isolate*", "editors/outliner/editing.html#bpy-ops-outliner-collection-isolate"), ("bpy.ops.pose.visual_transform_apply*", "animation/armatures/posing/editing/apply.html#bpy-ops-pose-visual-transform-apply"), + ("bpy.ops.render.shutter_curve_preset*", "render/cycles/render_settings/motion_blur.html#bpy-ops-render-shutter-curve-preset"), ("bpy.ops.sequencer.view_ghost_border*", "video_editing/preview/sidebar.html#bpy-ops-sequencer-view-ghost-border"), ("bpy.ops.ui.override_type_set_button*", "files/linked_libraries/library_overrides.html#bpy-ops-ui-override-type-set-button"), ("bpy.ops.view3d.blenderkit_asset_bar*", "addons/3d_view/blenderkit.html#bpy-ops-view3d-blenderkit-asset-bar"), @@ -765,10 +939,13 @@ url_manual_mapping = ( ("bpy.types.compositornodeellipsemask*", "compositing/types/matte/ellipse_mask.html#bpy-types-compositornodeellipsemask"), ("bpy.types.compositornodesplitviewer*", "compositing/types/output/split_viewer.html#bpy-types-compositornodesplitviewer"), ("bpy.types.curve.render_resolution_u*", "modeling/curves/properties/shape.html#bpy-types-curve-render-resolution-u"), + ("bpy.types.cyclesrendersettings.seed*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-seed"), ("bpy.types.dynamicpaintbrushsettings*", "physics/dynamic_paint/brush.html#bpy-types-dynamicpaintbrushsettings"), ("bpy.types.editbone.use_scale_easing*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-scale-easing"), ("bpy.types.fluiddomainsettings.alpha*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-alpha"), ("bpy.types.fluidflowsettings.density*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-density"), + ("bpy.types.freestylelineset.qi_start*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-qi-start"), + ("bpy.types.freestylelinestyle.rounds*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-rounds"), ("bpy.types.geometrynodeattributefill*", "modeling/geometry_nodes/attribute/attribute_fill.html#bpy-types-geometrynodeattributefill"), ("bpy.types.geometrynodeattributemath*", "modeling/geometry_nodes/attribute/attribute_math.html#bpy-types-geometrynodeattributemath"), ("bpy.types.geometrynodecurvetopoints*", "modeling/geometry_nodes/curve/curve_to_points.html#bpy-types-geometrynodecurvetopoints"), @@ -778,13 +955,15 @@ url_manual_mapping = ( ("bpy.types.geometrynodepointinstance*", "modeling/geometry_nodes/point/point_instance.html#bpy-types-geometrynodepointinstance"), ("bpy.types.geometrynodepointseparate*", "modeling/geometry_nodes/point/point_separate.html#bpy-types-geometrynodepointseparate"), ("bpy.types.geometrynoderesamplecurve*", "modeling/geometry_nodes/curve/resample_curve.html#bpy-types-geometrynoderesamplecurve"), + ("bpy.types.keyframe.handle_left_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left-type"), ("bpy.types.light.use_custom_distance*", "render/eevee/lighting.html#bpy-types-light-use-custom-distance"), ("bpy.types.materialgpencilstyle.flip*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-flip"), ("bpy.types.materialgpencilstyle.mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mode"), + ("bpy.types.modifier.show_in_editmode*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-in-editmode"), ("bpy.types.object.empty_display_size*", "modeling/empties.html#bpy-types-object-empty-display-size"), ("bpy.types.object.empty_display_type*", "modeling/empties.html#bpy-types-object-empty-display-type"), ("bpy.types.regionview3d.use_box_clip*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-use-box-clip"), - ("bpy.types.rendersettings.use_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-border"), + ("bpy.types.rendersettings.use_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-border"), ("bpy.types.rigidbodyconstraint.limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-limit"), ("bpy.types.rigidbodyobject.kinematic*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-kinematic"), ("bpy.types.scene.audio_doppler_speed*", "scene_layout/scene/properties.html#bpy-types-scene-audio-doppler-speed"), @@ -804,7 +983,9 @@ url_manual_mapping = ( ("bpy.types.spaceuveditor.lock_bounds*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-lock-bounds"), ("bpy.types.spline.tilt_interpolation*", "modeling/curves/properties/active_spline.html#bpy-types-spline-tilt-interpolation"), ("bpy.types.transformorientation.name*", "editors/3dview/controls/orientation.html#bpy-types-transformorientation-name"), + ("bpy.types.viewlayer.use_motion_blur*", "render/layers/introduction.html#bpy-types-viewlayer-use-motion-blur"), ("bpy.types.volumedisplay.slice_depth*", "modeling/volumes/properties.html#bpy-types-volumedisplay-slice-depth"), + ("bpy.types.worldmistsettings.falloff*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-falloff"), ("bpy.ops.clip.lock_selection_toggle*", "editors/clip/introduction.html#bpy-ops-clip-lock-selection-toggle"), ("bpy.ops.mesh.customdata_mask_clear*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-mesh-customdata-mask-clear"), ("bpy.ops.mesh.extrude_vertices_move*", "modeling/meshes/editing/vertex/extrude_vertices.html#bpy-ops-mesh-extrude-vertices-move"), @@ -848,9 +1029,11 @@ url_manual_mapping = ( ("bpy.types.copytransformsconstraint*", "animation/constraints/transform/copy_transforms.html#bpy-types-copytransformsconstraint"), ("bpy.types.correctivesmoothmodifier*", "modeling/modifiers/deform/corrective_smooth.html#bpy-types-correctivesmoothmodifier"), ("bpy.types.curve.bevel_factor_start*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-start"), - ("bpy.types.cyclesvisibilitysettings*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesvisibilitysettings"), ("bpy.types.fluiddomainsettings.beta*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-beta"), ("bpy.types.fluidmodifier.fluid_type*", "physics/fluid/type/index.html#bpy-types-fluidmodifier-fluid-type"), + ("bpy.types.freestylelineset.exclude*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-exclude"), + ("bpy.types.freestylelinestyle.alpha*", "render/freestyle/view_layer/line_style/alpha.html#bpy-types-freestylelinestyle-alpha"), + ("bpy.types.freestylelinestyle.color*", "render/freestyle/view_layer/line_style/color.html#bpy-types-freestylelinestyle-color"), ("bpy.types.functionnodefloatcompare*", "modeling/geometry_nodes/utilities/float_compare.html#bpy-types-functionnodefloatcompare"), ("bpy.types.geometrynodeattributemix*", "modeling/geometry_nodes/attribute/attribute_mix.html#bpy-types-geometrynodeattributemix"), ("bpy.types.geometrynodecurvereverse*", "modeling/geometry_nodes/curve/curve_reverse.html#bpy-types-geometrynodecurvereverse"), @@ -860,11 +1043,12 @@ url_manual_mapping = ( ("bpy.types.geometrynodevolumetomesh*", "modeling/geometry_nodes/volume/volume_to_mesh.html#bpy-types-geometrynodevolumetomesh"), ("bpy.types.image.use_half_precision*", "editors/image/image_settings.html#bpy-types-image-use-half-precision"), ("bpy.types.imagepaint.interpolation*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-types-imagepaint-interpolation"), - ("bpy.types.linestyle*modifier_noise*", "render/freestyle/parameter_editor/line_style/modifiers/color/noise.html#bpy-types-linestyle-modifier-noise"), + ("bpy.types.linestyle*modifier_noise*", "render/freestyle/view_layer/line_style/modifiers/color/noise.html#bpy-types-linestyle-modifier-noise"), ("bpy.types.maintainvolumeconstraint*", "animation/constraints/transform/maintain_volume.html#bpy-types-maintainvolumeconstraint"), ("bpy.types.mesh.use_mirror_topology*", "modeling/meshes/tools/tool_settings.html#bpy-types-mesh-use-mirror-topology"), ("bpy.types.movieclip.display_aspect*", "editors/clip/display/clip_display.html#bpy-types-movieclip-display-aspect"), ("bpy.types.nodesocketinterface.name*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-name"), + ("bpy.types.object.is_shadow_catcher*", "render/cycles/object_settings/object_data.html#bpy-types-object-is-shadow-catcher"), ("bpy.types.particleinstancemodifier*", "modeling/modifiers/physics/particle_instance.html#bpy-types-particleinstancemodifier"), ("bpy.types.sequencetransform.offset*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-offset"), ("bpy.types.shadernodebrightcontrast*", "render/shader_nodes/color/bright_contrast.html#bpy-types-shadernodebrightcontrast"), @@ -931,6 +1115,9 @@ url_manual_mapping = ( ("bpy.types.editbone.bbone_curveinz*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-curveinz"), ("bpy.types.editbone.bbone_scaleout*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-scaleout"), ("bpy.types.editbone.bbone_segments*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-segments"), + ("bpy.types.freestylelineset.qi_end*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-qi-end"), + ("bpy.types.freestylelinestyle.caps*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-caps"), + ("bpy.types.freestylelinestyle.dash*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-dash"), ("bpy.types.freestylemodulesettings*", "render/freestyle/python.html#bpy-types-freestylemodulesettings"), ("bpy.types.functionnodebooleanmath*", "modeling/geometry_nodes/utilities/boolean_math.html#bpy-types-functionnodebooleanmath"), ("bpy.types.functionnodeinputstring*", "modeling/geometry_nodes/input/string.html#bpy-types-functionnodeinputstring"), @@ -955,7 +1142,7 @@ url_manual_mapping = ( ("bpy.types.limitrotationconstraint*", "animation/constraints/transform/limit_rotation.html#bpy-types-limitrotationconstraint"), ("bpy.types.multiplygpencilmodifier*", "grease_pencil/modifiers/generate/multiple_strokes.html#bpy-types-multiplygpencilmodifier"), ("bpy.types.rendersettings.filepath*", "render/output/properties/output.html#bpy-types-rendersettings-filepath"), - ("bpy.types.rendersettings.fps_base*", "render/output/properties/dimensions.html#bpy-types-rendersettings-fps-base"), + ("bpy.types.rendersettings.fps_base*", "render/output/properties/format.html#bpy-types-rendersettings-fps-base"), ("bpy.types.rigidbodyobject.enabled*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-enabled"), ("bpy.types.sceneeevee.use_overscan*", "render/eevee/render_settings/film.html#bpy-types-sceneeevee-use-overscan"), ("bpy.types.sequencetransform.scale*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-scale"), @@ -965,13 +1152,16 @@ url_manual_mapping = ( ("bpy.types.shadernodevolumescatter*", "render/shader_nodes/shader/volume_scatter.html#bpy-types-shadernodevolumescatter"), ("bpy.types.simplifygpencilmodifier*", "grease_pencil/modifiers/generate/simplify.html#bpy-types-simplifygpencilmodifier"), ("bpy.types.spacegrapheditor.cursor*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-cursor"), - ("bpy.types.toolsettings.annotation*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation"), ("bpy.types.vertexweightmixmodifier*", "modeling/modifiers/modify/weight_mix.html#bpy-types-vertexweightmixmodifier"), - ("bpy.types.viewlayer.use_freestyle*", "render/freestyle/view_layer.html#bpy-types-viewlayer-use-freestyle"), + ("bpy.types.viewlayer.use_freestyle*", "render/freestyle/view_layer/freestyle.html#bpy-types-viewlayer-use-freestyle"), ("bpy.types.volumedisplay.use_slice*", "modeling/volumes/properties.html#bpy-types-volumedisplay-use-slice"), + ("bpy.types.worldlighting.ao_factor*", "render/cycles/world_settings.html#bpy-types-worldlighting-ao-factor"), + ("bpy.types.worldmistsettings.depth*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-depth"), + ("bpy.types.worldmistsettings.start*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-start"), ("bpy.ops.armature.armature_layers*", "animation/armatures/bones/editing/change_layers.html#bpy-ops-armature-armature-layers"), ("bpy.ops.armature.select_linked()*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-linked"), ("bpy.ops.clip.stabilize_2d_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-select"), + ("bpy.ops.constraint.move_to_index*", "animation/constraints/interface/header.html#bpy-ops-constraint-move-to-index"), ("bpy.ops.gpencil.frame_clean_fill*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-frame-clean-fill"), ("bpy.ops.gpencil.stroke_subdivide*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-subdivide"), ("bpy.ops.gpencil.vertex_color_hsv*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-hsv"), @@ -994,7 +1184,7 @@ url_manual_mapping = ( ("bpy.ops.outliner.collection_hide*", "editors/outliner/editing.html#bpy-ops-outliner-collection-hide"), ("bpy.ops.outliner.collection_show*", "editors/outliner/editing.html#bpy-ops-outliner-collection-show"), ("bpy.ops.paint.mask_lasso_gesture*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask-lasso-gesture"), - ("bpy.ops.rigidbody.mass_calculate*", "physics/rigid_body/editing.html#bpy-ops-rigidbody-mass-calculate"), + ("bpy.ops.rigidbody.mass_calculate*", "scene_layout/object/editing/rigid_body.html#bpy-ops-rigidbody-mass-calculate"), ("bpy.ops.screen.spacedata_cleanup*", "advanced/operators.html#bpy-ops-screen-spacedata-cleanup"), ("bpy.ops.sculpt.detail_flood_fill*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-detail-flood-fill"), ("bpy.ops.sequencer.duplicate_move*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-duplicate-move"), @@ -1037,6 +1227,8 @@ url_manual_mapping = ( ("bpy.types.editbone.bbone_rollout*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-rollout"), ("bpy.types.editbone.bbone_scalein*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-scalein"), ("bpy.types.editbone.inherit_scale*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-inherit-scale"), + ("bpy.types.freestylelinestyle.gap*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-gap"), + ("bpy.types.freestylesettings.mode*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-mode"), ("bpy.types.geometrynodeconvexhull*", "modeling/geometry_nodes/geometry/convex_hull.html#bpy-types-geometrynodeconvexhull"), ("bpy.types.geometrynodefloattoint*", "modeling/geometry_nodes/utilities/float_to_int.html#bpy-types-geometrynodefloattoint"), ("bpy.types.geometrynodeisviewport*", "modeling/geometry_nodes/input/is_viewport.html#bpy-types-geometrynodeisviewport"), @@ -1045,9 +1237,13 @@ url_manual_mapping = ( ("bpy.types.geometrynodepointscale*", "modeling/geometry_nodes/point/point_scale.html#bpy-types-geometrynodepointscale"), ("bpy.types.imagepaint.use_occlude*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-use-occlude"), ("bpy.types.imagesequence.use_flip*", "video_editing/sequencer/sidebar/strip.html#bpy-types-imagesequence-use-flip"), + ("bpy.types.keyframe.interpolation*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-interpolation"), ("bpy.types.latticegpencilmodifier*", "grease_pencil/modifiers/deform/lattice.html#bpy-types-latticegpencilmodifier"), ("bpy.types.lineartgpencilmodifier*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier"), + ("bpy.types.material.line_priority*", "render/freestyle/material.html#bpy-types-material-line-priority"), ("bpy.types.mesh.auto_smooth_angle*", "modeling/meshes/structure.html#bpy-types-mesh-auto-smooth-angle"), + ("bpy.types.modifier.show_viewport*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-viewport"), + ("bpy.types.object.visible_diffuse*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-diffuse"), ("bpy.types.objectsolverconstraint*", "animation/constraints/motion_tracking/object_solver.html#bpy-types-objectsolverconstraint"), ("bpy.types.opacitygpencilmodifier*", "grease_pencil/modifiers/color/opacity.html#bpy-types-opacitygpencilmodifier"), ("bpy.types.particlesystemmodifier*", "physics/particles/index.html#bpy-types-particlesystemmodifier"), @@ -1065,6 +1261,7 @@ url_manual_mapping = ( ("bpy.types.volumedisplacemodifier*", "modeling/modifiers/deform/volume_displace.html#bpy-types-volumedisplacemodifier"), ("bpy.types.volumerender.step_size*", "modeling/volumes/properties.html#bpy-types-volumerender-step-size"), ("bpy.types.weightednormalmodifier*", "modeling/modifiers/modify/weighted_normal.html#bpy-types-weightednormalmodifier"), + ("bpy.types.worldlighting.distance*", "render/cycles/world_settings.html#bpy-types-worldlighting-distance"), ("bpy.ops.armature.autoside_names*", "animation/armatures/bones/editing/naming.html#bpy-ops-armature-autoside-names"), ("bpy.ops.armature.calculate_roll*", "animation/armatures/bones/editing/bone_roll.html#bpy-ops-armature-calculate-roll"), ("bpy.ops.armature.duplicate_move*", "animation/armatures/bones/editing/duplicate.html#bpy-ops-armature-duplicate-move"), @@ -1143,6 +1340,7 @@ url_manual_mapping = ( ("bpy.types.dopesheet.filter_text*", "editors/graph_editor/channels.html#bpy-types-dopesheet-filter-text"), ("bpy.types.editbone.bbone_easein*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-easein"), ("bpy.types.editbone.bbone_rollin*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-rollin"), + ("bpy.types.fcurve.auto_smoothing*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-auto-smoothing"), ("bpy.types.fluideffectorsettings*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings"), ("bpy.types.followtrackconstraint*", "animation/constraints/motion_tracking/follow_track.html#bpy-types-followtrackconstraint"), ("bpy.types.geometrynodecurveline*", "modeling/geometry_nodes/curve_primitives/line.html#bpy-types-geometrynodecurveline"), @@ -1151,12 +1349,17 @@ url_manual_mapping = ( ("bpy.types.geometrynodeedgesplit*", "modeling/geometry_nodes/mesh/edge_split.html#bpy-types-geometrynodeedgesplit"), ("bpy.types.geometrynodetransform*", "modeling/geometry_nodes/geometry/transform.html#bpy-types-geometrynodetransform"), ("bpy.types.gpencilsculptsettings*", "grease_pencil/properties/index.html#bpy-types-gpencilsculptsettings"), + ("bpy.types.keyframe.handle_right*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right"), ("bpy.types.light.cutoff_distance*", "render/eevee/lighting.html#bpy-types-light-cutoff-distance"), ("bpy.types.lockedtrackconstraint*", "animation/constraints/tracking/locked_track.html#bpy-types-lockedtrackconstraint"), ("bpy.types.material.blend_method*", "render/eevee/materials/settings.html#bpy-types-material-blend-method"), ("bpy.types.mirrorgpencilmodifier*", "grease_pencil/modifiers/generate/mirror.html#bpy-types-mirrorgpencilmodifier"), + ("bpy.types.modifier.show_on_cage*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-on-cage"), ("bpy.types.movietrackingcamera.k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-k"), ("bpy.types.node.use_custom_color*", "interface/controls/nodes/sidebar.html#bpy-types-node-use-custom-color"), + ("bpy.types.object.visible_camera*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-camera"), + ("bpy.types.object.visible_glossy*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-glossy"), + ("bpy.types.object.visible_shadow*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-shadow"), ("bpy.types.offsetgpencilmodifier*", "grease_pencil/modifiers/deform/offset.html#bpy-types-offsetgpencilmodifier"), ("bpy.types.posebone.custom_shape*", "animation/armatures/bones/properties/display.html#bpy-types-posebone-custom-shape"), ("bpy.types.rendersettings.tile_x*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-x"), @@ -1177,6 +1380,7 @@ url_manual_mapping = ( ("bpy.types.spline.use_endpoint_u*", "modeling/curves/properties/active_spline.html#bpy-types-spline-use-endpoint-u"), ("bpy.types.surfacedeformmodifier*", "modeling/modifiers/deform/surface_deform.html#bpy-types-surfacedeformmodifier"), ("bpy.types.toolsettings.use_snap*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap"), + ("bpy.types.viewlayer.use_volumes*", "render/layers/introduction.html#bpy-types-viewlayer-use-volumes"), ("bpy.types.volume.frame_duration*", "modeling/volumes/properties.html#bpy-types-volume-frame-duration"), ("bpy.types.volumedisplay.density*", "modeling/volumes/properties.html#bpy-types-volumedisplay-density"), ("bpy.types.volumerender.clipping*", "modeling/volumes/properties.html#bpy-types-volumerender-clipping"), @@ -1192,6 +1396,7 @@ url_manual_mapping = ( ("bpy.ops.mesh.vert_connect_path*", "modeling/meshes/editing/vertex/connect_vertex_path.html#bpy-ops-mesh-vert-connect-path"), ("bpy.ops.nla.action_sync_length*", "editors/nla/editing.html#bpy-ops-nla-action-sync-length"), ("bpy.ops.object.make_links_data*", "scene_layout/object/editing/link_transfer/link_data.html#bpy-ops-object-make-links-data"), + ("bpy.ops.object.modifier_remove*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-remove"), ("bpy.ops.object.paths_calculate*", "animation/motion_paths.html#bpy-ops-object-paths-calculate"), ("bpy.ops.object.transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-transform-apply"), ("bpy.ops.outliner.lib_operation*", "files/linked_libraries/link_append.html#bpy-ops-outliner-lib-operation"), @@ -1232,11 +1437,13 @@ url_manual_mapping = ( ("bpy.types.geometrynodemeshline*", "modeling/geometry_nodes/mesh_primitives/line.html#bpy-types-geometrynodemeshline"), ("bpy.types.gpencillayer.opacity*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-opacity"), ("bpy.types.image.display_aspect*", "editors/image/sidebar.html#bpy-types-image-display-aspect"), + ("bpy.types.keyframe.handle_left*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left"), ("bpy.types.keyingsetsall.active*", "editors/timeline.html#bpy-types-keyingsetsall-active"), ("bpy.types.limitscaleconstraint*", "animation/constraints/transform/limit_scale.html#bpy-types-limitscaleconstraint"), ("bpy.types.materialgpencilstyle*", "grease_pencil/materials/index.html#bpy-types-materialgpencilstyle"), ("bpy.types.mesh.use_auto_smooth*", "modeling/meshes/structure.html#bpy-types-mesh-use-auto-smooth"), ("bpy.types.meshtovolumemodifier*", "modeling/modifiers/generate/mesh_to_volume.html#bpy-types-meshtovolumemodifier"), + ("bpy.types.modifier.show_render*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-render"), ("bpy.types.noisegpencilmodifier*", "grease_pencil/modifiers/deform/noise.html#bpy-types-noisegpencilmodifier"), ("bpy.types.object.hide_viewport*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-viewport"), ("bpy.types.posebone.rigify_type*", "addons/rigging/rigify/rig_types/index.html#bpy-types-posebone-rigify-type"), @@ -1291,6 +1498,7 @@ url_manual_mapping = ( ("bpy.ops.node.tree_socket_move*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-move"), ("bpy.ops.object.duplicate_move*", "scene_layout/object/editing/duplicate.html#bpy-ops-object-duplicate-move"), ("bpy.ops.object.hook_add_selob*", "modeling/meshes/editing/vertex/hooks.html#bpy-ops-object-hook-add-selob"), + ("bpy.ops.object.modifier_apply*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply"), ("bpy.ops.object.select_by_type*", "scene_layout/object/selecting.html#bpy-ops-object-select-by-type"), ("bpy.ops.object.select_grouped*", "scene_layout/object/selecting.html#bpy-ops-object-select-grouped"), ("bpy.ops.object.select_pattern*", "scene_layout/object/selecting.html#bpy-ops-object-select-pattern"), @@ -1341,6 +1549,7 @@ url_manual_mapping = ( ("bpy.types.hookgpencilmodifier*", "grease_pencil/modifiers/deform/hook.html#bpy-types-hookgpencilmodifier"), ("bpy.types.imageformatsettings*", "files/media/image_formats.html#bpy-types-imageformatsettings"), ("bpy.types.kinematicconstraint*", "animation/constraints/tracking/ik_solver.html#bpy-types-kinematicconstraint"), + ("bpy.types.material.line_color*", "render/freestyle/material.html#bpy-types-material-line-color"), ("bpy.types.mesh.use_paint_mask*", "sculpt_paint/brush/introduction.html#bpy-types-mesh-use-paint-mask"), ("bpy.types.movietrackingcamera*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera"), ("bpy.types.object.display_type*", "scene_layout/object/properties/display.html#bpy-types-object-display-type"), @@ -1402,6 +1611,7 @@ url_manual_mapping = ( ("bpy.ops.node.read_viewlayers*", "interface/controls/nodes/editing.html#bpy-ops-node-read-viewlayers"), ("bpy.ops.node.tree_socket_add*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-add"), ("bpy.ops.object.data_transfer*", "scene_layout/object/editing/link_transfer/transfer_mesh_data.html#bpy-ops-object-data-transfer"), + ("bpy.ops.object.modifier_copy*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-copy"), ("bpy.ops.object.select_camera*", "scene_layout/object/selecting.html#bpy-ops-object-select-camera"), ("bpy.ops.object.select_linked*", "scene_layout/object/selecting.html#bpy-ops-object-select-linked"), ("bpy.ops.object.select_mirror*", "scene_layout/object/selecting.html#bpy-ops-object-select-mirror"), @@ -1436,14 +1646,16 @@ url_manual_mapping = ( ("bpy.types.compositornodemask*", "compositing/types/input/mask.html#bpy-types-compositornodemask"), ("bpy.types.compositornodemath*", "compositing/types/converter/math.html#bpy-types-compositornodemath"), ("bpy.types.compositornodetime*", "compositing/types/input/time.html#bpy-types-compositornodetime"), + ("bpy.types.constraint.enabled*", "animation/constraints/interface/header.html#bpy-types-constraint-enabled"), ("bpy.types.curve.bevel_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-object"), ("bpy.types.curve.resolution_u*", "modeling/curves/properties/shape.html#bpy-types-curve-resolution-u"), ("bpy.types.curve.resolution_v*", "modeling/surfaces/properties/shape.html#bpy-types-curve-resolution-v"), ("bpy.types.curve.taper_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-taper-object"), ("bpy.types.curve.twist_smooth*", "modeling/curves/properties/shape.html#bpy-types-curve-twist-smooth"), ("bpy.types.curvepaintsettings*", "modeling/curves/tools/draw.html#bpy-types-curvepaintsettings"), - ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiergenerator"), - ("bpy.types.freestylelinestyle*", "render/freestyle/parameter_editor/line_style/index.html#bpy-types-freestylelinestyle"), + ("bpy.types.fcurve.array_index*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-array-index"), + ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"), + ("bpy.types.freestylelinestyle*", "render/freestyle/view_layer/line_style/index.html#bpy-types-freestylelinestyle"), ("bpy.types.gammacrosssequence*", "video_editing/sequencer/strips/transitions/gamma_cross.html#bpy-types-gammacrosssequence"), ("bpy.types.geometrynodeswitch*", "modeling/geometry_nodes/utilities/switch.html#bpy-types-geometrynodeswitch"), ("bpy.types.geometrynodeviewer*", "modeling/geometry_nodes/output/viewer.html#bpy-types-geometrynodeviewer"), @@ -1459,7 +1671,7 @@ url_manual_mapping = ( ("bpy.types.object.hide_select*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-select"), ("bpy.types.object.parent_type*", "scene_layout/object/properties/relations.html#bpy-types-object-parent-type"), ("bpy.types.object.show_bounds*", "scene_layout/object/properties/display.html#bpy-types-object-show-bounds"), - ("bpy.types.rendersettings.fps*", "render/output/properties/dimensions.html#bpy-types-rendersettings-fps"), + ("bpy.types.rendersettings.fps*", "render/output/properties/format.html#bpy-types-rendersettings-fps"), ("bpy.types.scene.audio_volume*", "scene_layout/scene/properties.html#bpy-types-scene-audio-volume"), ("bpy.types.sculpt.detail_size*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-size"), ("bpy.types.shadernodebsdfhair*", "render/shader_nodes/shader/hair.html#bpy-types-shadernodebsdfhair"), @@ -1558,9 +1770,10 @@ url_manual_mapping = ( ("bpy.types.curve.bevel_depth*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-depth"), ("bpy.types.curve.use_stretch*", "modeling/curves/properties/shape.html#bpy-types-curve-use-stretch"), ("bpy.types.edgesplitmodifier*", "modeling/modifiers/generate/edge_split.html#bpy-types-edgesplitmodifier"), + ("bpy.types.fcurve.color_mode*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-color-mode"), ("bpy.types.fluidflowsettings*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings"), - ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierenvelope"), - ("bpy.types.freestylesettings*", "render/freestyle/view_layer.html#bpy-types-freestylesettings"), + ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelope"), + ("bpy.types.freestylesettings*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings"), ("bpy.types.geometrynodegroup*", "modeling/geometry_nodes/group.html#bpy-types-geometrynodegroup"), ("bpy.types.gpencillayer.hide*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-hide"), ("bpy.types.gpencillayer.lock*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-lock"), @@ -1573,12 +1786,13 @@ url_manual_mapping = ( ("bpy.types.meshcachemodifier*", "modeling/modifiers/modify/mesh_cache.html#bpy-types-meshcachemodifier"), ("bpy.types.movieclipsequence*", "video_editing/sequencer/strips/clip.html#bpy-types-movieclipsequence"), ("bpy.types.object.dimensions*", "scene_layout/object/properties/transforms.html#bpy-types-object-dimensions"), + ("bpy.types.object.is_holdout*", "scene_layout/object/properties/visibility.html#bpy-types-object-is-holdout"), ("bpy.types.object.pass_index*", "scene_layout/object/properties/relations.html#bpy-types-object-pass-index"), ("bpy.types.object.track_axis*", "scene_layout/object/properties/relations.html#bpy-types-object-track-axis"), ("bpy.types.pose.use_mirror_x*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-mirror-x"), ("bpy.types.preferencessystem*", "editors/preferences/system.html#bpy-types-preferencessystem"), ("bpy.types.scene.active_clip*", "scene_layout/scene/properties.html#bpy-types-scene-active-clip"), - ("bpy.types.scene.frame_start*", "render/output/properties/dimensions.html#bpy-types-scene-frame-start"), + ("bpy.types.scene.frame_start*", "render/output/properties/frame_range.html#bpy-types-scene-frame-start"), ("bpy.types.sceneeevee.shadow*", "render/eevee/render_settings/shadows.html#bpy-types-sceneeevee-shadow"), ("bpy.types.screen.use_follow*", "editors/timeline.html#bpy-types-screen-use-follow"), ("bpy.types.sequencetransform*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform"), @@ -1661,8 +1875,9 @@ url_manual_mapping = ( ("bpy.types.displaysafeareas*", "render/cameras.html#bpy-types-displaysafeareas"), ("bpy.types.editbone.bbone_x*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-x"), ("bpy.types.editbone.bbone_z*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-z"), - ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierstepped"), - ("bpy.types.freestylelineset*", "render/freestyle/parameter_editor/line_set.html#bpy-types-freestylelineset"), + ("bpy.types.fcurve.data_path*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-data-path"), + ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"), + ("bpy.types.freestylelineset*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset"), ("bpy.types.mask.frame_start*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-start"), ("bpy.types.mesh.*customdata*", "modeling/meshes/properties/custom_data.html#bpy-types-mesh-customdata"), ("bpy.types.multicamsequence*", "video_editing/sequencer/strips/effects/multicam.html#bpy-types-multicamsequence"), @@ -1675,7 +1890,7 @@ url_manual_mapping = ( ("bpy.types.pose.use_auto_ik*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-auto-ik"), ("bpy.types.preferencesinput*", "editors/preferences/input.html#bpy-types-preferencesinput"), ("bpy.types.rigifyparameters*", "addons/rigging/rigify/rig_types/index.html#bpy-types-rigifyparameters"), - ("bpy.types.scene.frame_step*", "render/output/properties/dimensions.html#bpy-types-scene-frame-step"), + ("bpy.types.scene.frame_step*", "render/output/properties/frame_range.html#bpy-types-scene-frame-step"), ("bpy.types.sceneeevee.bloom*", "render/eevee/render_settings/bloom.html#bpy-types-sceneeevee-bloom"), ("bpy.types.sculpt.show_mask*", "sculpt_paint/sculpting/editing/mask.html#bpy-types-sculpt-show-mask"), ("bpy.types.sequence.channel*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequence-channel"), @@ -1708,6 +1923,7 @@ url_manual_mapping = ( ("bpy.ops.clip.clean_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-clean-tracks"), ("bpy.ops.clip.delete_track*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-delete-track"), ("bpy.ops.clip.solve_camera*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-solve-camera"), + ("bpy.ops.constraint.delete*", "animation/constraints/interface/header.html#bpy-ops-constraint-delete"), ("bpy.ops.curve.smooth_tilt*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-tilt"), ("bpy.ops.fluid.bake_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-bake-guides"), ("bpy.ops.fluid.free_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-free-guides"), @@ -1751,15 +1967,16 @@ url_manual_mapping = ( ("bpy.types.booleanmodifier*", "modeling/modifiers/generate/booleans.html#bpy-types-booleanmodifier"), ("bpy.types.brush.mask_tool*", "sculpt_paint/sculpting/tools/mask.html#bpy-types-brush-mask-tool"), ("bpy.types.constraint.mute*", "animation/constraints/interface/header.html#bpy-types-constraint-mute"), + ("bpy.types.constraint.name*", "animation/constraints/interface/header.html#bpy-types-constraint-name"), ("bpy.types.curve.eval_time*", "modeling/curves/properties/path_animation.html#bpy-types-curve-eval-time"), ("bpy.types.curve.fill_mode*", "modeling/curves/properties/shape.html#bpy-types-curve-fill-mode"), ("bpy.types.editbone.layers*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-layers"), ("bpy.types.editbone.parent*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-parent"), ("bpy.types.explodemodifier*", "modeling/modifiers/physics/explode.html#bpy-types-explodemodifier"), - ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fcurvemodifiers"), + ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fcurvemodifiers"), ("bpy.types.floorconstraint*", "animation/constraints/relationship/floor.html#bpy-types-floorconstraint"), - ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiercycles"), - ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierlimits"), + ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiercycles"), + ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierlimits"), ("bpy.types.imagepaint.mode*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-types-imagepaint-mode"), ("bpy.types.latticemodifier*", "modeling/modifiers/deform/lattice.html#bpy-types-latticemodifier"), ("bpy.types.musgravetexture*", "render/materials/legacy_textures/types/musgrave.html#bpy-types-musgravetexture"), @@ -1770,7 +1987,7 @@ url_manual_mapping = ( ("bpy.types.preferencesedit*", "editors/preferences/editing.html#bpy-types-preferencesedit"), ("bpy.types.preferencesview*", "editors/preferences/interface.html#bpy-types-preferencesview"), ("bpy.types.rigidbodyobject*", "physics/rigid_body/index.html#bpy-types-rigidbodyobject"), - ("bpy.types.scene.frame_end*", "render/output/properties/dimensions.html#bpy-types-scene-frame-end"), + ("bpy.types.scene.frame_end*", "render/output/properties/frame_range.html#bpy-types-scene-frame-end"), ("bpy.types.sceneeevee.gtao*", "render/eevee/render_settings/ambient_occlusion.html#bpy-types-sceneeevee-gtao"), ("bpy.types.screen.use_play*", "editors/timeline.html#bpy-types-screen-use-play"), ("bpy.types.shadernodebevel*", "render/shader_nodes/input/bevel.html#bpy-types-shadernodebevel"), @@ -1786,6 +2003,7 @@ url_manual_mapping = ( ("bpy.types.texturenodemath*", "editors/texture_node/types/converter/math.html#bpy-types-texturenodemath"), ("bpy.types.volume.filepath*", "modeling/volumes/properties.html#bpy-types-volume-filepath"), ("bpy.ops.clip.join_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-join-tracks"), + ("bpy.ops.constraint.apply*", "animation/constraints/interface/header.html#bpy-ops-constraint-apply"), ("bpy.ops.curve.select_row*", "modeling/surfaces/selecting.html#bpy-ops-curve-select-row"), ("bpy.ops.curve.tilt_clear*", "modeling/curves/editing/control_points.html#bpy-ops-curve-tilt-clear"), ("bpy.ops.curve.vertex_add*", "modeling/curves/editing/other.html#bpy-ops-curve-vertex-add"), @@ -1829,7 +2047,8 @@ url_manual_mapping = ( ("bpy.types.brush.hardness*", "sculpt_paint/sculpting/tool_settings/brush_settings.html#bpy-types-brush-hardness"), ("bpy.types.curvesmodifier*", "video_editing/sequencer/sidebar/modifiers.html#bpy-types-curvesmodifier"), ("bpy.types.ffmpegsettings*", "render/output/properties/output.html#bpy-types-ffmpegsettings"), - ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiernoise"), + ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"), + ("bpy.types.keyframe.co_ui*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-co-ui"), ("bpy.types.mask.frame_end*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-end"), ("bpy.types.material.paint*", "sculpt_paint/texture_paint/index.html#bpy-types-material-paint"), ("bpy.types.mirrormodifier*", "modeling/modifiers/generate/mirror.html#bpy-types-mirrormodifier"), @@ -1861,6 +2080,7 @@ url_manual_mapping = ( ("bpy.ops.clip.select_all*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-all"), ("bpy.ops.clip.select_box*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-box"), ("bpy.ops.clip.set_origin*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-origin"), + ("bpy.ops.constraint.copy*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy"), ("bpy.ops.curve.subdivide*", "modeling/curves/editing/segments.html#bpy-ops-curve-subdivide"), ("bpy.ops.ed.undo_history*", "interface/undo_redo.html#bpy-ops-ed-undo-history"), ("bpy.ops.file.unpack_all*", "files/blend/packed_data.html#bpy-ops-file-unpack-all"), @@ -1924,6 +2144,7 @@ url_manual_mapping = ( ("bpy.types.fieldsettings*", "physics/forces/force_fields/index.html#bpy-types-fieldsettings"), ("bpy.types.imagesequence*", "video_editing/sequencer/strips/image.html#bpy-types-imagesequence"), ("bpy.types.marbletexture*", "render/materials/legacy_textures/types/marble.html#bpy-types-marbletexture"), + ("bpy.types.modifier.name*", "modeling/modifiers/introduction.html#bpy-types-modifier-name"), ("bpy.types.modifier.show*", "modeling/modifiers/introduction.html#bpy-types-modifier-show"), ("bpy.types.moviesequence*", "video_editing/sequencer/strips/movie.html#bpy-types-moviesequence"), ("bpy.types.movietracking*", "movie_clip/tracking/index.html#bpy-types-movietracking"), @@ -1951,6 +2172,7 @@ url_manual_mapping = ( ("bpy.types.viewlayer.use*", "render/layers/view_layer.html#bpy-types-viewlayer-use"), ("bpy.types.volumedisplay*", "modeling/volumes/properties.html#bpy-types-volumedisplay"), ("bpy.types.windowmanager*", "interface/index.html#bpy-types-windowmanager"), + ("bpy.types.worldlighting*", "render/cycles/world_settings.html#bpy-types-worldlighting"), ("bpy.ops.*.select_lasso*", "interface/selecting.html#bpy-ops-select-lasso"), ("bpy.ops.armature.align*", "animation/armatures/bones/editing/transform.html#bpy-ops-armature-align"), ("bpy.ops.armature.split*", "animation/armatures/bones/editing/split.html#bpy-ops-armature-split"), @@ -2106,6 +2328,7 @@ url_manual_mapping = ( ("bpy.types.compositor*", "compositing/index.html#bpy-types-compositor"), ("bpy.types.constraint*", "animation/constraints/index.html#bpy-types-constraint"), ("bpy.types.imagepaint*", "sculpt_paint/texture_paint/index.html#bpy-types-imagepaint"), + ("bpy.types.keymapitem*", "editors/preferences/keymap.html#bpy-types-keymapitem"), ("bpy.types.lightprobe*", "render/eevee/light_probes/index.html#bpy-types-lightprobe"), ("bpy.types.maskparent*", "movie_clip/masking/sidebar.html#bpy-types-maskparent"), ("bpy.types.maskspline*", "movie_clip/masking/sidebar.html#bpy-types-maskspline"), @@ -2142,7 +2365,7 @@ url_manual_mapping = ( ("bpy.types.bone.hide*", "animation/armatures/bones/properties/display.html#bpy-types-bone-hide"), ("bpy.types.colorramp*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp"), ("bpy.types.dopesheet*", "editors/dope_sheet/index.html#bpy-types-dopesheet"), - ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifier"), + ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifier"), ("bpy.types.freestyle*", "render/freestyle/index.html#bpy-types-freestyle"), ("bpy.types.masklayer*", "movie_clip/masking/sidebar.html#bpy-types-masklayer"), ("bpy.types.movieclip*", "movie_clip/index.html#bpy-types-movieclip"), @@ -2185,7 +2408,7 @@ url_manual_mapping = ( ("bpy.types.editbone*", "animation/armatures/bones/editing/index.html#bpy-types-editbone"), ("bpy.types.facemaps*", "modeling/meshes/properties/object_data.html#bpy-types-facemaps"), ("bpy.types.keyframe*", "animation/keyframes/index.html#bpy-types-keyframe"), - ("bpy.types.linesets*", "render/freestyle/parameter_editor/line_set.html#bpy-types-linesets"), + ("bpy.types.linesets*", "render/freestyle/view_layer/line_set.html#bpy-types-linesets"), ("bpy.types.metaball*", "modeling/metas/index.html#bpy-types-metaball"), ("bpy.types.modifier*", "modeling/modifiers/index.html#bpy-types-modifier"), ("bpy.types.nlastrip*", "editors/nla/strips.html#bpy-types-nlastrip"), diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 27233170175..0aaa5f87df3 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -2606,8 +2606,7 @@ def km_sequencer(params): for i in range(10) ) ), - ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, - {"properties": [("deselect_all", True)]}), + ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, None), ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "shift": True}, {"properties": [("extend", True)]}), ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "alt": True}, @@ -7000,8 +6999,7 @@ def km_sequencer_editor_tool_select(params): "Sequencer Tool: Select", {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, - {"properties": [("deselect_all", not params.legacy)]}), + ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, None), *_template_items_change_frame(params), ]}, ) diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 1962c80b057..6a24f072ed0 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -1814,8 +1814,7 @@ def km_sequencer(params): for i in range(10) ) ), - ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, - {"properties": [("deselect_all", True)]}), + ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, {"properties": [("extend", True)]}), ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}, diff --git a/release/scripts/startup/bl_operators/assets.py b/release/scripts/startup/bl_operators/assets.py index 8c76018b7a1..b241e537c38 100644 --- a/release/scripts/startup/bl_operators/assets.py +++ b/release/scripts/startup/bl_operators/assets.py @@ -85,9 +85,9 @@ class ASSET_OT_open_containing_blend_file(Operator): @classmethod def poll(cls, context): asset_file_handle = getattr(context, 'asset_file_handle', None) - asset_library = getattr(context, 'asset_library', None) + asset_library_ref = getattr(context, 'asset_library_ref', None) - if not asset_library: + if not asset_library_ref: cls.poll_message_set("No asset library selected") return False if not asset_file_handle: @@ -100,13 +100,13 @@ class ASSET_OT_open_containing_blend_file(Operator): def execute(self, context): asset_file_handle = context.asset_file_handle - asset_library = context.asset_library + asset_library_ref = context.asset_library_ref if asset_file_handle.local_id: self.report({'WARNING'}, "This asset is stored in the current blend file") return {'CANCELLED'} - asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library) + asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library_ref) self.open_in_new_blender(asset_lib_path) wm = context.window_manager diff --git a/release/scripts/startup/bl_operators/geometry_nodes.py b/release/scripts/startup/bl_operators/geometry_nodes.py index ec2887a1a74..258a73bd70b 100644 --- a/release/scripts/startup/bl_operators/geometry_nodes.py +++ b/release/scripts/startup/bl_operators/geometry_nodes.py @@ -42,8 +42,8 @@ def geometry_node_group_empty_new(): def geometry_modifier_poll(context): ob = context.object - # Test object support for geometry node modifier (No curve, or hair object support yet) - if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME'}: + # Test object support for geometry node modifier (No hair object support yet) + if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME', 'CURVE', 'FONT'}: return False return True diff --git a/release/scripts/startup/bl_ui/properties_data_curve.py b/release/scripts/startup/bl_ui/properties_data_curve.py index 85f672cd50f..e5b675db2c5 100644 --- a/release/scripts/startup/bl_ui/properties_data_curve.py +++ b/release/scripts/startup/bl_ui/properties_data_curve.py @@ -120,7 +120,6 @@ class DATA_PT_shape_curve(CurveButtonsPanel, Panel): sub = col.column() sub.active = (curve.dimensions == '2D' or (curve.bevel_mode != 'OBJECT' and curve.dimensions == '3D')) sub.prop(curve, "fill_mode") - col.prop(curve, "use_fill_deform") if is_curve: col = layout.column() diff --git a/release/scripts/startup/bl_ui/properties_freestyle.py b/release/scripts/startup/bl_ui/properties_freestyle.py index fd12747e2fa..3c765c10154 100644 --- a/release/scripts/startup/bl_ui/properties_freestyle.py +++ b/release/scripts/startup/bl_ui/properties_freestyle.py @@ -22,7 +22,6 @@ from bpy.types import Menu, Panel, UIList # Render properties - class RenderFreestyleButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -49,20 +48,17 @@ class RENDER_PT_freestyle(RenderFreestyleButtonsPanel, Panel): def draw(self, context): layout = self.layout layout.use_property_split = True - layout.use_property_decorate = False # No animation. - + layout.use_property_decorate = False rd = context.scene.render - layout.active = rd.use_freestyle - - layout.prop(rd, "line_thickness_mode", expand=True) - + layout.row().prop(rd, "line_thickness_mode", expand=True, text="Line Thickness Mode") if rd.line_thickness_mode == 'ABSOLUTE': layout.prop(rd, "line_thickness") # Render layer properties + class ViewLayerFreestyleButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -76,8 +72,12 @@ class ViewLayerFreestyleButtonsPanel: rd = scene.render with_freestyle = bpy.app.build_options.freestyle - return (scene and with_freestyle and rd.use_freestyle and - (context.engine in cls.COMPAT_ENGINES)) + return ( + scene + and with_freestyle + and rd.use_freestyle + and (context.engine in cls.COMPAT_ENGINES) + ) class ViewLayerFreestyleEditorButtonsPanel(ViewLayerFreestyleButtonsPanel): @@ -88,7 +88,35 @@ class ViewLayerFreestyleEditorButtonsPanel(ViewLayerFreestyleButtonsPanel): if not super().poll(context): return False view_layer = context.view_layer - return view_layer and view_layer.freestyle_settings.mode == 'EDITOR' + return ( + view_layer + and view_layer.use_freestyle + and view_layer.freestyle_settings.mode == 'EDITOR' + ) + + +class ViewLayerFreestyleLineStyle(ViewLayerFreestyleEditorButtonsPanel): + # Freestyle Linestyle Panels + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + @classmethod + def poll(cls, context): + if not super().poll(context): + return False + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + if lineset is None: + return False + linestyle = lineset.linestyle + if linestyle is None: + return False + + return True + + +class ViewLayerFreestyleLinestyleStrokesSubPanel(ViewLayerFreestyleLineStyle): + # Freestyle Linestyle Strokes sub panels + bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes" class VIEWLAYER_UL_linesets(UIList): @@ -126,54 +154,85 @@ class VIEWLAYER_PT_freestyle(ViewLayerFreestyleButtonsPanel, Panel): def draw(self, context): layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False view_layer = context.view_layer freestyle = view_layer.freestyle_settings layout.active = view_layer.use_freestyle - row = layout.row() - layout.prop(freestyle, "mode", text="Control Mode") - layout.prop(freestyle, "use_view_map_cache", text="View Map Cache") - layout.prop(freestyle, "as_render_pass", text="As Render Pass") - layout.label(text="Edge Detection Options:") + col = layout.column(align=True) + col.prop(freestyle, "mode", text="Control Mode") + col.prop(freestyle, "use_view_map_cache", text="View Map Cache") + col.prop(freestyle, "as_render_pass", text="As Render Pass") + + +class VIEWLAYER_PT_freestyle_edge_detection(ViewLayerFreestyleButtonsPanel, Panel): + bl_label = "Edge Detection" + bl_parent_id = "VIEWLAYER_PT_freestyle" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False - split = layout.split() + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + + layout.active = view_layer.use_freestyle - col = split.column() + col = layout.column() col.prop(freestyle, "crease_angle") col.prop(freestyle, "use_culling") - col.prop(freestyle, "use_advanced_options") - - col = split.column() col.prop(freestyle, "use_smoothness") + if freestyle.mode == 'SCRIPT': col.prop(freestyle, "use_material_boundaries") - # Advanced options are hidden by default to warn new users - if freestyle.use_advanced_options: - if freestyle.mode == 'SCRIPT': - row = layout.row() - row.prop(freestyle, "use_ridges_and_valleys") - row.prop(freestyle, "use_suggestive_contours") - row = layout.row() - row.prop(freestyle, "sphere_radius") - row.prop(freestyle, "kr_derivative_epsilon") - if freestyle.mode == 'SCRIPT': - row = layout.row() - row.label(text="Style Modules:") - row.operator("scene.freestyle_module_add", text="Add") - for module in freestyle.modules: - box = layout.box() - box.context_pointer_set("freestyle_module", module) - row = box.row(align=True) - row.prop(module, "use", text="") - row.prop(module, "script", text="") - row.operator("scene.freestyle_module_open", icon='FILEBROWSER', text="") - row.operator("scene.freestyle_module_remove", icon='X', text="") - row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP' - row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + col.prop(freestyle, "use_ridges_and_valleys") + col.prop(freestyle, "use_suggestive_contours") + col.prop(freestyle, "sphere_radius") + col.prop(freestyle, "kr_derivative_epsilon") + + +class VIEWLAYER_PT_freestyle_style_modules(ViewLayerFreestyleButtonsPanel, Panel): + bl_label = "Style Modules" + bl_parent_id = "VIEWLAYER_PT_freestyle" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + @classmethod + def poll(cls, context): + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + return freestyle.mode == 'SCRIPT' + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + + layout.active = view_layer.use_freestyle + + col = layout.column() + col.use_property_split = False + row = col.row() + row.operator("scene.freestyle_module_add", text="Add") + for module in freestyle.modules: + box = col.box() + box.context_pointer_set("freestyle_module", module) + row = box.row(align=True) + row.prop(module, "use", text="") + row.prop(module, "script", text="") + row.operator("scene.freestyle_module_open", icon='FILEBROWSER', text="") + row.operator("scene.freestyle_module_remove", icon='X', text="") + row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP' + row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN' class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel): @@ -198,10 +257,13 @@ class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel freestyle = view_layer.freestyle_settings lineset = freestyle.linesets.active - layout.active = view_layer.use_freestyle - row = layout.row() - rows = 4 if lineset else 2 + + is_sortable = len(freestyle.linesets) > 1 + rows = 3 + if is_sortable: + rows = 5 + row.template_list( "VIEWLAYER_UL_linesets", "", @@ -212,157 +274,504 @@ class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel rows=rows, ) - sub = row.column(align=True) - sub.operator("scene.freestyle_lineset_add", icon='ADD', text="") - sub.operator("scene.freestyle_lineset_remove", icon='REMOVE', text="") - sub.menu("RENDER_MT_lineset_context_menu", icon='DOWNARROW_HLT', text="") + col = row.column(align=True) + col.operator("scene.freestyle_lineset_add", icon='ADD', text="") + col.operator("scene.freestyle_lineset_remove", icon='REMOVE', text="") + + col.separator() + + col.menu("RENDER_MT_lineset_context_menu", icon="DOWNARROW_HLT", text="") + + if is_sortable: + col.separator() + col.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP' + col.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + if lineset: - sub.separator() - sub.separator() - sub.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP' - sub.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN' - - col = layout.column() - col.label(text="Selection By:") - row = col.row(align=True) - row.prop(lineset, "select_by_visibility", text="Visibility", toggle=True) - row.prop(lineset, "select_by_edge_types", text="Edge Types", toggle=True) - row.prop(lineset, "select_by_face_marks", text="Face Marks", toggle=True) - row.prop(lineset, "select_by_collection", text="Collection", toggle=True) - row.prop(lineset, "select_by_image_border", text="Image Border", toggle=True) - - if lineset.select_by_visibility: - col.label(text="Visibility:") - row = col.row(align=True) - row.prop(lineset, "visibility", expand=True) - if lineset.visibility == 'RANGE': - row = col.row(align=True) - row.prop(lineset, "qi_start") - row.prop(lineset, "qi_end") - - if lineset.select_by_edge_types: - col.label(text="Edge Types:") - row = col.row() - row.prop(lineset, "edge_type_negation", expand=True) - row.prop(lineset, "edge_type_combination", expand=True) - - split = col.split() - - sub = split.column() - self.draw_edge_type_buttons(sub, lineset, "silhouette") - self.draw_edge_type_buttons(sub, lineset, "border") - self.draw_edge_type_buttons(sub, lineset, "contour") - self.draw_edge_type_buttons(sub, lineset, "suggestive_contour") - self.draw_edge_type_buttons(sub, lineset, "ridge_valley") - - sub = split.column() - self.draw_edge_type_buttons(sub, lineset, "crease") - self.draw_edge_type_buttons(sub, lineset, "edge_mark") - self.draw_edge_type_buttons(sub, lineset, "external_contour") - self.draw_edge_type_buttons(sub, lineset, "material_boundary") - - if lineset.select_by_face_marks: - col.label(text="Face Marks:") - row = col.row() - row.prop(lineset, "face_mark_negation", expand=True) - row.prop(lineset, "face_mark_condition", expand=True) - - if lineset.select_by_collection: - col.label(text="Collection:") - row = col.row() - row.prop(lineset, "collection", text="") - row.prop(lineset, "collection_negation", expand=True) - - -class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Panel): - bl_label = "Freestyle Line Style" + layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new") + layout.separator() + col = layout.column(heading="Select by") + col.use_property_split = True + col.use_property_decorate = False + col.prop(lineset, "select_by_image_border", text="Image Border") + + +# Freestyle Lineset Sub Panels +class VIEWLAYER_PT_freestyle_lineset_visibilty(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Visibility" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_visibility", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_visibility + col = layout.column(align=True, heading="Type") + col.prop(lineset, "visibility", text="Type", expand=False) + + if lineset.visibility == 'RANGE': + col = layout.column(align=True) + col.use_property_split = True + col.prop(lineset, "qi_start") + col.prop(lineset, "qi_end") + + +class VIEWLAYER_PT_freestyle_lineset_edgetype(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Edge Type" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_edge_types", text="") + + def draw_edge_type_buttons(self, box, lineset, edge_type): + # property names + select_edge_type = "select_" + edge_type + exclude_edge_type = "exclude_" + edge_type + # draw edge type buttons + row = box.row(align=True) + row.prop(lineset, select_edge_type) + sub = row.column(align=True) + sub.prop(lineset, exclude_edge_type, text="") + sub.active = getattr(lineset, select_edge_type) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_edge_types + layout.row().prop(lineset, "edge_type_negation", expand=True, text="Negation") + layout.row().prop(lineset, "edge_type_combination", expand=True, text="Combination") + + col = layout.column(heading="Type") + self.draw_edge_type_buttons(col, lineset, "silhouette") + self.draw_edge_type_buttons(col, lineset, "crease") + self.draw_edge_type_buttons(col, lineset, "border") + self.draw_edge_type_buttons(col, lineset, "edge_mark") + self.draw_edge_type_buttons(col, lineset, "contour") + self.draw_edge_type_buttons(col, lineset, "external_contour") + self.draw_edge_type_buttons(col, lineset, "material_boundary") + self.draw_edge_type_buttons(col, lineset, "suggestive_contour") + self.draw_edge_type_buttons(col, lineset, "ridge_valley") + + +class VIEWLAYER_PT_freestyle_lineset_facemarks(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Face Marks" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_face_marks", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_face_marks + layout.row().prop(lineset, "face_mark_negation", expand=True, text="Negation") + layout.row().prop(lineset, "face_mark_condition", expand=True, text="Condition") + + +class VIEWLAYER_PT_freestyle_lineset_collection(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Collection" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + bl_options = {'DEFAULT_CLOSED'} - def draw_modifier_box_header(self, box, modifier): - row = box.row() - row.context_pointer_set("modifier", modifier) - if modifier.expanded: - icon = 'TRIA_DOWN' - else: - icon = 'TRIA_RIGHT' - row.prop(modifier, "expanded", text="", icon=icon, emboss=False) - # TODO: Use icons rather than text label, would save some room! - row.label(text=modifier.rna_type.name) - row.prop(modifier, "name", text="") - if modifier.use: - icon = 'RESTRICT_RENDER_OFF' - else: - icon = 'RESTRICT_RENDER_ON' - row.prop(modifier, "use", text="", icon=icon) - sub = row.row(align=True) - sub.operator("scene.freestyle_modifier_copy", icon='NONE', text="Copy") - sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP' - sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN' - sub.operator("scene.freestyle_modifier_remove", icon='X', text="") - - def draw_modifier_box_error(self, box, _modifier, message): - row = box.row() - row.label(text=message, icon='ERROR') - - def draw_modifier_common(self, box, modifier): - row = box.row() - row.prop(modifier, "blend", text="") - row.prop(modifier, "influence") - - def draw_modifier_color_ramp_common(self, box, modifier, has_range): - box.template_color_ramp(modifier, "color_ramp", expand=True) - if has_range: - row = box.row(align=True) - row.prop(modifier, "range_min") - row.prop(modifier, "range_max") - - def draw_modifier_curve_common(self, box, modifier, has_range, has_value): - row = box.row() - row.prop(modifier, "mapping", text="") - sub = row.column() - sub.prop(modifier, "invert") - if modifier.mapping == 'CURVE': - sub.active = False - box.template_curve_mapping(modifier, "curve") - if has_range: - row = box.row(align=True) - row.prop(modifier, "range_min") - row.prop(modifier, "range_max") - if has_value: - row = box.row(align=True) - row.prop(modifier, "value_min") - row.prop(modifier, "value_max") + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_collection", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_collection + layout.row().prop(lineset, "collection", text="Line Set Collection") + layout.row().prop(lineset, "collection_negation", expand=True, text="Negation") + + +# Linestyle Modifier Drawing code +def draw_modifier_box_header(box, modifier): + row = box.row() + row.use_property_split = False + row.context_pointer_set("modifier", modifier) + if modifier.expanded: + icon = 'TRIA_DOWN' + else: + icon = 'TRIA_RIGHT' + row.prop(modifier, "expanded", text="", icon=icon, emboss=False) + + sub = row.row(align=True) + sub.prop(modifier, "name", text="") + if modifier.use: + icon = 'RESTRICT_RENDER_OFF' + else: + icon = 'RESTRICT_RENDER_ON' + sub.prop(modifier, "use", text="", icon=icon) + sub.operator("scene.freestyle_modifier_copy", icon='DUPLICATE', text="") + + sub = row.row(align=True) + sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP' + sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + + row.operator("scene.freestyle_modifier_remove", icon='X', text="", emboss=False) + + +def draw_modifier_box_error(box, _modifier, message): + row = box.row() + row.label(text=message, icon='ERROR') + + +def draw_modifier_common(box, modifier): + col = box.column() + col.prop(modifier, "blend", text="Blend Mode") + col.prop(modifier, "influence") + + +def draw_modifier_color_ramp_common(box, modifier, has_range): + box.template_color_ramp(modifier, "color_ramp", expand=True) + if has_range: + col = box.column(align=True) + col.prop(modifier, "range_min", text="Range Min") + col.prop(modifier, "range_max", text="Max") + + +def draw_modifier_curve_common(box, modifier, has_range, has_value): + row = box.row() + row.prop(modifier, "mapping", text="Mapping") + if modifier.mapping == 'LINEAR': + box.prop(modifier, "invert") + if has_range: + col = box.column(align=True) + col.prop(modifier, "range_min", text="Range Min") + col.prop(modifier, "range_max", text="Max") + if has_value: + col = box.column(align=True) + col.prop(modifier, "value_min", text="Value Min") + col.prop(modifier, "value_max", text="Max") + if modifier.mapping == 'CURVE': + box.template_curve_mapping(modifier, "curve") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Strokes" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column(align=True) + col.prop(linestyle, "caps", expand=False) + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_chaining(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Chaining" + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_chaining", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + layout.active = linestyle.use_chaining + layout.row().prop(linestyle, "chaining", expand=True, text="Method") + if linestyle.chaining == 'SKETCHY': + layout.prop(linestyle, "rounds") + layout.prop(linestyle, "use_same_object") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Splitting" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + row = layout.row(align=False, heading="Min 2D Angle") + row.prop(linestyle, "use_angle_min", text="") + sub = row.row() + sub.active = linestyle.use_angle_min + sub.prop(linestyle, "angle_min", text="") + + row = layout.row(align=False, heading="Max 2D Angle") + row.prop(linestyle, "use_angle_max", text="") + sub = row.row() + sub.active = linestyle.use_angle_max + sub.prop(linestyle, "angle_max", text="") + + row = layout.row(align=False, heading="2D Length") + row.prop(linestyle, "use_split_length", text="") + sub = row.row() + sub.active = linestyle.use_split_length + sub.prop(linestyle, "split_length", text="") + + layout.prop(linestyle, "material_boundary") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Split Pattern" + bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes_splitting" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_split_pattern", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + layout.active = linestyle.use_split_pattern + + col = layout.column(align=True) + col.prop(linestyle, "split_dash1", text="Dash 1") + col.prop(linestyle, "split_dash2", text="2") + col.prop(linestyle, "split_dash3", text="3") + col = layout.column(align=True) + col.prop(linestyle, "split_gap1", text="Gap 1") + col.prop(linestyle, "split_gap2", text="2") + col.prop(linestyle, "split_gap3", text="3") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_sorting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Sorting" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_sorting", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + + layout.active = linestyle.use_sorting + + layout.prop(linestyle, "sort_key") + + row = layout.row() + row.active = linestyle.sort_key in { + 'DISTANCE_FROM_CAMERA', + 'PROJECTED_X', + 'PROJECTED_Y', + } + row.prop(linestyle, "integration_type") + layout.row().prop(linestyle, "sort_order", expand=True, text="Sort Order") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_selection(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Selection" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + row = layout.row(align=False, heading="Min 2D Length") + row.prop(linestyle, "use_length_min", text="") + sub = row.row() + sub.active = linestyle.use_length_min + sub.prop(linestyle, "length_min", text="") + + row = layout.row(align=False, heading="Max 2D Length") + row.prop(linestyle, "use_length_max", text="") + sub = row.row() + sub.active = linestyle.use_length_max + sub.prop(linestyle, "length_max", text="") + + row = layout.row(align=False, heading="Chain Count") + row.prop(linestyle, "use_chain_count", text="") + sub = row.row() + sub.active = linestyle.use_chain_count + sub.prop(linestyle, "chain_count", text="") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Dashed Line" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_dashed_line", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + layout.active = linestyle.use_dashed_line + + col = layout.column(align=True) + col.prop(linestyle, "dash1", text="Dash 1") + col.prop(linestyle, "dash2", text="2") + col.prop(linestyle, "dash3", text="3") + col = layout.column(align=True) + col.prop(linestyle, "gap1", text="Gap 1") + col.prop(linestyle, "gap2", text="2") + col.prop(linestyle, "gap3", text="3") + + +class VIEWLAYER_PT_freestyle_linestyle_color(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Color" + bl_options = {'DEFAULT_CLOSED'} def draw_color_modifier(self, context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() - self.draw_modifier_common(box, modifier) + draw_modifier_common(box, modifier) if modifier.type == 'ALONG_STROKE': - self.draw_modifier_color_ramp_common(box, modifier, False) + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'DISTANCE_FROM_OBJECT': box.prop(modifier, "target") - self.draw_modifier_color_ramp_common(box, modifier, True) + draw_modifier_color_ramp_common(box, modifier, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'COLOR' prop.name = modifier.name elif modifier.type == 'DISTANCE_FROM_CAMERA': - self.draw_modifier_color_ramp_common(box, modifier, True) + draw_modifier_color_ramp_common(box, modifier, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'COLOR' prop.name = modifier.name elif modifier.type == 'MATERIAL': row = box.row() - row.prop(modifier, "material_attribute", text="") - sub = row.column() + row.prop(modifier, "material_attribute", + text="Material Attribute") + sub = box.column() sub.prop(modifier, "use_ramp") if modifier.material_attribute in {'LINE', 'DIFF', 'SPEC'}: sub.active = True @@ -371,166 +780,292 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan sub.active = False show_ramp = True if show_ramp: - self.draw_modifier_color_ramp_common(box, modifier, False) + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'TANGENT': - self.draw_modifier_color_ramp_common(box, modifier, False) + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'NOISE': - self.draw_modifier_color_ramp_common(box, modifier, False) - row = box.row(align=False) - row.prop(modifier, "amplitude") - row.prop(modifier, "period") - row.prop(modifier, "seed") + subcol = box.column(align=True) + subcol.prop(modifier, "amplitude") + subcol.prop(modifier, "period") + subcol.prop(modifier, "seed") + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'CREASE_ANGLE': - self.draw_modifier_color_ramp_common(box, modifier, False) - row = box.row(align=True) - row.prop(modifier, "angle_min") - row.prop(modifier, "angle_max") + subcol = box.column(align=True) + subcol.prop(modifier, "angle_min", text="Angle Min") + subcol.prop(modifier, "angle_max", text="Max") + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'CURVATURE_3D': - self.draw_modifier_color_ramp_common(box, modifier, False) - row = box.row(align=True) - row.prop(modifier, "curvature_min") - row.prop(modifier, "curvature_max") + subcol = box.column(align=True) + subcol.prop(modifier, "curvature_min", text="Curvature Min") + subcol.prop(modifier, "curvature_max", text="Max") + + draw_modifier_color_ramp_common(box, modifier, False) + freestyle = context.view_layer.freestyle_settings if not freestyle.use_smoothness: message = "Enable Face Smoothness to use this modifier" - self.draw_modifier_box_error(col.box(), modifier, message) + draw_modifier_box_error(col.box(), modifier, message) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column() + row = col.row() + row.prop(linestyle, "color", text="Base Color") + col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.color_modifiers: + self.draw_color_modifier(context, modifier) + + +class VIEWLAYER_PT_freestyle_linestyle_alpha(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Alpha" + bl_options = {'DEFAULT_CLOSED'} def draw_alpha_modifier(self, context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() - self.draw_modifier_common(box, modifier) + draw_modifier_common(box, modifier) if modifier.type == 'ALONG_STROKE': - self.draw_modifier_curve_common(box, modifier, False, False) + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'DISTANCE_FROM_OBJECT': box.prop(modifier, "target") - self.draw_modifier_curve_common(box, modifier, True, False) + draw_modifier_curve_common(box, modifier, True, False) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'ALPHA' prop.name = modifier.name elif modifier.type == 'DISTANCE_FROM_CAMERA': - self.draw_modifier_curve_common(box, modifier, True, False) + draw_modifier_curve_common(box, modifier, True, False) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'ALPHA' prop.name = modifier.name elif modifier.type == 'MATERIAL': - box.prop(modifier, "material_attribute", text="") - self.draw_modifier_curve_common(box, modifier, False, False) + box.prop(modifier, "material_attribute", text="Material Attribute") + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'TANGENT': - self.draw_modifier_curve_common(box, modifier, False, False) + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'NOISE': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=False) - row.prop(modifier, "amplitude") - row.prop(modifier, "period") - row.prop(modifier, "seed") + col = box.column(align=True) + col.prop(modifier, "amplitude") + col.prop(modifier, "period") + col.prop(modifier, "seed") + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'CREASE_ANGLE': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "angle_min") - row.prop(modifier, "angle_max") + col = box.column(align=True) + col.prop(modifier, "angle_min", text="Angle Min") + col.prop(modifier, "angle_max", text="Max") + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'CURVATURE_3D': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "curvature_min") - row.prop(modifier, "curvature_max") + draw_modifier_curve_common(box, modifier, False, False) + + subcol = box.column(align=True) + subcol.prop(modifier, "curvature_min", text="Curvature Min") + subcol.prop(modifier, "curvature_max", text="Max") + freestyle = context.view_layer.freestyle_settings if not freestyle.use_smoothness: message = "Enable Face Smoothness to use this modifier" - self.draw_modifier_box_error(col.box(), modifier, message) + draw_modifier_box_error(col.box(), modifier, message) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column() + row = col.row() + row.prop(linestyle, "alpha", text="Base Transparency") + col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.alpha_modifiers: + self.draw_alpha_modifier(context, modifier) + + +class VIEWLAYER_PT_freestyle_linestyle_thickness(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Thickness" + bl_options = {'DEFAULT_CLOSED'} def draw_thickness_modifier(self, context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() - self.draw_modifier_common(box, modifier) + draw_modifier_common(box, modifier) if modifier.type == 'ALONG_STROKE': - self.draw_modifier_curve_common(box, modifier, False, True) + draw_modifier_curve_common(box, modifier, False, True) elif modifier.type == 'DISTANCE_FROM_OBJECT': box.prop(modifier, "target") - self.draw_modifier_curve_common(box, modifier, True, True) + draw_modifier_curve_common(box, modifier, True, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'THICKNESS' prop.name = modifier.name elif modifier.type == 'DISTANCE_FROM_CAMERA': - self.draw_modifier_curve_common(box, modifier, True, True) + draw_modifier_curve_common(box, modifier, True, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'THICKNESS' prop.name = modifier.name elif modifier.type == 'MATERIAL': - box.prop(modifier, "material_attribute", text="") - self.draw_modifier_curve_common(box, modifier, False, True) + box.prop(modifier, "material_attribute", text="Material Attribute") + draw_modifier_curve_common(box, modifier, False, True) elif modifier.type == 'CALLIGRAPHY': box.prop(modifier, "orientation") - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") + subcol = box.column(align=True) + subcol.prop(modifier, "thickness_min", text="Thickness Min") + subcol.prop(modifier, "thickness_max", text="Max") elif modifier.type == 'TANGENT': - self.draw_modifier_curve_common(box, modifier, False, False) self.mapping = 'CURVE' - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") + subcol = box.column(align=True) + subcol.prop(modifier, "thickness_min", text="Thickness Min") + subcol.prop(modifier, "thickness_max", text="Max") + + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'NOISE': - row = box.row(align=False) - row.prop(modifier, "amplitude") - row.prop(modifier, "period") - row = box.row(align=False) - row.prop(modifier, "seed") - row.prop(modifier, "use_asymmetric") + col = box.column(align=True) + col.prop(modifier, "amplitude") + col.prop(modifier, "period") + + col = box.column(align=True) + col.prop(modifier, "seed") + col.prop(modifier, "use_asymmetric") elif modifier.type == 'CREASE_ANGLE': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") - row = box.row(align=True) - row.prop(modifier, "angle_min") - row.prop(modifier, "angle_max") + col = box.column(align=True) + col.prop(modifier, "thickness_min", text="Thickness Min") + col.prop(modifier, "thickness_max", text="Max") + + col = box.column(align=True) + col.prop(modifier, "angle_min", text="Angle Min") + col.prop(modifier, "angle_max", text="Max") + + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'CURVATURE_3D': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") - row = box.row(align=True) - row.prop(modifier, "curvature_min") - row.prop(modifier, "curvature_max") + subcol = box.column(align=True) + subcol.prop(modifier, "thickness_min", text="Thickness Min") + subcol.prop(modifier, "thickness_max", text="Max") + + subcol = box.column(align=True) + subcol.prop(modifier, "curvature_min") + subcol.prop(modifier, "curvature_max") + + draw_modifier_curve_common(box, modifier, False, False) + freestyle = context.view_layer.freestyle_settings if not freestyle.use_smoothness: message = "Enable Face Smoothness to use this modifier" - self.draw_modifier_box_error(col.box(), modifier, message) + draw_modifier_box_error(col.box(), modifier, message) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column() + row = col.row() + row.prop(linestyle, "thickness", text="Base Thickness") + subcol = col.column() + subcol.active = linestyle.chaining == 'PLAIN' and linestyle.use_same_object + row = subcol.row() + row.prop(linestyle, "thickness_position", expand=False) + + if linestyle.thickness_position == 'RELATIVE': + row = subcol.row() + row.prop(linestyle, "thickness_ratio") + + col = layout.column() + col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.thickness_modifiers: + self.draw_thickness_modifier(context, modifier) + + +class VIEWLAYER_PT_freestyle_linestyle_geometry(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Geometry" + bl_options = {'DEFAULT_CLOSED'} def draw_geometry_modifier(self, _context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() @@ -541,40 +1076,40 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan box.prop(modifier, "error") elif modifier.type == 'SINUS_DISPLACEMENT': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "wavelength") col.prop(modifier, "amplitude") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "phase") elif modifier.type == 'SPATIAL_NOISE': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "amplitude") col.prop(modifier, "scale") col.prop(modifier, "octaves") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "smooth") col.prop(modifier, "use_pure_random") elif modifier.type == 'PERLIN_NOISE_1D': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "frequency") col.prop(modifier, "amplitude") col.prop(modifier, "seed") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "octaves") col.prop(modifier, "angle") elif modifier.type == 'PERLIN_NOISE_2D': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "frequency") col.prop(modifier, "amplitude") col.prop(modifier, "seed") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "octaves") col.prop(modifier, "angle") @@ -594,33 +1129,34 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan row = box.row() row.prop(modifier, "shape", expand=True) box.prop(modifier, "rounds") - row = box.row() + subcol = box.column(align=True) if modifier.shape in {'CIRCLES', 'ELLIPSES'}: - row.prop(modifier, "random_radius") - row.prop(modifier, "random_center") + subcol.prop(modifier, "random_radius", text="Random Radius") + subcol.prop(modifier, "random_center", text="Center") elif modifier.shape == 'SQUARES': - row.prop(modifier, "backbone_length") - row.prop(modifier, "random_backbone") + subcol.prop(modifier, "backbone_length", text="Backbone Length") + subcol.prop(modifier, "random_backbone", text="Randomness") elif modifier.type == '2D_OFFSET': - row = box.row(align=True) - row.prop(modifier, "start") - row.prop(modifier, "end") - row = box.row(align=True) - row.prop(modifier, "x") - row.prop(modifier, "y") + subcol = box.column(align=True) + subcol.prop(modifier, "start") + subcol.prop(modifier, "end") + + subcol = box.column(align=True) + subcol.prop(modifier, "x") + subcol.prop(modifier, "y") elif modifier.type == '2D_TRANSFORM': box.prop(modifier, "pivot") if modifier.pivot == 'PARAM': box.prop(modifier, "pivot_u") elif modifier.pivot == 'ABSOLUTE': - row = box.row(align=True) - row.prop(modifier, "pivot_x") - row.prop(modifier, "pivot_y") - row = box.row(align=True) - row.prop(modifier, "scale_x") - row.prop(modifier, "scale_y") + subcol = box.column(align=True) + subcol.prop(modifier, "pivot_x", text="Pivot X") + subcol.prop(modifier, "pivot_y", text="Y") + subcol = box.column(align=True) + subcol.prop(modifier, "scale_x", text="Scale X") + subcol.prop(modifier, "scale_y", text="Y") box.prop(modifier, "angle") elif modifier.type == 'SIMPLIFICATION': @@ -628,6 +1164,8 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan def draw(self, context): layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False view_layer = context.view_layer lineset = view_layer.freestyle_settings.linesets.active @@ -638,181 +1176,61 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan return linestyle = lineset.linestyle - layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new") if linestyle is None: return + row = layout.row(align=True) - row.prop(linestyle, "panel", expand=True) - if linestyle.panel == 'STROKES': - # Chaining - layout.prop(linestyle, "use_chaining", text="Chaining:") - split = layout.split(align=True) - split.active = linestyle.use_chaining - # First column - col = split.column() - col.active = linestyle.use_chaining - col.prop(linestyle, "chaining", text="") - if linestyle.chaining == 'SKETCHY': - col.prop(linestyle, "rounds") - # Second column - col = split.column() - col.prop(linestyle, "use_same_object") - - # Splitting - layout.label(text="Splitting:") - split = layout.split(align=True) - # First column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_angle_min", text="") - sub = row.row() - sub.active = linestyle.use_angle_min - sub.prop(linestyle, "angle_min") - row = col.row(align=True) - row.prop(linestyle, "use_angle_max", text="") - sub = row.row() - sub.active = linestyle.use_angle_max - sub.prop(linestyle, "angle_max") - # Second column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_split_length", text="") - sub = row.row() - sub.active = linestyle.use_split_length - sub.prop(linestyle, "split_length", text="2D Length") - row = col.row(align=True) - row.prop(linestyle, "material_boundary") - # End of columns - row = layout.row(align=True) - row.prop(linestyle, "use_split_pattern", text="") - sub = row.row(align=True) - sub.active = linestyle.use_split_pattern - sub.prop(linestyle, "split_dash1", text="D1") - sub.prop(linestyle, "split_gap1", text="G1") - sub.prop(linestyle, "split_dash2", text="D2") - sub.prop(linestyle, "split_gap2", text="G2") - sub.prop(linestyle, "split_dash3", text="D3") - sub.prop(linestyle, "split_gap3", text="G3") - - # Sorting - layout.prop(linestyle, "use_sorting", text="Sorting:") - col = layout.column() - col.active = linestyle.use_sorting - row = col.row(align=True) - row.prop(linestyle, "sort_key", text="") - sub = row.row() - sub.active = linestyle.sort_key in {'DISTANCE_FROM_CAMERA', - 'PROJECTED_X', - 'PROJECTED_Y'} - sub.prop(linestyle, "integration_type", text="") - row = col.row(align=True) - row.prop(linestyle, "sort_order", expand=True) - - # Selection - layout.label(text="Selection:") - split = layout.split(align=True) - # First column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_length_min", text="") - sub = row.row() - sub.active = linestyle.use_length_min - sub.prop(linestyle, "length_min") - row = col.row(align=True) - row.prop(linestyle, "use_length_max", text="") - sub = row.row() - sub.active = linestyle.use_length_max - sub.prop(linestyle, "length_max") - # Second column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_chain_count", text="") - sub = row.row() - sub.active = linestyle.use_chain_count - sub.prop(linestyle, "chain_count") - - # Caps - layout.label(text="Caps:") - row = layout.row(align=True) - row.prop(linestyle, "caps", expand=True) - - # Dashed lines - layout.prop(linestyle, "use_dashed_line", text="Dashed Line:") - row = layout.row(align=True) - row.active = linestyle.use_dashed_line - row.prop(linestyle, "dash1", text="D1") - row.prop(linestyle, "gap1", text="G1") - row.prop(linestyle, "dash2", text="D2") - row.prop(linestyle, "gap2", text="G2") - row.prop(linestyle, "dash3", text="D3") - row.prop(linestyle, "gap3", text="G3") - - elif linestyle.panel == 'COLOR': - col = layout.column() - row = col.row() - row.label(text="Base Color:") - row.prop(linestyle, "color", text="") - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.color_modifiers: - self.draw_color_modifier(context, modifier) - - elif linestyle.panel == 'ALPHA': - col = layout.column() - row = col.row() - row.label(text="Base Transparency:") - row.prop(linestyle, "alpha") - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.alpha_modifiers: - self.draw_alpha_modifier(context, modifier) - - elif linestyle.panel == 'THICKNESS': - col = layout.column() - row = col.row() - row.label(text="Base Thickness:") - row.prop(linestyle, "thickness") - subcol = col.column() - subcol.active = linestyle.chaining == 'PLAIN' and linestyle.use_same_object - row = subcol.row() - row.prop(linestyle, "thickness_position", expand=True) - row = subcol.row() - row.prop(linestyle, "thickness_ratio") - row.active = (linestyle.thickness_position == 'RELATIVE') - col = layout.column() - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.thickness_modifiers: - self.draw_thickness_modifier(context, modifier) - - elif linestyle.panel == 'GEOMETRY': - col = layout.column() - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.geometry_modifiers: - self.draw_geometry_modifier(context, modifier) - - elif linestyle.panel == 'TEXTURE': - layout.separator() + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) - row = layout.row() - row.prop(linestyle, "use_nodes") - row.prop(linestyle, "texture_spacing", text="Spacing Along Stroke") + col = layout.column() + col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.geometry_modifiers: + self.draw_geometry_modifier(context, modifier) - row = layout.row() - props = row.operator( - "wm.properties_context_change", - text="Go to Linestyle Textures Properties", - icon='TEXTURE', - ) - props.context = 'TEXTURE' - elif linestyle.panel == 'MISC': - pass +class VIEWLAYER_PT_freestyle_linestyle_texture(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Texture" + bl_options = {'DEFAULT_CLOSED'} + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False -# Material properties + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + layout.prop(linestyle, "use_nodes") + layout.prop(linestyle, "texture_spacing", text="Spacing Along Stroke") + + row = layout.row() + props = row.operator( + "wm.properties_context_change", + text="Go to Linestyle Textures Properties", + icon='TEXTURE', + ) + props.context = 'TEXTURE' + + +# Material properties class MaterialFreestyleButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -825,8 +1243,11 @@ class MaterialFreestyleButtonsPanel: material = context.material with_freestyle = bpy.app.build_options.freestyle return ( - with_freestyle and material and scene and scene.render.use_freestyle and - (context.engine in cls.COMPAT_ENGINES) + with_freestyle + and material + and scene + and scene.render.use_freestyle + and (context.engine in cls.COMPAT_ENGINES) ) @@ -837,12 +1258,14 @@ class MATERIAL_PT_freestyle_line(MaterialFreestyleButtonsPanel, Panel): def draw(self, context): layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False mat = context.material - row = layout.row() - row.prop(mat, "line_color", text="") - row.prop(mat, "line_priority", text="Priority") + col = layout.column() + col.prop(mat, "line_color") + col.prop(mat, "line_priority", text="Priority") classes = ( @@ -850,12 +1273,30 @@ classes = ( VIEWLAYER_UL_linesets, RENDER_MT_lineset_context_menu, VIEWLAYER_PT_freestyle, + VIEWLAYER_PT_freestyle_edge_detection, + VIEWLAYER_PT_freestyle_style_modules, VIEWLAYER_PT_freestyle_lineset, - VIEWLAYER_PT_freestyle_linestyle, + VIEWLAYER_PT_freestyle_lineset_visibilty, + VIEWLAYER_PT_freestyle_lineset_edgetype, + VIEWLAYER_PT_freestyle_lineset_facemarks, + VIEWLAYER_PT_freestyle_lineset_collection, + VIEWLAYER_PT_freestyle_linestyle_strokes, + VIEWLAYER_PT_freestyle_linestyle_strokes_chaining, + VIEWLAYER_PT_freestyle_linestyle_strokes_splitting, + VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern, + VIEWLAYER_PT_freestyle_linestyle_strokes_sorting, + VIEWLAYER_PT_freestyle_linestyle_strokes_selection, + VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline, + VIEWLAYER_PT_freestyle_linestyle_color, + VIEWLAYER_PT_freestyle_linestyle_alpha, + VIEWLAYER_PT_freestyle_linestyle_thickness, + VIEWLAYER_PT_freestyle_linestyle_geometry, + VIEWLAYER_PT_freestyle_linestyle_texture, MATERIAL_PT_freestyle_line, ) if __name__ == "__main__": # only for live edit. from bpy.utils import register_class + for cls in classes: register_class(cls) diff --git a/release/scripts/startup/bl_ui/properties_mask_common.py b/release/scripts/startup/bl_ui/properties_mask_common.py index 40a704a65dd..705539e979c 100644 --- a/release/scripts/startup/bl_ui/properties_mask_common.py +++ b/release/scripts/startup/bl_ui/properties_mask_common.py @@ -417,6 +417,7 @@ class MASK_MT_select(Menu): layout.operator("mask.select_box") layout.operator("mask.select_circle") + layout.operator_menu_enum("mask.select_lasso", "mode") layout.separator() diff --git a/release/scripts/startup/bl_ui/properties_object.py b/release/scripts/startup/bl_ui/properties_object.py index 52af4fafd09..81a641a20cf 100644 --- a/release/scripts/startup/bl_ui/properties_object.py +++ b/release/scripts/startup/bl_ui/properties_object.py @@ -267,7 +267,8 @@ class OBJECT_PT_instancing(ObjectButtonsPanel, Panel): @classmethod def poll(cls, context): ob = context.object - return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD'}) + # FONT objects need (vertex) instancing for the 'Object Font' feature + return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD', 'FONT'}) def draw(self, context): layout = self.layout diff --git a/release/scripts/startup/bl_ui/properties_output.py b/release/scripts/startup/bl_ui/properties_output.py index 0c1a26ceec1..d96a53f6ab8 100644 --- a/release/scripts/startup/bl_ui/properties_output.py +++ b/release/scripts/startup/bl_ui/properties_output.py @@ -25,8 +25,8 @@ from bl_ui.utils import PresetPanel from bpy.app.translations import pgettext_tip as tip_ -class RENDER_PT_presets(PresetPanel, Panel): - bl_label = "Render Presets" +class RENDER_PT_format_presets(PresetPanel, Panel): + bl_label = "Format Presets" preset_subdir = "render" preset_operator = "script.execute_preset" preset_add_operator = "render.preset_add" @@ -56,21 +56,21 @@ class RenderOutputButtonsPanel: return (context.engine in cls.COMPAT_ENGINES) -class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): - bl_label = "Dimensions" +class RENDER_PT_format(RenderOutputButtonsPanel, Panel): + bl_label = "Format" COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} _frame_rate_args_prev = None _preset_class = None def draw_header_preset(self, _context): - RENDER_PT_presets.draw_panel_header(self.layout) + RENDER_PT_format_presets.draw_panel_header(self.layout) @staticmethod def _draw_framerate_label(*args): # avoids re-creating text string each draw - if RENDER_PT_dimensions._frame_rate_args_prev == args: - return RENDER_PT_dimensions._frame_rate_ret + if RENDER_PT_format._frame_rate_args_prev == args: + return RENDER_PT_format._frame_rate_ret fps, fps_base, preset_label = args @@ -89,17 +89,17 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): fps_label_text = tip_("%.4g fps") % fps_rate show_framerate = (preset_label == "Custom") - RENDER_PT_dimensions._frame_rate_args_prev = args - RENDER_PT_dimensions._frame_rate_ret = args = (fps_label_text, show_framerate) + RENDER_PT_format._frame_rate_args_prev = args + RENDER_PT_format._frame_rate_ret = args = (fps_label_text, show_framerate) return args @staticmethod def draw_framerate(layout, rd): - if RENDER_PT_dimensions._preset_class is None: - RENDER_PT_dimensions._preset_class = bpy.types.RENDER_MT_framerate_presets + if RENDER_PT_format._preset_class is None: + RENDER_PT_format._preset_class = bpy.types.RENDER_MT_framerate_presets - args = rd.fps, rd.fps_base, RENDER_PT_dimensions._preset_class.bl_label - fps_label_text, show_framerate = RENDER_PT_dimensions._draw_framerate_label(*args) + args = rd.fps, rd.fps_base, RENDER_PT_format._preset_class.bl_label + fps_label_text, show_framerate = RENDER_PT_format._draw_framerate_label(*args) layout.menu("RENDER_MT_framerate_presets", text=fps_label_text) @@ -113,8 +113,7 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): layout.use_property_split = True layout.use_property_decorate = False # No animation. - scene = context.scene - rd = scene.render + rd = context.scene.render col = layout.column(align=True) col.prop(rd, "resolution_x", text="Resolution X") @@ -131,18 +130,30 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): sub.active = rd.use_border sub.prop(rd, "use_crop_to_border") + col = layout.column(heading="Frame Rate") + self.draw_framerate(col, rd) + + +class RENDER_PT_frame_range(RenderOutputButtonsPanel, Panel): + bl_label = "Frame Range" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + scene = context.scene + col = layout.column(align=True) col.prop(scene, "frame_start", text="Frame Start") col.prop(scene, "frame_end", text="End") col.prop(scene, "frame_step", text="Step") - col = layout.column(heading="Frame Rate") - self.draw_framerate(col, rd) - -class RENDER_PT_frame_remapping(RenderOutputButtonsPanel, Panel): - bl_label = "Time Remapping" - bl_parent_id = "RENDER_PT_dimensions" +class RENDER_PT_time_stretching(RenderOutputButtonsPanel, Panel): + bl_label = "Time Stretching" + bl_parent_id = "RENDER_PT_frame_range" bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} @@ -481,11 +492,12 @@ class RENDER_PT_stereoscopy(RenderOutputButtonsPanel, Panel): classes = ( - RENDER_PT_presets, + RENDER_PT_format_presets, RENDER_PT_ffmpeg_presets, RENDER_MT_framerate_presets, - RENDER_PT_dimensions, - RENDER_PT_frame_remapping, + RENDER_PT_format, + RENDER_PT_frame_range, + RENDER_PT_time_stretching, RENDER_PT_stereoscopy, RENDER_PT_output, RENDER_PT_output_views, diff --git a/release/scripts/startup/bl_ui/space_clip.py b/release/scripts/startup/bl_ui/space_clip.py index a1e5b509295..c18d77987ad 100644 --- a/release/scripts/startup/bl_ui/space_clip.py +++ b/release/scripts/startup/bl_ui/space_clip.py @@ -1578,6 +1578,7 @@ class CLIP_MT_select(Menu): layout.operator("clip.select_box") layout.operator("clip.select_circle") + layout.operator_menu_enum("clip.select_lasso", "mode") layout.separator() diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py index 84d1c36c53d..781c430a752 100644 --- a/release/scripts/startup/bl_ui/space_dopesheet.py +++ b/release/scripts/startup/bl_ui/space_dopesheet.py @@ -394,6 +394,7 @@ class DOPESHEET_MT_select(Menu): layout.operator("action.select_box", text="Box Select (Axis Range)").axis_range = True layout.operator("action.select_circle") + layout.operator_menu_enum("action.select_lasso", "mode") layout.separator() layout.operator("action.select_column", text="Columns on Selected Keys").mode = 'KEYS' diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index ca018216a5a..e52136fc416 100644 --- a/release/scripts/startup/bl_ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -267,7 +267,7 @@ class FILEBROWSER_PT_bookmarks_system(Panel): @classmethod def poll(cls, context): return ( - not context.preferences.filepaths.hide_system_bookmarks and + context.preferences.filepaths.show_system_bookmarks and panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) ) @@ -300,7 +300,7 @@ class FILEBROWSER_PT_bookmarks_favorites(FileBrowserPanel, Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOLS' bl_category = "Bookmarks" - bl_label = "Favorites" + bl_label = "Bookmarks" @classmethod def poll(cls, context): @@ -345,7 +345,7 @@ class FILEBROWSER_PT_bookmarks_recents(Panel): @classmethod def poll(cls, context): return ( - not context.preferences.filepaths.hide_recent_locations and + context.preferences.filepaths.show_recent_locations and panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) ) diff --git a/release/scripts/startup/bl_ui/space_graph.py b/release/scripts/startup/bl_ui/space_graph.py index f8521592dd9..612b7ba2b09 100644 --- a/release/scripts/startup/bl_ui/space_graph.py +++ b/release/scripts/startup/bl_ui/space_graph.py @@ -179,6 +179,7 @@ class GRAPH_MT_select(Menu): props.include_handles = True layout.operator("graph.select_circle") + layout.operator_menu_enum("graph.select_lasso", "mode") layout.separator() layout.operator("graph.select_column", text="Columns on Selected Keys").mode = 'KEYS' diff --git a/release/scripts/startup/bl_ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py index fba86676ad4..5f36009901a 100644 --- a/release/scripts/startup/bl_ui/space_node.py +++ b/release/scripts/startup/bl_ui/space_node.py @@ -289,6 +289,7 @@ class NODE_MT_select(Menu): layout.operator("node.select_box").tweak = False layout.operator("node.select_circle") + layout.operator_menu_enum("node.select_lasso", "mode") layout.separator() layout.operator("node.select_all").action = 'TOGGLE' diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 30115618f3d..88cf8db686c 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -191,16 +191,6 @@ class SEQUENCER_PT_overlay(Panel): pass -class SEQUENCER_PT_overlay(Panel): - bl_space_type = 'SEQUENCE_EDITOR' - bl_region_type = 'HEADER' - bl_label = "Overlays" - bl_ui_units_x = 7 - - def draw(self, _context): - pass - - class SEQUENCER_PT_preview_overlay(Panel): bl_space_type = 'SEQUENCE_EDITOR' bl_region_type = 'HEADER' @@ -215,13 +205,14 @@ class SEQUENCER_PT_preview_overlay(Panel): def draw(self, context): ed = context.scene.sequence_editor st = context.space_data + overlay_settings = st.preview_overlay layout = self.layout layout.active = st.show_strip_overlay layout.prop(ed, "show_overlay", text="Frame Overlay") - layout.prop(st, "show_safe_areas", text="Safe Areas") - layout.prop(st, "show_metadata", text="Metadata") - layout.prop(st, "show_annotation", text="Annotations") + layout.prop(overlay_settings, "show_safe_areas", text="Safe Areas") + layout.prop(overlay_settings, "show_metadata", text="Metadata") + layout.prop(overlay_settings, "show_annotation", text="Annotations") class SEQUENCER_PT_sequencer_overlay(Panel): @@ -237,23 +228,24 @@ class SEQUENCER_PT_sequencer_overlay(Panel): def draw(self, context): st = context.space_data + overlay_settings = st.timeline_overlay layout = self.layout layout.active = st.show_strip_overlay - layout.prop(st, "show_strip_name", text="Name") - layout.prop(st, "show_strip_source", text="Source") - layout.prop(st, "show_strip_duration", text="Duration") + layout.prop(overlay_settings, "show_strip_name", text="Name") + layout.prop(overlay_settings, "show_strip_source", text="Source") + layout.prop(overlay_settings, "show_strip_duration", text="Duration") layout.separator() - layout.prop(st, "show_strip_offset", text="Offsets") - layout.prop(st, "show_fcurves", text="F-Curves") - layout.prop(st, "show_grid", text="Grid") + layout.prop(overlay_settings, "show_strip_offset", text="Offsets") + layout.prop(overlay_settings, "show_fcurves", text="F-Curves") + layout.prop(overlay_settings, "show_grid", text="Grid") layout.separator() - layout.prop_menu_enum(st, "waveform_display_type") + layout.prop_menu_enum(overlay_settings, "waveform_display_type") class SEQUENCER_MT_view_cache(Menu): @@ -1659,28 +1651,48 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel): def draw(self, context): layout = self.layout - layout.use_property_split = True + layout.use_property_split = False st = context.space_data + overlay_settings = st.timeline_overlay strip = context.active_sequence_strip sound = strip.sound layout.active = not strip.mute - col = layout.column() - - col.prop(strip, "volume", text="Volume") - col.prop(strip, "pitch") + if sound is not None: + col = layout.column() - col = layout.column() - col.prop(strip, "pan") - col.enabled = sound is not None and sound.use_mono + split = col.split(factor=0.4) + split.label(text="") + split.prop(sound, "use_mono") + if overlay_settings.waveform_display_type == 'DEFAULT_WAVEFORMS': + split = col.split(factor=0.4) + split.label(text="") + split.prop(strip, "show_waveform") - if sound is not None: col = layout.column() - if st.waveform_display_type == 'DEFAULT_WAVEFORMS': - col.prop(strip, "show_waveform") - col.prop(sound, "use_mono") + + split = col.split(factor=0.4) + split.alignment = 'RIGHT' + split.label(text="Volume") + split.prop(strip, "volume", text="") + + split = col.split(factor=0.4) + split.alignment = 'RIGHT' + split.label(text="Pitch") + split.prop(strip, "pitch", text="") + + split = col.split(factor=0.4) + split.alignment = 'RIGHT' + split.label(text="Pan") + audio_channels = context.scene.render.ffmpeg.audio_channels + pan_text = "" + if audio_channels != 'MONO' and audio_channels != 'STEREO': + pan_text = "%.2f°" % (strip.pan * 90) + split.prop(strip, "pan", text=pan_text) + split.enabled = sound.use_mono and audio_channels != 'MONO' + class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel): @@ -2081,17 +2093,16 @@ class SEQUENCER_PT_view_safe_areas(SequencerButtonsPanel_Output, Panel): return is_preview and (st.display_mode == 'IMAGE') def draw_header(self, context): - st = context.space_data - - self.layout.prop(st, "show_safe_areas", text="") + overlay_settings = context.space_data.preview_overlay + self.layout.prop(overlay_settings, "show_safe_areas", text="") def draw(self, context): layout = self.layout layout.use_property_split = True - st = context.space_data + overlay_settings = context.space_data.preview_overlay safe_data = context.scene.safe_areas - layout.active = st.show_safe_areas + layout.active = overlay_settings.show_safe_areas col = layout.column() @@ -2107,19 +2118,18 @@ class SEQUENCER_PT_view_safe_areas_center_cut(SequencerButtonsPanel_Output, Pane bl_category = "View" def draw_header(self, context): - st = context.space_data - layout = self.layout - layout.active = st.show_safe_areas - layout.prop(st, "show_safe_center", text="") + overlay_settings = context.space_data.preview_overlay + layout.active = overlay_settings.show_safe_areas + layout.prop(overlay_settings, "show_safe_center", text="") def draw(self, context): layout = self.layout layout.use_property_split = True safe_data = context.scene.safe_areas - st = context.space_data + overlay_settings = context.space_data.preview_overlay - layout.active = st.show_safe_areas and st.show_safe_center + layout.active = overlay_settings.show_safe_areas and overlay_settings.show_safe_center col = layout.column() col.prop(safe_data, "title_center", slider=True) diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index bacca6dedc2..1d75ad8ff0a 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -209,9 +209,9 @@ class TOPBAR_MT_editor_menus(Menu): # Allow calling this menu directly (this might not be a header area). if getattr(context.area, "show_menus", False): - layout.menu("TOPBAR_MT_app", text="", icon='BLENDER') + layout.menu("TOPBAR_MT_blender", text="", icon='BLENDER') else: - layout.menu("TOPBAR_MT_app", text="Blender") + layout.menu("TOPBAR_MT_blender", text="Blender") layout.menu("TOPBAR_MT_file") layout.menu("TOPBAR_MT_edit") @@ -222,7 +222,7 @@ class TOPBAR_MT_editor_menus(Menu): layout.menu("TOPBAR_MT_help") -class TOPBAR_MT_app(Menu): +class TOPBAR_MT_blender(Menu): bl_label = "Blender" def draw(self, _context): @@ -238,7 +238,7 @@ class TOPBAR_MT_app(Menu): layout.separator() - layout.menu("TOPBAR_MT_app_system") + layout.menu("TOPBAR_MT_blender_system") class TOPBAR_MT_file_cleanup(Menu): @@ -430,7 +430,7 @@ class TOPBAR_MT_file_defaults(Menu): # Include technical operators here which would otherwise have no way for users to access. -class TOPBAR_MT_app_system(Menu): +class TOPBAR_MT_blender_system(Menu): bl_label = "System" def draw(self, _context): @@ -655,6 +655,7 @@ class TOPBAR_MT_window(Menu): layout.separator() layout.operator("screen.screenshot") + layout.operator("screen.screenshot_area") if sys.platform[:3] == "win": layout.separator() @@ -854,8 +855,8 @@ classes = ( TOPBAR_MT_file_context_menu, TOPBAR_MT_workspace_menu, TOPBAR_MT_editor_menus, - TOPBAR_MT_app, - TOPBAR_MT_app_system, + TOPBAR_MT_blender, + TOPBAR_MT_blender_system, TOPBAR_MT_file, TOPBAR_MT_file_new, TOPBAR_MT_file_recover, diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 708701c4804..0093110d326 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -267,7 +267,6 @@ class USERPREF_PT_interface_editors(InterfacePanel, CenterAlignMixIn, Panel): col = layout.column() col.prop(system, "use_region_overlap") - col.prop(view, "show_layout_ui", text="Corner Splitting") col.prop(view, "show_navigate_ui") col.prop(view, "color_picker_type") col.row().prop(view, "header_align") @@ -1414,7 +1413,7 @@ class USERPREF_PT_saveload_blend(SaveLoadPanel, CenterAlignMixIn, Panel): col = layout.column(heading="Save") col.prop(view, "use_save_prompt") - col.prop(paths, "use_save_preview_images") + col.prop(paths, "file_preview_type") col = layout.column(heading="Default To") col.prop(paths, "use_relative_paths") @@ -1455,13 +1454,11 @@ class USERPREF_PT_saveload_file_browser(SaveLoadPanel, CenterAlignMixIn, Panel): prefs = context.preferences paths = prefs.filepaths - col = layout.column() + col = layout.column(heading="Defaults") col.prop(paths, "use_filter_files") - - col = layout.column(heading="Hide") - col.prop(paths, "show_hidden_files_datablocks", text="Dot File & Data-Blocks") - col.prop(paths, "hide_recent_locations", text="Recent Locations") - col.prop(paths, "hide_system_bookmarks", text="System Bookmarks") + col.prop(paths, "show_hidden_files_datablocks") + col.prop(paths, "show_recent_locations") + col.prop(paths, "show_system_bookmarks") # ----------------------------------------------------------------------------- @@ -2254,6 +2251,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_sculpt_tools_tilt"}, "T82877"), ({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")), ({"property": "use_override_templates"}, ("T73318", "Milestone 4")), + ({"property": "use_geometry_nodes_fields"}, "T91274"), ), ) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index c6bc6d9b5d3..a332295715c 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -1362,6 +1362,7 @@ class VIEW3D_MT_select_object(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1422,6 +1423,7 @@ class VIEW3D_MT_select_pose(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1456,6 +1458,7 @@ class VIEW3D_MT_select_particle(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1562,6 +1565,7 @@ class VIEW3D_MT_select_edit_mesh(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1615,6 +1619,7 @@ class VIEW3D_MT_select_edit_curve(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1650,6 +1655,7 @@ class VIEW3D_MT_select_edit_surface(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1711,6 +1717,7 @@ class VIEW3D_MT_select_edit_metaball(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1752,6 +1759,7 @@ class VIEW3D_MT_select_edit_lattice(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1782,6 +1790,7 @@ class VIEW3D_MT_select_edit_armature(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1849,6 +1858,7 @@ class VIEW3D_MT_select_gpencil(Menu): layout.operator("gpencil.select_box") layout.operator("gpencil.select_circle") + layout.operator_menu_enum("gpencil.select_lasso", "mode") layout.separator() @@ -1885,6 +1895,7 @@ class VIEW3D_MT_select_paint_mask(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -1905,6 +1916,7 @@ class VIEW3D_MT_select_paint_mask_vertex(Menu): layout.operator("view3d.select_box") layout.operator("view3d.select_circle") + layout.operator_menu_enum("view3d.select_lasso", "mode") layout.separator() @@ -3983,6 +3995,7 @@ class VIEW3D_MT_edit_mesh_vertices(Menu): layout.operator_context = 'INVOKE_REGION_WIN' layout.operator("mesh.extrude_vertices_move", text="Extrude Vertices") + layout.operator("mesh.dupli_extrude_cursor").rotate_source = True layout.operator("mesh.bevel", text="Bevel Vertices").affect = 'VERTICES' layout.separator() @@ -4414,6 +4427,7 @@ class VIEW3D_MT_edit_curve_ctrlpoints(Menu): if edit_object.type in {'CURVE', 'SURFACE'}: layout.operator("curve.extrude_move") + layout.operator("curve.vertex_add") layout.separator() @@ -4742,6 +4756,7 @@ class VIEW3D_MT_edit_armature(Menu): layout.separator() layout.operator("armature.extrude_move") + layout.operator("armature.click_extrude") if arm.use_mirror_x: layout.operator("armature.extrude_forked") diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index d78023b4e0e..aea9cbc5c62 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -180,6 +180,13 @@ def object_eevee_cycles_shader_nodes_poll(context): eevee_cycles_shader_nodes_poll(context)) +def geometry_nodes_fields_poll(context): + return context.preferences.experimental.use_geometry_nodes_fields + +def geometry_nodes_fields_legacy_poll(context): + return not context.preferences.experimental.use_geometry_nodes_fields + + # All standard node categories currently used in nodes. shader_node_categories = [ @@ -333,6 +340,7 @@ compositor_node_categories = [ NodeItem("CompositorNodeGamma"), NodeItem("CompositorNodeExposure"), NodeItem("CompositorNodeColorCorrection"), + NodeItem("CompositorNodePosterize"), NodeItem("CompositorNodeTonemap"), NodeItem("CompositorNodeZcombine"), ]), @@ -475,24 +483,26 @@ texture_node_categories = [ geometry_node_categories = [ # Geometry Nodes GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[ - NodeItem("GeometryNodeAttributeRandomize"), - NodeItem("GeometryNodeAttributeMath"), - NodeItem("GeometryNodeAttributeClamp"), - NodeItem("GeometryNodeAttributeCompare"), - NodeItem("GeometryNodeAttributeConvert"), - NodeItem("GeometryNodeAttributeCurveMap"), - NodeItem("GeometryNodeAttributeFill"), - NodeItem("GeometryNodeAttributeMix"), - NodeItem("GeometryNodeAttributeProximity"), - NodeItem("GeometryNodeAttributeColorRamp"), - NodeItem("GeometryNodeAttributeVectorMath"), - NodeItem("GeometryNodeAttributeVectorRotate"), - NodeItem("GeometryNodeAttributeSampleTexture"), - NodeItem("GeometryNodeAttributeCombineXYZ"), - NodeItem("GeometryNodeAttributeSeparateXYZ"), - NodeItem("GeometryNodeAttributeRemove"), - NodeItem("GeometryNodeAttributeMapRange"), - NodeItem("GeometryNodeAttributeTransfer"), + NodeItem("GeometryNodeLegacyAttributeRandomize", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMath", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeClamp", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCompare", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeConvert", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCurveMap", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeFill", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMix", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeProximity", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeColorRamp", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeVectorMath", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeVectorRotate", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeSampleTexture", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCombineXYZ", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeSeparateXYZ", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMapRange", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeTransfer", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeAttributeRemove", poll=geometry_nodes_fields_legacy_poll), + + NodeItem("GeometryNodeAttributeCapture", poll=geometry_nodes_fields_poll), ]), GeometryNodeCategory("GEO_COLOR", "Color", items=[ NodeItem("ShaderNodeMixRGB"), @@ -502,19 +512,20 @@ geometry_node_categories = [ NodeItem("ShaderNodeCombineRGB"), ]), GeometryNodeCategory("GEO_CURVE", "Curve", items=[ - NodeItem("GeometryNodeCurveSubdivide"), + NodeItem("GeometryNodeLegacyCurveSubdivide", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveReverse", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSplineType", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSetHandles", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSelectHandles", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyMeshToCurve", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeCurveToMesh"), NodeItem("GeometryNodeCurveResample"), - NodeItem("GeometryNodeMeshToCurve"), NodeItem("GeometryNodeCurveToPoints"), NodeItem("GeometryNodeCurveEndpoints"), NodeItem("GeometryNodeCurveFill"), NodeItem("GeometryNodeCurveTrim"), NodeItem("GeometryNodeCurveLength"), - NodeItem("GeometryNodeCurveReverse"), - NodeItem("GeometryNodeCurveSplineType"), - NodeItem("GeometryNodeCurveSetHandles"), - NodeItem("GeometryNodeCurveSelectHandles"), ]), GeometryNodeCategory("GEO_PRIMITIVES_CURVE", "Curve Primitives", items=[ NodeItem("GeometryNodeCurvePrimitiveLine"), @@ -526,13 +537,16 @@ geometry_node_categories = [ NodeItem("GeometryNodeCurvePrimitiveBezierSegment"), ]), GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[ + NodeItem("GeometryNodeLegacyDeleteGeometry", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyRaycast", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeBoundBox"), NodeItem("GeometryNodeConvexHull"), - NodeItem("GeometryNodeDeleteGeometry"), NodeItem("GeometryNodeTransform"), NodeItem("GeometryNodeJoinGeometry"), NodeItem("GeometryNodeSeparateComponents"), - NodeItem("GeometryNodeRaycast"), + NodeItem("GeometryNodeSetPosition", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeRealizeInstances", poll=geometry_nodes_fields_poll), ]), GeometryNodeCategory("GEO_INPUT", "Input", items=[ NodeItem("GeometryNodeObjectInfo"), @@ -543,10 +557,16 @@ geometry_node_categories = [ NodeItem("FunctionNodeInputVector"), NodeItem("GeometryNodeInputMaterial"), NodeItem("GeometryNodeIsViewport"), + NodeItem("GeometryNodeInputPosition", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeInputIndex", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeInputNormal", poll=geometry_nodes_fields_poll), ]), GeometryNodeCategory("GEO_MATERIAL", "Material", items=[ - NodeItem("GeometryNodeMaterialAssign"), - NodeItem("GeometryNodeSelectByMaterial"), + NodeItem("GeometryNodeLegacyMaterialAssign", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacySelectByMaterial", poll=geometry_nodes_fields_legacy_poll), + + NodeItem("GeometryNodeMaterialAssign", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeMaterialSelection", poll=geometry_nodes_fields_poll), NodeItem("GeometryNodeMaterialReplace"), ]), GeometryNodeCategory("GEO_MESH", "Mesh", items=[ @@ -566,15 +586,14 @@ geometry_node_categories = [ NodeItem("GeometryNodeMeshLine"), NodeItem("GeometryNodeMeshUVSphere"), ]), - GeometryNodeCategory("GEO_POINT", "Point", items=[ - NodeItem("GeometryNodePointDistribute"), - NodeItem("GeometryNodePointInstance"), - NodeItem("GeometryNodePointSeparate"), - NodeItem("GeometryNodePointScale"), - NodeItem("GeometryNodePointTranslate"), - NodeItem("GeometryNodeRotatePoints"), - NodeItem("GeometryNodeAlignRotationToVector"), + NodeItem("GeometryNodeLegacyPointDistribute", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointInstance", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointSeparate", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointScale", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointTranslate", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyRotatePoints", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAlignRotationToVector", poll=geometry_nodes_fields_legacy_poll), ]), GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[ NodeItem("ShaderNodeMapRange"), @@ -585,6 +604,9 @@ geometry_node_categories = [ NodeItem("FunctionNodeFloatToInt"), NodeItem("GeometryNodeSwitch"), ]), + GeometryNodeCategory("GEO_TEXTURE", "Texture", items=[ + NodeItem("ShaderNodeTexNoise", poll=geometry_nodes_fields_poll), + ]), GeometryNodeCategory("GEO_VECTOR", "Vector", items=[ NodeItem("ShaderNodeVectorCurve"), NodeItem("ShaderNodeSeparateXYZ"), @@ -596,7 +618,8 @@ geometry_node_categories = [ NodeItem("GeometryNodeViewer"), ]), GeometryNodeCategory("GEO_VOLUME", "Volume", items=[ - NodeItem("GeometryNodePointsToVolume"), + NodeItem("GeometryNodeLegacyPointsToVolume", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeVolumeToMesh"), ]), GeometryNodeCategory("GEO_GROUP", "Group", items=node_group_items), diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index 8d18cf0ae9a..fbc0ec440cf 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -92,6 +92,7 @@ set(SRC_DNA_INC ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_texture_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_tracking_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_userdef_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_uuid_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_vec_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_vfont_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_view2d_types.h diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.h b/source/blender/blenkernel/BKE_anonymous_attribute.h new file mode 100644 index 00000000000..ebdb0b05160 --- /dev/null +++ b/source/blender/blenkernel/BKE_anonymous_attribute.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup bke + * + * An #AnonymousAttributeID is used to identify attributes that are not explicitly named. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct AnonymousAttributeID AnonymousAttributeID; + +AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name); +AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name); +bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id); +const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id); +const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.hh b/source/blender/blenkernel/BKE_anonymous_attribute.hh new file mode 100644 index 00000000000..56e6335c7c0 --- /dev/null +++ b/source/blender/blenkernel/BKE_anonymous_attribute.hh @@ -0,0 +1,169 @@ +/* + * 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. + */ + +#pragma once + +#include <atomic> +#include <string> + +#include "BLI_hash.hh" +#include "BLI_string_ref.hh" + +#include "BKE_anonymous_attribute.h" + +namespace blender::bke { + +/** + * Wrapper for #AnonymousAttributeID with RAII semantics. + * This class should typically not be used directly. Instead use #StrongAnonymousAttributeID or + * #WeakAnonymousAttributeID. + */ +template<bool IsStrongReference> class OwnedAnonymousAttributeID { + private: + const AnonymousAttributeID *data_ = nullptr; + + template<bool OtherIsStrongReference> friend class OwnedAnonymousAttributeID; + + public: + OwnedAnonymousAttributeID() = default; + + /** Create a new anonymous attribute id. */ + explicit OwnedAnonymousAttributeID(StringRefNull debug_name) + { + if constexpr (IsStrongReference) { + data_ = BKE_anonymous_attribute_id_new_strong(debug_name.c_str()); + } + else { + data_ = BKE_anonymous_attribute_id_new_weak(debug_name.c_str()); + } + } + + /** + * This transfers ownership, so no incref is necessary. + * The caller has to make sure that it owned the anonymous id. + */ + explicit OwnedAnonymousAttributeID(const AnonymousAttributeID *anonymous_id) + : data_(anonymous_id) + { + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID(const OwnedAnonymousAttributeID<OtherIsStrong> &other) + { + data_ = other.data_; + this->incref(); + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID(OwnedAnonymousAttributeID<OtherIsStrong> &&other) + { + data_ = other.data_; + this->incref(); + other.decref(); + other.data_ = nullptr; + } + + ~OwnedAnonymousAttributeID() + { + this->decref(); + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID &operator=(const OwnedAnonymousAttributeID<OtherIsStrong> &other) + { + if (this == &other) { + return *this; + } + this->~OwnedAnonymousAttributeID(); + new (this) OwnedAnonymousAttributeID(other); + return *this; + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID &operator=(OwnedAnonymousAttributeID<OtherIsStrong> &&other) + { + if (this == &other) { + return *this; + } + this->~OwnedAnonymousAttributeID(); + new (this) OwnedAnonymousAttributeID(std::move(other)); + return *this; + } + + operator bool() const + { + return data_ != nullptr; + } + + StringRefNull debug_name() const + { + BLI_assert(data_ != nullptr); + return BKE_anonymous_attribute_id_debug_name(data_); + } + + bool has_strong_references() const + { + BLI_assert(data_ != nullptr); + return BKE_anonymous_attribute_id_has_strong_references(data_); + } + + /** Extract the ownership of the currently wrapped anonymous id. */ + const AnonymousAttributeID *extract() + { + const AnonymousAttributeID *extracted_data = data_; + /* Don't decref because the caller becomes the new owner. */ + data_ = nullptr; + return extracted_data; + } + + /** Get the wrapped anonymous id, without taking ownership. */ + const AnonymousAttributeID *get() const + { + return data_; + } + + private: + void incref() + { + if (data_ == nullptr) { + return; + } + if constexpr (IsStrongReference) { + BKE_anonymous_attribute_id_increment_strong(data_); + } + else { + BKE_anonymous_attribute_id_increment_weak(data_); + } + } + + void decref() + { + if (data_ == nullptr) { + return; + } + if constexpr (IsStrongReference) { + BKE_anonymous_attribute_id_decrement_strong(data_); + } + else { + BKE_anonymous_attribute_id_decrement_weak(data_); + } + } +}; + +using StrongAnonymousAttributeID = OwnedAnonymousAttributeID<true>; +using WeakAnonymousAttributeID = OwnedAnonymousAttributeID<false>; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h index 5fda30224c6..7476474258b 100644 --- a/source/blender/blenkernel/BKE_attribute.h +++ b/source/blender/blenkernel/BKE_attribute.h @@ -66,6 +66,11 @@ bool BKE_id_attribute_remove(struct ID *id, struct CustomDataLayer *layer, struct ReportList *reports); +struct CustomDataLayer *BKE_id_attribute_find(const struct ID *id, + const char *name, + const int type, + const AttributeDomain domain); + AttributeDomain BKE_id_attribute_domain(struct ID *id, struct CustomDataLayer *layer); int BKE_id_attribute_data_length(struct ID *id, struct CustomDataLayer *layer); bool BKE_id_attribute_required(struct ID *id, struct CustomDataLayer *layer); diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index c3f7dbd4bd9..cf54e7efa0d 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -22,6 +22,7 @@ #include "FN_generic_span.hh" #include "FN_generic_virtual_array.hh" +#include "BKE_anonymous_attribute.hh" #include "BKE_attribute.h" #include "BLI_color.hh" @@ -29,6 +30,81 @@ #include "BLI_float3.hh" #include "BLI_function_ref.hh" +namespace blender::bke { + +/** + * Identifies an attribute that is either named or anonymous. + * It does not own the identifier, so it is just a reference. + */ +class AttributeIDRef { + private: + StringRef name_; + const AnonymousAttributeID *anonymous_id_ = nullptr; + + public: + AttributeIDRef() = default; + + AttributeIDRef(StringRef name) : name_(name) + { + } + + AttributeIDRef(StringRefNull name) : name_(name) + { + } + + AttributeIDRef(const char *name) : name_(name) + { + } + + AttributeIDRef(const std::string &name) : name_(name) + { + } + + /* The anonymous id is only borrowed, the caller has to keep a reference to it. */ + AttributeIDRef(const AnonymousAttributeID *anonymous_id) : anonymous_id_(anonymous_id) + { + } + + operator bool() const + { + return this->is_named() || this->is_anonymous(); + } + + friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b) + { + return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_; + } + + uint64_t hash() const + { + return get_default_hash_2(name_, anonymous_id_); + } + + bool is_named() const + { + return !name_.is_empty(); + } + + bool is_anonymous() const + { + return anonymous_id_ != nullptr; + } + + StringRef name() const + { + BLI_assert(this->is_named()); + return name_; + } + + const AnonymousAttributeID &anonymous_id() const + { + BLI_assert(this->is_anonymous()); + return *anonymous_id_; + } +}; + +} // namespace blender::bke + /** * Contains information about an attribute in a geometry component. * More information can be added in the future. E.g. whether the attribute is builtin and how it is @@ -104,8 +180,8 @@ struct AttributeInitMove : public AttributeInit { }; /* Returns false when the iteration should be stopped. */ -using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name, - const AttributeMetaData &meta_data)>; +using AttributeForeachCallback = blender::FunctionRef<bool( + const blender::bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data)>; namespace blender::bke { @@ -171,7 +247,7 @@ class OutputAttribute { GVMutableArrayPtr varray_; AttributeDomain domain_; SaveFn save_; - std::optional<fn::GVMutableArray_GSpan> optional_span_varray_; + std::unique_ptr<fn::GVMutableArray_GSpan> optional_span_varray_; bool ignore_old_values_ = false; bool save_has_been_called_ = false; @@ -230,9 +306,10 @@ class OutputAttribute { fn::GMutableSpan as_span() { - if (!optional_span_varray_.has_value()) { + if (!optional_span_varray_) { const bool materialize_old_values = !ignore_old_values_; - optional_span_varray_.emplace(*varray_, materialize_old_values); + optional_span_varray_ = std::make_unique<fn::GVMutableArray_GSpan>(*varray_, + materialize_old_values); } fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_; return span_varray; @@ -333,26 +410,28 @@ class CustomDataAttributes { void reallocate(const int size); - std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const; + std::optional<blender::fn::GSpan> get_for_read(const AttributeIDRef &attribute_id) const; - blender::fn::GVArrayPtr get_for_read(const StringRef name, + blender::fn::GVArrayPtr get_for_read(const AttributeIDRef &attribute_id, const CustomDataType data_type, const void *default_value) const; template<typename T> - blender::fn::GVArray_Typed<T> get_for_read(const blender::StringRef name, + blender::fn::GVArray_Typed<T> get_for_read(const AttributeIDRef &attribute_id, const T &default_value) const { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - GVArrayPtr varray = this->get_for_read(name, type, &default_value); + GVArrayPtr varray = this->get_for_read(attribute_id, type, &default_value); return blender::fn::GVArray_Typed<T>(std::move(varray)); } - std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name); - bool create(const blender::StringRef name, const CustomDataType data_type); - bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer); - bool remove(const blender::StringRef name); + std::optional<blender::fn::GMutableSpan> get_for_write(const AttributeIDRef &attribute_id); + bool create(const AttributeIDRef &attribute_id, const CustomDataType data_type); + bool create_by_move(const AttributeIDRef &attribute_id, + const CustomDataType data_type, + void *buffer); + bool remove(const AttributeIDRef &attribute_id); bool foreach_attribute(const AttributeForeachCallback callback, const AttributeDomain domain) const; diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 5dc4ebaa7a4..63d6b9121d2 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 20 +#define BLENDER_FILE_SUBVERSION 23 /* 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 diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h index 7a44553c565..0732f1e5190 100644 --- a/source/blender/blenkernel/BKE_customdata.h +++ b/source/blender/blenkernel/BKE_customdata.h @@ -33,6 +33,7 @@ extern "C" { #endif +struct AnonymousAttributeID; struct BMesh; struct BlendDataReader; struct BlendWriter; @@ -193,6 +194,12 @@ void *CustomData_add_layer_named(struct CustomData *data, void *layer, int totelem, const char *name); +void *CustomData_add_layer_anonymous(struct CustomData *data, + int type, + eCDAllocType alloctype, + void *layer, + int totelem, + const struct AnonymousAttributeID *anonymous_id); /* frees the active or first data layer with the give type. * returns 1 on success, 0 if no layer with the given type is found @@ -231,6 +238,11 @@ void *CustomData_duplicate_referenced_layer_named(struct CustomData *data, const int type, const char *name, const int totelem); +void *CustomData_duplicate_referenced_layer_anonymous( + CustomData *data, + const int type, + const struct AnonymousAttributeID *anonymous_id, + const int totelem); bool CustomData_is_referenced_layer(struct CustomData *data, int type); /* Duplicate all the layers with flag NOFREE, and remove the flag from duplicated layers. */ diff --git a/source/blender/blenkernel/BKE_displist.h b/source/blender/blenkernel/BKE_displist.h index 0f37ba6c4ce..db5663fcc94 100644 --- a/source/blender/blenkernel/BKE_displist.h +++ b/source/blender/blenkernel/BKE_displist.h @@ -82,7 +82,6 @@ DispList *BKE_displist_find(struct ListBase *lb, int type); void BKE_displist_normals_add(struct ListBase *lb); void BKE_displist_count(const struct ListBase *lb, int *totvert, int *totface, int *tottri); void BKE_displist_free(struct ListBase *lb); -bool BKE_displist_has_faces(const struct ListBase *lb); void BKE_displist_make_curveTypes(struct Depsgraph *depsgraph, const struct Scene *scene, @@ -94,12 +93,8 @@ void BKE_displist_make_curveTypes_forRender(struct Depsgraph *depsgraph, struct ListBase *dispbase, struct Mesh **r_final); void BKE_displist_make_mball(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); -void BKE_displist_make_mball_forRender(struct Depsgraph *depsgraph, - struct Scene *scene, - struct Object *ob, - struct ListBase *dispbase); -bool BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, +void BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, const struct Scene *scene, struct Object *ob, struct ListBase *source_nurb, diff --git a/source/blender/blenkernel/BKE_duplilist.h b/source/blender/blenkernel/BKE_duplilist.h index c142d5338d1..989b68f4ccb 100644 --- a/source/blender/blenkernel/BKE_duplilist.h +++ b/source/blender/blenkernel/BKE_duplilist.h @@ -31,6 +31,7 @@ struct ListBase; struct Object; struct ParticleSystem; struct Scene; +struct ID; /* ---------------------------------------------------- */ /* Dupli-Geometry */ @@ -42,7 +43,10 @@ void free_object_duplilist(struct ListBase *lb); typedef struct DupliObject { struct DupliObject *next, *prev; + /* Object whose geometry is instanced. */ struct Object *ob; + /* Data owned by the object above that is instanced. This might not be the same as `ob->data`. */ + struct ID *ob_data; float mat[4][4]; float orco[3], uv[2]; diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h index 5f6a9ec7b91..17cdb9d6a42 100644 --- a/source/blender/blenkernel/BKE_geometry_set.h +++ b/source/blender/blenkernel/BKE_geometry_set.h @@ -41,7 +41,7 @@ typedef enum GeometryComponentType { void BKE_geometry_set_free(struct GeometrySet *geometry_set); -bool BKE_geometry_set_has_instances(const struct GeometrySet *geometry_set); +bool BKE_object_has_geometry_set_instances(const struct Object *ob); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 42e9ce82278..98f5de43f84 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -31,9 +31,12 @@ #include "BLI_user_counter.hh" #include "BLI_vector_set.hh" +#include "BKE_anonymous_attribute.hh" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.h" +#include "FN_field.hh" + struct Collection; struct Curve; struct CurveEval; @@ -88,11 +91,11 @@ class GeometryComponent { GeometryComponentType type() const; /* Return true when any attribute with this name exists, including built in attributes. */ - bool attribute_exists(const blender::StringRef attribute_name) const; + bool attribute_exists(const blender::bke::AttributeIDRef &attribute_id) const; /* Return the data type and domain of an attribute with the given name if it exists. */ std::optional<AttributeMetaData> attribute_get_meta_data( - const blender::StringRef attribute_name) const; + const blender::bke::AttributeIDRef &attribute_id) const; /* Returns true when the geometry component supports this attribute domain. */ bool attribute_domain_supported(const AttributeDomain domain) const; @@ -100,16 +103,17 @@ class GeometryComponent { virtual int attribute_domain_size(const AttributeDomain domain) const; bool attribute_is_builtin(const blender::StringRef attribute_name) const; + bool attribute_is_builtin(const blender::bke::AttributeIDRef &attribute_id) const; /* Get read-only access to the highest priority attribute with the given name. * Returns null if the attribute does not exist. */ blender::bke::ReadAttributeLookup attribute_try_get_for_read( - const blender::StringRef attribute_name) const; + const blender::bke::AttributeIDRef &attribute_id) const; /* Get read and write access to the highest priority attribute with the given name. * Returns null if the attribute does not exist. */ blender::bke::WriteAttributeLookup attribute_try_get_for_write( - const blender::StringRef attribute_name); + const blender::bke::AttributeIDRef &attribute_id); /* Get a read-only attribute for the domain based on the given attribute. This can be used to * interpolate from one domain to another. @@ -120,10 +124,10 @@ class GeometryComponent { const AttributeDomain to_domain) const; /* Returns true when the attribute has been deleted. */ - bool attribute_try_delete(const blender::StringRef attribute_name); + bool attribute_try_delete(const blender::bke::AttributeIDRef &attribute_id); /* Returns true when the attribute has been created. */ - bool attribute_try_create(const blender::StringRef attribute_name, + bool attribute_try_create(const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer); @@ -133,7 +137,7 @@ class GeometryComponent { bool attribute_try_create_builtin(const blender::StringRef attribute_name, const AttributeInit &initializer); - blender::Set<std::string> attribute_names() const; + blender::Set<blender::bke::AttributeIDRef> attribute_ids() const; bool attribute_foreach(const AttributeForeachCallback callback) const; virtual bool is_empty() const; @@ -142,7 +146,7 @@ class GeometryComponent { * Returns null when the attribute does not exist or cannot be converted to the requested domain * and data type. */ std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type) const; @@ -150,18 +154,18 @@ class GeometryComponent { * left unchanged. Returns null when the attribute does not exist or cannot be adapted to the * requested domain. */ std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read( - const blender::StringRef attribute_name, const AttributeDomain domain) const; + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) const; /* Get a virtual array to read data of an attribute with the given data type. The domain is * left unchanged. Returns null when the attribute does not exist or cannot be converted to the * requested data type. */ blender::bke::ReadAttributeLookup attribute_try_get_for_read( - const blender::StringRef attribute_name, const CustomDataType data_type) const; + const blender::bke::AttributeIDRef &attribute_id, const CustomDataType data_type) const; /* Get a virtual array to read the data of an attribute. If that is not possible, the returned * virtual array will contain a default value. This never returns null. */ std::unique_ptr<blender::fn::GVArray> attribute_get_for_read( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const void *default_value = nullptr) const; @@ -169,14 +173,15 @@ class GeometryComponent { /* Should be used instead of the method above when the requested data type is known at compile * time for better type safety. */ template<typename T> - blender::fn::GVArray_Typed<T> attribute_get_for_read(const blender::StringRef attribute_name, - const AttributeDomain domain, - const T &default_value) const + blender::fn::GVArray_Typed<T> attribute_get_for_read( + const blender::bke::AttributeIDRef &attribute_id, + const AttributeDomain domain, + const T &default_value) const { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); std::unique_ptr varray = this->attribute_get_for_read( - attribute_name, domain, type, &default_value); + attribute_id, domain, type, &default_value); return blender::fn::GVArray_Typed<T>(std::move(varray)); } @@ -191,7 +196,7 @@ class GeometryComponent { * is created that will overwrite the existing attribute in the end. */ blender::bke::OutputAttribute attribute_try_get_for_output( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const void *default_value = nullptr); @@ -200,28 +205,30 @@ class GeometryComponent { * attributes are not read, i.e. the attribute is used only for output. Since values are not read * from this attribute, no default value is necessary. */ blender::bke::OutputAttribute attribute_try_get_for_output_only( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type); /* Statically typed method corresponding to the equally named generic one. */ template<typename T> blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output( - const blender::StringRef attribute_name, const AttributeDomain domain, const T default_value) + const blender::bke::AttributeIDRef &attribute_id, + const AttributeDomain domain, + const T default_value) { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_try_get_for_output(attribute_name, domain, data_type, &default_value); + return this->attribute_try_get_for_output(attribute_id, domain, data_type, &default_value); } /* Statically typed method corresponding to the equally named generic one. */ template<typename T> blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only( - const blender::StringRef attribute_name, const AttributeDomain domain) + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_try_get_for_output_only(attribute_name, domain, data_type); + return this->attribute_try_get_for_output_only(attribute_id, domain, data_type); } private: @@ -283,6 +290,7 @@ struct GeometrySet { void clear(); + bool owns_direct_data() const; void ensure_owns_direct_data(); /* Utility methods for creation. */ @@ -447,12 +455,14 @@ class InstanceReference { None, Object, Collection, + GeometrySet, }; private: Type type_ = Type::None; /** Depending on the type this is either null, an Object or Collection pointer. */ void *data_ = nullptr; + std::unique_ptr<GeometrySet> geometry_set_; public: InstanceReference() = default; @@ -465,6 +475,19 @@ class InstanceReference { { } + InstanceReference(GeometrySet geometry_set) + : type_(Type::GeometrySet), + geometry_set_(std::make_unique<GeometrySet>(std::move(geometry_set))) + { + } + + InstanceReference(const InstanceReference &src) : type_(src.type_), data_(src.data_) + { + if (src.type_ == Type::GeometrySet) { + geometry_set_ = std::make_unique<GeometrySet>(*src.geometry_set_); + } + } + Type type() const { return type_; @@ -482,14 +505,37 @@ class InstanceReference { return *(Collection *)data_; } + const GeometrySet &geometry_set() const + { + BLI_assert(type_ == Type::GeometrySet); + return *geometry_set_; + } + + bool owns_direct_data() const + { + if (type_ != Type::GeometrySet) { + /* The object and collection instances are not direct data. */ + return true; + } + return geometry_set_->owns_direct_data(); + } + + void ensure_owns_direct_data() + { + if (type_ != Type::GeometrySet) { + return; + } + geometry_set_->ensure_owns_direct_data(); + } + uint64_t hash() const { - return blender::get_default_hash(data_); + return blender::get_default_hash_2(data_, geometry_set_.get()); } friend bool operator==(const InstanceReference &a, const InstanceReference &b) { - return a.data_ == b.data_; + return a.data_ == b.data_ && a.geometry_set_.get() == b.geometry_set_.get(); } }; @@ -529,7 +575,7 @@ class InstancesComponent : public GeometryComponent { void reserve(int min_capacity); void resize(int capacity); - int add_reference(InstanceReference reference); + int add_reference(const InstanceReference &reference); void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); blender::Span<InstanceReference> references() const; @@ -545,12 +591,17 @@ class InstancesComponent : public GeometryComponent { blender::Span<int> almost_unique_ids() const; + int attribute_domain_size(const AttributeDomain domain) const final; + bool is_empty() const final; bool owns_direct_data() const override; void ensure_owns_direct_data() override; static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_INSTANCES; + + private: + const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; }; /** A geometry component that stores volume grids. */ @@ -577,3 +628,78 @@ class VolumeComponent : public GeometryComponent { static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME; }; + +namespace blender::bke { + +class GeometryComponentFieldContext : public fn::FieldContext { + private: + const GeometryComponent &component_; + const AttributeDomain domain_; + + public: + GeometryComponentFieldContext(const GeometryComponent &component, const AttributeDomain domain) + : component_(component), domain_(domain) + { + } + + const GeometryComponent &geometry_component() const + { + return component_; + } + + AttributeDomain domain() const + { + return domain_; + } +}; + +class AttributeFieldInput : public fn::FieldInput { + private: + std::string name_; + + public: + AttributeFieldInput(std::string name, const CPPType &type) + : fn::FieldInput(type, name), name_(std::move(name)) + { + } + + StringRefNull attribute_name() const + { + return name_; + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const override; + + std::string socket_inspection_name() const override; + + uint64_t hash() const override; + bool is_equal_to(const fn::FieldNode &other) const override; +}; + +class AnonymousAttributeFieldInput : public fn::FieldInput { + private: + /** + * A strong reference is required to make sure that the referenced attribute is not removed + * automatically. + */ + StrongAnonymousAttributeID anonymous_id_; + + public: + AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id, const CPPType &type) + : fn::FieldInput(type, anonymous_id.debug_name()), anonymous_id_(std::move(anonymous_id)) + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const override; + + std::string socket_inspection_name() const override; + + uint64_t hash() const override; + bool is_equal_to(const fn::FieldNode &other) const override; +}; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh index 25876296a47..44a0ee30c4c 100644 --- a/source/blender/blenkernel/BKE_geometry_set_instances.hh +++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh @@ -59,9 +59,10 @@ struct AttributeKind { * will contain the highest complexity data type and the highest priority domain among every * attribute with the given name on all of the input components. */ -void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, - Span<GeometryComponentType> component_types, - const Set<std::string> &ignored_attributes, - Map<std::string, AttributeKind> &r_attributes); +void geometry_set_gather_instances_attribute_info( + Span<GeometryInstanceGroup> set_groups, + Span<GeometryComponentType> component_types, + const Set<std::string> &ignored_attributes, + Map<AttributeIDRef, AttributeKind> &r_attributes); } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h index 89713e9ad0a..7696b5c0189 100644 --- a/source/blender/blenkernel/BKE_global.h +++ b/source/blender/blenkernel/BKE_global.h @@ -124,7 +124,10 @@ enum { /** Don't overwrite these flags when reading a file. */ #define G_FLAG_ALL_RUNTIME \ (G_FLAG_SCRIPT_AUTOEXEC | G_FLAG_SCRIPT_OVERRIDE_PREF | G_FLAG_EVENT_SIMULATE | \ - G_FLAG_USERPREF_NO_SAVE_ON_EXIT) + G_FLAG_USERPREF_NO_SAVE_ON_EXIT | \ +\ + /* #BPY_python_reset is responsible for resetting these flags on file load. */ \ + G_FLAG_SCRIPT_AUTOEXEC_FAIL | G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET) /** Flags to read from blend file. */ #define G_FLAG_ALL_READFILE 0 diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index b0939ec884d..7136a3fd7af 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -45,8 +45,10 @@ enum { IDTYPE_FLAGS_NO_COPY = 1 << 0, /** Indicates that the given IDType does not support linking/appending from a library file. */ IDTYPE_FLAGS_NO_LIBLINKING = 1 << 1, - /** Indicates that the given IDType does not support making a library-linked ID local. */ - IDTYPE_FLAGS_NO_MAKELOCAL = 1 << 2, + /** Indicates that the given IDType should not be directly linked from a library file, but may be + * appended. + * NOTE: Mutually exclusive with `IDTYPE_FLAGS_NO_LIBLINKING`. */ + IDTYPE_FLAGS_ONLY_APPEND = 1 << 2, /** Indicates that the given IDType does not have animation data. */ IDTYPE_FLAGS_NO_ANIMDATA = 1 << 3, }; @@ -283,9 +285,14 @@ const struct IDTypeInfo *BKE_idtype_get_info_from_id(const struct ID *id); const char *BKE_idtype_idcode_to_name(const short idcode); const char *BKE_idtype_idcode_to_name_plural(const short idcode); const char *BKE_idtype_idcode_to_translation_context(const short idcode); -bool BKE_idtype_idcode_is_linkable(const short idcode); + bool BKE_idtype_idcode_is_valid(const short idcode); +bool BKE_idtype_idcode_is_linkable(const short idcode); +bool BKE_idtype_idcode_is_only_appendable(const short idcode); +/* Macro currently, since any linkable IDtype should be localizable. */ +#define BKE_idtype_idcode_is_localizable BKE_idtype_idcode_is_linkable + short BKE_idtype_idcode_from_name(const char *idtype_name); uint64_t BKE_idtype_idcode_to_idfilter(const short idcode); diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index a50faedcc3c..36f57209e33 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -230,20 +230,25 @@ void id_us_plus(struct ID *id); void id_us_min(struct ID *id); void id_fake_user_set(struct ID *id); void id_fake_user_clear(struct ID *id); -void BKE_id_clear_newpoin(struct ID *id); +void BKE_id_newptr_and_tag_clear(struct ID *id); /** Flags to control make local code behavior. */ enum { /** Making that ID local is part of making local a whole library. */ LIB_ID_MAKELOCAL_FULL_LIBRARY = 1 << 0, + /** In case caller code already knows this ID should be made local without copying. */ + LIB_ID_MAKELOCAL_FORCE_LOCAL = 1 << 1, + /** In case caller code already knows this ID should be made local using copying. */ + LIB_ID_MAKELOCAL_FORCE_COPY = 1 << 2, + /* Special type-specific options. */ /** For Objects, do not clear the proxy pointers while making the data-block local. */ LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING = 1 << 16, }; void BKE_lib_id_make_local_generic(struct Main *bmain, struct ID *id, const int flags); -bool BKE_lib_id_make_local(struct Main *bmain, struct ID *id, const bool test, const int flags); +bool BKE_lib_id_make_local(struct Main *bmain, struct ID *id, const int flags); bool id_single_user(struct bContext *C, struct ID *id, struct PointerRNA *ptr, diff --git a/source/blender/blenkernel/BKE_lib_remap.h b/source/blender/blenkernel/BKE_lib_remap.h index c90a284c204..c70521f9593 100644 --- a/source/blender/blenkernel/BKE_lib_remap.h +++ b/source/blender/blenkernel/BKE_lib_remap.h @@ -112,6 +112,7 @@ void BKE_libblock_relink_ex(struct Main *bmain, const short remap_flags) ATTR_NONNULL(1, 2); void BKE_libblock_relink_to_newid(struct ID *id) ATTR_NONNULL(); +void BKE_libblock_relink_to_newid_new(struct Main *bmain, struct ID *id) ATTR_NONNULL(); typedef void (*BKE_library_free_notifier_reference_cb)(const void *); typedef void (*BKE_library_remap_editor_id_reference_cb)(struct ID *, struct ID *); diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index dbcefb8b6d5..b0a8fee1178 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -123,7 +123,7 @@ void BKE_mesh_eval_delete(struct Mesh *mesh_eval); /* Performs copy for use during evaluation, * optional referencing original arrays to reduce memory. */ -struct Mesh *BKE_mesh_copy_for_eval(struct Mesh *source, bool reference); +struct Mesh *BKE_mesh_copy_for_eval(const struct Mesh *source, bool reference); /* These functions construct a new Mesh, * contrary to BKE_mesh_to_curve_nurblist which modifies ob itself. */ diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 5e0526ab262..8e82ab6d6be 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -731,6 +731,8 @@ void nodeSetSocketAvailability(struct bNodeSocket *sock, bool is_available); int nodeSocketLinkLimit(const struct bNodeSocket *sock); +void nodeDeclarationEnsure(struct bNodeTree *ntree, struct bNode *node); + /* Node Clipboard */ void BKE_node_clipboard_init(const struct bNodeTree *ntree); void BKE_node_clipboard_clear(void); @@ -1255,6 +1257,7 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree, #define CMP_NODE_DENOISE 324 #define CMP_NODE_EXPOSURE 325 #define CMP_NODE_CRYPTOMATTE 326 +#define CMP_NODE_POSTERIZE 327 /* channel toggles */ #define CMP_CHAN_RGB 1 @@ -1411,34 +1414,34 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_EDGE_SPLIT 1001 #define GEO_NODE_TRANSFORM 1002 #define GEO_NODE_BOOLEAN 1003 -#define GEO_NODE_POINT_DISTRIBUTE 1004 -#define GEO_NODE_POINT_INSTANCE 1005 +#define GEO_NODE_LEGACY_POINT_DISTRIBUTE 1004 +#define GEO_NODE_LEGACY_POINT_INSTANCE 1005 #define GEO_NODE_SUBDIVISION_SURFACE 1006 #define GEO_NODE_OBJECT_INFO 1007 -#define GEO_NODE_ATTRIBUTE_RANDOMIZE 1008 -#define GEO_NODE_ATTRIBUTE_MATH 1009 +#define GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE 1008 +#define GEO_NODE_LEGACY_ATTRIBUTE_MATH 1009 #define GEO_NODE_JOIN_GEOMETRY 1010 -#define GEO_NODE_ATTRIBUTE_FILL 1011 -#define GEO_NODE_ATTRIBUTE_MIX 1012 -#define GEO_NODE_ATTRIBUTE_COLOR_RAMP 1013 -#define GEO_NODE_POINT_SEPARATE 1014 -#define GEO_NODE_ATTRIBUTE_COMPARE 1015 -#define GEO_NODE_POINT_ROTATE 1016 -#define GEO_NODE_ATTRIBUTE_VECTOR_MATH 1017 -#define GEO_NODE_ALIGN_ROTATION_TO_VECTOR 1018 -#define GEO_NODE_POINT_TRANSLATE 1019 -#define GEO_NODE_POINT_SCALE 1020 -#define GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE 1021 -#define GEO_NODE_POINTS_TO_VOLUME 1022 +#define GEO_NODE_LEGACY_ATTRIBUTE_FILL 1011 +#define GEO_NODE_LEGACY_ATTRIBUTE_MIX 1012 +#define GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP 1013 +#define GEO_NODE_LEGACY_POINT_SEPARATE 1014 +#define GEO_NODE_LEGACY_ATTRIBUTE_COMPARE 1015 +#define GEO_NODE_LEGACY_POINT_ROTATE 1016 +#define GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH 1017 +#define GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR 1018 +#define GEO_NODE_LEGACY_POINT_TRANSLATE 1019 +#define GEO_NODE_LEGACY_POINT_SCALE 1020 +#define GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE 1021 +#define GEO_NODE_LEGACY_POINTS_TO_VOLUME 1022 #define GEO_NODE_COLLECTION_INFO 1023 #define GEO_NODE_IS_VIEWPORT 1024 -#define GEO_NODE_ATTRIBUTE_PROXIMITY 1025 +#define GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY 1025 #define GEO_NODE_VOLUME_TO_MESH 1026 -#define GEO_NODE_ATTRIBUTE_COMBINE_XYZ 1027 -#define GEO_NODE_ATTRIBUTE_SEPARATE_XYZ 1028 +#define GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ 1027 +#define GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ 1028 #define GEO_NODE_MESH_SUBDIVIDE 1029 #define GEO_NODE_ATTRIBUTE_REMOVE 1030 -#define GEO_NODE_ATTRIBUTE_CONVERT 1031 +#define GEO_NODE_LEGACY_ATTRIBUTE_CONVERT 1031 #define GEO_NODE_MESH_PRIMITIVE_CUBE 1032 #define GEO_NODE_MESH_PRIMITIVE_CIRCLE 1033 #define GEO_NODE_MESH_PRIMITIVE_UV_SPHERE 1034 @@ -1447,28 +1450,28 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_MESH_PRIMITIVE_CONE 1037 #define GEO_NODE_MESH_PRIMITIVE_LINE 1038 #define GEO_NODE_MESH_PRIMITIVE_GRID 1039 -#define GEO_NODE_ATTRIBUTE_MAP_RANGE 1040 -#define GEO_NODE_ATTRIBUTE_CLAMP 1041 +#define GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE 1040 +#define GEO_NODE_LECAGY_ATTRIBUTE_CLAMP 1041 #define GEO_NODE_BOUNDING_BOX 1042 #define GEO_NODE_SWITCH 1043 -#define GEO_NODE_ATTRIBUTE_TRANSFER 1044 +#define GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER 1044 #define GEO_NODE_CURVE_TO_MESH 1045 -#define GEO_NODE_ATTRIBUTE_CURVE_MAP 1046 +#define GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP 1046 #define GEO_NODE_CURVE_RESAMPLE 1047 #define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048 -#define GEO_NODE_MATERIAL_ASSIGN 1049 +#define GEO_NODE_LEGACY_MATERIAL_ASSIGN 1049 #define GEO_NODE_INPUT_MATERIAL 1050 #define GEO_NODE_MATERIAL_REPLACE 1051 -#define GEO_NODE_MESH_TO_CURVE 1052 -#define GEO_NODE_DELETE_GEOMETRY 1053 +#define GEO_NODE_LEGACY_MESH_TO_CURVE 1052 +#define GEO_NODE_LEGACY_DELETE_GEOMETRY 1053 #define GEO_NODE_CURVE_LENGTH 1054 -#define GEO_NODE_SELECT_BY_MATERIAL 1055 +#define GEO_NODE_LEGACY_SELECT_BY_MATERIAL 1055 #define GEO_NODE_CONVEX_HULL 1056 #define GEO_NODE_CURVE_TO_POINTS 1057 -#define GEO_NODE_CURVE_REVERSE 1058 +#define GEO_NODE_LEGACY_CURVE_REVERSE 1058 #define GEO_NODE_SEPARATE_COMPONENTS 1059 -#define GEO_NODE_CURVE_SUBDIVIDE 1060 -#define GEO_NODE_RAYCAST 1061 +#define GEO_NODE_LEGACY_CURVE_SUBDIVIDE 1060 +#define GEO_NODE_LEGACY_RAYCAST 1061 #define GEO_NODE_CURVE_PRIMITIVE_STAR 1062 #define GEO_NODE_CURVE_PRIMITIVE_SPIRAL 1063 #define GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER 1064 @@ -1479,10 +1482,18 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_ENDPOINTS 1069 #define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070 #define GEO_NODE_CURVE_TRIM 1071 -#define GEO_NODE_CURVE_SET_HANDLES 1072 -#define GEO_NODE_CURVE_SPLINE_TYPE 1073 -#define GEO_NODE_CURVE_SELECT_HANDLES 1074 +#define GEO_NODE_LEGACY_CURVE_SET_HANDLES 1072 +#define GEO_NODE_LEGACY_CURVE_SPLINE_TYPE 1073 +#define GEO_NODE_LEGACY_CURVE_SELECT_HANDLES 1074 #define GEO_NODE_CURVE_FILL 1075 +#define GEO_NODE_INPUT_POSITION 1076 +#define GEO_NODE_SET_POSITION 1077 +#define GEO_NODE_INPUT_INDEX 1078 +#define GEO_NODE_INPUT_NORMAL 1079 +#define GEO_NODE_ATTRIBUTE_CAPTURE 1080 +#define GEO_NODE_MATERIAL_SELECTION 1081 +#define GEO_NODE_MATERIAL_ASSIGN 1082 +#define GEO_NODE_REALIZE_INSTANCES 1083 /** \} */ diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index a823602e341..0e153c5a82a 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -458,9 +458,13 @@ void BKE_object_modifiers_lib_link_common(void *userData, struct ID **idpoin, int cb_flag); +void BKE_object_replace_data_on_shallow_copy(struct Object *ob, struct ID *new_data); + struct PartEff; struct PartEff *BKE_object_do_version_give_parteff_245(struct Object *ob); +bool BKE_object_supports_material_slots(struct Object *ob); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_packedFile.h b/source/blender/blenkernel/BKE_packedFile.h index c45a0bc857d..8cb0c78d9aa 100644 --- a/source/blender/blenkernel/BKE_packedFile.h +++ b/source/blender/blenkernel/BKE_packedFile.h @@ -74,6 +74,12 @@ char *BKE_packedfile_unpack_to_file(struct ReportList *reports, const char *local_name, struct PackedFile *pf, enum ePF_FileStatus how); +char *BKE_packedfile_unpack(struct Main *bmain, + struct ReportList *reports, + struct ID *id, + const char *orig_file_path, + struct PackedFile *pf, + enum ePF_FileStatus how); int BKE_packedfile_unpack_vfont(struct Main *bmain, struct ReportList *reports, struct VFont *vfont, diff --git a/source/blender/blenkernel/BKE_sound.h b/source/blender/blenkernel/BKE_sound.h index fa58813c5f8..8796e2c18f3 100644 --- a/source/blender/blenkernel/BKE_sound.h +++ b/source/blender/blenkernel/BKE_sound.h @@ -96,13 +96,24 @@ typedef struct SoundInfo { eSoundChannels channels; } specs; float length; - double start_offset; } SoundInfo; +typedef struct SoundStreamInfo { + double duration; + double start; +} SoundStreamInfo; + /* Get information about given sound. Returns truth on success., false if sound can not be loaded * or if the codes is not supported. */ bool BKE_sound_info_get(struct Main *main, struct bSound *sound, SoundInfo *sound_info); +/* Get information about given sound. Returns truth on success., false if sound can not be loaded + * or if the codes is not supported. */ +bool BKE_sound_stream_info_get(struct Main *main, + const char *filepath, + int stream, + SoundStreamInfo *sound_info); + #if defined(WITH_AUDASPACE) AUD_Device *BKE_sound_mixdown(const struct Scene *scene, AUD_DeviceSpecs specs, diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index fc145f1ddf1..0fbf39a52fa 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -131,6 +131,11 @@ class Spline { virtual void transform(const blender::float4x4 &matrix); /** + * Change the direction of the spline (switch the start and end) without changing its shape. + */ + void reverse(); + + /** * Mark all caches for re-computation. This must be called after any operation that would * change the generated positions, tangents, normals, mapping, etc. of the evaluated points. */ @@ -210,6 +215,7 @@ class Spline { virtual void correct_end_tangents() const = 0; virtual void copy_settings(Spline &dst) const = 0; virtual void copy_data(Spline &dst) const = 0; + virtual void reverse_impl() = 0; }; /** @@ -353,6 +359,9 @@ class BezierSpline final : public Spline { void correct_end_tangents() const final; void copy_settings(Spline &dst) const final; void copy_data(Spline &dst) const final; + + protected: + void reverse_impl() override; }; /** @@ -469,6 +478,7 @@ class NURBSpline final : public Spline { void correct_end_tangents() const final; void copy_settings(Spline &dst) const final; void copy_data(Spline &dst) const final; + void reverse_impl() override; void calculate_knots() const; blender::Span<BasisCache> calculate_basis_cache() const; @@ -519,6 +529,7 @@ class PolySpline final : public Spline { void correct_end_tangents() const final; void copy_settings(Spline &dst) const final; void copy_data(Spline &dst) const final; + void reverse_impl() override; }; /** diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 64cfbd8f491..0b082bf1c5a 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -76,6 +76,7 @@ set(SRC intern/anim_path.c intern/anim_sys.c intern/anim_visualization.c + intern/anonymous_attribute.cc intern/appdir.c intern/armature.c intern/armature_deform.c @@ -185,7 +186,7 @@ set(SRC intern/mball_tessellate.c intern/mesh.c intern/mesh_boolean_convert.cc - intern/mesh_convert.c + intern/mesh_convert.cc intern/mesh_evaluate.cc intern/mesh_fair.cc intern/mesh_iterators.c @@ -295,6 +296,8 @@ set(SRC BKE_anim_path.h BKE_anim_visualization.h BKE_animsys.h + BKE_anonymous_attribute.h + BKE_anonymous_attribute.hh BKE_appdir.h BKE_armature.h BKE_armature.hh diff --git a/source/blender/blenkernel/intern/anonymous_attribute.cc b/source/blender/blenkernel/intern/anonymous_attribute.cc new file mode 100644 index 00000000000..67611053d83 --- /dev/null +++ b/source/blender/blenkernel/intern/anonymous_attribute.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 "BKE_anonymous_attribute.hh" + +using namespace blender::bke; + +/** + * A struct that identifies an attribute. It's lifetime is managed by an atomic reference count. + * + * Additionally, this struct can be strongly or weakly owned. The difference is that strong + * ownership means that attributes with this id will be kept around. Weak ownership just makes sure + * that the struct itself stays alive, but corresponding attributes might still be removed + * automatically. + */ +struct AnonymousAttributeID { + /** + * Total number of references to this attribute id. Once this reaches zero, the struct can be + * freed. This includes strong and weak references. + */ + mutable std::atomic<int> refcount_tot = 0; + + /** + * Number of strong references to this attribute id. When this is zero, the corresponding + * attributes can be removed from geometries automatically. + */ + mutable std::atomic<int> refcount_strong = 0; + + /** + * Only used to identify this struct in a debugging session. + */ + std::string debug_name; + + /** + * Unique name of the this attribute id during the current session. + */ + std::string internal_name; +}; + +/** Every time this function is called, it outputs a different name. */ +static std::string get_new_internal_name() +{ + static std::atomic<int> index = 0; + const int next_index = index.fetch_add(1); + return "anonymous_attribute_" + std::to_string(next_index); +} + +AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name) +{ + AnonymousAttributeID *anonymous_id = new AnonymousAttributeID(); + anonymous_id->debug_name = debug_name; + anonymous_id->internal_name = get_new_internal_name(); + anonymous_id->refcount_tot.store(1); + return anonymous_id; +} + +AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name) +{ + AnonymousAttributeID *anonymous_id = new AnonymousAttributeID(); + anonymous_id->debug_name = debug_name; + anonymous_id->internal_name = get_new_internal_name(); + anonymous_id->refcount_tot.store(1); + anonymous_id->refcount_strong.store(1); + return anonymous_id; +} + +bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id) +{ + return anonymous_id->refcount_strong.load() >= 1; +} + +void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id) +{ + anonymous_id->refcount_tot.fetch_add(1); +} + +void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id) +{ + anonymous_id->refcount_tot.fetch_add(1); + anonymous_id->refcount_strong.fetch_add(1); +} + +void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id) +{ + const int new_refcount = anonymous_id->refcount_tot.fetch_sub(1) - 1; + if (new_refcount == 0) { + delete anonymous_id; + } +} + +void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id) +{ + anonymous_id->refcount_strong.fetch_sub(1); + BKE_anonymous_attribute_id_decrement_weak(anonymous_id); +} + +const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id) +{ + return anonymous_id->debug_name.c_str(); +} + +const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id) +{ + return anonymous_id->internal_name.c_str(); +} diff --git a/source/blender/blenkernel/intern/attribute.c b/source/blender/blenkernel/intern/attribute.c index e9444cf88a6..ee8ef5e97f7 100644 --- a/source/blender/blenkernel/intern/attribute.c +++ b/source/blender/blenkernel/intern/attribute.c @@ -51,7 +51,7 @@ typedef struct DomainInfo { int length; } DomainInfo; -static void get_domains(ID *id, DomainInfo info[ATTR_DOMAIN_NUM]) +static void get_domains(const ID *id, DomainInfo info[ATTR_DOMAIN_NUM]) { memset(info, 0, sizeof(DomainInfo) * ATTR_DOMAIN_NUM); @@ -223,6 +223,29 @@ bool BKE_id_attribute_remove(ID *id, CustomDataLayer *layer, ReportList *reports return true; } +CustomDataLayer *BKE_id_attribute_find(const ID *id, + const char *name, + const int type, + const AttributeDomain domain) +{ + DomainInfo info[ATTR_DOMAIN_NUM]; + get_domains(id, info); + + CustomData *customdata = info[domain].customdata; + if (customdata == NULL) { + return NULL; + } + + for (int i = 0; i < customdata->totlayer; i++) { + CustomDataLayer *layer = &customdata->layers[i]; + if (layer->type == type && STREQ(layer->name, name)) { + return layer; + } + } + + return NULL; +} + int BKE_id_attributes_length(ID *id, const CustomDataMask mask) { DomainInfo info[ATTR_DOMAIN_NUM]; diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index aa0af294bc3..8c4f87be91f 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -32,6 +32,8 @@ #include "BLI_float2.hh" #include "BLI_span.hh" +#include "BLT_translation.h" + #include "CLG_log.h" #include "NOD_type_conversions.hh" @@ -44,6 +46,8 @@ using blender::float3; using blender::Set; using blender::StringRef; using blender::StringRefNull; +using blender::bke::AttributeIDRef; +using blender::bke::OutputAttribute; using blender::fn::GMutableSpan; using blender::fn::GSpan; using blender::fn::GVArray_For_GSpan; @@ -186,7 +190,7 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) void OutputAttribute::save() { save_has_been_called_ = true; - if (optional_span_varray_.has_value()) { + if (optional_span_varray_) { optional_span_varray_->save(); } if (save_) { @@ -334,8 +338,20 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component) return data != nullptr; } +static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer, + const AttributeIDRef &attribute_id) +{ + if (!attribute_id) { + return false; + } + if (attribute_id.is_anonymous()) { + return layer.anonymous_id == &attribute_id.anonymous_id(); + } + return layer.name == attribute_id.name(); +} + ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( - const GeometryComponent &component, const StringRef attribute_name) const + const GeometryComponent &component, const AttributeIDRef &attribute_id) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); if (custom_data == nullptr) { @@ -343,7 +359,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( } const int domain_size = component.attribute_domain_size(domain_); for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { - if (layer.name != attribute_name) { + if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) { continue; } const CustomDataType data_type = (CustomDataType)layer.type; @@ -368,7 +384,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( } WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( - GeometryComponent &component, const StringRef attribute_name) const + GeometryComponent &component, const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -376,10 +392,17 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( } const int domain_size = component.attribute_domain_size(domain_); for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) { - if (layer.name != attribute_name) { + if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) { continue; } - CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, domain_size); + if (attribute_id.is_named()) { + CustomData_duplicate_referenced_layer_named( + custom_data, layer.type, layer.name, domain_size); + } + else { + CustomData_duplicate_referenced_layer_anonymous( + custom_data, layer.type, &attribute_id.anonymous_id(), domain_size); + } const CustomDataType data_type = (CustomDataType)layer.type; switch (data_type) { case CD_PROP_FLOAT: @@ -402,7 +425,7 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( } bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -411,7 +434,8 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, const int domain_size = component.attribute_domain_size(domain_); for (const int i : IndexRange(custom_data->totlayer)) { const CustomDataLayer &layer = custom_data->layers[i]; - if (this->type_is_supported((CustomDataType)layer.type) && layer.name == attribute_name) { + if (this->type_is_supported((CustomDataType)layer.type) && + custom_data_layer_matches_attribute_id(layer, attribute_id)) { CustomData_free_layer(custom_data, layer.type, domain_size, i); return true; } @@ -419,24 +443,39 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, return false; } -static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name, - CustomData &custom_data, - const CustomDataType data_type, - const int domain_size, - const AttributeInit &initializer) +static void *add_generic_custom_data_layer(CustomData &custom_data, + const CustomDataType data_type, + const eCDAllocType alloctype, + void *layer_data, + const int domain_size, + const AttributeIDRef &attribute_id) { - char attribute_name_c[MAX_NAME]; - attribute_name.copy(attribute_name_c); + if (attribute_id.is_named()) { + char attribute_name_c[MAX_NAME]; + attribute_id.name().copy(attribute_name_c); + return CustomData_add_layer_named( + &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + } + const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id(); + return CustomData_add_layer_anonymous( + &custom_data, data_type, alloctype, layer_data, domain_size, &anonymous_id); +} +static bool add_custom_data_layer_from_attribute_init(const AttributeIDRef &attribute_id, + CustomData &custom_data, + const CustomDataType data_type, + const int domain_size, + const AttributeInit &initializer) +{ switch (initializer.type) { case AttributeInit::Type::Default: { - void *data = CustomData_add_layer_named( - &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + void *data = add_generic_custom_data_layer( + custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id); return data != nullptr; } case AttributeInit::Type::VArray: { - void *data = CustomData_add_layer_named( - &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + void *data = add_generic_custom_data_layer( + custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id); if (data == nullptr) { return false; } @@ -446,8 +485,8 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr } case AttributeInit::Type::MoveArray: { void *source_data = static_cast<const AttributeInitMove &>(initializer).data; - void *data = CustomData_add_layer_named( - &custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c); + void *data = add_generic_custom_data_layer( + custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_id); if (data == nullptr) { MEM_freeN(source_data); return false; @@ -461,7 +500,7 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr } bool CustomDataAttributeProvider::try_create(GeometryComponent &component, - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) const @@ -477,13 +516,13 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component, return false; } for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { return false; } } const int domain_size = component.attribute_domain_size(domain_); - add_named_custom_data_layer_from_attribute_init( - attribute_name, *custom_data, data_type, domain_size, initializer); + add_custom_data_layer_from_attribute_init( + attribute_id, *custom_data, data_type, domain_size, initializer); return true; } @@ -498,7 +537,14 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com const CustomDataType data_type = (CustomDataType)layer.type; if (this->type_is_supported(data_type)) { AttributeMetaData meta_data{domain_, data_type}; - if (!callback(layer.name, meta_data)) { + AttributeIDRef attribute_id; + if (layer.anonymous_id != nullptr) { + attribute_id = layer.anonymous_id; + } + else { + attribute_id = layer.name; + } + if (!callback(attribute_id, meta_data)) { return false; } } @@ -507,7 +553,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com } ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( - const GeometryComponent &component, const StringRef attribute_name) const + const GeometryComponent &component, const AttributeIDRef &attribute_id) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); if (custom_data == nullptr) { @@ -515,7 +561,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( } for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { if (layer.type == stored_type_) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const int domain_size = component.attribute_domain_size(domain_); return {as_read_attribute_(layer.data, domain_size), domain_}; } @@ -525,7 +571,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( } WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( - GeometryComponent &component, const StringRef attribute_name) const + GeometryComponent &component, const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -533,7 +579,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( } for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) { if (layer.type == stored_type_) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const int domain_size = component.attribute_domain_size(domain_); void *data_old = layer.data; void *data_new = CustomData_duplicate_referenced_layer_named( @@ -549,7 +595,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( } bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -558,7 +604,7 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, for (const int i : IndexRange(custom_data->totlayer)) { const CustomDataLayer &layer = custom_data->layers[i]; if (layer.type == stored_type_) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const int domain_size = component.attribute_domain_size(domain_); CustomData_free_layer(custom_data, stored_type_, domain_size, i); custom_data_access_.update_custom_data_pointers(component); @@ -627,11 +673,11 @@ CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes return *this; } -std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const +std::optional<GSpan> CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id) const { BLI_assert(size_ != 0); for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { - if (layer.name == name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); BLI_assert(cpp_type != nullptr); return GSpan(*cpp_type, layer.data, size_); @@ -645,13 +691,13 @@ std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) co * value if the attribute doesn't exist. If no default value is provided, the default value for the * type will be used. */ -GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name, +GVArrayPtr CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id, const CustomDataType data_type, const void *default_value) const { const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); - std::optional<GSpan> attribute = this->get_for_read(name); + std::optional<GSpan> attribute = this->get_for_read(attribute_id); if (!attribute) { const int domain_size = this->size_; return std::make_unique<GVArray_For_SingleValue>( @@ -666,12 +712,12 @@ GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name, return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type); } -std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name) +std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const AttributeIDRef &attribute_id) { /* If this assert hits, it most likely means that #reallocate was not called at some point. */ BLI_assert(size_ != 0); for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) { - if (layer.name == name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); BLI_assert(cpp_type != nullptr); return GMutableSpan(*cpp_type, layer.data, size_); @@ -680,30 +726,29 @@ std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef return {}; } -bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type) +bool CustomDataAttributes::create(const AttributeIDRef &attribute_id, + const CustomDataType data_type) { - char name_c[MAX_NAME]; - name.copy(name_c); - void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c); + void *result = add_generic_custom_data_layer( + data, data_type, CD_DEFAULT, nullptr, size_, attribute_id); return result != nullptr; } -bool CustomDataAttributes::create_by_move(const blender::StringRef name, +bool CustomDataAttributes::create_by_move(const AttributeIDRef &attribute_id, const CustomDataType data_type, void *buffer) { - char name_c[MAX_NAME]; - name.copy(name_c); - void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c); + void *result = add_generic_custom_data_layer( + data, data_type, CD_ASSIGN, buffer, size_, attribute_id); return result != nullptr; } -bool CustomDataAttributes::remove(const blender::StringRef name) +bool CustomDataAttributes::remove(const AttributeIDRef &attribute_id) { bool result = false; for (const int i : IndexRange(data.totlayer)) { const CustomDataLayer &layer = data.layers[i]; - if (layer.name == name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { CustomData_free_layer(&data, layer.type, size_, i); result = true; } @@ -722,7 +767,14 @@ bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback call { for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { AttributeMetaData meta_data{domain, (CustomDataType)layer.type}; - if (!callback(layer.name, meta_data)) { + AttributeIDRef attribute_id; + if (layer.anonymous_id != nullptr) { + attribute_id = layer.anonymous_id; + } + else { + attribute_id = layer.name; + } + if (!callback(attribute_id, meta_data)) { return false; } } @@ -765,22 +817,30 @@ bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_ return providers->builtin_attribute_providers().contains_as(attribute_name); } +bool GeometryComponent::attribute_is_builtin(const AttributeIDRef &attribute_id) const +{ + /* Anonymous attributes cannot be built-in. */ + return attribute_id.is_named() && this->attribute_is_builtin(attribute_id.name()); +} + blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return {}; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()}; + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()}; + } } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name); + ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_id); if (attribute) { return attribute; } @@ -800,21 +860,23 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_dom } blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write( - const StringRef attribute_name) + const AttributeIDRef &attribute_id) { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return {}; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()}; + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()}; + } } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name); + WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_id); if (attribute) { return attribute; } @@ -822,53 +884,57 @@ blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_writ return {}; } -bool GeometryComponent::attribute_try_delete(const StringRef attribute_name) +bool GeometryComponent::attribute_try_delete(const AttributeIDRef &attribute_id) { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return {}; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - return builtin_provider->try_delete(*this); + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + return builtin_provider->try_delete(*this); + } } bool success = false; for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - success = dynamic_provider->try_delete(*this, attribute_name) || success; + success = dynamic_provider->try_delete(*this, attribute_id) || success; } return success; } -bool GeometryComponent::attribute_try_create(const StringRef attribute_name, +bool GeometryComponent::attribute_try_create(const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) { using namespace blender::bke; - if (attribute_name.is_empty()) { + if (!attribute_id) { return false; } const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return false; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - if (builtin_provider->domain() != domain) { - return false; - } - if (builtin_provider->data_type() != data_type) { - return false; + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + if (builtin_provider->domain() != domain) { + return false; + } + if (builtin_provider->data_type() != data_type) { + return false; + } + return builtin_provider->try_create(*this, initializer); } - return builtin_provider->try_create(*this, initializer); } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) { + if (dynamic_provider->try_create(*this, attribute_id, domain, data_type, initializer)) { return true; } } @@ -894,13 +960,14 @@ bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef at return builtin_provider->try_create(*this, initializer); } -Set<std::string> GeometryComponent::attribute_names() const +Set<AttributeIDRef> GeometryComponent::attribute_ids() const { - Set<std::string> attributes; - this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) { - attributes.add(name); - return true; - }); + Set<AttributeIDRef> attributes; + this->attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + attributes.add(attribute_id); + return true; + }); return attributes; } @@ -931,9 +998,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac } for (const DynamicAttributesProvider *provider : providers->dynamic_attribute_providers()) { const bool continue_loop = provider->foreach_attribute( - *this, [&](StringRefNull name, const AttributeMetaData &meta_data) { - if (handled_attribute_names.add(name)) { - return callback(name, meta_data); + *this, [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_anonymous() || handled_attribute_names.add(attribute_id.name())) { + return callback(attribute_id, meta_data); } return true; }); @@ -945,9 +1012,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac return true; } -bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const +bool GeometryComponent::attribute_exists(const AttributeIDRef &attribute_id) const { - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (attribute) { return true; } @@ -955,16 +1022,17 @@ bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name } std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data( - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { std::optional<AttributeMetaData> result{std::nullopt}; - this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (attribute_name == name) { - result = meta_data; - return false; - } - return true; - }); + this->attribute_foreach( + [&](const AttributeIDRef ¤t_attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id == current_attribute_id) { + result = meta_data; + return false; + } + return true; + }); return result; } @@ -977,11 +1045,11 @@ static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type( } std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read( - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type) const { - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (!attribute) { return {}; } @@ -1007,13 +1075,13 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r } std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read( - const StringRef attribute_name, const AttributeDomain domain) const + const AttributeIDRef &attribute_id, const AttributeDomain domain) const { if (!this->attribute_domain_supported(domain)) { return {}; } - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (!attribute) { return {}; } @@ -1026,9 +1094,9 @@ std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_ } blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( - const blender::StringRef attribute_name, const CustomDataType data_type) const + const AttributeIDRef &attribute_id, const CustomDataType data_type) const { - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (!attribute) { return {}; } @@ -1043,13 +1111,13 @@ blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( } std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read( - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const void *default_value) const { std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read( - attribute_name, domain, data_type); + attribute_id, domain, data_type); if (varray) { return varray; } @@ -1065,15 +1133,22 @@ class GVMutableAttribute_For_OutputAttribute : public blender::fn::GVMutableArray_For_GMutableSpan { public: GeometryComponent *component; - std::string final_name; + std::string attribute_name; + blender::bke::WeakAnonymousAttributeID anonymous_attribute_id; GVMutableAttribute_For_OutputAttribute(GMutableSpan data, GeometryComponent &component, - std::string final_name) - : blender::fn::GVMutableArray_For_GMutableSpan(data), - component(&component), - final_name(std::move(final_name)) + const AttributeIDRef &attribute_id) + : blender::fn::GVMutableArray_For_GMutableSpan(data), component(&component) { + if (attribute_id.is_named()) { + this->attribute_name = attribute_id.name(); + } + else { + const AnonymousAttributeID *anonymous_id = &attribute_id.anonymous_id(); + BKE_anonymous_attribute_id_increment_weak(anonymous_id); + this->anonymous_attribute_id = blender::bke::WeakAnonymousAttributeID{anonymous_id}; + } } ~GVMutableAttribute_For_OutputAttribute() override @@ -1083,7 +1158,7 @@ class GVMutableAttribute_For_OutputAttribute } }; -static void save_output_attribute(blender::bke::OutputAttribute &output_attribute) +static void save_output_attribute(OutputAttribute &output_attribute) { using namespace blender; using namespace blender::fn; @@ -1093,21 +1168,28 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray()); GeometryComponent &component = *varray.component; - const StringRefNull name = varray.final_name; + AttributeIDRef attribute_id; + if (!varray.attribute_name.empty()) { + attribute_id = varray.attribute_name; + } + else { + attribute_id = varray.anonymous_attribute_id.extract(); + } const AttributeDomain domain = output_attribute.domain(); const CustomDataType data_type = output_attribute.custom_data_type(); const CPPType &cpp_type = output_attribute.cpp_type(); - component.attribute_try_delete(name); - if (!component.attribute_try_create( - varray.final_name, domain, data_type, AttributeInitDefault())) { - CLOG_WARN(&LOG, - "Could not create the '%s' attribute with type '%s'.", - name.c_str(), - cpp_type.name().c_str()); + component.attribute_try_delete(attribute_id); + if (!component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault())) { + if (!varray.attribute_name.empty()) { + CLOG_WARN(&LOG, + "Could not create the '%s' attribute with type '%s'.", + varray.attribute_name.c_str(), + cpp_type.name().c_str()); + } return; } - WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name); + WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id); BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer); for (const int i : IndexRange(varray.size())) { varray.get(i, buffer); @@ -1115,19 +1197,18 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut } } -static blender::bke::OutputAttribute create_output_attribute( - GeometryComponent &component, - const blender::StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const bool ignore_old_values, - const void *default_value) +static OutputAttribute create_output_attribute(GeometryComponent &component, + const AttributeIDRef &attribute_id, + const AttributeDomain domain, + const CustomDataType data_type, + const bool ignore_old_values, + const void *default_value) { using namespace blender; using namespace blender::fn; using namespace blender::bke; - if (attribute_name.is_empty()) { + if (!attribute_id) { return {}; } @@ -1135,7 +1216,8 @@ static blender::bke::OutputAttribute create_output_attribute( BLI_assert(cpp_type != nullptr); const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions(); - if (component.attribute_is_builtin(attribute_name)) { + if (component.attribute_is_builtin(attribute_id)) { + const StringRef attribute_name = attribute_id.name(); WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); if (!attribute) { if (default_value) { @@ -1169,18 +1251,18 @@ static blender::bke::OutputAttribute create_output_attribute( const int domain_size = component.attribute_domain_size(domain); - WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); + WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_id); if (!attribute) { if (default_value) { const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value}; component.attribute_try_create( - attribute_name, domain, data_type, AttributeInitVArray(&default_varray)); + attribute_id, domain, data_type, AttributeInitVArray(&default_varray)); } else { - component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault()); + component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault()); } - attribute = component.attribute_try_get_for_write(attribute_name); + attribute = component.attribute_try_get_for_write(attribute_id); if (!attribute) { /* Can't create the attribute. */ return {}; @@ -1202,28 +1284,104 @@ static blender::bke::OutputAttribute create_output_attribute( else { /* Fill the temporary array with values from the existing attribute. */ GVArrayPtr old_varray = component.attribute_get_for_read( - attribute_name, domain, data_type, default_value); + attribute_id, domain, data_type, default_value); old_varray->materialize_to_uninitialized(IndexRange(domain_size), data); } GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>( - GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name); + GMutableSpan{*cpp_type, data, domain_size}, component, attribute_id); return OutputAttribute(std::move(varray), domain, save_output_attribute, true); } -blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output( - const StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) +OutputAttribute GeometryComponent::attribute_try_get_for_output(const AttributeIDRef &attribute_id, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value) { - return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value); + return create_output_attribute(*this, attribute_id, domain, data_type, false, default_value); } -blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only( - const blender::StringRef attribute_name, +OutputAttribute GeometryComponent::attribute_try_get_for_output_only( + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type) { - return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr); + return create_output_attribute(*this, attribute_id, domain, data_type, true, nullptr); +} + +namespace blender::bke { + +const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContext &context, + IndexMask UNUSED(mask), + ResourceScope &scope) const +{ + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + const CustomDataType data_type = cpp_type_to_custom_data_type(*type_); + GVArrayPtr attribute = component.attribute_try_get_for_read(name_, domain, data_type); + if (attribute) { + return scope.add(std::move(attribute)); + } + } + return nullptr; +} + +std::string AttributeFieldInput::socket_inspection_name() const +{ + std::stringstream ss; + ss << TIP_("Attribute: ") << name_; + return ss.str(); +} + +uint64_t AttributeFieldInput::hash() const +{ + return get_default_hash_2(name_, type_); +} + +bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const +{ + if (const AttributeFieldInput *other_typed = dynamic_cast<const AttributeFieldInput *>(&other)) { + return name_ == other_typed->name_ && type_ == other_typed->type_; + } + return false; } + +const GVArray *AnonymousAttributeFieldInput::get_varray_for_context( + const fn::FieldContext &context, IndexMask UNUSED(mask), ResourceScope &scope) const +{ + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + const CustomDataType data_type = cpp_type_to_custom_data_type(*type_); + GVArrayPtr attribute = component.attribute_try_get_for_read( + anonymous_id_.get(), domain, data_type); + return scope.add(std::move(attribute)); + } + return nullptr; +} + +std::string AnonymousAttributeFieldInput::socket_inspection_name() const +{ + std::stringstream ss; + ss << TIP_("Anonymous Attribute: ") << debug_name_; + return ss.str(); +} + +uint64_t AnonymousAttributeFieldInput::hash() const +{ + return get_default_hash_2(anonymous_id_.get(), type_); +} + +bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const +{ + if (const AnonymousAttributeFieldInput *other_typed = + dynamic_cast<const AnonymousAttributeFieldInput *>(&other)) { + return anonymous_id_.get() == other_typed->anonymous_id_.get() && type_ == other_typed->type_; + } + return false; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index b3a795faa30..261cb26d4e5 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -116,12 +116,13 @@ class BuiltinAttributeProvider { class DynamicAttributesProvider { public: virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const = 0; + const AttributeIDRef &attribute_id) const = 0; virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const = 0; - virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0; + const AttributeIDRef &attribute_id) const = 0; + virtual bool try_delete(GeometryComponent &component, + const AttributeIDRef &attribute_id) const = 0; virtual bool try_create(GeometryComponent &UNUSED(component), - const StringRef UNUSED(attribute_name), + const AttributeIDRef &UNUSED(attribute_id), const AttributeDomain UNUSED(domain), const CustomDataType UNUSED(data_type), const AttributeInit &UNUSED(initializer)) const @@ -154,15 +155,15 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { } ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final; bool try_create(GeometryComponent &component, - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) const final; @@ -231,10 +232,10 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { } ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final; bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final; void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final; diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c index 97a54f289ee..97f8bddc043 100644 --- a/source/blender/blenkernel/intern/blender.c +++ b/source/blender/blenkernel/intern/blender.c @@ -362,7 +362,9 @@ void BKE_blender_userdef_app_template_data_swap(UserDef *userdef_a, UserDef *use DATA_SWAP(app_flag); /* We could add others. */ - FLAG_SWAP(uiflag, int, USER_SAVE_PROMPT); + FLAG_SWAP(uiflag, int, USER_SAVE_PROMPT | USER_SPLASH_DISABLE | USER_SHOW_GIZMO_NAVIGATE); + + DATA_SWAP(ui_scale); #undef SWAP_TYPELESS #undef DATA_SWAP diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index d60ef28efda..d70b941695e 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -142,8 +142,16 @@ static void brush_free_data(ID *id) static void brush_make_local(Main *bmain, ID *id, const int flags) { + if (!ID_IS_LINKED(id)) { + return; + } + Brush *brush = (Brush *)id; const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; + bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; + bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; + BLI_assert(force_copy == false || force_copy != force_local); + bool is_local = false, is_lib = false; /* - only lib users: do nothing (unless force_local is set) @@ -151,36 +159,46 @@ static void brush_make_local(Main *bmain, ID *id, const int flags) * - mixed: make copy */ - if (!ID_IS_LINKED(brush)) { - return; - } - if (brush->clone.image) { /* Special case: ima always local immediately. Clone image should only have one user anyway. */ - BKE_lib_id_make_local(bmain, &brush->clone.image->id, false, 0); + /* FIXME: Recursive calls affecting other non-embedded IDs are really bad and should be avoided + * in IDType callbacks. Higher-level ID management code usually does not expect such things and + * does not deal properly with it. */ + /* NOTE: assert below ensures that the comment above is valid, and that that exception is + * acceptable for the time being. */ + BKE_lib_id_make_local(bmain, &brush->clone.image->id, 0); + BLI_assert(brush->clone.image->id.lib == NULL && brush->clone.image->id.newid == NULL); + } + + if (!force_local && !force_copy) { + BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib); + if (lib_local || is_local) { + if (!is_lib) { + force_local = true; + } + else { + force_copy = true; + } + } } - BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib); + if (force_local) { + BKE_lib_id_clear_library_data(bmain, &brush->id); + BKE_lib_id_expand_local(bmain, &brush->id); - if (lib_local || is_local) { - if (!is_lib) { - BKE_lib_id_clear_library_data(bmain, &brush->id); - BKE_lib_id_expand_local(bmain, &brush->id); - - /* enable fake user by default */ - id_fake_user_set(&brush->id); - } - else { - Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */ + /* enable fake user by default */ + id_fake_user_set(&brush->id); + } + else if (force_copy) { + Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */ - brush_new->id.us = 0; + brush_new->id.us = 0; - /* setting newid is mandatory for complex make_lib_local logic... */ - ID_NEW_SET(brush, brush_new); + /* setting newid is mandatory for complex make_lib_local logic... */ + ID_NEW_SET(brush, brush_new); - if (!lib_local) { - BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE); - } + if (!lib_local) { + BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE); } } } diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 72f14d94833..b9b15eba6a4 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -3900,7 +3900,11 @@ static void clampto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar unit_m4(targetMatrix); INIT_MINMAX(curveMin, curveMax); - /* XXX(campbell): don't think this is good calling this here. */ + /* XXX(@campbellbarton): don't think this is good calling this here because + * the other object's data is lazily initializing bounding-box information. + * This could cause issues when evaluating from a thread. + * If the depsgraph ensures the bound-box is always available, a code-path could + * be used that doesn't lazy initialize to avoid thread safety issues in the future. */ BKE_object_minmax(ct->tar, curveMin, curveMax, true); /* get targetmatrix */ diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c index 397838e6904..f22c3b13efc 100644 --- a/source/blender/blenkernel/intern/curve.c +++ b/source/blender/blenkernel/intern/curve.c @@ -5269,6 +5269,8 @@ void BKE_curve_transform_ex(Curve *cu, BezTriple *bezt; int i; + const bool is_uniform_scaled = is_uniform_scaled_m4(mat); + LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) { if (nu->type == CU_BEZIER) { i = nu->pntsu; @@ -5279,6 +5281,11 @@ void BKE_curve_transform_ex(Curve *cu, if (do_props) { bezt->radius *= unit_scale; } + if (!is_uniform_scaled) { + if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) || ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) { + bezt->h1 = bezt->h2 = HD_ALIGN; + } + } } BKE_nurb_handles_calc(nu); } diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 5c18f6f3807..ea84766943d 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -25,6 +25,7 @@ #include "DNA_curve_types.h" +#include "BKE_anonymous_attribute.hh" #include "BKE_curve.h" #include "BKE_spline.hh" @@ -37,6 +38,7 @@ using blender::MutableSpan; using blender::Span; using blender::StringRefNull; using blender::Vector; +using blender::bke::AttributeIDRef; blender::Span<SplinePtr> CurveEval::splines() const { @@ -108,7 +110,7 @@ void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluate } /** - * Return the start indices for each of the curve spline's evaluated points, as if they were part + * Return the start indices for each of the curve spline's control points, if they were part * of a flattened array. This can be used to facilitate parallelism by avoiding the need to * accumulate an offset while doing more complex calculations. * @@ -330,13 +332,13 @@ void CurveEval::assert_valid_point_attributes() const return; } const int layer_len = splines_.first()->attributes.data.totlayer; - Map<StringRefNull, AttributeMetaData> map; + Map<AttributeIDRef, AttributeMetaData> map; for (const SplinePtr &spline : splines_) { BLI_assert(spline->attributes.data.totlayer == layer_len); spline->attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { map.add_or_modify( - name, + attribute_id, [&](AttributeMetaData *map_data) { /* All unique attribute names should be added on the first spline. */ BLI_assert(spline == splines_.first()); diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index 1a3200a9b6c..ad2d5d267d5 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -46,6 +46,7 @@ #include "BLT_translation.h" +#include "BKE_anonymous_attribute.h" #include "BKE_customdata.h" #include "BKE_customdata_file.h" #include "BKE_deform.h" @@ -2127,6 +2128,11 @@ bool CustomData_merge(const struct CustomData *source, if (flag & CD_FLAG_NOCOPY) { continue; } + if (layer->anonymous_id && + !BKE_anonymous_attribute_id_has_strong_references(layer->anonymous_id)) { + /* This attribute is not referenced anymore, so it can be treated as if it didn't exist. */ + continue; + } if (!(mask & CD_TYPE_AS_MASK(type))) { continue; } @@ -2166,6 +2172,11 @@ bool CustomData_merge(const struct CustomData *source, newlayer->active_mask = lastmask; newlayer->flag |= flag & (CD_FLAG_EXTERNAL | CD_FLAG_IN_MEMORY); changed = true; + + if (layer->anonymous_id != NULL) { + BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id); + newlayer->anonymous_id = layer->anonymous_id; + } } } @@ -2206,6 +2217,10 @@ static void customData_free_layer__internal(CustomDataLayer *layer, int totelem) { const LayerTypeInfo *typeInfo; + if (layer->anonymous_id != NULL) { + BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id); + layer->anonymous_id = NULL; + } if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) { typeInfo = layerType_getInfo(layer->type); @@ -2649,6 +2664,27 @@ void *CustomData_add_layer_named(CustomData *data, return NULL; } +void *CustomData_add_layer_anonymous(struct CustomData *data, + int type, + eCDAllocType alloctype, + void *layerdata, + int totelem, + const AnonymousAttributeID *anonymous_id) +{ + const char *name = BKE_anonymous_attribute_id_internal_name(anonymous_id); + CustomDataLayer *layer = customData_add_layer__internal( + data, type, alloctype, layerdata, totelem, name); + CustomData_update_typemap(data); + + if (layer == NULL) { + return NULL; + } + + BKE_anonymous_attribute_id_increment_weak(anonymous_id); + layer->anonymous_id = anonymous_id; + return layer->data; +} + bool CustomData_free_layer(CustomData *data, int type, int totelem, int index) { const int index_first = CustomData_get_layer_index(data, type); @@ -2812,6 +2848,20 @@ void *CustomData_duplicate_referenced_layer_named(CustomData *data, return customData_duplicate_referenced_layer_index(data, layer_index, totelem); } +void *CustomData_duplicate_referenced_layer_anonymous(CustomData *data, + const int UNUSED(type), + const AnonymousAttributeID *anonymous_id, + const int totelem) +{ + for (int i = 0; i < data->totlayer; i++) { + if (data->layers[i].anonymous_id == anonymous_id) { + return customData_duplicate_referenced_layer_index(data, i, totelem); + } + } + BLI_assert_unreachable(); + return NULL; +} + void CustomData_duplicate_referenced_layers(CustomData *data, int totelem) { for (int i = 0; i < data->totlayer; i++) { @@ -4244,7 +4294,8 @@ void CustomData_blend_write_prepare(CustomData *data, for (i = 0, j = 0; i < totlayer; i++) { CustomDataLayer *layer = &data->layers[i]; - if (layer->flag & CD_FLAG_NOCOPY) { /* Layers with this flag set are not written to file. */ + /* Layers with this flag set are not written to file. */ + if ((layer->flag & CD_FLAG_NOCOPY) || layer->anonymous_id != NULL) { data->totlayer--; // CLOG_WARN(&LOG, "skipping layer %p (%s)", layer, layer->name); } diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index 58509e95de6..e756daa1156 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -40,6 +40,7 @@ #include "BLI_math.h" #include "BLI_memarena.h" #include "BLI_scanfill.h" +#include "BLI_span.hh" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -47,6 +48,7 @@ #include "BKE_curve.h" #include "BKE_displist.h" #include "BKE_font.h" +#include "BKE_geometry_set.hh" #include "BKE_key.h" #include "BKE_lattice.h" #include "BKE_lib_id.h" @@ -55,6 +57,7 @@ #include "BKE_mesh.h" #include "BKE_modifier.h" #include "BKE_object.h" +#include "BKE_spline.hh" #include "BLI_sys_types.h" /* For #intptr_t support. */ @@ -101,17 +104,6 @@ DispList *BKE_displist_find(ListBase *lb, int type) return nullptr; } -bool BKE_displist_has_faces(const ListBase *lb) -{ - LISTBASE_FOREACH (const DispList *, dl, lb) { - if (ELEM(dl->type, DL_INDEX3, DL_INDEX4, DL_SURF)) { - return true; - } - } - - return false; -} - void BKE_displist_copy(ListBase *lbn, const ListBase *lb) { BKE_displist_free(lbn); @@ -688,23 +680,9 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob) BKE_mball_texspace_calc(ob); object_deform_mball(ob, &ob->runtime.curve_cache->disp); - - /* No-op for MBALLs anyway... */ - boundbox_displist_object(ob); } } -void BKE_displist_make_mball_forRender(Depsgraph *depsgraph, - Scene *scene, - Object *ob, - ListBase *dispbase) -{ - BKE_mball_polygonize(depsgraph, scene, ob, dispbase); - BKE_mball_texspace_calc(ob); - - object_deform_mball(ob, dispbase); -} - static ModifierData *curve_get_tessellate_point(const Scene *scene, const Object *ob, const bool for_render, @@ -745,10 +723,7 @@ static ModifierData *curve_get_tessellate_point(const Scene *scene, return pretessellatePoint; } -/** - * \return True if any modifier was applied. - */ -bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, +void BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, const Scene *scene, Object *ob, ListBase *source_nurb, @@ -793,7 +768,6 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, const ModifierEvalContext mectx = {depsgraph, ob, apply_flag}; ModifierData *pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode); - bool modified = false; if (pretessellatePoint) { VirtualModifierData virtualModifierData; @@ -813,7 +787,6 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, } mti->deformVerts(md, &mectx, nullptr, deformedVerts, numVerts); - modified = true; if (md == pretessellatePoint) { break; @@ -832,48 +805,59 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, if (keyVerts) { MEM_freeN(keyVerts); } - return modified; } -static float (*displist_vert_coords_alloc(ListBase *dispbase, int *r_vert_len))[3] +/** + * \return True if the deformed curve control point data should be implicitly + * converted directly to a mesh, or false if it can be left as curve data via #CurveEval. + */ +static bool do_curve_implicit_mesh_conversion(const Curve *curve, + ModifierData *first_modifier, + const Scene *scene, + const ModifierMode required_mode) { - *r_vert_len = 0; + /* Skip implicit filling and conversion to mesh when using "fast text editing". */ + if (curve->flag & CU_FAST) { + return false; + } - LISTBASE_FOREACH (DispList *, dl, dispbase) { - *r_vert_len += (dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr; + /* Do implicit conversion to mesh with the object bevel mode. */ + if (curve->bevel_mode == CU_BEV_MODE_OBJECT && curve->bevobj != nullptr) { + return true; } - float(*allverts)[3] = (float(*)[3])MEM_mallocN(sizeof(float[3]) * (*r_vert_len), __func__); - float *fp = (float *)allverts; - LISTBASE_FOREACH (DispList *, dl, dispbase) { - const int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); - memcpy(fp, dl->verts, sizeof(float) * ofs); - fp += ofs; + /* 2D curves are sometimes implicitly filled and converted to a mesh. */ + if (CU_DO_2DFILL(curve)) { + return true; } - return allverts; -} + /* Curve objects with implicit "tube" meshes should convert implicitly to a mesh. */ + if (curve->ext1 != 0.0f || curve->ext2 != 0.0f) { + return true; + } -static void displist_vert_coords_apply(ListBase *dispbase, const float (*allverts)[3]) -{ - const float *fp = (float *)allverts; - LISTBASE_FOREACH (DispList *, dl, dispbase) { - int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); - memcpy(dl->verts, fp, sizeof(float) * ofs); - fp += ofs; + /* If a non-geometry-nodes modifier is enabled before a nodes modifier, + * force conversion to mesh, since only the nodes modifier supports curve data. */ + ModifierData *md = first_modifier; + for (; md; md = md->next) { + if (BKE_modifier_is_enabled(scene, md, required_mode)) { + if (md->type == eModifierType_Nodes) { + break; + } + return true; + } } + + return false; } -static void curve_calc_modifiers_post(Depsgraph *depsgraph, - const Scene *scene, - Object *ob, - ListBase *dispbase, - const bool for_render, - const bool force_mesh_conversion, - Mesh **r_final) +static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph, + const Scene *scene, + Object *ob, + const ListBase *dispbase, + const bool for_render) { const Curve *cu = (const Curve *)ob->data; - const bool editmode = (!for_render && (cu->editnurb || cu->editfont)); const bool use_cache = !for_render; @@ -897,166 +881,64 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData) : pretessellatePoint->next; - if (r_final && *r_final) { - BKE_id_free(nullptr, *r_final); + GeometrySet geometry_set; + if (ob->type == OB_SURF || do_curve_implicit_mesh_conversion(cu, md, scene, required_mode)) { + Mesh *mesh = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); + geometry_set.replace_mesh(mesh); + } + else { + std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve( + *cu, ob->runtime.curve_cache->deformed_nurbs); + geometry_set.replace_curve(curve_eval.release()); } - Mesh *modified = nullptr; - float(*vertCos)[3] = nullptr; - int totvert = 0; for (; md; md = md->next) { const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); - if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; } - /* If we need normals, no choice, have to convert to mesh now. */ - const bool need_normal = mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md); - /* XXX 2.8 : now that batch cache is stored inside the ob->data - * we need to create a Mesh for each curve that uses modifiers. */ - if (modified == nullptr /* && need_normal */) { - if (vertCos != nullptr) { - displist_vert_coords_apply(dispbase, vertCos); - } - - if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, dispbase); - } + if (md->type == eModifierType_Nodes) { + mti->modifyGeometrySet(md, &mectx_apply, &geometry_set); + continue; + } - modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); + if (!geometry_set.has_mesh()) { + geometry_set.replace_mesh(BKE_mesh_new_nomain(0, 0, 0, 0, 0)); } + Mesh *mesh = geometry_set.get_mesh_for_write(); - if (mti->type == eModifierTypeType_OnlyDeform || - (mti->type == eModifierTypeType_DeformOrConstruct && !modified)) { - if (modified) { - if (!vertCos) { - vertCos = BKE_mesh_vert_coords_alloc(modified, &totvert); - } - if (need_normal) { - BKE_mesh_ensure_normals(modified); - } - mti->deformVerts(md, &mectx_deform, modified, vertCos, totvert); - } - else { - if (!vertCos) { - vertCos = displist_vert_coords_alloc(dispbase, &totvert); - } - mti->deformVerts(md, &mectx_deform, nullptr, vertCos, totvert); + if (mti->type == eModifierTypeType_OnlyDeform) { + int totvert; + float(*vertex_coords)[3] = BKE_mesh_vert_coords_alloc(mesh, &totvert); + if (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) { + BKE_mesh_ensure_normals(mesh); } + mti->deformVerts(md, &mectx_deform, mesh, vertex_coords, totvert); + BKE_mesh_vert_coords_apply(mesh, vertex_coords); + MEM_freeN(vertex_coords); } else { - if (!r_final) { - /* makeDisplistCurveTypes could be used for beveling, where mesh - * is totally unnecessary, so we could stop modifiers applying - * when we found constructive modifier but mesh is unwanted. */ - break; - } - - if (modified) { - if (vertCos) { - Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( - nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); - BKE_id_free(nullptr, modified); - modified = temp_mesh; - - BKE_mesh_vert_coords_apply(modified, vertCos); - } - } - else { - if (vertCos) { - displist_vert_coords_apply(dispbase, vertCos); - } - - if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, dispbase); - } - - modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); - } - - if (vertCos) { - /* Vertex coordinates were applied to necessary data, could free it */ - MEM_freeN(vertCos); - vertCos = nullptr; - } - - if (need_normal) { - BKE_mesh_ensure_normals(modified); + if (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) { + BKE_mesh_ensure_normals(mesh); } - Mesh *mesh_applied = mti->modifyMesh(md, &mectx_apply, modified); - - if (mesh_applied) { - if (modified && modified != mesh_applied) { - BKE_id_free(nullptr, modified); - } - modified = mesh_applied; + Mesh *output_mesh = mti->modifyMesh(md, &mectx_apply, mesh); + if (mesh != output_mesh) { + geometry_set.replace_mesh(output_mesh); } } } - if (vertCos) { - if (modified) { - Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( - nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); - BKE_id_free(nullptr, modified); - modified = temp_mesh; + if (geometry_set.has_mesh()) { + Mesh *final_mesh = geometry_set.get_mesh_for_write(); - BKE_mesh_vert_coords_apply(modified, vertCos); - BKE_mesh_calc_normals(modified); + BKE_mesh_calc_normals(final_mesh); - MEM_freeN(vertCos); - } - else { - displist_vert_coords_apply(dispbase, vertCos); - MEM_freeN(vertCos); - vertCos = nullptr; - } + BLI_strncpy(final_mesh->id.name, cu->id.name, sizeof(final_mesh->id.name)); + *((short *)final_mesh->id.name) = ID_ME; } - if (r_final) { - if (force_mesh_conversion && !modified) { - /* XXX 2.8 : This is a workaround for by some deeper technical debts: - * - DRW Batch cache is stored inside the ob->data. - * - Curve data is not COWed for instances that use different modifiers. - * This can causes the modifiers to be applied on all user of the same data-block - * (see T71055) - * - * The easy workaround is to force to generate a Mesh that will be used for display data - * since a Mesh output is already used for generative modifiers. - * However it does not fix problems with actual edit data still being shared. - * - * The right solution would be to COW the Curve data block at the input of the modifier - * stack just like what the mesh modifier does. - */ - modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); - } - - if (modified) { - - /* XXX2.8(Sybren): make sure the face normals are recalculated as well */ - BKE_mesh_ensure_normals(modified); - - /* Special tweaks, needed since neither BKE_mesh_new_nomain_from_template() nor - * BKE_mesh_new_nomain_from_curve_displist() properly duplicate mat info... */ - BLI_strncpy(modified->id.name, cu->id.name, sizeof(modified->id.name)); - *((short *)modified->id.name) = ID_ME; - MEM_SAFE_FREE(modified->mat); - /* Set flag which makes it easier to see what's going on in a debugger. */ - modified->id.tag |= LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT; - modified->mat = (Material **)MEM_dupallocN(cu->mat); - modified->totcol = cu->totcol; - - (*r_final) = modified; - } - else { - (*r_final) = nullptr; - } - } - else if (modified != nullptr) { - /* Pretty stupid to generate that whole mesh if it's unused, yet we have to free it. */ - BKE_id_free(nullptr, modified); - } + return geometry_set; } static void displist_surf_indices(DispList *dl) @@ -1109,8 +991,7 @@ static void evaluate_surface_object(Depsgraph *depsgraph, BKE_nurbList_duplicate(deformed_nurbs, &cu->nurb); } - bool force_mesh_conversion = BKE_curve_calc_modifiers_pre( - depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); + BKE_curve_calc_modifiers_pre(depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); LISTBASE_FOREACH (const Nurb *, nu, deformed_nurbs) { if (!(for_render || nu->hide == 0) || !BKE_nurb_check_valid_uv(nu)) { @@ -1173,8 +1054,14 @@ static void evaluate_surface_object(Depsgraph *depsgraph, } } - curve_calc_modifiers_post( - depsgraph, scene, ob, r_dispbase, for_render, force_mesh_conversion, r_final); + curve_to_filledpoly(cu, r_dispbase); + GeometrySet geometry_set = curve_calc_modifiers_post( + depsgraph, scene, ob, r_dispbase, for_render); + if (!geometry_set.has_mesh()) { + geometry_set.replace_mesh(BKE_mesh_new_nomain(0, 0, 0, 0, 0)); + } + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + *r_final = mesh_component.release(); } static void rotateBevelPiece(const Curve *cu, @@ -1394,12 +1281,11 @@ static void calc_bevfac_mapping(const Curve *cu, } } -static void evaluate_curve_type_object(Depsgraph *depsgraph, - const Scene *scene, - Object *ob, - const bool for_render, - ListBase *r_dispbase, - Mesh **r_final) +static GeometrySet evaluate_curve_type_object(Depsgraph *depsgraph, + const Scene *scene, + Object *ob, + const bool for_render, + ListBase *r_dispbase) { BLI_assert(ELEM(ob->type, OB_CURVE, OB_FONT)); const Curve *cu = (const Curve *)ob->data; @@ -1413,8 +1299,7 @@ static void evaluate_curve_type_object(Depsgraph *depsgraph, BKE_nurbList_duplicate(deformed_nurbs, BKE_curve_nurbs_get_for_read(cu)); } - bool force_mesh_conversion = BKE_curve_calc_modifiers_pre( - depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); + BKE_curve_calc_modifiers_pre(depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); BKE_curve_bevelList_make(ob, deformed_nurbs, for_render); @@ -1603,16 +1488,8 @@ static void evaluate_curve_type_object(Depsgraph *depsgraph, BKE_displist_free(&dlbev); - if (!(cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, r_dispbase); - } - - curve_calc_modifiers_post( - depsgraph, scene, ob, r_dispbase, for_render, force_mesh_conversion, r_final); - - if (cu->flag & CU_DEFORM_FILL && !ob->runtime.data_eval) { - curve_to_filledpoly(cu, r_dispbase); - } + curve_to_filledpoly(cu, r_dispbase); + return curve_calc_modifiers_post(depsgraph, scene, ob, r_dispbase, for_render); } void BKE_displist_make_curveTypes(Depsgraph *depsgraph, @@ -1621,25 +1498,43 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, const bool for_render) { BLI_assert(ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT)); + Curve &cow_curve = *(Curve *)ob->data; BKE_object_free_derived_caches(ob); + cow_curve.curve_eval = nullptr; - if (!ob->runtime.curve_cache) { - ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); - } - - ListBase *dispbase = &(ob->runtime.curve_cache->disp); + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); + ListBase *dispbase = &ob->runtime.curve_cache->disp; - Mesh *mesh_eval = nullptr; if (ob->type == OB_SURF) { + Mesh *mesh_eval; evaluate_surface_object(depsgraph, scene, ob, for_render, dispbase, &mesh_eval); + BKE_object_eval_assign_data(ob, &mesh_eval->id, true); } else { - evaluate_curve_type_object(depsgraph, scene, ob, for_render, dispbase, &mesh_eval); - } + GeometrySet geometry = evaluate_curve_type_object(depsgraph, scene, ob, for_render, dispbase); + + if (geometry.has_curve()) { + /* Assign the evaluated curve to the object's "data_eval". In addition to the curve_eval + * added to the curve here, it will also contain a copy of the original curve's data. This is + * essential, because it maintains the expected behavior for evaluated curve data from before + * the CurveEval data type was introduced, when an evaluated object's curve data was just a + * copy of the original curve and everything else ended up in #CurveCache. */ + CurveComponent &curve_component = geometry.get_component_for_write<CurveComponent>(); + cow_curve.curve_eval = curve_component.get_for_write(); + BKE_object_eval_assign_data(ob, &cow_curve.id, false); + } + else if (geometry.has_mesh()) { + /* Most areas of Blender don't yet know how to look in #geometry_set_eval for evaluated mesh + * data, and look in #data_eval instead. When the object evaluates to a curve, that field + * must be used for the evaluated curve data, but otherwise we can use the field to store a + * pointer to the mesh, so more areas can retrieve the mesh. */ + MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>(); + Mesh *mesh_eval = mesh_component.get_for_write(); + BKE_object_eval_assign_data(ob, &mesh_eval->id, false); + } - if (mesh_eval != nullptr) { - BKE_object_eval_assign_data(ob, &mesh_eval->id, true); + ob->runtime.geometry_set_eval = new GeometrySet(std::move(geometry)); } boundbox_displist_object(ob); @@ -1656,7 +1551,9 @@ void BKE_displist_make_curveTypes_forRender( evaluate_surface_object(depsgraph, scene, ob, true, r_dispbase, r_final); } else { - evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase, r_final); + 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(); } } @@ -1684,27 +1581,26 @@ void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) /* this is confusing, there's also min_max_object, applying the obmat... */ static void boundbox_displist_object(Object *ob) { - if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) { - /* Curve's BB is already calculated as a part of modifier stack, - * here we only calculate object BB based on final display list. */ + BLI_assert(ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)); + /* Curve's BB is already calculated as a part of modifier stack, + * here we only calculate object BB based on final display list. */ - /* object's BB is calculated from final displist */ - if (ob->runtime.bb == nullptr) { - ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__); - } + /* object's BB is calculated from final displist */ + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__); + } - const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval) { - BKE_object_boundbox_calc_from_mesh(ob, mesh_eval); - } - else { - float min[3], max[3]; + const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); + if (mesh_eval) { + BKE_object_boundbox_calc_from_mesh(ob, mesh_eval); + } + else { + float min[3], max[3]; - INIT_MINMAX(min, max); - BKE_displist_minmax(&ob->runtime.curve_cache->disp, min, max); - BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + INIT_MINMAX(min, max); + BKE_displist_minmax(&ob->runtime.curve_cache->disp, min, max); + BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); - ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY; - } + ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY; } } diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index 0dc4f64cec1..d75b3259148 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -2064,7 +2064,7 @@ static Mesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object * } if (update_normals) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } } /* make a copy of mesh to use as brush data */ diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 5a5e1208ff0..1324b37f39c 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -39,6 +39,7 @@ #include "DNA_object_types.h" #include "DNA_rigidbody_types.h" +#include "BKE_attribute.h" #include "BKE_effect.h" #include "BKE_fluid.h" #include "BKE_global.h" @@ -529,8 +530,7 @@ static bool BKE_fluid_modifier_init( copy_v3_v3_int(fds->res_max, res); /* Set time, frame length = 0.1 is at 25fps. */ - float fps = scene->r.frs_sec / scene->r.frs_sec_base; - fds->frame_length = DT_DEFAULT * (25.0f / fps) * fds->time_scale; + fds->frame_length = DT_DEFAULT * (25.0f / FPS) * fds->time_scale; /* Initially dt is equal to frame length (dt can change with adaptive-time stepping though). */ fds->dt = fds->frame_length; fds->time_per_frame = 0; @@ -3256,7 +3256,10 @@ static void update_effectors( BKE_effectors_free(effectors); } -static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Object *ob) +static Mesh *create_liquid_geometry(FluidDomainSettings *fds, + Scene *scene, + Mesh *orgmesh, + Object *ob) { Mesh *me; MVert *mverts; @@ -3303,22 +3306,6 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj /* Normals are per vertex, so these must match. */ BLI_assert(num_verts == num_normals); - /* If needed, vertex velocities will be read too. */ - bool use_speedvectors = fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS; - FluidDomainVertexVelocity *velarray = NULL; - float time_mult = 25.0f * DT_DEFAULT; - - if (use_speedvectors) { - if (fds->mesh_velocities) { - MEM_freeN(fds->mesh_velocities); - } - - fds->mesh_velocities = MEM_calloc_arrayN( - num_verts, sizeof(FluidDomainVertexVelocity), "fluid_mesh_vertvelocities"); - fds->totvert = num_verts; - velarray = fds->mesh_velocities; - } - me = BKE_mesh_new_nomain(num_verts, 0, 0, num_faces * 3, num_faces); if (!me) { return NULL; @@ -3350,6 +3337,18 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj /* Normals. */ normals = MEM_callocN(sizeof(short[3]) * num_normals, "Fluidmesh_tmp_normals"); + /* Velocities. */ + /* If needed, vertex velocities will be read too. */ + bool use_speedvectors = fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS; + float(*velarray)[3] = NULL; + float time_mult = fds->dx / (DT_DEFAULT * (25.0f / FPS)); + + if (use_speedvectors) { + CustomDataLayer *velocity_layer = BKE_id_attribute_new( + &me->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT, NULL); + velarray = velocity_layer->data; + } + /* Loop for vertices and normals. */ for (i = 0, no_s = normals; i < num_verts && i < num_normals; i++, mverts++, no_s += 3) { @@ -3389,18 +3388,18 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj # endif if (use_speedvectors) { - velarray[i].vel[0] = manta_liquid_get_vertvel_x_at(fds->fluid, i) * (fds->dx / time_mult); - velarray[i].vel[1] = manta_liquid_get_vertvel_y_at(fds->fluid, i) * (fds->dx / time_mult); - velarray[i].vel[2] = manta_liquid_get_vertvel_z_at(fds->fluid, i) * (fds->dx / time_mult); + velarray[i][0] = manta_liquid_get_vertvel_x_at(fds->fluid, i) * time_mult; + velarray[i][1] = manta_liquid_get_vertvel_y_at(fds->fluid, i) * time_mult; + velarray[i][2] = manta_liquid_get_vertvel_z_at(fds->fluid, i) * time_mult; # ifdef DEBUG_PRINT /* Debugging: Print velocities of vertices. */ - printf("velarray[%d].vel[0]: %f, velarray[%d].vel[1]: %f, velarray[%d].vel[2]: %f\n", + printf("velarray[%d][0]: %f, velarray[%d][1]: %f, velarray[%d][2]: %f\n", i, - velarray[i].vel[0], + velarray[i][0], i, - velarray[i].vel[1], + velarray[i][1], i, - velarray[i].vel[2]); + velarray[i][2]); # endif } } @@ -3574,7 +3573,7 @@ static Mesh *create_smoke_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obje } BKE_mesh_calc_edges(result, false, false); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } @@ -3670,8 +3669,7 @@ static void manta_guiding( Depsgraph *depsgraph, Scene *scene, Object *ob, FluidModifierData *fmd, int frame) { FluidDomainSettings *fds = fmd->domain; - float fps = scene->r.frs_sec / scene->r.frs_sec_base; - float dt = DT_DEFAULT * (25.0f / fps) * fds->time_scale; + float dt = DT_DEFAULT * (25.0f / FPS) * fds->time_scale; BLI_mutex_lock(&object_update_lock); @@ -3842,8 +3840,7 @@ static void BKE_fluid_modifier_processDomain(FluidModifierData *fmd, copy_v3_v3_int(o_shift, fds->shift); /* Ensure that time parameters are initialized correctly before every step. */ - float fps = scene->r.frs_sec / scene->r.frs_sec_base; - fds->frame_length = DT_DEFAULT * (25.0f / fps) * fds->time_scale; + fds->frame_length = DT_DEFAULT * (25.0f / FPS) * fds->time_scale; fds->dt = fds->frame_length; fds->time_per_frame = 0; @@ -4216,7 +4213,7 @@ struct Mesh *BKE_fluid_modifier_do( if (needs_viewport_update) { /* Return generated geometry depending on domain type. */ if (fmd->domain->type == FLUID_DOMAIN_TYPE_LIQUID) { - result = create_liquid_geometry(fmd->domain, me, ob); + result = create_liquid_geometry(fmd->domain, scene, me, ob); } if (fmd->domain->type == FLUID_DOMAIN_TYPE_GAS) { result = create_smoke_geometry(fmd->domain, me, ob); @@ -4773,8 +4770,6 @@ static void BKE_fluid_modifier_freeDomain(FluidModifierData *fmd) fmd->domain->point_cache[0] = NULL; } - MEM_SAFE_FREE(fmd->domain->mesh_velocities); - if (fmd->domain->coba) { MEM_freeN(fmd->domain->coba); } @@ -5010,16 +5005,12 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd, tfds->viscosity_exponent = fds->viscosity_exponent; /* mesh options */ - if (fds->mesh_velocities) { - tfds->mesh_velocities = MEM_dupallocN(fds->mesh_velocities); - } tfds->mesh_concave_upper = fds->mesh_concave_upper; tfds->mesh_concave_lower = fds->mesh_concave_lower; tfds->mesh_particle_radius = fds->mesh_particle_radius; tfds->mesh_smoothen_pos = fds->mesh_smoothen_pos; tfds->mesh_smoothen_neg = fds->mesh_smoothen_neg; tfds->mesh_scale = fds->mesh_scale; - tfds->totvert = fds->totvert; tfds->mesh_generator = fds->mesh_generator; /* secondary particle options */ diff --git a/source/blender/blenkernel/intern/fmodifier.c b/source/blender/blenkernel/intern/fmodifier.c index 5aa3815729f..121927513cc 100644 --- a/source/blender/blenkernel/intern/fmodifier.c +++ b/source/blender/blenkernel/intern/fmodifier.c @@ -1419,17 +1419,19 @@ static float eval_fmodifier_influence(FModifier *fcm, float evaltime) /* restricted range or full range? */ if (fcm->flag & FMODIFIER_FLAG_RANGERESTRICT) { - if ((evaltime <= fcm->sfra) || (evaltime >= fcm->efra)) { + if ((evaltime < fcm->sfra) || (evaltime > fcm->efra)) { /* out of range */ return 0.0f; } - if ((evaltime > fcm->sfra) && (evaltime < fcm->sfra + fcm->blendin)) { + if ((fcm->blendin != 0.0f) && (evaltime >= fcm->sfra) && + (evaltime <= fcm->sfra + fcm->blendin)) { /* blend in range */ float a = fcm->sfra; float b = fcm->sfra + fcm->blendin; return influence * (evaltime - a) / (b - a); } - if ((evaltime < fcm->efra) && (evaltime > fcm->efra - fcm->blendout)) { + if ((fcm->blendout != 0.0f) && (evaltime <= fcm->efra) && + (evaltime >= fcm->efra - fcm->blendout)) { /* blend out range */ float a = fcm->efra; float b = fcm->efra - fcm->blendout; diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c index c1765967238..842a701f525 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/font.c @@ -719,6 +719,9 @@ typedef struct VFontToCurveIter { * * Currently only disabled when scale-to-fit is enabled, * so floating-point error doesn't cause unexpected wrapping, see T89241. + * + * \note This should only be set once, in the #VFONT_TO_CURVE_INIT pass + * otherwise iterations wont behave predictably, see T91401. */ bool word_wrap; int status; @@ -750,8 +753,15 @@ enum { * * The em_height here is relative to FT_Face->bbox. */ -#define ASCENT(vfd) ((vfd)->ascender * (vfd)->em_height) -#define DESCENT(vfd) ((vfd)->em_height - ASCENT(vfd)) + +static float vfont_ascent(const VFontData *vfd) +{ + return vfd->ascender * vfd->em_height; +} +static float vfont_descent(const VFontData *vfd) +{ + return vfd->em_height - vfont_ascent(vfd); +} static bool vfont_to_curve(Object *ob, Curve *cu, @@ -1234,17 +1244,17 @@ static bool vfont_to_curve(Object *ob, case CU_ALIGN_Y_TOP_BASELINE: break; case CU_ALIGN_Y_TOP: - yoff = textbox_y_origin - ASCENT(vfd); + yoff = textbox_y_origin - vfont_ascent(vfd); break; case CU_ALIGN_Y_CENTER: - yoff = ((((vfd->em_height + (lines - 1) * linedist) * 0.5f) - ASCENT(vfd)) - + yoff = ((((vfd->em_height + (lines - 1) * linedist) * 0.5f) - vfont_ascent(vfd)) - (tb_scale.h * 0.5f) + textbox_y_origin); break; case CU_ALIGN_Y_BOTTOM_BASELINE: yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h; break; case CU_ALIGN_Y_BOTTOM: - yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h + DESCENT(vfd); + yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h + vfont_descent(vfd); break; } @@ -1265,16 +1275,16 @@ static bool vfont_to_curve(Object *ob, case CU_ALIGN_Y_TOP_BASELINE: break; case CU_ALIGN_Y_TOP: - yoff = -ASCENT(vfd); + yoff = -vfont_ascent(vfd); break; case CU_ALIGN_Y_CENTER: - yoff = ((vfd->em_height + (lnr - 1) * linedist) * 0.5f) - ASCENT(vfd); + yoff = ((vfd->em_height + (lnr - 1) * linedist) * 0.5f) - vfont_ascent(vfd); break; case CU_ALIGN_Y_BOTTOM_BASELINE: yoff = (lnr - 1) * linedist; break; case CU_ALIGN_Y_BOTTOM: - yoff = (lnr - 1) * linedist + DESCENT(vfd); + yoff = (lnr - 1) * linedist + vfont_descent(vfd); break; } @@ -1640,7 +1650,6 @@ static bool vfont_to_curve(Object *ob, else { iter_data->scale_to_fit = iter_data->bisect.min; iter_data->status = VFONT_TO_CURVE_SCALE_ONCE; - iter_data->word_wrap = false; } } } diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 0b6ba966974..7d0537178ef 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -64,6 +64,8 @@ void CurveComponent::clear() delete curve_; } if (curve_for_render_ != nullptr) { + /* The curve created by this component should not have any edit mode data. */ + BLI_assert(curve_for_render_->editfont == nullptr && curve_for_render_->editnurb == nullptr); BKE_id_free(nullptr, curve_for_render_); curve_for_render_ = nullptr; } @@ -220,6 +222,37 @@ static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, mixer.finalize(); } +/** + * A spline is selected if all of its control points were selected. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ +template<> +void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + const int splines_len = curve.splines().size(); + Array<int> offsets = curve.control_point_offsets(); + BLI_assert(r_values.size() == splines_len); + + r_values.fill(true); + + for (const int i_spline : IndexRange(splines_len)) { + const int spline_offset = offsets[i_spline]; + const int spline_point_len = offsets[i_spline + 1] - spline_offset; + + for (const int i_point : IndexRange(spline_point_len)) { + if (!old_values[spline_offset + i_point]) { + r_values[i_spline] = false; + break; + } + } + } +} + static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -892,7 +925,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { public: ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr || curve->splines().size() == 0) { @@ -902,13 +935,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { Span<SplinePtr> splines = curve->splines(); Vector<GSpan> spans; /* GSpan has no default constructor. */ spans.reserve(splines.size()); - std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name); + std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_id); if (!first_span) { return {}; } spans.append(*first_span); for (const int i : IndexRange(1, splines.size() - 1)) { - std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name); + std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_id); if (!span) { /* All splines should have the same set of data layers. It would be possible to recover * here and return partial data instead, but that would add a lot of complexity for a @@ -945,7 +978,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* This function is almost the same as #try_get_for_read, but without const. */ WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr || curve->splines().size() == 0) { @@ -955,13 +988,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { MutableSpan<SplinePtr> splines = curve->splines(); Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */ spans.reserve(splines.size()); - std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name); + std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_id); if (!first_span) { return {}; } spans.append(*first_span); for (const int i : IndexRange(1, splines.size() - 1)) { - std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name); + std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_id); if (!span) { /* All splines should have the same set of data layers. It would be possible to recover * here and return partial data instead, but that would add a lot of complexity for a @@ -996,7 +1029,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { return attribute; } - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final { CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { @@ -1006,7 +1039,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* Reuse the boolean for all splines; we expect all splines to have the same attributes. */ bool layer_freed = false; for (SplinePtr &spline : curve->splines()) { - layer_freed = spline->attributes.remove(attribute_name); + layer_freed = spline->attributes.remove(attribute_id); } return layer_freed; } @@ -1034,7 +1067,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { } bool try_create(GeometryComponent &component, - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) const final @@ -1053,7 +1086,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* First check the one case that allows us to avoid copying the input data. */ if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) { void *source_data = static_cast<const AttributeInitMove &>(initializer).data; - if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) { + if (!splines[0]->attributes.create_by_move(attribute_id, data_type, source_data)) { MEM_freeN(source_data); return false; } @@ -1062,7 +1095,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* Otherwise just create a custom data layer on each of the splines. */ for (const int i : splines.index_range()) { - if (!splines[i]->attributes.create(attribute_name, data_type)) { + if (!splines[i]->attributes.create(attribute_id, data_type)) { /* If attribute creation fails on one of the splines, we cannot leave the custom data * layers in the previous splines around, so delete them before returning. However, * this is not an expected case. */ @@ -1076,7 +1109,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { return true; } - WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name); + WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_id); /* We just created the attribute, it should exist. */ BLI_assert(write_attribute); diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 3b1b7456162..c4e1fe2f8e9 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -25,6 +25,8 @@ #include "BKE_geometry_set.hh" +#include "attribute_access_intern.hh" + using blender::float4x4; using blender::Map; using blender::MutableSpan; @@ -122,7 +124,7 @@ blender::Span<int> InstancesComponent::instance_ids() const * If the reference exists already, the handle of the existing reference is returned. * Otherwise a new handle is added. */ -int InstancesComponent::add_reference(InstanceReference reference) +int InstancesComponent::add_reference(const InstanceReference &reference) { return references_.index_of_or_add_as(reference); } @@ -144,14 +146,23 @@ bool InstancesComponent::is_empty() const bool InstancesComponent::owns_direct_data() const { - /* The object and collection instances are not direct data. Instance transforms are direct data - * and are always owned. Therefore, instance components always own all their direct data. */ + for (const InstanceReference &reference : references_) { + if (!reference.owns_direct_data()) { + return false; + } + } return true; } void InstancesComponent::ensure_owns_direct_data() { BLI_assert(this->is_mutable()); + for (const InstanceReference &const_reference : references_) { + /* Const cast is fine because we are not changing anything that would change the hash of the + * reference. */ + InstanceReference &reference = const_cast<InstanceReference &>(const_reference); + reference.ensure_owns_direct_data(); + } } static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids) @@ -216,4 +227,85 @@ blender::Span<int> InstancesComponent::almost_unique_ids() const return almost_unique_ids_; } +int InstancesComponent::attribute_domain_size(const AttributeDomain domain) const +{ + if (domain != ATTR_DOMAIN_POINT) { + return 0; + } + return this->instances_amount(); +} + +namespace blender::bke { + +static float3 get_transform_position(const float4x4 &transform) +{ + return transform.translation(); +} + +static void set_transform_position(float4x4 &transform, const float3 position) +{ + copy_v3_v3(transform.values[3], position); +} + +class InstancePositionAttributeProvider final : public BuiltinAttributeProvider { + public: + InstancePositionAttributeProvider() + : BuiltinAttributeProvider( + "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, NonCreatable, Writable, NonDeletable) + { + } + + GVArrayPtr try_get_for_read(const GeometryComponent &component) const final + { + const InstancesComponent &instances_component = static_cast<const InstancesComponent &>( + component); + Span<float4x4> transforms = instances_component.instance_transforms(); + return std::make_unique<fn::GVArray_For_DerivedSpan<float4x4, float3, get_transform_position>>( + transforms); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final + { + InstancesComponent &instances_component = static_cast<InstancesComponent &>(component); + MutableSpan<float4x4> transforms = instances_component.instance_transforms(); + return std::make_unique<fn::GVMutableArray_For_DerivedSpan<float4x4, + float3, + get_transform_position, + set_transform_position>>( + transforms); + } + + 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 &UNUSED(component)) const final + { + return true; + } +}; + +static ComponentAttributeProviders create_attribute_providers_for_instances() +{ + static InstancePositionAttributeProvider position; + + return ComponentAttributeProviders({&position}, {}); +} +} // namespace blender::bke + +const blender::bke::ComponentAttributeProviders *InstancesComponent::get_attribute_providers() + const +{ + static blender::bke::ComponentAttributeProviders providers = + blender::bke::create_attribute_providers_for_instances(); + return &providers; +} + /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index ef93a3f9b3f..0c98aa5551b 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -175,6 +175,34 @@ static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, mixer.finalize(); } +/* A vertex is selected if all connected face corners were selected and it is not loose. */ +template<> +void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totvert); + Array<bool> loose_verts(mesh.totvert, true); + + r_values.fill(true); + for (const int loop_index : IndexRange(mesh.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int point_index = loop.v; + + loose_verts[point_index] = false; + if (!old_values[loop_index]) { + r_values[point_index] = false; + } + } + + /* Deselect loose vertices without corners that are still selected from the 'true' default. */ + for (const int vert_index : IndexRange(mesh.totvert)) { + if (loose_verts[vert_index]) { + r_values[vert_index] = false; + } + } +} + static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -191,6 +219,13 @@ static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr return new_varray; } +/** + * Each corner's value is simply a copy of the value at its vertex. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ template<typename T> static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, const VArray<T> &old_values, @@ -209,10 +244,6 @@ static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr GVArrayPtr new_varray; attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); - /* It is not strictly necessary to compute the value for all corners here. Instead one could - * lazily lookup the mesh topology when a specific index accessed. This can be more efficient - * when an algorithm only accesses very few of the corner values. However, for the algorithms - * we currently have, precomputing the array is fine. Also, it is easier to implement. */ Array<T> values(mesh.totloop); adapt_mesh_domain_point_to_corner_impl<T>(mesh, varray->typed<T>(), values); new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); @@ -244,6 +275,26 @@ static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, mixer.finalize(); } +/* A face is selected if all of its corners were selected. */ +template<> +void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totpoly); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + if (!old_values[loop_index]) { + r_values[poly_index] = false; + break; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -282,6 +333,41 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, mixer.finalize(); } +/* An edge is selected if all corners on adjacent faces were selected. */ +template<> +void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totedge); + + /* It may be possible to rely on the #ME_LOOSEEDGE flag, but that seems error-prone. */ + Array<bool> loose_edges(mesh.totedge, true); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const int loop_index_next = (loop_index == poly.totloop) ? poly.loopstart : (loop_index + 1); + const MLoop &loop = mesh.mloop[loop_index]; + const int edge_index = loop.e; + loose_edges[edge_index] = false; + + if (!old_values[loop_index] || !old_values[loop_index_next]) { + r_values[edge_index] = false; + } + } + } + + /* Deselect loose edges without corners that are still selected from the 'true' default. */ + for (const int edge_index : IndexRange(mesh.totedge)) { + if (loose_edges[edge_index]) { + r_values[edge_index] = false; + } + } +} + static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -317,6 +403,27 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, mixer.finalize(); } +/* A vertex is selected if any of the connected faces were selected. */ +template<> +void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totvert); + + r_values.fill(false); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + if (old_values[poly_index]) { + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int vert_index = loop.v; + r_values[vert_index] = true; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -331,6 +438,7 @@ static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr v return new_varray; } +/* Each corner's value is simply a copy of the value at its face. */ template<typename T> void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, const VArray<T> &old_values, @@ -378,6 +486,27 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, mixer.finalize(); } +/* An edge is selected if any connected face was selected. */ +template<> +void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totedge); + + r_values.fill(false); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + if (old_values[poly_index]) { + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int edge_index = loop.e; + r_values[edge_index] = true; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -416,6 +545,28 @@ static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, mixer.finalize(); } +/* A face is selected if all of its vertices were selected too. */ +template<> +void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totpoly); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + MLoop &loop = mesh.mloop[loop_index]; + const int vert_index = loop.v; + if (!old_values[vert_index]) { + r_values[poly_index] = false; + break; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -452,6 +603,20 @@ static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, mixer.finalize(); } +/* An edge is selected if both of its vertices were selected. */ +template<> +void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totedge); + + for (const int edge_index : IndexRange(mesh.totedge)) { + const MEdge &edge = mesh.medge[edge_index]; + r_values[edge_index] = old_values[edge.v1] && old_values[edge.v2]; + } +} + static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -490,6 +655,29 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, mixer.finalize(); } +/* A corner is selected if its two adjacent edges were selected. */ +template<> +void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totloop); + + r_values.fill(false); + + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop; + const MLoop &loop = mesh.mloop[loop_index]; + const MLoop &loop_prev = mesh.mloop[loop_index_prev]; + if (old_values[loop.e] && old_values[loop_prev.e]) { + r_values[loop_index] = true; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -522,6 +710,24 @@ static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, mixer.finalize(); } +/* A vertex is selected if any connected edge was selected. */ +template<> +void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totvert); + + r_values.fill(false); + for (const int edge_index : IndexRange(mesh.totedge)) { + const MEdge &edge = mesh.medge[edge_index]; + if (old_values[edge_index]) { + r_values[edge.v1] = true; + r_values[edge.v2] = true; + } + } +} + static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -560,6 +766,28 @@ static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, mixer.finalize(); } +/* A face is selected if all of its edges are selected. */ +template<> +void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totpoly); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int edge_index = loop.e; + if (!old_values[edge_index]) { + r_values[poly_index] = false; + break; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -698,7 +926,7 @@ static void tag_normals_dirty_when_writing_position(GeometryComponent &component { Mesh *mesh = get_mesh_from_component_for_write(component); if (mesh != nullptr) { - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } } @@ -818,16 +1046,20 @@ class VArray_For_VertexWeights final : public VArray<float> { class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { public: ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); + if (!attribute_id.is_named()) { + return {}; + } const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); const Mesh *mesh = mesh_component.get_for_read(); if (mesh == nullptr) { return {}; } + const std::string name = attribute_id.name(); const int vertex_group_index = BLI_findstringindex( - &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name)); + &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name)); if (vertex_group_index < 0) { return {}; } @@ -843,17 +1075,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { } WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); + if (!attribute_id.is_named()) { + return {}; + } MeshComponent &mesh_component = static_cast<MeshComponent &>(component); Mesh *mesh = mesh_component.get_for_write(); if (mesh == nullptr) { return {}; } + const std::string name = attribute_id.name(); const int vertex_group_index = BLI_findstringindex( - &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name)); + &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name)); if (vertex_group_index < 0) { return {}; } @@ -872,17 +1108,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { ATTR_DOMAIN_POINT}; } - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); + if (!attribute_id.is_named()) { + return false; + } MeshComponent &mesh_component = static_cast<MeshComponent &>(component); Mesh *mesh = mesh_component.get_for_write(); if (mesh == nullptr) { return true; } + const std::string name = attribute_id.name(); const int vertex_group_index = BLI_findstringindex( - &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name)); + &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name)); if (vertex_group_index < 0) { return false; } diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 07b4e715ea9..e717d289894 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -218,6 +218,16 @@ void GeometrySet::ensure_owns_direct_data() } } +bool GeometrySet::owns_direct_data() const +{ + for (const GeometryComponentPtr &component : components_.values()) { + if (!component->owns_direct_data()) { + return false; + } + } + return true; +} + /* Returns a read-only mesh or null. */ const Mesh *GeometrySet::get_mesh_for_read() const { @@ -376,9 +386,32 @@ void BKE_geometry_set_free(GeometrySet *geometry_set) delete geometry_set; } -bool BKE_geometry_set_has_instances(const GeometrySet *geometry_set) +bool BKE_object_has_geometry_set_instances(const Object *ob) { - return geometry_set->get_component_for_read<InstancesComponent>() != nullptr; + const GeometrySet *geometry_set = ob->runtime.geometry_set_eval; + if (geometry_set == nullptr) { + return false; + } + if (geometry_set->has_instances()) { + return true; + } + const bool has_mesh = geometry_set->has_mesh(); + const bool has_pointcloud = geometry_set->has_pointcloud(); + const bool has_volume = geometry_set->has_volume(); + const bool has_curve = geometry_set->has_curve(); + if (ob->type == OB_MESH) { + return has_pointcloud || has_volume || has_curve; + } + if (ob->type == OB_POINTCLOUD) { + return has_mesh || has_volume || has_curve; + } + if (ob->type == OB_VOLUME) { + return has_mesh || has_pointcloud || has_curve; + } + if (ELEM(ob->type, OB_CURVE, OB_FONT)) { + return has_mesh || has_pointcloud || has_volume; + } + return false; } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 32a65ab47bf..9dca2c2907e 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -51,16 +51,6 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS } } -static void add_curve_data_as_geometry_component(const Object &object, GeometrySet &geometry_set) -{ - BLI_assert(object.type == OB_CURVE); - if (object.data != nullptr) { - std::unique_ptr<CurveEval> curve = curve_eval_from_dna_curve(*(const Curve *)object.data); - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - curve_component.replace(curve.release(), GeometryOwnershipType::Owned); - } -} - /** * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. */ @@ -84,9 +74,6 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object) if (object.type == OB_MESH) { add_final_mesh_as_geometry_component(object, geometry_set); } - else if (object.type == OB_CURVE) { - add_curve_data_as_geometry_component(object, geometry_set); - } /* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the * #geometry_set_eval case above. */ @@ -168,6 +155,11 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set, collection, instance_transform, r_sets); break; } + case InstanceReference::Type::GeometrySet: { + const GeometrySet &geometry_set = reference.geometry_set(); + geometry_set_collect_recursive(geometry_set, instance_transform, r_sets); + break; + } case InstanceReference::Type::None: { break; } @@ -290,6 +282,13 @@ static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_se } 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; } @@ -319,7 +318,7 @@ void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, Span<GeometryComponentType> component_types, const Set<std::string> &ignored_attributes, - Map<std::string, AttributeKind> &r_attributes) + Map<AttributeIDRef, AttributeKind> &r_attributes) { for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; @@ -329,23 +328,24 @@ void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> se } const GeometryComponent &component = *set.get_component_for_read(component_type); - component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (ignored_attributes.contains(name)) { - return true; - } - 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 = meta_data.domain; /* TODO: Use highest priority domain. */ - attribute_kind->data_type = bke::attribute_data_type_highest_complexity( - {attribute_kind->data_type, meta_data.data_type}); - }; - - r_attributes.add_or_modify(name, add_info, modify_info); - return true; - }); + component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) { + return true; + } + 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 = meta_data.domain; /* TODO: Use highest priority 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); + return true; + }); } } } @@ -500,11 +500,11 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou static void join_attributes(Span<GeometryInstanceGroup> set_groups, Span<GeometryComponentType> component_types, - const Map<std::string, AttributeKind> &attribute_info, + const Map<AttributeIDRef, AttributeKind> &attribute_info, GeometryComponent &result) { - for (Map<std::string, AttributeKind>::Item entry : attribute_info.items()) { - StringRef name = entry.key; + for (Map<AttributeIDRef, AttributeKind>::Item entry : attribute_info.items()) { + const AttributeIDRef attribute_id = entry.key; const AttributeDomain domain_output = entry.value.domain; const CustomDataType data_type_output = entry.value.data_type; const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type_output); @@ -512,7 +512,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, result.attribute_try_create( entry.key, domain_output, data_type_output, AttributeInitDefault()); - WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(name); + WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(attribute_id); if (!write_attribute || &write_attribute.varray->type() != cpp_type || write_attribute.domain != domain_output) { continue; @@ -531,7 +531,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, continue; /* Domain size is 0, so no need to increment the offset. */ } GVArrayPtr source_attribute = component.attribute_try_get_for_read( - name, domain_output, data_type_output); + attribute_id, domain_output, data_type_output); if (source_attribute) { fn::GVArray_GSpan src_span{*source_attribute}; @@ -641,7 +641,7 @@ static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, } /* Don't copy attributes that are stored directly in the mesh data structs. */ - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; geometry_set_gather_instances_attribute_info( set_groups, component_types, @@ -662,7 +662,7 @@ static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_grou PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>(); dst_component.replace(new_pointcloud); - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, {"position"}, attributes); join_attributes(set_groups, @@ -696,7 +696,7 @@ static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, G CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); dst_component.replace(curve); - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_CURVE}, diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index a143645c2ee..82a44afbbb1 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -3063,13 +3063,12 @@ void BKE_gpencil_update_layer_transforms(const Depsgraph *depsgraph, Object *ob) Object *ob_parent = DEG_get_evaluated_object(depsgraph, gpl->parent); /* calculate new matrix */ if (ELEM(gpl->partype, PAROBJECT, PARSKEL)) { - copy_m4_m4(cur_mat, ob_parent->obmat); + mul_m4_m4m4(cur_mat, ob->imat, ob_parent->obmat); } else if (gpl->partype == PARBONE) { bPoseChannel *pchan = BKE_pose_channel_find_name(ob_parent->pose, gpl->parsubstr); if (pchan != NULL) { - copy_m4_m4(cur_mat, ob->imat); - mul_m4_m4m4(cur_mat, ob_parent->obmat, pchan->pose_mat); + mul_m4_series(cur_mat, ob->imat, ob_parent->obmat, pchan->pose_mat); } else { unit_m4(cur_mat); diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index a30376b9bad..6be03bffb3c 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -938,6 +938,11 @@ void BKE_gpencil_modifier_blend_write(BlendWriter *writer, ListBase *modbase) BKE_curvemapping_blend_write(writer, gpmd->curve_intensity); } } + else if (md->type == eGpencilModifierType_Dash) { + DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md; + BLO_write_struct_array( + writer, DashGpencilModifierSegment, gpmd->segments_len, gpmd->segments); + } } } @@ -1017,6 +1022,13 @@ void BKE_gpencil_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb) BKE_curvemapping_init(gpmd->curve_intensity); } } + else if (md->type == eGpencilModifierType_Dash) { + DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md; + BLO_read_data_address(reader, &gpmd->segments); + for (int i = 0; i < gpmd->segments_len; i++) { + gpmd->segments[i].dmd = gpmd; + } + } } } diff --git a/source/blender/blenkernel/intern/icons.cc b/source/blender/blenkernel/intern/icons.cc index 5a4b2448a73..97c742b1ec1 100644 --- a/source/blender/blenkernel/intern/icons.cc +++ b/source/blender/blenkernel/intern/icons.cc @@ -633,12 +633,6 @@ void BKE_previewimg_blend_write(BlendWriter *writer, const PreviewImage *prv) } PreviewImage prv_copy = *prv; - /* don't write out large previews if not requested */ - if (!(U.flag & USER_SAVE_PREVIEWS)) { - prv_copy.w[1] = 0; - prv_copy.h[1] = 0; - prv_copy.rect[1] = nullptr; - } BLO_write_struct_at_address(writer, PreviewImage, prv, &prv_copy); if (prv_copy.rect[0]) { BLO_write_uint32_array(writer, prv_copy.w[0] * prv_copy.h[0], prv_copy.rect[0]); diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index fee70922570..b2efccc53c4 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -224,10 +224,10 @@ bool BKE_idtype_idcode_is_valid(const short idcode) } /** - * Return non-zero when an ID type is linkable. + * Check if an ID type is linkable. * - * \param idcode: The code to check. - * \return Boolean, 0 when non linkable. + * \param idcode: The IDType code to check. + * \return Boolean, false when non linkable, true otherwise. */ bool BKE_idtype_idcode_is_linkable(const short idcode) { @@ -237,6 +237,24 @@ bool BKE_idtype_idcode_is_linkable(const short idcode) } /** + * Check if an ID type is only appendable. + * + * \param idcode: The IDType code to check. + * \return Boolean, false when also linkable, true when only appendable. + */ +bool BKE_idtype_idcode_is_only_appendable(const short idcode) +{ + const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode); + BLI_assert(id_type != NULL); + if (id_type != NULL && (id_type->flags & IDTYPE_FLAGS_ONLY_APPEND) != 0) { + /* Only appendable ID types should also always be linkable. */ + BLI_assert((id_type->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0); + return true; + } + return false; +} + +/** * Convert an \a idcode into an \a idfilter (e.g. ID_OB -> FILTER_ID_OB). */ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index d87290e1eb4..33f007c6dee 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -155,7 +155,9 @@ static void image_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c for (int eye = 0; eye < 2; eye++) { for (int i = 0; i < TEXTARGET_COUNT; i++) { - image_dst->gputexture[i][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + image_dst->gputexture[i][eye][resolution] = NULL; + } } } @@ -208,9 +210,11 @@ static void image_foreach_cache(ID *id, for (int eye = 0; eye < 2; eye++) { for (int a = 0; a < TEXTARGET_COUNT; a++) { - key.offset_in_ID = offsetof(Image, gputexture[a][eye]); - key.cache_v = image->gputexture[a][eye]; - function_callback(id, &key, (void **)&image->gputexture[a][eye], 0, user_data); + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + key.offset_in_ID = offsetof(Image, gputexture[a][eye][resolution]); + key.cache_v = image->gputexture[a][eye]; + function_callback(id, &key, (void **)&image->gputexture[a][eye][resolution], 0, user_data); + } } } @@ -239,7 +243,9 @@ static void image_blend_write(BlendWriter *writer, ID *id, const void *id_addres BLI_listbase_clear(&ima->gpu_refresh_areas); for (int i = 0; i < 3; i++) { for (int j = 0; j < 2; j++) { - ima->gputexture[i][j] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + ima->gputexture[i][j][resolution] = NULL; + } } } @@ -677,8 +683,10 @@ bool BKE_image_has_opengl_texture(Image *ima) { for (int eye = 0; eye < 2; eye++) { for (int i = 0; i < TEXTARGET_COUNT; i++) { - if (ima->gputexture[i][eye] != NULL) { - return true; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + return true; + } } } } @@ -3531,9 +3539,11 @@ static void image_free_tile(Image *ima, ImageTile *tile) } for (int eye = 0; eye < 2; eye++) { - if (ima->gputexture[i][eye] != NULL) { - GPU_texture_free(ima->gputexture[i][eye]); - ima->gputexture[i][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[i][eye][resolution]); + ima->gputexture[i][eye][resolution] = NULL; + } } } } @@ -3801,14 +3811,16 @@ ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *la } for (int eye = 0; eye < 2; eye++) { - /* Reallocate GPU tile array. */ - if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]); - ima->gputexture[TEXTARGET_2D_ARRAY][eye] = NULL; - } - if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]); - ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + /* Reallocate GPU tile array. */ + if (ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]); + ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] = NULL; + } + if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution]); + ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] = NULL; + } } } @@ -3863,14 +3875,17 @@ void BKE_image_reassign_tile(struct Image *ima, ImageTile *tile, int new_tile_nu } for (int eye = 0; eye < 2; eye++) { - /* Reallocate GPU tile array. */ - if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]); - ima->gputexture[TEXTARGET_2D_ARRAY][eye] = NULL; - } - if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]); - ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + + /* Reallocate GPU tile array. */ + if (ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]); + ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] = NULL; + } + if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution]); + ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] = NULL; + } } } } diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.c index d179dd40c33..9712e912bed 100644 --- a/source/blender/blenkernel/intern/image_gpu.c +++ b/source/blender/blenkernel/intern/image_gpu.c @@ -49,6 +49,7 @@ /* Prototypes. */ static void gpu_free_unused_buffers(void); static void image_free_gpu(Image *ima, const bool immediate); +static void image_free_gpu_limited_scale(Image *ima); static void image_update_gputexture_ex( Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h); @@ -97,9 +98,11 @@ static int smaller_power_of_2_limit(int num, bool limit_gl_texture_size) return power_of_2_min_i(GPU_texture_size_with_limit(num, limit_gl_texture_size)); } -static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multiview_eye) +static GPUTexture *gpu_texture_create_tile_mapping( + Image *ima, const int multiview_eye, const eImageTextureResolution texture_resolution) { - GPUTexture *tilearray = ima->gputexture[TEXTARGET_2D_ARRAY][multiview_eye]; + const int resolution = (texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED) ? 1 : 0; + GPUTexture *tilearray = ima->gputexture[TEXTARGET_2D_ARRAY][multiview_eye][resolution]; if (tilearray == NULL) { return 0; @@ -121,13 +124,14 @@ static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multivi } LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { int i = tile->tile_number - 1001; - data[4 * i] = tile->runtime.tilearray_layer; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + data[4 * i] = tile_runtime->tilearray_layer; float *tile_info = &data[4 * width + 4 * i]; - tile_info[0] = tile->runtime.tilearray_offset[0] / array_w; - tile_info[1] = tile->runtime.tilearray_offset[1] / array_h; - tile_info[2] = tile->runtime.tilearray_size[0] / array_w; - tile_info[3] = tile->runtime.tilearray_size[1] / array_h; + tile_info[0] = tile_runtime->tilearray_offset[0] / array_w; + tile_info[1] = tile_runtime->tilearray_offset[1] / array_h; + tile_info[2] = tile_runtime->tilearray_size[0] / array_w; + tile_info[3] = tile_runtime->tilearray_size[1] / array_h; } GPUTexture *tex = GPU_texture_create_1d_array(ima->id.name + 2, width, 2, 1, GPU_RGBA32F, data); @@ -152,9 +156,12 @@ static int compare_packtile(const void *a, const void *b) return tile_a->pack_score < tile_b->pack_score; } -static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) +static GPUTexture *gpu_texture_create_tile_array(Image *ima, + ImBuf *main_ibuf, + const eImageTextureResolution texture_resolution) { - const bool limit_gl_texture_size = (ima->gpuflag & IMA_GPU_MAX_RESOLUTION) == 0; + const bool limit_gl_texture_size = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED; + const int resolution = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED ? 1 : 0; int arraywidth = 0, arrayheight = 0; ListBase boxes = {NULL}; @@ -200,14 +207,15 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) LISTBASE_FOREACH (PackTile *, packtile, &packed) { ImageTile *tile = packtile->tile; - int *tileoffset = tile->runtime.tilearray_offset; - int *tilesize = tile->runtime.tilearray_size; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tileoffset = tile_runtime->tilearray_offset; + int *tilesize = tile_runtime->tilearray_size; tileoffset[0] = packtile->boxpack.x; tileoffset[1] = packtile->boxpack.y; tilesize[0] = packtile->boxpack.w; tilesize[1] = packtile->boxpack.h; - tile->runtime.tilearray_layer = arraylayers; + tile_runtime->tilearray_layer = arraylayers; } BLI_freelistN(&packed); @@ -221,9 +229,10 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) /* Upload each tile one by one. */ LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { - int tilelayer = tile->runtime.tilearray_layer; - int *tileoffset = tile->runtime.tilearray_offset; - int *tilesize = tile->runtime.tilearray_size; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int tilelayer = tile_runtime->tilearray_layer; + int *tileoffset = tile_runtime->tilearray_offset; + int *tilesize = tile_runtime->tilearray_size; if (tilesize[0] == 0 || tilesize[1] == 0) { continue; @@ -268,16 +277,33 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) /** \name Regular gpu texture * \{ */ +static bool image_max_resolution_texture_fits_in_limited_scale(Image *ima, + eGPUTextureTarget textarget, + const int multiview_eye) +{ + BLI_assert_msg(U.glreslimit != 0, + "limited scale function called without limited scale being set."); + GPUTexture *max_resolution_texture = + ima->gputexture[textarget][multiview_eye][IMA_TEXTURE_RESOLUTION_FULL]; + if (max_resolution_texture && GPU_texture_width(max_resolution_texture) <= U.glreslimit && + GPU_texture_height(max_resolution_texture) <= U.glreslimit) { + return true; + } + return false; +} + static GPUTexture **get_image_gpu_texture_ptr(Image *ima, eGPUTextureTarget textarget, - const int multiview_eye) + const int multiview_eye, + const eImageTextureResolution texture_resolution) { const bool in_range = (textarget >= 0) && (textarget < TEXTARGET_COUNT); BLI_assert(in_range); BLI_assert(multiview_eye == 0 || multiview_eye == 1); + const int resolution = (texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED) ? 1 : 0; if (in_range) { - return &(ima->gputexture[textarget][multiview_eye]); + return &(ima->gputexture[textarget][multiview_eye][resolution]); } return NULL; } @@ -296,6 +322,21 @@ static GPUTexture *image_gpu_texture_error_create(eGPUTextureTarget textarget) } } +static void image_update_reusable_textures(Image *ima, + eGPUTextureTarget textarget, + const int multiview_eye) +{ + if ((ima->gpuflag & IMA_GPU_HAS_LIMITED_SCALE_TEXTURES) == 0) { + return; + } + + if (ELEM(textarget, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) { + if (image_max_resolution_texture_fits_in_limited_scale(ima, textarget, multiview_eye)) { + image_free_gpu_limited_scale(ima); + } + } +} + static GPUTexture *image_get_gpu_texture(Image *ima, ImageUser *iuser, ImBuf *ibuf, @@ -315,24 +356,17 @@ static GPUTexture *image_get_gpu_texture(Image *ima, short requested_pass = iuser ? iuser->pass : 0; short requested_layer = iuser ? iuser->layer : 0; short requested_view = iuser ? iuser->multi_index : 0; - const bool limit_resolution = U.glreslimit != 0 && - ((iuser && (iuser->flag & IMA_SHOW_MAX_RESOLUTION) == 0) || - (iuser == NULL)); - short requested_gpu_flags = limit_resolution ? 0 : IMA_GPU_MAX_RESOLUTION; -#define GPU_FLAGS_TO_CHECK (IMA_GPU_MAX_RESOLUTION) /* There is room for 2 multiview textures. When a higher number is requested we should always * target the first view slot. This is fine as multi view images aren't used together. */ if (requested_view < 2) { requested_view = 0; } if (ima->gpu_pass != requested_pass || ima->gpu_layer != requested_layer || - ima->gpu_view != requested_view || - ((ima->gpuflag & GPU_FLAGS_TO_CHECK) != requested_gpu_flags)) { + ima->gpu_view != requested_view) { ima->gpu_pass = requested_pass; ima->gpu_layer = requested_layer; ima->gpu_view = requested_view; - ima->gpuflag &= ~GPU_FLAGS_TO_CHECK; - ima->gpuflag |= requested_gpu_flags | IMA_GPU_REFRESH; + ima->gpuflag |= IMA_GPU_REFRESH; } #undef GPU_FLAGS_TO_CHECK @@ -369,7 +403,14 @@ static GPUTexture *image_get_gpu_texture(Image *ima, if (current_view >= 2) { current_view = 0; } - GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view); + const bool limit_resolution = U.glreslimit != 0 && + ((iuser && (iuser->flag & IMA_SHOW_MAX_RESOLUTION) == 0) || + (iuser == NULL)) && + ((ima->gpuflag & IMA_GPU_REUSE_MAX_RESOLUTION) == 0); + const eImageTextureResolution texture_resolution = limit_resolution ? + IMA_TEXTURE_RESOLUTION_LIMITED : + IMA_TEXTURE_RESOLUTION_FULL; + GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view, texture_resolution); if (*tex) { return *tex; } @@ -392,22 +433,19 @@ static GPUTexture *image_get_gpu_texture(Image *ima, } if (textarget == TEXTARGET_2D_ARRAY) { - *tex = gpu_texture_create_tile_array(ima, ibuf_intern); + *tex = gpu_texture_create_tile_array(ima, ibuf_intern, texture_resolution); } else if (textarget == TEXTARGET_TILE_MAPPING) { - *tex = gpu_texture_create_tile_mapping(ima, iuser ? iuser->multiview_eye : 0); + *tex = gpu_texture_create_tile_mapping( + ima, iuser ? iuser->multiview_eye : 0, texture_resolution); } else { const bool use_high_bitdepth = (ima->flag & IMA_HIGH_BITDEPTH); const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima, ibuf_intern); - const bool limit_gl_texture_size = (ima->gpuflag & IMA_GPU_MAX_RESOLUTION) == 0; - *tex = IMB_create_gpu_texture(ima->id.name + 2, - ibuf_intern, - use_high_bitdepth, - store_premultiplied, - limit_gl_texture_size); + *tex = IMB_create_gpu_texture( + ima->id.name + 2, ibuf_intern, use_high_bitdepth, store_premultiplied, limit_resolution); if (*tex) { GPU_texture_wrap_mode(*tex, true, false); @@ -425,6 +463,20 @@ static GPUTexture *image_get_gpu_texture(Image *ima, } } + switch (texture_resolution) { + case IMA_TEXTURE_RESOLUTION_LIMITED: + ima->gpuflag |= IMA_GPU_HAS_LIMITED_SCALE_TEXTURES; + break; + + case IMA_TEXTURE_RESOLUTION_FULL: + image_update_reusable_textures(ima, textarget, current_view); + break; + + case IMA_TEXTURE_RESOLUTION_LEN: + BLI_assert_unreachable(); + break; + } + /* if `ibuf` was given, we don't own the `ibuf_intern` */ if (ibuf == NULL) { BKE_image_release_ibuf(ima, ibuf_intern, NULL); @@ -497,22 +549,39 @@ static void image_free_gpu(Image *ima, const bool immediate) { for (int eye = 0; eye < 2; eye++) { for (int i = 0; i < TEXTARGET_COUNT; i++) { - if (ima->gputexture[i][eye] != NULL) { - if (immediate) { - GPU_texture_free(ima->gputexture[i][eye]); - } - else { - BLI_mutex_lock(&gpu_texture_queue_mutex); - BLI_linklist_prepend(&gpu_texture_free_queue, ima->gputexture[i][eye]); - BLI_mutex_unlock(&gpu_texture_queue_mutex); + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + if (immediate) { + GPU_texture_free(ima->gputexture[i][eye][resolution]); + } + else { + BLI_mutex_lock(&gpu_texture_queue_mutex); + BLI_linklist_prepend(&gpu_texture_free_queue, ima->gputexture[i][eye][resolution]); + BLI_mutex_unlock(&gpu_texture_queue_mutex); + } + + ima->gputexture[i][eye][resolution] = NULL; } + } + } + } + + ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_HAS_LIMITED_SCALE_TEXTURES); +} - ima->gputexture[i][eye] = NULL; +static void image_free_gpu_limited_scale(Image *ima) +{ + const eImageTextureResolution resolution = IMA_TEXTURE_RESOLUTION_LIMITED; + for (int eye = 0; eye < 2; eye++) { + for (int i = 0; i < TEXTARGET_COUNT; i++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[i][eye][resolution]); + ima->gputexture[i][eye][resolution] = NULL; } } } - ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE; + ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_HAS_LIMITED_SCALE_TEXTURES); } void BKE_image_free_gputextures(Image *ima) @@ -689,12 +758,21 @@ static void gpu_texture_update_unscaled(GPUTexture *tex, GPU_unpack_row_length_set(0); } -static void gpu_texture_update_from_ibuf( - GPUTexture *tex, Image *ima, ImBuf *ibuf, ImageTile *tile, int x, int y, int w, int h) +static void gpu_texture_update_from_ibuf(GPUTexture *tex, + Image *ima, + ImBuf *ibuf, + ImageTile *tile, + int x, + int y, + int w, + int h, + eImageTextureResolution texture_resolution) { + const int resolution = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED ? 1 : 0; bool scaled; if (tile != NULL) { - int *tilesize = tile->runtime.tilearray_size; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tilesize = tile_runtime->tilearray_size; scaled = (ibuf->x != tilesize[0]) || (ibuf->y != tilesize[1]); } else { @@ -758,9 +836,10 @@ static void gpu_texture_update_from_ibuf( if (scaled) { /* Slower update where we first have to scale the input pixels. */ if (tile != NULL) { - int *tileoffset = tile->runtime.tilearray_offset; - int *tilesize = tile->runtime.tilearray_size; - int tilelayer = tile->runtime.tilearray_layer; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tileoffset = tile_runtime->tilearray_offset; + int *tilesize = tile_runtime->tilearray_size; + int tilelayer = tile_runtime->tilearray_layer; gpu_texture_update_scaled( tex, rect, rect_float, ibuf->x, ibuf->y, x, y, tilelayer, tileoffset, tilesize, w, h); } @@ -772,8 +851,9 @@ static void gpu_texture_update_from_ibuf( else { /* Fast update at same resolution. */ if (tile != NULL) { - int *tileoffset = tile->runtime.tilearray_offset; - int tilelayer = tile->runtime.tilearray_layer; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tileoffset = tile_runtime->tilearray_offset; + int tilelayer = tile_runtime->tilearray_layer; gpu_texture_update_unscaled( tex, rect, rect_float, x, y, tilelayer, tileoffset, w, h, tex_stride, tex_offset); } @@ -804,16 +884,20 @@ static void gpu_texture_update_from_ibuf( static void image_update_gputexture_ex( Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h) { - GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0]; - /* Check if we need to update the main gputexture. */ - if (tex != NULL && tile == ima->tiles.first) { - gpu_texture_update_from_ibuf(tex, ima, ibuf, NULL, x, y, w, h); - } + const int eye = 0; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + GPUTexture *tex = ima->gputexture[TEXTARGET_2D][eye][resolution]; + eImageTextureResolution texture_resolution = resolution; + /* Check if we need to update the main gputexture. */ + if (tex != NULL && tile == ima->tiles.first) { + gpu_texture_update_from_ibuf(tex, ima, ibuf, NULL, x, y, w, h, texture_resolution); + } - /* Check if we need to update the array gputexture. */ - tex = ima->gputexture[TEXTARGET_2D_ARRAY][0]; - if (tex != NULL) { - gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h); + /* Check if we need to update the array gputexture. */ + tex = ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]; + if (tex != NULL) { + gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h, texture_resolution); + } } } @@ -917,12 +1001,14 @@ void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap) LISTBASE_FOREACH (Image *, ima, &bmain->images) { if (BKE_image_has_opengl_texture(ima)) { if (ima->gpuflag & IMA_GPU_MIPMAP_COMPLETE) { - for (int eye = 0; eye < 2; eye++) { - for (int a = 0; a < TEXTARGET_COUNT; a++) { - if (ELEM(a, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) { - GPUTexture *tex = ima->gputexture[a][eye]; - if (tex != NULL) { - GPU_texture_mipmap_mode(tex, mipmap, true); + for (int a = 0; a < TEXTARGET_COUNT; a++) { + if (ELEM(a, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) { + for (int eye = 0; eye < 2; eye++) { + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + GPUTexture *tex = ima->gputexture[a][eye][resolution]; + if (tex != NULL) { + GPU_texture_mipmap_mode(tex, mipmap, true); + } } } } diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c index aac081991e3..9b72a2d1a72 100644 --- a/source/blender/blenkernel/intern/ipo.c +++ b/source/blender/blenkernel/intern/ipo.c @@ -184,8 +184,7 @@ IDTypeInfo IDType_ID_IP = { .name = "Ipo", .name_plural = "ipos", .translation_context = "", - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL | - IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index f79058dcf21..44fc86877a7 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -212,7 +212,7 @@ IDTypeInfo IDType_ID_KE = { .name = "Key", .name_plural = "shape_keys", .translation_context = BLT_I18NCONTEXT_ID_SHAPEKEY, - .flags = IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL, + .flags = IDTYPE_FLAGS_NO_LIBLINKING, .init_data = NULL, .copy_data = shapekey_copy_data, diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 11e9053df43..60b6d7ad66d 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -98,7 +98,7 @@ IDTypeInfo IDType_ID_LINK_PLACEHOLDER = { .name = "LinkPlaceholder", .name_plural = "link_placeholders", .translation_context = BLT_I18NCONTEXT_ID_ID, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING, .init_data = NULL, .copy_data = NULL, @@ -336,12 +336,34 @@ void id_fake_user_clear(ID *id) } } -void BKE_id_clear_newpoin(ID *id) +void BKE_id_newptr_and_tag_clear(ID *id) { - if (id->newid) { - id->newid->tag &= ~LIB_TAG_NEW; + /* We assume that if this ID has no new ID, its embedded data has not either. */ + if (id->newid == NULL) { + return; } + + id->newid->tag &= ~LIB_TAG_NEW; id->newid = NULL; + + /* Deal with embedded data too. */ + /* NOTE: even though ShapeKeys are not technically embedded data currently, they behave as such + * in most cases, so for sake of consistency treat them as such here. Also mirrors the behavior + * in `BKE_lib_id_make_local`. */ + Key *key = BKE_key_from_id(id); + if (key != NULL) { + BKE_id_newptr_and_tag_clear(&key->id); + } + bNodeTree *ntree = ntreeFromID(id); + if (ntree != NULL) { + BKE_id_newptr_and_tag_clear(&ntree->id); + } + if (GS(id->name) == ID_SCE) { + Collection *master_collection = ((Scene *)id)->master_collection; + if (master_collection != NULL) { + BKE_id_newptr_and_tag_clear(&master_collection->id); + } + } } static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data) @@ -406,7 +428,15 @@ static void lib_id_copy_ensure_local(Main *bmain, const ID *old_id, ID *new_id) */ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) { + if (!ID_IS_LINKED(id)) { + return; + } + const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; + bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; + bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; + BLI_assert(force_copy == false || force_copy != force_local); + bool is_local = false, is_lib = false; /* - only lib users: do nothing (unless force_local is set) @@ -416,47 +446,51 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) * we always want to localize, and we skip remapping (done later). */ - if (!ID_IS_LINKED(id)) { - return; + if (!force_copy && !force_local) { + BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); + if (lib_local || is_local) { + if (!is_lib) { + force_local = true; + } + else { + force_copy = true; + } + } } - BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); + if (force_local) { + BKE_lib_id_clear_library_data(bmain, id); + BKE_lib_id_expand_local(bmain, id); + } + else if (force_copy) { + ID *id_new = BKE_id_copy(bmain, id); - if (lib_local || is_local) { - if (!is_lib) { - BKE_lib_id_clear_library_data(bmain, id); - BKE_lib_id_expand_local(bmain, id); - } - else { - ID *id_new = BKE_id_copy(bmain, id); - - /* Should not fail in expected use cases, - * but a few ID types cannot be copied (LIB, WM, SCR...). */ - if (id_new != NULL) { - id_new->us = 0; - - /* setting newid is mandatory for complex make_lib_local logic... */ - ID_NEW_SET(id, id_new); - Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id); - if (key && key_new) { - ID_NEW_SET(key, key_new); - } - bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new); - if (ntree && ntree_new) { - ID_NEW_SET(ntree, ntree_new); - } - if (GS(id->name) == ID_SCE) { - Collection *master_collection = ((Scene *)id)->master_collection, - *master_collection_new = ((Scene *)id_new)->master_collection; - if (master_collection && master_collection_new) { - ID_NEW_SET(master_collection, master_collection_new); - } - } + /* Should not fail in expected use cases, + * but a few ID types cannot be copied (LIB, WM, SCR...). */ + if (id_new != NULL) { + id_new->us = 0; - if (!lib_local) { - BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE); + /* setting newid is mandatory for complex make_lib_local logic... */ + ID_NEW_SET(id, id_new); + Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id); + if (key && key_new) { + ID_NEW_SET(key, key_new); + } + bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new); + if (ntree && ntree_new) { + ID_NEW_SET(ntree, ntree_new); + } + if (GS(id->name) == ID_SCE) { + Collection *master_collection = ((Scene *)id)->master_collection, + *master_collection_new = ((Scene *)id_new)->master_collection; + if (master_collection && master_collection_new) { + ID_NEW_SET(master_collection, master_collection_new); } } + + if (!lib_local) { + BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE); + } } } } @@ -468,10 +502,9 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) * * \param flags: Special flag used when making a whole library's content local, * it needs specific handling. - * - * \return true if the block can be made local. + * \return true is the ID has successfully been made local. */ -bool BKE_lib_id_make_local(Main *bmain, ID *id, const bool test, const int flags) +bool BKE_lib_id_make_local(Main *bmain, ID *id, const int flags) { const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; @@ -483,23 +516,21 @@ bool BKE_lib_id_make_local(Main *bmain, ID *id, const bool test, const int flags const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id); - if (idtype_info != NULL) { - if ((idtype_info->flags & IDTYPE_FLAGS_NO_MAKELOCAL) == 0) { - if (!test) { - if (idtype_info->make_local != NULL) { - idtype_info->make_local(bmain, id, flags); - } - else { - BKE_lib_id_make_local_generic(bmain, id, flags); - } - } - return true; - } + if (idtype_info == NULL) { + BLI_assert_msg(0, "IDType Missing IDTypeInfo"); return false; } - BLI_assert_msg(0, "IDType Missing IDTypeInfo"); - return false; + BLI_assert((idtype_info->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0); + + if (idtype_info->make_local != NULL) { + idtype_info->make_local(bmain, id, flags); + } + else { + BKE_lib_id_make_local_generic(bmain, id, flags); + } + + return true; } struct IDCopyLibManagementData { @@ -1694,7 +1725,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ * * Only for local IDs (linked ones already have a unique ID in their library). * - * \param do_linked_data if true, also ensure a unique name in case the given \a id is linked + * \param do_linked_data: if true, also ensure a unique name in case the given \a id is linked * (otherwise, just ensure that it is properly sorted). * * \return true if a new name had to be created. @@ -1754,8 +1785,7 @@ void BKE_main_id_newptr_and_tag_clear(Main *bmain) ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { - id->newid = NULL; - id->tag &= ~LIB_TAG_NEW; + BKE_id_newptr_and_tag_clear(id); } FOREACH_MAIN_ID_END; } @@ -2022,11 +2052,8 @@ void BKE_library_make_local(Main *bmain, * Note that for objects, we don't want proxy pointers to be cleared yet. This will happen * down the road in this function. */ - BKE_lib_id_make_local(bmain, - id, - false, - LIB_ID_MAKELOCAL_FULL_LIBRARY | - LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); + BKE_lib_id_make_local( + bmain, id, LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); if (id->newid) { if (GS(id->newid->name) == ID_OB) { diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 8c1e04838df..3fead8b0f39 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -333,11 +333,11 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, * main. You can add more local IDs to be remapped to use new overriding ones by setting their * LIB_TAG_DOIT tag. * - * \param reference_library the library from which the linked data being overridden come from + * \param reference_library: the library from which the linked data being overridden come from * (i.e. the library of the linked reference ID). * - * \param do_no_main Create the new override data outside of Main database. Used for resyncing of - * linked overrides. + * \param do_no_main: Create the new override data outside of Main database. + * Used for resyncing of linked overrides. * * \return \a true on success, \a false otherwise. */ @@ -901,7 +901,7 @@ static void lib_override_library_create_post_process(Main *bmain, * \param id_reference: Some reference ID used to do some post-processing after overrides have been * created, may be NULL. Typically, the Empty object instantiating the linked collection we * override, currently. - * \param r_id_root_override if not NULL, the override generated for the given \a id_root. + * \param r_id_root_override: if not NULL, the override generated for the given \a id_root. * \return true if override was successfully created. */ bool BKE_lib_override_library_create(Main *bmain, diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index 36cbf35b251..2ac92828cec 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -720,9 +720,9 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain, * Valid usages here are defined as ref-counting usages, which are not towards embedded or * loop-back data. * - * \param r_num_tagged If non-NULL, must be a zero-initialized array of #INDEX_ID_MAX integers. - * Number of tagged-as-unused IDs is then set for each type, and as total in - * #INDEX_ID_NULL item. + * \param r_num_tagged: If non-NULL, must be a zero-initialized array of #INDEX_ID_MAX integers. + * Number of tagged-as-unused IDs is then set for each type, and as total in + * #INDEX_ID_NULL item. */ void BKE_lib_query_unused_ids_tag(Main *bmain, const int tag, diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index bba15a3bcdf..250b8d4d515 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -699,6 +699,9 @@ static int id_relink_to_newid_looper(LibraryIDLinkCallbackData *cb_data) * * Very specific usage, not sure we'll keep it on the long run, * currently only used in Object/Collection duplication code... + * + * WARNING: This is a deprecated version of this function, should not be used by new code. See + * #BKE_libblock_relink_to_newid_new below. */ void BKE_libblock_relink_to_newid(ID *id) { @@ -708,3 +711,53 @@ void BKE_libblock_relink_to_newid(ID *id) BKE_library_foreach_ID_link(NULL, id, id_relink_to_newid_looper, NULL, 0); } + +/* ************************ + * FIXME: Port all usages of #BKE_libblock_relink_to_newid to this + * #BKE_libblock_relink_to_newid_new new code and remove old one. + ************************** */ +static int id_relink_to_newid_looper_new(LibraryIDLinkCallbackData *cb_data) +{ + const int cb_flag = cb_data->cb_flag; + if (cb_flag & IDWALK_CB_EMBEDDED) { + return IDWALK_RET_NOP; + } + + Main *bmain = cb_data->bmain; + ID *id_owner = cb_data->id_owner; + ID **id_pointer = cb_data->id_pointer; + ID *id = *id_pointer; + if (id) { + /* See: NEW_ID macro */ + if (id->newid != NULL) { + BKE_libblock_relink_ex(bmain, id_owner, id, id->newid, ID_REMAP_SKIP_INDIRECT_USAGE); + id = id->newid; + } + if (id->tag & LIB_TAG_NEW) { + id->tag &= ~LIB_TAG_NEW; + BKE_libblock_relink_to_newid_new(bmain, id); + } + } + return IDWALK_RET_NOP; +} + +/** + * Remaps ID usages of given ID to their `id->newid` pointer if not None, and proceeds recursively + * in the dependency tree of IDs for all data-blocks tagged with `LIB_TAG_NEW`. + * + * NOTE: `LIB_TAG_NEW` is cleared + * + * Very specific usage, not sure we'll keep it on the long run, + * currently only used in Object/Collection duplication code... + */ +void BKE_libblock_relink_to_newid_new(Main *bmain, ID *id) +{ + if (ID_IS_LINKED(id)) { + return; + } + /* We do not want to have those cached relationship data here. */ + BLI_assert(bmain->relations == NULL); + + id->tag &= ~LIB_TAG_NEW; + BKE_library_foreach_ID_link(bmain, id, id_relink_to_newid_looper_new, NULL, 0); +} diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index 07a3396ad5f..36958e36004 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -68,8 +68,7 @@ IDTypeInfo IDType_ID_LI = { .name = "Library", .name_plural = "libraries", .translation_context = BLT_I18NCONTEXT_ID_LIBRARY, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL | - IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index bb33f5f9f87..981f1d4a623 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -405,11 +405,8 @@ ImBuf *BKE_main_thumbnail_to_imbuf(Main *bmain, BlendThumbnail *data) } if (data) { - /* NOTE: we cannot use IMB_allocFromBuffer(), since it tries to dupalloc passed buffer, - * which will fail here (we do not want to pass the first two ints!). */ - img = IMB_allocImBuf( - (unsigned int)data->width, (unsigned int)data->height, 32, IB_rect | IB_metadata); - memcpy(img->rect, data->rect, BLEN_THUMB_MEMSIZE(data->width, data->height) - sizeof(*data)); + img = IMB_allocFromBuffer( + (const uint *)data->rect, NULL, (uint)data->width, (uint)data->height, 4); } return img; diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.c index 2efe0d77d87..d631993c4e8 100644 --- a/source/blender/blenkernel/intern/mesh.c +++ b/source/blender/blenkernel/intern/mesh.c @@ -1111,7 +1111,7 @@ void BKE_mesh_eval_delete(struct Mesh *mesh_eval) MEM_freeN(mesh_eval); } -Mesh *BKE_mesh_copy_for_eval(struct Mesh *source, bool reference) +Mesh *BKE_mesh_copy_for_eval(const Mesh *source, bool reference) { int flags = LIB_ID_COPY_LOCALIZE; @@ -1859,7 +1859,7 @@ void BKE_mesh_vert_coords_apply(Mesh *mesh, const float (*vert_coords)[3]) for (int i = 0; i < mesh->totvert; i++, mv++) { copy_v3_v3(mv->co, vert_coords[i]); } - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, @@ -1872,7 +1872,7 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, for (int i = 0; i < mesh->totvert; i++, mv++) { mul_v3_m4v3(mv->co, mat, vert_coords[i]); } - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } void BKE_mesh_vert_normals_apply(Mesh *mesh, const short (*vert_normals)[3]) diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.cc index d5524312612..07dc6db05aa 100644 --- a/source/blender/blenkernel/intern/mesh_convert.c +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -68,7 +68,7 @@ #ifdef VALIDATE_MESH # define ASSERT_IS_VALID_MESH(mesh) \ - (BLI_assert((mesh == NULL) || (BKE_mesh_is_valid(mesh) == true))) + (BLI_assert((mesh == nullptr) || (BKE_mesh_is_valid(mesh) == true))) #else # define ASSERT_IS_VALID_MESH(mesh) #endif @@ -84,15 +84,16 @@ void BKE_mesh_from_metaball(ListBase *lb, Mesh *me) const float *nors, *verts; int a, *index; - dl = lb->first; - if (dl == NULL) { + dl = (DispList *)lb->first; + if (dl == nullptr) { return; } if (dl->type == DL_INDEX4) { - mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, dl->nr); - allloop = mloop = CustomData_add_layer(&me->ldata, CD_MLOOP, CD_CALLOC, NULL, dl->parts * 4); - mpoly = CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CALLOC, NULL, dl->parts); + mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, dl->nr); + allloop = mloop = (MLoop *)CustomData_add_layer( + &me->ldata, CD_MLOOP, CD_CALLOC, nullptr, dl->parts * 4); + mpoly = (MPoly *)CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CALLOC, nullptr, dl->parts); me->mvert = mvert; me->mloop = mloop; me->mpoly = mpoly; @@ -177,9 +178,10 @@ static void make_edges_mdata_extend( MEdge *medge; uint e_index = totedge; - *r_alledge = medge = (*r_alledge ? - MEM_reallocN(*r_alledge, sizeof(MEdge) * (totedge + totedge_new)) : - MEM_calloc_arrayN(totedge_new, sizeof(MEdge), __func__)); + *r_alledge = medge = (MEdge *)(*r_alledge ? + MEM_reallocN(*r_alledge, + sizeof(MEdge) * (totedge + totedge_new)) : + MEM_calloc_arrayN(totedge_new, sizeof(MEdge), __func__)); medge += totedge; totedge += totedge_new; @@ -209,7 +211,7 @@ static void make_edges_mdata_extend( } } - BLI_edgehash_free(eh, NULL); + BLI_edgehash_free(eh, nullptr); } /* Initialize mverts, medges and, faces for converting nurbs to mesh and derived mesh */ @@ -229,7 +231,7 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu, MVert *mvert; MPoly *mpoly; MLoop *mloop; - MLoopUV *mloopuv = NULL; + MLoopUV *mloopuv = nullptr; MEdge *medge; const float *data; int a, b, ofs, vertcount, startvert, totvert = 0, totedge = 0, totloop = 0, totpoly = 0; @@ -277,14 +279,15 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu, return -1; } - *r_allvert = mvert = MEM_calloc_arrayN(totvert, sizeof(MVert), "nurbs_init mvert"); - *r_alledge = medge = MEM_calloc_arrayN(totedge, sizeof(MEdge), "nurbs_init medge"); - *r_allloop = mloop = MEM_calloc_arrayN( + *r_allvert = mvert = (MVert *)MEM_calloc_arrayN(totvert, sizeof(MVert), "nurbs_init mvert"); + *r_alledge = medge = (MEdge *)MEM_calloc_arrayN(totedge, sizeof(MEdge), "nurbs_init medge"); + *r_allloop = mloop = (MLoop *)MEM_calloc_arrayN( totpoly, sizeof(MLoop[4]), "nurbs_init mloop"); /* totloop */ - *r_allpoly = mpoly = MEM_calloc_arrayN(totpoly, sizeof(MPoly), "nurbs_init mloop"); + *r_allpoly = mpoly = (MPoly *)MEM_calloc_arrayN(totpoly, sizeof(MPoly), "nurbs_init mloop"); if (r_alluv) { - *r_alluv = mloopuv = MEM_calloc_arrayN(totpoly, sizeof(MLoopUV[4]), "nurbs_init mloopuv"); + *r_alluv = mloopuv = (MLoopUV *)MEM_calloc_arrayN( + totpoly, sizeof(MLoopUV[4]), "nurbs_init mloopuv"); } /* verts and faces */ @@ -483,17 +486,33 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu, return 0; } +/** + * Copy evaluated texture space from curve to mesh. + * + * \note We disable auto texture space feature since that will cause texture space to evaluate + * differently for curve and mesh, since curves use control points and handles to calculate the + * bounding box, and mesh uses the tessellated curve. + */ +static void mesh_copy_texture_space_from_curve_type(const Curve *cu, Mesh *me) +{ + me->texflag = cu->texflag & ~CU_AUTOSPACE; + copy_v3_v3(me->loc, cu->loc); + copy_v3_v3(me->size, cu->size); + BKE_mesh_texspace_calc(me); +} + Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase *dispbase) { + const Curve *cu = (const Curve *)ob->data; Mesh *mesh; MVert *allvert; MEdge *alledge; MLoop *allloop; MPoly *allpoly; - MLoopUV *alluv = NULL; + MLoopUV *alluv = nullptr; int totvert, totedge, totloop, totpoly; - if (mesh_nurbs_displist_to_mdata(ob->data, + if (mesh_nurbs_displist_to_mdata(cu, dispbase, &allvert, &totvert, @@ -509,7 +528,7 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase * } mesh = BKE_mesh_new_nomain(totvert, totedge, 0, totloop, totpoly); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); if (totvert != 0) { memcpy(mesh->mvert, allvert, totvert * sizeof(MVert)); @@ -529,6 +548,12 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase * CustomData_add_layer_named(&mesh->ldata, CD_MLOOPUV, CD_ASSIGN, alluv, totloop, uvname); } + mesh_copy_texture_space_from_curve_type(cu, mesh); + + /* Copy curve materials. */ + mesh->mat = (Material **)MEM_dupallocN(cu->mat); + mesh->totcol = cu->totcol; + MEM_freeN(allvert); MEM_freeN(alledge); MEM_freeN(allloop); @@ -539,7 +564,7 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase * Mesh *BKE_mesh_new_nomain_from_curve(const Object *ob) { - ListBase disp = {NULL, NULL}; + ListBase disp = {nullptr, nullptr}; if (ob->runtime.curve_cache) { disp = ob->runtime.curve_cache->disp; @@ -556,16 +581,16 @@ static void mesh_from_nurbs_displist(Object *ob, ListBase *dispbase, const char Mesh *me_eval = (Mesh *)ob->runtime.data_eval; Mesh *me; - MVert *allvert = NULL; - MEdge *alledge = NULL; - MLoop *allloop = NULL; - MLoopUV *alluv = NULL; - MPoly *allpoly = NULL; + MVert *allvert = nullptr; + MEdge *alledge = nullptr; + MLoop *allloop = nullptr; + MLoopUV *alluv = nullptr; + MPoly *allpoly = nullptr; int totvert, totedge, totloop, totpoly; - Curve *cu = ob->data; + Curve *cu = (Curve *)ob->data; - if (me_eval == NULL) { + if (me_eval == nullptr) { if (mesh_nurbs_displist_to_mdata(cu, dispbase, &allvert, @@ -582,49 +607,43 @@ static void mesh_from_nurbs_displist(Object *ob, ListBase *dispbase, const char } /* make mesh */ - me = BKE_id_new_nomain(ID_ME, obdata_name); + me = (Mesh *)BKE_id_new_nomain(ID_ME, obdata_name); me->totvert = totvert; me->totedge = totedge; me->totloop = totloop; me->totpoly = totpoly; - me->mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_ASSIGN, allvert, me->totvert); - me->medge = CustomData_add_layer(&me->edata, CD_MEDGE, CD_ASSIGN, alledge, me->totedge); - me->mloop = CustomData_add_layer(&me->ldata, CD_MLOOP, CD_ASSIGN, allloop, me->totloop); - me->mpoly = CustomData_add_layer(&me->pdata, CD_MPOLY, CD_ASSIGN, allpoly, me->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 = CustomData_add_layer_named( + me->mloopuv = (MLoopUV *)CustomData_add_layer_named( &me->ldata, CD_MLOOPUV, CD_ASSIGN, alluv, me->totloop, uvname); } BKE_mesh_calc_normals(me); } else { - me = BKE_id_new_nomain(ID_ME, obdata_name); + me = (Mesh *)BKE_id_new_nomain(ID_ME, obdata_name); - ob->runtime.data_eval = NULL; + 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; - /* Copy evaluated texture space from curve to mesh. - * - * Note that we disable auto texture space feature since that will cause - * texture space to evaluate differently for curve and mesh, since curve - * uses CV to calculate bounding box, and mesh uses what is coming from - * tessellated curve. - */ - me->texflag = cu->texflag & ~CU_AUTOSPACE; - copy_v3_v3(me->loc, cu->loc); - copy_v3_v3(me->size, cu->size); - BKE_mesh_texspace_calc(me); + mesh_copy_texture_space_from_curve_type(cu, me); - cu->mat = NULL; + cu->mat = nullptr; cu->totcol = 0; /* Do not decrement ob->data usercount here, @@ -635,29 +654,29 @@ static void mesh_from_nurbs_displist(Object *ob, ListBase *dispbase, const char /* 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(NULL, cu); + BKE_id_free(nullptr, cu); } -typedef struct EdgeLink { +struct EdgeLink { struct EdgeLink *next, *prev; void *edge; -} EdgeLink; +}; -typedef struct VertLink { +struct VertLink { Link *next, *prev; uint index; -} VertLink; +}; static void prependPolyLineVert(ListBase *lb, uint index) { - VertLink *vl = MEM_callocN(sizeof(VertLink), "VertLink"); + VertLink *vl = (VertLink *)MEM_callocN(sizeof(VertLink), "VertLink"); vl->index = index; BLI_addhead(lb, vl); } static void appendPolyLineVert(ListBase *lb, uint index) { - VertLink *vl = MEM_callocN(sizeof(VertLink), "VertLink"); + VertLink *vl = (VertLink *)MEM_callocN(sizeof(VertLink), "VertLink"); vl->index = index; BLI_addtail(lb, vl); } @@ -677,10 +696,10 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed /* only to detect edge polylines */ int *edge_users; - ListBase edges = {NULL, NULL}; + ListBase edges = {nullptr, nullptr}; /* get boundary edges */ - edge_users = MEM_calloc_arrayN(medge_len, sizeof(int), __func__); + edge_users = (int *)MEM_calloc_arrayN(medge_len, sizeof(int), __func__); for (i = 0, mp = mpoly; i < mpoly_len; i++, mp++) { MLoop *ml = &mloop[mp->loopstart]; int j; @@ -693,7 +712,7 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed med = medge; for (i = 0; i < medge_len; i++, med++) { if (edge_users[i] == edge_users_test) { - EdgeLink *edl = MEM_callocN(sizeof(EdgeLink), "EdgeLink"); + EdgeLink *edl = (EdgeLink *)MEM_callocN(sizeof(EdgeLink), "EdgeLink"); edl->edge = med; BLI_addtail(&edges, edl); @@ -706,10 +725,10 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed while (edges.first) { /* each iteration find a polyline and add this as a nurbs poly spline */ - ListBase polyline = {NULL, NULL}; /* store a list of VertLink's */ + ListBase polyline = {nullptr, nullptr}; /* store a list of VertLink's */ bool closed = false; int totpoly = 0; - MEdge *med_current = ((EdgeLink *)edges.last)->edge; + MEdge *med_current = (MEdge *)((EdgeLink *)edges.last)->edge; uint startVert = med_current->v1; uint endVert = med_current->v2; bool ok = true; @@ -722,12 +741,12 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed totedges--; while (ok) { /* while connected edges are found... */ - EdgeLink *edl = edges.last; + EdgeLink *edl = (EdgeLink *)edges.last; ok = false; while (edl) { EdgeLink *edl_prev = edl->prev; - med = edl->edge; + med = (MEdge *)edl->edge; if (med->v1 == endVert) { endVert = med->v2; @@ -791,7 +810,7 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed nu->bp = (BPoint *)MEM_calloc_arrayN(totpoly, sizeof(BPoint), "bpoints"); /* add points */ - vl = polyline.first; + vl = (VertLink *)polyline.first; for (i = 0, bp = nu->bp; i < totpoly; i++, bp++, vl = (VertLink *)vl->next) { copy_v3_v3(bp->vec, mvert[vl->index].co); bp->f1 = SELECT; @@ -813,7 +832,7 @@ void BKE_mesh_to_curve(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(scene), Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); Mesh *me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &CD_MASK_MESH); - ListBase nurblist = {NULL, NULL}; + ListBase nurblist = {nullptr, nullptr}; BKE_mesh_to_curve_nurblist(me_eval, &nurblist, 0); BKE_mesh_to_curve_nurblist(me_eval, &nurblist, 1); @@ -834,16 +853,13 @@ void BKE_mesh_to_curve(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(scene), void BKE_pointcloud_from_mesh(Mesh *me, PointCloud *pointcloud) { - BLI_assert(me != NULL); + BLI_assert(me != nullptr); pointcloud->totpoint = me->totvert; CustomData_realloc(&pointcloud->pdata, pointcloud->totpoint); /* Copy over all attributes. */ - const CustomData_MeshMasks mask = { - .vmask = CD_MASK_PROP_ALL, - }; - CustomData_merge(&me->vdata, &pointcloud->pdata, mask.vmask, CD_DUPLICATE, me->totvert); + CustomData_merge(&me->vdata, &pointcloud->pdata, CD_MASK_PROP_ALL, CD_DUPLICATE, me->totvert); BKE_pointcloud_update_customdata_pointers(pointcloud); CustomData_update_typemap(&pointcloud->pdata); @@ -862,7 +878,7 @@ void BKE_mesh_to_pointcloud(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(sce Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); Mesh *me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &CD_MASK_MESH); - PointCloud *pointcloud = BKE_pointcloud_add(bmain, ob->id.name + 2); + PointCloud *pointcloud = (PointCloud *)BKE_pointcloud_add(bmain, ob->id.name + 2); BKE_pointcloud_from_mesh(me_eval, pointcloud); @@ -877,24 +893,22 @@ void BKE_mesh_to_pointcloud(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(sce void BKE_mesh_from_pointcloud(const PointCloud *pointcloud, Mesh *me) { - BLI_assert(pointcloud != NULL); + BLI_assert(pointcloud != nullptr); me->totvert = pointcloud->totpoint; /* Merge over all attributes. */ - const CustomData_MeshMasks mask = { - .vmask = CD_MASK_PROP_ALL, - }; - CustomData_merge(&pointcloud->pdata, &me->vdata, mask.vmask, CD_DUPLICATE, pointcloud->totpoint); + CustomData_merge( + &pointcloud->pdata, &me->vdata, CD_MASK_PROP_ALL, CD_DUPLICATE, pointcloud->totpoint); /* Convert the Position attribute to a mesh vertex. */ - me->mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, me->totvert); + me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, me->totvert); CustomData_update_typemap(&me->vdata); const int layer_idx = CustomData_get_named_layer_index( &me->vdata, CD_PROP_FLOAT3, POINTCLOUD_ATTR_POSITION); CustomDataLayer *pos_layer = &me->vdata.layers[layer_idx]; - float(*positions)[3] = pos_layer->data; + float(*positions)[3] = (float(*)[3])pos_layer->data; MVert *mvert; mvert = me->mvert; @@ -944,7 +958,8 @@ static Object *object_for_curve_to_mesh_create(Object *object) Curve *curve = (Curve *)object->data; /* Create object itself. */ - Object *temp_object = (Object *)BKE_id_copy_ex(NULL, &object->id, NULL, LIB_ID_COPY_LOCALIZE); + Object *temp_object = (Object *)BKE_id_copy_ex( + nullptr, &object->id, nullptr, LIB_ID_COPY_LOCALIZE); /* Remove all modifiers, since we don't want them to be applied. */ BKE_object_free_modifiers(temp_object, LIB_ID_CREATE_NO_USER_REFCOUNT); @@ -953,26 +968,27 @@ static Object *object_for_curve_to_mesh_create(Object *object) * * 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 == NULL) { - temp_object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), - "CurveCache for curve types"); + 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 != NULL) { + 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 != NULL) { + if (object->runtime.data_eval != nullptr) { BKE_id_copy_ex( - NULL, object->runtime.data_eval, &temp_object->runtime.data_eval, LIB_ID_COPY_LOCALIZE); + 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(NULL, object->data, (ID **)&temp_object->data, LIB_ID_COPY_LOCALIZE); + BKE_id_copy_ex( + nullptr, (const ID *)object->data, (ID **)&temp_object->data, LIB_ID_COPY_LOCALIZE); 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 @@ -999,8 +1015,9 @@ static void curve_to_mesh_eval_ensure(Object *object) remapped_object.data = &remapped_curve; - if (object->runtime.curve_cache == NULL) { - object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for 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`. */ @@ -1013,8 +1030,8 @@ 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 = {{NULL}}; - if (remapped_curve.bevobj != NULL) { + Object bevel_object = {{nullptr}}; + if (remapped_curve.bevobj != nullptr) { bevel_object = *remapped_curve.bevobj; BLI_listbase_clear(&bevel_object.modifiers); BKE_object_runtime_reset(&bevel_object); @@ -1022,34 +1039,34 @@ static void curve_to_mesh_eval_ensure(Object *object) } /* Same thing for taper. */ - Object taper_object = {{NULL}}; - if (remapped_curve.taperobj != NULL) { + Object taper_object = {{nullptr}}; + if (remapped_curve.taperobj != nullptr) { taper_object = *remapped_curve.taperobj; BLI_listbase_clear(&taper_object.modifiers); BKE_object_runtime_reset(&taper_object); remapped_curve.taperobj = &taper_object; } - /* NOTE: We don't have dependency graph or scene here, so we pass NULL. This is all fine since + /* 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. * Brecht says hold off with that. */ - Mesh *mesh_eval = NULL; + Mesh *mesh_eval = nullptr; BKE_displist_make_curveTypes_forRender( - NULL, NULL, &remapped_object, &remapped_object.runtime.curve_cache->disp, &mesh_eval); + nullptr, nullptr, &remapped_object, &remapped_object.runtime.curve_cache->disp, &mesh_eval); /* 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 != NULL) { + 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 = NULL; + remapped_object.runtime.curve_cache = nullptr; BKE_object_runtime_free_data(&remapped_object); BKE_object_runtime_free_data(&taper_object); @@ -1058,7 +1075,7 @@ static void curve_to_mesh_eval_ensure(Object *object) static Mesh *mesh_new_from_curve_type_object(Object *object) { - Curve *curve = object->data; + Curve *curve = (Curve *)object->data; Object *temp_object = object_for_curve_to_mesh_create(object); Curve *temp_curve = (Curve *)temp_object->data; @@ -1069,8 +1086,8 @@ static Mesh *mesh_new_from_curve_type_object(Object *object) } /* Reset pointers before conversion. */ - temp_curve->editfont = NULL; - temp_curve->editnurb = NULL; + temp_curve->editfont = nullptr; + temp_curve->editnurb = nullptr; /* Convert to mesh. */ mesh_from_nurbs_displist( @@ -1079,14 +1096,14 @@ static Mesh *mesh_new_from_curve_type_object(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(NULL, temp_object->data); - BKE_id_free(NULL, temp_object); - return NULL; + BKE_id_free(nullptr, temp_object->data); + BKE_id_free(nullptr, temp_object); + return nullptr; } - Mesh *mesh_result = temp_object->data; + Mesh *mesh_result = (Mesh *)temp_object->data; - BKE_id_free(NULL, temp_object); + BKE_id_free(nullptr, temp_object); /* NOTE: Materials are copied in #mesh_from_nurbs_displist(). */ @@ -1102,19 +1119,19 @@ static Mesh *mesh_new_from_mball_object(Object *object) * ball). * * We create empty mesh so scripters don't run into None objects. */ - if (!DEG_is_evaluated_object(object) || object->runtime.curve_cache == NULL || + if (!DEG_is_evaluated_object(object) || object->runtime.curve_cache == nullptr || BLI_listbase_is_empty(&object->runtime.curve_cache->disp)) { - return BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2); + return (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2); } - Mesh *mesh_result = BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2); + Mesh *mesh_result = (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2); BKE_mesh_from_metaball(&object->runtime.curve_cache->disp, mesh_result); BKE_mesh_texspace_copy_from_object(mesh_result, object); /* Copy materials. */ mesh_result->totcol = mball->totcol; - mesh_result->mat = MEM_dupallocN(mball->mat); - if (mball->mat != NULL) { + mesh_result->mat = (Material **)MEM_dupallocN(mball->mat); + if (mball->mat != nullptr) { for (int i = mball->totcol; i-- > 0;) { mesh_result->mat[i] = BKE_object_material_get(object, i + 1); } @@ -1130,7 +1147,7 @@ static Mesh *mesh_new_from_mesh(Object *object, Mesh *mesh) BKE_mesh_wrapper_ensure_mdata(mesh); Mesh *mesh_result = (Mesh *)BKE_id_copy_ex( - NULL, &mesh->id, NULL, LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT); + nullptr, &mesh->id, nullptr, LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT); /* NOTE: Materials should already be copied. */ /* Copy original mesh name. This is because edit meshes might not have one properly set name. */ BLI_strncpy(mesh_result->id.name, ((ID *)object->data)->name, sizeof(mesh_result->id.name)); @@ -1145,12 +1162,12 @@ static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, return mesh_new_from_mesh(object, (Mesh *)object->data); } - if (depsgraph == NULL) { - return NULL; + if (depsgraph == nullptr) { + return nullptr; } Object object_for_eval = *object; - if (object_for_eval.runtime.data_orig != NULL) { + if (object_for_eval.runtime.data_orig != nullptr) { object_for_eval.data = object_for_eval.runtime.data_orig; } @@ -1174,10 +1191,10 @@ static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, if (preserve_all_data_layers || preserve_origindex) { return mesh_new_from_mesh_object_with_layers(depsgraph, object, preserve_origindex); } - Mesh *mesh_input = object->data; + Mesh *mesh_input = (Mesh *)object->data; /* If we are in edit mode, use evaluated mesh from edit structure, matching to what * viewport is using for visualization. */ - if (mesh_input->edit_mesh != NULL && mesh_input->edit_mesh->mesh_eval_final) { + if (mesh_input->edit_mesh != nullptr && mesh_input->edit_mesh->mesh_eval_final) { mesh_input = mesh_input->edit_mesh->mesh_eval_final; } return mesh_new_from_mesh(object, mesh_input); @@ -1188,7 +1205,7 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, const bool preserve_all_data_layers, const bool preserve_origindex) { - Mesh *new_mesh = NULL; + Mesh *new_mesh = nullptr; switch (object->type) { case OB_FONT: case OB_CURVE: @@ -1204,11 +1221,11 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, break; default: /* Object does not have geometry data. */ - return NULL; + return nullptr; } - if (new_mesh == NULL) { + if (new_mesh == nullptr) { /* Happens in special cases like request of mesh for non-mother meta ball. */ - return NULL; + return nullptr; } /* The result must have 0 users, since it's just a mesh which is free-dangling data-block. @@ -1221,9 +1238,9 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, * ownership. * * Here we are constructing a mesh which is supposed to be independent, which means no shared - * ownership is allowed, so we make sure edit mesh is reset to NULL (which is similar to as if + * ownership is allowed, so we make sure edit mesh is reset to nullptr (which is similar to as if * one duplicates the objects and applies all the modifiers). */ - new_mesh->edit_mesh = NULL; + new_mesh->edit_mesh = nullptr; return new_mesh; } @@ -1231,7 +1248,7 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, static int foreach_libblock_make_original_callback(LibraryIDLinkCallbackData *cb_data) { ID **id_p = cb_data->id_pointer; - if (*id_p == NULL) { + if (*id_p == nullptr) { return IDWALK_RET_NOP; } *id_p = DEG_get_original_id(*id_p); @@ -1242,7 +1259,7 @@ static int foreach_libblock_make_original_callback(LibraryIDLinkCallbackData *cb static int foreach_libblock_make_usercounts_callback(LibraryIDLinkCallbackData *cb_data) { ID **id_p = cb_data->id_pointer; - if (*id_p == NULL) { + if (*id_p == nullptr) { return IDWALK_RET_NOP; } @@ -1266,7 +1283,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, BLI_assert(ELEM(object->type, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_MESH)); Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); - if (mesh == NULL) { + if (mesh == nullptr) { /* Unable to convert the object to a mesh, return an empty one. */ Mesh *mesh_in_bmain = BKE_mesh_add(bmain, ((ID *)object->data)->name + 2); id_us_min(&mesh_in_bmain->id); @@ -1282,7 +1299,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, * Note that user-count updates has to be done *after* mesh has been transferred to Main database * (since doing refcounting on non-Main IDs is forbidden). */ BKE_library_foreach_ID_link( - NULL, &mesh->id, foreach_libblock_make_original_callback, NULL, IDWALK_NOP); + nullptr, &mesh->id, foreach_libblock_make_original_callback, nullptr, IDWALK_NOP); /* Append the mesh to 'bmain'. * We do it a bit longer way since there is no simple and clear way of adding existing data-block @@ -1299,14 +1316,14 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, mesh_in_bmain->totcol = mesh->totcol; mesh_in_bmain->flag = mesh->flag; mesh_in_bmain->smoothresh = mesh->smoothresh; - mesh->mat = NULL; + mesh->mat = nullptr; - BKE_mesh_nomain_to_mesh(mesh, mesh_in_bmain, NULL, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(mesh, mesh_in_bmain, nullptr, &CD_MASK_MESH, true); /* User-count is required because so far mesh was in a limbo, where library management does * not perform any user management (i.e. copy of a mesh will not increase users of materials). */ BKE_library_foreach_ID_link( - NULL, &mesh_in_bmain->id, foreach_libblock_make_usercounts_callback, NULL, IDWALK_NOP); + nullptr, &mesh_in_bmain->id, foreach_libblock_make_usercounts_callback, nullptr, IDWALK_NOP); /* Make sure user count from BKE_mesh_add() is the one we expect here and bring it down to 0. */ BLI_assert(mesh_in_bmain->id.us == 1); @@ -1335,7 +1352,7 @@ static void add_shapekey_layers(Mesh *mesh_dest, Mesh *mesh_src) return; } - for (i = 0, kb = key->block.first; kb; kb = kb->next, i++) { + for (i = 0, kb = (KeyBlock *)key->block.first; kb; kb = kb->next, i++) { int ci; float *array; @@ -1346,10 +1363,10 @@ static void add_shapekey_layers(Mesh *mesh_dest, Mesh *mesh_src) mesh_src->totvert, kb->name, kb->totelem); - array = MEM_calloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__); + array = (float *)MEM_calloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__); } else { - array = MEM_malloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__); + array = (float *)MEM_malloc_arrayN((size_t)mesh_src->totvert, sizeof(float[3]), __func__); memcpy(array, kb->data, sizeof(float[3]) * (size_t)mesh_src->totvert); } @@ -1367,9 +1384,10 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, ModifierData *md_eval, const bool build_shapekey_layers) { - Mesh *me = ob_eval->runtime.data_orig ? ob_eval->runtime.data_orig : ob_eval->data; - const ModifierTypeInfo *mti = BKE_modifier_get_info(md_eval->type); - Mesh *result = NULL; + Mesh *me = ob_eval->runtime.data_orig ? (Mesh *)ob_eval->runtime.data_orig : + (Mesh *)ob_eval->data; + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_eval->type); + Mesh *result = nullptr; KeyBlock *kb; ModifierEvalContext mectx = {depsgraph, ob_eval, MOD_APPLY_TO_BASE_MESH}; @@ -1377,12 +1395,12 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, return result; } - if (mti->isDisabled && mti->isDisabled(scene, md_eval, 0)) { + if (mti->isDisabled && mti->isDisabled(scene, md_eval, false)) { return result; } if (build_shapekey_layers && me->key && - (kb = BLI_findlink(&me->key->block, ob_eval->shapenr - 1))) { + (kb = (KeyBlock *)BLI_findlink(&me->key->block, ob_eval->shapenr - 1))) { BKE_keyblock_convert_to_mesh(kb, me); } @@ -1390,7 +1408,7 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, int numVerts; float(*deformedVerts)[3] = BKE_mesh_vert_coords_alloc(me, &numVerts); - result = (Mesh *)BKE_id_copy_ex(NULL, &me->id, NULL, LIB_ID_COPY_LOCALIZE); + result = (Mesh *)BKE_id_copy_ex(nullptr, &me->id, nullptr, LIB_ID_COPY_LOCALIZE); mti->deformVerts(md_eval, &mectx, result, deformedVerts, numVerts); BKE_mesh_vert_coords_apply(result, deformedVerts); @@ -1401,7 +1419,7 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, MEM_freeN(deformedVerts); } else { - Mesh *mesh_temp = (Mesh *)BKE_id_copy_ex(NULL, &me->id, NULL, LIB_ID_COPY_LOCALIZE); + Mesh *mesh_temp = (Mesh *)BKE_id_copy_ex(nullptr, &me->id, nullptr, LIB_ID_COPY_LOCALIZE); if (build_shapekey_layers) { add_shapekey_layers(mesh_temp, me); @@ -1411,7 +1429,7 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, ASSERT_IS_VALID_MESH(result); if (mesh_temp != result) { - BKE_id_free(NULL, mesh_temp); + BKE_id_free(nullptr, mesh_temp); } } @@ -1434,7 +1452,7 @@ static void shapekey_layers_to_keyblocks(Mesh *mesh_src, Mesh *mesh_dst, int act &mesh_src->vdata.layers[CustomData_get_layer_index_n(&mesh_src->vdata, CD_SHAPEKEY, i)]; float(*cos)[3], (*kbcos)[3]; - for (kb = mesh_dst->key->block.first; kb; kb = kb->next) { + for (kb = (KeyBlock *)mesh_dst->key->block.first; kb; kb = kb->next) { if (kb->uid == layer->uid) { break; } @@ -1449,10 +1467,10 @@ static void shapekey_layers_to_keyblocks(Mesh *mesh_src, Mesh *mesh_dst, int act MEM_freeN(kb->data); } - cos = CustomData_get_layer_n(&mesh_src->vdata, CD_SHAPEKEY, i); + cos = (float(*)[3])CustomData_get_layer_n(&mesh_src->vdata, CD_SHAPEKEY, i); kb->totelem = mesh_src->totvert; - kb->data = kbcos = MEM_malloc_arrayN(kb->totelem, sizeof(float[3]), __func__); + kb->data = kbcos = (float(*)[3])MEM_malloc_arrayN(kb->totelem, sizeof(float[3]), __func__); if (kb->uid == actshape_uid) { MVert *mvert = mesh_src->mvert; @@ -1467,7 +1485,7 @@ static void shapekey_layers_to_keyblocks(Mesh *mesh_src, Mesh *mesh_dst, int act } } - for (kb = mesh_dst->key->block.first; kb; kb = kb->next) { + for (kb = (KeyBlock *)mesh_dst->key->block.first; kb; kb = kb->next) { if (kb->totelem != mesh_src->totvert) { if (kb->data) { MEM_freeN(kb->data); @@ -1532,7 +1550,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, int uid; if (ob) { - kb = BLI_findlink(&mesh_dst->key->block, ob->shapenr - 1); + kb = (KeyBlock *)BLI_findlink(&mesh_dst->key->block, ob->shapenr - 1); if (kb) { uid = kb->uid; } @@ -1595,11 +1613,11 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, /* NOTE(nazgul): maybe some other layers should be copied? */ if (CustomData_has_layer(&mesh_dst->ldata, CD_MDISPS)) { if (totloop == mesh_dst->totloop) { - MDisps *mdisps = CustomData_get_layer(&mesh_dst->ldata, CD_MDISPS); + MDisps *mdisps = (MDisps *)CustomData_get_layer(&mesh_dst->ldata, CD_MDISPS); CustomData_add_layer(&tmp.ldata, CD_MDISPS, alloctype, mdisps, totloop); if (alloctype == CD_ASSIGN) { - /* Assign NULL to prevent double-free. */ - CustomData_set_layer(&mesh_dst->ldata, CD_MDISPS, NULL); + /* Assign nullptr to prevent double-free. */ + CustomData_set_layer(&mesh_dst->ldata, CD_MDISPS, nullptr); } } } @@ -1621,7 +1639,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, if (tmp.key && !(tmp.id.tag & LIB_TAG_NO_MAIN)) { id_us_min(&tmp.key->id); } - tmp.key = NULL; + tmp.key = nullptr; } /* Clear selection history */ @@ -1648,7 +1666,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, CustomData_free_typemask(&mesh_src->ldata, mesh_src->totloop, ~mask->lmask); CustomData_free_typemask(&mesh_src->pdata, mesh_src->totpoly, ~mask->pmask); } - BKE_id_free(NULL, mesh_src); + BKE_id_free(nullptr, mesh_src); } } @@ -1670,7 +1688,7 @@ void BKE_mesh_nomain_to_meshkey(Mesh *mesh_src, Mesh *mesh_dst, KeyBlock *kb) kb->data = MEM_malloc_arrayN(mesh_dst->key->elemsize, mesh_dst->totvert, "kb->data"); kb->totelem = totvert; - fp = kb->data; + fp = (float *)kb->data; mvert = mesh_src->mvert; for (a = 0; a < kb->totelem; a++, fp += 3, mvert++) { diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index b328a31cda5..b55b02c7bf2 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -1473,7 +1473,6 @@ void BKE_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb, Object fmd->domain->tex_velocity_y = NULL; fmd->domain->tex_velocity_z = NULL; fmd->domain->tex_wt = NULL; - fmd->domain->mesh_velocities = NULL; BLO_read_data_address(reader, &fmd->domain->coba); BLO_read_data_address(reader, &fmd->domain->effector_weights); diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 48d747e2bf3..3a76cbf6f84 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -511,7 +511,8 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); } - else if ((ntree->type == NTREE_GEOMETRY) && (node->type == GEO_NODE_ATTRIBUTE_CURVE_MAP)) { + else if ((ntree->type == NTREE_GEOMETRY) && + (node->type == GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP)) { BLO_write_struct_by_name(writer, node->typeinfo->storagename, node->storage); NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_vec); @@ -652,6 +653,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) BLO_read_list(reader, &ntree->nodes); LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { node->typeinfo = nullptr; + node->declaration = nullptr; BLO_read_list(reader, &node->inputs); BLO_read_list(reader, &node->outputs); @@ -689,7 +691,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) BKE_curvemapping_blend_read(reader, (CurveMapping *)node->storage); break; } - case GEO_NODE_ATTRIBUTE_CURVE_MAP: { + case GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP: { NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; BLO_read_data_address(reader, &data->curve_vec); if (data->curve_vec) { @@ -1013,10 +1015,8 @@ IDTypeInfo IDType_ID_NT = { static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType *ntype) { if (ntype->declare != nullptr) { - blender::nodes::NodeDeclaration node_decl; - blender::nodes::NodeDeclarationBuilder builder{node_decl}; - ntype->declare(builder); - node_decl.build(*ntree, *node); + nodeDeclarationEnsure(ntree, node); + node->declaration->build(*ntree, *node); return; } bNodeSocketTemplate *sockdef; @@ -2215,6 +2215,10 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree, bNodeLink *link_dst, *link_src; *node_dst = *node_src; + + /* Reset the declaration of the new node. */ + node_dst->declaration = nullptr; + /* can be called for nodes outside a node tree (e.g. clipboard) */ if (ntree) { if (unique_name) { @@ -3102,6 +3106,8 @@ static void node_free_node(bNodeTree *ntree, bNode *node) MEM_freeN(node->prop); } + delete node->declaration; + MEM_freeN(node); if (ntree) { @@ -3888,7 +3894,7 @@ void nodeSetActive(bNodeTree *ntree, bNode *node) } } if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) || - (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) { + (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) { tnode->flag &= ~NODE_ACTIVE_TEXTURE; } } @@ -3898,7 +3904,7 @@ void nodeSetActive(bNodeTree *ntree, bNode *node) node->flag |= NODE_ACTIVE_ID; } if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) || - (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) { + (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) { node->flag |= NODE_ACTIVE_TEXTURE; } } @@ -3932,6 +3938,24 @@ int nodeSocketLinkLimit(const bNodeSocket *sock) return sock->limit; } +/** + * If the node implements a `declare` function, this function makes sure that `node->declaration` + * is up to date. + */ +void nodeDeclarationEnsure(bNodeTree *UNUSED(ntree), bNode *node) +{ + if (node->typeinfo->declare == nullptr) { + return; + } + if (node->declaration != nullptr) { + return; + } + + node->declaration = new blender::nodes::NodeDeclaration(); + blender::nodes::NodeDeclarationBuilder builder{*node->declaration}; + node->typeinfo->declare(builder); +} + /* ************** Node Clipboard *********** */ #define USE_NODE_CB_VALIDATE @@ -4919,6 +4943,7 @@ static void registerCompositNodes() register_node_type_cmp_inpaint(); register_node_type_cmp_despeckle(); register_node_type_cmp_defocus(); + register_node_type_cmp_posterize(); register_node_type_cmp_sunbeams(); register_node_type_cmp_denoise(); register_node_type_cmp_antialiasing(); @@ -5131,6 +5156,9 @@ static void registerGeometryNodes() { register_node_type_geo_group(); + register_node_type_geo_legacy_material_assign(); + register_node_type_geo_legacy_select_by_material(); + register_node_type_geo_align_rotation_to_vector(); register_node_type_geo_attribute_clamp(); register_node_type_geo_attribute_color_ramp(); @@ -5139,6 +5167,7 @@ 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(); @@ -5173,7 +5202,10 @@ static void registerGeometryNodes() register_node_type_geo_curve_trim(); register_node_type_geo_delete_geometry(); 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_is_viewport(); register_node_type_geo_join_geometry(); register_node_type_geo_material_assign(); @@ -5197,10 +5229,12 @@ static void registerGeometryNodes() register_node_type_geo_point_translate(); register_node_type_geo_points_to_volume(); 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_select_by_material(); + register_node_type_geo_material_selection(); register_node_type_geo_separate_components(); + register_node_type_geo_set_position(); register_node_type_geo_subdivision_surface(); register_node_type_geo_switch(); register_node_type_geo_transform(); diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 062264c5729..465ec9dc665 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -324,9 +324,17 @@ static void object_free_data(ID *id) static void object_make_local(Main *bmain, ID *id, const int flags) { + if (!ID_IS_LINKED(id)) { + return; + } + Object *ob = (Object *)id; const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; const bool clear_proxy = (flags & LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING) == 0; + bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; + bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; + BLI_assert(force_copy == false || force_copy != force_local); + bool is_local = false, is_lib = false; /* - only lib users: do nothing (unless force_local is set) @@ -336,36 +344,40 @@ static void object_make_local(Main *bmain, ID *id, const int flags) * we always want to localize, and we skip remapping (done later). */ - if (!ID_IS_LINKED(ob)) { - return; + if (!force_local && !force_copy) { + BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib); + if (lib_local || is_local) { + if (!is_lib) { + force_local = true; + } + else { + force_copy = true; + } + } } - BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib); - - if (lib_local || is_local) { - if (!is_lib) { - BKE_lib_id_clear_library_data(bmain, &ob->id); - BKE_lib_id_expand_local(bmain, &ob->id); - if (clear_proxy) { - if (ob->proxy_from != NULL) { - ob->proxy_from->proxy = NULL; - ob->proxy_from->proxy_group = NULL; - } - ob->proxy = ob->proxy_from = ob->proxy_group = NULL; + if (force_local) { + BKE_lib_id_clear_library_data(bmain, &ob->id); + BKE_lib_id_expand_local(bmain, &ob->id); + if (clear_proxy) { + if (ob->proxy_from != NULL) { + ob->proxy_from->proxy = NULL; + ob->proxy_from->proxy_group = NULL; } + ob->proxy = ob->proxy_from = ob->proxy_group = NULL; } - else { - Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id); - id_us_min(&ob_new->id); + } + else if (force_copy) { + Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id); + id_us_min(&ob_new->id); - ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL; + ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL; - /* setting newid is mandatory for complex make_lib_local logic... */ - ID_NEW_SET(ob, ob_new); + /* setting newid is mandatory for complex make_lib_local logic... */ + ID_NEW_SET(ob, ob_new); - if (!lib_local) { - BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE); - } + if (!lib_local) { + BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE); } } } @@ -1329,6 +1341,11 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) { const ModifierTypeInfo *mti = BKE_modifier_get_info(modifier_type); + /* Surface and lattice objects don't output geometry sets. */ + if (mti->modifyGeometrySet != NULL && ELEM(ob->type, OB_SURF, OB_LATTICE)) { + return false; + } + /* Only geometry objects should be able to get modifiers T25291. */ if (ob->type == OB_HAIR) { return (mti->modifyHair != NULL) || (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly); @@ -1979,8 +1996,7 @@ int BKE_object_visibility(const Object *ob, const int dag_eval_mode) visibility |= OB_VISIBLE_INSTANCES; } - if (ob->runtime.geometry_set_eval != NULL && - BKE_geometry_set_has_instances(ob->runtime.geometry_set_eval)) { + if (BKE_object_has_geometry_set_instances(ob)) { visibility |= OB_VISIBLE_INSTANCES; } @@ -5307,7 +5323,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot) unsigned int i; Mesh *me_eval = ob->runtime.mesh_deform_eval ? ob->runtime.mesh_deform_eval : - ob->runtime.mesh_deform_eval; + BKE_object_get_evaluated_mesh(ob); const int *index; if (me_eval && (index = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX))) { @@ -5737,3 +5753,21 @@ void BKE_object_modifiers_lib_link_common(void *userData, id_us_plus_no_lib(*idpoin); } } + +void BKE_object_replace_data_on_shallow_copy(Object *ob, ID *new_data) +{ + ob->type = BKE_object_obdata_to_type(new_data); + ob->data = new_data; + ob->runtime.geometry_set_eval = NULL; + ob->runtime.data_eval = NULL; + if (ob->runtime.bb != NULL) { + ob->runtime.bb->flag |= BOUNDBOX_DIRTY; + } + ob->id.py_instance = NULL; +} + +bool BKE_object_supports_material_slots(struct Object *ob) +{ + return ELEM( + ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_MBALL, OB_HAIR, OB_POINTCLOUD, OB_VOLUME); +} diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index a46ac4b1175..04739ec19d3 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -194,6 +194,7 @@ static DupliObject *make_dupli(const DupliContext *ctx, } dob->ob = ob; + dob->ob_data = (ID *)ob->data; mul_m4_m4m4(dob->mat, (float(*)[4])ctx->space_mat, mat); dob->type = ctx->gen->type; @@ -834,14 +835,59 @@ static const DupliGenerator gen_dupli_verts_pointcloud = { /** \name Instances Geometry Component Implementation * \{ */ -static void make_duplis_instances_component(const DupliContext *ctx) +static void make_duplis_geometry_set_impl(const DupliContext *ctx, + const GeometrySet &geometry_set, + const float parent_transform[4][4], + bool geometry_set_is_instance) { - const InstancesComponent *component = - ctx->object->runtime.geometry_set_eval->get_component_for_read<InstancesComponent>(); + int component_index = 0; + if (ctx->object->type != OB_MESH || geometry_set_is_instance) { + const Mesh *mesh = geometry_set.get_mesh_for_read(); + if (mesh != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)mesh; + } + } + if (ctx->object->type != OB_VOLUME || geometry_set_is_instance) { + const Volume *volume = geometry_set.get_volume_for_read(); + if (volume != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)volume; + } + } + if (!ELEM(ctx->object->type, OB_CURVE, OB_FONT) || geometry_set_is_instance) { + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + if (curve_component != nullptr) { + const Curve *curve = curve_component->get_curve_for_render(); + if (curve != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)curve; + } + } + } + if (ctx->object->type != OB_POINTCLOUD || geometry_set_is_instance) { + const PointCloud *pointcloud = geometry_set.get_pointcloud_for_read(); + if (pointcloud != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)pointcloud; + } + } + const bool creates_duplis_for_components = component_index >= 1; + + const InstancesComponent *component = geometry_set.get_component_for_read<InstancesComponent>(); if (component == nullptr) { return; } + const DupliContext *instances_ctx = ctx; + /* Create a sub-context if some duplis were created above. This is to avoid dupli id collisions + * between the instances component below and the other components above. */ + DupliContext new_instances_ctx; + if (creates_duplis_for_components) { + copy_dupli_context(&new_instances_ctx, ctx, ctx->object, nullptr, component_index); + instances_ctx = &new_instances_ctx; + } + Span<float4x4> instance_offset_matrices = component->instance_transforms(); Span<int> instance_reference_handles = component->instance_reference_handles(); Span<int> almost_unique_ids = component->almost_unique_ids(); @@ -855,13 +901,13 @@ static void make_duplis_instances_component(const DupliContext *ctx) case InstanceReference::Type::Object: { Object &object = reference.object(); float matrix[4][4]; - mul_m4_m4m4(matrix, ctx->object->obmat, instance_offset_matrices[i].values); - make_dupli(ctx, &object, matrix, id); + mul_m4_m4m4(matrix, parent_transform, instance_offset_matrices[i].values); + make_dupli(instances_ctx, &object, matrix, id); float space_matrix[4][4]; mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object.imat); - mul_m4_m4_pre(space_matrix, ctx->object->obmat); - make_recursive_duplis(ctx, &object, space_matrix, id); + mul_m4_m4_pre(space_matrix, parent_transform); + make_recursive_duplis(instances_ctx, &object, space_matrix, id); break; } case InstanceReference::Type::Collection: { @@ -870,23 +916,36 @@ static void make_duplis_instances_component(const DupliContext *ctx) unit_m4(collection_matrix); sub_v3_v3(collection_matrix[3], collection.instance_offset); mul_m4_m4_pre(collection_matrix, instance_offset_matrices[i].values); - mul_m4_m4_pre(collection_matrix, ctx->object->obmat); + mul_m4_m4_pre(collection_matrix, parent_transform); + + DupliContext sub_ctx; + copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id); - eEvaluationMode mode = DEG_get_mode(ctx->depsgraph); + eEvaluationMode mode = DEG_get_mode(instances_ctx->depsgraph); + int object_id = 0; FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (&collection, object, mode) { - if (object == ctx->object) { + if (object == instances_ctx->object) { continue; } float instance_matrix[4][4]; mul_m4_m4m4(instance_matrix, collection_matrix, object->obmat); - make_dupli(ctx, object, instance_matrix, id); - make_recursive_duplis(ctx, object, collection_matrix, id); + make_dupli(&sub_ctx, object, instance_matrix, object_id++); + make_recursive_duplis(&sub_ctx, object, collection_matrix, object_id++); } FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; break; } + case InstanceReference::Type::GeometrySet: { + float new_transform[4][4]; + mul_m4_m4m4(new_transform, parent_transform, instance_offset_matrices[i].values); + + DupliContext sub_ctx; + copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id); + make_duplis_geometry_set_impl(&sub_ctx, reference.geometry_set(), new_transform, true); + break; + } case InstanceReference::Type::None: { break; } @@ -894,9 +953,15 @@ static void make_duplis_instances_component(const DupliContext *ctx) } } -static const DupliGenerator gen_dupli_instances_component = { +static void make_duplis_geometry_set(const DupliContext *ctx) +{ + const GeometrySet *geometry_set = ctx->object->runtime.geometry_set_eval; + make_duplis_geometry_set_impl(ctx, *geometry_set, ctx->object->obmat, false); +} + +static const DupliGenerator gen_dupli_geometry_set = { 0, - make_duplis_instances_component, + make_duplis_geometry_set, }; /** \} */ @@ -1567,8 +1632,8 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) } if (ctx->object->runtime.geometry_set_eval != nullptr) { - if (BKE_geometry_set_has_instances(ctx->object->runtime.geometry_set_eval)) { - return &gen_dupli_instances_component; + if (BKE_object_has_geometry_set_instances(ctx->object)) { + return &gen_dupli_geometry_set; } } diff --git a/source/blender/blenkernel/intern/packedFile.c b/source/blender/blenkernel/intern/packedFile.c index 78e7e11c248..baff1bb47cc 100644 --- a/source/blender/blenkernel/intern/packedFile.c +++ b/source/blender/blenkernel/intern/packedFile.c @@ -576,26 +576,42 @@ static void unpack_generate_paths(const char *name, } } +char *BKE_packedfile_unpack(Main *bmain, + ReportList *reports, + ID *id, + const char *orig_file_path, + PackedFile *pf, + enum ePF_FileStatus how) +{ + char localname[FILE_MAX], absname[FILE_MAX]; + char *new_name = NULL; + + if (id != NULL) { + unpack_generate_paths( + orig_file_path, id, absname, localname, sizeof(absname), sizeof(localname)); + new_name = BKE_packedfile_unpack_to_file( + reports, BKE_main_blendfile_path(bmain), absname, localname, pf, how); + } + + return new_name; +} + int BKE_packedfile_unpack_vfont(Main *bmain, ReportList *reports, VFont *vfont, enum ePF_FileStatus how) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newname; int ret_value = RET_ERROR; + if (vfont) { + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)vfont, vfont->filepath, vfont->packedfile, how); - if (vfont != NULL) { - unpack_generate_paths( - vfont->filepath, (ID *)vfont, absname, localname, sizeof(absname), sizeof(localname)); - newname = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, vfont->packedfile, how); - if (newname != NULL) { + if (new_file_path != NULL) { ret_value = RET_OK; BKE_packedfile_free(vfont->packedfile); vfont->packedfile = NULL; - BLI_strncpy(vfont->filepath, newname, sizeof(vfont->filepath)); - MEM_freeN(newname); + BLI_strncpy(vfont->filepath, new_file_path, sizeof(vfont->filepath)); + MEM_freeN(new_file_path); } } @@ -607,18 +623,14 @@ int BKE_packedfile_unpack_sound(Main *bmain, bSound *sound, enum ePF_FileStatus how) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newname; int ret_value = RET_ERROR; if (sound != NULL) { - unpack_generate_paths( - sound->filepath, (ID *)sound, absname, localname, sizeof(absname), sizeof(localname)); - newname = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, sound->packedfile, how); - if (newname != NULL) { - BLI_strncpy(sound->filepath, newname, sizeof(sound->filepath)); - MEM_freeN(newname); + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)sound, sound->filepath, sound->packedfile, how); + if (new_file_path != NULL) { + BLI_strncpy(sound->filepath, new_file_path, sizeof(sound->filepath)); + MEM_freeN(new_file_path); BKE_packedfile_free(sound->packedfile); sound->packedfile = NULL; @@ -641,16 +653,11 @@ int BKE_packedfile_unpack_image(Main *bmain, if (ima != NULL) { while (ima->packedfiles.last) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newname; ImagePackedFile *imapf = ima->packedfiles.last; + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)ima, imapf->filepath, imapf->packedfile, how); - unpack_generate_paths( - imapf->filepath, (ID *)ima, absname, localname, sizeof(absname), sizeof(localname)); - newname = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, imapf->packedfile, how); - - if (newname != NULL) { + if (new_file_path != NULL) { ImageView *iv; ret_value = ret_value == RET_ERROR ? RET_ERROR : RET_OK; @@ -660,14 +667,14 @@ int BKE_packedfile_unpack_image(Main *bmain, /* update the new corresponding view filepath */ iv = BLI_findstring(&ima->views, imapf->filepath, offsetof(ImageView, filepath)); if (iv) { - BLI_strncpy(iv->filepath, newname, sizeof(imapf->filepath)); + BLI_strncpy(iv->filepath, new_file_path, sizeof(imapf->filepath)); } /* keep the new name in the image for non-pack specific reasons */ if (how != PF_REMOVE) { - BLI_strncpy(ima->filepath, newname, sizeof(imapf->filepath)); + BLI_strncpy(ima->filepath, new_file_path, sizeof(imapf->filepath)); } - MEM_freeN(newname); + MEM_freeN(new_file_path); } else { ret_value = RET_ERROR; @@ -690,18 +697,14 @@ int BKE_packedfile_unpack_volume(Main *bmain, Volume *volume, enum ePF_FileStatus how) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newfilepath; int ret_value = RET_ERROR; if (volume != NULL) { - unpack_generate_paths( - volume->filepath, (ID *)volume, absname, localname, sizeof(absname), sizeof(localname)); - newfilepath = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, volume->packedfile, how); - if (newfilepath != NULL) { - BLI_strncpy(volume->filepath, newfilepath, sizeof(volume->filepath)); - MEM_freeN(newfilepath); + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)volume, volume->filepath, volume->packedfile, how); + if (new_file_path != NULL) { + BLI_strncpy(volume->filepath, new_file_path, sizeof(volume->filepath)); + MEM_freeN(new_file_path); BKE_packedfile_free(volume->packedfile); volume->packedfile = NULL; diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 065240bddbc..73e25a22225 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -312,7 +312,7 @@ IDTypeInfo IDType_ID_SCR = { .name = "Screen", .name_plural = "screens", .translation_context = BLT_I18NCONTEXT_ID_SCREEN, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_MAKELOCAL | IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/blenkernel/intern/sound.c b/source/blender/blenkernel/intern/sound.c index c61fa793367..ccb10f080e3 100644 --- a/source/blender/blenkernel/intern/sound.c +++ b/source/blender/blenkernel/intern/sound.c @@ -1213,7 +1213,6 @@ static bool sound_info_from_playback_handle(void *playback_handle, SoundInfo *so AUD_SoundInfo info = AUD_getInfo(playback_handle); sound_info->specs.channels = (eSoundChannels)info.specs.channels; sound_info->length = info.length; - sound_info->start_offset = info.start_offset; return true; } @@ -1231,6 +1230,44 @@ bool BKE_sound_info_get(struct Main *main, struct bSound *sound, SoundInfo *soun return result; } +bool BKE_sound_stream_info_get(struct Main *main, const char *filepath, int stream, SoundStreamInfo *sound_info) +{ + const char *path; + char str[FILE_MAX]; + AUD_Sound *sound; + AUD_StreamInfo *stream_infos; + int stream_count; + + BLI_strncpy(str, filepath, sizeof(str)); + path = BKE_main_blendfile_path(main); + BLI_path_abs(str, path); + + sound = AUD_Sound_file(str); + if (!sound) { + return false; + } + + stream_count = AUD_Sound_getFileStreams(sound, &stream_infos); + + AUD_Sound_free(sound); + + if (!stream_infos) { + return false; + } + + if ((stream < 0) || (stream >= stream_count)) { + free(stream_infos); + return false; + } + + sound_info->start = stream_infos[stream].start; + sound_info->duration = stream_infos[stream].duration; + + free(stream_infos); + + return true; +} + #else /* WITH_AUDASPACE */ # include "BLI_utildefines.h" @@ -1400,6 +1437,14 @@ bool BKE_sound_info_get(struct Main *UNUSED(main), return false; } +bool BKE_sound_stream_info_get(struct Main *UNUSED(main), + const char *UNUSED(filepath), + int UNUSED(stream), + SoundStreamInfo *UNUSED(sound_info)) +{ + return false; +} + #endif /* WITH_AUDASPACE */ void BKE_sound_reset_scene_runtime(Scene *scene) diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc index 732fabc6582..663c1951ba3 100644 --- a/source/blender/blenkernel/intern/spline_base.cc +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -19,6 +19,8 @@ #include "BLI_task.hh" #include "BLI_timeit.hh" +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" #include "BKE_spline.hh" #include "FN_generic_virtual_array.hh" @@ -28,6 +30,8 @@ using blender::float3; using blender::IndexRange; using blender::MutableSpan; using blender::Span; +using blender::attribute_math::convert_to_static_type; +using blender::bke::AttributeIDRef; using blender::fn::GMutableSpan; using blender::fn::GSpan; using blender::fn::GVArray; @@ -110,10 +114,36 @@ void Spline::transform(const blender::float4x4 &matrix) this->mark_cache_invalid(); } +void Spline::reverse() +{ + this->positions().reverse(); + this->radii().reverse(); + this->tilts().reverse(); + + this->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + std::optional<blender::fn::GMutableSpan> attribute = this->attributes.get_for_write(id); + if (!attribute) { + BLI_assert_unreachable(); + return false; + } + convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + attribute->typed<T>().reverse(); + }); + return true; + }, + ATTR_DOMAIN_POINT); + + this->reverse_impl(); + this->mark_cache_invalid(); +} + int Spline::evaluated_edges_size() const { const int eval_size = this->evaluated_points_size(); - if (eval_size == 1) { + if (eval_size < 2) { + /* Two points are required for an edge. */ return 0; } @@ -161,7 +191,7 @@ static void accumulate_lengths(Span<float3> positions, * Return non-owning access to the cache of accumulated lengths along the spline. Each item is the * length of the subsequent segment, i.e. the first value is the length of the first segment rather * than 0. This calculation is rather trivial, and only depends on the evaluated positions. - * However, the results are used often, so it makes sense to cache it. + * However, the results are used often, and it is necessarily single threaded, so it is cached. */ Span<float> Spline::evaluated_lengths() const { @@ -176,9 +206,10 @@ Span<float> Spline::evaluated_lengths() const const int total = evaluated_edges_size(); evaluated_lengths_cache_.resize(total); - - Span<float3> positions = this->evaluated_positions(); - accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_); + if (total != 0) { + Span<float3> positions = this->evaluated_positions(); + accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_); + } length_cache_dirty_ = false; return evaluated_lengths_cache_; diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index b6764f65631..79d2137ee84 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -166,6 +166,17 @@ MutableSpan<float3> BezierSpline::handle_positions_right() return handle_positions_right_; } +void BezierSpline::reverse_impl() +{ + this->handle_positions_left().reverse(); + this->handle_positions_right().reverse(); + std::swap(this->handle_positions_left_, this->handle_positions_right_); + + this->handle_types_left().reverse(); + this->handle_types_right().reverse(); + std::swap(this->handle_types_left_, this->handle_types_right_); +} + static float3 previous_position(Span<float3> positions, const bool cyclic, const int i) { if (i == 0) { diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index ac6f1bd082c..6d30d8ba916 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -142,6 +142,11 @@ Span<float> NURBSpline::weights() const return weights_; } +void NURBSpline::reverse_impl() +{ + this->weights().reverse(); +} + void NURBSpline::mark_cache_invalid() { basis_cache_dirty_ = true; diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc index dfd24b2566e..338b5d0ac9e 100644 --- a/source/blender/blenkernel/intern/spline_poly.cc +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -91,6 +91,10 @@ Span<float> PolySpline::tilts() const return tilts_; } +void PolySpline::reverse_impl() +{ +} + void PolySpline::mark_cache_invalid() { tangent_cache_dirty_ = true; diff --git a/source/blender/blenkernel/intern/studiolight.c b/source/blender/blenkernel/intern/studiolight.c index 95436372a65..29f726ece71 100644 --- a/source/blender/blenkernel/intern/studiolight.c +++ b/source/blender/blenkernel/intern/studiolight.c @@ -439,17 +439,15 @@ static void studiolight_load_equirect_image(StudioLight *sl) if (ctx.diffuse_pass != NULL) { float *converted_pass = studiolight_multilayer_convert_pass( ibuf, ctx.diffuse_pass, ctx.num_diffuse_channels); - diffuse_ibuf = IMB_allocFromBuffer( + diffuse_ibuf = IMB_allocFromBufferOwn( NULL, converted_pass, ibuf->x, ibuf->y, ctx.num_diffuse_channels); - MEM_freeN(converted_pass); } if (ctx.specular_pass != NULL) { float *converted_pass = studiolight_multilayer_convert_pass( ibuf, ctx.specular_pass, ctx.num_specular_channels); - specular_ibuf = IMB_allocFromBuffer( + specular_ibuf = IMB_allocFromBufferOwn( NULL, converted_pass, ibuf->x, ibuf->y, ctx.num_specular_channels); - MEM_freeN(converted_pass); } IMB_exr_close(ibuf->userdata); @@ -1148,12 +1146,11 @@ static void studiolight_calculate_irradiance_equirect_image(StudioLight *sl) } ITER_PIXELS_END; - sl->equirect_irradiance_buffer = IMB_allocFromBuffer(NULL, - colbuf, - STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH, - STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT, - 4); - MEM_freeN(colbuf); + sl->equirect_irradiance_buffer = IMB_allocFromBufferOwn(NULL, + colbuf, + STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH, + STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT, + 4); } sl->flag |= STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED; } diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c index da6ee8d8779..e9cd0b70019 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_mesh.c @@ -1232,7 +1232,7 @@ Mesh *BKE_subdiv_to_mesh(Subdiv *subdiv, // BKE_mesh_validate(result, true, true); BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH); if (!subdiv_context.can_evaluate_normals) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } /* Free used memory. */ subdiv_mesh_context_free(&subdiv_context); diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c index 0ca2b97b4ef..db5184edfd2 100644 --- a/source/blender/blenkernel/intern/undo_system.c +++ b/source/blender/blenkernel/intern/undo_system.c @@ -744,16 +744,15 @@ static UndoStep *undosys_step_iter_first(UndoStep *us_reference, const eUndoStep /** * Undo/Redo until the given `us_target` step becomes the active (currently loaded) one. * - * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target` will become the - * active step. + * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target` + * will become the active step. * - * \note In case `use_skip` is true, the final target will always be **beyond** the given one (if - * the given one has to be skipped). + * \note In case `use_skip` is true, the final target will always be **beyond** the given one + * (if the given one has to be skipped). * - * \param us_reference If NULL, will be set to current active step in the undo stack. Otherwise, it - * is assumed to match the current state, and will be used as basis for the - * undo/redo process (i.e. all steps in-between `us_reference` and `us_target` - * will be processed). + * \param us_reference: If NULL, will be set to current active step in the undo stack. Otherwise, + * it is assumed to match the current state, and will be used as basis for the undo/redo process + * (i.e. all steps in-between `us_reference` and `us_target` will be processed). */ bool BKE_undosys_step_load_data_ex(UndoStack *ustack, bContext *C, diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c index 329633c6759..3c168a6c7b2 100644 --- a/source/blender/blenkernel/intern/workspace.c +++ b/source/blender/blenkernel/intern/workspace.c @@ -186,7 +186,7 @@ IDTypeInfo IDType_ID_WS = { .name = "WorkSpace", .name_plural = "workspaces", .translation_context = BLT_I18NCONTEXT_ID_WORKSPACE, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_MAKELOCAL | IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = workspace_init_data, .copy_data = NULL, diff --git a/source/blender/blenlib/BLI_float4.hh b/source/blender/blenlib/BLI_float4.hh new file mode 100644 index 00000000000..b1feee3121b --- /dev/null +++ b/source/blender/blenlib/BLI_float4.hh @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#pragma once + +namespace blender { + +struct float4 { + float x, y, z, w; + + float4() = default; + + float4(const float *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}, w{ptr[3]} + { + } + + explicit float4(float value) : x(value), y(value), z(value), w(value) + { + } + + explicit float4(int value) : x(value), y(value), z(value), w(value) + { + } + + float4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) + { + } + + operator float *() + { + return &x; + } + + operator const float *() const + { + return &x; + } + + float4 &operator+=(const float4 &other) + { + x += other.x; + y += other.y; + z += other.z; + w += other.w; + return *this; + } + + friend float4 operator+(const float4 &a, const float4 &b) + { + return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; + } + + float4 &operator*=(float factor) + { + x *= factor; + y *= factor; + z *= factor; + w *= factor; + return *this; + } + + friend float4 operator*(const float4 &a, float b) + { + return {a.x * b, a.y * b, a.z * b, a.w * b}; + } + + friend float4 operator*(float a, const float4 &b) + { + return b * a; + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_hash.hh b/source/blender/blenlib/BLI_hash.hh index fbed321534c..11ff7d040aa 100644 --- a/source/blender/blenlib/BLI_hash.hh +++ b/source/blender/blenlib/BLI_hash.hh @@ -250,6 +250,20 @@ template<typename T> struct DefaultHash<std::unique_ptr<T>> { } }; +template<typename T> struct DefaultHash<std::shared_ptr<T>> { + uint64_t operator()(const std::shared_ptr<T> &value) const + { + return get_default_hash(value.get()); + } +}; + +template<typename T> struct DefaultHash<std::reference_wrapper<T>> { + uint64_t operator()(const std::reference_wrapper<T> &value) const + { + return get_default_hash(value.get()); + } +}; + template<typename T1, typename T2> struct DefaultHash<std::pair<T1, T2>> { uint64_t operator()(const std::pair<T1, T2> &value) const { diff --git a/source/blender/blenlib/BLI_index_mask.hh b/source/blender/blenlib/BLI_index_mask.hh index 7a3169520ca..ad030e127fe 100644 --- a/source/blender/blenlib/BLI_index_mask.hh +++ b/source/blender/blenlib/BLI_index_mask.hh @@ -39,6 +39,7 @@ #include "BLI_index_range.hh" #include "BLI_span.hh" +#include "BLI_vector.hh" namespace blender { @@ -221,6 +222,8 @@ class IndexMask { { return indices_.is_empty(); } + + IndexMask slice_and_offset(IndexRange slice, Vector<int64_t> &r_new_indices) const; }; } // namespace blender diff --git a/source/blender/blenlib/BLI_noise.hh b/source/blender/blenlib/BLI_noise.hh new file mode 100644 index 00000000000..760ff082d06 --- /dev/null +++ b/source/blender/blenlib/BLI_noise.hh @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#pragma once + +#include "BLI_float2.hh" +#include "BLI_float3.hh" +#include "BLI_float4.hh" + +namespace blender::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, + float distortion); +float3 perlin_float3_fractal_distorted(float2 position, + float octaves, + float roughness, + float distortion); +float3 perlin_float3_fractal_distorted(float3 position, + float octaves, + float roughness, + float distortion); +float3 perlin_float3_fractal_distorted(float4 position, + float octaves, + float roughness, + float distortion); + +} // namespace blender::noise diff --git a/source/blender/blenlib/BLI_resource_scope.hh b/source/blender/blenlib/BLI_resource_scope.hh index 6a98c2dcc1c..edffb148477 100644 --- a/source/blender/blenlib/BLI_resource_scope.hh +++ b/source/blender/blenlib/BLI_resource_scope.hh @@ -50,11 +50,10 @@ class ResourceScope : NonCopyable, NonMovable { struct ResourceData { void *data; void (*free)(void *data); - const char *debug_name; }; - LinearAllocator<> m_allocator; - Vector<ResourceData> m_resources; + LinearAllocator<> allocator_; + Vector<ResourceData> resources_; public: ResourceScope() = default; @@ -62,8 +61,8 @@ class ResourceScope : NonCopyable, NonMovable { ~ResourceScope() { /* Free in reversed order. */ - for (int64_t i = m_resources.size(); i--;) { - ResourceData &data = m_resources[i]; + for (int64_t i = resources_.size(); i--;) { + ResourceData &data = resources_[i]; data.free(data.data); } } @@ -72,20 +71,17 @@ class ResourceScope : NonCopyable, NonMovable { * Pass ownership of the resource to the ResourceScope. It will be destructed and freed when * the collector is destructed. */ - template<typename T> T *add(std::unique_ptr<T> resource, const char *name) + template<typename T> T *add(std::unique_ptr<T> resource) { BLI_assert(resource.get() != nullptr); T *ptr = resource.release(); if (ptr == nullptr) { return nullptr; } - this->add( - ptr, - [](void *data) { - T *typed_data = reinterpret_cast<T *>(data); - delete typed_data; - }, - name); + this->add(ptr, [](void *data) { + T *typed_data = reinterpret_cast<T *>(data); + delete typed_data; + }); return ptr; } @@ -93,7 +89,7 @@ class ResourceScope : NonCopyable, NonMovable { * Pass ownership of the resource to the ResourceScope. It will be destructed when the * collector is destructed. */ - template<typename T> T *add(destruct_ptr<T> resource, const char *name) + template<typename T> T *add(destruct_ptr<T> resource) { T *ptr = resource.release(); if (ptr == nullptr) { @@ -104,13 +100,10 @@ class ResourceScope : NonCopyable, NonMovable { return ptr; } - this->add( - ptr, - [](void *data) { - T *typed_data = reinterpret_cast<T *>(data); - typed_data->~T(); - }, - name); + this->add(ptr, [](void *data) { + T *typed_data = reinterpret_cast<T *>(data); + typed_data->~T(); + }); return ptr; } @@ -118,22 +111,31 @@ class ResourceScope : NonCopyable, NonMovable { * Pass ownership of some resource to the ResourceScope. The given free function will be * called when the collector is destructed. */ - void add(void *userdata, void (*free)(void *), const char *name) + void add(void *userdata, void (*free)(void *)) { ResourceData data; - data.debug_name = name; data.data = userdata; data.free = free; - m_resources.append(data); + resources_.append(data); } /** * Construct an object with the same value in the ResourceScope and return a reference to the * new value. */ - template<typename T> T &add_value(T &&value, const char *name) + template<typename T> T &add_value(T &&value) { - return this->construct<T>(name, std::forward<T>(value)); + return this->construct<T>(std::forward<T>(value)); + } + + /** + * The passed in function will be called when the scope is destructed. + */ + template<typename Func> void add_destruct_call(Func func) + { + void *buffer = allocator_.allocate(sizeof(Func), alignof(Func)); + new (buffer) Func(std::move(func)); + this->add(buffer, [](void *data) { (*(Func *)data)(); }); } /** @@ -142,37 +144,19 @@ class ResourceScope : NonCopyable, NonMovable { */ LinearAllocator<> &linear_allocator() { - return m_allocator; + return allocator_; } /** * Utility method to construct an instance of type T that will be owned by the ResourceScope. */ - template<typename T, typename... Args> T &construct(const char *name, Args &&...args) + template<typename T, typename... Args> T &construct(Args &&...args) { - destruct_ptr<T> value_ptr = m_allocator.construct<T>(std::forward<Args>(args)...); + destruct_ptr<T> value_ptr = allocator_.construct<T>(std::forward<Args>(args)...); T &value_ref = *value_ptr; - this->add(std::move(value_ptr), name); + this->add(std::move(value_ptr)); return value_ref; } - - /** - * Print the names of all the resources that are owned by this ResourceScope. This can be - * useful for debugging. - */ - void print(StringRef name) const - { - if (m_resources.size() == 0) { - std::cout << "\"" << name << "\" has no resources.\n"; - return; - } - else { - std::cout << "Resources for \"" << name << "\":\n"; - for (const ResourceData &data : m_resources) { - std::cout << " " << data.data << ": " << data.debug_name << '\n'; - } - } - } }; } // namespace blender diff --git a/source/blender/blenlib/BLI_session_uuid.h b/source/blender/blenlib/BLI_session_uuid.h index 05355a85b39..887044e9b54 100644 --- a/source/blender/blenlib/BLI_session_uuid.h +++ b/source/blender/blenlib/BLI_session_uuid.h @@ -18,6 +18,13 @@ /** \file * \ingroup bli + * + * Functions for generating and handling "Session UUIDs". + * + * Note that these are not true universally-unique identifiers, but only unique during the current + * Blender session. + * + * For true UUIDs, see `BLI_uuid.h`. */ #ifdef __cplusplus diff --git a/source/blender/blenlib/BLI_span.hh b/source/blender/blenlib/BLI_span.hh index e04295b0e51..5adb47ba0b0 100644 --- a/source/blender/blenlib/BLI_span.hh +++ b/source/blender/blenlib/BLI_span.hh @@ -644,6 +644,16 @@ template<typename T> class MutableSpan { } /** + * Reverse the data in the MutableSpan. + */ + constexpr void reverse() + { + for (const int i : IndexRange(size_ / 2)) { + std::swap(data_[size_ - 1 - i], data_[i]); + } + } + + /** * Returns an (immutable) Span that references the same array. This is usually not needed, * due to implicit conversions. However, sometimes automatic type deduction needs some help. */ diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h index eab39433796..d3dc05edd9e 100644 --- a/source/blender/blenlib/BLI_string.h +++ b/source/blender/blenlib/BLI_string.h @@ -62,9 +62,11 @@ bool BLI_str_quoted_substr_range(const char *__restrict str, int *__restrict r_start, int *__restrict r_end) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3, 4); +#if 0 /* UNUSED */ char *BLI_str_quoted_substrN(const char *__restrict str, const char *__restrict prefix) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL() ATTR_MALLOC; +#endif bool BLI_str_quoted_substr(const char *__restrict str, const char *__restrict prefix, char *result, diff --git a/source/blender/blenlib/BLI_user_counter.hh b/source/blender/blenlib/BLI_user_counter.hh index 3e6d5af4c3f..8cebadeac4c 100644 --- a/source/blender/blenlib/BLI_user_counter.hh +++ b/source/blender/blenlib/BLI_user_counter.hh @@ -84,12 +84,24 @@ template<typename T> class UserCounter { return data_; } + const T *operator->() const + { + BLI_assert(data_ != nullptr); + return data_; + } + T &operator*() { BLI_assert(data_ != nullptr); return *data_; } + const T &operator*() const + { + BLI_assert(data_ != nullptr); + return *data_; + } + operator bool() const { return data_ != nullptr; diff --git a/source/blender/blenlib/BLI_utildefines.h b/source/blender/blenlib/BLI_utildefines.h index 5b84e050f82..dec8acd7549 100644 --- a/source/blender/blenlib/BLI_utildefines.h +++ b/source/blender/blenlib/BLI_utildefines.h @@ -683,12 +683,22 @@ extern bool BLI_memory_is_zero(const void *arr, const size_t arr_size); # define UNUSED(x) UNUSED_##x #endif +/** + * WARNING: this doesn't warn when returning pointer types (because of the placement of `*`). + * Use #UNUSED_FUNCTION_WITH_RETURN_TYPE instead in this case. + */ #if defined(__GNUC__) || defined(__clang__) # define UNUSED_FUNCTION(x) __attribute__((__unused__)) UNUSED_##x #else # define UNUSED_FUNCTION(x) UNUSED_##x #endif +#if defined(__GNUC__) || defined(__clang__) +# define UNUSED_FUNCTION_WITH_RETURN_TYPE(rtype, x) __attribute__((__unused__)) rtype UNUSED_##x +#else +# define UNUSED_FUNCTION_WITH_RETURN_TYPE(rtype, x) rtype UNUSED_##x +#endif + /** * UNUSED_VARS#(a, ...): quiet unused warnings * diff --git a/source/blender/blenlib/BLI_uuid.h b/source/blender/blenlib/BLI_uuid.h new file mode 100644 index 00000000000..1ce294ed723 --- /dev/null +++ b/source/blender/blenlib/BLI_uuid.h @@ -0,0 +1,75 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup bli + * + * Functions for generating and handling UUID structs according to RFC4122. + * + * Note that these are true UUIDs, not to be confused with the "session uuid" defined in + * `BLI_session_uuid.h`. + */ +#include "DNA_uuid_types.h" + +#include "BLI_compiler_attrs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UUID generator for random (version 4) UUIDs. See RFC4122 section 4.4. + * This function is not thread-safe. */ +UUID BLI_uuid_generate_random(void); + +/** + * Return the UUID nil value, consisting of all-zero fields. + */ +UUID BLI_uuid_nil(void); + +/** Return true only if this is the nil UUID. */ +bool BLI_uuid_is_nil(UUID uuid); + +/** Compare two UUIDs, return true only if they are equal. */ +bool BLI_uuid_equal(UUID uuid1, UUID uuid2); + +/** + * Format UUID as string. + * The buffer must be at least 37 bytes (36 bytes for the UUID + terminating 0). + */ +void BLI_uuid_format(char *buffer, UUID uuid) ATTR_NONNULL(); + +/** + * Parse a string as UUID. + * The string MUST be in the format `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, + * as produced by #BLI_uuid_format(). + * + * Return true if the string could be parsed, and false otherwise. In the latter case, the UUID may + * have been partially updated. + */ +bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) ATTR_NONNULL(); + +#ifdef __cplusplus +} + +# include <ostream> + +/** Output the UUID as formatted ASCII string, see #BLI_uuid_format(). */ +std::ostream &operator<<(std::ostream &stream, UUID uuid); + +#endif diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 24178535068..1eaf007e01b 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -86,6 +86,7 @@ set(SRC intern/hash_md5.c intern/hash_mm2a.c intern/hash_mm3.c + intern/index_mask.cc intern/jitter_2d.c intern/kdtree_1d.c intern/kdtree_2d.c @@ -116,6 +117,7 @@ set(SRC intern/mesh_boolean.cc intern/mesh_intersect.cc intern/noise.c + intern/noise.cc intern/path_util.c intern/polyfill_2d.c intern/polyfill_2d_beautify.c @@ -145,6 +147,7 @@ set(SRC intern/time.c intern/timecode.c intern/timeit.cc + intern/uuid.cc intern/uvproject.c intern/voronoi_2d.c intern/voxel.c @@ -203,6 +206,7 @@ set(SRC BLI_filereader.h BLI_float2.hh BLI_float3.hh + BLI_float4.hh BLI_float4x4.hh BLI_fnmatch.h BLI_function_ref.hh @@ -264,6 +268,7 @@ set(SRC BLI_mpq3.hh BLI_multi_value_map.hh BLI_noise.h + BLI_noise.hh BLI_path_util.h BLI_polyfill_2d.h BLI_polyfill_2d_beautify.h @@ -306,6 +311,7 @@ set(SRC BLI_utildefines_stack.h BLI_utildefines_variadic.h BLI_utility_mixins.hh + BLI_uuid.h BLI_uvproject.h BLI_vector.hh BLI_vector_adaptor.hh @@ -451,6 +457,7 @@ if(WITH_GTESTS) tests/BLI_string_utf8_test.cc tests/BLI_task_graph_test.cc tests/BLI_task_test.cc + tests/BLI_uuid_test.cc tests/BLI_vector_set_test.cc tests/BLI_vector_test.cc tests/BLI_virtual_array_test.cc diff --git a/source/blender/blenlib/intern/freetypefont.c b/source/blender/blenlib/intern/freetypefont.c index e1e3aa273b5..34de8fe7f6d 100644 --- a/source/blender/blenlib/intern/freetypefont.c +++ b/source/blender/blenlib/intern/freetypefont.c @@ -369,36 +369,28 @@ static VFontData *objfnt_to_ftvfontdata(PackedFile *pf) return vfd; } -static int check_freetypefont(PackedFile *pf) +static bool check_freetypefont(PackedFile *pf) { - FT_Face face; - FT_GlyphSlot glyph; - FT_UInt glyph_index; - int success = 0; + FT_Face face = NULL; + FT_UInt glyph_index = 0; + bool success = false; err = FT_New_Memory_Face(library, pf->data, pf->size, 0, &face); if (err) { - success = 0; + return false; // XXX error("This is not a valid font"); } - else { - glyph_index = FT_Get_Char_Index(face, 'A'); + + FT_Get_First_Char(face, &glyph_index); + if (glyph_index) { err = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP); - if (err) { - success = 0; - } - else { - glyph = face->glyph; - if (glyph->format == ft_glyph_format_outline) { - success = 1; - } - else { - // XXX error("Selected Font has no outline data"); - success = 0; - } + if (!err) { + success = (face->glyph->format == ft_glyph_format_outline); } } + FT_Done_Face(face); + return success; } @@ -413,7 +405,6 @@ static int check_freetypefont(PackedFile *pf) VFontData *BLI_vfontdata_from_freetypefont(PackedFile *pf) { VFontData *vfd = NULL; - int success = 0; /* init Freetype */ err = FT_Init_FreeType(&library); @@ -422,9 +413,7 @@ VFontData *BLI_vfontdata_from_freetypefont(PackedFile *pf) return NULL; } - success = check_freetypefont(pf); - - if (success) { + if (check_freetypefont(pf)) { vfd = objfnt_to_ftvfontdata(pf); } diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc new file mode 100644 index 00000000000..cba985b8a44 --- /dev/null +++ b/source/blender/blenlib/intern/index_mask.cc @@ -0,0 +1,57 @@ +/* + * 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_index_mask.hh" + +namespace blender { + +/** + * Create a sub-mask that is also shifted to the beginning. The shifting to the beginning allows + * code to work with smaller indices, which is more memory efficient. + * + * \return New index mask with the size of #slice. It is either empty or starts with 0. It might + * reference indices that have been appended to #r_new_indices. + * + * Example: + * this: [2, 3, 5, 7, 8, 9, 10] + * slice: ^--------^ + * output: [0, 2, 4, 5] + * + * All the indices in the sub-mask are shifted by 3 towards zero, so that the first index in the + * output is zero. + */ +IndexMask IndexMask::slice_and_offset(const IndexRange slice, Vector<int64_t> &r_new_indices) const +{ + const int slice_size = slice.size(); + if (slice_size == 0) { + return {}; + } + IndexMask sliced_mask{indices_.slice(slice)}; + if (sliced_mask.is_range()) { + return IndexMask(slice_size); + } + const int64_t offset = sliced_mask.indices().first(); + if (offset == 0) { + return sliced_mask; + } + r_new_indices.resize(slice_size); + for (const int i : IndexRange(slice_size)) { + r_new_indices[i] = sliced_mask[i] - offset; + } + return IndexMask(r_new_indices.as_span()); +} + +} // namespace blender diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc new file mode 100644 index 00000000000..c057c12e543 --- /dev/null +++ b/source/blender/blenlib/intern/noise.cc @@ -0,0 +1,693 @@ +/* + * Adapted from Open Shading Language with this license: + * + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * Modifications Copyright 2011, Blender Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sony Pictures Imageworks nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * 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 <cmath> +#include <cstdint> + +#include "BLI_float2.hh" +#include "BLI_float3.hh" +#include "BLI_float4.hh" +#include "BLI_noise.hh" +#include "BLI_utildefines.h" + +namespace blender::noise { +/* ------------------------------ + * Jenkins Lookup3 Hash Functions + * ------------------------------ + * + * https://burtleburtle.net/bob/c/lookup3.c + * + */ + +BLI_INLINE uint32_t hash_bit_rotate(uint32_t x, uint32_t k) +{ + return (x << k) | (x >> (32 - k)); +} + +BLI_INLINE void hash_bit_mix(uint32_t &a, uint32_t &b, uint32_t &c) +{ + a -= c; + a ^= hash_bit_rotate(c, 4); + c += b; + b -= a; + b ^= hash_bit_rotate(a, 6); + a += c; + c -= b; + c ^= hash_bit_rotate(b, 8); + b += a; + a -= c; + a ^= hash_bit_rotate(c, 16); + c += b; + b -= a; + b ^= hash_bit_rotate(a, 19); + a += c; + c -= b; + c ^= hash_bit_rotate(b, 4); + b += a; +} + +BLI_INLINE void hash_bit_final(uint32_t &a, uint32_t &b, uint32_t &c) +{ + c ^= b; + c -= hash_bit_rotate(b, 14); + a ^= c; + a -= hash_bit_rotate(c, 11); + b ^= a; + b -= hash_bit_rotate(a, 25); + c ^= b; + c -= hash_bit_rotate(b, 16); + a ^= c; + a -= hash_bit_rotate(c, 4); + b ^= a; + b -= hash_bit_rotate(a, 14); + c ^= b; + c -= hash_bit_rotate(b, 24); +} + +BLI_INLINE uint32_t hash(uint32_t kx) +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (1 << 2) + 13; + + a += kx; + hash_bit_final(a, b, c); + + return c; +} + +BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky) +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (2 << 2) + 13; + + b += ky; + a += kx; + hash_bit_final(a, b, c); + + return c; +} + +BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (3 << 2) + 13; + + c += kz; + b += ky; + a += kx; + hash_bit_final(a, b, c); + + return c; +} + +BLI_INLINE 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; + + a += kx; + b += ky; + c += kz; + hash_bit_mix(a, b, c); + + a += kw; + hash_bit_final(a, b, c); + + return c; +} + +/* Hashing a number of uint32_t into a float in the range [0, 1]. */ + +BLI_INLINE float hash_to_float(uint32_t kx) +{ + return static_cast<float>(hash(kx)) / static_cast<float>(0xFFFFFFFFu); +} + +BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky) +{ + return static_cast<float>(hash(kx, ky)) / static_cast<float>(0xFFFFFFFFu); +} + +BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz) +{ + return static_cast<float>(hash(kx, ky, kz)) / static_cast<float>(0xFFFFFFFFu); +} + +BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +{ + return static_cast<float>(hash(kx, ky, kz, kw)) / static_cast<float>(0xFFFFFFFFu); +} + +/* Hashing a number of floats 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(float k) +{ + return hash_to_float(float_as_uint(k)); +} + +BLI_INLINE float hash_to_float(float2 k) +{ + return hash_to_float(float_as_uint(k.x), float_as_uint(k.y)); +} + +BLI_INLINE float hash_to_float(float3 k) +{ + return hash_to_float(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z)); +} + +BLI_INLINE float hash_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)); +} + +/* ------------ + * Perlin Noise + * ------------ + * + * Perlin, Ken. "Improving noise." Proceedings of the 29th annual conference on Computer graphics + * and interactive techniques. 2002. + * + * This implementation is functionally identical to the implementations in EEVEE, OSL, and SVM. So + * any changes should be applied in all relevant implementations. + */ + +/* Linear Interpolation. */ +BLI_INLINE float mix(float v0, float v1, float x) +{ + return (1 - x) * v0 + x * v1; +} + +/* Bilinear Interpolation: + * + * v2 v3 + * @ + + + + @ y + * + + ^ + * + + | + * + + | + * @ + + + + @ @------> x + * v0 v1 + * + */ +BLI_INLINE float mix(float v0, float v1, float v2, float v3, float x, float y) +{ + float x1 = 1.0 - x; + return (1.0 - y) * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x); +} + +/* Trilinear Interpolation: + * + * v6 v7 + * @ + + + + + + @ + * +\ +\ + * + \ + \ + * + \ + \ + * + \ v4 + \ v5 + * + @ + + + +++ + @ z + * + + + + y ^ + * v2 @ + +++ + + + @ v3 + \ | + * \ + \ + \ | + * \ + \ + \| + * \ + \ + +---------> x + * \+ \+ + * @ + + + + + + @ + * v0 v1 + */ +BLI_INLINE float mix(float v0, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6, + float v7, + float x, + float y, + float z) +{ + float x1 = 1.0 - x; + float y1 = 1.0 - y; + float z1 = 1.0 - z; + return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) + + z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x)); +} + +/* Quadrilinear Interpolation. */ +BLI_INLINE float mix(float v0, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6, + float v7, + float v8, + float v9, + float v10, + float v11, + float v12, + float v13, + float v14, + float v15, + float x, + float y, + float z, + float w) +{ + return mix(mix(v0, v1, v2, v3, v4, v5, v6, v7, x, y, z), + mix(v8, v9, v10, v11, v12, v13, v14, v15, x, y, z), + w); +} + +BLI_INLINE float fade(float t) +{ + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +BLI_INLINE float negate_if(float value, uint32_t condition) +{ + return (condition != 0u) ? -value : value; +} + +BLI_INLINE float noise_grad(uint32_t hash, float x) +{ + uint32_t h = hash & 15u; + float g = 1u + (h & 7u); + return negate_if(g, h & 8u) * x; +} + +BLI_INLINE float noise_grad(uint32_t hash, float x, float y) +{ + uint32_t h = hash & 7u; + float u = h < 4u ? x : y; + float v = 2.0 * (h < 4u ? y : x); + return negate_if(u, h & 1u) + negate_if(v, h & 2u); +} + +BLI_INLINE float noise_grad(uint32_t hash, float x, float y, float z) +{ + uint32_t h = hash & 15u; + float u = h < 8u ? x : y; + float vt = ((h == 12u) || (h == 14u)) ? x : z; + float v = h < 4u ? y : vt; + return negate_if(u, h & 1u) + negate_if(v, h & 2u); +} + +BLI_INLINE float noise_grad(uint32_t hash, float x, float y, float z, float w) +{ + uint32_t h = hash & 31u; + float u = h < 24u ? x : y; + float v = h < 16u ? y : z; + float s = h < 8u ? z : w; + return negate_if(u, h & 1u) + negate_if(v, h & 2u) + negate_if(s, h & 4u); +} + +BLI_INLINE float floor_fraction(float x, int &i) +{ + i = (int)x - ((x < 0) ? 1 : 0); + return x - i; +} + +BLI_INLINE float perlin_noise(float position) +{ + int X; + + float fx = floor_fraction(position, X); + + float u = fade(fx); + + float r = mix(noise_grad(hash(X), fx), noise_grad(hash(X + 1), fx - 1.0), u); + + return r; +} + +BLI_INLINE float perlin_noise(float2 position) +{ + int X, Y; + + float fx = floor_fraction(position.x, X); + float fy = floor_fraction(position.y, Y); + + float u = fade(fx); + float v = fade(fy); + + float r = mix(noise_grad(hash(X, Y), fx, fy), + noise_grad(hash(X + 1, Y), fx - 1.0, fy), + noise_grad(hash(X, Y + 1), fx, fy - 1.0), + noise_grad(hash(X + 1, Y + 1), fx - 1.0, fy - 1.0), + u, + v); + + return r; +} + +BLI_INLINE float perlin_noise(float3 position) +{ + int X, Y, Z; + + float fx = floor_fraction(position.x, X); + float fy = floor_fraction(position.y, Y); + float fz = floor_fraction(position.z, Z); + + float u = fade(fx); + float v = fade(fy); + float w = fade(fz); + + float r = mix(noise_grad(hash(X, Y, Z), fx, fy, fz), + noise_grad(hash(X + 1, Y, Z), fx - 1, fy, fz), + noise_grad(hash(X, Y + 1, Z), fx, fy - 1, fz), + noise_grad(hash(X + 1, Y + 1, Z), fx - 1, fy - 1, fz), + noise_grad(hash(X, Y, Z + 1), fx, fy, fz - 1), + noise_grad(hash(X + 1, Y, Z + 1), fx - 1, fy, fz - 1), + noise_grad(hash(X, Y + 1, Z + 1), fx, fy - 1, fz - 1), + noise_grad(hash(X + 1, Y + 1, Z + 1), fx - 1, fy - 1, fz - 1), + u, + v, + w); + + return r; +} + +BLI_INLINE float perlin_noise(float4 position) +{ + int X, Y, Z, W; + + float fx = floor_fraction(position.x, X); + float fy = floor_fraction(position.y, Y); + float fz = floor_fraction(position.z, Z); + float fw = floor_fraction(position.w, W); + + float u = fade(fx); + float v = fade(fy); + float t = fade(fz); + float s = fade(fw); + + float r = mix( + noise_grad(hash(X, Y, Z, W), fx, fy, fz, fw), + noise_grad(hash(X + 1, Y, Z, W), fx - 1.0, fy, fz, fw), + noise_grad(hash(X, Y + 1, Z, W), fx, fy - 1.0, fz, fw), + noise_grad(hash(X + 1, Y + 1, Z, W), fx - 1.0, fy - 1.0, fz, fw), + noise_grad(hash(X, Y, Z + 1, W), fx, fy, fz - 1.0, fw), + noise_grad(hash(X + 1, Y, Z + 1, W), fx - 1.0, fy, fz - 1.0, fw), + noise_grad(hash(X, Y + 1, Z + 1, W), fx, fy - 1.0, fz - 1.0, fw), + noise_grad(hash(X + 1, Y + 1, Z + 1, W), fx - 1.0, fy - 1.0, fz - 1.0, fw), + noise_grad(hash(X, Y, Z, W + 1), fx, fy, fz, fw - 1.0), + noise_grad(hash(X + 1, Y, Z, W + 1), fx - 1.0, fy, fz, fw - 1.0), + noise_grad(hash(X, Y + 1, Z, W + 1), fx, fy - 1.0, fz, fw - 1.0), + noise_grad(hash(X + 1, Y + 1, Z, W + 1), fx - 1.0, fy - 1.0, fz, fw - 1.0), + noise_grad(hash(X, Y, Z + 1, W + 1), fx, fy, fz - 1.0, fw - 1.0), + noise_grad(hash(X + 1, Y, Z + 1, W + 1), fx - 1.0, fy, fz - 1.0, fw - 1.0), + noise_grad(hash(X, Y + 1, Z + 1, W + 1), fx, fy - 1.0, fz - 1.0, fw - 1.0), + noise_grad(hash(X + 1, Y + 1, Z + 1, W + 1), fx - 1.0, fy - 1.0, fz - 1.0, fw - 1.0), + u, + v, + t, + s); + + return r; +} + +/* Signed versions of perlin noise in the range [-1, 1]. The scale values were computed + * experimentally by the OSL developers to remap the noise output to the correct range. */ + +float perlin_signed(float position) +{ + return perlin_noise(position) * 0.2500f; +} + +float perlin_signed(float2 position) +{ + return perlin_noise(position) * 0.6616f; +} + +float perlin_signed(float3 position) +{ + return perlin_noise(position) * 0.9820f; +} + +float perlin_signed(float4 position) +{ + return perlin_noise(position) * 0.8344f; +} + +/* Positive versions of perlin noise in the range [0, 1]. */ + +float perlin(float position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +float perlin(float2 position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +float perlin(float3 position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +float perlin(float4 position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +/* Positive fractal perlin noise. */ + +template<typename T> float perlin_fractal_template(T position, float octaves, float roughness) +{ + float fscale = 1.0f; + float amp = 1.0f; + float maxamp = 0.0f; + float sum = 0.0f; + octaves = CLAMPIS(octaves, 0.0f, 16.0f); + int n = static_cast<int>(octaves); + for (int i = 0; i <= n; i++) { + float t = perlin(fscale * position); + sum += t * amp; + maxamp += amp; + amp *= CLAMPIS(roughness, 0.0f, 1.0f); + fscale *= 2.0f; + } + float rmd = octaves - std::floor(octaves); + if (rmd == 0.0f) { + return sum / maxamp; + } + + float t = perlin(fscale * position); + float sum2 = sum + t * amp; + sum /= maxamp; + sum2 /= maxamp + amp; + return (1.0f - rmd) * sum + rmd * sum2; +} + +float perlin_fractal(float position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +float perlin_fractal(float2 position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +float perlin_fractal(float3 position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +float perlin_fractal(float4 position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +/* The following offset functions generate random offsets to be added to + * positions to act as a seed since the noise functions don't have seed values. + * The offset's components are in the range [100, 200], not too high to cause + * bad precision and not too small to be noticeable. We use float seed because + * OSL only support float hashes and we need to maintain compatibility with it. + */ + +BLI_INLINE float random_float_offset(float seed) +{ + return 100.0f + hash_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); +} + +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); +} + +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); +} + +/* Perlin noises to be added to the position to distort other noises. */ + +BLI_INLINE float perlin_distortion(float position, float strength) +{ + return perlin_signed(position + random_float_offset(0.0)) * strength; +} + +BLI_INLINE float2 perlin_distortion(float2 position, float strength) +{ + return float2(perlin_signed(position + random_float2_offset(0.0f)) * strength, + perlin_signed(position + random_float2_offset(1.0f)) * strength); +} + +BLI_INLINE float3 perlin_distortion(float3 position, float strength) +{ + return float3(perlin_signed(position + random_float3_offset(0.0f)) * strength, + perlin_signed(position + random_float3_offset(1.0f)) * strength, + perlin_signed(position + random_float3_offset(2.0f)) * strength); +} + +BLI_INLINE float4 perlin_distortion(float4 position, float strength) +{ + return float4(perlin_signed(position + random_float4_offset(0.0f)) * strength, + perlin_signed(position + random_float4_offset(1.0f)) * strength, + perlin_signed(position + random_float4_offset(2.0f)) * strength, + perlin_signed(position + random_float4_offset(3.0f)) * strength); +} + +/* Positive distorted fractal perlin noise. */ + +float perlin_fractal_distorted(float position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +float perlin_fractal_distorted(float2 position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +float perlin_fractal_distorted(float3 position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +float perlin_fractal_distorted(float4 position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +/* Positive distorted fractal perlin noise that outputs a float3. The arbitrary seeds are for + * compatibility with shading functions. */ + +float3 perlin_float3_fractal_distorted(float position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float_offset(1.0f), octaves, roughness), + perlin_fractal(position + random_float_offset(2.0f), octaves, roughness)); +} + +float3 perlin_float3_fractal_distorted(float2 position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float2_offset(2.0f), octaves, roughness), + perlin_fractal(position + random_float2_offset(3.0f), octaves, roughness)); +} + +float3 perlin_float3_fractal_distorted(float3 position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float3_offset(3.0f), octaves, roughness), + perlin_fractal(position + random_float3_offset(4.0f), octaves, roughness)); +} + +float3 perlin_float3_fractal_distorted(float4 position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float4_offset(4.0f), octaves, roughness), + perlin_fractal(position + random_float4_offset(5.0f), octaves, roughness)); +} + +} // namespace blender::noise diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c index fab5e44de25..0ea784c95b0 100644 --- a/source/blender/blenlib/intern/string.c +++ b/source/blender/blenlib/intern/string.c @@ -509,8 +509,12 @@ bool BLI_str_quoted_substr_range(const char *__restrict str, return true; } +/* NOTE(@campbellbarton): in principal it should be possible to access a quoted string + * with an arbitrary size, currently all callers for this functionality + * happened to use a fixed size buffer, so only #BLI_str_quoted_substr is needed. */ +#if 0 /** - * Makes a copy of the text within the "" that appear after some text `blahblah`. + * Makes a copy of the text within the "" that appear after the contents of \a prefix. * i.e. for string `pose["apples"]` with prefix `pose[`, it will return `apples`. * * \param str: is the entire string to chop. @@ -533,7 +537,21 @@ char *BLI_str_quoted_substrN(const char *__restrict str, const char *__restrict } return result; } +#endif +/** + * Fills \a result with text within "" that appear after some the contents of \a prefix. + * i.e. for string `pose["apples"]` with prefix `pose[`, it will return `apples`. + * + * \param str: is the entire string to chop. + * \param prefix: is the part of the string to step over. + * \param result: The buffer to fill. + * \param result_maxlen: The maximum size of the buffer (including nil terminator). + * \return True if the prefix was found and the entire quoted string was copied into result. + * + * Assume that the strings returned must be freed afterwards, + * and that the inputs will contain data we want. + */ bool BLI_str_quoted_substr(const char *__restrict str, const char *__restrict prefix, char *result, diff --git a/source/blender/blenlib/intern/uuid.cc b/source/blender/blenlib/intern/uuid.cc new file mode 100644 index 00000000000..f5edb356acc --- /dev/null +++ b/source/blender/blenlib/intern/uuid.cc @@ -0,0 +1,139 @@ +/* + * 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 bli + */ + +#include "BLI_uuid.h" + +#include <cstdio> +#include <cstring> +#include <ctime> +#include <random> +#include <string> + +/* Ensure the UUID struct doesn't have any padding, to be compatible with memcmp(). */ +static_assert(sizeof(UUID) == 16, "expect UUIDs to be 128 bit exactly"); + +UUID BLI_uuid_generate_random() +{ + static std::mt19937_64 rng = []() { + std::mt19937_64 rng; + + /* Ensure the RNG really can output 64-bit values. */ + static_assert(std::mt19937_64::min() == 0LL); + static_assert(std::mt19937_64::max() == 0xffffffffffffffffLL); + + struct timespec ts; +#ifdef __APPLE__ + /* `timespec_get()` is only available on macOS 10.15+, so until that's the minimum version + * supported by Blender, use another function to get the timespec. + * + * `clock_gettime()` is only available on POSIX, so not on Windows; Linux uses the newer C++11 + * function `timespec_get()` as well. */ + clock_gettime(CLOCK_REALTIME, &ts); +#else + timespec_get(&ts, TIME_UTC); +#endif + /* XOR the nanosecond and second fields, just in case the clock only has seconds resolution. */ + uint64_t seed = ts.tv_nsec; + seed ^= ts.tv_sec; + rng.seed(seed); + + return rng; + }(); + + UUID uuid; + + /* RFC4122 suggests setting certain bits to a fixed value, and then randomizing the remaining + * bits. The opposite is easier to implement, though, so that's what's done here. */ + + /* Read two 64-bit numbers to randomize all 128 bits of the UUID. */ + uint64_t *uuid_as_int64 = reinterpret_cast<uint64_t *>(&uuid); + uuid_as_int64[0] = rng(); + uuid_as_int64[1] = rng(); + + /* Set the most significant four bits to 0b0100 to indicate version 4 (random UUID). */ + uuid.time_hi_and_version &= ~0xF000; + uuid.time_hi_and_version |= 0x4000; + + /* Set the most significant two bits to 0b10 to indicate compatibility with RFC4122. */ + uuid.clock_seq_hi_and_reserved &= ~0x40; + uuid.clock_seq_hi_and_reserved |= 0x80; + + return uuid; +} + +UUID BLI_uuid_nil(void) +{ + const UUID nil = {0, 0, 0, 0, 0, 0}; + return nil; +} + +bool BLI_uuid_is_nil(UUID uuid) +{ + return BLI_uuid_equal(BLI_uuid_nil(), uuid); +} + +bool BLI_uuid_equal(const UUID uuid1, const UUID uuid2) +{ + return std::memcmp(&uuid1, &uuid2, sizeof(uuid1)) == 0; +} + +void BLI_uuid_format(char *buffer, const UUID uuid) +{ + std::sprintf(buffer, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.time_low, + uuid.time_mid, + uuid.time_hi_and_version, + uuid.clock_seq_hi_and_reserved, + uuid.clock_seq_low, + uuid.node[0], + uuid.node[1], + uuid.node[2], + uuid.node[3], + uuid.node[4], + uuid.node[5]); +} + +bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) +{ + const int num_fields_parsed = std::sscanf( + buffer, + "%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", + &uuid->time_low, + &uuid->time_mid, + &uuid->time_hi_and_version, + &uuid->clock_seq_hi_and_reserved, + &uuid->clock_seq_low, + &uuid->node[0], + &uuid->node[1], + &uuid->node[2], + &uuid->node[3], + &uuid->node[4], + &uuid->node[5]); + return num_fields_parsed == 11; +} + +std::ostream &operator<<(std::ostream &stream, UUID uuid) +{ + std::string buffer(36, '\0'); + BLI_uuid_format(buffer.data(), uuid); + stream << buffer; + return stream; +} diff --git a/source/blender/blenlib/tests/BLI_color_test.cc b/source/blender/blenlib/tests/BLI_color_test.cc index 14796e6bf71..a91c743b133 100644 --- a/source/blender/blenlib/tests/BLI_color_test.cc +++ b/source/blender/blenlib/tests/BLI_color_test.cc @@ -128,6 +128,6 @@ TEST(color, SceneLinearByteDecoding) EXPECT_NEAR(0.5f, decoded.a, 0.01f); } -/* \} */ +/** \} */ } // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_index_mask_test.cc b/source/blender/blenlib/tests/BLI_index_mask_test.cc index 4d6060e51c9..0778d71df01 100644 --- a/source/blender/blenlib/tests/BLI_index_mask_test.cc +++ b/source/blender/blenlib/tests/BLI_index_mask_test.cc @@ -40,4 +40,28 @@ TEST(index_mask, RangeConstructor) EXPECT_EQ(indices[2], 5); } +TEST(index_mask, SliceAndOffset) +{ + Vector<int64_t> indices; + { + IndexMask mask{IndexRange(10)}; + IndexMask new_mask = mask.slice_and_offset(IndexRange(3, 5), indices); + EXPECT_TRUE(new_mask.is_range()); + EXPECT_EQ(new_mask.size(), 5); + EXPECT_EQ(new_mask[0], 0); + EXPECT_EQ(new_mask[1], 1); + } + { + Vector<int64_t> original_indices = {2, 3, 5, 7, 8, 9, 10}; + IndexMask mask{original_indices.as_span()}; + IndexMask new_mask = mask.slice_and_offset(IndexRange(1, 4), indices); + EXPECT_FALSE(new_mask.is_range()); + EXPECT_EQ(new_mask.size(), 4); + EXPECT_EQ(new_mask[0], 0); + EXPECT_EQ(new_mask[1], 2); + EXPECT_EQ(new_mask[2], 4); + EXPECT_EQ(new_mask[3], 5); + } +} + } // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_span_test.cc b/source/blender/blenlib/tests/BLI_span_test.cc index 4d23a53c08a..fb88fb63e53 100644 --- a/source/blender/blenlib/tests/BLI_span_test.cc +++ b/source/blender/blenlib/tests/BLI_span_test.cc @@ -362,6 +362,29 @@ TEST(span, ReverseIterator) EXPECT_EQ_ARRAY(reversed_vec.data(), Span({7, 6, 5, 4}).data(), 4); } +TEST(span, ReverseMutableSpan) +{ + std::array<int, 0> src0 = {}; + MutableSpan<int> span0 = src0; + span0.reverse(); + EXPECT_EQ_ARRAY(span0.data(), Span<int>({}).data(), 0); + + std::array<int, 1> src1 = {4}; + MutableSpan<int> span1 = src1; + span1.reverse(); + EXPECT_EQ_ARRAY(span1.data(), Span<int>({4}).data(), 1); + + std::array<int, 2> src2 = {4, 5}; + MutableSpan<int> span2 = src2; + span2.reverse(); + EXPECT_EQ_ARRAY(span2.data(), Span<int>({5, 4}).data(), 2); + + std::array<int, 5> src5 = {4, 5, 6, 7, 8}; + MutableSpan<int> span5 = src5; + span5.reverse(); + EXPECT_EQ_ARRAY(span5.data(), Span<int>({8, 7, 6, 5, 4}).data(), 5); +} + TEST(span, MutableReverseIterator) { std::array<int, 4> src = {4, 5, 6, 7}; diff --git a/source/blender/blenlib/tests/BLI_uuid_test.cc b/source/blender/blenlib/tests/BLI_uuid_test.cc new file mode 100644 index 00000000000..31c69002c1c --- /dev/null +++ b/source/blender/blenlib/tests/BLI_uuid_test.cc @@ -0,0 +1,145 @@ +/* + * 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 "testing/testing.h" +#include <cstring> + +#include "BLI_uuid.h" + +TEST(BLI_uuid, generate_random) +{ + const UUID uuid = BLI_uuid_generate_random(); + + // The 4 MSbits represent the "version" of the UUID. + const uint16_t version = uuid.time_hi_and_version >> 12; + EXPECT_EQ(version, 4); + + // The 2 MSbits should be 0b10, indicating compliance with RFC4122. + const uint8_t reserved = uuid.clock_seq_hi_and_reserved >> 6; + EXPECT_EQ(reserved, 0b10); +} + +TEST(BLI_uuid, generate_many_random) +{ + const UUID first_uuid = BLI_uuid_generate_random(); + + /* Generate lots of UUIDs to get some indication that the randomness is okay. */ + for (int i = 0; i < 1000000; ++i) { + const UUID uuid = BLI_uuid_generate_random(); + EXPECT_FALSE(BLI_uuid_equal(first_uuid, uuid)); + + // Check that the non-random bits are set according to RFC4122. + const uint16_t version = uuid.time_hi_and_version >> 12; + EXPECT_EQ(version, 4); + const uint8_t reserved = uuid.clock_seq_hi_and_reserved >> 6; + EXPECT_EQ(reserved, 0b10); + } +} + +TEST(BLI_uuid, nil_value) +{ + const UUID nil_uuid = BLI_uuid_nil(); + const UUID zeroes_uuid = {0, 0, 0, 0, 0, 0}; + + EXPECT_TRUE(BLI_uuid_equal(nil_uuid, zeroes_uuid)); + EXPECT_TRUE(BLI_uuid_is_nil(nil_uuid)); + + std::string buffer(36, '\0'); + BLI_uuid_format(buffer.data(), nil_uuid); + EXPECT_EQ("00000000-0000-0000-0000-000000000000", buffer); +} + +TEST(BLI_uuid, equality) +{ + const UUID uuid1 = BLI_uuid_generate_random(); + const UUID uuid2 = BLI_uuid_generate_random(); + + EXPECT_TRUE(BLI_uuid_equal(uuid1, uuid1)); + EXPECT_FALSE(BLI_uuid_equal(uuid1, uuid2)); +} + +TEST(BLI_uuid, string_formatting) +{ + UUID uuid; + std::string buffer(36, '\0'); + + memset(&uuid, 0, sizeof(uuid)); + BLI_uuid_format(buffer.data(), uuid); + EXPECT_EQ("00000000-0000-0000-0000-000000000000", buffer); + + /* Demo of where the bits end up in the formatted string. */ + uuid.time_low = 1; + uuid.time_mid = 2; + uuid.time_hi_and_version = 3; + uuid.clock_seq_hi_and_reserved = 4; + uuid.clock_seq_low = 5; + uuid.node[0] = 6; + uuid.node[5] = 7; + BLI_uuid_format(buffer.data(), uuid); + EXPECT_EQ("00000001-0002-0003-0405-060000000007", buffer); + + /* Somewhat more complex bit patterns. This is a version 1 UUID generated from Python. */ + const UUID 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 UUID namespace_dns = { + 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); +} + +TEST(BLI_uuid, string_parsing_ok) +{ + UUID uuid; + std::string buffer(36, '\0'); + + const bool parsed_ok = BLI_uuid_parse_string(&uuid, "d30a0e60-14a2-11ec-8b99-f7736944db8b"); + EXPECT_TRUE(parsed_ok); + BLI_uuid_format(buffer.data(), uuid); + EXPECT_EQ("d30a0e60-14a2-11ec-8b99-f7736944db8b", buffer); +} + +TEST(BLI_uuid, string_parsing_capitalisation) +{ + UUID uuid; + std::string buffer(36, '\0'); + + /* RFC4122 demands acceptance of upper-case hex digits. */ + const bool parsed_ok = BLI_uuid_parse_string(&uuid, "D30A0E60-14A2-11EC-8B99-F7736944DB8B"); + EXPECT_TRUE(parsed_ok); + BLI_uuid_format(buffer.data(), uuid); + + /* Software should still output lower-case hex digits, though. */ + EXPECT_EQ("d30a0e60-14a2-11ec-8b99-f7736944db8b", buffer); +} + +TEST(BLI_uuid, string_parsing_fail) +{ + UUID uuid; + std::string buffer(36, '\0'); + + const bool parsed_ok = BLI_uuid_parse_string(&uuid, "d30a0e60!14a2-11ec-8b99-f7736944db8b"); + EXPECT_FALSE(parsed_ok); +} + +TEST(BLI_uuid, stream_operator) +{ + std::stringstream ss; + const UUID uuid = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; + ss << uuid; + EXPECT_EQ(ss.str(), "d30a0e60-14a2-11ec-8b99-f7736944db8b"); +} diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 3e9ea8db758..15653264211 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -4500,7 +4500,8 @@ static void add_loose_objects_to_scene(Main *mainvar, * 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; - if (do_it || ((ob->id.tag & LIB_TAG_INDIRECT) && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) { + if (do_it || + ((ob->id.tag & LIB_TAG_INDIRECT) != 0 && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) { if (do_append) { if (ob->id.us == 0) { do_it = true; @@ -4560,6 +4561,17 @@ static void add_loose_object_data_to_scene(Main *mainvar, active_collection = lc->collection; } + /* Do not re-instantiate obdata IDs that are already instantiated by an object. */ + LISTBASE_FOREACH (Object *, ob, &mainvar->objects) { + if ((ob->id.tag & LIB_TAG_PRE_EXISTING) == 0 && ob->data != NULL) { + ID *obdata = ob->data; + BLI_assert(ID_REAL_USERS(obdata) > 0); + if ((obdata->tag & LIB_TAG_PRE_EXISTING) == 0) { + obdata->tag &= ~LIB_TAG_DOIT; + } + } + } + /* Loop over all ID types, instancing object-data for ID types that have support for it. */ ListBase *lbarray[INDEX_ID_MAX]; int i = set_listbasepointers(mainvar, lbarray); @@ -4648,7 +4660,7 @@ static void add_collections_to_scene(Main *mainvar, LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { Object *ob = coll_ob->ob; if ((ob->id.tag & (LIB_TAG_PRE_EXISTING | LIB_TAG_DOIT | LIB_TAG_INDIRECT)) == 0 && - (ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == 0)) { + (ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == false)) { do_add_collection = true; break; } diff --git a/source/blender/blenloader/intern/versioning_260.c b/source/blender/blenloader/intern/versioning_260.c index b71dd5a27bb..55252210a78 100644 --- a/source/blender/blenloader/intern/versioning_260.c +++ b/source/blender/blenloader/intern/versioning_260.c @@ -1800,7 +1800,7 @@ void blo_do_versions_260(FileData *fd, Library *UNUSED(lib), Main *bmain) } case SPACE_SEQ: { SpaceSeq *sseq = (SpaceSeq *)sl; - sseq->flag |= SEQ_SHOW_GPENCIL; + sseq->flag |= SEQ_PREVIEW_SHOW_GPENCIL; break; } case SPACE_IMAGE: { diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 2598c53a5e0..f667361d166 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -3416,7 +3416,7 @@ 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_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_6 | + sfile->params->flag &= ~(FILE_APPEND_SET_FAKEUSER | FILE_APPEND_RECURSIVE | FILE_OBDATA_INSTANCE); } break; @@ -4966,7 +4966,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) for (SpaceLink *sl = area->spacedata.first; sl; sl = sl->next) { if (sl->spacetype == SPACE_SEQ) { SpaceSeq *sseq = (SpaceSeq *)sl; - sseq->flag |= SEQ_SHOW_FCURVES; + sseq->flag |= SEQ_TIMELINE_SHOW_FCURVES; } } } diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index b217850e119..bafba486c88 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -829,33 +829,6 @@ static void do_versions_strip_cache_settings_recursive(const ListBase *seqbase) } } -static void version_node_socket_name(bNodeTree *ntree, - const int node_type, - const char *old_name, - const char *new_name) -{ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == node_type) { - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - } - } -} - static void version_node_join_geometry_for_multi_input_socket(bNodeTree *ntree) { LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) { @@ -1119,8 +1092,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) if (md->type == eModifierType_MeshSequenceCache) { MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; mcmd->velocity_scale = 1.0f; - mcmd->vertex_velocities = NULL; - mcmd->num_vertices = 0; } } } @@ -1558,8 +1529,8 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { if (sl->spacetype == SPACE_SEQ) { SpaceSeq *sseq = (SpaceSeq *)sl; - sseq->flag |= (SEQ_SHOW_STRIP_OVERLAY | SEQ_SHOW_STRIP_NAME | SEQ_SHOW_STRIP_SOURCE | - SEQ_SHOW_STRIP_DURATION); + sseq->flag |= (SEQ_SHOW_OVERLAY | SEQ_TIMELINE_SHOW_STRIP_NAME | + SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_DURATION); } } } @@ -1586,7 +1557,7 @@ void blo_do_versions_290(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_ATTRIBUTE_MATH && node->storage == NULL) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_MATH && node->storage == NULL) { const int old_use_attibute_a = (1 << 0); const int old_use_attibute_b = (1 << 1); NodeAttributeMath *data = MEM_callocN(sizeof(NodeAttributeMath), "NodeAttributeMath"); @@ -1747,7 +1718,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) continue; } LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_POINT_INSTANCE && node->storage == NULL) { + if (node->type == GEO_NODE_LEGACY_POINT_INSTANCE && node->storage == NULL) { NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( sizeof(NodeGeometryPointInstance), __func__); data->instance_type = node->custom1; @@ -1764,7 +1735,7 @@ void blo_do_versions_290(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_ATTRIBUTE_MATH) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_MATH) { NodeAttributeMath *data = (NodeAttributeMath *)node->storage; data->input_type_c = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; } @@ -1823,7 +1794,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) continue; } LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_ATTRIBUTE_RANDOMIZE && node->storage == NULL) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE && node->storage == NULL) { NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN( sizeof(NodeAttributeRandomize), __func__); data->data_type = node->custom1; @@ -1859,7 +1830,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { - version_node_socket_name(ntree, GEO_NODE_ATTRIBUTE_PROXIMITY, "Result", "Distance"); + version_node_socket_name(ntree, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Result", "Distance"); } } FOREACH_NODETREE_END; @@ -1868,7 +1839,8 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) if (!MAIN_VERSION_ATLEAST(bmain, 293, 10)) { FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { - version_node_socket_name(ntree, GEO_NODE_ATTRIBUTE_PROXIMITY, "Location", "Position"); + version_node_socket_name( + ntree, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Location", "Position"); } } FOREACH_NODETREE_END; @@ -1962,7 +1934,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { if (ntree->type == NTREE_GEOMETRY) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_ATTRIBUTE_FILL) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_FILL) { node->custom2 = ATTR_DOMAIN_AUTO; } } diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 13577164f20..30e7c9bde4c 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -37,6 +37,8 @@ #include "DNA_constraint_types.h" #include "DNA_curve_types.h" #include "DNA_genfile.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_lineart_types.h" #include "DNA_listBase.h" #include "DNA_material_types.h" #include "DNA_modifier_types.h" @@ -237,6 +239,16 @@ static void do_versions_idproperty_bones_recursive(Bone *bone) } } +static void do_versions_idproperty_seq_recursive(ListBase *seqbase) +{ + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + version_idproperty_ui_data(seq->prop); + if (seq->type == SEQ_TYPE_META) { + do_versions_idproperty_seq_recursive(&seq->seqbase); + } + } +} + /** * For every data block that supports them, initialize the new IDProperty UI data struct based on * the old more complicated storage. Assumes only the top level of IDProperties below the parent @@ -297,9 +309,7 @@ static void do_versions_idproperty_ui_data(Main *bmain) /* Sequences. */ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { if (scene->ed != NULL) { - LISTBASE_FOREACH (Sequence *, seq, &scene->ed->seqbase) { - version_idproperty_ui_data(seq->prop); - } + do_versions_idproperty_seq_recursive(&scene->ed->seqbase); } } } @@ -446,7 +456,7 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) continue; } LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type != GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) { + if (node->type != GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE) { continue; } if (node->id == NULL) { @@ -526,33 +536,6 @@ static void version_switch_node_input_prefix(Main *bmain) FOREACH_NODETREE_END; } -static void version_node_socket_name(bNodeTree *ntree, - const int node_type, - const char *old_name, - const char *new_name) -{ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == node_type) { - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - } - } -} - static bool replace_bbone_len_scale_rnapath(char **p_old_path, int *p_index) { char *old_path = *p_old_path; @@ -657,6 +640,141 @@ static bNodeSocket *do_version_replace_float_size_with_vector(bNodeTree *ntree, return new_socket; } +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: + case GEO_NODE_MESH_PRIMITIVE_CIRCLE: + case GEO_NODE_MESH_PRIMITIVE_UV_SPHERE: + case GEO_NODE_MESH_PRIMITIVE_CYLINDER: + case GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE: + case GEO_NODE_MESH_PRIMITIVE_CONE: + case GEO_NODE_MESH_PRIMITIVE_LINE: + case GEO_NODE_MESH_PRIMITIVE_GRID: + case GEO_NODE_BOUNDING_BOX: + case GEO_NODE_CURVE_RESAMPLE: + case GEO_NODE_INPUT_MATERIAL: + case GEO_NODE_MATERIAL_REPLACE: + case GEO_NODE_CURVE_LENGTH: + case GEO_NODE_CONVEX_HULL: + case GEO_NODE_SEPARATE_COMPONENTS: + case GEO_NODE_CURVE_PRIMITIVE_STAR: + case GEO_NODE_CURVE_PRIMITIVE_SPIRAL: + case GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER: + case GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT: + case GEO_NODE_CURVE_PRIMITIVE_CIRCLE: + case GEO_NODE_VIEWER: + case GEO_NODE_CURVE_PRIMITIVE_LINE: + case GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL: + case GEO_NODE_CURVE_FILL: + case GEO_NODE_CURVE_TRIM: + case GEO_NODE_CURVE_TO_MESH: + return false; + + /* Not legacy: Newly added with fields patch. */ + case GEO_NODE_INPUT_POSITION: + case GEO_NODE_SET_POSITION: + case GEO_NODE_INPUT_INDEX: + case GEO_NODE_INPUT_NORMAL: + case GEO_NODE_ATTRIBUTE_CAPTURE: + return false; + + /* Maybe legacy: Might need special attribute handling, depending on design. */ + case GEO_NODE_SWITCH: + case GEO_NODE_JOIN_GEOMETRY: + case GEO_NODE_ATTRIBUTE_REMOVE: + case GEO_NODE_OBJECT_INFO: + 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. */ + case GEO_NODE_VOLUME_TO_MESH: + return false; + + /* Legacy: Attribute operation completely replaced by field nodes. */ + case GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE: + case GEO_NODE_LEGACY_ATTRIBUTE_MATH: + case GEO_NODE_LEGACY_ATTRIBUTE_FILL: + case GEO_NODE_LEGACY_ATTRIBUTE_MIX: + case GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP: + case GEO_NODE_LEGACY_ATTRIBUTE_COMPARE: + case GEO_NODE_LEGACY_POINT_ROTATE: + 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_CURVE_MAP: + case GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE: + case GEO_NODE_LECAGY_ATTRIBUTE_CLAMP: + case GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH: + case GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ: + case GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ: + return true; + + /* Legacy: Replaced by field node depending on another geometry. */ + case GEO_NODE_LEGACY_RAYCAST: + case GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER: + case GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY: + return true; + + /* Legacy: Simple selection attribute input. */ + case GEO_NODE_LEGACY_MESH_TO_CURVE: + case GEO_NODE_LEGACY_POINT_SEPARATE: + case GEO_NODE_LEGACY_CURVE_SELECT_HANDLES: + case GEO_NODE_LEGACY_CURVE_SPLINE_TYPE: + case GEO_NODE_LEGACY_CURVE_REVERSE: + case GEO_NODE_LEGACY_MATERIAL_ASSIGN: + 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. */ + return true; + } + return false; +} + +static void version_geometry_nodes_change_legacy_names(bNodeTree *ntree) +{ + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (geometry_node_is_293_legacy(node->type)) { + if (strstr(node->idname, "Legacy")) { + /* Make sure we haven't changed this idname already, better safe than sorry. */ + continue; + } + + char temp_idname[sizeof(node->idname)]; + BLI_strncpy(temp_idname, node->idname, sizeof(node->idname)); + + BLI_snprintf(node->idname, + sizeof(node->idname), + "GeometryNodeLegacy%s", + temp_idname + strlen("GeometryNode")); + } + } +} + /* NOLINTNEXTLINE: readability-function-size */ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) { @@ -958,7 +1076,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { if (sl->spacetype == SPACE_SEQ) { SpaceSeq *sseq = (SpaceSeq *)sl; - sseq->flag |= SEQ_SHOW_GRID; + sseq->flag |= SEQ_TIMELINE_SHOW_GRID; } } } @@ -1111,6 +1229,67 @@ 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) { + if (ob->type == OB_GPENCIL) { + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (md->type == eGpencilModifierType_Lineart) { + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + lmd->calculation_flags |= LRT_USE_CREASE_ON_SMOOTH_SURFACES; + } + } + } + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 23)) { + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_FILE) { + SpaceFile *sfile = (SpaceFile *)sl; + if (sfile->asset_params) { + sfile->asset_params->base_params.recursion_level = FILE_SELECT_MAX_RECURSIONS; + } + } + } + } + } + + 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; + int seq_show_safe_margins = (sseq->flag & SEQ_PREVIEW_SHOW_SAFE_MARGINS); + int seq_show_gpencil = (sseq->flag & SEQ_PREVIEW_SHOW_GPENCIL); + int seq_show_fcurves = (sseq->flag & SEQ_TIMELINE_SHOW_FCURVES); + int seq_show_safe_center = (sseq->flag & SEQ_PREVIEW_SHOW_SAFE_CENTER); + int seq_show_metadata = (sseq->flag & SEQ_PREVIEW_SHOW_METADATA); + int seq_show_strip_name = (sseq->flag & SEQ_TIMELINE_SHOW_STRIP_NAME); + int seq_show_strip_source = (sseq->flag & SEQ_TIMELINE_SHOW_STRIP_SOURCE); + int seq_show_strip_duration = (sseq->flag & SEQ_TIMELINE_SHOW_STRIP_DURATION); + int seq_show_grid = (sseq->flag & SEQ_TIMELINE_SHOW_GRID); + int show_strip_offset = (sseq->draw_flag & SEQ_TIMELINE_SHOW_STRIP_OFFSETS); + sseq->preview_overlay.flag = (seq_show_safe_margins | seq_show_gpencil | + seq_show_safe_center | seq_show_metadata); + sseq->timeline_overlay.flag = (seq_show_fcurves | seq_show_strip_name | + seq_show_strip_source | seq_show_strip_duration | + seq_show_grid | show_strip_offset); + } + } + } + } + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index 208c02b60d1..3f13d1ec12e 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -22,6 +22,7 @@ #include <cstring> +#include "DNA_node_types.h" #include "DNA_screen_types.h" #include "BLI_listbase.h" @@ -85,3 +86,30 @@ ID *do_versions_rename_id(Main *bmain, } return id; } + +void version_node_socket_name(bNodeTree *ntree, + const int node_type, + const char *old_name, + const char *new_name) +{ + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == node_type) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + if (STREQ(socket->name, old_name)) { + BLI_strncpy(socket->name, new_name, sizeof(socket->name)); + } + if (STREQ(socket->identifier, old_name)) { + BLI_strncpy(socket->identifier, new_name, sizeof(socket->name)); + } + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + if (STREQ(socket->name, old_name)) { + BLI_strncpy(socket->name, new_name, sizeof(socket->name)); + } + if (STREQ(socket->identifier, old_name)) { + BLI_strncpy(socket->identifier, new_name, sizeof(socket->name)); + } + } + } + } +} diff --git a/source/blender/blenloader/intern/versioning_common.h b/source/blender/blenloader/intern/versioning_common.h index 47e0b74a3e4..c1fe2b591cd 100644 --- a/source/blender/blenloader/intern/versioning_common.h +++ b/source/blender/blenloader/intern/versioning_common.h @@ -23,6 +23,7 @@ struct ARegion; struct ListBase; struct Main; +struct bNodeTree; #ifdef __cplusplus extern "C" { @@ -38,6 +39,11 @@ ID *do_versions_rename_id(Main *bmain, const char *name_src, const char *name_dst); +void version_node_socket_name(struct bNodeTree *ntree, + const int node_type, + const char *old_name, + const char *new_name); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 82c577d11a0..074cae669af 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -52,6 +52,7 @@ #include "BKE_brush.h" #include "BKE_colortools.h" #include "BKE_curveprofile.h" +#include "BKE_customdata.h" #include "BKE_gpencil.h" #include "BKE_layer.h" #include "BKE_lib_id.h" @@ -155,12 +156,10 @@ static void blo_update_defaults_screen(bScreen *screen, } else if (area->spacetype == SPACE_SEQ) { SpaceSeq *seq = area->spacedata.first; - seq->flag |= SEQ_SHOW_MARKERS | SEQ_SHOW_FCURVES | SEQ_ZOOM_TO_FIT | SEQ_SHOW_STRIP_OVERLAY | - SEQ_SHOW_STRIP_SOURCE | SEQ_SHOW_STRIP_NAME | SEQ_SHOW_STRIP_DURATION | - SEQ_SHOW_GRID; - + seq->flag |= SEQ_SHOW_MARKERS | SEQ_ZOOM_TO_FIT | SEQ_USE_PROXIES | SEQ_SHOW_OVERLAY; seq->render_size = SEQ_RENDER_SIZE_PROXY_100; - seq->flag |= SEQ_USE_PROXIES; + seq->timeline_overlay.flag |= SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_NAME | + SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID; } else if (area->spacetype == SPACE_TEXT) { /* Show syntax and line numbers in Script workspace text editor. */ @@ -552,6 +551,11 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) mesh->flag |= ME_REMESH_FIX_POLES | ME_REMESH_REPROJECT_VOLUME; BKE_mesh_smooth_flag_set(mesh, false); } + else { + /* Remove sculpt-mask data in default mesh objects for all non-sculpt templates. */ + CustomData_free_layers(&mesh->vdata, CD_PAINT_MASK, mesh->totvert); + CustomData_free_layers(&mesh->ldata, CD_GRID_PAINT_MASK, mesh->totloop); + } } for (Camera *camera = bmain->cameras.first; camera; camera = camera->id.next) { diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index 0042ff29dc2..f4853ff803f 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -885,6 +885,14 @@ void blo_do_versions_userdef(UserDef *userdef) BKE_addon_ensure(&userdef->addons, "pose_library"); } + if (!USER_VERSION_ATLEAST(300, 21)) { + /* Deprecated userdef->flag USER_SAVE_PREVIEWS */ + userdef->file_preview_type = (userdef->flag & USER_FLAG_UNUSED_5) ? USER_FILE_PREVIEW_AUTO : + USER_FILE_PREVIEW_NONE; + /* Clear for reuse. */ + userdef->flag &= ~USER_FLAG_UNUSED_5; + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/bmesh/intern/bmesh_opdefines.c b/source/blender/bmesh/intern/bmesh_opdefines.c index b63a09a97a6..7865c79323d 100644 --- a/source/blender/bmesh/intern/bmesh_opdefines.c +++ b/source/blender/bmesh/intern/bmesh_opdefines.c @@ -1587,7 +1587,7 @@ static BMOpDefine bmo_create_uvsphere_def = { /* slots_in */ {{"u_segments", BMO_OP_SLOT_INT}, /* number of u segments */ {"v_segments", BMO_OP_SLOT_INT}, /* number of v segment */ - {"diameter", BMO_OP_SLOT_FLT}, /* diameter */ + {"radius", BMO_OP_SLOT_FLT}, /* radius */ {"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */ {"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */ {{'\0'}}, @@ -1610,7 +1610,7 @@ static BMOpDefine bmo_create_icosphere_def = { "create_icosphere", /* slots_in */ {{"subdivisions", BMO_OP_SLOT_INT}, /* how many times to recursively subdivide the sphere */ - {"diameter", BMO_OP_SLOT_FLT}, /* diameter */ + {"radius", BMO_OP_SLOT_FLT}, /* radius */ {"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */ {"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */ {{'\0'}}, @@ -1656,8 +1656,8 @@ static BMOpDefine bmo_create_cone_def = { {{"cap_ends", BMO_OP_SLOT_BOOL}, /* whether or not to fill in the ends with faces */ {"cap_tris", BMO_OP_SLOT_BOOL}, /* fill ends with triangles instead of ngons */ {"segments", BMO_OP_SLOT_INT}, /* number of vertices in the base circle */ - {"diameter1", BMO_OP_SLOT_FLT}, /* diameter of one end */ - {"diameter2", BMO_OP_SLOT_FLT}, /* diameter of the opposite */ + {"radius1", BMO_OP_SLOT_FLT}, /* radius of one end */ + {"radius2", BMO_OP_SLOT_FLT}, /* radius of the opposite */ {"depth", BMO_OP_SLOT_FLT}, /* distance between ends */ {"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */ {"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */ diff --git a/source/blender/bmesh/intern/bmesh_query.c b/source/blender/bmesh/intern/bmesh_query.c index cb5764b1c91..795d8829ee7 100644 --- a/source/blender/bmesh/intern/bmesh_query.c +++ b/source/blender/bmesh/intern/bmesh_query.c @@ -2637,7 +2637,7 @@ int BM_mesh_calc_face_groups(BMesh *bm, STACK_DECLARE(stack); BMIter iter; - BMFace *f; + BMFace *f, *f_next; int i; STACK_INIT(group_array, bm->totface); @@ -2662,6 +2662,8 @@ int BM_mesh_calc_face_groups(BMesh *bm, /* detect groups */ stack = MEM_mallocN(sizeof(*stack) * tot_faces, __func__); + f_next = BM_iter_new(&iter, bm, BM_FACES_OF_MESH, NULL); + while (tot_touch != tot_faces) { int *group_item; bool ok = false; @@ -2670,10 +2672,10 @@ int BM_mesh_calc_face_groups(BMesh *bm, STACK_INIT(stack, tot_faces); - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_TAG) == false) { - BM_elem_flag_enable(f, BM_ELEM_TAG); - STACK_PUSH(stack, f); + for (; f_next; f_next = BM_iter_step(&iter)) { + if (BM_elem_flag_test(f_next, BM_ELEM_TAG) == false) { + BM_elem_flag_enable(f_next, BM_ELEM_TAG); + STACK_PUSH(stack, f_next); ok = true; break; } @@ -2799,9 +2801,8 @@ int BM_mesh_calc_edge_groups(BMesh *bm, STACK_DECLARE(stack); BMIter iter; - BMEdge *e; + BMEdge *e, *e_next; int i; - STACK_INIT(group_array, bm->totedge); /* init the array */ @@ -2822,6 +2823,8 @@ int BM_mesh_calc_edge_groups(BMesh *bm, /* detect groups */ stack = MEM_mallocN(sizeof(*stack) * tot_edges, __func__); + e_next = BM_iter_new(&iter, bm, BM_EDGES_OF_MESH, NULL); + while (tot_touch != tot_edges) { int *group_item; bool ok = false; @@ -2830,10 +2833,10 @@ int BM_mesh_calc_edge_groups(BMesh *bm, STACK_INIT(stack, tot_edges); - BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_TAG) == false) { - BM_elem_flag_enable(e, BM_ELEM_TAG); - STACK_PUSH(stack, e); + for (; e_next; e_next = BM_iter_step(&iter)) { + if (BM_elem_flag_test(e_next, BM_ELEM_TAG) == false) { + BM_elem_flag_enable(e_next, BM_ELEM_TAG); + STACK_PUSH(stack, e_next); ok = true; break; } diff --git a/source/blender/bmesh/operators/bmo_primitive.c b/source/blender/bmesh/operators/bmo_primitive.c index 8d5963cfb1c..d8047499780 100644 --- a/source/blender/bmesh/operators/bmo_primitive.c +++ b/source/blender/bmesh/operators/bmo_primitive.c @@ -854,7 +854,7 @@ void BM_mesh_calc_uvs_grid(BMesh *bm, void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) { - const float dia = BMO_slot_float_get(op->slots_in, "diameter"); + const float rad = BMO_slot_float_get(op->slots_in, "radius"); const int seg = BMO_slot_int_get(op->slots_in, "u_segments"); const int tot = BMO_slot_int_get(op->slots_in, "v_segments"); @@ -881,8 +881,8 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) const float phi = M_PI * ((double)a / (double)tot); vec[0] = 0.0; - vec[1] = dia * sinf(phi); - vec[2] = dia * cosf(phi); + vec[1] = rad * sinf(phi); + vec[2] = rad * cosf(phi); eve = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); BMO_vert_flag_enable(bm, eve, VERT_MARK); @@ -921,12 +921,12 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) { float len, len2, vec2[3]; - len = 2 * dia * sinf(phid / 2.0f); + len = 2 * rad * sinf(phid / 2.0f); /* Length of one segment in shortest parallel. */ - vec[0] = dia * sinf(phid); + vec[0] = rad * sinf(phid); vec[1] = 0.0f; - vec[2] = dia * cosf(phid); + vec[2] = rad * cosf(phid); mul_v3_m3v3(vec2, cmat, vec); len2 = len_v3v3(vec, vec2); @@ -973,8 +973,8 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op) { - const float dia = BMO_slot_float_get(op->slots_in, "diameter"); - const float dia_div = dia / 200.0f; + const float rad = BMO_slot_float_get(op->slots_in, "radius"); + const float rad_div = rad / 200.0f; const int subdiv = BMO_slot_int_get(op->slots_in, "subdivisions"); const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); @@ -994,9 +994,9 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op) /* phi = 0.25f * (float)M_PI; */ /* UNUSED */ for (a = 0; a < 12; a++) { - vec[0] = dia_div * icovert[a][0]; - vec[1] = dia_div * icovert[a][1]; - vec[2] = dia_div * icovert[a][2]; + vec[0] = rad_div * icovert[a][0]; + vec[1] = rad_div * icovert[a][1]; + vec[2] = rad_div * icovert[a][2]; eva[a] = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); BMO_vert_flag_enable(bm, eva[a], VERT_MARK); @@ -1041,7 +1041,7 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op) "cuts=%i " "use_grid_fill=%b use_sphere=%b", EDGE_MARK, - dia, + rad, (1 << (subdiv - 1)) - 1, true, true); @@ -1392,8 +1392,8 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) BMVert *v1, *v2, *lastv1 = NULL, *lastv2 = NULL, *cent1, *cent2, *firstv1, *firstv2; BMFace *f; float vec[3], mat[4][4]; - const float dia1 = BMO_slot_float_get(op->slots_in, "diameter1"); - const float dia2 = BMO_slot_float_get(op->slots_in, "diameter2"); + const float rad1 = BMO_slot_float_get(op->slots_in, "radius1"); + const float rad2 = BMO_slot_float_get(op->slots_in, "radius2"); const float depth_half = 0.5f * BMO_slot_float_get(op->slots_in, "depth"); int segs = BMO_slot_int_get(op->slots_in, "segments"); const bool cap_ends = BMO_slot_bool_get(op->slots_in, "cap_ends"); @@ -1431,15 +1431,14 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) for (int i = 0; i < segs; i++) { /* Calculate with doubles for higher precision, see: T87779. */ const float phi = (2.0 * M_PI) * ((double)i / (double)segs); - - vec[0] = dia1 * sinf(phi); - vec[1] = dia1 * cosf(phi); + vec[0] = rad1 * sinf(phi); + vec[1] = rad1 * cosf(phi); vec[2] = -depth_half; mul_m4_v3(mat, vec); v1 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); - vec[0] = dia2 * sinf(phi); - vec[1] = dia2 * cosf(phi); + vec[0] = rad2 * sinf(phi); + vec[1] = rad2 * cosf(phi); vec[2] = depth_half; mul_m4_v3(mat, vec); v2 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); @@ -1497,11 +1496,11 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) } if (calc_uvs) { - BM_mesh_calc_uvs_cone(bm, mat, dia2, dia1, segs, cap_ends, FACE_MARK, cd_loop_uv_offset); + BM_mesh_calc_uvs_cone(bm, mat, rad2, rad1, segs, cap_ends, FACE_MARK, cd_loop_uv_offset); } /* Collapse vertices at the first end. */ - if (dia1 == 0.0f) { + if (rad1 == 0.0f) { if (cap_ends) { BM_vert_kill(bm, cent1); } @@ -1513,7 +1512,7 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) } /* Collapse vertices at the second end. */ - if (dia2 == 0.0f) { + if (rad2 == 0.0f) { if (cap_ends) { BM_vert_kill(bm, cent2); } diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 8ddcf11602a..10e385e0187 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -317,6 +317,8 @@ set(SRC nodes/COM_FilterNode.h nodes/COM_InpaintNode.cc nodes/COM_InpaintNode.h + nodes/COM_PosterizeNode.cc + nodes/COM_PosterizeNode.h operations/COM_BlurBaseOperation.cc operations/COM_BlurBaseOperation.h @@ -346,6 +348,8 @@ set(SRC operations/COM_MovieClipAttributeOperation.h operations/COM_MovieDistortionOperation.cc operations/COM_MovieDistortionOperation.h + operations/COM_PosterizeOperation.cc + operations/COM_PosterizeOperation.h operations/COM_SMAAOperation.cc operations/COM_SMAAOperation.h operations/COM_VariableSizeBokehBlurOperation.cc @@ -647,6 +651,7 @@ if(WITH_GTESTS) tests/COM_BufferArea_test.cc tests/COM_BufferRange_test.cc tests/COM_BuffersIterator_test.cc + tests/COM_NodeOperation_test.cc ) set(TEST_INC ) diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index 1983eb190e2..4b103c21c75 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -90,6 +90,7 @@ #include "COM_OutputFileNode.h" #include "COM_PixelateNode.h" #include "COM_PlaneTrackDeformNode.h" +#include "COM_PosterizeNode.h" #include "COM_RenderLayersNode.h" #include "COM_RotateNode.h" #include "COM_ScaleNode.h" @@ -424,6 +425,9 @@ Node *COM_convert_bnode(bNode *b_node) case CMP_NODE_ANTIALIASING: node = new AntiAliasingNode(b_node); break; + case CMP_NODE_POSTERIZE: + node = new PosterizeNode(b_node); + break; } return node; } diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index a0333cf96cf..f2dcba65b7c 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -425,7 +425,8 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma } const bool has_execution_groups = system->getContext().get_execution_model() == - eExecutionModel::Tiled; + eExecutionModel::Tiled && + system->m_groups.size() > 0; len += graphviz_legend(str + len, maxlen > len ? maxlen - len : 0, has_execution_groups); len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "}\r\n"); @@ -467,11 +468,13 @@ static std::string get_operations_export_dir() void DebugInfo::export_operation(const NodeOperation *op, MemoryBuffer *render) { - ImBuf *ibuf = IMB_allocFromBuffer(nullptr, - render->getBuffer(), - render->getWidth(), - render->getHeight(), - render->get_num_channels()); + const int width = render->getWidth(); + const int height = render->getHeight(); + const int num_channels = render->get_num_channels(); + + ImBuf *ibuf = IMB_allocImBuf(width, height, 8 * num_channels, IB_rectfloat); + MemoryBuffer mem_ibuf(ibuf->rect_float, 4, width, height); + mem_ibuf.copy_from(render, render->get_rect(), 0, num_channels, 0); const std::string file_name = operation_class_name(op) + "_" + std::to_string(op->get_id()) + ".png"; diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index 1fbf502fea6..5327be50b53 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -32,7 +32,7 @@ BLI_assert((buf)->get_rect().ymax >= (y) + BLI_rcti_size_y(&(area))) #define ASSERT_VALID_ELEM_SIZE(buf, channel_offset, elem_size) \ - BLI_assert((buf)->get_num_channels() <= (channel_offset) + (elem_size)) + BLI_assert((buf)->get_num_channels() >= (channel_offset) + (elem_size)) namespace blender::compositor { diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index f3e15c2a495..f730d53acec 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -373,6 +373,12 @@ class MemoryBuffer { return this->m_buffer; } + float *release_ownership_buffer() + { + owns_data_ = false; + return this->m_buffer; + } + MemoryBuffer *inflate() const; inline void wrap_pixel(int &x, int &y, MemoryBufferExtend extend_x, MemoryBufferExtend extend_y) diff --git a/source/blender/compositor/intern/COM_NodeOperation.cc b/source/blender/compositor/intern/COM_NodeOperation.cc index 1b87cdf72fb..3bbd1b22d60 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.cc +++ b/source/blender/compositor/intern/COM_NodeOperation.cc @@ -41,6 +41,53 @@ NodeOperation::NodeOperation() this->m_btree = nullptr; } +/** + * Generate a hash that identifies the operation result in the current execution. + * Requires `hash_output_params` to be implemented, otherwise `std::nullopt` is returned. + * If the operation parameters or its linked inputs change, the hash must be re-generated. + */ +std::optional<NodeOperationHash> NodeOperation::generate_hash() +{ + params_hash_ = get_default_hash_2(m_width, m_height); + + /* Hash subclasses params. */ + is_hash_output_params_implemented_ = true; + hash_output_params(); + if (!is_hash_output_params_implemented_) { + return std::nullopt; + } + + hash_param(getOutputSocket()->getDataType()); + NodeOperationHash hash; + hash.params_hash_ = params_hash_; + + hash.parents_hash_ = 0; + for (NodeOperationInput &socket : m_inputs) { + if (!socket.isConnected()) { + continue; + } + + NodeOperation &input = socket.getLink()->getOperation(); + const bool is_constant = input.get_flags().is_constant_operation; + combine_hashes(hash.parents_hash_, get_default_hash(is_constant)); + if (is_constant) { + const float *elem = ((ConstantOperation *)&input)->get_constant_elem(); + const int num_channels = COM_data_type_num_channels(socket.getDataType()); + for (const int i : IndexRange(num_channels)) { + combine_hashes(hash.parents_hash_, get_default_hash(elem[i])); + } + } + else { + combine_hashes(hash.parents_hash_, get_default_hash(input.get_id())); + } + } + + hash.type_hash_ = typeid(*this).hash_code(); + hash.operation_ = this; + + return hash; +} + NodeOperationOutput *NodeOperation::getOutputSocket(unsigned int index) { return &m_outputs[index]; diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h index b402dc7f174..ef7cf319222 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.h +++ b/source/blender/compositor/intern/COM_NodeOperation.h @@ -22,6 +22,8 @@ #include <sstream> #include <string> +#include "BLI_ghash.h" +#include "BLI_hash.hh" #include "BLI_math_color.h" #include "BLI_math_vector.h" #include "BLI_threads.h" @@ -269,6 +271,42 @@ struct NodeOperationFlags { } }; +/** Hash that identifies an operation output result in the current execution. */ +struct NodeOperationHash { + private: + NodeOperation *operation_; + size_t type_hash_; + size_t parents_hash_; + size_t params_hash_; + + friend class NodeOperation; + + public: + NodeOperation *get_operation() const + { + return operation_; + } + + bool operator==(const NodeOperationHash &other) const + { + return type_hash_ == other.type_hash_ && parents_hash_ == other.parents_hash_ && + params_hash_ == other.params_hash_; + } + + bool operator!=(const NodeOperationHash &other) const + { + return !(*this == other); + } + + bool operator<(const NodeOperationHash &other) const + { + return type_hash_ < other.type_hash_ || + (type_hash_ == other.type_hash_ && parents_hash_ < other.parents_hash_) || + (type_hash_ == other.type_hash_ && parents_hash_ == other.parents_hash_ && + params_hash_ < other.params_hash_); + } +}; + /** * \brief NodeOperation contains calculation logic * @@ -282,6 +320,9 @@ class NodeOperation { Vector<NodeOperationInput> m_inputs; Vector<NodeOperationOutput> m_outputs; + size_t params_hash_; + bool is_hash_output_params_implemented_; + /** * \brief the index of the input socket that will be used to determine the resolution */ @@ -363,6 +404,8 @@ class NodeOperation { return flags; } + std::optional<NodeOperationHash> generate_hash(); + unsigned int getNumberOfInputSockets() const { return m_inputs.size(); @@ -624,6 +667,33 @@ class NodeOperation { protected: NodeOperation(); + /* Overridden by subclasses to allow merging equal operations on compiling. Implementations must + * hash any subclass parameter that affects the output result using `hash_params` methods. */ + virtual void hash_output_params() + { + is_hash_output_params_implemented_ = false; + } + + static void combine_hashes(size_t &combined, size_t other) + { + combined = BLI_ghashutil_combine_hash(combined, other); + } + + template<typename T> void hash_param(T param) + { + combine_hashes(params_hash_, get_default_hash(param)); + } + + template<typename T1, typename T2> void hash_params(T1 param1, T2 param2) + { + combine_hashes(params_hash_, get_default_hash_2(param1, param2)); + } + + template<typename T1, typename T2, typename T3> void hash_params(T1 param1, T2 param2, T3 param3) + { + combine_hashes(params_hash_, get_default_hash_3(param1, param2, param3)); + } + void addInputSocket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center); void addOutputSocket(DataType datatype); diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc index 10a91bbcd3e..b2cd76be2c3 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc @@ -101,16 +101,16 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) add_datatype_conversions(); if (m_context->get_execution_model() == eExecutionModel::FullFrame) { - /* Copy operations to system. Needed for graphviz. */ - system->set_operations(m_operations, {}); - - DebugInfo::graphviz(system, "compositor_prior_folding"); + save_graphviz("compositor_prior_folding"); ConstantFolder folder(*this); folder.fold_operations(); } determineResolutions(); + save_graphviz("compositor_prior_merging"); + merge_equal_operations(); + if (m_context->get_execution_model() == eExecutionModel::Tiled) { /* surround complex ops with read/write buffer */ add_complex_operation_buffers(); @@ -149,22 +149,28 @@ void NodeOperationBuilder::replace_operation_with_constant(NodeOperation *operat ConstantOperation *constant_operation) { BLI_assert(constant_operation->getNumberOfInputSockets() == 0); + unlink_inputs_and_relink_outputs(operation, constant_operation); + addOperation(constant_operation); +} + +void NodeOperationBuilder::unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op, + NodeOperation *linked_op) +{ int i = 0; while (i < m_links.size()) { Link &link = m_links[i]; - if (&link.to()->getOperation() == operation) { + if (&link.to()->getOperation() == unlinked_op) { link.to()->setLink(nullptr); m_links.remove(i); continue; } - if (&link.from()->getOperation() == operation) { - link.to()->setLink(constant_operation->getOutputSocket()); - m_links[i] = Link(constant_operation->getOutputSocket(), link.to()); + if (&link.from()->getOperation() == unlinked_op) { + link.to()->setLink(linked_op->getOutputSocket()); + m_links[i] = Link(linked_op->getOutputSocket(), link.to()); } i++; } - addOperation(constant_operation); } void NodeOperationBuilder::mapInputSocket(NodeInput *node_socket, @@ -456,6 +462,50 @@ void NodeOperationBuilder::determineResolutions() } } +static Vector<NodeOperationHash> generate_hashes(Span<NodeOperation *> operations) +{ + Vector<NodeOperationHash> hashes; + for (NodeOperation *op : operations) { + std::optional<NodeOperationHash> hash = op->generate_hash(); + if (hash) { + hashes.append(std::move(*hash)); + } + } + return hashes; +} + +/** Merge operations with same type, inputs and parameters that produce the same result. */ +void NodeOperationBuilder::merge_equal_operations() +{ + bool check_for_next_merge = true; + while (check_for_next_merge) { + /* Re-generate hashes with any change. */ + Vector<NodeOperationHash> hashes = generate_hashes(m_operations); + + /* Make hashes be consecutive when they are equal. */ + std::sort(hashes.begin(), hashes.end()); + + bool any_merged = false; + const NodeOperationHash *prev_hash = nullptr; + for (const NodeOperationHash &hash : hashes) { + if (prev_hash && *prev_hash == hash) { + merge_equal_operations(prev_hash->get_operation(), hash.get_operation()); + any_merged = true; + } + prev_hash = &hash; + } + + check_for_next_merge = any_merged; + } +} + +void NodeOperationBuilder::merge_equal_operations(NodeOperation *from, NodeOperation *into) +{ + unlink_inputs_and_relink_outputs(from, into); + m_operations.remove_first_occurrence_and_reorder(from); + delete from; +} + Vector<NodeOperationInput *> NodeOperationBuilder::cache_output_links( NodeOperationOutput *output) const { @@ -728,6 +778,14 @@ void NodeOperationBuilder::group_operations() } } +void NodeOperationBuilder::save_graphviz(StringRefNull name) +{ + if (COM_EXPORT_GRAPHVIZ) { + exec_system_->set_operations(m_operations, m_groups); + DebugInfo::graphviz(exec_system_, name); + } +} + /** Create a graphviz representation of the NodeOperationBuilder. */ std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder &builder) { diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.h b/source/blender/compositor/intern/COM_NodeOperationBuilder.h index 1f76765c846..aca4d043d41 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.h +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.h @@ -169,7 +169,10 @@ class NodeOperationBuilder { private: PreviewOperation *make_preview_operation() const; - + void unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op, NodeOperation *linked_op); + void merge_equal_operations(); + void merge_equal_operations(NodeOperation *from, NodeOperation *into); + void save_graphviz(StringRefNull name = ""); #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeCompilerImpl") #endif diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index 8e49bf34b51..a08f9dd284c 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -298,7 +298,7 @@ static void opencl_deinitialize() g_work_scheduler.opencl.initialized = false; } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Single threaded Scheduling @@ -310,7 +310,7 @@ static void threading_model_single_thread_execute(WorkPackage *package) device.execute(package); } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Queue Scheduling @@ -388,7 +388,7 @@ static void threading_model_queue_deinitialize() } } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Task Scheduling @@ -426,7 +426,7 @@ static void threading_model_task_stop() BLI_thread_local_delete(g_thread_device); } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Public API @@ -587,6 +587,6 @@ int WorkScheduler::current_thread_id() return device->thread_id(); } -/* \} */ +/** \} */ } // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_CryptomatteNode.cc b/source/blender/compositor/nodes/COM_CryptomatteNode.cc index 5835f051ce3..c04d98d6a2b 100644 --- a/source/blender/compositor/nodes/COM_CryptomatteNode.cc +++ b/source/blender/compositor/nodes/COM_CryptomatteNode.cc @@ -33,7 +33,8 @@ namespace blender::compositor { -/** \name Cryptomatte base +/* -------------------------------------------------------------------- */ +/** \name Cryptomatte Base * \{ */ void CryptomatteBaseNode::convertToOperations(NodeConverter &converter, @@ -73,10 +74,12 @@ void CryptomatteBaseNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(output_pick_socket, extract_pick_operation->getOutputSocket(0)); } -/* \} */ +/** \} */ +/* -------------------------------------------------------------------- */ /** \name Cryptomatte V2 * \{ */ + static std::string prefix_from_node(const CompositorContext &context, const bNode &node) { char prefix[MAX_NAME]; @@ -247,9 +250,10 @@ CryptomatteOperation *CryptomatteNode::create_cryptomatte_operation( return operation; } -/* \} */ +/** \} */ -/** \name Cryptomatte legacy +/* -------------------------------------------------------------------- */ +/** \name Cryptomatte Legacy * \{ */ CryptomatteOperation *CryptomatteLegacyNode::create_cryptomatte_operation( @@ -273,6 +277,6 @@ CryptomatteOperation *CryptomatteLegacyNode::create_cryptomatte_operation( return operation; } -/* \} */ +/** \} */ } // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_DenoiseNode.cc b/source/blender/compositor/nodes/COM_DenoiseNode.cc index e58a9c7ba9a..cc9328414ef 100644 --- a/source/blender/compositor/nodes/COM_DenoiseNode.cc +++ b/source/blender/compositor/nodes/COM_DenoiseNode.cc @@ -31,6 +31,12 @@ DenoiseNode::DenoiseNode(bNode *editorNode) : Node(editorNode) void DenoiseNode::convertToOperations(NodeConverter &converter, const CompositorContext & /*context*/) const { + if (!COM_is_denoise_supported()) { + converter.mapOutputSocket(getOutputSocket(0), + converter.addInputProxy(getInputSocket(0), false)); + return; + } + bNode *node = this->getbNode(); NodeDenoise *denoise = (NodeDenoise *)node->storage; @@ -39,8 +45,28 @@ void DenoiseNode::convertToOperations(NodeConverter &converter, operation->setDenoiseSettings(denoise); converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0)); - converter.mapInputSocket(getInputSocket(1), operation->getInputSocket(1)); - converter.mapInputSocket(getInputSocket(2), operation->getInputSocket(2)); + if (denoise && denoise->prefilter == CMP_NODE_DENOISE_PREFILTER_ACCURATE) { + { + DenoisePrefilterOperation *normal_prefilter = new DenoisePrefilterOperation( + DataType::Vector); + normal_prefilter->set_image_name("normal"); + converter.addOperation(normal_prefilter); + converter.mapInputSocket(getInputSocket(1), normal_prefilter->getInputSocket(0)); + converter.addLink(normal_prefilter->getOutputSocket(), operation->getInputSocket(1)); + } + { + DenoisePrefilterOperation *albedo_prefilter = new DenoisePrefilterOperation(DataType::Color); + albedo_prefilter->set_image_name("albedo"); + converter.addOperation(albedo_prefilter); + converter.mapInputSocket(getInputSocket(2), albedo_prefilter->getInputSocket(0)); + converter.addLink(albedo_prefilter->getOutputSocket(), operation->getInputSocket(2)); + } + } + else { + converter.mapInputSocket(getInputSocket(1), operation->getInputSocket(1)); + converter.mapInputSocket(getInputSocket(2), operation->getInputSocket(2)); + } + converter.mapOutputSocket(getOutputSocket(0), operation->getOutputSocket(0)); } diff --git a/source/blender/compositor/nodes/COM_PosterizeNode.cc b/source/blender/compositor/nodes/COM_PosterizeNode.cc new file mode 100644 index 00000000000..9f5a69961a4 --- /dev/null +++ b/source/blender/compositor/nodes/COM_PosterizeNode.cc @@ -0,0 +1,41 @@ +/* + * 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. + * + * Copyright 2020, Blender Foundation. + */ + +#include "COM_PosterizeNode.h" +#include "COM_ExecutionSystem.h" +#include "COM_PosterizeOperation.h" + +namespace blender::compositor { + +PosterizeNode::PosterizeNode(bNode *editorNode) : Node(editorNode) +{ + /* pass */ +} + +void PosterizeNode::convertToOperations(NodeConverter &converter, + const CompositorContext & /*context*/) const +{ + PosterizeOperation *operation = new PosterizeOperation(); + converter.addOperation(operation); + + converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0)); + converter.mapInputSocket(getInputSocket(1), operation->getInputSocket(1)); + converter.mapOutputSocket(getOutputSocket(0), operation->getOutputSocket(0)); +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_PosterizeNode.h b/source/blender/compositor/nodes/COM_PosterizeNode.h new file mode 100644 index 00000000000..bb9bef2bdd0 --- /dev/null +++ b/source/blender/compositor/nodes/COM_PosterizeNode.h @@ -0,0 +1,36 @@ +/* + * 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. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_Node.h" + +namespace blender::compositor { + +/** + * \brief PosterizeNode + * \ingroup Node + */ +class PosterizeNode : public Node { + public: + PosterizeNode(bNode *editorNode); + void convertToOperations(NodeConverter &converter, + const CompositorContext &context) const override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_AlphaOverPremultiplyOperation.cc b/source/blender/compositor/operations/COM_AlphaOverPremultiplyOperation.cc index a57e8c7f8a3..911e8d2df92 100644 --- a/source/blender/compositor/operations/COM_AlphaOverPremultiplyOperation.cc +++ b/source/blender/compositor/operations/COM_AlphaOverPremultiplyOperation.cc @@ -62,7 +62,8 @@ void AlphaOverPremultiplyOperation::update_memory_buffer_row(PixelCursor &p) const float *over_color = p.color2; const float value = *p.value; - if (over_color[3] <= 0.0f) { + /* Zero alpha values should still permit an add of RGB data. */ + if (over_color[3] < 0.0f) { copy_v4_v4(p.out, color1); } else if (value == 1.0f && over_color[3] >= 1.0f) { diff --git a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc index a9c58b55d73..405ba03abf3 100644 --- a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc +++ b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc @@ -116,4 +116,31 @@ void ConvertDepthToRadiusOperation::deinitExecution() this->m_inputOperation = nullptr; } +void ConvertDepthToRadiusOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float z = *it.in(0); + if (z == 0.0f) { + *it.out = 0.0f; + continue; + } + + const float inv_z = (1.0f / z); + + /* Bug T6656 part 2b, do not re-scale. */ +#if 0 + bcrad = 0.5f * fabs(aperture * (dof_sp * (cam_invfdist - iZ) - 1.0f)); + /* Scale crad back to original maximum and blend: + * `crad->rect[px] = bcrad + wts->rect[px] * (scf * crad->rect[px] - bcrad);` */ +#endif + const float radius = 0.5f * + fabsf(m_aperture * (m_dof_sp * (m_inverseFocalDistance - inv_z) - 1.0f)); + /* Bug T6615, limit minimum radius to 1 pixel, + * not really a solution, but somewhat mitigates the problem. */ + *it.out = CLAMPIS(radius, 0.0f, m_maxRadius); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h index 1f4e856b128..3d163843d06 100644 --- a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h +++ b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h @@ -19,7 +19,7 @@ #pragma once #include "COM_FastGaussianBlurOperation.h" -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_object_types.h" namespace blender::compositor { @@ -28,7 +28,7 @@ namespace blender::compositor { * this program converts an input color to an output value. * it assumes we are in sRGB color space. */ -class ConvertDepthToRadiusOperation : public NodeOperation { +class ConvertDepthToRadiusOperation : public MultiThreadedOperation { private: /** * Cached reference to the inputProgram @@ -83,6 +83,10 @@ class ConvertDepthToRadiusOperation : public NodeOperation { { this->m_blurPostOperation = operation; } + + 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_ConvertOperation.cc b/source/blender/compositor/operations/COM_ConvertOperation.cc index d377903efea..9a3733dda5b 100644 --- a/source/blender/compositor/operations/COM_ConvertOperation.cc +++ b/source/blender/compositor/operations/COM_ConvertOperation.cc @@ -40,6 +40,10 @@ void ConvertBaseOperation::deinitExecution() this->m_inputOperation = nullptr; } +void ConvertBaseOperation::hash_output_params() +{ +} + void ConvertBaseOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) @@ -269,6 +273,12 @@ void ConvertRGBToYCCOperation::executePixelSampled(float output[4], output[3] = inputColor[3]; } +void ConvertRGBToYCCOperation::hash_output_params() +{ + ConvertBaseOperation::hash_output_params(); + hash_param(m_mode); +} + void ConvertRGBToYCCOperation::update_memory_buffer_partial(BuffersIterator<float> &it) { for (; !it.is_end(); ++it) { @@ -327,6 +337,12 @@ void ConvertYCCToRGBOperation::executePixelSampled(float output[4], output[3] = inputColor[3]; } +void ConvertYCCToRGBOperation::hash_output_params() +{ + ConvertBaseOperation::hash_output_params(); + hash_param(m_mode); +} + void ConvertYCCToRGBOperation::update_memory_buffer_partial(BuffersIterator<float> &it) { for (; !it.is_end(); ++it) { diff --git a/source/blender/compositor/operations/COM_ConvertOperation.h b/source/blender/compositor/operations/COM_ConvertOperation.h index 0334959ae7e..72864b3c5e2 100644 --- a/source/blender/compositor/operations/COM_ConvertOperation.h +++ b/source/blender/compositor/operations/COM_ConvertOperation.h @@ -37,6 +37,7 @@ class ConvertBaseOperation : public MultiThreadedOperation { Span<MemoryBuffer *> inputs) final; protected: + virtual void hash_output_params() override; virtual void update_memory_buffer_partial(BuffersIterator<float> &it) = 0; }; @@ -124,6 +125,7 @@ class ConvertRGBToYCCOperation : public ConvertBaseOperation { void setMode(int mode); protected: + void hash_output_params() override; void update_memory_buffer_partial(BuffersIterator<float> &it) override; }; @@ -141,6 +143,7 @@ class ConvertYCCToRGBOperation : public ConvertBaseOperation { void setMode(int mode); protected: + void hash_output_params() override; void update_memory_buffer_partial(BuffersIterator<float> &it) override; }; diff --git a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc index 5ead300a368..9127a871b04 100644 --- a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc +++ b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc @@ -95,4 +95,81 @@ void ConvolutionEdgeFilterOperation::executePixel(float output[4], int x, int y, output[3] = MAX2(output[3], 0.0f); } +void ConvolutionEdgeFilterOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const int last_x = getWidth() - 1; + const int last_y = getHeight() - 1; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int left_offset = (it.x == 0) ? 0 : -image->elem_stride; + const int right_offset = (it.x == last_x) ? 0 : image->elem_stride; + const int down_offset = (it.y == 0) ? 0 : -image->row_stride; + const int up_offset = (it.y == last_y) ? 0 : image->row_stride; + + const float *center_color = it.in(IMAGE_INPUT_INDEX); + float res1[4] = {0}; + float res2[4] = {0}; + + const float *color = center_color + down_offset + left_offset; + madd_v3_v3fl(res1, color, m_filter[0]); + copy_v3_v3(res2, res1); + + color = center_color + down_offset; + madd_v3_v3fl(res1, color, m_filter[1]); + madd_v3_v3fl(res2, color, m_filter[3]); + + color = center_color + down_offset + right_offset; + madd_v3_v3fl(res1, color, m_filter[2]); + madd_v3_v3fl(res2, color, m_filter[6]); + + color = center_color + left_offset; + madd_v3_v3fl(res1, color, m_filter[3]); + madd_v3_v3fl(res2, color, m_filter[1]); + + { + float rgb_filtered[3]; + mul_v3_v3fl(rgb_filtered, center_color, m_filter[4]); + add_v3_v3(res1, rgb_filtered); + add_v3_v3(res2, rgb_filtered); + } + + color = center_color + right_offset; + madd_v3_v3fl(res1, color, m_filter[5]); + madd_v3_v3fl(res2, color, m_filter[7]); + + color = center_color + up_offset + left_offset; + madd_v3_v3fl(res1, color, m_filter[6]); + madd_v3_v3fl(res2, color, m_filter[2]); + + color = center_color + up_offset; + madd_v3_v3fl(res1, color, m_filter[7]); + madd_v3_v3fl(res2, color, m_filter[5]); + + { + color = center_color + up_offset + right_offset; + float rgb_filtered[3]; + mul_v3_v3fl(rgb_filtered, color, m_filter[8]); + add_v3_v3(res1, rgb_filtered); + add_v3_v3(res2, rgb_filtered); + } + + it.out[0] = sqrt(res1[0] * res1[0] + res2[0] * res2[0]); + it.out[1] = sqrt(res1[1] * res1[1] + res2[1] * res2[1]); + it.out[2] = sqrt(res1[2] * res1[2] + res2[2] * res2[2]); + + const float factor = *it.in(FACTOR_INPUT_INDEX); + const float m_factor = 1.0f - factor; + it.out[0] = it.out[0] * factor + center_color[0] * m_factor; + it.out[1] = it.out[1] * factor + center_color[1] * m_factor; + it.out[2] = it.out[2] * factor + center_color[2] * m_factor; + + it.out[3] = center_color[3]; + + /* Make sure we don't return negative color. */ + CLAMP4_MIN(it.out, 0.0f); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h index 319b424bd4a..bd38e27165a 100644 --- a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h +++ b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h @@ -25,6 +25,10 @@ namespace blender::compositor { class ConvolutionEdgeFilterOperation : public ConvolutionFilterOperation { public: void executePixel(float output[4], int x, int y, void *data) 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_ConvolutionFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc index 72cbbf4283a..11a077229fd 100644 --- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc +++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc @@ -127,4 +127,62 @@ bool ConvolutionFilterOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void ConvolutionFilterOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + switch (input_idx) { + case IMAGE_INPUT_INDEX: { + const int add_x = (m_filterWidth - 1) / 2 + 1; + const int add_y = (m_filterHeight - 1) / 2 + 1; + r_input_area.xmin = output_area.xmin - add_x; + r_input_area.xmax = output_area.xmax + add_x; + r_input_area.ymin = output_area.ymin - add_y; + r_input_area.ymax = output_area.ymax + add_y; + break; + } + case FACTOR_INPUT_INDEX: { + r_input_area = output_area; + break; + } + } +} + +void ConvolutionFilterOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const int last_x = getWidth() - 1; + const int last_y = getHeight() - 1; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int left_offset = (it.x == 0) ? 0 : -image->elem_stride; + const int right_offset = (it.x == last_x) ? 0 : image->elem_stride; + const int down_offset = (it.y == 0) ? 0 : -image->row_stride; + const int up_offset = (it.y == last_y) ? 0 : image->row_stride; + + const float *center_color = it.in(IMAGE_INPUT_INDEX); + zero_v4(it.out); + madd_v4_v4fl(it.out, center_color + down_offset + left_offset, m_filter[0]); + madd_v4_v4fl(it.out, center_color + down_offset, m_filter[1]); + madd_v4_v4fl(it.out, center_color + down_offset + right_offset, m_filter[2]); + madd_v4_v4fl(it.out, center_color + left_offset, m_filter[3]); + madd_v4_v4fl(it.out, center_color, m_filter[4]); + madd_v4_v4fl(it.out, center_color + right_offset, m_filter[5]); + madd_v4_v4fl(it.out, center_color + up_offset + left_offset, m_filter[6]); + madd_v4_v4fl(it.out, center_color + up_offset, m_filter[7]); + madd_v4_v4fl(it.out, center_color + up_offset + right_offset, m_filter[8]); + + const float factor = *it.in(FACTOR_INPUT_INDEX); + const float m_factor = 1.0f - factor; + it.out[0] = it.out[0] * factor + center_color[0] * m_factor; + it.out[1] = it.out[1] * factor + center_color[1] * m_factor; + it.out[2] = it.out[2] * factor + center_color[2] * m_factor; + it.out[3] = it.out[3] * factor + center_color[3] * m_factor; + + /* Make sure we don't return negative color. */ + CLAMP4_MIN(it.out, 0.0f); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h index 16dee502929..7e12c7faa5c 100644 --- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h +++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h @@ -18,11 +18,15 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class ConvolutionFilterOperation : public NodeOperation { +class ConvolutionFilterOperation : public MultiThreadedOperation { + protected: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int FACTOR_INPUT_INDEX = 1; + private: int m_filterWidth; int m_filterHeight; @@ -43,6 +47,11 @@ class ConvolutionFilterOperation : public NodeOperation { void initExecution() override; void deinitExecution() override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final; + virtual 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_DenoiseOperation.cc b/source/blender/compositor/operations/COM_DenoiseOperation.cc index ec11ad4d69a..0c660e0b723 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.cc +++ b/source/blender/compositor/operations/COM_DenoiseOperation.cc @@ -28,6 +28,137 @@ static pthread_mutex_t oidn_lock = BLI_MUTEX_INITIALIZER; namespace blender::compositor { +bool COM_is_denoise_supported() +{ +#ifdef WITH_OPENIMAGEDENOISE + /* Always supported through Accelerate framework BNNS on macOS. */ +# ifdef __APPLE__ + return true; +# else + return BLI_cpu_support_sse41(); +# endif + +#else + return false; +#endif +} + +class DenoiseFilter { + private: +#ifdef WITH_OPENIMAGEDENOISE + oidn::DeviceRef device; + oidn::FilterRef filter; +#endif + bool initialized_ = false; + + public: + ~DenoiseFilter() + { + BLI_assert(!initialized_); + } + +#ifdef WITH_OPENIMAGEDENOISE + void init_and_lock_denoiser(MemoryBuffer *output) + { + /* Since it's memory intensive, it's better to run only one instance of OIDN at a time. + * OpenImageDenoise is multithreaded internally and should use all available cores + * nonetheless. */ + BLI_mutex_lock(&oidn_lock); + + device = oidn::newDevice(); + device.commit(); + filter = device.newFilter("RT"); + initialized_ = true; + set_image("output", output); + } + + void deinit_and_unlock_denoiser() + { + BLI_mutex_unlock(&oidn_lock); + initialized_ = false; + } + + void set_image(const StringRef name, MemoryBuffer *buffer) + { + BLI_assert(initialized_); + BLI_assert(!buffer->is_a_single_elem()); + filter.setImage(name.data(), + buffer->getBuffer(), + oidn::Format::Float3, + buffer->getWidth(), + buffer->getHeight(), + 0, + buffer->get_elem_bytes_len()); + } + + template<typename T> void set(const StringRef option_name, T value) + { + BLI_assert(initialized_); + filter.set(option_name.data(), value); + } + + void execute() + { + BLI_assert(initialized_); + filter.commit(); + filter.execute(); + } + +#else + void init_and_lock_denoiser(MemoryBuffer *UNUSED(output)) + { + } + + void deinit_and_unlock_denoiser() + { + } + + void set_image(const StringRef UNUSED(name), MemoryBuffer *UNUSED(buffer)) + { + } + + template<typename T> void set(const StringRef UNUSED(option_name), T UNUSED(value)) + { + } + + void execute() + { + } +#endif +}; + +DenoiseBaseOperation::DenoiseBaseOperation() +{ + flags.is_fullframe_operation = true; + output_rendered_ = false; +} + +bool DenoiseBaseOperation::determineDependingAreaOfInterest(rcti * /*input*/, + ReadBufferOperation *readOperation, + rcti *output) +{ + if (isCached()) { + return false; + } + + rcti newInput; + newInput.xmax = this->getWidth(); + newInput.xmin = 0; + newInput.ymax = this->getHeight(); + newInput.ymin = 0; + return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); +} + +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(); +} + DenoiseOperation::DenoiseOperation() { this->addInputSocket(DataType::Color); @@ -52,6 +183,25 @@ void DenoiseOperation::deinitExecution() SingleThreadedOperation::deinitExecution(); } +static bool are_guiding_passes_noise_free(NodeDenoise *settings) +{ + switch (settings->prefilter) { + case CMP_NODE_DENOISE_PREFILTER_NONE: + case CMP_NODE_DENOISE_PREFILTER_ACCURATE: /* Prefiltered with #DenoisePrefilterOperation. */ + return true; + case CMP_NODE_DENOISE_PREFILTER_FAST: + default: + return false; + } +} + +void DenoiseOperation::hash_output_params() +{ + if (m_settings) { + hash_params((int)m_settings->hdr, are_guiding_passes_noise_free(m_settings)); + } +} + MemoryBuffer *DenoiseOperation::createMemoryBuffer(rcti *rect2) { MemoryBuffer *tileColor = (MemoryBuffer *)this->m_inputProgramColor->initializeTileData(rect2); @@ -63,109 +213,124 @@ MemoryBuffer *DenoiseOperation::createMemoryBuffer(rcti *rect2) rect.xmax = getWidth(); rect.ymax = getHeight(); MemoryBuffer *result = new MemoryBuffer(DataType::Color, rect); - float *data = result->getBuffer(); - this->generateDenoise(data, tileColor, tileNormal, tileAlbedo, this->m_settings); + this->generateDenoise(result, tileColor, tileNormal, tileAlbedo, this->m_settings); return result; } -bool DenoiseOperation::determineDependingAreaOfInterest(rcti * /*input*/, - ReadBufferOperation *readOperation, - rcti *output) +void DenoiseOperation::generateDenoise(MemoryBuffer *output, + MemoryBuffer *input_color, + MemoryBuffer *input_normal, + MemoryBuffer *input_albedo, + NodeDenoise *settings) { - if (isCached()) { - return false; + BLI_assert(input_color->getBuffer()); + if (!input_color->getBuffer()) { + return; } - rcti newInput; - newInput.xmax = this->getWidth(); - newInput.xmin = 0; - newInput.ymax = this->getHeight(); - newInput.ymin = 0; - return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); + BLI_assert(COM_is_denoise_supported()); + /* OpenImageDenoise needs full buffers. */ + MemoryBuffer *buf_color = input_color->is_a_single_elem() ? input_color->inflate() : input_color; + MemoryBuffer *buf_normal = input_normal && input_normal->is_a_single_elem() ? + input_normal->inflate() : + input_normal; + MemoryBuffer *buf_albedo = input_albedo && input_albedo->is_a_single_elem() ? + input_albedo->inflate() : + input_albedo; + + DenoiseFilter filter; + filter.init_and_lock_denoiser(output); + + filter.set_image("color", buf_color); + filter.set_image("normal", buf_normal); + filter.set_image("albedo", buf_albedo); + + BLI_assert(settings); + if (settings) { + filter.set("hdr", settings->hdr); + filter.set("srgb", false); + filter.set("cleanAux", are_guiding_passes_noise_free(settings)); + } + + filter.execute(); + filter.deinit_and_unlock_denoiser(); + + /* Copy the alpha channel, OpenImageDenoise currently only supports RGB. */ + output->copy_from(input_color, input_color->get_rect(), 3, COM_DATA_TYPE_VALUE_CHANNELS, 3); + + /* Delete inflated buffers. */ + if (input_color->is_a_single_elem()) { + delete buf_color; + } + if (input_normal && input_normal->is_a_single_elem()) { + delete buf_normal; + } + if (input_albedo && input_albedo->is_a_single_elem()) { + delete buf_albedo; + } } -void DenoiseOperation::generateDenoise(float *data, - MemoryBuffer *inputTileColor, - MemoryBuffer *inputTileNormal, - MemoryBuffer *inputTileAlbedo, - NodeDenoise *settings) +void DenoiseOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) { - float *inputBufferColor = inputTileColor->getBuffer(); - BLI_assert(inputBufferColor); - if (!inputBufferColor) { - return; + if (!output_rendered_) { + this->generateDenoise(output, inputs[0], inputs[1], inputs[2], m_settings); + output_rendered_ = true; } -#ifdef WITH_OPENIMAGEDENOISE - /* Always supported through Accelerate framework BNNS on macOS. */ -# ifndef __APPLE__ - if (BLI_cpu_support_sse41()) -# endif - { - /* Since it's memory intensive, it's better to run only one instance of OIDN at a time. - * OpenImageDenoise is multithreaded internally and should use all available cores nonetheless. - */ - BLI_mutex_lock(&oidn_lock); +} - oidn::DeviceRef device = oidn::newDevice(); - device.commit(); +DenoisePrefilterOperation::DenoisePrefilterOperation(DataType data_type) +{ + this->addInputSocket(data_type); + this->addOutputSocket(data_type); + image_name_ = ""; +} - oidn::FilterRef filter = device.newFilter("RT"); - filter.setImage("color", - inputBufferColor, - oidn::Format::Float3, - inputTileColor->getWidth(), - inputTileColor->getHeight(), - 0, - sizeof(float[4])); - if (inputTileNormal && inputTileNormal->getBuffer()) { - filter.setImage("normal", - inputTileNormal->getBuffer(), - oidn::Format::Float3, - inputTileNormal->getWidth(), - inputTileNormal->getHeight(), - 0, - sizeof(float[3])); - } - if (inputTileAlbedo && inputTileAlbedo->getBuffer()) { - filter.setImage("albedo", - inputTileAlbedo->getBuffer(), - oidn::Format::Float3, - inputTileAlbedo->getWidth(), - inputTileAlbedo->getHeight(), - 0, - sizeof(float[4])); - } - filter.setImage("output", - data, - oidn::Format::Float3, - inputTileColor->getWidth(), - inputTileColor->getHeight(), - 0, - sizeof(float[4])); +void DenoisePrefilterOperation::hash_output_params() +{ + hash_param(image_name_); +} + +MemoryBuffer *DenoisePrefilterOperation::createMemoryBuffer(rcti *rect2) +{ + MemoryBuffer *input = (MemoryBuffer *)this->get_input_operation(0)->initializeTileData(rect2); + rcti rect; + BLI_rcti_init(&rect, 0, getWidth(), 0, getHeight()); - BLI_assert(settings); - if (settings) { - filter.set("hdr", settings->hdr); - filter.set("srgb", false); - } + MemoryBuffer *result = new MemoryBuffer(getOutputSocket()->getDataType(), rect); + generate_denoise(result, input); - filter.commit(); - filter.execute(); - BLI_mutex_unlock(&oidn_lock); + return result; +} - /* copy the alpha channel, OpenImageDenoise currently only supports RGB */ - size_t numPixels = inputTileColor->getWidth() * inputTileColor->getHeight(); - for (size_t i = 0; i < numPixels; i++) { - data[i * 4 + 3] = inputBufferColor[i * 4 + 3]; - } - return; +void DenoisePrefilterOperation::generate_denoise(MemoryBuffer *output, MemoryBuffer *input) +{ + BLI_assert(COM_is_denoise_supported()); + + /* Denoising needs full buffers. */ + MemoryBuffer *input_buf = input->is_a_single_elem() ? input->inflate() : input; + + DenoiseFilter filter; + filter.init_and_lock_denoiser(output); + filter.set_image(image_name_, input_buf); + filter.execute(); + filter.deinit_and_unlock_denoiser(); + + /* Delete inflated buffers. */ + if (input->is_a_single_elem()) { + delete input_buf; + } +} + +void DenoisePrefilterOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) +{ + if (!output_rendered_) { + this->generate_denoise(output, inputs[0]); + output_rendered_ = true; } -#endif - /* If built without OIDN or running on an unsupported CPU, just pass through. */ - UNUSED_VARS(inputTileAlbedo, inputTileNormal, settings); - ::memcpy(data, - inputBufferColor, - sizeof(float[4]) * inputTileColor->getWidth() * inputTileColor->getHeight()); } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.h b/source/blender/compositor/operations/COM_DenoiseOperation.h index a9298c17e92..1b053b79c2d 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.h +++ b/source/blender/compositor/operations/COM_DenoiseOperation.h @@ -23,7 +23,24 @@ namespace blender::compositor { -class DenoiseOperation : public SingleThreadedOperation { +bool COM_is_denoise_supported(); + +class DenoiseBaseOperation : public SingleThreadedOperation { + protected: + bool output_rendered_; + + protected: + DenoiseBaseOperation(); + + public: + bool determineDependingAreaOfInterest(rcti *input, + ReadBufferOperation *readOperation, + rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; +}; + +class DenoiseOperation : public DenoiseBaseOperation { private: /** * \brief Cached reference to the input programs @@ -53,18 +70,44 @@ class DenoiseOperation : public SingleThreadedOperation { { this->m_settings = settings; } - bool determineDependingAreaOfInterest(rcti *input, - ReadBufferOperation *readOperation, - rcti *output) override; + + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; protected: - void generateDenoise(float *data, - MemoryBuffer *inputTileColor, - MemoryBuffer *inputTileNormal, - MemoryBuffer *inputTileAlbedo, + void hash_output_params() override; + void generateDenoise(MemoryBuffer *output, + MemoryBuffer *input_color, + MemoryBuffer *input_normal, + MemoryBuffer *input_albedo, NodeDenoise *settings); MemoryBuffer *createMemoryBuffer(rcti *rect) override; }; +class DenoisePrefilterOperation : public DenoiseBaseOperation { + private: + std::string image_name_; + + public: + DenoisePrefilterOperation(DataType data_type); + + void set_image_name(StringRef name) + { + image_name_ = name; + } + + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + + protected: + void hash_output_params() override; + MemoryBuffer *createMemoryBuffer(rcti *rect) override; + + private: + void generate_denoise(MemoryBuffer *output, MemoryBuffer *input); +}; + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.cc b/source/blender/compositor/operations/COM_DespeckleOperation.cc index fc8778c7d2e..19bd7b2af6f 100644 --- a/source/blender/compositor/operations/COM_DespeckleOperation.cc +++ b/source/blender/compositor/operations/COM_DespeckleOperation.cc @@ -127,6 +127,11 @@ void DespeckleOperation::executePixel(float output[4], int x, int y, void * /*da else { copy_v4_v4(output, color_org); } + +#undef TOT_DIV_ONE +#undef TOT_DIV_CNR +#undef WTOT +#undef COLOR_ADD } bool DespeckleOperation::determineDependingAreaOfInterest(rcti *input, @@ -144,4 +149,106 @@ bool DespeckleOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DespeckleOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + switch (input_idx) { + case IMAGE_INPUT_INDEX: { + const int add_x = 2; //(this->m_filterWidth - 1) / 2 + 1; + const int add_y = 2; //(this->m_filterHeight - 1) / 2 + 1; + r_input_area.xmin = output_area.xmin - add_x; + r_input_area.xmax = output_area.xmax + add_x; + r_input_area.ymin = output_area.ymin - add_y; + r_input_area.ymax = output_area.ymax + add_y; + break; + } + case FACTOR_INPUT_INDEX: { + r_input_area = output_area; + break; + } + } +} + +void DespeckleOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const int last_x = getWidth() - 1; + const int last_y = getHeight() - 1; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int x1 = MAX2(it.x - 1, 0); + const int x2 = it.x; + const int x3 = MIN2(it.x + 1, last_x); + const int y1 = MAX2(it.y - 1, 0); + const int y2 = it.y; + const int y3 = MIN2(it.y + 1, last_y); + + float w = 0.0f; + const float *color_org = it.in(IMAGE_INPUT_INDEX); + float color_mid[4]; + float color_mid_ok[4]; + const float *in1 = nullptr; + +#define TOT_DIV_ONE 1.0f +#define TOT_DIV_CNR (float)M_SQRT1_2 + +#define WTOT (TOT_DIV_ONE * 4 + TOT_DIV_CNR * 4) + +#define COLOR_ADD(fac) \ + { \ + madd_v4_v4fl(color_mid, in1, fac); \ + if (color_diff(in1, color_org, m_threshold)) { \ + w += fac; \ + madd_v4_v4fl(color_mid_ok, in1, fac); \ + } \ + } + + zero_v4(color_mid); + zero_v4(color_mid_ok); + + in1 = image->get_elem(x1, y1); + COLOR_ADD(TOT_DIV_CNR) + in1 = image->get_elem(x2, y1); + COLOR_ADD(TOT_DIV_ONE) + in1 = image->get_elem(x3, y1); + COLOR_ADD(TOT_DIV_CNR) + in1 = image->get_elem(x1, y2); + COLOR_ADD(TOT_DIV_ONE) + +#if 0 + const float* in2 = image->get_elem(x2, y2); + madd_v4_v4fl(color_mid, in2, this->m_filter[4]); +#endif + + in1 = image->get_elem(x3, y2); + COLOR_ADD(TOT_DIV_ONE) + in1 = image->get_elem(x1, y3); + COLOR_ADD(TOT_DIV_CNR) + in1 = image->get_elem(x2, y3); + COLOR_ADD(TOT_DIV_ONE) + in1 = image->get_elem(x3, y3); + COLOR_ADD(TOT_DIV_CNR) + + mul_v4_fl(color_mid, 1.0f / (4.0f + (4.0f * (float)M_SQRT1_2))); + // mul_v4_fl(color_mid, 1.0f / w); + + if ((w != 0.0f) && ((w / WTOT) > (m_threshold_neighbor)) && + color_diff(color_mid, color_org, m_threshold)) { + const float factor = *it.in(FACTOR_INPUT_INDEX); + mul_v4_fl(color_mid_ok, 1.0f / w); + interp_v4_v4v4(it.out, color_org, color_mid_ok, factor); + } + else { + copy_v4_v4(it.out, color_org); + } + +#undef TOT_DIV_ONE +#undef TOT_DIV_CNR +#undef WTOT +#undef COLOR_ADD + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.h b/source/blender/compositor/operations/COM_DespeckleOperation.h index e8d3461d2ec..70d6c2227f4 100644 --- a/source/blender/compositor/operations/COM_DespeckleOperation.h +++ b/source/blender/compositor/operations/COM_DespeckleOperation.h @@ -18,12 +18,15 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class DespeckleOperation : public NodeOperation { +class DespeckleOperation : public MultiThreadedOperation { private: + constexpr static int IMAGE_INPUT_INDEX = 0; + constexpr static int FACTOR_INPUT_INDEX = 1; + float m_threshold; float m_threshold_neighbor; @@ -52,6 +55,11 @@ class DespeckleOperation : public NodeOperation { void initExecution() override; void deinitExecution() 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; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.cc b/source/blender/compositor/operations/COM_DilateErodeOperation.cc index c459d09f02c..28b40021cd9 100644 --- a/source/blender/compositor/operations/COM_DilateErodeOperation.cc +++ b/source/blender/compositor/operations/COM_DilateErodeOperation.cc @@ -35,9 +35,9 @@ DilateErodeThresholdOperation::DilateErodeThresholdOperation() this->m__switch = 0.5f; this->m_distance = 0.0f; } -void DilateErodeThresholdOperation::initExecution() + +void DilateErodeThresholdOperation::init_data() { - this->m_inputProgram = this->getInputSocketReader(0); if (this->m_distance < 0.0f) { this->m_scope = -this->m_distance + this->m_inset; } @@ -54,6 +54,11 @@ void DilateErodeThresholdOperation::initExecution() } } +void DilateErodeThresholdOperation::initExecution() +{ + this->m_inputProgram = this->getInputSocketReader(0); +} + void *DilateErodeThresholdOperation::initializeTileData(rcti * /*rect*/) { void *buffer = this->m_inputProgram->initializeTileData(nullptr); @@ -160,6 +165,112 @@ bool DilateErodeThresholdOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DilateErodeThresholdOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = output_area.xmin - m_scope; + r_input_area.xmax = output_area.xmax + m_scope; + r_input_area.ymin = output_area.ymin - m_scope; + r_input_area.ymax = output_area.ymax + m_scope; +} + +struct DilateErodeThresholdOperation::PixelData { + int x; + int y; + int xmin; + int xmax; + int ymin; + int ymax; + const float *elem; + float distance; + int elem_stride; + int row_stride; + /** Switch. */ + float sw; +}; + +template<template<typename> typename TCompare> +static float get_min_distance(DilateErodeThresholdOperation::PixelData &p) +{ + /* TODO(manzanilla): bad performance, generate a table with relative offsets on operation + * initialization to loop from less to greater distance and break as soon as #compare is + * true. */ + const TCompare compare; + float min_dist = p.distance; + const float *row = p.elem + ((intptr_t)p.ymin - p.y) * p.row_stride + + ((intptr_t)p.xmin - p.x) * p.elem_stride; + for (int yi = p.ymin; yi < p.ymax; yi++) { + const float dy = yi - p.y; + const float dist_y = dy * dy; + const float *elem = row; + for (int xi = p.xmin; xi < p.xmax; xi++) { + if (compare(*elem, p.sw)) { + const float dx = xi - p.x; + const float dist = dx * dx + dist_y; + min_dist = MIN2(min_dist, dist); + } + elem += p.elem_stride; + } + row += p.row_stride; + } + return min_dist; +} + +void DilateErodeThresholdOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *input = inputs[0]; + const rcti &input_rect = input->get_rect(); + const float rd = m_scope * m_scope; + const float inset = m_inset; + + PixelData p; + p.sw = m__switch; + p.distance = rd * 2; + p.elem_stride = input->elem_stride; + p.row_stride = input->row_stride; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + p.x = it.x; + p.y = it.y; + p.xmin = MAX2(p.x - m_scope, input_rect.xmin); + p.ymin = MAX2(p.y - m_scope, input_rect.ymin); + p.xmax = MIN2(p.x + m_scope, input_rect.xmax); + p.ymax = MIN2(p.y + m_scope, input_rect.ymax); + p.elem = it.in(0); + + float pixel_value; + if (*p.elem > p.sw) { + pixel_value = -sqrtf(get_min_distance<std::less>(p)); + } + else { + pixel_value = sqrtf(get_min_distance<std::greater>(p)); + } + + if (m_distance > 0.0f) { + const float delta = m_distance - pixel_value; + if (delta >= 0.0f) { + *it.out = delta >= inset ? 1.0f : delta / inset; + } + else { + *it.out = 0.0f; + } + } + else { + const float delta = -m_distance + pixel_value; + if (delta < 0.0f) { + *it.out = delta < -inset ? 1.0f : (-delta) / inset; + } + else { + *it.out = 0.0f; + } + } + } +} + /* Dilate Distance. */ DilateDistanceOperation::DilateDistanceOperation() { @@ -170,15 +281,20 @@ DilateDistanceOperation::DilateDistanceOperation() flags.complex = true; flags.open_cl = true; } -void DilateDistanceOperation::initExecution() + +void DilateDistanceOperation::init_data() { - this->m_inputProgram = this->getInputSocketReader(0); this->m_scope = this->m_distance; if (this->m_scope < 3) { this->m_scope = 3; } } +void DilateDistanceOperation::initExecution() +{ + this->m_inputProgram = this->getInputSocketReader(0); +} + void *DilateDistanceOperation::initializeTileData(rcti * /*rect*/) { void *buffer = this->m_inputProgram->initializeTileData(nullptr); @@ -258,6 +374,92 @@ void DilateDistanceOperation::executeOpenCL(OpenCLDevice *device, device->COM_clEnqueueRange(dilateKernel, outputMemoryBuffer, 7, this); } +void DilateDistanceOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = output_area.xmin - m_scope; + r_input_area.xmax = output_area.xmax + m_scope; + r_input_area.ymin = output_area.ymin - m_scope; + r_input_area.ymax = output_area.ymax + m_scope; +} + +struct DilateDistanceOperation::PixelData { + int x; + int y; + int xmin; + int xmax; + int ymin; + int ymax; + const float *elem; + float min_distance; + int scope; + int elem_stride; + int row_stride; + const rcti &input_rect; + + PixelData(MemoryBuffer *input, const int distance, const int scope) + : min_distance(distance * distance), + scope(scope), + elem_stride(input->elem_stride), + row_stride(input->row_stride), + input_rect(input->get_rect()) + { + } + + void update(BuffersIterator<float> &it) + { + x = it.x; + y = it.y; + xmin = MAX2(x - scope, input_rect.xmin); + ymin = MAX2(y - scope, input_rect.ymin); + xmax = MIN2(x + scope, input_rect.xmax); + ymax = MIN2(y + scope, input_rect.ymax); + elem = it.in(0); + } +}; + +template<template<typename> typename TCompare> +static float get_distance_value(DilateDistanceOperation::PixelData &p, const float start_value) +{ + /* TODO(manzanilla): bad performance, only loop elements within minimum distance removing + * coordinates and conditional if `dist <= min_dist`. May need to generate a table of offsets. */ + const TCompare compare; + const float min_dist = p.min_distance; + float value = start_value; + const float *row = p.elem + ((intptr_t)p.ymin - p.y) * p.row_stride + + ((intptr_t)p.xmin - p.x) * p.elem_stride; + for (int yi = p.ymin; yi < p.ymax; yi++) { + const float dy = yi - p.y; + const float dist_y = dy * dy; + const float *elem = row; + for (int xi = p.xmin; xi < p.xmax; xi++) { + const float dx = xi - p.x; + const float dist = dx * dx + dist_y; + if (dist <= min_dist) { + value = compare(*elem, value) ? *elem : value; + } + elem += p.elem_stride; + } + row += p.row_stride; + } + + return value; +} + +void DilateDistanceOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + PixelData p(inputs[0], m_distance, m_scope); + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + p.update(it); + *it.out = get_distance_value<std::greater>(p, 0.0f); + } +} + /* Erode Distance */ ErodeDistanceOperation::ErodeDistanceOperation() : DilateDistanceOperation() { @@ -318,6 +520,17 @@ void ErodeDistanceOperation::executeOpenCL(OpenCLDevice *device, device->COM_clEnqueueRange(erodeKernel, outputMemoryBuffer, 7, this); } +void ErodeDistanceOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + PixelData p(inputs[0], m_distance, m_scope); + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + p.update(it); + *it.out = get_distance_value<std::less>(p, 1.0f); + } +} + /* Dilate step */ DilateStepOperation::DilateStepOperation() { @@ -475,6 +688,126 @@ bool DilateStepOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DilateStepOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = output_area.xmin - m_iterations; + r_input_area.xmax = output_area.xmax + m_iterations; + r_input_area.ymin = output_area.ymin - m_iterations; + r_input_area.ymax = output_area.ymax + m_iterations; +} + +template<typename TCompareSelector> +static void step_update_memory_buffer(MemoryBuffer *output, + const MemoryBuffer *input, + const rcti &area, + const int num_iterations, + const float compare_min_value) +{ + TCompareSelector selector; + + const int width = output->getWidth(); + const int height = output->getHeight(); + + const int half_window = num_iterations; + const int window = half_window * 2 + 1; + + const int xmin = MAX2(0, area.xmin - half_window); + const int ymin = MAX2(0, area.ymin - half_window); + const int xmax = MIN2(width, area.xmax + half_window); + const int ymax = MIN2(height, area.ymax + half_window); + + const int bwidth = area.xmax - area.xmin; + const int bheight = area.ymax - area.ymin; + + /* NOTE: #result has area width, but new height. + * We have to calculate the additional rows in the first pass, + * to have valid data available for the second pass. */ + rcti result_area; + BLI_rcti_init(&result_area, area.xmin, area.xmax, ymin, ymax); + MemoryBuffer result(DataType::Value, result_area); + + /* #temp holds maxima for every step in the algorithm, #buf holds a + * single row or column of input values, padded with #limit values to + * simplify the logic. */ + float *temp = (float *)MEM_mallocN(sizeof(float) * (2 * window - 1), "dilate erode temp"); + float *buf = (float *)MEM_mallocN(sizeof(float) * (MAX2(bwidth, bheight) + 5 * half_window), + "dilate erode buf"); + + /* The following is based on the van Herk/Gil-Werman algorithm for morphology operations. */ + /* First pass, horizontal dilate/erode. */ + for (int y = ymin; y < ymax; y++) { + for (int x = 0; x < bwidth + 5 * half_window; x++) { + buf[x] = compare_min_value; + } + for (int x = xmin; x < xmax; x++) { + buf[x - area.xmin + window - 1] = input->get_value(x, y, 0); + } + + for (int i = 0; i < (bwidth + 3 * half_window) / window; i++) { + int start = (i + 1) * window - 1; + + temp[window - 1] = buf[start]; + for (int x = 1; x < window; x++) { + temp[window - 1 - x] = selector(temp[window - x], buf[start - x]); + temp[window - 1 + x] = selector(temp[window + x - 2], buf[start + x]); + } + + start = half_window + (i - 1) * window + 1; + for (int x = -MIN2(0, start); x < window - MAX2(0, start + window - bwidth); x++) { + result.get_value(start + x + area.xmin, y, 0) = selector(temp[x], temp[x + window - 1]); + } + } + } + + /* Second pass, vertical dilate/erode. */ + for (int x = 0; x < bwidth; x++) { + for (int y = 0; y < bheight + 5 * half_window; y++) { + buf[y] = compare_min_value; + } + for (int y = ymin; y < ymax; y++) { + buf[y - area.ymin + window - 1] = result.get_value(x + area.xmin, y, 0); + } + + for (int i = 0; i < (bheight + 3 * half_window) / window; i++) { + int start = (i + 1) * window - 1; + + temp[window - 1] = buf[start]; + for (int y = 1; y < window; y++) { + temp[window - 1 - y] = selector(temp[window - y], buf[start - y]); + temp[window - 1 + y] = selector(temp[window + y - 2], buf[start + y]); + } + + 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]); + } + } + } + + MEM_freeN(temp); + MEM_freeN(buf); + + output->copy_from(&result, area); +} + +struct Max2Selector { + float operator()(float f1, float f2) const + { + return MAX2(f1, f2); + } +}; + +void DilateStepOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + step_update_memory_buffer<Max2Selector>(output, inputs[0], area, m_iterations, -FLT_MAX); +} + /* Erode step */ ErodeStepOperation::ErodeStepOperation() : DilateStepOperation() { @@ -571,4 +904,18 @@ void *ErodeStepOperation::initializeTileData(rcti *rect) return result; } +struct Min2Selector { + float operator()(float f1, float f2) const + { + return MIN2(f1, f2); + } +}; + +void ErodeStepOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + step_update_memory_buffer<Min2Selector>(output, inputs[0], area, m_iterations, FLT_MAX); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.h b/source/blender/compositor/operations/COM_DilateErodeOperation.h index a489e293e8e..9c32a5ac1fd 100644 --- a/source/blender/compositor/operations/COM_DilateErodeOperation.h +++ b/source/blender/compositor/operations/COM_DilateErodeOperation.h @@ -18,11 +18,14 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class DilateErodeThresholdOperation : public NodeOperation { +class DilateErodeThresholdOperation : public MultiThreadedOperation { + public: + struct PixelData; + private: /** * Cached reference to the inputProgram @@ -47,6 +50,7 @@ class DilateErodeThresholdOperation : public NodeOperation { */ void executePixel(float output[4], int x, int y, void *data) override; + void init_data() override; /** * Initialize the execution */ @@ -74,10 +78,17 @@ class DilateErodeThresholdOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) 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; }; -class DilateDistanceOperation : public NodeOperation { - private: +class DilateDistanceOperation : public MultiThreadedOperation { + public: + struct PixelData; + protected: /** * Cached reference to the inputProgram @@ -94,6 +105,7 @@ class DilateDistanceOperation : public NodeOperation { */ void executePixel(float output[4], int x, int y, void *data) override; + void init_data() override; /** * Initialize the execution */ @@ -119,7 +131,13 @@ class DilateDistanceOperation : public NodeOperation { MemoryBuffer **inputMemoryBuffers, std::list<cl_mem> *clMemToCleanUp, std::list<cl_kernel> *clKernelsToCleanUp) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final; + virtual void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; + class ErodeDistanceOperation : public DilateDistanceOperation { public: ErodeDistanceOperation(); @@ -135,9 +153,13 @@ class ErodeDistanceOperation : public DilateDistanceOperation { MemoryBuffer **inputMemoryBuffers, std::list<cl_mem> *clMemToCleanUp, std::list<cl_kernel> *clKernelsToCleanUp) override; + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; -class DilateStepOperation : public NodeOperation { +class DilateStepOperation : public MultiThreadedOperation { protected: /** * Cached reference to the inputProgram @@ -174,6 +196,11 @@ class DilateStepOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final; + virtual void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; class ErodeStepOperation : public DilateStepOperation { @@ -181,6 +208,9 @@ class ErodeStepOperation : public DilateStepOperation { ErodeStepOperation(); void *initializeTileData(rcti *rect) 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_DirectionalBlurOperation.cc b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc index 97bdc25af3b..102025ed915 100644 --- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc +++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc @@ -146,4 +146,58 @@ bool DirectionalBlurOperation::determineDependingAreaOfInterest(rcti * /*input*/ return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DirectionalBlurOperation::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 DirectionalBlurOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *input = inputs[0]; + const int iterations = pow(2.0f, this->m_data->iter); + for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { + const int x = it.x; + const int y = it.y; + float color_accum[4]; + input->read_elem_bilinear(x, y, color_accum); + + /* Blur pixel. */ + /* TODO(manzanilla): Many values used on iterations can be calculated beforehand. Create a + * table on operation initialization. */ + float ltx = this->m_tx; + float lty = this->m_ty; + float lsc = this->m_sc; + float lrot = this->m_rot; + for (int i = 0; i < iterations; i++) { + const float cs = cosf(lrot), ss = sinf(lrot); + const float isc = 1.0f / (1.0f + lsc); + + const float v = isc * (y - this->m_center_y_pix) + lty; + const float u = isc * (x - this->m_center_x_pix) + ltx; + + float color[4]; + input->read_elem_bilinear( + cs * u + ss * v + this->m_center_x_pix, cs * v - ss * u + this->m_center_y_pix, color); + add_v4_v4(color_accum, color); + + /* Double transformations. */ + ltx += this->m_tx; + lty += this->m_ty; + lrot += this->m_rot; + lsc += this->m_sc; + } + + mul_v4_v4fl(it.out, color_accum, 1.0f / (iterations + 1)); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DirectionalBlurOperation.h b/source/blender/compositor/operations/COM_DirectionalBlurOperation.h index 5555520462b..9a982bf6481 100644 --- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.h +++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.h @@ -18,12 +18,12 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "COM_QualityStepHelper.h" namespace blender::compositor { -class DirectionalBlurOperation : public NodeOperation, public QualityStepHelper { +class DirectionalBlurOperation : public MultiThreadedOperation, public QualityStepHelper { private: SocketReader *m_inputProgram; NodeDBlurData *m_data; @@ -65,6 +65,11 @@ class DirectionalBlurOperation : public NodeOperation, public QualityStepHelper MemoryBuffer **inputMemoryBuffers, std::list<cl_mem> *clMemToCleanUp, std::list<cl_kernel> *clKernelsToCleanUp) 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; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DotproductOperation.cc b/source/blender/compositor/operations/COM_DotproductOperation.cc index 07075ae1d9d..875b161e208 100644 --- a/source/blender/compositor/operations/COM_DotproductOperation.cc +++ b/source/blender/compositor/operations/COM_DotproductOperation.cc @@ -28,6 +28,7 @@ DotproductOperation::DotproductOperation() this->setResolutionInputSocketIndex(0); this->m_input1Operation = nullptr; this->m_input2Operation = nullptr; + flags.can_be_constant = true; } void DotproductOperation::initExecution() { @@ -55,4 +56,15 @@ void DotproductOperation::executePixelSampled(float output[4], output[0] = -(input1[0] * input2[0] + input1[1] * input2[1] + input1[2] * input2[2]); } +void DotproductOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float *input1 = it.in(0); + const float *input2 = it.in(1); + *it.out = -(input1[0] * input2[0] + input1[1] * input2[1] + input1[2] * input2[2]); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DotproductOperation.h b/source/blender/compositor/operations/COM_DotproductOperation.h index 728033bcf32..c3f39d43fff 100644 --- a/source/blender/compositor/operations/COM_DotproductOperation.h +++ b/source/blender/compositor/operations/COM_DotproductOperation.h @@ -18,11 +18,11 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class DotproductOperation : public NodeOperation { +class DotproductOperation : public MultiThreadedOperation { private: SocketReader *m_input1Operation; SocketReader *m_input2Operation; @@ -33,6 +33,10 @@ class DotproductOperation : public NodeOperation { void initExecution() override; void deinitExecution() 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_InpaintOperation.cc b/source/blender/compositor/operations/COM_InpaintOperation.cc index bfcd504177f..5e76c41752c 100644 --- a/source/blender/compositor/operations/COM_InpaintOperation.cc +++ b/source/blender/compositor/operations/COM_InpaintOperation.cc @@ -39,6 +39,7 @@ InpaintSimpleOperation::InpaintSimpleOperation() this->m_manhattan_distance = nullptr; this->m_cached_buffer = nullptr; this->m_cached_buffer_ready = false; + flags.is_fullframe_operation = true; } void InpaintSimpleOperation::initExecution() { @@ -286,4 +287,47 @@ bool InpaintSimpleOperation::determineDependingAreaOfInterest(rcti * /*input*/, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void InpaintSimpleOperation::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 InpaintSimpleOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + /* TODO(manzanilla): once tiled implementation is removed, run multi-threaded where possible. */ + MemoryBuffer *input = inputs[0]; + if (!m_cached_buffer_ready) { + if (input->is_a_single_elem()) { + MemoryBuffer *tmp = input->inflate(); + m_cached_buffer = tmp->release_ownership_buffer(); + delete tmp; + } + else { + m_cached_buffer = (float *)MEM_dupallocN(input->getBuffer()); + } + + this->calc_manhattan_distance(); + + int curr = 0; + int x, y; + while (this->next_pixel(x, y, curr, this->m_iterations)) { + this->pix_step(x, y); + } + m_cached_buffer_ready = true; + } + + const int num_channels = COM_data_type_num_channels(getOutputSocket()->getDataType()); + MemoryBuffer buf(m_cached_buffer, num_channels, input->getWidth(), input->getHeight()); + output->copy_from(&buf, area); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_InpaintOperation.h b/source/blender/compositor/operations/COM_InpaintOperation.h index e3d27bf7704..e11610bd263 100644 --- a/source/blender/compositor/operations/COM_InpaintOperation.h +++ b/source/blender/compositor/operations/COM_InpaintOperation.h @@ -66,6 +66,13 @@ class InpaintSimpleOperation : public NodeOperation { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) override; + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + private: void calc_manhattan_distance(); void clamp_xy(int &x, int &y); diff --git a/source/blender/compositor/operations/COM_MapRangeOperation.cc b/source/blender/compositor/operations/COM_MapRangeOperation.cc index ada3cd6f159..82fb033bf24 100644 --- a/source/blender/compositor/operations/COM_MapRangeOperation.cc +++ b/source/blender/compositor/operations/COM_MapRangeOperation.cc @@ -30,6 +30,7 @@ MapRangeOperation::MapRangeOperation() this->addOutputSocket(DataType::Value); this->m_inputOperation = nullptr; this->m_useClamp = false; + flags.can_be_constant = true; } void MapRangeOperation::initExecution() @@ -104,4 +105,43 @@ void MapRangeOperation::deinitExecution() this->m_destMaxOperation = nullptr; } +void MapRangeOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float source_min = *it.in(1); + const float source_max = *it.in(2); + if (fabsf(source_max - source_min) < 1e-6f) { + it.out[0] = 0.0f; + continue; + } + + float value = *it.in(0); + const float dest_min = *it.in(3); + const float dest_max = *it.in(4); + if (value >= -BLENDER_ZMAX && value <= BLENDER_ZMAX) { + value = (value - source_min) / (source_max - source_min); + value = dest_min + value * (dest_max - dest_min); + } + else if (value > BLENDER_ZMAX) { + value = dest_max; + } + else { + value = dest_min; + } + + if (m_useClamp) { + if (dest_max > dest_min) { + CLAMP(value, dest_min, dest_max); + } + else { + CLAMP(value, dest_max, dest_min); + } + } + + it.out[0] = value; + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_MapRangeOperation.h b/source/blender/compositor/operations/COM_MapRangeOperation.h index a544c59887e..a01be14d528 100644 --- a/source/blender/compositor/operations/COM_MapRangeOperation.h +++ b/source/blender/compositor/operations/COM_MapRangeOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_texture_types.h" namespace blender::compositor { @@ -27,7 +27,7 @@ namespace blender::compositor { * this program converts an input color to an output value. * it assumes we are in sRGB color space. */ -class MapRangeOperation : public NodeOperation { +class MapRangeOperation : public MultiThreadedOperation { private: /** * Cached reference to the inputProgram @@ -68,6 +68,10 @@ class MapRangeOperation : public NodeOperation { { this->m_useClamp = value; } + + 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_MapValueOperation.cc b/source/blender/compositor/operations/COM_MapValueOperation.cc index 03fa80d220d..94fecc3f49e 100644 --- a/source/blender/compositor/operations/COM_MapValueOperation.cc +++ b/source/blender/compositor/operations/COM_MapValueOperation.cc @@ -25,6 +25,7 @@ MapValueOperation::MapValueOperation() this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Value); this->m_inputOperation = nullptr; + flags.can_be_constant = true; } void MapValueOperation::initExecution() @@ -60,4 +61,27 @@ void MapValueOperation::deinitExecution() this->m_inputOperation = nullptr; } +void MapValueOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float input = *it.in(0); + TexMapping *texmap = this->m_settings; + float value = (input + texmap->loc[0]) * texmap->size[0]; + if (texmap->flag & TEXMAP_CLIP_MIN) { + if (value < texmap->min[0]) { + value = texmap->min[0]; + } + } + if (texmap->flag & TEXMAP_CLIP_MAX) { + if (value > texmap->max[0]) { + value = texmap->max[0]; + } + } + + it.out[0] = value; + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_MapValueOperation.h b/source/blender/compositor/operations/COM_MapValueOperation.h index eb7714714e9..a595eac3155 100644 --- a/source/blender/compositor/operations/COM_MapValueOperation.h +++ b/source/blender/compositor/operations/COM_MapValueOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_texture_types.h" namespace blender::compositor { @@ -27,7 +27,7 @@ namespace blender::compositor { * this program converts an input color to an output value. * it assumes we are in sRGB color space. */ -class MapValueOperation : public NodeOperation { +class MapValueOperation : public MultiThreadedOperation { private: /** * Cached reference to the inputProgram @@ -63,6 +63,10 @@ class MapValueOperation : public NodeOperation { { this->m_settings = settings; } + + 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_NormalizeOperation.cc b/source/blender/compositor/operations/COM_NormalizeOperation.cc index f93afcaab95..c3e72d2575f 100644 --- a/source/blender/compositor/operations/COM_NormalizeOperation.cc +++ b/source/blender/compositor/operations/COM_NormalizeOperation.cc @@ -27,6 +27,7 @@ NormalizeOperation::NormalizeOperation() this->m_imageReader = nullptr; this->m_cachedInstance = nullptr; this->flags.complex = true; + flags.can_be_constant = true; } void NormalizeOperation::initExecution() { @@ -56,6 +57,7 @@ void NormalizeOperation::deinitExecution() { this->m_imageReader = nullptr; delete this->m_cachedInstance; + m_cachedInstance = nullptr; NodeOperation::deinitMutex(); } @@ -127,4 +129,60 @@ void NormalizeOperation::deinitializeTileData(rcti * /*rect*/, void * /*data*/) /* pass */ } +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(); +} + +void NormalizeOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) +{ + if (m_cachedInstance == nullptr) { + MemoryBuffer *input = inputs[0]; + + /* Using generic two floats struct to store `x: min`, `y: multiply`. */ + NodeTwoFloats *minmult = new NodeTwoFloats(); + + float minv = 1.0f + BLENDER_ZMAX; + float maxv = -1.0f - BLENDER_ZMAX; + for (const float *elem : input->as_range()) { + const float value = *elem; + if ((value > maxv) && (value <= BLENDER_ZMAX)) { + maxv = value; + } + if ((value < minv) && (value >= -BLENDER_ZMAX)) { + minv = value; + } + } + + minmult->x = minv; + /* The case of a flat buffer would cause a divide by 0. */ + minmult->y = ((maxv != minv) ? 1.0f / (maxv - minv) : 0.0f); + + m_cachedInstance = minmult; + } +} + +void NormalizeOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + NodeTwoFloats *minmult = m_cachedInstance; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float input_value = *it.in(0); + + *it.out = (input_value - minmult->x) * minmult->y; + + /* Clamp infinities. */ + CLAMP(*it.out, 0.0f, 1.0f); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_NormalizeOperation.h b/source/blender/compositor/operations/COM_NormalizeOperation.h index c89ba372189..7af2aad8a88 100644 --- a/source/blender/compositor/operations/COM_NormalizeOperation.h +++ b/source/blender/compositor/operations/COM_NormalizeOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_node_types.h" namespace blender::compositor { @@ -27,7 +27,7 @@ namespace blender::compositor { * \brief base class of normalize, implementing the simple normalize * \ingroup operation */ -class NormalizeOperation : public NodeOperation { +class NormalizeOperation : public MultiThreadedOperation { protected: /** * \brief Cached reference to the reader @@ -64,6 +64,14 @@ class NormalizeOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_started(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) 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_PosterizeOperation.cc b/source/blender/compositor/operations/COM_PosterizeOperation.cc new file mode 100644 index 00000000000..db5860f48f8 --- /dev/null +++ b/source/blender/compositor/operations/COM_PosterizeOperation.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. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_PosterizeOperation.h" + +namespace blender::compositor { + +PosterizeOperation::PosterizeOperation() +{ + this->addInputSocket(DataType::Color); + this->addInputSocket(DataType::Value); + this->addOutputSocket(DataType::Color); + this->m_inputProgram = nullptr; + this->m_inputStepsProgram = nullptr; + flags.can_be_constant = true; +} + +void PosterizeOperation::initExecution() +{ + this->m_inputProgram = this->getInputSocketReader(0); + this->m_inputStepsProgram = this->getInputSocketReader(1); +} + +void PosterizeOperation::executePixelSampled(float output[4], + float x, + float y, + PixelSampler sampler) +{ + float inputValue[4]; + float inputSteps[4]; + + this->m_inputProgram->readSampled(inputValue, x, y, sampler); + this->m_inputStepsProgram->readSampled(inputSteps, x, y, sampler); + CLAMP(inputSteps[0], 2.0f, 1024.0f); + const float steps_inv = 1.0f / inputSteps[0]; + + output[0] = floor(inputValue[0] / steps_inv) * steps_inv; + output[1] = floor(inputValue[1] / steps_inv) * steps_inv; + output[2] = floor(inputValue[2] / steps_inv) * steps_inv; + output[3] = inputValue[3]; +} + +void PosterizeOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float *in_value = it.in(0); + const float *in_steps = it.in(1); + float steps = in_steps[0]; + CLAMP(steps, 2.0f, 1024.0f); + const float steps_inv = 1.0f / steps; + + it.out[0] = floor(in_value[0] / steps_inv) * steps_inv; + it.out[1] = floor(in_value[1] / steps_inv) * steps_inv; + it.out[2] = floor(in_value[2] / steps_inv) * steps_inv; + it.out[3] = in_value[3]; + } +} + +void PosterizeOperation::deinitExecution() +{ + this->m_inputProgram = nullptr; + this->m_inputStepsProgram = nullptr; +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PosterizeOperation.h b/source/blender/compositor/operations/COM_PosterizeOperation.h new file mode 100644 index 00000000000..c625cbb83c6 --- /dev/null +++ b/source/blender/compositor/operations/COM_PosterizeOperation.h @@ -0,0 +1,56 @@ +/* + * 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. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_MultiThreadedOperation.h" + +namespace blender::compositor { + +class PosterizeOperation : public MultiThreadedOperation { + private: + /** + * Cached reference to the inputProgram + */ + SocketReader *m_inputProgram; + SocketReader *m_inputStepsProgram; + + public: + PosterizeOperation(); + + /** + * The inner loop of this operation. + */ + void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + /** + * Initialize the execution + */ + void initExecution() override; + + /** + * Deinitialize the execution + */ + void deinitExecution() 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_PreviewOperation.cc b/source/blender/compositor/operations/COM_PreviewOperation.cc index e7c11613aa3..fa8b5ffcabf 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.cc +++ b/source/blender/compositor/operations/COM_PreviewOperation.cc @@ -171,4 +171,43 @@ eCompositorPriority PreviewOperation::getRenderPriority() const return eCompositorPriority::Low; } +void PreviewOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + + r_input_area.xmin = output_area.xmin / m_divider; + r_input_area.xmax = output_area.xmax / m_divider; + r_input_area.ymin = output_area.ymin / m_divider; + r_input_area.ymax = output_area.ymax / m_divider; +} + +void PreviewOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(output), + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + MemoryBuffer *input = inputs[0]; + struct ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_new( + m_viewSettings, m_displaySettings); + + rcti buffer_area; + BLI_rcti_init(&buffer_area, 0, this->getWidth(), 0, this->getHeight()); + BuffersIteratorBuilder<uchar> it_builder( + m_outputBuffer, buffer_area, area, COM_data_type_num_channels(DataType::Color)); + + for (BuffersIterator<uchar> it = it_builder.build(); !it.is_end(); ++it) { + const float rx = it.x / m_divider; + const float ry = it.y / m_divider; + + float color[4]; + input->read_elem_checked(rx, ry, color); + IMB_colormanagement_processor_apply_v4(cm_processor, color); + rgba_float_to_uchar(it.out, color); + } + + IMB_colormanagement_processor_free(cm_processor); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PreviewOperation.h b/source/blender/compositor/operations/COM_PreviewOperation.h index 0f43f01c5d6..05dae9c4dd8 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.h +++ b/source/blender/compositor/operations/COM_PreviewOperation.h @@ -20,13 +20,13 @@ #include "BKE_global.h" #include "BLI_rect.h" -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_color_types.h" #include "DNA_image_types.h" namespace blender::compositor { -class PreviewOperation : public NodeOperation { +class PreviewOperation : public MultiThreadedOperation { protected: unsigned char *m_outputBuffer; @@ -63,6 +63,11 @@ class PreviewOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) 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; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SMAAOperation.cc b/source/blender/compositor/operations/COM_SMAAOperation.cc index b078d85372d..4153b9c8523 100644 --- a/source/blender/compositor/operations/COM_SMAAOperation.cc +++ b/source/blender/compositor/operations/COM_SMAAOperation.cc @@ -61,6 +61,8 @@ namespace blender::compositor { /*-----------------------------------------------------------------------------*/ /* Internal Functions to Sample Pixel Color from Image */ +/* TODO(manzanilla): to be removed with tiled implementation. Replace it with + * #buffer->read_elem_checked. */ static inline void sample(SocketReader *reader, int x, int y, float color[4]) { if (x < 0 || x >= reader->getWidth() || y < 0 || y >= reader->getHeight()) { @@ -71,8 +73,13 @@ static inline void sample(SocketReader *reader, int x, int y, float color[4]) reader->read(color, x, y, nullptr); } -static void sample_bilinear_vertical( - SocketReader *reader, int x, int y, float yoffset, float color[4]) +static inline void sample(MemoryBuffer *reader, int x, int y, float color[4]) +{ + reader->read_elem_checked(x, y, color); +} + +template<typename T> +static void sample_bilinear_vertical(T *reader, int x, int y, float yoffset, float color[4]) { float iy = floorf(yoffset); float fy = yoffset - iy; @@ -89,8 +96,8 @@ static void sample_bilinear_vertical( color[3] = interpf(color01[3], color00[3], fy); } -static void sample_bilinear_horizontal( - SocketReader *reader, int x, int y, float xoffset, float color[4]) +template<typename T> +static void sample_bilinear_horizontal(T *reader, int x, int y, float xoffset, float color[4]) { float ix = floorf(xoffset); float fx = xoffset - ix; @@ -162,7 +169,7 @@ static void area_diag(int d1, int d2, int e1, int e2, float weights[2]) SMAAEdgeDetectionOperation::SMAAEdgeDetectionOperation() { this->addInputSocket(DataType::Color); /* image */ - this->addInputSocket(DataType::Value); /* depth, material ID, etc. */ + this->addInputSocket(DataType::Value); /* Depth, material ID, etc. TODO: currently unused. */ this->addOutputSocket(DataType::Color); this->flags.complex = true; this->m_imageReader = nullptr; @@ -207,6 +214,16 @@ bool SMAAEdgeDetectionOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void SMAAEdgeDetectionOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &output_area, + rcti &r_input_area) +{ + r_input_area.xmax = output_area.xmax + 1; + r_input_area.xmin = output_area.xmin - 2; + r_input_area.ymax = output_area.ymax + 1; + r_input_area.ymin = output_area.ymin - 2; +} + void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, void * /*data*/) { float color[4]; @@ -288,6 +305,94 @@ void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, voi } } +void SMAAEdgeDetectionOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[0]; + for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { + float color[4]; + const int x = it.x; + const int y = it.y; + + /* Calculate luma deltas: */ + image->read_elem_checked(x, y, color); + const float L = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x - 1, y, color); + const float Lleft = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x, y - 1, color); + const float Ltop = IMB_colormanagement_get_luminance(color); + const float Dleft = fabsf(L - Lleft); + const float Dtop = fabsf(L - Ltop); + + /* We do the usual threshold: */ + it.out[0] = (x > 0 && Dleft >= m_threshold) ? 1.0f : 0.0f; + it.out[1] = (y > 0 && Dtop >= m_threshold) ? 1.0f : 0.0f; + it.out[2] = 0.0f; + it.out[3] = 1.0f; + + /* Then discard if there is no edge: */ + if (is_zero_v2(it.out)) { + continue; + } + + /* Calculate right and bottom deltas: */ + image->read_elem_checked(x + 1, y, color); + const float Lright = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x, y + 1, color); + const float Lbottom = IMB_colormanagement_get_luminance(color); + const float Dright = fabsf(L - Lright); + const float Dbottom = fabsf(L - Lbottom); + + /* Calculate the maximum delta in the direct neighborhood: */ + float maxDelta = fmaxf(fmaxf(Dleft, Dright), fmaxf(Dtop, Dbottom)); + + /* Calculate luma used for both left and top edges: */ + image->read_elem_checked(x - 1, y - 1, color); + const float Llefttop = IMB_colormanagement_get_luminance(color); + + /* Left edge */ + if (it.out[0] != 0.0f) { + /* Calculate deltas around the left pixel: */ + image->read_elem_checked(x - 2, y, color); + const float Lleftleft = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x - 1, y + 1, color); + const float Lleftbottom = IMB_colormanagement_get_luminance(color); + const float Dleftleft = fabsf(Lleft - Lleftleft); + const float Dlefttop = fabsf(Lleft - Llefttop); + const float Dleftbottom = fabsf(Lleft - Lleftbottom); + + /* Calculate the final maximum delta: */ + maxDelta = fmaxf(maxDelta, fmaxf(Dleftleft, fmaxf(Dlefttop, Dleftbottom))); + + /* Local contrast adaptation: */ + if (maxDelta > m_contrast_limit * Dleft) { + it.out[0] = 0.0f; + } + } + + /* Top edge */ + if (it.out[1] != 0.0f) { + /* Calculate top-top delta: */ + image->read_elem_checked(x, y - 2, color); + const float Ltoptop = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x + 1, y - 1, color); + const float Ltopright = IMB_colormanagement_get_luminance(color); + const float Dtoptop = fabsf(Ltop - Ltoptop); + const float Dtopleft = fabsf(Ltop - Llefttop); + const float Dtopright = fabsf(Ltop - Ltopright); + + /* Calculate the final maximum delta: */ + maxDelta = fmaxf(maxDelta, fmaxf(Dtoptop, fmaxf(Dtopleft, Dtopright))); + + /* Local contrast adaptation: */ + if (maxDelta > m_contrast_limit * Dtop) { + it.out[1] = 0.0f; + } + } + } +} + /*-----------------------------------------------------------------------------*/ /* Blending Weight Calculation (Second Pass) */ /*-----------------------------------------------------------------------------*/ @@ -309,6 +414,9 @@ void *SMAABlendingWeightCalculationOperation::initializeTileData(rcti *rect) void SMAABlendingWeightCalculationOperation::initExecution() { this->m_imageReader = this->getInputSocketReader(0); + if (execution_model_ == eExecutionModel::Tiled) { + sample_image_fn_ = [=](int x, int y, float *out) { sample(m_imageReader, x, y, out); }; + } } void SMAABlendingWeightCalculationOperation::setCornerRounding(float rounding) @@ -414,6 +522,113 @@ void SMAABlendingWeightCalculationOperation::executePixel(float output[4], } } +void SMAABlendingWeightCalculationOperation::update_memory_buffer_started( + MemoryBuffer *UNUSED(output), const rcti &UNUSED(out_area), Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[0]; + sample_image_fn_ = [=](int x, int y, float *out) { image->read_elem_checked(x, y, out); }; +} + +void SMAABlendingWeightCalculationOperation::update_memory_buffer_partial( + MemoryBuffer *output, const rcti &out_area, Span<MemoryBuffer *> UNUSED(inputs)) +{ + for (BuffersIterator<float> it = output->iterate_with({}, out_area); !it.is_end(); ++it) { + const int x = it.x; + const int y = it.y; + zero_v4(it.out); + + float edges[4]; + sample_image_fn_(x, y, edges); + + /* Edge at north */ + float c[4]; + if (edges[1] > 0.0f) { + /* Diagonals have both north and west edges, so calculating weights for them */ + /* in one of the boundaries is enough. */ + calculateDiagWeights(x, y, edges, it.out); + + /* We give priority to diagonals, so if we find a diagonal we skip. */ + /* horizontal/vertical processing. */ + if (!is_zero_v2(it.out)) { + continue; + } + + /* Find the distance to the left and the right: */ + int left = searchXLeft(x, y); + int right = searchXRight(x, y); + int d1 = x - left, d2 = right - x; + + /* Fetch the left and right crossing edges: */ + int e1 = 0, e2 = 0; + sample_image_fn_(left, y - 1, c); + if (c[0] > 0.0) { + e1 += 1; + } + sample_image_fn_(left, y, c); + if (c[0] > 0.0) { + e1 += 2; + } + sample_image_fn_(right + 1, y - 1, c); + if (c[0] > 0.0) { + e2 += 1; + } + sample_image_fn_(right + 1, y, c); + if (c[0] > 0.0) { + e2 += 2; + } + + /* Ok, we know how this pattern looks like, now it is time for getting */ + /* the actual area: */ + area(d1, d2, e1, e2, it.out); /* R, G */ + + /* Fix corners: */ + if (m_corner_rounding) { + detectHorizontalCornerPattern(it.out, left, right, y, d1, d2); + } + } + + /* Edge at west */ + if (edges[0] > 0.0f) { + /* Did we already do diagonal search for this west edge from the left neighboring pixel? */ + if (isVerticalSearchUnneeded(x, y)) { + continue; + } + + /* Find the distance to the top and the bottom: */ + int top = searchYUp(x, y); + int bottom = searchYDown(x, y); + int d1 = y - top, d2 = bottom - y; + + /* Fetch the top and bottom crossing edges: */ + int e1 = 0, e2 = 0; + sample_image_fn_(x - 1, top, c); + if (c[1] > 0.0) { + e1 += 1; + } + sample_image_fn_(x, top, c); + if (c[1] > 0.0) { + e1 += 2; + } + sample_image_fn_(x - 1, bottom + 1, c); + if (c[1] > 0.0) { + e2 += 1; + } + sample_image_fn_(x, bottom + 1, c); + if (c[1] > 0.0) { + e2 += 2; + } + + /* Get the area for this direction: */ + area(d1, d2, e1, e2, it.out + 2); /* B, A */ + + /* Fix corners: */ + if (m_corner_rounding) { + detectVerticalCornerPattern(it.out + 2, x, top, bottom, d1, d2); + } + } + } +} + void SMAABlendingWeightCalculationOperation::deinitExecution() { this->m_imageReader = nullptr; @@ -434,6 +649,19 @@ bool SMAABlendingWeightCalculationOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void SMAABlendingWeightCalculationOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &output_area, + rcti &r_input_area) +{ + r_input_area.xmax = output_area.xmax + + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG + 1); + r_input_area.xmin = output_area.xmin - + fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG + 1); + r_input_area.ymax = output_area.ymax + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG); + r_input_area.ymin = output_area.ymin - + fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG); +} + /*-----------------------------------------------------------------------------*/ /* Diagonal Search Functions */ @@ -449,7 +677,7 @@ int SMAABlendingWeightCalculationOperation::searchDiag1(int x, int y, int dir, b while (x != end) { x += dir; y -= dir; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f) { *found = true; break; @@ -472,12 +700,12 @@ int SMAABlendingWeightCalculationOperation::searchDiag2(int x, int y, int dir, b while (x != end) { x += dir; y += dir; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f) { *found = true; break; } - sample(m_imageReader, x + 1, y, e); + sample_image_fn_(x + 1, y, e); if (e[0] == 0.0f) { *found = true; return (dir > 0) ? x : x - dir; @@ -522,11 +750,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int left = x - d1, bottom = y + d1; - sample(m_imageReader, left - 1, bottom, c); + sample_image_fn_(left - 1, bottom, c); if (c[1] > 0.0) { e1 += 2; } - sample(m_imageReader, left, bottom, c); + sample_image_fn_(left, bottom, c); if (c[0] > 0.0) { e1 += 1; } @@ -536,11 +764,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int right = x + d2, top = y - d2; - sample(m_imageReader, right + 1, top, c); + sample_image_fn_(right + 1, top, c); if (c[1] > 0.0) { e2 += 2; } - sample(m_imageReader, right + 1, top - 1, c); + sample_image_fn_(right + 1, top - 1, c); if (c[0] > 0.0) { e2 += 1; } @@ -552,7 +780,7 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Search for the line ends: */ d1 = x - searchDiag2(x, y, -1, &d1_found); - sample(m_imageReader, x + 1, y, e); + sample_image_fn_(x + 1, y, e); if (e[0] > 0.0f) { d2 = searchDiag2(x, y, 1, &d2_found) - x; } @@ -568,11 +796,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int left = x - d1, top = y - d1; - sample(m_imageReader, left - 1, top, c); + sample_image_fn_(left - 1, top, c); if (c[1] > 0.0) { e1 += 2; } - sample(m_imageReader, left, top - 1, c); + sample_image_fn_(left, top - 1, c); if (c[0] > 0.0) { e1 += 1; } @@ -582,7 +810,7 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int right = x + d2, bottom = y + d2; - sample(m_imageReader, right + 1, bottom, c); + sample_image_fn_(right + 1, bottom, c); if (c[1] > 0.0) { e2 += 2; } @@ -610,7 +838,7 @@ bool SMAABlendingWeightCalculationOperation::isVerticalSearchUnneeded(int x, int } /* Search for the line ends: */ - sample(m_imageReader, x - 1, y, e); + sample_image_fn_(x - 1, y, e); if (e[1] > 0.0f) { d1 = x - searchDiag2(x - 1, y, -1, &found); } @@ -631,14 +859,14 @@ int SMAABlendingWeightCalculationOperation::searchXLeft(int x, int y) float e[4]; while (x > end) { - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f) { /* Is the edge not activated? */ break; } if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return x; } - sample(m_imageReader, x, y - 1, e); + sample_image_fn_(x, y - 1, e); if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return x; } @@ -655,12 +883,12 @@ int SMAABlendingWeightCalculationOperation::searchXRight(int x, int y) while (x < end) { x++; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f || /* Is the edge not activated? */ e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } - sample(m_imageReader, x, y - 1, e); + sample_image_fn_(x, y - 1, e); if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } @@ -675,14 +903,14 @@ int SMAABlendingWeightCalculationOperation::searchYUp(int x, int y) float e[4]; while (y > end) { - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[0] == 0.0f) { /* Is the edge not activated? */ break; } if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return y; } - sample(m_imageReader, x - 1, y, e); + sample_image_fn_(x - 1, y, e); if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return y; } @@ -699,12 +927,12 @@ int SMAABlendingWeightCalculationOperation::searchYDown(int x, int y) while (y < end) { y++; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[0] == 0.0f || /* Is the edge not activated? */ e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } - sample(m_imageReader, x - 1, y, e); + sample_image_fn_(x - 1, y, e); if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } @@ -728,16 +956,16 @@ void SMAABlendingWeightCalculationOperation::detectHorizontalCornerPattern( /* Near the left corner */ if (d1 <= d2) { - sample(m_imageReader, left, y + 1, e); + sample_image_fn_(left, y + 1, e); factor[0] -= rounding * e[0]; - sample(m_imageReader, left, y - 2, e); + sample_image_fn_(left, y - 2, e); factor[1] -= rounding * e[0]; } /* Near the right corner */ if (d1 >= d2) { - sample(m_imageReader, right + 1, y + 1, e); + sample_image_fn_(right + 1, y + 1, e); factor[0] -= rounding * e[0]; - sample(m_imageReader, right + 1, y - 2, e); + sample_image_fn_(right + 1, y - 2, e); factor[1] -= rounding * e[0]; } @@ -757,16 +985,16 @@ void SMAABlendingWeightCalculationOperation::detectVerticalCornerPattern( /* Near the top corner */ if (d1 <= d2) { - sample(m_imageReader, x + 1, top, e); + sample_image_fn_(x + 1, top, e); factor[0] -= rounding * e[1]; - sample(m_imageReader, x - 2, top, e); + sample_image_fn_(x - 2, top, e); factor[1] -= rounding * e[1]; } /* Near the bottom corner */ if (d1 >= d2) { - sample(m_imageReader, x + 1, bottom + 1, e); + sample_image_fn_(x + 1, bottom + 1, e); factor[0] -= rounding * e[1]; - sample(m_imageReader, x - 2, bottom + 1, e); + sample_image_fn_(x - 2, bottom + 1, e); factor[1] -= rounding * e[1]; } @@ -847,6 +1075,59 @@ void SMAANeighborhoodBlendingOperation::executePixel(float output[4], madd_v4_v4fl(output, color2, weight2); } +void SMAANeighborhoodBlendingOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &out_area, + Span<MemoryBuffer *> inputs) +{ + MemoryBuffer *image1 = inputs[0]; + MemoryBuffer *image2 = inputs[1]; + for (BuffersIterator<float> it = output->iterate_with({}, out_area); !it.is_end(); ++it) { + const float x = it.x; + const float y = it.y; + float w[4]; + + /* Fetch the blending weights for current pixel: */ + image2->read_elem_checked(x, y, w); + const float left = w[2], top = w[0]; + image2->read_elem_checked(x + 1, y, w); + const float right = w[3]; + image2->read_elem_checked(x, y + 1, w); + const float bottom = w[1]; + + /* Is there any blending weight with a value greater than 0.0? */ + if (right + bottom + left + top < 1e-5f) { + image1->read_elem_checked(x, y, it.out); + continue; + } + + /* Calculate the blending offsets: */ + void (*sample_fn)(MemoryBuffer * reader, int x, int y, float xoffset, float color[4]); + float offset1, offset2, weight1, weight2, color1[4], color2[4]; + + if (fmaxf(right, left) > fmaxf(bottom, top)) { /* `max(horizontal) > max(vertical)` */ + sample_fn = sample_bilinear_horizontal; + offset1 = right; + offset2 = -left; + weight1 = right / (right + left); + weight2 = left / (right + left); + } + else { + sample_fn = sample_bilinear_vertical; + offset1 = bottom; + offset2 = -top; + weight1 = bottom / (bottom + top); + weight2 = top / (bottom + top); + } + + /* We exploit bilinear filtering to mix current pixel with the chosen neighbor: */ + sample_fn(image1, x, y, offset1, color1); + sample_fn(image1, x, y, offset2, color2); + + mul_v4_v4fl(it.out, color1, weight1); + madd_v4_v4fl(it.out, color2, weight2); + } +} + void SMAANeighborhoodBlendingOperation::deinitExecution() { this->m_image1Reader = nullptr; @@ -866,4 +1147,12 @@ bool SMAANeighborhoodBlendingOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void SMAANeighborhoodBlendingOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &output_area, + rcti &r_input_area) +{ + r_input_area = output_area; + expand_area_for_sampler(r_input_area, PixelSampler::Bilinear); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SMAAOperation.h b/source/blender/compositor/operations/COM_SMAAOperation.h index 781762202b4..91b9299ee43 100644 --- a/source/blender/compositor/operations/COM_SMAAOperation.h +++ b/source/blender/compositor/operations/COM_SMAAOperation.h @@ -20,14 +20,14 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { /*-----------------------------------------------------------------------------*/ /* Edge Detection (First Pass) */ -class SMAAEdgeDetectionOperation : public NodeOperation { +class SMAAEdgeDetectionOperation : public MultiThreadedOperation { protected: SocketReader *m_imageReader; SocketReader *m_valueReader; @@ -60,15 +60,20 @@ class SMAAEdgeDetectionOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) 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; }; /*-----------------------------------------------------------------------------*/ /* Blending Weight Calculation (Second Pass) */ -class SMAABlendingWeightCalculationOperation : public NodeOperation { +class SMAABlendingWeightCalculationOperation : public MultiThreadedOperation { private: SocketReader *m_imageReader; - + std::function<void(int x, int y, float *out)> sample_image_fn_; int m_corner_rounding; public: @@ -96,6 +101,14 @@ class SMAABlendingWeightCalculationOperation : public NodeOperation { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_started(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + private: /* Diagonal Search Functions */ int searchDiag1(int x, int y, int dir, bool *found); @@ -117,7 +130,7 @@ class SMAABlendingWeightCalculationOperation : public NodeOperation { /*-----------------------------------------------------------------------------*/ /* Neighborhood Blending (Third Pass) */ -class SMAANeighborhoodBlendingOperation : public NodeOperation { +class SMAANeighborhoodBlendingOperation : public MultiThreadedOperation { private: SocketReader *m_image1Reader; SocketReader *m_image2Reader; @@ -144,6 +157,11 @@ class SMAANeighborhoodBlendingOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) 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; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.cc b/source/blender/compositor/operations/COM_VectorBlurOperation.cc index df65044afc1..5405e6d424a 100644 --- a/source/blender/compositor/operations/COM_VectorBlurOperation.cc +++ b/source/blender/compositor/operations/COM_VectorBlurOperation.cc @@ -57,6 +57,7 @@ VectorBlurOperation::VectorBlurOperation() this->m_inputSpeedProgram = nullptr; this->m_inputZProgram = nullptr; flags.complex = true; + flags.is_fullframe_operation = true; } void VectorBlurOperation::initExecution() { @@ -121,6 +122,51 @@ bool VectorBlurOperation::determineDependingAreaOfInterest(rcti * /*input*/, return false; } +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(); +} + +void VectorBlurOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + /* TODO(manzanilla): once tiled implementation is removed, run multi-threaded where possible. */ + if (!m_cachedInstance) { + MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const bool is_image_inflated = image->is_a_single_elem(); + image = is_image_inflated ? image->inflate() : image; + + /* Must be a copy because it's modified in #generateVectorBlur. */ + MemoryBuffer *speed = inputs[SPEED_INPUT_INDEX]; + speed = speed->is_a_single_elem() ? speed->inflate() : new MemoryBuffer(*speed); + + MemoryBuffer *z = inputs[Z_INPUT_INDEX]; + const bool is_z_inflated = z->is_a_single_elem(); + z = is_z_inflated ? z->inflate() : z; + + m_cachedInstance = (float *)MEM_dupallocN(image->getBuffer()); + this->generateVectorBlur(m_cachedInstance, image, speed, z); + + if (is_image_inflated) { + delete image; + } + delete speed; + if (is_z_inflated) { + delete z; + } + } + + const int num_channels = COM_data_type_num_channels(getOutputSocket()->getDataType()); + MemoryBuffer buf(m_cachedInstance, num_channels, this->getWidth(), this->getHeight()); + output->copy_from(&buf, area); +} + void VectorBlurOperation::generateVectorBlur(float *data, MemoryBuffer *inputImage, MemoryBuffer *inputSpeed, diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.h b/source/blender/compositor/operations/COM_VectorBlurOperation.h index dfcf1fb16f7..c30c150db3c 100644 --- a/source/blender/compositor/operations/COM_VectorBlurOperation.h +++ b/source/blender/compositor/operations/COM_VectorBlurOperation.h @@ -26,6 +26,10 @@ namespace blender::compositor { class VectorBlurOperation : public NodeOperation, public QualityStepHelper { private: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int Z_INPUT_INDEX = 1; + static constexpr int SPEED_INPUT_INDEX = 2; + /** * \brief Cached reference to the inputProgram */ @@ -68,6 +72,13 @@ class VectorBlurOperation : public NodeOperation, public QualityStepHelper { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) override; + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + protected: void generateVectorBlur(float *data, MemoryBuffer *inputImage, diff --git a/source/blender/compositor/tests/COM_NodeOperation_test.cc b/source/blender/compositor/tests/COM_NodeOperation_test.cc new file mode 100644 index 00000000000..94e9fdeedb1 --- /dev/null +++ b/source/blender/compositor/tests/COM_NodeOperation_test.cc @@ -0,0 +1,169 @@ +/* + * 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. + * + * Copyright 2021, Blender Foundation. + */ + +#include "testing/testing.h" + +#include "COM_ConstantOperation.h" + +namespace blender::compositor::tests { + +class NonHashedOperation : public NodeOperation { + public: + NonHashedOperation(int id) + { + set_id(id); + addOutputSocket(DataType::Value); + setWidth(2); + setHeight(3); + } +}; + +class NonHashedConstantOperation : public ConstantOperation { + float constant_; + + public: + NonHashedConstantOperation(int id) + { + set_id(id); + addOutputSocket(DataType::Value); + setWidth(2); + setHeight(3); + constant_ = 1.0f; + } + + const float *get_constant_elem() override + { + return &constant_; + } + + void set_constant(float value) + { + constant_ = value; + } +}; + +class HashedOperation : public NodeOperation { + private: + int param1; + float param2; + + public: + HashedOperation(NodeOperation &input, int width, int height) + { + addInputSocket(DataType::Value); + addOutputSocket(DataType::Color); + setWidth(width); + setHeight(height); + param1 = 2; + param2 = 7.0f; + + getInputSocket(0)->setLink(input.getOutputSocket()); + } + + void set_param1(int value) + { + param1 = value; + } + + void hash_output_params() override + { + hash_params(param1, param2); + } +}; + +static void test_non_equal_hashes_compare(NodeOperationHash &h1, + NodeOperationHash &h2, + NodeOperationHash &h3) +{ + if (h1 < h2) { + if (h3 < h1) { + EXPECT_TRUE(h3 < h2); + } + else if (h3 < h2) { + EXPECT_TRUE(h1 < h3); + } + else { + EXPECT_TRUE(h1 < h3); + EXPECT_TRUE(h2 < h3); + } + } + else { + EXPECT_TRUE(h2 < h1); + } +} + +TEST(NodeOperation, generate_hash) +{ + /* Constant input. */ + { + NonHashedConstantOperation input_op1(1); + input_op1.set_constant(1.0f); + EXPECT_EQ(input_op1.generate_hash(), std::nullopt); + + HashedOperation op1(input_op1, 6, 4); + std::optional<NodeOperationHash> hash1_opt = op1.generate_hash(); + EXPECT_NE(hash1_opt, std::nullopt); + NodeOperationHash hash1 = *hash1_opt; + + NonHashedConstantOperation input_op2(1); + input_op2.set_constant(1.0f); + HashedOperation op2(input_op2, 6, 4); + NodeOperationHash hash2 = *op2.generate_hash(); + EXPECT_EQ(hash1, hash2); + + input_op2.set_constant(3.0f); + hash2 = *op2.generate_hash(); + EXPECT_NE(hash1, hash2); + } + + /* Non constant input. */ + { + NonHashedOperation input_op(1); + EXPECT_EQ(input_op.generate_hash(), std::nullopt); + + HashedOperation op1(input_op, 6, 4); + HashedOperation op2(input_op, 6, 4); + NodeOperationHash hash1 = *op1.generate_hash(); + NodeOperationHash hash2 = *op2.generate_hash(); + EXPECT_EQ(hash1, hash2); + op1.set_param1(-1); + hash1 = *op1.generate_hash(); + EXPECT_NE(hash1, hash2); + + HashedOperation op3(input_op, 11, 14); + NodeOperationHash hash3 = *op3.generate_hash(); + EXPECT_NE(hash2, hash3); + EXPECT_NE(hash1, hash3); + + test_non_equal_hashes_compare(hash1, hash2, hash3); + test_non_equal_hashes_compare(hash3, hash2, hash1); + test_non_equal_hashes_compare(hash2, hash3, hash1); + test_non_equal_hashes_compare(hash3, hash1, hash2); + + NonHashedOperation input_op2(2); + HashedOperation op4(input_op2, 11, 14); + NodeOperationHash hash4 = *op4.generate_hash(); + EXPECT_NE(hash3, hash4); + + input_op2.set_id(1); + hash4 = *op4.generate_hash(); + EXPECT_EQ(hash3, hash4); + } +} + +} // namespace blender::compositor::tests diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt index 3ad26c6f4db..41253117096 100644 --- a/source/blender/depsgraph/CMakeLists.txt +++ b/source/blender/depsgraph/CMakeLists.txt @@ -161,6 +161,13 @@ set(LIB bf_blenkernel ) +if(WITH_PYTHON) + add_definitions(-DWITH_PYTHON) + list(APPEND INC + ../python + ) +endif() + blender_add_lib(bf_depsgraph "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") if(WITH_GTESTS) diff --git a/source/blender/depsgraph/DEG_depsgraph_query.h b/source/blender/depsgraph/DEG_depsgraph_query.h index 17f5ca0db79..e9195a1eb26 100644 --- a/source/blender/depsgraph/DEG_depsgraph_query.h +++ b/source/blender/depsgraph/DEG_depsgraph_query.h @@ -145,15 +145,7 @@ typedef struct DEGObjectIterData { eEvaluationMode eval_mode; - /* **** Iteration over geometry components **** */ - - /* The object whose components we currently iterate over. - * This might point to #temp_dupli_object. */ - struct Object *geometry_component_owner; - /* Some identifier that is used to determine which geometry component should be returned next. */ - int geometry_component_id; - /* Temporary storage for an object that is created from a component. */ - struct Object temp_geometry_component_object; + struct Object *next_object; /* **** Iteration over dupli-list. *** */ diff --git a/source/blender/depsgraph/intern/depsgraph_query_iter.cc b/source/blender/depsgraph/intern/depsgraph_query_iter.cc index 770d9775dd3..7af3d03d478 100644 --- a/source/blender/depsgraph/intern/depsgraph_query_iter.cc +++ b/source/blender/depsgraph/intern/depsgraph_query_iter.cc @@ -120,130 +120,6 @@ bool deg_object_hide_original(eEvaluationMode eval_mode, Object *ob, DupliObject return false; } -void deg_iterator_components_init(DEGObjectIterData *data, Object *object) -{ - data->geometry_component_owner = object; - data->geometry_component_id = 0; -} - -/* Returns false when iterator is exhausted. */ -bool deg_iterator_components_step(BLI_Iterator *iter) -{ - DEGObjectIterData *data = (DEGObjectIterData *)iter->data; - if (data->geometry_component_owner == nullptr) { - return false; - } - - if (data->geometry_component_owner->runtime.geometry_set_eval == nullptr) { - /* Return the object itself, if it does not have a geometry set yet. */ - iter->current = data->geometry_component_owner; - data->geometry_component_owner = nullptr; - return true; - } - - GeometrySet *geometry_set = data->geometry_component_owner->runtime.geometry_set_eval; - if (geometry_set == nullptr) { - data->geometry_component_owner = nullptr; - return false; - } - - /* The mesh component. */ - if (data->geometry_component_id == 0) { - data->geometry_component_id++; - - /* Don't use a temporary object for this component, when the owner is a mesh object. */ - if (data->geometry_component_owner->type == OB_MESH) { - iter->current = data->geometry_component_owner; - return true; - } - - const Mesh *mesh = geometry_set->get_mesh_for_read(); - if (mesh != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_MESH; - temp_object->data = (void *)mesh; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - - /* The pointcloud component. */ - if (data->geometry_component_id == 1) { - data->geometry_component_id++; - - /* Don't use a temporary object for this component, when the owner is a point cloud object. */ - if (data->geometry_component_owner->type == OB_POINTCLOUD) { - iter->current = data->geometry_component_owner; - return true; - } - - const PointCloud *pointcloud = geometry_set->get_pointcloud_for_read(); - if (pointcloud != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_POINTCLOUD; - temp_object->data = (void *)pointcloud; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - - /* The volume component. */ - if (data->geometry_component_id == 2) { - data->geometry_component_id++; - - /* Don't use a temporary object for this component, when the owner is a volume object. */ - if (data->geometry_component_owner->type == OB_VOLUME) { - iter->current = data->geometry_component_owner; - return true; - } - - const VolumeComponent *component = geometry_set->get_component_for_read<VolumeComponent>(); - if (component != nullptr) { - const Volume *volume = component->get_for_read(); - - if (volume != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_VOLUME; - temp_object->data = (void *)volume; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - } - - /* The curve component. */ - if (data->geometry_component_id == 3) { - data->geometry_component_id++; - - const CurveComponent *component = geometry_set->get_component_for_read<CurveComponent>(); - if (component != nullptr) { - const Curve *curve = component->get_curve_for_render(); - - if (curve != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_CURVE; - temp_object->data = (void *)curve; - /* Assign data_eval here too, because curve rendering code tries - * to use a mesh if it can find one in this pointer. */ - temp_object->runtime.data_eval = (ID *)curve; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - } - - data->geometry_component_owner = nullptr; - return false; -} - void deg_iterator_duplis_init(DEGObjectIterData *data, Object *object) { if ((data->flag & DEG_ITER_OBJECT_FLAG_DUPLI) && @@ -292,6 +168,9 @@ bool deg_iterator_duplis_step(DEGObjectIterData *data) temp_dupli_object->dt = MIN2(temp_dupli_object->dt, dupli_parent->dt); copy_v4_v4(temp_dupli_object->color, dupli_parent->color); temp_dupli_object->runtime.select_id = dupli_parent->runtime.select_id; + if (dob->ob->data != dob->ob_data) { + BKE_object_replace_data_on_shallow_copy(temp_dupli_object, dob->ob_data); + } /* Duplicated elements shouldn't care whether their original collection is visible or not. */ temp_dupli_object->base_flag |= BASE_VISIBLE_DEPSGRAPH; @@ -308,7 +187,7 @@ bool deg_iterator_duplis_step(DEGObjectIterData *data) copy_m4_m4(data->temp_dupli_object.obmat, dob->mat); invert_m4_m4(data->temp_dupli_object.imat, data->temp_dupli_object.obmat); - deg_iterator_components_init(data, &data->temp_dupli_object); + data->next_object = &data->temp_dupli_object; BLI_assert(deg::deg_validate_copy_on_write_datablock(&data->temp_dupli_object.id)); return true; } @@ -377,7 +256,7 @@ bool deg_iterator_objects_step(DEGObjectIterData *data) } if (ob_visibility & (OB_VISIBLE_SELF | OB_VISIBLE_PARTICLES)) { - deg_iterator_components_init(data, object); + data->next_object = object; } data->id_node_index++; return true; @@ -400,6 +279,7 @@ void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data) return; } + data->next_object = nullptr; data->dupli_parent = nullptr; data->dupli_list = nullptr; data->dupli_object_next = nullptr; @@ -408,8 +288,6 @@ void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data) data->id_node_index = 0; data->num_id_nodes = num_id_nodes; data->eval_mode = DEG_get_mode(depsgraph); - data->geometry_component_id = 0; - data->geometry_component_owner = nullptr; deg_invalidate_iterator_work_data(data); DEG_iterator_objects_next(iter); @@ -419,7 +297,9 @@ void DEG_iterator_objects_next(BLI_Iterator *iter) { DEGObjectIterData *data = (DEGObjectIterData *)iter->data; while (true) { - if (deg_iterator_components_step(iter)) { + if (data->next_object != nullptr) { + iter->current = data->next_object; + data->next_object = nullptr; return; } if (deg_iterator_duplis_step(data)) { diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index ad88cf656ad..c816c7b8db5 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -41,6 +41,10 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" +#ifdef WITH_PYTHON +# include "BPY_extern.h" +#endif + #include "atomic_ops.h" #include "intern/depsgraph.h" @@ -375,6 +379,11 @@ void deg_evaluate_on_refresh(Depsgraph *graph) graph->debug.begin_graph_evaluation(); +#ifdef WITH_PYTHON + /* Release the GIL so that Python drivers can be evaluated. See T91046. */ + BPy_BEGIN_ALLOW_THREADS; +#endif + graph->is_evaluating = true; depsgraph_ensure_view_layer(graph); /* Set up evaluation state. */ @@ -415,6 +424,10 @@ void deg_evaluate_on_refresh(Depsgraph *graph) deg_graph_clear_tags(graph); graph->is_evaluating = false; +#ifdef WITH_PYTHON + BPy_END_ALLOW_THREADS; +#endif + graph->debug.end_graph_evaluation(); } diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc index 30ec9e948fd..1081528ece1 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc @@ -98,7 +98,7 @@ void ObjectRuntimeBackup::restore_to_object(Object *object) object->runtime = runtime; object->runtime.data_orig = data_orig; object->runtime.bb = bb; - if (ELEM(object->type, OB_MESH, OB_LATTICE) && data_eval != nullptr) { + if (ELEM(object->type, OB_MESH, OB_LATTICE, OB_CURVE, OB_FONT) && data_eval != nullptr) { if (object->id.recalc & ID_RECALC_GEOMETRY) { /* If geometry is tagged for update it means, that part of * evaluated mesh are not valid anymore. In this case we can not @@ -112,9 +112,11 @@ void ObjectRuntimeBackup::restore_to_object(Object *object) BKE_object_free_derived_caches(object); } else { - /* Do same thing as object update: override actual object data - * pointer with evaluated datablock. */ - object->data = data_eval; + /* Do same thing as object update: override actual object data pointer with evaluated + * datablock, but only if the evaluated data has the same type as the original data. */ + if (GS(((ID *)object->data)->name) == GS(data_eval->name)) { + object->data = data_eval; + } /* Evaluated mesh simply copied edit_mesh pointer from * original mesh during update, need to make sure no dead diff --git a/source/blender/draw/DRW_engine.h b/source/blender/draw/DRW_engine.h index 8a35ab2aeb9..a125a13eaf9 100644 --- a/source/blender/draw/DRW_engine.h +++ b/source/blender/draw/DRW_engine.h @@ -110,6 +110,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, bool use_obedit_skip, bool draw_surface, bool use_nearest, + const bool do_material_sub_selection, const struct rcti *rect, DRW_SelectPassFn select_pass_fn, void *select_pass_user_data, diff --git a/source/blender/draw/engines/basic/basic_engine.c b/source/blender/draw/engines/basic/basic_engine.c index c120df7e897..87f5c6f5857 100644 --- a/source/blender/draw/engines/basic/basic_engine.c +++ b/source/blender/draw/engines/basic/basic_engine.c @@ -25,9 +25,12 @@ #include "DRW_render.h" +#include "BKE_object.h" #include "BKE_paint.h" #include "BKE_particle.h" +#include "BLI_alloca.h" + #include "DNA_particle_types.h" #include "GPU_shader.h" @@ -80,6 +83,7 @@ typedef struct BASIC_PrivateData { DRWShadingGroup *depth_shgrp[2]; DRWShadingGroup *depth_shgrp_cull[2]; DRWShadingGroup *depth_hair_shgrp[2]; + bool use_material_slot_selection; } BASIC_PrivateData; /* Transient data */ /* Functions */ @@ -131,6 +135,8 @@ static void basic_cache_init(void *vedata) stl->g_data = MEM_callocN(sizeof(*stl->g_data), __func__); } + stl->g_data->use_material_slot_selection = DRW_state_is_material_select(); + /* Twice for normal and in front objects. */ for (int i = 0; i < 2; i++) { DRWState clip_state = (draw_ctx->sh_cfg == GPU_SHADER_CFG_CLIPPED) ? DRW_STATE_CLIP_PLANES : 0; @@ -155,6 +161,38 @@ static void basic_cache_init(void *vedata) } } +/* TODO(fclem): DRW_cache_object_surface_material_get needs a refactor to allow passing NULL + * instead of gpumat_array. Avoiding all this boilerplate code. */ +static struct GPUBatch **basic_object_surface_material_get(Object *ob) +{ + const int materials_len = DRW_cache_object_material_count_get(ob); + struct GPUMaterial **gpumat_array = BLI_array_alloca(gpumat_array, materials_len); + memset(gpumat_array, 0, sizeof(*gpumat_array) * materials_len); + + return DRW_cache_object_surface_material_get(ob, gpumat_array, materials_len); +} + +static void basic_cache_populate_particles(void *vedata, Object *ob) +{ + const bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; + BASIC_StorageList *stl = ((BASIC_Data *)vedata)->stl; + for (ParticleSystem *psys = ob->particlesystem.first; psys != NULL; psys = psys->next) { + if (!DRW_object_is_visible_psys_in_active_context(ob, psys)) { + continue; + } + ParticleSettings *part = psys->part; + const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as; + if (draw_as == PART_DRAW_PATH) { + struct GPUBatch *hairs = DRW_cache_particles_get_hair(ob, psys, NULL); + if (stl->g_data->use_material_slot_selection) { + const short material_slot = part->omat; + DRW_select_load_id(ob->runtime.select_id | (material_slot << 16)); + } + DRW_shgroup_call(stl->g_data->depth_hair_shgrp[do_in_front], hairs, NULL); + } + } +} + static void basic_cache_populate(void *vedata, Object *ob) { BASIC_StorageList *stl = ((BASIC_Data *)vedata)->stl; @@ -165,24 +203,13 @@ static void basic_cache_populate(void *vedata, Object *ob) return; } - bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; - const DRWContextState *draw_ctx = DRW_context_state_get(); if (ob != draw_ctx->object_edit) { - for (ParticleSystem *psys = ob->particlesystem.first; psys != NULL; psys = psys->next) { - if (!DRW_object_is_visible_psys_in_active_context(ob, psys)) { - continue; - } - ParticleSettings *part = psys->part; - const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as; - if (draw_as == PART_DRAW_PATH) { - struct GPUBatch *hairs = DRW_cache_particles_get_hair(ob, psys, NULL); - DRW_shgroup_call(stl->g_data->depth_hair_shgrp[do_in_front], hairs, NULL); - } - } + basic_cache_populate_particles(vedata, ob); } /* Make flat object selectable in ortho view if wireframe is enabled. */ + const bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; if ((draw_ctx->v3d->overlay.flag & V3D_OVERLAY_WIREFRAMES) || (draw_ctx->v3d->shading.type == OB_WIRE) || (ob->dtx & OB_DRAWWIRE) || (ob->dt == OB_WIRE)) { int flat_axis = 0; @@ -211,9 +238,25 @@ static void basic_cache_populate(void *vedata, Object *ob) DRW_shgroup_call_sculpt(shgrp, ob, false, false); } else { - struct GPUBatch *geom = DRW_cache_object_surface_get(ob); - if (geom) { - DRW_shgroup_call(shgrp, geom, ob); + if (stl->g_data->use_material_slot_selection && BKE_object_supports_material_slots(ob)) { + struct GPUBatch **geoms = basic_object_surface_material_get(ob); + if (geoms) { + const int materials_len = DRW_cache_object_material_count_get(ob); + for (int i = 0; i < materials_len; i++) { + if (geoms[i] == NULL) { + continue; + } + const short material_slot_select_id = i + 1; + DRW_select_load_id(ob->runtime.select_id | (material_slot_select_id << 16)); + DRW_shgroup_call(shgrp, geoms[i], ob); + } + } + } + else { + struct GPUBatch *geom = DRW_cache_object_surface_get(ob); + if (geom) { + DRW_shgroup_call(shgrp, geom, ob); + } } } } diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.c b/source/blender/draw/engines/gpencil/gpencil_engine.c index d3a0c40fae5..1078cebdbff 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.c +++ b/source/blender/draw/engines/gpencil/gpencil_engine.c @@ -240,8 +240,9 @@ void GPENCIL_cache_init(void *ved) } else { pd->do_onion = true; - pd->simplify_fill = false; - pd->simplify_fx = false; + Scene *scene = draw_ctx->scene; + pd->simplify_fill = GPENCIL_SIMPLIFY_FILL(scene, false); + pd->simplify_fx = GPENCIL_SIMPLIFY_FX(scene, false); pd->fade_layer_opacity = -1.0f; pd->playing = false; } diff --git a/source/blender/draw/engines/overlay/overlay_edit_text.c b/source/blender/draw/engines/overlay/overlay_edit_text.c index fd68b319f02..5356700f156 100644 --- a/source/blender/draw/engines/overlay/overlay_edit_text.c +++ b/source/blender/draw/engines/overlay/overlay_edit_text.c @@ -180,19 +180,12 @@ static void edit_text_cache_populate_boxes(OVERLAY_Data *vedata, Object *ob) void OVERLAY_edit_text_cache_populate(OVERLAY_Data *vedata, Object *ob) { OVERLAY_PrivateData *pd = vedata->stl->pd; - Curve *cu = ob->data; struct GPUBatch *geom; bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; - bool has_surface = (cu->flag & (CU_FRONT | CU_BACK)) || cu->ext1 != 0.0f || cu->ext2 != 0.0f; - if ((cu->flag & CU_FAST) || !has_surface) { - geom = DRW_cache_text_edge_wire_get(ob); - if (geom) { - DRW_shgroup_call(pd->edit_text_wire_grp[do_in_front], geom, ob); - } - } - else { - /* object mode draws */ + geom = DRW_cache_text_edge_wire_get(ob); + if (geom) { + DRW_shgroup_call(pd->edit_text_wire_grp[do_in_front], geom, ob); } edit_text_cache_populate_select(vedata, ob); diff --git a/source/blender/draw/engines/overlay/overlay_wireframe.c b/source/blender/draw/engines/overlay/overlay_wireframe.c index b8a61ecc403..fde376beeb2 100644 --- a/source/blender/draw/engines/overlay/overlay_wireframe.c +++ b/source/blender/draw/engines/overlay/overlay_wireframe.c @@ -218,18 +218,10 @@ void OVERLAY_wireframe_cache_populate(OVERLAY_Data *vedata, struct GPUBatch *geom = NULL; switch (ob->type) { case OB_CURVE: - if (!pd->wireframe_mode && !use_wire && ob->runtime.curve_cache && - BKE_displist_has_faces(&ob->runtime.curve_cache->disp)) { - break; - } geom = DRW_cache_curve_edge_wire_get(ob); break; case OB_FONT: - if (!pd->wireframe_mode && !use_wire && ob->runtime.curve_cache && - BKE_displist_has_faces(&ob->runtime.curve_cache->disp)) { - break; - } - geom = DRW_cache_text_loose_edges_get(ob); + geom = DRW_cache_text_edge_wire_get(ob); break; case OB_SURF: geom = DRW_cache_surf_edge_wire_get(ob); diff --git a/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl b/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl index fcc05414ea3..f09918da6dc 100644 --- a/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl +++ b/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl @@ -6,7 +6,7 @@ out vec4 fragColor; void main() { - fragColor = color; - float scene_depth = texelFetch(depthBuffer, ivec2(gl_FragCoord.xy), 0).r; - fragColor.a = (scene_depth == 1.0) ? 1.0 : 0.0; + fragColor = color; + float scene_depth = texelFetch(depthBuffer, ivec2(gl_FragCoord.xy), 0).r; + fragColor.a = (scene_depth == 1.0) ? 1.0 : 0.0; } diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h index 6639a100af9..660a4adaf51 100644 --- a/source/blender/draw/intern/DRW_render.h +++ b/source/blender/draw/intern/DRW_render.h @@ -731,6 +731,7 @@ void DRW_select_load_id(uint id); /* Draw State */ bool DRW_state_is_fbo(void); bool DRW_state_is_select(void); +bool DRW_state_is_material_select(void); bool DRW_state_is_depth(void); bool DRW_state_is_image_render(void); bool DRW_state_is_scene_render(void); diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index 000ab540813..a3a5d6b065a 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -794,6 +794,10 @@ GPUBatch *DRW_gpencil_dummy_buffer_get(void) /* -------------------------------------------------------------------- */ /** \name Common Object API + * + * \note Curve and text objects evaluate to the evaluated geometry set's mesh component if + * they have a surface, so curve objects themselves do not have a surface (the mesh component + * is presented to render engines as a separate object). * \{ */ GPUBatch *DRW_cache_object_all_edges_get(Object *ob) @@ -814,11 +818,11 @@ GPUBatch *DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold) case OB_MESH: return DRW_cache_mesh_edge_detection_get(ob, r_is_manifold); case OB_CURVE: - return DRW_cache_curve_edge_detection_get(ob, r_is_manifold); + return NULL; case OB_SURF: return DRW_cache_surf_edge_detection_get(ob, r_is_manifold); case OB_FONT: - return DRW_cache_text_edge_detection_get(ob, r_is_manifold); + return NULL; case OB_MBALL: return DRW_cache_mball_edge_detection_get(ob, r_is_manifold); case OB_HAIR: @@ -838,11 +842,11 @@ GPUBatch *DRW_cache_object_face_wireframe_get(Object *ob) case OB_MESH: return DRW_cache_mesh_face_wireframe_get(ob); case OB_CURVE: - return DRW_cache_curve_face_wireframe_get(ob); + return NULL; case OB_SURF: return DRW_cache_surf_face_wireframe_get(ob); case OB_FONT: - return DRW_cache_text_face_wireframe_get(ob); + return NULL; case OB_MBALL: return DRW_cache_mball_face_wireframe_get(ob); case OB_HAIR: @@ -865,11 +869,11 @@ GPUBatch *DRW_cache_object_loose_edges_get(struct Object *ob) case OB_MESH: return DRW_cache_mesh_loose_edges_get(ob); case OB_CURVE: - return DRW_cache_curve_loose_edges_get(ob); + return NULL; case OB_SURF: return DRW_cache_surf_loose_edges_get(ob); case OB_FONT: - return DRW_cache_text_loose_edges_get(ob); + return NULL; case OB_MBALL: return NULL; case OB_HAIR: @@ -889,11 +893,11 @@ GPUBatch *DRW_cache_object_surface_get(Object *ob) case OB_MESH: return DRW_cache_mesh_surface_get(ob); case OB_CURVE: - return DRW_cache_curve_surface_get(ob); + return NULL; case OB_SURF: return DRW_cache_surf_surface_get(ob); case OB_FONT: - return DRW_cache_text_surface_get(ob); + return NULL; case OB_MBALL: return DRW_cache_mball_surface_get(ob); case OB_HAIR: @@ -939,9 +943,9 @@ int DRW_cache_object_material_count_get(struct Object *ob) Mesh *me = BKE_object_get_evaluated_mesh(ob); if (me != NULL && type != OB_POINTCLOUD) { - /* Some object types (e.g. curves) can have a Curve in ob->data, but will be rendered as mesh. - * For point clouds this never happens. Ideally this check would happen at another level and we - * would just have to care about ob->data here. */ + /* Some object types can have one data type in ob->data, but will be rendered as mesh. + * For point clouds this never happens. Ideally this check would happen at another level + * and we would just have to care about ob->data here. */ type = OB_MESH; } @@ -974,11 +978,11 @@ GPUBatch **DRW_cache_object_surface_material_get(struct Object *ob, case OB_MESH: return DRW_cache_mesh_surface_shaded_get(ob, gpumat_array, gpumat_array_len); case OB_CURVE: - return DRW_cache_curve_surface_shaded_get(ob, gpumat_array, gpumat_array_len); + return NULL; case OB_SURF: return DRW_cache_surf_surface_shaded_get(ob, gpumat_array, gpumat_array_len); case OB_FONT: - return DRW_cache_text_surface_shaded_get(ob, gpumat_array, gpumat_array_len); + return NULL; case OB_MBALL: return DRW_cache_mball_surface_shaded_get(ob, gpumat_array, gpumat_array_len); case OB_HAIR: @@ -2929,20 +2933,13 @@ GPUBatch *DRW_cache_mesh_surface_mesh_analysis_get(Object *ob) GPUBatch *DRW_cache_curve_edge_wire_get(Object *ob) { BLI_assert(ob->type == OB_CURVE); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - return DRW_curve_batch_cache_get_wire_edge(cu); } GPUBatch *DRW_cache_curve_edge_normal_get(Object *ob) { BLI_assert(ob->type == OB_CURVE); - struct Curve *cu = ob->data; return DRW_curve_batch_cache_get_normal_edge(cu); } @@ -2963,75 +2960,6 @@ GPUBatch *DRW_cache_curve_vert_overlay_get(Object *ob) return DRW_curve_batch_cache_get_edit_verts(cu); } -GPUBatch *DRW_cache_curve_surface_get(Object *ob) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface(mesh_eval); - } - - return DRW_curve_batch_cache_get_triangles_with_normals(cu); -} - -GPUBatch *DRW_cache_curve_loose_edges_get(Object *ob) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - - /* TODO */ - UNUSED_VARS(cu); - return NULL; -} - -GPUBatch *DRW_cache_curve_face_wireframe_get(Object *ob) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_wireframes_face(mesh_eval); - } - - return DRW_curve_batch_cache_get_wireframes_face(cu); -} - -GPUBatch *DRW_cache_curve_edge_detection_get(Object *ob, bool *r_is_manifold) -{ - BLI_assert(ob->type == OB_CURVE); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_edge_detection(mesh_eval, r_is_manifold); - } - - return DRW_curve_batch_cache_get_edge_detection(cu, r_is_manifold); -} - -/* Return list of batches */ -GPUBatch **DRW_cache_curve_surface_shaded_get(Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface_shaded(mesh_eval, gpumat_array, gpumat_array_len); - } - - return DRW_curve_batch_cache_get_surface_shaded(cu, gpumat_array, gpumat_array_len); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -3075,96 +3003,9 @@ GPUBatch *DRW_cache_text_edge_wire_get(Object *ob) { BLI_assert(ob->type == OB_FONT); struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - const bool has_surface = (cu->flag & (CU_FRONT | CU_BACK)) || cu->ext1 != 0.0f || - cu->ext2 != 0.0f; - if (!has_surface) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - - return DRW_curve_batch_cache_get_wire_edge(cu); -} - -GPUBatch *DRW_cache_text_surface_get(Object *ob) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface(mesh_eval); - } - - return DRW_curve_batch_cache_get_triangles_with_normals(cu); -} - -GPUBatch *DRW_cache_text_edge_detection_get(Object *ob, bool *r_is_manifold) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_edge_detection(mesh_eval, r_is_manifold); - } - - return DRW_curve_batch_cache_get_edge_detection(cu, r_is_manifold); -} - -GPUBatch *DRW_cache_text_loose_edges_get(Object *ob) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - return DRW_curve_batch_cache_get_wire_edge(cu); } -GPUBatch *DRW_cache_text_face_wireframe_get(Object *ob) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_wireframes_face(mesh_eval); - } - - return DRW_curve_batch_cache_get_wireframes_face(cu); -} - -GPUBatch **DRW_cache_text_surface_shaded_get(Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface_shaded(mesh_eval, gpumat_array, gpumat_array_len); - } - - return DRW_curve_batch_cache_get_surface_shaded(cu, gpumat_array, gpumat_array_len); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -3544,6 +3385,8 @@ void drw_batch_cache_validate(Object *ob) break; case OB_CURVE: case OB_FONT: + DRW_curve_batch_cache_validate((Curve *)ob->data); + break; case OB_SURF: if (mesh_eval != NULL) { DRW_mesh_batch_cache_validate(mesh_eval); @@ -3592,6 +3435,8 @@ void drw_batch_cache_generate_requested(Object *ob) break; case OB_CURVE: case OB_FONT: + DRW_curve_batch_cache_create_requested(ob, scene); + break; case OB_SURF: if (mesh_eval) { DRW_mesh_batch_cache_create_requested( @@ -3618,8 +3463,6 @@ void DRW_batch_cache_free_old(Object *ob, int ctime) case OB_MESH: DRW_mesh_batch_cache_free_old((Mesh *)ob->data, ctime); break; - case OB_CURVE: - case OB_FONT: case OB_SURF: if (mesh_eval) { DRW_mesh_batch_cache_free_old(mesh_eval, ctime); diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index 6b2b0a173fe..5863ada2ccf 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -154,28 +154,14 @@ struct GPUBatch *DRW_cache_mesh_surface_mesh_analysis_get(struct Object *ob); struct GPUBatch *DRW_cache_mesh_face_wireframe_get(struct Object *ob); /* Curve */ -struct GPUBatch *DRW_cache_curve_surface_get(struct Object *ob); -struct GPUBatch **DRW_cache_curve_surface_shaded_get(struct Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len); -struct GPUBatch *DRW_cache_curve_loose_edges_get(struct Object *ob); struct GPUBatch *DRW_cache_curve_edge_wire_get(struct Object *ob); -struct GPUBatch *DRW_cache_curve_face_wireframe_get(struct Object *ob); -struct GPUBatch *DRW_cache_curve_edge_detection_get(struct Object *ob, bool *r_is_manifold); /* edit-mode */ struct GPUBatch *DRW_cache_curve_edge_normal_get(struct Object *ob); struct GPUBatch *DRW_cache_curve_edge_overlay_get(struct Object *ob); struct GPUBatch *DRW_cache_curve_vert_overlay_get(struct Object *ob); /* Font */ -struct GPUBatch *DRW_cache_text_surface_get(struct Object *ob); -struct GPUBatch *DRW_cache_text_edge_detection_get(struct Object *ob, bool *r_is_manifold); -struct GPUBatch *DRW_cache_text_loose_edges_get(struct Object *ob); struct GPUBatch *DRW_cache_text_edge_wire_get(struct Object *ob); -struct GPUBatch **DRW_cache_text_surface_shaded_get(struct Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len); -struct GPUBatch *DRW_cache_text_face_wireframe_get(struct Object *ob); /* Surface */ struct GPUBatch *DRW_cache_surf_surface_get(struct Object *ob); diff --git a/source/blender/draw/intern/draw_cache_impl_curve.cc b/source/blender/draw/intern/draw_cache_impl_curve.cc index 1efe0c080be..0804745fab5 100644 --- a/source/blender/draw/intern/draw_cache_impl_curve.cc +++ b/source/blender/draw/intern/draw_cache_impl_curve.cc @@ -112,43 +112,6 @@ static void curve_render_overlay_verts_edges_len_get(ListBase *lb, } } -static void curve_render_wire_verts_edges_len_get(const CurveCache *ob_curve_cache, - int *r_curve_len, - int *r_vert_len, - int *r_edge_len) -{ - BLI_assert(r_vert_len || r_edge_len); - int vert_len = 0; - int edge_len = 0; - int curve_len = 0; - LISTBASE_FOREACH (const BevList *, bl, &ob_curve_cache->bev) { - if (bl->nr > 0) { - const bool is_cyclic = bl->poly != -1; - edge_len += (is_cyclic) ? bl->nr : bl->nr - 1; - vert_len += bl->nr; - curve_len += 1; - } - } - LISTBASE_FOREACH (const DispList *, dl, &ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - BLI_assert(dl->parts == 1); - const bool is_cyclic = dl->type == DL_POLY; - edge_len += (is_cyclic) ? dl->nr : dl->nr - 1; - vert_len += dl->nr; - curve_len += 1; - } - } - if (r_vert_len) { - *r_vert_len = vert_len; - } - if (r_edge_len) { - *r_edge_len = edge_len; - } - if (r_curve_len) { - *r_curve_len = curve_len; - } -} - static void curve_eval_render_wire_verts_edges_len_get(const CurveEval &curve_eval, int *r_curve_len, int *r_vert_len, @@ -243,7 +206,7 @@ enum { }; /* - * ob_curve_cache can be NULL, only needed for CU_DATATYPE_WIRE + * ob_curve_cache can be NULL */ static CurveRenderData *curve_render_data_create(Curve *cu, CurveCache *ob_curve_cache, @@ -267,12 +230,6 @@ static CurveRenderData *curve_render_data_create(Curve *cu, &rdata->wire.vert_len, &rdata->wire.edge_len); } - else { - curve_render_wire_verts_edges_len_get(rdata->ob_curve_cache, - &rdata->wire.curve_len, - &rdata->wire.vert_len, - &rdata->wire.edge_len); - } } if (cu->editnurb) { @@ -594,6 +551,10 @@ void DRW_curve_batch_cache_free(Curve *cu) /* GPUBatch cache usage. */ static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curves_pos) { + if (rdata->curve_eval == nullptr) { + return; + } + static GPUVertFormat format = {0}; static struct { uint pos; @@ -606,46 +567,26 @@ static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curv GPU_vertbuf_init_with_format(vbo_curves_pos, &format); GPU_vertbuf_data_alloc(vbo_curves_pos, vert_len); - if (rdata->curve_eval != nullptr) { - const CurveEval &curve_eval = *rdata->curve_eval; - Span<SplinePtr> splines = curve_eval.splines(); - Array<int> offsets = curve_eval.evaluated_point_offsets(); - BLI_assert(offsets.last() == vert_len); - - for (const int i_spline : splines.index_range()) { - Span<float3> positions = splines[i_spline]->evaluated_positions(); - for (const int i_point : positions.index_range()) { - GPU_vertbuf_attr_set( - vbo_curves_pos, attr_id.pos, offsets[i_spline] + i_point, positions[i_point]); - } - } - } - else { - BLI_assert(rdata->ob_curve_cache != nullptr); - - int v_idx = 0; - LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { - if (bl->nr <= 0) { - continue; - } - const int i_end = v_idx + bl->nr; - for (const BevPoint *bevp = bl->bevpoints; v_idx < i_end; v_idx++, bevp++) { - GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, bevp->vec); - } - } - LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - for (int i = 0; i < dl->nr; v_idx++, i++) { - GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, &((float(*)[3])dl->verts)[i]); - } - } + const CurveEval &curve_eval = *rdata->curve_eval; + Span<SplinePtr> splines = curve_eval.splines(); + Array<int> offsets = curve_eval.evaluated_point_offsets(); + BLI_assert(offsets.last() == vert_len); + + for (const int i_spline : splines.index_range()) { + Span<float3> positions = splines[i_spline]->evaluated_positions(); + for (const int i_point : positions.index_range()) { + GPU_vertbuf_attr_set( + vbo_curves_pos, attr_id.pos, offsets[i_spline] + i_point, positions[i_point]); } - BLI_assert(v_idx == vert_len); } } static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_curve_lines) { + if (rdata->curve_eval == nullptr) { + return; + } + const int vert_len = curve_render_data_wire_verts_len_get(rdata); const int edge_len = curve_render_data_wire_edges_len_get(rdata); const int curve_len = curve_render_data_wire_curve_len_get(rdata); @@ -655,54 +596,20 @@ static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_c GPUIndexBufBuilder elb; GPU_indexbuf_init_ex(&elb, GPU_PRIM_LINE_STRIP, index_len, vert_len); - if (rdata->curve_eval != nullptr) { - const CurveEval &curve_eval = *rdata->curve_eval; - Span<SplinePtr> splines = curve_eval.splines(); - Array<int> offsets = curve_eval.evaluated_point_offsets(); - BLI_assert(offsets.last() == vert_len); - - for (const int i_spline : splines.index_range()) { - const int eval_size = splines[i_spline]->evaluated_points_size(); - if (splines[i_spline]->is_cyclic() && splines[i_spline]->evaluated_edges_size() > 1) { - GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + eval_size - 1); - } - for (const int i_point : IndexRange(eval_size)) { - GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + i_point); - } - GPU_indexbuf_add_primitive_restart(&elb); - } - } - else { - BLI_assert(rdata->ob_curve_cache != nullptr); + const CurveEval &curve_eval = *rdata->curve_eval; + Span<SplinePtr> splines = curve_eval.splines(); + Array<int> offsets = curve_eval.evaluated_point_offsets(); + BLI_assert(offsets.last() == vert_len); - int v_idx = 0; - LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { - if (bl->nr <= 0) { - continue; - } - const bool is_cyclic = bl->poly != -1; - if (is_cyclic) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + (bl->nr - 1)); - } - for (int i = 0; i < bl->nr; i++) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + i); - } - GPU_indexbuf_add_primitive_restart(&elb); - v_idx += bl->nr; + for (const int i_spline : splines.index_range()) { + const int eval_size = splines[i_spline]->evaluated_points_size(); + if (splines[i_spline]->is_cyclic() && splines[i_spline]->evaluated_edges_size() > 1) { + GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + eval_size - 1); } - LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - const bool is_cyclic = dl->type == DL_POLY; - if (is_cyclic) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + (dl->nr - 1)); - } - for (int i = 0; i < dl->nr; i++) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + i); - } - GPU_indexbuf_add_primitive_restart(&elb); - v_idx += dl->nr; - } + for (const int i_point : IndexRange(eval_size)) { + GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + i_point); } + GPU_indexbuf_add_primitive_restart(&elb); } GPU_indexbuf_build_in_place(&elb, ibo_curve_lines); diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 7f850435a64..47adc0acc60 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -632,14 +632,29 @@ void DRW_viewport_request_redraw(void) /** \name Duplis * \{ */ -static void drw_duplidata_load(DupliObject *dupli) +static uint dupli_key_hash(const void *key) { + const DupliKey *dupli_key = (const DupliKey *)key; + return BLI_ghashutil_ptrhash(dupli_key->ob) ^ BLI_ghashutil_ptrhash(dupli_key->ob_data); +} + +static bool dupli_key_cmp(const void *key1, const void *key2) +{ + const DupliKey *dupli_key1 = (const DupliKey *)key1; + const DupliKey *dupli_key2 = (const DupliKey *)key2; + return dupli_key1->ob != dupli_key2->ob || dupli_key1->ob_data != dupli_key2->ob_data; +} + +static void drw_duplidata_load(Object *ob) +{ + DupliObject *dupli = DST.dupli_source; if (dupli == NULL) { return; } - if (DST.dupli_origin != dupli->ob) { + if (DST.dupli_origin != dupli->ob || (DST.dupli_origin_data != dupli->ob_data)) { DST.dupli_origin = dupli->ob; + DST.dupli_origin_data = dupli->ob_data; } else { /* Same data as previous iter. No need to poll ghash for this. */ @@ -647,16 +662,23 @@ static void drw_duplidata_load(DupliObject *dupli) } if (DST.dupli_ghash == NULL) { - DST.dupli_ghash = BLI_ghash_ptr_new(__func__); + DST.dupli_ghash = BLI_ghash_new(dupli_key_hash, dupli_key_cmp, __func__); } + DupliKey *key = MEM_callocN(sizeof(DupliKey), __func__); + key->ob = dupli->ob; + key->ob_data = dupli->ob_data; + void **value; - if (!BLI_ghash_ensure_p(DST.dupli_ghash, DST.dupli_origin, &value)) { + if (!BLI_ghash_ensure_p(DST.dupli_ghash, key, &value)) { *value = MEM_callocN(sizeof(void *) * DST.enabled_engine_count, __func__); /* TODO: Meh a bit out of place but this is nice as it is - * only done once per "original" object. */ - drw_batch_cache_validate(DST.dupli_origin); + * only done once per instance type. */ + drw_batch_cache_validate(ob); + } + else { + MEM_freeN(key); } DST.dupli_datas = *(void ***)value; } @@ -670,12 +692,24 @@ static void duplidata_value_free(void *val) MEM_freeN(val); } +static void duplidata_key_free(void *key) +{ + DupliKey *dupli_key = (DupliKey *)key; + if (dupli_key->ob_data == dupli_key->ob->data) { + drw_batch_cache_generate_requested(dupli_key->ob); + } + else { + Object temp_object = *dupli_key->ob; + BKE_object_replace_data_on_shallow_copy(&temp_object, dupli_key->ob_data); + drw_batch_cache_generate_requested(&temp_object); + } + MEM_freeN(key); +} + static void drw_duplidata_free(void) { if (DST.dupli_ghash != NULL) { - BLI_ghash_free(DST.dupli_ghash, - (void (*)(void *key))drw_batch_cache_generate_requested, - duplidata_value_free); + BLI_ghash_free(DST.dupli_ghash, duplidata_key_free, duplidata_value_free); DST.dupli_ghash = NULL; } } @@ -1523,6 +1557,7 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph, /* Only iterate over objects for internal engines or when overlays are enabled */ if (do_populate_loop) { DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) { if ((object_type_exclude_viewport & (1 << ob->type)) != 0) { continue; @@ -1532,7 +1567,7 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph, } DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); drw_engines_cache_populate(ob); } DEG_OBJECT_ITER_FOR_RENDER_ENGINE_END; @@ -1881,12 +1916,13 @@ void DRW_render_object_iter( draw_ctx->v3d->object_type_exclude_viewport : 0; DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) { if ((object_type_exclude_viewport & (1 << ob->type)) == 0) { DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; DST.ob_handle = 0; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); if (!DST.dupli_source) { drw_batch_cache_validate(ob); @@ -2196,6 +2232,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, bool use_obedit_skip, bool draw_surface, bool UNUSED(use_nearest), + const bool do_material_sub_selection, const rcti *rect, DRW_SelectPassFn select_pass_fn, void *select_pass_user_data, @@ -2263,6 +2300,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, DST.viewport = viewport; DST.options.is_select = true; + DST.options.is_material_select = do_material_sub_selection; drw_task_graph_init(); /* Get list of enabled engines */ if (use_obedit) { @@ -2330,6 +2368,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, v3d->object_type_exclude_select); bool filter_exclude = false; DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) { if (!BKE_object_is_visible_in_viewport(v3d, ob)) { continue; @@ -2362,7 +2401,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, DRW_select_load_id(ob->runtime.select_id); DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); drw_engines_cache_populate(ob); } } @@ -2475,6 +2514,7 @@ static void drw_draw_depth_loop_impl(struct Depsgraph *depsgraph, const int object_type_exclude_viewport = v3d->object_type_exclude_viewport; DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (DST.draw_ctx.depsgraph, ob) { if ((object_type_exclude_viewport & (1 << ob->type)) != 0) { continue; @@ -2484,7 +2524,7 @@ static void drw_draw_depth_loop_impl(struct Depsgraph *depsgraph, } DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); drw_engines_cache_populate(ob); } DEG_OBJECT_ITER_FOR_RENDER_ENGINE_END; @@ -2738,6 +2778,11 @@ bool DRW_state_is_select(void) return DST.options.is_select; } +bool DRW_state_is_material_select(void) +{ + return DST.options.is_material_select; +} + bool DRW_state_is_depth(void) { return DST.options.is_depth; diff --git a/source/blender/draw/intern/draw_manager.h b/source/blender/draw/intern/draw_manager.h index 33e1a57198c..c09126c98ef 100644 --- a/source/blender/draw/intern/draw_manager.h +++ b/source/blender/draw/intern/draw_manager.h @@ -497,6 +497,11 @@ typedef struct DRWDebugSphere { /* ------------- DRAW MANAGER ------------ */ +typedef struct DupliKey { + struct Object *ob; + struct ID *ob_data; +} DupliKey; + #define DST_MAX_SLOTS 64 /* Cannot be changed without modifying RST.bound_tex_slots */ #define MAX_CLIP_PLANES 6 /* GL_MAX_CLIP_PLANES is at least 6 */ #define STENCIL_UNDEFINED 256 @@ -515,15 +520,19 @@ typedef struct DRWManager { /** Handle of next DRWPass to be allocated. */ DRWResourceHandle pass_handle; - /** Dupli state. NULL if not dupli. */ + /** Dupli object that corresponds to the current object. */ struct DupliObject *dupli_source; + /** Object that created the dupli-list the current object is part of. */ struct Object *dupli_parent; + /** Object referenced by the current dupli object. */ struct Object *dupli_origin; - /** Ghash containing original objects. */ + /** Object-data referenced by the current dupli object. */ + struct ID *dupli_origin_data; + /** Ghash: #DupliKey -> void pointer for each enabled engine. */ struct GHash *dupli_ghash; /** TODO(fclem): try to remove usage of this. */ DRWInstanceData *object_instance_data[MAX_INSTANCE_DATA_SIZE]; - /* Array of dupli_data (one for each enabled engine) to handle duplis. */ + /* Dupli data for the current dupli for each enabled engine. */ void **dupli_datas; /* Rendering state */ @@ -544,6 +553,7 @@ typedef struct DRWManager { struct { uint is_select : 1; + uint is_material_select : 1; uint is_depth : 1; uint is_image_render : 1; uint is_scene_render : 1; diff --git a/source/blender/editors/animation/anim_draw.c b/source/blender/editors/animation/anim_draw.c index 6d272bfc180..993d10cf303 100644 --- a/source/blender/editors/animation/anim_draw.c +++ b/source/blender/editors/animation/anim_draw.c @@ -521,6 +521,7 @@ static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_ MaskLayer *masklay = BKE_mask_layer_active(mask); mask_to_keylist(&ads, masklay, keylist); } + ED_keylist_prepare_for_direct_access(keylist); /* TODO(jbakker): Keylists are ordered, no need to do any searching at all. */ /* find matching keyframe in the right direction */ diff --git a/source/blender/editors/animation/anim_motion_paths.c b/source/blender/editors/animation/anim_motion_paths.c index d976f5f72ad..d130941b9bc 100644 --- a/source/blender/editors/animation/anim_motion_paths.c +++ b/source/blender/editors/animation/anim_motion_paths.c @@ -329,6 +329,7 @@ static void motionpath_calculate_update_range(MPathTarget *mpt, for (FCurve *fcu = fcurve_list->first; fcu != NULL; fcu = fcu->next) { struct AnimKeylist *keylist = ED_keylist_create(); fcurve_to_keylist(adt, fcu, keylist, 0); + ED_keylist_prepare_for_direct_access(keylist); int fcu_sfra = motionpath_get_prev_prev_keyframe(mpt, keylist, current_frame); int fcu_efra = motionpath_get_next_next_keyframe(mpt, keylist, current_frame); @@ -443,6 +444,7 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph, action_to_keylist(adt, adt->action, mpt->keylist, 0); } } + ED_keylist_prepare_for_direct_access(mpt->keylist); if (range == ANIMVIZ_CALC_RANGE_CHANGED) { int mpt_sfra, mpt_efra; diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c index 61918871b90..ac7db9f4f46 100644 --- a/source/blender/editors/animation/keyframes_draw.c +++ b/source/blender/editors/animation/keyframes_draw.c @@ -30,6 +30,7 @@ #include "BLI_dlrbTree.h" #include "BLI_listbase.h" #include "BLI_rect.h" +#include "BLI_task.h" #include "DNA_anim_types.h" #include "DNA_gpencil_types.h" @@ -346,10 +347,12 @@ static void draw_keylist_block(const DrawKeylistUIData *ctx, const ActKeyColumn } static void draw_keylist_blocks(const DrawKeylistUIData *ctx, - const ListBase * /*ActKeyColumn*/ columns, + const ActKeyColumn *keys, + const int key_len, float ypos) { - LISTBASE_FOREACH (ActKeyColumn *, ab, columns) { + for (int i = 0; i < key_len; i++) { + const ActKeyColumn *ab = &keys[i]; draw_keylist_block(ctx, ab, ypos); } } @@ -362,13 +365,15 @@ static bool draw_keylist_is_visible_key(const View2D *v2d, const ActKeyColumn *a static void draw_keylist_keys(const DrawKeylistUIData *ctx, View2D *v2d, const KeyframeShaderBindings *sh_bindings, - const ListBase * /*ActKeyColumn*/ keys, + const ActKeyColumn *keys, + const int key_len, float ypos, eSAction_Flag saction_flag) { short handle_type = KEYFRAME_HANDLE_NONE, extreme_type = KEYFRAME_EXTREME_NONE; - LISTBASE_FOREACH (ActKeyColumn *, ak, keys) { + for (int i = 0; i < key_len; i++) { + const ActKeyColumn *ak = &keys[i]; if (draw_keylist_is_visible_key(v2d, ak)) { if (ctx->show_ipo) { handle_type = ak->handle_type; @@ -469,8 +474,9 @@ static void ED_keylist_draw_list_elem_draw_blocks(AnimKeylistDrawListElem *elem, DrawKeylistUIData ctx; draw_keylist_ui_data_init(&ctx, v2d, elem->yscale_fac, elem->channel_locked, elem->saction_flag); - const ListBase *keys = ED_keylist_listbase(elem->keylist); - draw_keylist_blocks(&ctx, keys, elem->ypos); + const int key_len = ED_keylist_array_len(elem->keylist); + const ActKeyColumn *keys = ED_keylist_array(elem->keylist); + draw_keylist_blocks(&ctx, keys, key_len, elem->ypos); } static void ED_keylist_draw_list_elem_draw_keys(AnimKeylistDrawListElem *elem, @@ -479,8 +485,15 @@ static void ED_keylist_draw_list_elem_draw_keys(AnimKeylistDrawListElem *elem, { DrawKeylistUIData ctx; draw_keylist_ui_data_init(&ctx, v2d, elem->yscale_fac, elem->channel_locked, elem->saction_flag); - const ListBase *keys = ED_keylist_listbase(elem->keylist); - draw_keylist_keys(&ctx, v2d, sh_bindings, keys, elem->ypos, elem->saction_flag); + + const int key_len = ED_keylist_array_len(elem->keylist); + const ActKeyColumn *keys = ED_keylist_array(elem->keylist); + draw_keylist_keys(&ctx, v2d, sh_bindings, keys, key_len, elem->ypos, elem->saction_flag); +} + +static void ED_keylist_draw_list_elem_prepare_for_drawing(AnimKeylistDrawListElem *elem) +{ + ED_keylist_prepare_for_direct_access(elem->keylist); } typedef struct AnimKeylistDrawList { @@ -492,11 +505,25 @@ AnimKeylistDrawList *ED_keylist_draw_list_create(void) return MEM_callocN(sizeof(AnimKeylistDrawList), __func__); } +static void ED_keylist_draw_list_elem_build_task(void *__restrict UNUSED(userdata), + void *item, + int UNUSED(index), + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + AnimKeylistDrawListElem *elem = item; + ED_keylist_draw_list_elem_build_keylist(elem); + ED_keylist_draw_list_elem_prepare_for_drawing(elem); +} + static void ED_keylist_draw_list_build_keylists(AnimKeylistDrawList *draw_list) { - LISTBASE_FOREACH (AnimKeylistDrawListElem *, elem, &draw_list->channels) { - ED_keylist_draw_list_elem_build_keylist(elem); - } + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + /* Create a task per item, a single item is complex enough to deserve its own task. */ + settings.min_iter_per_thread = 1; + + BLI_task_parallel_listbase( + &draw_list->channels, NULL, ED_keylist_draw_list_elem_build_task, &settings); } static void ED_keylist_draw_list_draw_blocks(AnimKeylistDrawList *draw_list, View2D *v2d) diff --git a/source/blender/editors/animation/keyframes_keylist.cc b/source/blender/editors/animation/keyframes_keylist.cc index f6ade11a517..c1a18196a3a 100644 --- a/source/blender/editors/animation/keyframes_keylist.cc +++ b/source/blender/editors/animation/keyframes_keylist.cc @@ -23,15 +23,20 @@ /* System includes ----------------------------------------------------- */ +#include <algorithm> #include <cfloat> #include <cmath> #include <cstdlib> #include <cstring> +#include <functional> +#include <optional> #include "MEM_guardedalloc.h" +#include "BLI_array.hh" #include "BLI_dlrbTree.h" #include "BLI_listbase.h" +#include "BLI_math.h" #include "BLI_range.h" #include "BLI_utildefines.h" @@ -50,117 +55,294 @@ extern "C" { /* *************************** Keyframe Processing *************************** */ -struct AnimKeylist { - DLRBT_Tree keys; -}; +/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */ + +BLI_INLINE bool is_cfra_eq(const float a, const float b) +{ + return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH); +} -static void ED_keylist_init(AnimKeylist *keylist) +BLI_INLINE bool is_cfra_lt(const float a, const float b) { - BLI_dlrbTree_init(&keylist->keys); + return (b - a) > BEZT_BINARYSEARCH_THRESH; } +/* --------------- */ + +struct AnimKeylist { + /* Number of ActKeyColumn's in the keylist. */ + size_t column_len = 0; + + bool is_runtime_initialized = false; + + /* Before initializing the runtime, the key_columns list base is used to quickly add columns. + * Contains `ActKeyColumn`. Should not be used after runtime is initialized. */ + ListBase /* ActKeyColumn */ key_columns; + /* Last accessed column in the key_columns list base. Inserting columns are typically done in + * order. The last accessed column is used as starting point to search for a location to add or + * update the next column.*/ + std::optional<ActKeyColumn *> last_accessed_column = std::nullopt; + + struct { + /* When initializing the runtime the columns from the list base `AnimKeyList.key_columns` are + * transferred to an array to support binary searching and index based access. */ + blender::Array<ActKeyColumn> key_columns; + /* Wrapper around runtime.key_columns so it can still be accessed as a ListBase. Elements are + * owned by runtime.key_columns. */ + ListBase /* ActKeyColumn */ list_wrapper; + } runtime; + + AnimKeylist() + { + BLI_listbase_clear(&this->key_columns); + BLI_listbase_clear(&this->runtime.list_wrapper); + } + + ~AnimKeylist() + { + BLI_freelistN(&this->key_columns); + BLI_listbase_clear(&this->runtime.list_wrapper); + } + +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("editors:AnimKeylist") +#endif +}; + AnimKeylist *ED_keylist_create(void) { - AnimKeylist *keylist = static_cast<AnimKeylist *>(MEM_callocN(sizeof(AnimKeylist), __func__)); - ED_keylist_init(keylist); + AnimKeylist *keylist = new AnimKeylist(); return keylist; } void ED_keylist_free(AnimKeylist *keylist) { BLI_assert(keylist); - BLI_dlrbTree_free(&keylist->keys); - MEM_freeN(keylist); + delete keylist; } -const ActKeyColumn *ED_keylist_find_exact(const AnimKeylist *keylist, float cfra) +static void ED_keylist_convert_key_columns_to_array(AnimKeylist *keylist) { - return (const ActKeyColumn *)BLI_dlrbTree_search_exact( - &keylist->keys, compare_ak_cfraPtr, &cfra); + size_t index; + LISTBASE_FOREACH_INDEX (ActKeyColumn *, key, &keylist->key_columns, index) { + keylist->runtime.key_columns[index] = *key; + } } -const ActKeyColumn *ED_keylist_find_next(const AnimKeylist *keylist, float cfra) +static void ED_keylist_runtime_update_key_column_next_prev(AnimKeylist *keylist) { - return (const ActKeyColumn *)BLI_dlrbTree_search_next(&keylist->keys, compare_ak_cfraPtr, &cfra); + for (size_t index = 0; index < keylist->column_len; index++) { + const bool is_first = (index == 0); + keylist->runtime.key_columns[index].prev = is_first ? nullptr : + &keylist->runtime.key_columns[index - 1]; + const bool is_last = (index == keylist->column_len - 1); + keylist->runtime.key_columns[index].next = is_last ? nullptr : + &keylist->runtime.key_columns[index + 1]; + } } -const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, float cfra) +static void ED_keylist_runtime_init_listbase(AnimKeylist *keylist) { - return (const ActKeyColumn *)BLI_dlrbTree_search_prev(&keylist->keys, compare_ak_cfraPtr, &cfra); + if (ED_keylist_is_empty(keylist)) { + BLI_listbase_clear(&keylist->runtime.list_wrapper); + return; + } + + keylist->runtime.list_wrapper.first = &keylist->runtime.key_columns[0]; + keylist->runtime.list_wrapper.last = &keylist->runtime.key_columns[keylist->column_len - 1]; } -/* TODO(jbakker): Should we change this to use `ED_keylist_find_next(keys, min_fra)` and only check - * boundary of `max_fra`. */ -const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist, - const Range2f frame_range) +static void ED_keylist_runtime_init(AnimKeylist *keylist) { - for (const ActKeyColumn *ak = static_cast<const ActKeyColumn *>(keylist->keys.root); ak; - ak = static_cast<const ActKeyColumn *>((ak->cfra < frame_range.min) ? ak->right : - ak->left)) { - if (range2f_in_range(&frame_range, ak->cfra)) { - return ak; - } + BLI_assert(!keylist->is_runtime_initialized); + + keylist->runtime.key_columns = blender::Array<ActKeyColumn>(keylist->column_len); + + /* Convert linked list to array to support fast searching. */ + ED_keylist_convert_key_columns_to_array(keylist); + /* Ensure that the array can also be used as a listbase for external usages. */ + ED_keylist_runtime_update_key_column_next_prev(keylist); + ED_keylist_runtime_init_listbase(keylist); + + keylist->is_runtime_initialized = true; +} + +static void ED_keylist_reset_last_accessed(AnimKeylist *keylist) +{ + BLI_assert(!keylist->is_runtime_initialized); + keylist->last_accessed_column.reset(); +} + +void ED_keylist_prepare_for_direct_access(AnimKeylist *keylist) +{ + if (keylist->is_runtime_initialized) { + return; + } + ED_keylist_runtime_init(keylist); +} + +static const ActKeyColumn *ED_keylist_find_lower_bound(const AnimKeylist *keylist, + const float cfra) +{ + BLI_assert(!ED_keylist_is_empty(keylist)); + const ActKeyColumn *begin = std::begin(keylist->runtime.key_columns); + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + ActKeyColumn value; + value.cfra = cfra; + + const ActKeyColumn *found_column = std::lower_bound( + begin, end, value, [](const ActKeyColumn &column, const ActKeyColumn &other) { + return is_cfra_lt(column.cfra, other.cfra); + }); + return found_column; +} + +static const ActKeyColumn *ED_keylist_find_upper_bound(const AnimKeylist *keylist, + const float cfra) +{ + BLI_assert(!ED_keylist_is_empty(keylist)); + const ActKeyColumn *begin = std::begin(keylist->runtime.key_columns); + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + ActKeyColumn value; + value.cfra = cfra; + + const ActKeyColumn *found_column = std::upper_bound( + begin, end, value, [](const ActKeyColumn &column, const ActKeyColumn &other) { + return is_cfra_lt(column.cfra, other.cfra); + }); + return found_column; +} + +const ActKeyColumn *ED_keylist_find_exact(const AnimKeylist *keylist, const float cfra) +{ + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); + + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + const ActKeyColumn *found_column = ED_keylist_find_lower_bound(keylist, cfra); + + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + if (found_column == end) { + return nullptr; + } + if (is_cfra_eq(found_column->cfra, cfra)) { + return found_column; } return nullptr; } -bool ED_keylist_is_empty(const struct AnimKeylist *keylist) +const ActKeyColumn *ED_keylist_find_next(const AnimKeylist *keylist, const float cfra) { - return keylist->keys.root == nullptr; + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); + + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + const ActKeyColumn *found_column = ED_keylist_find_upper_bound(keylist, cfra); + + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + if (found_column == end) { + return nullptr; + } + return found_column; } -const struct ListBase *ED_keylist_listbase(const AnimKeylist *keylist) +const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, const float cfra) { - return (ListBase *)&keylist->keys; + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); + + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + const ActKeyColumn *found_column = ED_keylist_find_lower_bound(keylist, cfra); + + if (found_column == end) { + /* Nothing found, return the last item. */ + return end - 1; + } + + const ActKeyColumn *prev_column = found_column->prev; + return prev_column; } -bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range) +const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist, + const Range2f frame_range) { - BLI_assert(r_frame_range); + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); if (ED_keylist_is_empty(keylist)) { - return false; + return nullptr; } - const ActKeyColumn *first_column = (const ActKeyColumn *)keylist->keys.first; - r_frame_range->min = first_column->cfra; + const ActKeyColumn *column = ED_keylist_find_lower_bound(keylist, frame_range.min); + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + if (column == end) { + return nullptr; + } + if (column->cfra >= frame_range.max) { + return nullptr; + } + return column; +} - const ActKeyColumn *last_column = (const ActKeyColumn *)keylist->keys.last; - r_frame_range->max = last_column->cfra; +const ActKeyColumn *ED_keylist_array(const struct AnimKeylist *keylist) +{ + BLI_assert_msg( + keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before accessing array."); + return keylist->runtime.key_columns.data(); +} - return true; +int64_t ED_keylist_array_len(const struct AnimKeylist *keylist) +{ + return keylist->column_len; } -/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */ -BLI_INLINE bool is_cfra_eq(const float a, const float b) +bool ED_keylist_is_empty(const struct AnimKeylist *keylist) { - return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH); + return keylist->column_len == 0; } -BLI_INLINE bool is_cfra_lt(const float a, const float b) +const struct ListBase *ED_keylist_listbase(const AnimKeylist *keylist) { - return (b - a) > BEZT_BINARYSEARCH_THRESH; + if (keylist->is_runtime_initialized) { + return &keylist->runtime.list_wrapper; + } + return &keylist->key_columns; } -/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ -/* NOTE: this is exported to other modules that use the ActKeyColumns for finding keyframes */ -short compare_ak_cfraPtr(void *node, void *data) +bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range) { - ActKeyColumn *ak = (ActKeyColumn *)node; - const float *cframe = static_cast<const float *>(data); - const float val = *cframe; + BLI_assert(r_frame_range); - if (is_cfra_eq(val, ak->cfra)) { - return 0; + if (ED_keylist_is_empty(keylist)) { + return false; } - if (val < ak->cfra) { - return -1; + const ActKeyColumn *first_column; + const ActKeyColumn *last_column; + if (keylist->is_runtime_initialized) { + first_column = &keylist->runtime.key_columns[0]; + last_column = &keylist->runtime.key_columns[keylist->column_len - 1]; } - return 1; -} + else { + first_column = static_cast<const ActKeyColumn *>(keylist->key_columns.first); + last_column = static_cast<const ActKeyColumn *>(keylist->key_columns.last); + } + r_frame_range->min = first_column->cfra; + r_frame_range->max = last_column->cfra; -/* --------------- */ + return true; +} /* Set of references to three logically adjacent keys. */ struct BezTripleChain { @@ -243,16 +425,8 @@ static eKeyframeExtremeDrawOpts bezt_extreme_type(const BezTripleChain *chain) return KEYFRAME_EXTREME_NONE; } -/* Comparator callback used for ActKeyColumns and BezTripleChain */ -static short compare_ak_bezt(void *node, void *data) -{ - BezTripleChain *chain = static_cast<BezTripleChain *>(data); - - return compare_ak_cfraPtr(node, &chain->cur->vec[1][0]); -} - /* New node callback used for building ActKeyColumns from BezTripleChain */ -static DLRBT_Node *nalloc_ak_bezt(void *data) +static ActKeyColumn *nalloc_ak_bezt(void *data) { ActKeyColumn *ak = static_cast<ActKeyColumn *>( MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumn")); @@ -269,13 +443,12 @@ static DLRBT_Node *nalloc_ak_bezt(void *data) /* count keyframes in this column */ ak->totkey = 1; - return (DLRBT_Node *)ak; + return ak; } /* Node updater callback used for building ActKeyColumns from BezTripleChain */ -static void nupdate_ak_bezt(void *node, void *data) +static void nupdate_ak_bezt(ActKeyColumn *ak, void *data) { - ActKeyColumn *ak = static_cast<ActKeyColumn *>(node); const BezTripleChain *chain = static_cast<const BezTripleChain *>(data); const BezTriple *bezt = chain->cur; @@ -312,17 +485,8 @@ static void nupdate_ak_bezt(void *node, void *data) /* ......... */ -/* Comparator callback used for ActKeyColumns and GPencil frame */ -static short compare_ak_gpframe(void *node, void *data) -{ - const bGPDframe *gpf = (bGPDframe *)data; - - float frame = gpf->framenum; - return compare_ak_cfraPtr(node, &frame); -} - /* New node callback used for building ActKeyColumns from GPencil frames */ -static DLRBT_Node *nalloc_ak_gpframe(void *data) +static ActKeyColumn *nalloc_ak_gpframe(void *data) { ActKeyColumn *ak = static_cast<ActKeyColumn *>( MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF")); @@ -340,14 +504,13 @@ static DLRBT_Node *nalloc_ak_gpframe(void *data) ak->block.sel = ak->sel; ak->block.flag |= ACTKEYBLOCK_FLAG_GPENCIL; - return (DLRBT_Node *)ak; + return ak; } /* Node updater callback used for building ActKeyColumns from GPencil frames */ -static void nupdate_ak_gpframe(void *node, void *data) +static void nupdate_ak_gpframe(ActKeyColumn *ak, void *data) { - ActKeyColumn *ak = (ActKeyColumn *)node; - const bGPDframe *gpf = (bGPDframe *)data; + bGPDframe *gpf = (bGPDframe *)data; /* set selection status and 'touched' status */ if (gpf->flag & GP_FRAME_SELECT) { @@ -366,17 +529,8 @@ static void nupdate_ak_gpframe(void *node, void *data) /* ......... */ -/* Comparator callback used for ActKeyColumns and GPencil frame */ -static short compare_ak_masklayshape(void *node, void *data) -{ - const MaskLayerShape *masklay_shape = (const MaskLayerShape *)data; - - float frame = masklay_shape->frame; - return compare_ak_cfraPtr(node, &frame); -} - /* New node callback used for building ActKeyColumns from GPencil frames */ -static DLRBT_Node *nalloc_ak_masklayshape(void *data) +static ActKeyColumn *nalloc_ak_masklayshape(void *data) { ActKeyColumn *ak = static_cast<ActKeyColumn *>( MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF")); @@ -389,14 +543,13 @@ static DLRBT_Node *nalloc_ak_masklayshape(void *data) /* count keyframes in this column */ ak->totkey = 1; - return (DLRBT_Node *)ak; + return ak; } /* Node updater callback used for building ActKeyColumns from GPencil frames */ -static void nupdate_ak_masklayshape(void *node, void *data) +static void nupdate_ak_masklayshape(ActKeyColumn *ak, void *data) { - ActKeyColumn *ak = (ActKeyColumn *)node; - const MaskLayerShape *masklay_shape = (const MaskLayerShape *)data; + MaskLayerShape *masklay_shape = (MaskLayerShape *)data; /* set selection status and 'touched' status */ if (masklay_shape->flag & MASK_SHAPE_SELECT) { @@ -408,6 +561,95 @@ static void nupdate_ak_masklayshape(void *node, void *data) } /* --------------- */ +using KeylistCreateColumnFunction = std::function<ActKeyColumn *(void *userdata)>; +using KeylistUpdateColumnFunction = std::function<void(ActKeyColumn *, void *)>; + +/* `ED_keylist_find_neighbour_front_to_back` is called before the runtime can be initialized so we + * cannot use bin searching. */ +static ActKeyColumn *ED_keylist_find_neighbour_front_to_back(ActKeyColumn *cursor, float cfra) +{ + while (cursor->next && cursor->next->cfra <= cfra) { + cursor = cursor->next; + } + return cursor; +} + +/* `ED_keylist_find_neighbour_back_to_front` is called before the runtime can be initialized so we + * cannot use bin searching. */ +static ActKeyColumn *ED_keylist_find_neighbour_back_to_front(ActKeyColumn *cursor, float cfra) +{ + while (cursor->prev && cursor->prev->cfra >= cfra) { + cursor = cursor->prev; + } + return cursor; +} + +/* + * `ED_keylist_find_exact_or_neighbour_column` is called before the runtime can be initialized so + * we cannot use bin searching. + * + * This function is called to add or update columns in the keylist. + * Typically columns are sorted by frame number so keeping track of the last_accessed_column + * reduces searching. + */ +static ActKeyColumn *ED_keylist_find_exact_or_neighbour_column(AnimKeylist *keylist, float cfra) +{ + BLI_assert(!keylist->is_runtime_initialized); + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + ActKeyColumn *cursor = keylist->last_accessed_column.value_or( + static_cast<ActKeyColumn *>(keylist->key_columns.first)); + if (!is_cfra_eq(cursor->cfra, cfra)) { + const bool walking_direction_front_to_back = cursor->cfra <= cfra; + if (walking_direction_front_to_back) { + cursor = ED_keylist_find_neighbour_front_to_back(cursor, cfra); + } + else { + cursor = ED_keylist_find_neighbour_back_to_front(cursor, cfra); + } + } + + keylist->last_accessed_column = cursor; + return cursor; +} + +static void ED_keylist_add_or_update_column(AnimKeylist *keylist, + float cfra, + KeylistCreateColumnFunction create_func, + KeylistUpdateColumnFunction update_func, + void *userdata) +{ + BLI_assert_msg( + !keylist->is_runtime_initialized, + "Modifying AnimKeylist isn't allowed after runtime is initialized " + "keylist->key_columns/columns_len will get out of sync with runtime.key_columns."); + if (ED_keylist_is_empty(keylist)) { + ActKeyColumn *key_column = create_func(userdata); + BLI_addhead(&keylist->key_columns, key_column); + keylist->column_len += 1; + keylist->last_accessed_column = key_column; + return; + } + + ActKeyColumn *nearest = ED_keylist_find_exact_or_neighbour_column(keylist, cfra); + if (is_cfra_eq(nearest->cfra, cfra)) { + update_func(nearest, userdata); + } + else if (is_cfra_lt(nearest->cfra, cfra)) { + ActKeyColumn *key_column = create_func(userdata); + BLI_insertlinkafter(&keylist->key_columns, nearest, key_column); + keylist->column_len += 1; + keylist->last_accessed_column = key_column; + } + else { + ActKeyColumn *key_column = create_func(userdata); + BLI_insertlinkbefore(&keylist->key_columns, nearest, key_column); + keylist->column_len += 1; + keylist->last_accessed_column = key_column; + } +} /* Add the given BezTriple to the given 'list' of Keyframes */ static void add_bezt_to_keycolumns_list(AnimKeylist *keylist, BezTripleChain *bezt) @@ -416,7 +658,8 @@ static void add_bezt_to_keycolumns_list(AnimKeylist *keylist, BezTripleChain *be return; } - BLI_dlrbTree_add(&keylist->keys, compare_ak_bezt, nalloc_ak_bezt, nupdate_ak_bezt, bezt); + float cfra = bezt->cur->vec[1][0]; + ED_keylist_add_or_update_column(keylist, cfra, nalloc_ak_bezt, nupdate_ak_bezt, bezt); } /* Add the given GPencil Frame to the given 'list' of Keyframes */ @@ -426,7 +669,8 @@ static void add_gpframe_to_keycolumns_list(AnimKeylist *keylist, bGPDframe *gpf) return; } - BLI_dlrbTree_add(&keylist->keys, compare_ak_gpframe, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf); + float cfra = gpf->framenum; + ED_keylist_add_or_update_column(keylist, cfra, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf); } /* Add the given MaskLayerShape Frame to the given 'list' of Keyframes */ @@ -436,11 +680,9 @@ static void add_masklay_to_keycolumns_list(AnimKeylist *keylist, MaskLayerShape return; } - BLI_dlrbTree_add(&keylist->keys, - compare_ak_masklayshape, - nalloc_ak_masklayshape, - nupdate_ak_masklayshape, - masklay_shape); + float cfra = masklay_shape->frame; + ED_keylist_add_or_update_column( + keylist, cfra, nalloc_ak_masklayshape, nupdate_ak_masklayshape, masklay_shape); } /* ActKeyBlocks (Long Keyframes) ------------------------------------------ */ @@ -476,7 +718,7 @@ static void compute_keyblock_data(ActKeyBlockInfo *info, hold = IS_EQF(beztn->vec[1][1], beztn->vec[0][1]) && IS_EQF(prev->vec[1][1], prev->vec[2][1]); } - /* This interpolation type induces movement even between identical keys. */ + /* This interpolation type induces movement even between identical columns. */ else { hold = !ELEM(prev->ipo, BEZT_IPO_ELASTIC); } @@ -514,7 +756,7 @@ static void add_keyblock_info(ActKeyColumn *col, const ActKeyBlockInfo *block) static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, const int bezt_len) { - ActKeyColumn *col = static_cast<ActKeyColumn *>(keylist->keys.first); + ActKeyColumn *col = static_cast<ActKeyColumn *>(keylist->key_columns.first); if (bezt && bezt_len >= 2) { ActKeyBlockInfo block; @@ -532,19 +774,15 @@ static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, co if (is_cfra_lt(bezt[1].vec[1][0], bezt[0].vec[1][0])) { /* Backtrack to find the right location. */ if (is_cfra_lt(bezt[1].vec[1][0], col->cfra)) { - ActKeyColumn *newcol = (ActKeyColumn *)BLI_dlrbTree_search_exact( - &keylist->keys, compare_ak_cfraPtr, &bezt[1].vec[1][0]); + ActKeyColumn *newcol = ED_keylist_find_exact_or_neighbour_column(keylist, col->cfra); - if (newcol != nullptr) { - col = newcol; + BLI_assert(newcol); + BLI_assert(newcol->cfra == col->cfra); - /* The previous keyblock is garbage too. */ - if (col->prev != nullptr) { - add_keyblock_info(col->prev, &dummy_keyblock); - } - } - else { - BLI_assert(false); + col = newcol; + /* The previous keyblock is garbage too. */ + if (col->prev != nullptr) { + add_keyblock_info(col->prev, &dummy_keyblock); } } @@ -577,20 +815,17 @@ static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, co */ static void update_keyblocks(AnimKeylist *keylist, BezTriple *bezt, const int bezt_len) { - /* Recompute the prev/next linked list. */ - BLI_dlrbTree_linkedlist_sync(&keylist->keys); - /* Find the curve count */ int max_curve = 0; - LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->keys) { + LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->key_columns) { max_curve = MAX2(max_curve, col->totcurve); } /* Propagate blocks to inserted keys */ ActKeyColumn *prev_ready = nullptr; - LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->keys) { + LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->key_columns) { /* Pre-existing column. */ if (col->totcurve > 0) { prev_ready = col; @@ -775,6 +1010,7 @@ void cachefile_to_keylist(bDopeSheet *ads, void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const int saction_flag) { if (fcu && fcu->totvert && fcu->bezt) { + ED_keylist_reset_last_accessed(keylist); /* apply NLA-mapping (if applicable) */ if (adt) { ANIM_nla_mapping_apply_fcurve(adt, fcu, false, false); @@ -790,7 +1026,7 @@ void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const i for (int v = 0; v < fcu->totvert; v++) { chain.cur = &fcu->bezt[v]; - /* Neighbor keys, accounting for being cyclic. */ + /* Neighbor columns, accounting for being cyclic. */ if (do_extremes) { chain.prev = (v > 0) ? &fcu->bezt[v - 1] : is_cyclic ? &fcu->bezt[fcu->totvert - 2] : @@ -856,6 +1092,7 @@ void gpencil_to_keylist(bDopeSheet *ads, bGPdata *gpd, AnimKeylist *keylist, con void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, AnimKeylist *keylist) { if (gpl && keylist) { + ED_keylist_reset_last_accessed(keylist); /* Although the frames should already be in an ordered list, * they are not suitable for displaying yet. */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { @@ -869,6 +1106,7 @@ void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, AnimKeylist *keylis void mask_to_keylist(bDopeSheet *UNUSED(ads), MaskLayer *masklay, AnimKeylist *keylist) { if (masklay && keylist) { + ED_keylist_reset_last_accessed(keylist); LISTBASE_FOREACH (MaskLayerShape *, masklay_shape, &masklay->splines_shapes) { add_masklay_to_keycolumns_list(keylist, masklay_shape); } diff --git a/source/blender/editors/armature/armature_add.c b/source/blender/editors/armature/armature_add.c index 45bf18fe1bb..21a5c6c2865 100644 --- a/source/blender/editors/armature/armature_add.c +++ b/source/blender/editors/armature/armature_add.c @@ -259,7 +259,7 @@ static int armature_click_extrude_invoke(bContext *C, wmOperator *op, const wmEv void ARMATURE_OT_click_extrude(wmOperatorType *ot) { /* identifiers */ - ot->name = "Click-Extrude"; + ot->name = "Extrude to Cursor"; ot->idname = "ARMATURE_OT_click_extrude"; ot->description = "Create a new bone going from the last selected joint to the mouse position"; @@ -269,7 +269,7 @@ void ARMATURE_OT_click_extrude(wmOperatorType *ot) ot->poll = ED_operator_editarmature; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* props */ } diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index bc5cbd92deb..f23376867af 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -976,6 +976,7 @@ static int pose_slide_invoke_common(bContext *C, wmOperator *op, const wmEvent * } /* Cancel if no keyframes found. */ + ED_keylist_prepare_for_direct_access(pso->keylist); if (ED_keylist_is_empty(pso->keylist)) { BKE_report(op->reports, RPT_ERROR, "No keyframes to slide between"); pose_slide_exit(C, op); @@ -1267,6 +1268,8 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Perform pose updates - in response to some user action * (e.g. pressing a key or moving the mouse). */ if (do_pose_update) { + RNA_float_set(op->ptr, "factor", ED_slider_factor_get(pso->slider)); + /* Update percentage indicator in header. */ pose_slide_draw_status(C, pso); @@ -1712,6 +1715,7 @@ static float pose_propagate_get_boneHoldEndFrame(tPChanFCurveLink *pfl, float st FCurve *fcu = (FCurve *)ld->data; fcurve_to_keylist(adt, fcu, keylist, 0); } + ED_keylist_prepare_for_direct_access(keylist); /* Find the long keyframe (i.e. hold), and hence obtain the endFrame value * - the best case would be one that starts on the frame itself diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index c399abfa52d..9b43e23bd32 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -5620,7 +5620,7 @@ static int add_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) void CURVE_OT_vertex_add(wmOperatorType *ot) { /* identifiers */ - ot->name = "Add Vertex"; + ot->name = "Extrude to Cursor or Add"; ot->idname = "CURVE_OT_vertex_add"; ot->description = "Add a new control point (linked to only selected end-curve one, if any)"; @@ -5630,7 +5630,7 @@ void CURVE_OT_vertex_add(wmOperatorType *ot) ot->poll = ED_operator_editcurve; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ RNA_def_float_vector_xyz(ot->srna, diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c index d1fe162fc4a..75fb17e8cc1 100644 --- a/source/blender/editors/curve/editcurve_add.c +++ b/source/blender/editors/curve/editcurve_add.c @@ -515,7 +515,6 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) bool newob = false; bool enter_editmode; ushort local_view_bits; - float dia; float loc[3], rot[3]; float mat[4][4]; @@ -535,7 +534,6 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) newob = true; cu = (Curve *)obedit->data; - cu->flag |= CU_DEFORM_FILL; if (type & CU_PRIM_PATH) { cu->flag |= CU_PATH | CU_3D; @@ -556,9 +554,10 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) } } - ED_object_new_primitive_matrix(C, obedit, loc, rot, mat); - dia = RNA_float_get(op->ptr, "radius"); - mul_mat3_m4_fl(mat, dia); + float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); + ED_object_new_primitive_matrix(C, obedit, loc, rot, scale, mat); nu = ED_curve_add_nurbs_primitive(C, obedit, mat, type, newob); editnurb = object_editcurve_get(obedit); diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt index c4916b9182f..702fd2e375a 100644 --- a/source/blender/editors/datafiles/CMakeLists.txt +++ b/source/blender/editors/datafiles/CMakeLists.txt @@ -336,6 +336,7 @@ set(ICON_NAMES lightprobe_cubemap lightprobe_planar lightprobe_grid + mod_dash color_red color_green color_blue @@ -390,6 +391,9 @@ set(ICON_NAMES small_caps modifier con_action + mod_length + mod_dash + mod_lineart holdout_off holdout_on indirect_only_off diff --git a/source/blender/editors/gizmo_library/CMakeLists.txt b/source/blender/editors/gizmo_library/CMakeLists.txt index eeb1e60166b..bfe8334b390 100644 --- a/source/blender/editors/gizmo_library/CMakeLists.txt +++ b/source/blender/editors/gizmo_library/CMakeLists.txt @@ -27,6 +27,7 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager + ../../../../intern/clog ../../../../intern/eigen ../../../../intern/glew-mx ../../../../intern/guardedalloc diff --git a/source/blender/editors/gizmo_library/gizmo_library_utils.c b/source/blender/editors/gizmo_library/gizmo_library_utils.c index 7d0ae5afb9b..a0db2a8e627 100644 --- a/source/blender/editors/gizmo_library/gizmo_library_utils.c +++ b/source/blender/editors/gizmo_library/gizmo_library_utils.c @@ -39,9 +39,13 @@ #include "ED_view3d.h" +#include "CLG_log.h" + /* own includes */ #include "gizmo_library_intern.h" +static CLG_LogRef LOG = {"ed.gizmo.library_utils"}; + /* factor for precision tweaking */ #define GIZMO_PRECISION_FAC 0.05f @@ -182,7 +186,7 @@ bool gizmo_window_project_2d(bContext *C, bool use_offset, float r_co[2]) { - float mat[4][4]; + float mat[4][4], imat[4][4]; { float mat_identity[4][4]; struct WM_GizmoMatrixParams params = {NULL}; @@ -193,6 +197,14 @@ bool gizmo_window_project_2d(bContext *C, WM_gizmo_calc_matrix_final_params(gz, ¶ms, mat); } + if (!invert_m4_m4(imat, mat)) { + CLOG_WARN(&LOG, + "Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted " + "(projection will fail)", + gz->type->idname, + gz->parent_gzgroup->type->idname); + } + /* rotate mouse in relation to the center and relocate it */ if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) { /* For 3d views, transform 2D mouse pos onto plane. */ @@ -202,8 +214,6 @@ bool gizmo_window_project_2d(bContext *C, plane_from_point_normal_v3(plane, mat[3], mat[2]); bool clip_ray = ((RegionView3D *)region->regiondata)->is_persp; if (ED_view3d_win_to_3d_on_plane(region, plane, mval, clip_ray, co)) { - float imat[4][4]; - invert_m4_m4(imat, mat); mul_m4_v3(imat, co); r_co[0] = co[(axis + 1) % 3]; r_co[1] = co[(axis + 2) % 3]; @@ -213,8 +223,6 @@ bool gizmo_window_project_2d(bContext *C, } float co[3] = {mval[0], mval[1], 0.0f}; - float imat[4][4]; - invert_m4_m4(imat, mat); mul_m4_v3(imat, co); copy_v2_v2(r_co, co); return true; @@ -223,7 +231,7 @@ bool gizmo_window_project_2d(bContext *C, bool gizmo_window_project_3d( bContext *C, const struct wmGizmo *gz, const float mval[2], bool use_offset, float r_co[3]) { - float mat[4][4]; + float mat[4][4], imat[4][4]; { float mat_identity[4][4]; struct WM_GizmoMatrixParams params = {NULL}; @@ -234,20 +242,25 @@ bool gizmo_window_project_3d( WM_gizmo_calc_matrix_final_params(gz, ¶ms, mat); } + if (!invert_m4_m4(imat, mat)) { + CLOG_WARN(&LOG, + "Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted " + "(projection will fail)", + gz->type->idname, + gz->parent_gzgroup->type->idname); + } + if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) { View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); /* NOTE: we might want a custom reference point passed in, * instead of the gizmo center. */ ED_view3d_win_to_3d(v3d, region, mat[3], mval, r_co); - invert_m4(mat); - mul_m4_v3(mat, r_co); + mul_m4_v3(imat, r_co); return true; } float co[3] = {mval[0], mval[1], 0.0f}; - float imat[4][4]; - invert_m4_m4(imat, mat); mul_m4_v3(imat, co); copy_v2_v2(r_co, co); return true; diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index 65c5b8ee573..68e2aece6e2 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -1418,7 +1418,7 @@ static void annotation_visible_on_space(tGPsdata *p) } case SPACE_SEQ: { SpaceSeq *sseq = (SpaceSeq *)area->spacedata.first; - sseq->flag |= SEQ_SHOW_GPENCIL; + sseq->flag |= SEQ_PREVIEW_SHOW_GPENCIL; break; } case SPACE_IMAGE: { diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index aa3178ddc2c..75ddfa47c57 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -5243,7 +5243,7 @@ void GPENCIL_OT_stroke_cutter(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flag */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_gesture_lasso(ot); diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index 93bae7d3614..6ad2fffc773 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -2347,7 +2347,7 @@ void GPENCIL_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flags */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_select_operation(ot); diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 72d10d840fa..bb05b93ad81 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -3284,7 +3284,8 @@ bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C, gpencil_point_to_parent_space(pt, diff_mat, &pt_parent); gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_end[0], &pt2d_target_end[1]); - /* If the distance to the original stroke extremes is too big, the stroke must not be joined. */ + /* If the distance to the original stroke extremes is too big, the stroke must not be joined. + */ if ((len_squared_v2v2(ctrl1, pt2d_target_start) > radius_sqr) && (len_squared_v2v2(ctrl1, pt2d_target_end) > radius_sqr) && (len_squared_v2v2(ctrl2, pt2d_target_start) > radius_sqr) && diff --git a/source/blender/editors/gpencil/gpencil_vertex_ops.c b/source/blender/editors/gpencil/gpencil_vertex_ops.c index 402bccce2f7..5c3a7cf9e6f 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_ops.c +++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c @@ -588,6 +588,7 @@ static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op) changed = true; copy_v3_v3(gps->vert_color_fill, brush->rgb); gps->vert_color_fill[3] = factor; + srgb_to_linearrgb_v4(gps->vert_color_fill, gps->vert_color_fill); } /* Stroke points. */ @@ -596,10 +597,13 @@ static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op) int i; bGPDspoint *pt; + float color[4]; + copy_v3_v3(color, brush->rgb); + color[3] = factor; + srgb_to_linearrgb_v4(color, color); for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if ((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) { - copy_v3_v3(pt->vert_color, brush->rgb); - pt->vert_color[3] = factor; + copy_v3_v3(pt->vert_color, color); } } } diff --git a/source/blender/editors/include/ED_keyframes_keylist.h b/source/blender/editors/include/ED_keyframes_keylist.h index 3a9750c1206..4194444ca0f 100644 --- a/source/blender/editors/include/ED_keyframes_keylist.h +++ b/source/blender/editors/include/ED_keyframes_keylist.h @@ -139,14 +139,20 @@ typedef enum eKeyframeExtremeDrawOpts { struct AnimKeylist *ED_keylist_create(void); void ED_keylist_free(struct AnimKeylist *keylist); -const struct ActKeyColumn *ED_keylist_find_exact(const struct AnimKeylist *keylist, float cfra); -const struct ActKeyColumn *ED_keylist_find_next(const struct AnimKeylist *keylist, float cfra); -const struct ActKeyColumn *ED_keylist_find_prev(const struct AnimKeylist *keylist, float cfra); +void ED_keylist_prepare_for_direct_access(struct AnimKeylist *keylist); +const struct ActKeyColumn *ED_keylist_find_exact(const struct AnimKeylist *keylist, + const float cfra); +const struct ActKeyColumn *ED_keylist_find_next(const struct AnimKeylist *keylist, + const float cfra); +const struct ActKeyColumn *ED_keylist_find_prev(const struct AnimKeylist *keylist, + const float cfra); const struct ActKeyColumn *ED_keylist_find_any_between(const struct AnimKeylist *keylist, const Range2f frame_range); bool ED_keylist_is_empty(const struct AnimKeylist *keylist); const struct ListBase /* ActKeyColumn */ *ED_keylist_listbase(const struct AnimKeylist *keylist); bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range); +const ActKeyColumn *ED_keylist_array(const struct AnimKeylist *keylist); +int64_t ED_keylist_array_len(const struct AnimKeylist *keylist); /* Key-data Generation --------------- */ @@ -197,8 +203,6 @@ void mask_to_keylist(struct bDopeSheet *ads, struct AnimKeylist *keylist); /* ActKeyColumn API ---------------- */ -/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ -short compare_ak_cfraPtr(void *node, void *data); /* Checks if ActKeyColumn has any block data */ bool actkeyblock_is_valid(const ActKeyColumn *ac); diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h index a9cf04e1ad7..5397cd95ace 100644 --- a/source/blender/editors/include/ED_object.h +++ b/source/blender/editors/include/ED_object.h @@ -286,6 +286,7 @@ float ED_object_new_primitive_matrix(struct bContext *C, struct Object *obedit, const float loc[3], const float rot[3], + const float scale[3], float primmat[4][4]); /* Avoid allowing too much insane values even by typing diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 2c958d282f9..cf8dcbd7995 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -566,6 +566,13 @@ eV3DSelectObjectFilter ED_view3d_select_filter_from_mode(const struct Scene *sce void view3d_opengl_select_cache_begin(void); void view3d_opengl_select_cache_end(void); +int view3d_opengl_select_ex(struct ViewContext *vc, + unsigned int *buffer, + unsigned int bufsize, + const struct rcti *input, + eV3DSelectMode select_mode, + eV3DSelectObjectFilter select_filter, + const bool do_material_slot_selection); int view3d_opengl_select(struct ViewContext *vc, unsigned int *buffer, unsigned int bufsize, @@ -638,6 +645,9 @@ void ED_view3d_draw_setup_view(const struct wmWindowManager *wm, struct Base *ED_view3d_give_base_under_cursor(struct bContext *C, const int mval[2]); struct Object *ED_view3d_give_object_under_cursor(struct bContext *C, const int mval[2]); +struct Object *ED_view3d_give_material_slot_under_cursor(struct bContext *C, + const int mval[2], + int *r_material_slot); bool ED_view3d_is_object_under_cursor(struct bContext *C, const int mval[2]); void ED_view3d_quadview_update(struct ScrArea *area, struct ARegion *region, bool do_clip); void ED_view3d_update_viewmat(struct Depsgraph *depsgraph, diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h index 1708c3598b1..ddd9ca4a98c 100644 --- a/source/blender/editors/include/UI_icons.h +++ b/source/blender/editors/include/UI_icons.h @@ -490,9 +490,9 @@ DEF_ICON_MODIFIER(CON_ACTION) DEF_ICON_BLANK(745) DEF_ICON_BLANK(746) DEF_ICON_BLANK(747) -DEF_ICON_BLANK(748) -DEF_ICON_BLANK(749) -DEF_ICON_BLANK(750) +DEF_ICON_MODIFIER(MOD_LENGTH) +DEF_ICON_MODIFIER(MOD_DASH) +DEF_ICON_MODIFIER(MOD_LINEART) DEF_ICON_BLANK(751) DEF_ICON(HOLDOUT_OFF) DEF_ICON(HOLDOUT_ON) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 7211cf9f893..916105b0f8e 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2209,6 +2209,11 @@ enum uiTemplateListFlags { UI_TEMPLATE_LIST_SORT_LOCK = (1 << 1), /* Don't allow resizing the list, i.e. don't add the grip button. */ UI_TEMPLATE_LIST_NO_GRIP = (1 << 2), + /** Do not show filtering options, not even the button to expand/collapse them. Also hides the + * grip button. */ + UI_TEMPLATE_LIST_NO_FILTER_OPTIONS = (1 << 3), + /** For #UILST_LAYOUT_BIG_PREVIEW_GRID, don't reserve space for the name label. */ + UI_TEMPLATE_LIST_NO_NAMES = (1 << 4), UI_TEMPLATE_LIST_FLAGS_LAST }; @@ -2289,6 +2294,12 @@ int uiTemplateRecentFiles(struct uiLayout *layout, int rows); void uiTemplateFileSelectPath(uiLayout *layout, struct bContext *C, struct FileSelectParams *params); + +enum { + UI_TEMPLATE_ASSET_DRAW_NO_NAMES = (1 << 0), + UI_TEMPLATE_ASSET_DRAW_NO_FILTER = (1 << 1), + UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY = (1 << 2), +}; void uiTemplateAssetView(struct uiLayout *layout, struct bContext *C, const char *list_id, @@ -2299,6 +2310,7 @@ void uiTemplateAssetView(struct uiLayout *layout, struct PointerRNA *active_dataptr, const char *active_propname, const struct AssetFilterSettings *filter_settings, + const int display_flags, const char *activate_opname, struct PointerRNA *r_activate_op_properties, const char *drag_opname, diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 977e9661dd9..77ae16d7cc7 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -508,6 +508,7 @@ typedef struct uiAfterFunc { bContextStore *context; char undostr[BKE_UNDO_STR_MAX]; + char drawstr[UI_MAX_DRAW_STR]; } uiAfterFunc; static void button_activate_init(bContext *C, @@ -790,6 +791,10 @@ static void ui_handle_afterfunc_add_operator_ex(wmOperatorType *ot, if (context_but && context_but->context) { after->context = CTX_store_copy(context_but->context); } + + if (context_but) { + ui_but_drawstr_without_sep_char(context_but, after->drawstr, sizeof(after->drawstr)); + } } void ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext) @@ -900,6 +905,8 @@ static void ui_apply_but_func(bContext *C, uiBut *but) after->context = CTX_store_copy(but->context); } + ui_but_drawstr_without_sep_char(but, after->drawstr, sizeof(after->drawstr)); + but->optype = NULL; but->opcontext = 0; but->opptr = NULL; @@ -1021,7 +1028,8 @@ static void ui_apply_but_funcs_after(bContext *C) } if (after.optype) { - WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL); + WM_operator_name_call_ptr_with_depends_on_cursor( + C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL, after.drawstr); } if (after.opptr) { @@ -4190,10 +4198,11 @@ static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtra ui_apply_but(C, but->block, but, but->active, true); } button_activate_state(C, but, BUTTON_STATE_EXIT); - WM_operator_name_call_ptr(C, - op_icon->optype_params->optype, - op_icon->optype_params->opcontext, - op_icon->optype_params->opptr); + WM_operator_name_call_ptr_with_depends_on_cursor(C, + op_icon->optype_params->optype, + op_icon->optype_params->opcontext, + op_icon->optype_params->opptr, + NULL); /* Force recreation of extra operator icons (pseudo update). */ ui_but_extra_operator_icons_free(but); diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index ec5a30f7793..66c75c63050 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -921,10 +921,10 @@ static void ui_keymap_but_cb(bContext *UNUSED(C), void *but_v, void *UNUSED(key_ { uiBut *but = but_v; - RNA_boolean_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) != 0); - RNA_boolean_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) != 0); - RNA_boolean_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) != 0); - RNA_boolean_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) != 0); + RNA_int_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) ? KM_MOD_HELD : KM_NOTHING); + RNA_int_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) ? KM_MOD_HELD : KM_NOTHING); + RNA_int_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) ? KM_MOD_HELD : KM_NOTHING); + RNA_int_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) ? KM_MOD_HELD : KM_NOTHING); } /** diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc index 9b601727e29..f27b37a27de 100644 --- a/source/blender/editors/interface/interface_template_asset_view.cc +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -46,6 +46,7 @@ struct AssetViewListData { AssetLibraryReference asset_library_ref; bScreen *screen; + bool show_names; }; static void asset_view_item_but_drag_set(uiBut *but, @@ -95,14 +96,15 @@ static void asset_view_draw_item(uiList *ui_list, uiLayoutSetContextPointer(layout, "asset_handle", itemptr); uiBlock *block = uiLayoutGetBlock(layout); + const bool show_names = list_data->show_names; /* TODO ED_fileselect_init_layout(). Share somehow? */ const float size_x = (96.0f / 20.0f) * UI_UNIT_X; - const float size_y = (96.0f / 20.0f) * UI_UNIT_Y; + const float size_y = (96.0f / 20.0f) * UI_UNIT_Y - (show_names ? 0 : UI_UNIT_Y); uiBut *but = uiDefIconTextBut(block, UI_BTYPE_PREVIEW_TILE, 0, ED_asset_handle_get_preview_icon_id(asset_handle), - ED_asset_handle_get_name(asset_handle), + show_names ? ED_asset_handle_get_name(asset_handle) : "", 0, 0, size_x, @@ -202,6 +204,7 @@ void uiTemplateAssetView(uiLayout *layout, PointerRNA *active_dataptr, const char *active_propname, const AssetFilterSettings *filter_settings, + const int display_flags, const char *activate_opname, PointerRNA *r_activate_op_properties, const char *drag_opname, @@ -220,9 +223,11 @@ void uiTemplateAssetView(uiLayout *layout, RNA_property_enum_get(asset_library_dataptr, asset_library_prop)); uiLayout *row = uiLayoutRow(col, true); - uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0); - if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) { - uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh"); + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY) == 0) { + uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0); + if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) { + uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh"); + } } ED_assetlist_storage_fetch(&asset_library_ref, C); @@ -236,6 +241,15 @@ void uiTemplateAssetView(uiLayout *layout, "AssetViewListData"); list_data->asset_library_ref = asset_library_ref; list_data->screen = CTX_wm_screen(C); + list_data->show_names = (display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) == 0; + + uiTemplateListFlags template_list_flags = UI_TEMPLATE_LIST_NO_GRIP; + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) != 0) { + template_list_flags |= UI_TEMPLATE_LIST_NO_NAMES; + } + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_FILTER) != 0) { + template_list_flags |= UI_TEMPLATE_LIST_NO_FILTER_OPTIONS; + } /* TODO can we have some kind of model-view API to handle referencing, filtering and lazy loading * (of previews) of the items? */ @@ -252,7 +266,7 @@ void uiTemplateAssetView(uiLayout *layout, 0, UILST_LAYOUT_BIG_PREVIEW_GRID, 0, - UI_TEMPLATE_LIST_NO_GRIP, + template_list_flags, list_data); if (!list) { /* List creation failed. */ diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc index 0ab45ea0f81..845a7813da2 100644 --- a/source/blender/editors/interface/interface_template_list.cc +++ b/source/blender/editors/interface/interface_template_list.cc @@ -31,6 +31,7 @@ #include "BLT_translation.h" +#include "ED_asset.h" #include "ED_screen.h" #include "MEM_guardedalloc.h" @@ -216,7 +217,18 @@ static void uilist_filter_items_default(struct uiList *ui_list, RNA_PROP_BEGIN (dataptr, itemptr, prop) { bool do_order = false; - char *namebuf = RNA_struct_name_get_alloc(&itemptr, nullptr, 0, nullptr); + char *namebuf; + if (RNA_struct_is_a(itemptr.type, &RNA_AssetHandle)) { + /* XXX The AssetHandle design is hacky and meant to be temporary. It can't have a proper + * name property, so for now this hardcoded exception is needed. */ + AssetHandle *asset_handle = (AssetHandle *)itemptr.data; + const char *asset_name = ED_asset_handle_get_name(asset_handle); + namebuf = BLI_strdup(asset_name); + } + else { + namebuf = RNA_struct_name_get_alloc(&itemptr, nullptr, 0, nullptr); + } + const char *name = namebuf ? namebuf : ""; if (filter[0]) { @@ -944,10 +956,16 @@ static void ui_template_list_layout_draw(bContext *C, /* For scrollbar. */ row = uiLayoutRow(glob, false); + const bool show_names = (flags & UI_TEMPLATE_LIST_NO_NAMES) == 0; + /* TODO ED_fileselect_init_layout(). Share somehow? */ float size_x = (96.0f / 20.0f) * UI_UNIT_X; float size_y = (96.0f / 20.0f) * UI_UNIT_Y; + if (!show_names) { + size_y -= UI_UNIT_Y; + } + const int cols_per_row = MAX2((uiLayoutGetWidth(box) - V2D_SCROLL_WIDTH) / size_x, 1); uiLayout *grid = uiLayoutGridFlow(row, true, cols_per_row, true, true, true); @@ -1033,7 +1051,8 @@ static void ui_template_list_layout_draw(bContext *C, break; } - if (glob) { + const bool add_filters_but = (flags & UI_TEMPLATE_LIST_NO_FILTER_OPTIONS) == 0; + if (glob && add_filters_but) { const bool add_grip_but = (flags & UI_TEMPLATE_LIST_NO_GRIP) == 0; /* About #UI_BTYPE_GRIP drag-resize: diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c index 3105891142f..672f1b64943 100644 --- a/source/blender/editors/interface/interface_template_search_menu.c +++ b/source/blender/editors/interface/interface_template_search_menu.c @@ -955,7 +955,8 @@ static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) switch (item->type) { case MENU_SEARCH_TYPE_OP: { CTX_store_set(C, item->op.context); - WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr); + WM_operator_name_call_ptr_with_depends_on_cursor( + C, item->op.type, item->op.opcontext, item->op.opptr, item->drawstr); CTX_store_set(C, NULL); break; } diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 08d78552710..0c9eb20af19 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -673,8 +673,8 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) } } else { - if (BKE_lib_id_make_local(bmain, id, false, 0)) { - BKE_main_id_newptr_and_tag_clear(bmain); + if (BKE_lib_id_make_local(bmain, id, 0)) { + BKE_id_newptr_and_tag_clear(id); /* Reassign to get proper updates/notifiers. */ idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); @@ -1031,7 +1031,7 @@ static void template_ID(const bContext *C, UI_but_flag_enable(but, UI_BUT_DISABLED); } else { - const bool disabled = (!BKE_lib_id_make_local(CTX_data_main(C), id, true /* test */, 0) || + const bool disabled = (!BKE_idtype_idcode_is_localizable(GS(id->name)) || (idfrom && idfrom->lib)); but = uiDefIconBut(block, UI_BTYPE_BUT, diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index a2b86ccd947..0dc7c2d3f9a 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -5443,13 +5443,20 @@ void ui_draw_preview_item_stateless(const uiFontStyle *fstyle, rcti trect = *rect; const float text_size = UI_UNIT_Y; float font_dims[2] = {0.0f, 0.0f}; + const bool has_text = name && name[0]; - /* draw icon in rect above the space reserved for the label */ - rect->ymin += text_size; + if (has_text) { + /* draw icon in rect above the space reserved for the label */ + rect->ymin += text_size; + } GPU_blend(GPU_BLEND_ALPHA); widget_draw_preview(iconid, 1.0f, rect); GPU_blend(GPU_BLEND_NONE); + if (!has_text) { + return; + } + BLF_width_and_height( fstyle->uifont_id, name, BLF_DRAW_STR_DUMMY_MAX, &font_dims[0], &font_dims[1]); diff --git a/source/blender/editors/mask/mask_select.c b/source/blender/editors/mask/mask_select.c index 6a1be8dcef3..fe6acac7d29 100644 --- a/source/blender/editors/mask/mask_select.c +++ b/source/blender/editors/mask/mask_select.c @@ -629,7 +629,7 @@ void MASK_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flags */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_gesture_lasso(ot); diff --git a/source/blender/editors/mesh/editmesh_add.c b/source/blender/editors/mesh/editmesh_add.c index a64b90e15a3..c826da74010 100644 --- a/source/blender/editors/mesh/editmesh_add.c +++ b/source/blender/editors/mesh/editmesh_add.c @@ -73,11 +73,7 @@ static Object *make_prim_init(bContext *C, r_creation_data->was_editmode = true; } - ED_object_new_primitive_matrix(C, obedit, loc, rot, r_creation_data->mat); - - if (scale) { - rescale_m4(r_creation_data->mat, scale); - } + ED_object_new_primitive_matrix(C, obedit, loc, rot, scale, r_creation_data->mat); return obedit; } @@ -351,7 +347,7 @@ static int add_primitive_cylinder_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b " + "create_cone segments=%i radius1=%f radius2=%f cap_ends=%b " "cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "vertices"), RNA_float_get(op->ptr, "radius"), @@ -427,7 +423,7 @@ static int add_primitive_cone_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b " + "create_cone segments=%i radius1=%f radius2=%f cap_ends=%b " "cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "vertices"), RNA_float_get(op->ptr, "radius1"), @@ -642,7 +638,7 @@ static int add_primitive_uvsphere_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_uvsphere u_segments=%i v_segments=%i diameter=%f matrix=%m4 calc_uvs=%b", + "create_uvsphere u_segments=%i v_segments=%i radius=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "segments"), RNA_int_get(op->ptr, "ring_count"), RNA_float_get(op->ptr, "radius"), @@ -710,7 +706,7 @@ static int add_primitive_icosphere_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_icosphere subdivisions=%i diameter=%f matrix=%m4 calc_uvs=%b", + "create_icosphere subdivisions=%i radius=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "subdivisions"), RNA_float_get(op->ptr, "radius"), creation_data.mat, diff --git a/source/blender/editors/mesh/editmesh_bevel.c b/source/blender/editors/mesh/editmesh_bevel.c index 01736f2919a..0d74187b50e 100644 --- a/source/blender/editors/mesh/editmesh_bevel.c +++ b/source/blender/editors/mesh/editmesh_bevel.c @@ -97,7 +97,6 @@ typedef struct { int launch_event; float mcenter[2]; void *draw_handle_pixel; - short gizmo_flag; short value_mode; /* Which value does mouse movement and numeric input affect? */ float segments; /* Segments as float so smooth mouse pan works in small increments */ @@ -307,11 +306,6 @@ static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal) opdata->draw_handle_pixel = ED_region_draw_cb_activate( region->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL); G.moving = G_TRANSFORM_EDIT; - - if (v3d) { - opdata->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; - } } return true; @@ -433,15 +427,11 @@ static void edbm_bevel_exit(bContext *C, wmOperator *op) } if (opdata->is_modal) { - View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup); } ED_region_draw_cb_exit(region->type, opdata->draw_handle_pixel); - if (v3d) { - v3d->gizmo_flag = opdata->gizmo_flag; - } G.moving = 0; } MEM_SAFE_FREE(opdata->ob_store); diff --git a/source/blender/editors/mesh/editmesh_bisect.c b/source/blender/editors/mesh/editmesh_bisect.c index 3c8afe8e7db..27a1bf9658f 100644 --- a/source/blender/editors/mesh/editmesh_bisect.c +++ b/source/blender/editors/mesh/editmesh_bisect.c @@ -72,7 +72,6 @@ typedef struct { bool is_dirty; } * backup; int backup_len; - short gizmo_flag; } BisectData; static void mesh_bisect_interactive_calc(bContext *C, @@ -88,6 +87,7 @@ static void mesh_bisect_interactive_calc(bContext *C, int y_start = RNA_int_get(op->ptr, "ystart"); int x_end = RNA_int_get(op->ptr, "xend"); int y_end = RNA_int_get(op->ptr, "yend"); + const bool use_flip = RNA_boolean_get(op->ptr, "flip"); /* reference location (some point in front of the view) for finding a point on a plane */ const float *co_ref = rv3d->ofs; @@ -105,6 +105,9 @@ static void mesh_bisect_interactive_calc(bContext *C, /* cross both to get a normal */ cross_v3_v3v3(plane_no, co_a, co_b); normalize_v3(plane_no); /* not needed but nicer for user */ + if (use_flip) { + negate_v3(plane_no); + } /* point on plane, can use either start or endpoint */ ED_view3d_win_to_3d(v3d, region, co_ref, co_a_ss, plane_co); @@ -116,7 +119,7 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) int valid_objects = 0; /* If the properties are set or there is no rv3d, - * skip model and exec immediately. */ + * skip modal and exec immediately. */ if ((CTX_wm_region_view3d(C) == NULL) || (RNA_struct_property_is_set(op->ptr, "plane_co") && RNA_struct_property_is_set(op->ptr, "plane_no"))) { return mesh_bisect_exec(C, op); @@ -140,10 +143,19 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; } - int ret = WM_gesture_straightline_invoke(C, op, event); - if (ret & OPERATOR_RUNNING_MODAL) { - View3D *v3d = CTX_wm_view3d(C); + /* Support flipping if side matters. */ + int ret; + const bool clear_inner = RNA_boolean_get(op->ptr, "clear_inner"); + const bool clear_outer = RNA_boolean_get(op->ptr, "clear_outer"); + const bool use_fill = RNA_boolean_get(op->ptr, "use_fill"); + if ((clear_inner != clear_outer) || use_fill) { + ret = WM_gesture_straightline_active_side_invoke(C, op, event); + } + else { + ret = WM_gesture_straightline_invoke(C, op, event); + } + if (ret & OPERATOR_RUNNING_MODAL) { wmGesture *gesture = op->customdata; BisectData *opdata; @@ -166,8 +178,6 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) /* Misc other vars. */ G.moving = G_TRANSFORM_EDIT; - opdata->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; /* Initialize modal callout. */ ED_workspace_status_text(C, TIP_("LMB: Click and drag to draw cut line")); @@ -176,10 +186,8 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) return ret; } -static void edbm_bisect_exit(bContext *C, BisectData *opdata) +static void edbm_bisect_exit(BisectData *opdata) { - View3D *v3d = CTX_wm_view3d(C); - v3d->gizmo_flag = opdata->gizmo_flag; G.moving = 0; for (int ob_index = 0; ob_index < opdata->backup_len; ob_index++) { @@ -210,7 +218,7 @@ static int mesh_bisect_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (ret & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) { - edbm_bisect_exit(C, &opdata_back); + edbm_bisect_exit(&opdata_back); #ifdef USE_GIZMO /* Setup gizmos */ @@ -769,7 +777,7 @@ static void MESH_GGT_bisect(struct wmGizmoGroupType *gzgt) gzgt->name = "Mesh Bisect"; gzgt->idname = "MESH_GGT_bisect"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D; + gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; diff --git a/source/blender/editors/mesh/editmesh_extrude.c b/source/blender/editors/mesh/editmesh_extrude.c index 907881a44f3..e4cd48d95bb 100644 --- a/source/blender/editors/mesh/editmesh_extrude.c +++ b/source/blender/editors/mesh/editmesh_extrude.c @@ -925,7 +925,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w void MESH_OT_dupli_extrude_cursor(wmOperatorType *ot) { /* identifiers */ - ot->name = "Duplicate or Extrude to Cursor"; + ot->name = "Extrude to Cursor or Add"; ot->idname = "MESH_OT_dupli_extrude_cursor"; ot->description = "Duplicate and extrude selected vertices, edges or faces towards the mouse cursor"; @@ -935,7 +935,7 @@ void MESH_OT_dupli_extrude_cursor(wmOperatorType *ot) ot->poll = ED_operator_editmesh_region_view3d; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; RNA_def_boolean(ot->srna, "rotate_source", diff --git a/source/blender/editors/mesh/editmesh_inset.c b/source/blender/editors/mesh/editmesh_inset.c index 18f51ae9df2..7be169f70f4 100644 --- a/source/blender/editors/mesh/editmesh_inset.c +++ b/source/blender/editors/mesh/editmesh_inset.c @@ -76,7 +76,6 @@ typedef struct { int launch_event; float mcenter[2]; void *draw_handle_pixel; - short gizmo_flag; } InsetData; static void edbm_inset_update_header(wmOperator *op, bContext *C) @@ -177,7 +176,6 @@ static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal) opdata->num_input.unit_type[1] = B_UNIT_LENGTH; if (is_modal) { - View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { @@ -189,10 +187,6 @@ static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal) opdata->draw_handle_pixel = ED_region_draw_cb_activate( region->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL); G.moving = G_TRANSFORM_EDIT; - if (v3d) { - opdata->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; - } } return true; @@ -206,15 +200,11 @@ static void edbm_inset_exit(bContext *C, wmOperator *op) opdata = op->customdata; if (opdata->is_modal) { - View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup); } ED_region_draw_cb_exit(region->type, opdata->draw_handle_pixel); - if (v3d) { - v3d->gizmo_flag = opdata->gizmo_flag; - } G.moving = 0; } diff --git a/source/blender/editors/mesh/editmesh_rip.c b/source/blender/editors/mesh/editmesh_rip.c index 0553fa077f8..d1df063d9d0 100644 --- a/source/blender/editors/mesh/editmesh_rip.c +++ b/source/blender/editors/mesh/editmesh_rip.c @@ -1124,7 +1124,7 @@ void MESH_OT_rip(wmOperatorType *ot) ot->poll = EDBM_view3d_poll; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* to give to transform */ Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY); diff --git a/source/blender/editors/mesh/editmesh_rip_edge.c b/source/blender/editors/mesh/editmesh_rip_edge.c index f7e88284d93..ce49f0f80a3 100644 --- a/source/blender/editors/mesh/editmesh_rip_edge.c +++ b/source/blender/editors/mesh/editmesh_rip_edge.c @@ -249,7 +249,7 @@ void MESH_OT_rip_edge(wmOperatorType *ot) ot->poll = EDBM_view3d_poll; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* to give to transform */ Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY); diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index 956658bd2b7..122214b87d5 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -4323,7 +4323,7 @@ void MESH_OT_knife_cut(wmOperatorType *ot) ot->poll = EDBM_view3d_poll; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ PropertyRNA *prop; diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 18f2b58eb65..040b5cd5066 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -37,6 +37,9 @@ set(INC ../../../../intern/clog ../../../../intern/glew-mx ../../../../intern/guardedalloc + + # dna_type_offsets.h in BLO_read_write.h + ${CMAKE_BINARY_DIR}/source/blender/makesdna/intern ) set(SRC @@ -93,3 +96,5 @@ if(WITH_EXPERIMENTAL_FEATURES) endif() blender_add_lib(bf_editor_object "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +add_dependencies(bf_editor_object bf_dna) diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 2e34284f46e..beadbf2689e 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -332,8 +332,12 @@ void ED_object_base_init_transform_on_add(Object *object, const float loc[3], co /* Uses context to figure out transform for primitive. * Returns standard diameter. */ -float ED_object_new_primitive_matrix( - bContext *C, Object *obedit, const float loc[3], const float rot[3], float r_primmat[4][4]) +float ED_object_new_primitive_matrix(bContext *C, + Object *obedit, + const float loc[3], + const float rot[3], + const float scale[3], + float r_primmat[4][4]) { Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); @@ -356,6 +360,10 @@ float ED_object_new_primitive_matrix( invert_m3_m3(imat, mat); mul_m3_v3(imat, r_primmat[3]); + if (scale != NULL) { + rescale_m4(r_primmat, scale); + } + { const float dia = v3d ? ED_view3d_grid_scale(scene, v3d, NULL) : ED_scene_grid_scale(scene, NULL); @@ -863,7 +871,7 @@ static int effector_add_exec(bContext *C, wmOperator *op) ED_object_editmode_enter_ex(bmain, scene, ob, 0); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); + ED_object_new_primitive_matrix(C, ob, loc, rot, NULL, mat); mul_mat3_m4_fl(mat, dia); BLI_addtail(&cu->editnurb->nurbs, ED_curve_add_nurbs_primitive(C, ob, mat, CU_NURBS | CU_PRIM_PATH, 1)); @@ -999,7 +1007,7 @@ static int object_metaball_add_exec(bContext *C, wmOperator *op) } float mat[4][4]; - ED_object_new_primitive_matrix(C, obedit, loc, rot, mat); + ED_object_new_primitive_matrix(C, obedit, loc, rot, NULL, mat); /* Halving here is done to account for constant values from #BKE_mball_element_add. * While the default radius of the resulting meta element is 2, * we want to pass in 1 so other values such as resolution are scaled by 1.0. */ @@ -1365,30 +1373,28 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) case GP_EMPTY: { float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); + ED_object_new_primitive_matrix(C, ob, loc, rot, NULL, mat); ED_gpencil_create_blank(C, ob, mat); break; } case GP_STROKE: { float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); - mul_v3_fl(mat[0], radius); - mul_v3_fl(mat[1], radius); - mul_v3_fl(mat[2], radius); + ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat); ED_gpencil_create_stroke(C, ob, mat); break; } case GP_MONKEY: { float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); - mul_v3_fl(mat[0], radius); - mul_v3_fl(mat[1], radius); - mul_v3_fl(mat[2], radius); + ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat); ED_gpencil_create_monkey(C, ob, mat); break; @@ -1397,12 +1403,11 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) case GP_LRT_COLLECTION: case GP_LRT_OBJECT: { float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); - mul_v3_fl(mat[0], radius); - mul_v3_fl(mat[1], radius); - mul_v3_fl(mat[2], radius); + ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat); ED_gpencil_create_lineart(C, ob); @@ -2246,13 +2251,6 @@ static bool dupliobject_instancer_cmp(const void *a_, const void *b_) return false; } -static bool object_has_geometry_set_instances(const Object *object_eval) -{ - struct GeometrySet *geometry_set = object_eval->runtime.geometry_set_eval; - - return (geometry_set != NULL) && BKE_geometry_set_has_instances(geometry_set); -} - static void make_object_duplilist_real(bContext *C, Depsgraph *depsgraph, Scene *scene, @@ -2266,7 +2264,8 @@ static void make_object_duplilist_real(bContext *C, Object *object_eval = DEG_get_evaluated_object(depsgraph, base->object); - if (!(base->object->transflag & OB_DUPLI) && !object_has_geometry_set_instances(object_eval)) { + if (!(base->object->transflag & OB_DUPLI) && + !BKE_object_has_geometry_set_instances(object_eval)) { return; } diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c index e0419e0a4cc..8702b18a46f 100644 --- a/source/blender/editors/object/object_constraint.c +++ b/source/blender/editors/object/object_constraint.c @@ -1786,7 +1786,8 @@ static bool constraint_copy_to_selected_poll(bContext *C) if (pchan) { bool found = false; - CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, UNUSED(ob)) { + CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, ob) { + UNUSED_VARS(ob); if (pchan != chan) { /** NOTE: Can not return here, because CTX_DATA_BEGIN_WITH_ID allocated * a list that needs to be freed by CTX_DATA_END. */ @@ -1862,6 +1863,7 @@ static int constraint_move_down_exec(bContext *C, wmOperator *op) BLI_remlink(conlist, con); BLI_insertlinkafter(conlist, nextCon, con); + ED_object_constraint_update(CTX_data_main(C), ob); WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob); return OPERATOR_FINISHED; @@ -1917,6 +1919,7 @@ static int constraint_move_up_exec(bContext *C, wmOperator *op) BLI_remlink(conlist, con); BLI_insertlinkbefore(conlist, prevCon, con); + ED_object_constraint_update(CTX_data_main(C), ob); WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob); return OPERATOR_FINISHED; @@ -1970,6 +1973,8 @@ static int constraint_move_to_index_exec(bContext *C, wmOperator *op) if (con) { ED_object_constraint_move_to_index(ob, con, new_index); + ED_object_constraint_update(CTX_data_main(C), ob); + return OPERATOR_FINISHED; } diff --git a/source/blender/editors/object/object_gpencil_modifier.c b/source/blender/editors/object/object_gpencil_modifier.c index 3995728c428..e3c2932e17a 100644 --- a/source/blender/editors/object/object_gpencil_modifier.c +++ b/source/blender/editors/object/object_gpencil_modifier.c @@ -28,6 +28,7 @@ #include "MEM_guardedalloc.h" +#include "DNA_defaults.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_gpencil_types.h" #include "DNA_object_types.h" @@ -35,6 +36,7 @@ #include "BLI_listbase.h" #include "BLI_string_utf8.h" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_context.h" @@ -55,6 +57,8 @@ #include "ED_object.h" #include "ED_screen.h" +#include "BLT_translation.h" + #include "UI_interface.h" #include "WM_api.h" @@ -939,3 +943,237 @@ void OBJECT_OT_gpencil_modifier_copy_to_selected(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; gpencil_edit_modifier_properties(ot); } + +/************************* Dash Modifier *******************************/ + +static bool dash_segment_poll(bContext *C) +{ + return gpencil_edit_modifier_poll_generic(C, &RNA_DashGpencilModifierData, 0, false); +} + +static bool dash_segment_name_exists_fn(void *arg, const char *name) +{ + const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg; + for (int i = 0; i < dmd->segments_len; i++) { + if (STREQ(dmd->segments[i].name, name)) { + return true; + } + } + return false; +} + +static int dash_segment_add_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get( + op, ob, eGpencilModifierType_Dash); + + const int new_active_index = dmd->segment_active_index + 1; + DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN( + dmd->segments_len + 1, sizeof(DashGpencilModifierSegment), __func__); + + if (dmd->segments_len != 0) { + /* Copy the segments before the new segment. */ + memcpy(new_segments, dmd->segments, sizeof(DashGpencilModifierSegment) * new_active_index); + /* Copy the segments after the new segment. */ + memcpy(new_segments + new_active_index + 1, + dmd->segments + new_active_index, + sizeof(DashGpencilModifierSegment) * (dmd->segments_len - new_active_index)); + } + + /* Create the new segment. */ + DashGpencilModifierSegment *ds = &new_segments[new_active_index]; + memcpy( + ds, DNA_struct_default_get(DashGpencilModifierSegment), sizeof(DashGpencilModifierSegment)); + BLI_uniquename_cb( + dash_segment_name_exists_fn, dmd, DATA_("Segment"), '.', ds->name, sizeof(ds->name)); + ds->dmd = dmd; + + MEM_SAFE_FREE(dmd->segments); + dmd->segments = new_segments; + dmd->segments_len++; + dmd->segment_active_index++; + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + + return OPERATOR_FINISHED; +} + +static int dash_segment_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) { + return dash_segment_add_exec(C, op); + } + return OPERATOR_CANCELLED; +} + +void GPENCIL_OT_segment_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Segment"; + ot->description = "Add a segment to the dash modifier"; + ot->idname = "GPENCIL_OT_segment_add"; + + /* api callbacks */ + ot->poll = dash_segment_poll; + ot->invoke = dash_segment_add_invoke; + ot->exec = dash_segment_add_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + edit_modifier_properties(ot); +} + +static int dash_segment_remove_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + + DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get( + op, ob, eGpencilModifierType_Dash); + + if (dmd->segment_active_index < 0 || dmd->segment_active_index >= dmd->segments_len) { + return OPERATOR_CANCELLED; + } + + if (dmd->segments_len == 1) { + MEM_SAFE_FREE(dmd->segments); + dmd->segment_active_index = -1; + } + else { + DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN( + dmd->segments_len, sizeof(DashGpencilModifierSegment), __func__); + + /* Copy the segments before the deleted segment. */ + memcpy(new_segments, + dmd->segments, + sizeof(DashGpencilModifierSegment) * dmd->segment_active_index); + + /* Copy the segments after the deleted segment. */ + memcpy(new_segments + dmd->segment_active_index, + dmd->segments + dmd->segment_active_index + 1, + sizeof(DashGpencilModifierSegment) * + (dmd->segments_len - dmd->segment_active_index - 1)); + + MEM_freeN(dmd->segments); + dmd->segments = new_segments; + dmd->segment_active_index = MAX2(dmd->segment_active_index - 1, 0); + } + + dmd->segments_len--; + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + + return OPERATOR_FINISHED; +} + +static int dash_segment_remove_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) { + return dash_segment_remove_exec(C, op); + } + return OPERATOR_CANCELLED; +} + +void GPENCIL_OT_segment_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Dash Segment"; + ot->description = "Remove the active segment from the dash modifier"; + ot->idname = "GPENCIL_OT_segment_remove"; + + /* api callbacks */ + ot->poll = dash_segment_poll; + ot->invoke = dash_segment_remove_invoke; + ot->exec = dash_segment_remove_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + edit_modifier_properties(ot); + + RNA_def_int( + ot->srna, "index", 0, 0, INT_MAX, "Index", "Index of the segment to remove", 0, INT_MAX); +} + +enum { + GP_SEGEMENT_MOVE_UP = -1, + GP_SEGEMENT_MOVE_DOWN = 1, +}; + +static int dash_segment_move_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + + DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get( + op, ob, eGpencilModifierType_Dash); + + if (dmd->segments_len < 2) { + return OPERATOR_CANCELLED; + } + + const int direction = RNA_enum_get(op->ptr, "type"); + if (direction == GP_SEGEMENT_MOVE_UP) { + if (dmd->segment_active_index == 0) { + return OPERATOR_CANCELLED; + } + + SWAP(DashGpencilModifierSegment, + dmd->segments[dmd->segment_active_index], + dmd->segments[dmd->segment_active_index - 1]); + + dmd->segment_active_index--; + } + else if (direction == GP_SEGEMENT_MOVE_DOWN) { + if (dmd->segment_active_index == dmd->segments_len - 1) { + return OPERATOR_CANCELLED; + } + + SWAP(DashGpencilModifierSegment, + dmd->segments[dmd->segment_active_index], + dmd->segments[dmd->segment_active_index + 1]); + + dmd->segment_active_index++; + } + else { + return OPERATOR_CANCELLED; + } + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + + return OPERATOR_FINISHED; +} + +static int dash_segment_move_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) { + return dash_segment_move_exec(C, op); + } + return OPERATOR_CANCELLED; +} + +void GPENCIL_OT_segment_move(wmOperatorType *ot) +{ + static const EnumPropertyItem segment_move[] = { + {GP_SEGEMENT_MOVE_UP, "UP", 0, "Up", ""}, + {GP_SEGEMENT_MOVE_DOWN, "DOWN", 0, "Down", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Move Dash Segment"; + ot->description = "Move the active dash segment up or down"; + ot->idname = "GPENCIL_OT_segment_move"; + + /* api callbacks */ + ot->poll = dash_segment_poll; + ot->invoke = dash_segment_move_invoke; + ot->exec = dash_segment_move_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + edit_modifier_properties(ot); + + ot->prop = RNA_def_enum(ot->srna, "type", segment_move, 0, "Type", ""); +} diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 10e016738d0..50dd9322c5c 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -191,6 +191,7 @@ void OBJECT_OT_skin_radii_equalize(struct wmOperatorType *ot); void OBJECT_OT_skin_armature_create(struct wmOperatorType *ot); void OBJECT_OT_laplaciandeform_bind(struct wmOperatorType *ot); void OBJECT_OT_surfacedeform_bind(struct wmOperatorType *ot); +void OBJECT_OT_geometry_nodes_input_attribute_toggle(struct wmOperatorType *ot); /* object_gpencil_modifiers.c */ void OBJECT_OT_gpencil_modifier_add(struct wmOperatorType *ot); @@ -202,6 +203,10 @@ void OBJECT_OT_gpencil_modifier_apply(struct wmOperatorType *ot); void OBJECT_OT_gpencil_modifier_copy(struct wmOperatorType *ot); void OBJECT_OT_gpencil_modifier_copy_to_selected(struct wmOperatorType *ot); +void GPENCIL_OT_segment_add(struct wmOperatorType *ot); +void GPENCIL_OT_segment_remove(struct wmOperatorType *ot); +void GPENCIL_OT_segment_move(struct wmOperatorType *ot); + /* object_shader_fx.c */ void OBJECT_OT_shaderfx_add(struct wmOperatorType *ot); void OBJECT_OT_shaderfx_copy(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index 2a1a6696493..b9942bc563a 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -3242,3 +3242,54 @@ void OBJECT_OT_surfacedeform_bind(wmOperatorType *ot) } /** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Toggle Value or Attribute Operator + * + * \note This operator basically only exists to provide a better tooltip for the toggle button, + * since it is stored as an IDProperty. It also stops the button from being highlighted when + * "use_attribute" is on, which isn't expected. + * \{ */ + +static int geometry_nodes_input_attribute_toggle_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + + char modifier_name[MAX_NAME]; + RNA_string_get(op->ptr, "modifier_name", modifier_name); + NodesModifierData *nmd = (NodesModifierData *)BKE_modifiers_findby_name(ob, modifier_name); + if (nmd == NULL) { + return OPERATOR_CANCELLED; + } + + char prop_path[MAX_NAME]; + RNA_string_get(op->ptr, "prop_path", prop_path); + + PointerRNA mod_ptr; + RNA_pointer_create(&ob->id, &RNA_Modifier, nmd, &mod_ptr); + + const int old_value = RNA_int_get(&mod_ptr, prop_path); + const int new_value = !old_value; + RNA_int_set(&mod_ptr, prop_path, new_value); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + return OPERATOR_FINISHED; +} + +void OBJECT_OT_geometry_nodes_input_attribute_toggle(wmOperatorType *ot) +{ + ot->name = "Input Attribute Toggle"; + ot->description = + "Switch between an attribute and a single value to define the data for every element"; + ot->idname = "OBJECT_OT_geometry_nodes_input_attribute_toggle"; + + ot->exec = geometry_nodes_input_attribute_toggle_exec; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + + RNA_def_string(ot->srna, "prop_path", NULL, 0, "Prop Path", ""); + RNA_def_string(ot->srna, "modifier_name", NULL, MAX_NAME, "Modifier Name", ""); +} + +/** \} */ diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index c1928cf7f8a..aa9ae082317 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -145,6 +145,7 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_skin_loose_mark_clear); WM_operatortype_append(OBJECT_OT_skin_radii_equalize); WM_operatortype_append(OBJECT_OT_skin_armature_create); + WM_operatortype_append(OBJECT_OT_geometry_nodes_input_attribute_toggle); /* grease pencil modifiers */ WM_operatortype_append(OBJECT_OT_gpencil_modifier_add); @@ -156,6 +157,10 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy); WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy_to_selected); + WM_operatortype_append(GPENCIL_OT_segment_add); + WM_operatortype_append(GPENCIL_OT_segment_remove); + WM_operatortype_append(GPENCIL_OT_segment_move); + /* grease pencil line art */ WM_operatortypes_lineart(); diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index ec72ff11683..75269dffec8 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -2729,25 +2729,26 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C, PointerRNA *properties, const wmEvent *event) { - Object *ob = ED_view3d_give_object_under_cursor(C, event->mval); + int mat_slot = 0; + Object *ob = ED_view3d_give_material_slot_under_cursor(C, event->mval, &mat_slot); if (ob == NULL) { return BLI_strdup(""); } + mat_slot = max_ii(mat_slot, 1); char name[MAX_ID_NAME - 2]; RNA_string_get(properties, "name", name); - int active_mat_slot = max_ii(ob->actcol, 1); - Material *prev_mat = BKE_object_material_get(ob, active_mat_slot); + Material *prev_mat = BKE_object_material_get(ob, mat_slot); char *result; if (prev_mat) { const char *tooltip = TIP_("Drop %s on %s (slot %d, replacing %s)"); - result = BLI_sprintfN(tooltip, name, ob->id.name + 2, active_mat_slot, prev_mat->id.name + 2); + result = BLI_sprintfN(tooltip, name, ob->id.name + 2, mat_slot, prev_mat->id.name + 2); } else { const char *tooltip = TIP_("Drop %s on %s (slot %d)"); - result = BLI_sprintfN(tooltip, name, ob->id.name + 2, active_mat_slot); + result = BLI_sprintfN(tooltip, name, ob->id.name + 2, mat_slot); } return result; } @@ -2755,7 +2756,10 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C, static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Main *bmain = CTX_data_main(C); - Object *ob = ED_view3d_give_object_under_cursor(C, event->mval); + int mat_slot = 0; + Object *ob = ED_view3d_give_material_slot_under_cursor(C, event->mval, &mat_slot); + mat_slot = max_ii(mat_slot, 1); + Material *ma; char name[MAX_ID_NAME - 2]; @@ -2765,9 +2769,7 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_CANCELLED; } - const short active_mat_slot = ob->actcol; - - BKE_object_material_assign(CTX_data_main(C), ob, ma, active_mat_slot, BKE_MAT_ASSIGN_USERPREF); + BKE_object_material_assign(CTX_data_main(C), ob, ma, mat_slot, BKE_MAT_ASSIGN_USERPREF); DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); diff --git a/source/blender/editors/render/render_opengl.c b/source/blender/editors/render/render_opengl.c index 834c023dde9..e4bbbfb0f57 100644 --- a/source/blender/editors/render/render_opengl.c +++ b/source/blender/editors/render/render_opengl.c @@ -303,7 +303,7 @@ static void screen_opengl_render_doit(const bContext *C, OGLRender *oglrender, R if (oglrender->is_sequencer) { SpaceSeq *sseq = oglrender->sseq; - struct bGPdata *gpd = (sseq && (sseq->flag & SEQ_SHOW_GPENCIL)) ? sseq->gpd : NULL; + struct bGPdata *gpd = (sseq && (sseq->flag & SEQ_PREVIEW_SHOW_GPENCIL)) ? sseq->gpd : NULL; /* use pre-calculated ImBuf (avoids deadlock), see: */ ImBuf *ibuf = oglrender->seq_data.ibufs_arr[oglrender->view_id]; diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index e08a4e946f6..9546035375c 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -882,7 +882,7 @@ static void area_azone_init(wmWindow *win, const bScreen *screen, ScrArea *area) return; } - if (U.app_flag & USER_APP_LOCK_UI_LAYOUT) { + if (U.app_flag & USER_APP_LOCK_CORNER_SPLIT) { return; } @@ -1058,6 +1058,14 @@ static bool region_azone_edge_poll(const ARegion *region, const bool is_fullscre return false; } + if (is_hidden && (U.app_flag & USER_APP_HIDE_REGION_TOGGLE)) { + return false; + } + + if (!is_hidden && (U.app_flag & USER_APP_LOCK_EDGE_RESIZE)) { + return false; + } + return true; } diff --git a/source/blender/editors/screen/screen_geometry.c b/source/blender/editors/screen/screen_geometry.c index 51edad0332b..e67c933cb8e 100644 --- a/source/blender/editors/screen/screen_geometry.c +++ b/source/blender/editors/screen/screen_geometry.c @@ -130,6 +130,10 @@ ScrEdge *screen_geom_find_active_scredge(const wmWindow *win, const int mx, const int my) { + if (U.app_flag & USER_APP_LOCK_EDGE_RESIZE) { + return NULL; + } + /* Use layout size (screen excluding global areas) for screen-layout area edges */ rcti screen_rect; WM_window_screen_rect_calc(win, &screen_rect); diff --git a/source/blender/editors/screen/screen_intern.h b/source/blender/editors/screen/screen_intern.h index 683f2844371..4016ef84bfd 100644 --- a/source/blender/editors/screen/screen_intern.h +++ b/source/blender/editors/screen/screen_intern.h @@ -128,6 +128,7 @@ extern const char *screen_context_dir[]; /* doc access */ /* screendump.c */ void SCREEN_OT_screenshot(struct wmOperatorType *ot); +void SCREEN_OT_screenshot_area(struct wmOperatorType *ot); /* workspace_layout_edit.c */ bool workspace_layout_set_poll(const struct WorkSpaceLayout *layout); diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index daac196a90c..674a2deb929 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -1134,14 +1134,22 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) if ((ED_area_actionzone_find_xy(sad->sa1, &event->x) != sad->az) && (screen_geom_area_map_find_active_scredge( AREAMAP_FROM_SCREEN(screen), &screen_rect, event->x, event->y) == NULL)) { - /* Are we still in same area? */ - if (BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) { + + /* What area are we now in? */ + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y); + + if (area == sad->sa1) { /* Same area, so possible split. */ WM_cursor_set(win, SCREEN_DIR_IS_VERTICAL(sad->gesture_dir) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); is_gesture = (delta_max > split_threshold); } + else if (!area || area->global) { + /* No area or Top bar or Status bar. */ + WM_cursor_set(win, WM_CURSOR_STOP); + is_gesture = false; + } else { /* Different area, so possible join. */ if (sad->gesture_dir == SCREEN_DIR_N) { @@ -1161,7 +1169,7 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) } } else { - WM_cursor_set(CTX_wm_window(C), WM_CURSOR_CROSS); + WM_cursor_set(win, WM_CURSOR_CROSS); is_gesture = false; } } @@ -1466,15 +1474,39 @@ static void SCREEN_OT_area_dupli(wmOperatorType *ot) * Close selected area, replace by expanding a neighbor * \{ */ -/* operator callback */ -static int area_close_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event)) +/** + * \note This can be used interactively or from Python. + * + * \note Most of the window management operators don't support execution from Python. + * An exception is made for closing areas since it allows application templates + * to customize the layout. + */ +static int area_close_exec(bContext *C, wmOperator *op) { + bScreen *screen = CTX_wm_screen(C); ScrArea *area = CTX_wm_area(C); - if ((area != NULL) && screen_area_close(C, CTX_wm_screen(C), area)) { - WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); - return OPERATOR_FINISHED; + + /* This operator is scriptable, so the area passed could be invalid. */ + if (BLI_findindex(&screen->areabase, area) == -1) { + BKE_report(op->reports, RPT_ERROR, "Area not found in the active screen"); + return OPERATOR_CANCELLED; } - return OPERATOR_CANCELLED; + + if (!screen_area_close(C, screen, area)) { + BKE_report(op->reports, RPT_ERROR, "Unable to close area"); + return OPERATOR_CANCELLED; + } + + /* Ensure the event loop doesn't attempt to continue handling events. + * + * This causes execution from the Python console fail to return to the prompt as it should. + * This glitch could be solved in the event loop handling as other operators may also + * destructively manipulate windowing data. */ + CTX_wm_window_set(C, NULL); + + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); + + return OPERATOR_FINISHED; } static bool area_close_poll(bContext *C) @@ -1506,7 +1538,7 @@ static void SCREEN_OT_area_close(wmOperatorType *ot) ot->name = "Close Area"; ot->description = "Close selected area"; ot->idname = "SCREEN_OT_area_close"; - ot->invoke = area_close_invoke; + ot->exec = area_close_exec; ot->poll = area_close_poll; } @@ -3075,6 +3107,7 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op) mask_to_keylist(&ads, masklay, keylist); } } + ED_keylist_prepare_for_direct_access(keylist); /* find matching keyframe in the right direction */ const ActKeyColumn *ak; @@ -5657,6 +5690,7 @@ void ED_operatortypes_screen(void) WM_operatortype_append(SCREEN_OT_back_to_previous); WM_operatortype_append(SCREEN_OT_spacedata_cleanup); WM_operatortype_append(SCREEN_OT_screenshot); + WM_operatortype_append(SCREEN_OT_screenshot_area); WM_operatortype_append(SCREEN_OT_userpref_show); WM_operatortype_append(SCREEN_OT_drivers_editor_show); WM_operatortype_append(SCREEN_OT_info_log_show); diff --git a/source/blender/editors/screen/screendump.c b/source/blender/editors/screen/screendump.c index 6df96b1e30f..bc1c15abb96 100644 --- a/source/blender/editors/screen/screendump.c +++ b/source/blender/editors/screen/screendump.c @@ -42,6 +42,7 @@ #include "BKE_image.h" #include "BKE_main.h" #include "BKE_report.h" +#include "BKE_screen.h" #include "RNA_access.h" #include "RNA_define.h" @@ -57,12 +58,13 @@ typedef struct ScreenshotData { uint *dumprect; int dumpsx, dumpsy; rcti crop; + bool use_crop; ImageFormatData im_format; } ScreenshotData; /* call from both exec and invoke */ -static int screenshot_data_create(bContext *C, wmOperator *op) +static int screenshot_data_create(bContext *C, wmOperator *op, ScrArea *area) { int dumprect_size[2]; @@ -76,7 +78,6 @@ static int screenshot_data_create(bContext *C, wmOperator *op) if (dumprect) { ScreenshotData *scd = MEM_callocN(sizeof(ScreenshotData), "screenshot"); - ScrArea *area = CTX_wm_area(C); scd->dumpsx = dumprect_size[0]; scd->dumpsy = dumprect_size[1]; @@ -110,12 +111,13 @@ static void screenshot_data_free(wmOperator *op) static int screenshot_exec(bContext *C, wmOperator *op) { + const bool use_crop = STREQ(op->idname, "SCREEN_OT_screenshot_area"); ScreenshotData *scd = op->customdata; bool ok = false; if (scd == NULL) { /* when running exec directly */ - screenshot_data_create(C, op); + screenshot_data_create(C, op, use_crop ? CTX_wm_area(C) : NULL); scd = op->customdata; } @@ -132,7 +134,7 @@ static int screenshot_exec(bContext *C, wmOperator *op) ibuf->rect = scd->dumprect; /* crop to show only single editor */ - if (!RNA_boolean_get(op->ptr, "full")) { + if (use_crop) { IMB_rect_crop(ibuf, &scd->crop); scd->dumprect = ibuf->rect; } @@ -157,9 +159,20 @@ static int screenshot_exec(bContext *C, wmOperator *op) return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } -static int screenshot_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +static int screenshot_invoke(bContext *C, wmOperator *op, const wmEvent *event) { - if (screenshot_data_create(C, op)) { + const bool use_crop = STREQ(op->idname, "SCREEN_OT_screenshot_area"); + ScrArea *area = NULL; + if (use_crop) { + area = CTX_wm_area(C); + bScreen *screen = CTX_wm_screen(C); + ScrArea *area_test = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y); + if (area_test != NULL) { + area = area_test; + } + } + + if (screenshot_data_create(C, op, area)) { if (RNA_struct_property_is_set(op->ptr, "filepath")) { return screenshot_exec(C, op); } @@ -226,12 +239,8 @@ static bool screenshot_poll(bContext *C) return WM_operator_winactive(C); } -void SCREEN_OT_screenshot(wmOperatorType *ot) +static void screen_screenshot_impl(wmOperatorType *ot) { - ot->name = "Save Screenshot"; - ot->idname = "SCREEN_OT_screenshot"; - ot->description = "Capture a picture of the active area or whole Blender window"; - ot->invoke = screenshot_invoke; ot->check = screenshot_check; ot->exec = screenshot_exec; @@ -239,8 +248,6 @@ void SCREEN_OT_screenshot(wmOperatorType *ot) ot->ui = screenshot_draw; ot->poll = screenshot_poll; - ot->flag = 0; - WM_operator_properties_filesel(ot, FILE_TYPE_FOLDER | FILE_TYPE_IMAGE, FILE_SPECIAL, @@ -248,9 +255,27 @@ void SCREEN_OT_screenshot(wmOperatorType *ot) WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY, FILE_SORT_DEFAULT); - RNA_def_boolean(ot->srna, - "full", - 1, - "Full Screen", - "Capture the whole window (otherwise only capture the active area)"); +} + +void SCREEN_OT_screenshot(wmOperatorType *ot) +{ + ot->name = "Save Screenshot"; + ot->idname = "SCREEN_OT_screenshot"; + ot->description = "Capture a picture of the whole Blender window"; + + screen_screenshot_impl(ot); + + ot->flag = 0; +} + +void SCREEN_OT_screenshot_area(wmOperatorType *ot) +{ + /* NOTE: the term "area" is a Blender internal name, "Editor" makes more sense for the UI. */ + ot->name = "Save Screenshot (Editor)"; + ot->idname = "SCREEN_OT_screenshot_area"; + ot->description = "Capture a picture of an editor"; + + screen_screenshot_impl(ot); + + ot->flag = OPTYPE_DEPENDS_ON_CURSOR; } diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c index d968b6cc319..e65d6ce2d48 100644 --- a/source/blender/editors/sculpt_paint/paint_mask.c +++ b/source/blender/editors/sculpt_paint/paint_mask.c @@ -1300,7 +1300,7 @@ static void sculpt_gesture_apply_trim(SculptGestureContext *sgcontext) }), sculpt_mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); BKE_mesh_nomain_to_mesh( result, sgcontext->vc.obact->data, sgcontext->vc.obact, &CD_MASK_MESH, true); } @@ -1651,7 +1651,7 @@ void PAINT_OT_mask_lasso_gesture(wmOperatorType *ot) ot->poll = SCULPT_mode_poll_view3d; - ot->flag = OPTYPE_REGISTER; + ot->flag = OPTYPE_REGISTER | OPTYPE_DEPENDS_ON_CURSOR; /* Properties. */ WM_operator_properties_gesture_lasso(ot); @@ -1714,6 +1714,8 @@ void SCULPT_OT_face_set_lasso_gesture(wmOperatorType *ot) ot->poll = SCULPT_mode_poll_view3d; + ot->flag = OPTYPE_DEPENDS_ON_CURSOR; + /* Properties. */ WM_operator_properties_gesture_lasso(ot); sculpt_gesture_operator_properties(ot); @@ -1750,7 +1752,7 @@ void SCULPT_OT_trim_lasso_gesture(wmOperatorType *ot) ot->poll = SCULPT_mode_poll_view3d; - ot->flag = OPTYPE_REGISTER; + ot->flag = OPTYPE_REGISTER | OPTYPE_DEPENDS_ON_CURSOR; /* Properties. */ WM_operator_properties_gesture_lasso(ot); diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c index 6f4e295cbb2..3e38be243c9 100644 --- a/source/blender/editors/space_action/action_edit.c +++ b/source/blender/editors/space_action/action_edit.c @@ -192,7 +192,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const bGPDlayer *gpl = ale->data; bGPDframe *gpf; - /* find gp-frame which is less than or equal to cframe */ + /* Find gp-frame which is less than or equal to current-frame. */ for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { const float framenum = (float)gpf->framenum; *min = min_ff(*min, framenum); @@ -204,7 +204,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const MaskLayer *masklay = ale->data; MaskLayerShape *masklay_shape; - /* find mask layer which is less than or equal to cframe */ + /* Find mask layer which is less than or equal to current-frame. */ for (masklay_shape = masklay->splines_shapes.first; masklay_shape; masklay_shape = masklay_shape->next) { const float framenum = (float)masklay_shape->frame; diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c index 9dcfc626a50..26d71178b68 100644 --- a/source/blender/editors/space_action/action_select.c +++ b/source/blender/editors/space_action/action_select.c @@ -162,6 +162,7 @@ static void actkeys_find_key_in_list_element(bAnimContext *ac, struct AnimKeylist *keylist = ED_keylist_create(); actkeys_list_element_to_keylist(ac, keylist, ale); + ED_keylist_prepare_for_direct_access(keylist); AnimData *adt = ANIM_nla_mapping_get(ac, ale); @@ -833,7 +834,7 @@ void ACTION_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flags */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_gesture_lasso(ot); diff --git a/source/blender/editors/space_clip/tracking_select.c b/source/blender/editors/space_clip/tracking_select.c index c7f0f4c228f..73a73eb7911 100644 --- a/source/blender/editors/space_clip/tracking_select.c +++ b/source/blender/editors/space_clip/tracking_select.c @@ -721,7 +721,7 @@ void CLIP_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flags */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_gesture_lasso(ot); diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index c7d23943b6c..511b5b255e9 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -1442,7 +1442,9 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat if (preview->in_memory_preview) { if (BKE_previewimg_is_finished(preview->in_memory_preview, ICON_SIZE_PREVIEW)) { ImBuf *imbuf = BKE_previewimg_to_imbuf(preview->in_memory_preview, ICON_SIZE_PREVIEW); - preview->icon_id = BKE_icon_imbuf_create(imbuf); + if (imbuf) { + preview->icon_id = BKE_icon_imbuf_create(imbuf); + } done = true; } } @@ -1953,7 +1955,9 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in if (entry->local_data.preview_image && BKE_previewimg_is_finished(entry->local_data.preview_image, ICON_SIZE_PREVIEW)) { ImBuf *ibuf = BKE_previewimg_to_imbuf(entry->local_data.preview_image, ICON_SIZE_PREVIEW); - ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); + if (ibuf) { + ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); + } } BLI_addtail(&cache->cached_entries, ret); return ret; diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index 4ab7014cf82..11b06d2b414 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -135,7 +135,8 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile) base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR; base_params->display = FILE_IMGDISPLAY; base_params->sort = FILE_SORT_ALPHA; - base_params->recursion_level = 1; + /* Asset libraries include all sub-directories, so enable maximal recursion. */ + base_params->recursion_level = FILE_SELECT_MAX_RECURSIONS; /* 'SMALL' size by default. More reasonable since this is typically used as regular editor, * space is more of an issue here. */ base_params->thumbnail_size = 96; diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index a4f36c2a6ee..42a9c4aa2d5 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -738,8 +738,18 @@ static void file_tools_region_draw(const bContext *C, ARegion *region) ED_region_panels(C, region); } -static void file_tools_region_listener(const wmRegionListenerParams *UNUSED(listener_params)) +static void file_tools_region_listener(const wmRegionListenerParams *listener_params) { + const wmNotifier *wmn = listener_params->notifier; + ARegion *region = listener_params->region; + + switch (wmn->category) { + case NC_SCENE: + if (ELEM(wmn->data, ND_MODE)) { + ED_region_tag_redraw(region); + } + break; + } } static void file_tool_props_region_listener(const wmRegionListenerParams *listener_params) @@ -754,6 +764,11 @@ static void file_tool_props_region_listener(const wmRegionListenerParams *listen ED_region_tag_redraw(region); } break; + case NC_SCENE: + if (ELEM(wmn->data, ND_MODE)) { + ED_region_tag_redraw(region); + } + break; } } diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index 2955c4ef7ae..872b17372de 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -1098,7 +1098,8 @@ static int graphkeys_sound_bake_exec(bContext *C, wmOperator *op) RNA_boolean_get(op->ptr, "use_square"), RNA_float_get(op->ptr, "sthreshold"), FPS, - &sbi.length); + &sbi.length, + 0); if (sbi.samples == NULL) { BKE_report(op->reports, RPT_ERROR, "Unsupported audio format"); diff --git a/source/blender/editors/space_graph/graph_select.c b/source/blender/editors/space_graph/graph_select.c index a853efb1ace..ffe74e20bdf 100644 --- a/source/blender/editors/space_graph/graph_select.c +++ b/source/blender/editors/space_graph/graph_select.c @@ -1006,7 +1006,7 @@ void GRAPH_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* Flags. */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* Properties. */ WM_operator_properties_gesture_lasso(ot); diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c index 10629caa8b0..f04336cab84 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.c +++ b/source/blender/editors/space_graph/graph_slider_ops.c @@ -41,6 +41,7 @@ #include "ED_keyframes_edit.h" #include "ED_numinput.h" #include "ED_screen.h" +#include "ED_util.h" #include "WM_api.h" #include "WM_types.h" @@ -94,6 +95,8 @@ typedef struct tDecimateGraphOp { /** The original bezt curve data (used for restoring fcurves). */ ListBase bezt_arr_list; + struct tSlider *slider; + NumInput num; } tDecimateGraphOp; @@ -161,6 +164,8 @@ static void decimate_exit(bContext *C, wmOperator *op) ScrArea *area = dgo->area; LinkData *link; + ED_slider_destroy(C, dgo->slider); + for (link = dgo->bezt_arr_list.first; link != NULL; link = link->next) { tBeztCopyData *copy = link->data; MEM_freeN(copy->bezt); @@ -178,11 +183,14 @@ static void decimate_exit(bContext *C, wmOperator *op) op->customdata = NULL; } -/* Draw a percentage indicator in header. */ -static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo) +/* Draw a percentage indicator in workspace footer. */ +static void decimate_draw_status(bContext *C, tDecimateGraphOp *dgo) { char status_str[UI_MAX_DRAW_STR]; char mode_str[32]; + char slider_string[UI_MAX_DRAW_STR]; + + ED_slider_status_string_get(dgo->slider, slider_string, UI_MAX_DRAW_STR); strcpy(mode_str, TIP_("Decimate Keyframes")); @@ -194,23 +202,10 @@ static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo) BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_ofs); } else { - float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop); - BLI_snprintf( - status_str, sizeof(status_str), "%s: %d %%", mode_str, (int)(percentage * 100.0f)); + BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, slider_string); } - ED_area_status_text(dgo->area, status_str); -} - -/* Calculate percentage based on position of mouse (we only use x-axis for now. - * Since this is more convenient for users to do), and store new percentage value. - */ -static void decimate_mouse_update_percentage(tDecimateGraphOp *dgo, - wmOperator *op, - const wmEvent *event) -{ - float percentage = (event->x - dgo->region->winrct.xmin) / ((float)dgo->region->winx); - RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage); + ED_workspace_status_text(C, status_str); } static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent *event) @@ -234,10 +229,11 @@ static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent dgo->area = CTX_wm_area(C); dgo->region = CTX_wm_region(C); - /* Initialize percentage so that it will have the correct value before the first mouse move. */ - decimate_mouse_update_percentage(dgo, op, event); + dgo->slider = ED_slider_create(C); + ED_slider_init(dgo->slider, event); + ED_slider_allow_overshoot_set(dgo->slider, false); - decimate_draw_status_header(op, dgo); + decimate_draw_status(C, dgo); /* Construct a list with the original bezt arrays so we can restore them during modal operation. */ @@ -300,13 +296,14 @@ static void graphkeys_decimate_modal_update(bContext *C, wmOperator *op) * (e.g. pressing a key or moving the mouse). */ tDecimateGraphOp *dgo = op->customdata; - decimate_draw_status_header(op, dgo); + decimate_draw_status(C, dgo); /* Reset keyframe data (so we get back to the original state). */ decimate_reset_bezts(dgo); /* Apply... */ - float remove_ratio = RNA_property_float_get(op->ptr, dgo->percentage_prop); + float remove_ratio = ED_slider_factor_get(dgo->slider); + RNA_property_float_set(op->ptr, dgo->percentage_prop, remove_ratio); /* We don't want to limit the decimation to a certain error margin. */ const float error_sq_max = FLT_MAX; decimate_graph_keys(&dgo->ac, remove_ratio, error_sq_max); @@ -323,6 +320,8 @@ static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent * const bool has_numinput = hasNumInput(&dgo->num); + ED_slider_modal(dgo->slider, event); + switch (event->type) { case LEFTMOUSE: /* Confirm */ case EVT_RETKEY: @@ -353,9 +352,6 @@ static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent * case MOUSEMOVE: /* Calculate new position. */ { if (has_numinput == false) { - /* Update percentage based on position of mouse. */ - decimate_mouse_update_percentage(dgo, op, event); - /* Update pose to reflect the new values. */ graphkeys_decimate_modal_update(C, op); } diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c index 4107fd619aa..de8e4684d45 100644 --- a/source/blender/editors/space_image/space_image.c +++ b/source/blender/editors/space_image/space_image.c @@ -461,7 +461,7 @@ static void IMAGE_GGT_gizmo2d(wmGizmoGroupType *gzgt) gzgt->name = "UV Transform Gizmo"; gzgt->idname = "IMAGE_GGT_gizmo2d"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; @@ -475,7 +475,7 @@ static void IMAGE_GGT_gizmo2d_translate(wmGizmoGroupType *gzgt) gzgt->name = "UV Translate Gizmo"; gzgt->idname = "IMAGE_GGT_gizmo2d_translate"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; @@ -489,7 +489,7 @@ static void IMAGE_GGT_gizmo2d_resize(wmGizmoGroupType *gzgt) gzgt->name = "UV Transform Gizmo Resize"; gzgt->idname = "IMAGE_GGT_gizmo2d_resize"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; @@ -503,7 +503,7 @@ static void IMAGE_GGT_gizmo2d_rotate(wmGizmoGroupType *gzgt) gzgt->name = "UV Transform Gizmo Resize"; gzgt->idname = "IMAGE_GGT_gizmo2d_rotate"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 4b859de0ac9..62f40152416 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -2865,6 +2865,8 @@ static void node_composit_buts_denoise(uiLayout *layout, bContext *UNUSED(C), Po # endif #endif + uiItemL(layout, IFACE_("Prefilter:"), ICON_NONE); + uiItemR(layout, ptr, "prefilter", DEFAULT_FLAGS, nullptr, ICON_NONE); uiItemR(layout, ptr, "use_hdr", DEFAULT_FLAGS, nullptr, ICON_NONE); } diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index 9264c9d3572..7b6ca5e6e61 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -312,7 +312,7 @@ void NODE_OT_add_reroute(wmOperatorType *ot) ot->poll = ED_operator_node_editable; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ PropertyRNA *prop; @@ -575,7 +575,7 @@ static int node_add_texture_exec(bContext *C, wmOperator *op) bNode *texture_node = node_add_node(C, nullptr, - GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, + GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, snode->runtime->cursor[0], snode->runtime->cursor[1]); if (!texture_node) { diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 5b4e3b3b6f5..aa241071425 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -41,10 +41,12 @@ #include "BLI_span.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" +#include "BLI_vector_set.hh" #include "BLT_translation.h" #include "BKE_context.h" +#include "BKE_geometry_set.hh" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -78,6 +80,8 @@ #include "NOD_geometry_nodes_eval_log.hh" +#include "FN_field_cpp_type.hh" + #include "node_intern.h" /* own include */ #ifdef WITH_COMPOSITOR @@ -88,7 +92,11 @@ using blender::Map; using blender::Set; using blender::Span; using blender::Vector; +using blender::VectorSet; using blender::fn::CPPType; +using blender::fn::FieldCPPType; +using blender::fn::FieldInput; +using blender::fn::GField; using blender::fn::GPointer; namespace geo_log = blender::nodes::geometry_nodes_eval_log; @@ -850,31 +858,70 @@ static void create_inspection_string_for_generic_value(const geo_log::GenericVal }; const GPointer value = value_log.value(); - if (value.is_type<int>()) { - ss << *value.get<int>() << TIP_(" (Integer)"); - } - else if (value.is_type<float>()) { - ss << *value.get<float>() << TIP_(" (Float)"); - } - else if (value.is_type<blender::float3>()) { - ss << *value.get<blender::float3>() << TIP_(" (Vector)"); - } - else if (value.is_type<bool>()) { - ss << (*value.get<bool>() ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); - } - else if (value.is_type<std::string>()) { - ss << *value.get<std::string>() << TIP_(" (String)"); + const CPPType &type = *value.type(); + if (const FieldCPPType *field_type = dynamic_cast<const FieldCPPType *>(&type)) { + const CPPType &base_type = field_type->field_type(); + BUFFER_FOR_CPP_TYPE_VALUE(base_type, buffer); + const GField &field = field_type->get_gfield(value.get()); + if (field.node().depends_on_input()) { + if (base_type.is<int>()) { + ss << TIP_("Integer Field"); + } + else if (base_type.is<float>()) { + ss << TIP_("Float Field"); + } + else if (base_type.is<blender::float3>()) { + ss << TIP_("Vector Field"); + } + else if (base_type.is<bool>()) { + ss << TIP_("Boolean Field"); + } + else if (base_type.is<std::string>()) { + ss << TIP_("String Field"); + } + ss << TIP_(" based on:\n"); + + /* Use vector set to deduplicate inputs. */ + VectorSet<std::reference_wrapper<const FieldInput>> field_inputs; + field.node().foreach_field_input( + [&](const FieldInput &field_input) { field_inputs.add(field_input); }); + for (const FieldInput &field_input : field_inputs) { + ss << "\u2022 " << field_input.socket_inspection_name(); + if (field_input != field_inputs.as_span().last().get()) { + ss << ".\n"; + } + } + } + else { + blender::fn::evaluate_constant_field(field, buffer); + if (base_type.is<int>()) { + ss << *(int *)buffer << TIP_(" (Integer)"); + } + else if (base_type.is<float>()) { + ss << *(float *)buffer << TIP_(" (Float)"); + } + else if (base_type.is<blender::float3>()) { + ss << *(blender::float3 *)buffer << TIP_(" (Vector)"); + } + else if (base_type.is<bool>()) { + ss << ((*(bool *)buffer) ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); + } + else if (base_type.is<std::string>()) { + ss << *(std::string *)buffer << TIP_(" (String)"); + } + base_type.destruct(buffer); + } } - else if (value.is_type<Object *>()) { + else if (type.is<Object *>()) { id_to_inspection_string((ID *)*value.get<Object *>(), ID_OB); } - else if (value.is_type<Material *>()) { + else if (type.is<Material *>()) { id_to_inspection_string((ID *)*value.get<Material *>(), ID_MA); } - else if (value.is_type<Tex *>()) { + else if (type.is<Tex *>()) { id_to_inspection_string((ID *)*value.get<Tex *>(), ID_TE); } - else if (value.is_type<Collection *>()) { + else if (type.is<Collection *>()) { id_to_inspection_string((ID *)*value.get<Collection *>(), ID_GR); } } diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index e908a61eed9..7d95659e403 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -1427,7 +1427,7 @@ void NODE_OT_links_cut(wmOperatorType *ot) ot->poll = ED_operator_node_editable; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ PropertyRNA *prop; @@ -1533,7 +1533,7 @@ void NODE_OT_links_mute(wmOperatorType *ot) ot->poll = ED_operator_node_editable; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ PropertyRNA *prop; diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index adff85a2b8c..29b8372d043 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -923,7 +923,7 @@ void NODE_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flags */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ RNA_def_boolean(ot->srna, diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index db37c8c1c8c..c06a1010168 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -32,6 +32,7 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_sequence_types.h" +#include "DNA_text_types.h" #include "BLI_blenlib.h" #include "BLI_math.h" @@ -59,6 +60,7 @@ #include "DEG_depsgraph_build.h" #include "ED_armature.h" +#include "ED_fileselect.h" #include "ED_outliner.h" #include "ED_screen.h" @@ -2625,9 +2627,17 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) case ID_NLA: data.icon = ICON_NLA; break; - case ID_TXT: - data.icon = ICON_SCRIPT; + case ID_TXT: { + Text *text = (Text *)tselem->id; + if (text->filepath == NULL || (text->flags & TXT_ISMEM)) { + data.icon = ICON_FILE_TEXT; + } + else { + /* Helps distinguish text-based formats like the file-browser does. */ + data.icon = ED_file_extension_icon(text->filepath); + } break; + } case ID_GR: data.icon = ICON_OUTLINER_COLLECTION; break; diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c index 7709c6bb053..9e314701719 100644 --- a/source/blender/editors/space_outliner/outliner_tools.c +++ b/source/blender/editors/space_outliner/outliner_tools.c @@ -737,13 +737,8 @@ static void id_local_fn(bContext *C, { if (ID_IS_LINKED(tselem->id) && (tselem->id->tag & LIB_TAG_EXTERN)) { Main *bmain = CTX_data_main(C); - /* if the ID type has no special local function, - * just clear the lib */ - if (BKE_lib_id_make_local(bmain, tselem->id, false, 0) == false) { - BKE_lib_id_clear_library_data(bmain, tselem->id); - } - else { - BKE_main_id_newptr_and_tag_clear(bmain); + if (BKE_lib_id_make_local(bmain, tselem->id, 0)) { + BKE_id_newptr_and_tag_clear(tselem->id); } } else if (ID_IS_OVERRIDE_LIBRARY_REAL(tselem->id)) { diff --git a/source/blender/editors/space_sequencer/sequencer_add.c b/source/blender/editors/space_sequencer/sequencer_add.c index 081f0241e94..bdfa639b327 100644 --- a/source/blender/editors/space_sequencer/sequencer_add.c +++ b/source/blender/editors/space_sequencer/sequencer_add.c @@ -47,6 +47,7 @@ #include "BKE_mask.h" #include "BKE_movieclip.h" #include "BKE_report.h" +#include "BKE_sound.h" #include "IMB_imbuf.h" @@ -643,7 +644,15 @@ static void sequencer_add_movie_multiple_strips(bContext *C, BLI_strncpy(load_data->name, file_only, sizeof(load_data->name)); Sequence *seq_movie = NULL; Sequence *seq_sound = NULL; - double video_start_offset; + double video_start_offset = -1; + double audio_start_offset = 0; + + if (RNA_boolean_get(op->ptr, "sound")) { + SoundStreamInfo sound_info; + if (BKE_sound_stream_info_get(bmain, load_data->path, 0, &sound_info)) { + audio_start_offset = video_start_offset = sound_info.start; + } + } load_data->channel++; seq_movie = SEQ_add_movie_strip(bmain, scene, ed->seqbasep, load_data, &video_start_offset); @@ -653,9 +662,30 @@ static void sequencer_add_movie_multiple_strips(bContext *C, } else { if (RNA_boolean_get(op->ptr, "sound")) { - seq_sound = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data, video_start_offset); + int minimum_frame_offset = MIN2(video_start_offset, audio_start_offset) * FPS; + + int video_frame_offset = video_start_offset * FPS; + int audio_frame_offset = audio_start_offset * FPS; + + double video_frame_remainder = video_start_offset * FPS - video_frame_offset; + double audio_frame_remainder = audio_start_offset * FPS - audio_frame_offset; + + double audio_skip = (video_frame_remainder - audio_frame_remainder) / FPS; + + video_frame_offset -= minimum_frame_offset; + audio_frame_offset -= minimum_frame_offset; + + load_data->start_frame += audio_frame_offset; + seq_sound = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data, audio_skip); + + int min_startdisp = MIN2(seq_movie->startdisp, seq_sound->startdisp); + int max_enddisp = MAX2(seq_movie->enddisp, seq_sound->enddisp); + + load_data->start_frame += max_enddisp - min_startdisp - audio_frame_offset; + } + else { + load_data->start_frame += seq_movie->enddisp - seq_movie->startdisp; } - load_data->start_frame += seq_movie->enddisp - seq_movie->startdisp; seq_load_apply_generic_options(C, op, seq_sound); seq_load_apply_generic_options(C, op, seq_movie); seq_build_proxy(C, seq_movie); @@ -672,7 +702,15 @@ static bool sequencer_add_movie_single_strip(bContext *C, wmOperator *op, SeqLoa Sequence *seq_movie = NULL; Sequence *seq_sound = NULL; - double video_start_offset; + double video_start_offset = -1; + double audio_start_offset = 0; + + if (RNA_boolean_get(op->ptr, "sound")) { + SoundStreamInfo sound_info; + if (BKE_sound_stream_info_get(bmain, load_data->path, 0, &sound_info)) { + audio_start_offset = video_start_offset = sound_info.start; + } + } load_data->channel++; seq_movie = SEQ_add_movie_strip(bmain, scene, ed->seqbasep, load_data, &video_start_offset); @@ -683,7 +721,21 @@ static bool sequencer_add_movie_single_strip(bContext *C, wmOperator *op, SeqLoa return false; } if (RNA_boolean_get(op->ptr, "sound")) { - seq_sound = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data, video_start_offset); + int minimum_frame_offset = MIN2(video_start_offset, audio_start_offset) * FPS; + + int video_frame_offset = video_start_offset * FPS; + int audio_frame_offset = audio_start_offset * FPS; + + double video_frame_remainder = video_start_offset * FPS - video_frame_offset; + double audio_frame_remainder = audio_start_offset * FPS - audio_frame_offset; + + double audio_skip = (video_frame_remainder - audio_frame_remainder) / FPS; + + video_frame_offset -= minimum_frame_offset; + audio_frame_offset -= minimum_frame_offset; + + load_data->start_frame += audio_frame_offset; + seq_sound = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data, audio_skip); } seq_load_apply_generic_options(C, op, seq_sound); seq_load_apply_generic_options(C, op, seq_movie); diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index b56ad48cec2..5b39feacfe3 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -327,7 +327,8 @@ static void draw_seq_waveform_overlay(View2D *v2d, float y2, float frames_per_pixel) { - if (seq->sound && ((sseq->flag & SEQ_ALL_WAVEFORMS) || (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM))) { + if (seq->sound && + ((sseq->flag & SEQ_TIMELINE_ALL_WAVEFORMS) || (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM))) { /* Make sure that the start drawing position is aligned to the pixels on the screen to avoid * flickering when moving around the strip. * To do this we figure out the fractional offset in pixel space by checking where the @@ -420,6 +421,10 @@ static void draw_seq_waveform_overlay(View2D *v2d, float sample_offset = start_sample + i * samples_per_pix; int p = sample_offset; + if (p < 0) { + continue; + } + if (p >= waveform->length) { break; } @@ -872,12 +877,12 @@ static size_t draw_seq_text_get_overlay_string(SpaceSeq *sseq, const char *text_array[5]; int i = 0; - if (sseq->flag & SEQ_SHOW_STRIP_NAME) { + if (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_NAME) { text_array[i++] = draw_seq_text_get_name(seq); } char source[FILE_MAX]; - if (sseq->flag & SEQ_SHOW_STRIP_SOURCE) { + if (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_SOURCE) { draw_seq_text_get_source(seq, source, sizeof(source)); if (source[0] != '\0') { if (i != 0) { @@ -888,7 +893,7 @@ static size_t draw_seq_text_get_overlay_string(SpaceSeq *sseq, } char strip_duration_text[16]; - if (sseq->flag & SEQ_SHOW_STRIP_DURATION) { + if (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_DURATION) { const int strip_duration = seq->enddisp - seq->startdisp; SNPRINTF(strip_duration_text, "%d", strip_duration); if (i != 0) { @@ -1306,8 +1311,9 @@ static void draw_seq_strip(const bContext *C, float text_margin_y; bool y_threshold; - if ((sseq->flag & SEQ_SHOW_STRIP_NAME) || (sseq->flag & SEQ_SHOW_STRIP_SOURCE) || - (sseq->flag & SEQ_SHOW_STRIP_DURATION)) { + if ((sseq->flag & SEQ_TIMELINE_SHOW_STRIP_NAME) || + (sseq->flag & SEQ_TIMELINE_SHOW_STRIP_SOURCE) || + (sseq->flag & SEQ_TIMELINE_SHOW_STRIP_DURATION)) { /* Calculate height needed for drawing text on strip. */ text_margin_y = y2 - min_ff(0.40f, 20 * U.dpi_fac * pixely); @@ -1331,9 +1337,10 @@ static void draw_seq_strip(const bContext *C, } /* Draw strip offsets when flag is enabled or during "solo preview". */ - if (sseq->flag & SEQ_SHOW_STRIP_OVERLAY) { + if (sseq->flag & SEQ_SHOW_OVERLAY) { if (!is_single_image && (seq->startofs || seq->endofs) && pixely > 0) { - if ((sseq->draw_flag & SEQ_DRAW_OFFSET_EXT) || (seq == special_seq_update)) { + if ((sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_OFFSETS) || + (seq == special_seq_update)) { draw_sequence_extensions_overlay(scene, seq, pos, pixely); } } @@ -1348,13 +1355,14 @@ static void draw_seq_strip(const bContext *C, drawmeta_contents(scene, seq, x1, y1, x2, y2); } - if ((sseq->flag & SEQ_SHOW_STRIP_OVERLAY) && (sseq->flag & SEQ_SHOW_FCURVES)) { + if ((sseq->flag & SEQ_SHOW_OVERLAY) && + (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_FCURVES)) { draw_seq_fcurve_overlay(scene, v2d, seq, x1, y1, x2, y2, pixelx); } /* Draw sound strip waveform. */ - if ((seq->type == SEQ_TYPE_SOUND_RAM) && ((sseq->flag & SEQ_SHOW_STRIP_OVERLAY)) && - (sseq->flag & SEQ_NO_WAVEFORMS) == 0) { + if ((seq->type == SEQ_TYPE_SOUND_RAM) && ((sseq->flag & SEQ_SHOW_OVERLAY)) && + (sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) == 0) { draw_seq_waveform_overlay(v2d, C, sseq, @@ -1394,13 +1402,14 @@ static void draw_seq_strip(const bContext *C, /* If a waveform is drawn, avoid drawing text when there is not enough vertical space. */ if (seq->type == SEQ_TYPE_SOUND_RAM) { - if (!y_threshold && (sseq->flag & SEQ_NO_WAVEFORMS) == 0 && - ((sseq->flag & SEQ_ALL_WAVEFORMS) || (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM))) { + if (!y_threshold && (sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) == 0 && + ((sseq->timeline_overlay.flag & SEQ_TIMELINE_ALL_WAVEFORMS) || + (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM))) { return; } } - if (sseq->flag & SEQ_SHOW_STRIP_OVERLAY) { + if (sseq->flag & SEQ_SHOW_OVERLAY) { /* Don't draw strip if there is not enough vertical or horizontal space. */ if (((x2 - x1) > 32 * pixelx * U.dpi_fac) && ((y2 - y1) > 8 * pixely * U.dpi_fac)) { /* Depending on the vertical space, draw text on top or in the center of strip. */ @@ -1643,7 +1652,7 @@ static void sequencer_draw_borders_overlay(const SpaceSeq *sseq, imm_draw_box_wire_2d(shdr_pos, x1 - 0.5f, y1 - 0.5f, x2 + 0.5f, y2 + 0.5f); /* Draw safety border. */ - if (sseq->flag & SEQ_SHOW_SAFE_MARGINS) { + if (sseq->preview_overlay.flag & SEQ_PREVIEW_SHOW_SAFE_MARGINS) { immUniformThemeColorBlend(TH_VIEW_OVERLAY, TH_BACK, 0.25f); UI_draw_safe_areas(shdr_pos, @@ -1656,7 +1665,7 @@ static void sequencer_draw_borders_overlay(const SpaceSeq *sseq, scene->safe_areas.title, scene->safe_areas.action); - if (sseq->flag & SEQ_SHOW_SAFE_CENTER) { + if (sseq->preview_overlay.flag & SEQ_PREVIEW_SHOW_SAFE_CENTER) { UI_draw_safe_areas(shdr_pos, &(const rctf){ .xmin = x1, @@ -2063,7 +2072,7 @@ void sequencer_draw_preview(const bContext *C, struct ImBuf *scope = NULL; float viewrect[2]; const bool show_imbuf = ED_space_sequencer_check_show_imbuf(sseq); - const bool draw_gpencil = ((sseq->flag & SEQ_SHOW_GPENCIL) && sseq->gpd); + const bool draw_gpencil = ((sseq->preview_overlay.flag & SEQ_PREVIEW_SHOW_GPENCIL) && sseq->gpd); const char *names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME}; sequencer_stop_running_jobs(C, scene); @@ -2114,16 +2123,16 @@ void sequencer_draw_preview(const bContext *C, C, scene, region, sseq, ibuf, scope, draw_overlay, draw_backdrop); /* Draw over image. */ - if (sseq->flag & SEQ_SHOW_METADATA && sseq->flag & SEQ_SHOW_STRIP_OVERLAY) { + if (sseq->preview_overlay.flag & SEQ_PREVIEW_SHOW_METADATA && sseq->flag & SEQ_SHOW_OVERLAY) { ED_region_image_metadata_draw(0.0, 0.0, ibuf, &v2d->tot, 1.0, 1.0); } } - if (show_imbuf && (sseq->flag & SEQ_SHOW_STRIP_OVERLAY)) { + if (show_imbuf && (sseq->flag & SEQ_SHOW_OVERLAY)) { sequencer_draw_borders_overlay(sseq, v2d, scene); } - if (draw_gpencil && show_imbuf && (sseq->flag & SEQ_SHOW_STRIP_OVERLAY)) { + if (draw_gpencil && show_imbuf && (sseq->flag & SEQ_SHOW_OVERLAY)) { sequencer_draw_gpencil_overlay(C); } #if 0 @@ -2611,7 +2620,7 @@ void draw_timeline_seq(const bContext *C, ARegion *region) /* Get timeline bound-box, needed for the scroll-bars. */ SEQ_timeline_boundbox(scene, SEQ_active_seqbase_get(ed), &v2d->tot); draw_seq_backdrop(v2d); - if ((sseq->flag & SEQ_SHOW_STRIP_OVERLAY) && (sseq->flag & SEQ_SHOW_GRID)) { + if ((sseq->flag & SEQ_SHOW_OVERLAY) && (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_GRID)) { U.v2d_min_gridsize *= 3; UI_view2d_draw_lines_x__discrete_frames_or_seconds( v2d, scene, (sseq->flag & SEQ_DRAWFRAMES) == 0, false); diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c index 4c938a412d2..80d3e2cbdaa 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.c +++ b/source/blender/editors/space_sequencer/sequencer_select.c @@ -504,238 +504,245 @@ void SEQUENCER_OT_select_inverse(struct wmOperatorType *ot) /** \name Select Operator * \{ */ -static int sequencer_select_exec(bContext *C, wmOperator *op) +static void sequencer_select_set_active(Scene *scene, Sequence *seq) { - View2D *v2d = UI_view2d_fromcontext(C); - Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); - const bool extend = RNA_boolean_get(op->ptr, "extend"); - const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all"); - const bool linked_handle = RNA_boolean_get(op->ptr, "linked_handle"); - const bool linked_time = RNA_boolean_get(op->ptr, "linked_time"); - bool side_of_frame = RNA_boolean_get(op->ptr, "side_of_frame"); - bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); - int mval[2]; - int ret_value = OPERATOR_CANCELLED; - mval[0] = RNA_int_get(op->ptr, "mouse_x"); - mval[1] = RNA_int_get(op->ptr, "mouse_y"); - - Sequence *seq, *neighbor, *act_orig; - int hand, sel_side; + SEQ_select_active_set(scene, seq); - if (ed == NULL) { - return OPERATOR_CANCELLED; + if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) { + if (seq->strip) { + BLI_strncpy(ed->act_imagedir, seq->strip->dir, FILE_MAXDIR); + } } - - if (extend) { - wait_to_deselect_others = false; + else if (seq->type == SEQ_TYPE_SOUND_RAM) { + if (seq->strip) { + BLI_strncpy(ed->act_sounddir, seq->strip->dir, FILE_MAXDIR); + } } + recurs_sel_seq(seq); +} - seq = find_nearest_seq(scene, v2d, &hand, mval); +static void sequencer_select_do_updates(bContext *C, Scene *scene) +{ + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); +} - /* XXX: not nice, Ctrl+RMB needs to do side_of_frame only when not over a strip. */ - if (seq && linked_time) { - side_of_frame = false; - } +static void sequencer_select_side_of_frame(const bContext *C, + const View2D *v2d, + const int mval[2], + Scene *scene) +{ + Editing *ed = SEQ_editing_get(scene); - /* Select left, right or overlapping the current frame. */ - if (side_of_frame) { - /* Use different logic for this. */ - if (extend == false) { - ED_sequencer_deselect_all(scene); + const float x = UI_view2d_region_to_view_x(v2d, mval[0]); + LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) { + if (((x < CFRA) && (seq_iter->enddisp <= CFRA)) || + ((x >= CFRA) && (seq_iter->startdisp >= CFRA))) { + /* Select left or right. */ + seq_iter->flag |= SELECT; + recurs_sel_seq(seq_iter); } + } - const float x = UI_view2d_region_to_view_x(v2d, mval[0]); + { + SpaceSeq *sseq = CTX_wm_space_seq(C); + if (sseq && sseq->flag & SEQ_MARKER_TRANS) { + TimeMarker *tmarker; - LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) { - if (((x < CFRA) && (seq_iter->enddisp <= CFRA)) || - ((x >= CFRA) && (seq_iter->startdisp >= CFRA))) { - /* Select left or right. */ - seq_iter->flag |= SELECT; - recurs_sel_seq(seq_iter); + for (tmarker = scene->markers.first; tmarker; tmarker = tmarker->next) { + if (((x < CFRA) && (tmarker->frame <= CFRA)) || + ((x >= CFRA) && (tmarker->frame >= CFRA))) { + tmarker->flag |= SELECT; + } + else { + tmarker->flag &= ~SELECT; + } } } + } +} - { - SpaceSeq *sseq = CTX_wm_space_seq(C); - if (sseq && sseq->flag & SEQ_MARKER_TRANS) { - TimeMarker *tmarker; +static void sequencer_select_linked_handle(const bContext *C, + Sequence *seq, + const int handle_clicked) +{ + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + if (!ELEM(handle_clicked, SEQ_SIDE_LEFT, SEQ_SIDE_RIGHT)) { + /* First click selects the strip and its adjacent handles (if valid). + * Second click selects the strip, + * both of its handles and its adjacent handles (if valid). */ + const bool is_striponly_selected = ((seq->flag & SEQ_ALLSEL) == SELECT); + seq->flag &= ~SEQ_ALLSEL; + seq->flag |= is_striponly_selected ? SEQ_ALLSEL : SELECT; + select_surrounding_handles(scene, seq); + } + else { + /* Always select the strip under the cursor. */ + seq->flag |= SELECT; - for (tmarker = scene->markers.first; tmarker; tmarker = tmarker->next) { - if (((x < CFRA) && (tmarker->frame <= CFRA)) || - ((x >= CFRA) && (tmarker->frame >= CFRA))) { - tmarker->flag |= SELECT; + /* First click selects adjacent handles on that side. + * Second click selects all strips in that direction. + * If there are no adjacent strips, it just selects all in that direction. + */ + int sel_side = handle_clicked; + Sequence *neighbor = find_neighboring_sequence(scene, seq, sel_side, -1); + if (neighbor) { + switch (sel_side) { + case SEQ_SIDE_LEFT: + if ((seq->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) { + seq->flag |= SELECT; + select_active_side(ed->seqbasep, SEQ_SIDE_LEFT, seq->machine, seq->startdisp); } else { - tmarker->flag &= ~SELECT; + seq->flag |= SELECT; + neighbor->flag |= SELECT; + recurs_sel_seq(neighbor); + neighbor->flag |= SEQ_RIGHTSEL; + seq->flag |= SEQ_LEFTSEL; } - } + break; + case SEQ_SIDE_RIGHT: + if ((seq->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) { + seq->flag |= SELECT; + select_active_side(ed->seqbasep, SEQ_SIDE_RIGHT, seq->machine, seq->startdisp); + } + else { + seq->flag |= SELECT; + neighbor->flag |= SELECT; + recurs_sel_seq(neighbor); + neighbor->flag |= SEQ_LEFTSEL; + seq->flag |= SEQ_RIGHTSEL; + } + break; } } + else { - ret_value = OPERATOR_FINISHED; + select_active_side(ed->seqbasep, sel_side, seq->machine, seq->startdisp); + } } - else { - act_orig = ed->act_seq; - - if (seq) { - /* Are we trying to select a handle that's already selected? */ - const bool handle_selected = ((hand == SEQ_SIDE_LEFT) && (seq->flag & SEQ_LEFTSEL)) || - ((hand == SEQ_SIDE_RIGHT) && (seq->flag & SEQ_RIGHTSEL)); - - if (wait_to_deselect_others && (seq->flag & SELECT) && - (hand == SEQ_SIDE_NONE || handle_selected)) { - ret_value = OPERATOR_RUNNING_MODAL; - } - else if (!extend && !linked_handle) { - ED_sequencer_deselect_all(scene); - ret_value = OPERATOR_FINISHED; - } - else { - ret_value = OPERATOR_FINISHED; - } - - SEQ_select_active_set(scene, seq); +} - if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) { - if (seq->strip) { - BLI_strncpy(ed->act_imagedir, seq->strip->dir, FILE_MAXDIR); - } - } - else if (seq->type == SEQ_TYPE_SOUND_RAM) { - if (seq->strip) { - BLI_strncpy(ed->act_sounddir, seq->strip->dir, FILE_MAXDIR); - } - } +static bool element_already_selected(const Sequence *seq, const int handle_clicked) +{ + const bool handle_already_selected = ((handle_clicked == SEQ_SIDE_LEFT) && + (seq->flag & SEQ_LEFTSEL)) || + ((handle_clicked == SEQ_SIDE_RIGHT) && + (seq->flag & SEQ_RIGHTSEL)); + return ((seq->flag & SELECT) && handle_clicked == SEQ_SIDE_NONE) || handle_already_selected; +} - /* On Alt selection, select the strip and bordering handles. */ - if (linked_handle) { - if (!ELEM(hand, SEQ_SIDE_LEFT, SEQ_SIDE_RIGHT)) { - /* First click selects the strip and its adjacent handles (if valid). - * Second click selects the strip, - * both of its handles and its adjacent handles (if valid). */ - const bool is_striponly_selected = ((seq->flag & SEQ_ALLSEL) == SELECT); +static void sequencer_select_strip_impl(const Editing *ed, + Sequence *seq, + const int handle_clicked, + const bool extend) +{ + /* Deselect strip. */ + if (extend && (seq->flag & SELECT) && ed->act_seq == seq) { + switch (handle_clicked) { + case SEQ_SIDE_NONE: + seq->flag &= ~SEQ_ALLSEL; + break; + case SEQ_SIDE_LEFT: + seq->flag ^= SEQ_LEFTSEL; + break; + case SEQ_SIDE_RIGHT: + seq->flag ^= SEQ_RIGHTSEL; + break; + } + } + else { /* Select strip. */ + seq->flag |= SELECT; + if (handle_clicked == SEQ_SIDE_LEFT) { + seq->flag |= SEQ_LEFTSEL; + } + if (handle_clicked == SEQ_SIDE_RIGHT) { + seq->flag |= SEQ_RIGHTSEL; + } + } +} - if (!extend) { - ED_sequencer_deselect_all(scene); - } - seq->flag &= ~SEQ_ALLSEL; - seq->flag |= is_striponly_selected ? SEQ_ALLSEL : SELECT; - select_surrounding_handles(scene, seq); - } - else { - /* Always select the strip under the cursor. */ - seq->flag |= SELECT; +static int sequencer_select_exec(bContext *C, wmOperator *op) +{ + View2D *v2d = UI_view2d_fromcontext(C); + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + const bool extend = RNA_boolean_get(op->ptr, "extend"); - /* First click selects adjacent handles on that side. - * Second click selects all strips in that direction. - * If there are no adjacent strips, it just selects all in that direction. - */ - sel_side = hand; - neighbor = find_neighboring_sequence(scene, seq, sel_side, -1); - if (neighbor) { - switch (sel_side) { - case SEQ_SIDE_LEFT: - if ((seq->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - select_active_side(ed->seqbasep, SEQ_SIDE_LEFT, seq->machine, seq->startdisp); - } - else { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - neighbor->flag |= SELECT; - recurs_sel_seq(neighbor); - neighbor->flag |= SEQ_RIGHTSEL; - seq->flag |= SEQ_LEFTSEL; - } - break; - case SEQ_SIDE_RIGHT: - if ((seq->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - select_active_side(ed->seqbasep, SEQ_SIDE_RIGHT, seq->machine, seq->startdisp); - } - else { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - neighbor->flag |= SELECT; - recurs_sel_seq(neighbor); - neighbor->flag |= SEQ_LEFTSEL; - seq->flag |= SEQ_RIGHTSEL; - } - break; - } - } - else { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - select_active_side(ed->seqbasep, sel_side, seq->machine, seq->startdisp); - } - } + if (ed == NULL) { + return OPERATOR_CANCELLED; + } - ret_value = OPERATOR_FINISHED; - } - else { - if (extend && (seq->flag & SELECT) && ed->act_seq == act_orig) { - switch (hand) { - case SEQ_SIDE_NONE: - if (linked_handle == 0) { - seq->flag &= ~SEQ_ALLSEL; - } - break; - case SEQ_SIDE_LEFT: - seq->flag ^= SEQ_LEFTSEL; - break; - case SEQ_SIDE_RIGHT: - seq->flag ^= SEQ_RIGHTSEL; - break; - } - ret_value = OPERATOR_FINISHED; - } - else { - seq->flag |= SELECT; - if (hand == SEQ_SIDE_LEFT) { - seq->flag |= SEQ_LEFTSEL; - } - if (hand == SEQ_SIDE_RIGHT) { - seq->flag |= SEQ_RIGHTSEL; - } - } - } + int mval[2]; + mval[0] = RNA_int_get(op->ptr, "mouse_x"); + mval[1] = RNA_int_get(op->ptr, "mouse_y"); - recurs_sel_seq(seq); + int handle_clicked; + Sequence *seq = find_nearest_seq(scene, v2d, &handle_clicked, mval); - if (linked_time) { - select_linked_time(ed->seqbasep, seq); - } + /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap, + * therefore both properties can be true at the same time. */ + if (seq && RNA_boolean_get(op->ptr, "linked_time")) { + if (!extend) { + ED_sequencer_deselect_all(scene); + } + sequencer_select_strip_impl(ed, seq, handle_clicked, extend); + select_linked_time(ed->seqbasep, seq); + sequencer_select_do_updates(C, scene); + sequencer_select_set_active(scene, seq); + return OPERATOR_FINISHED; + } - BLI_assert((ret_value & OPERATOR_CANCELLED) == 0); + /* Select left, right or overlapping the current frame. */ + if (RNA_boolean_get(op->ptr, "side_of_frame")) { + if (!extend) { + ED_sequencer_deselect_all(scene); } - else if (deselect_all) { + sequencer_select_side_of_frame(C, v2d, mval, scene); + sequencer_select_do_updates(C, scene); + return OPERATOR_FINISHED; + } + + /* On Alt selection, select the strip and bordering handles. */ + if (seq && RNA_boolean_get(op->ptr, "linked_handle")) { + if (!extend) { ED_sequencer_deselect_all(scene); - ret_value = OPERATOR_FINISHED; } + sequencer_select_linked_handle(C, seq, handle_clicked); + sequencer_select_do_updates(C, scene); + sequencer_select_set_active(scene, seq); + return OPERATOR_FINISHED; } - ED_outliner_select_sync_from_sequence_tag(C); + const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); - WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); + /* Clicking on already selected element falls on modal operation. + * All strips are deselected on mouse button release unless extend mode is used. */ + if (seq && element_already_selected(seq, handle_clicked) && wait_to_deselect_others && !extend) { + return OPERATOR_RUNNING_MODAL; + } + + int ret_value = OPERATOR_CANCELLED; + if (!extend) { + ED_sequencer_deselect_all(scene); + ret_value = OPERATOR_FINISHED; + } + + /* Nothing to select, but strips could be deselected. */ + if (!seq) { + sequencer_select_do_updates(C, scene); + return ret_value; + } + + /* Do actual selection. */ + sequencer_select_strip_impl(ed, seq, handle_clicked, extend); + ret_value = OPERATOR_FINISHED; + sequencer_select_do_updates(C, scene); + sequencer_select_set_active(scene, seq); return ret_value; } @@ -764,13 +771,6 @@ void SEQUENCER_OT_select(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, - "deselect_all", - false, - "Deselect On Nothing", - "Deselect all when nothing under the cursor"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - - prop = RNA_def_boolean(ot->srna, "linked_handle", false, "Linked Handle", diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 2a6e49edfb6..0d09f2564e8 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -98,9 +98,10 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce sseq->chanshown = 0; sseq->view = SEQ_VIEW_SEQUENCE; sseq->mainb = SEQ_DRAW_IMG_IMBUF; - sseq->flag = SEQ_SHOW_GPENCIL | SEQ_USE_ALPHA | SEQ_SHOW_MARKERS | SEQ_SHOW_FCURVES | - SEQ_ZOOM_TO_FIT | SEQ_SHOW_STRIP_OVERLAY | SEQ_SHOW_STRIP_NAME | - SEQ_SHOW_STRIP_SOURCE | SEQ_SHOW_STRIP_DURATION | SEQ_SHOW_GRID; + sseq->flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_USE_ALPHA | SEQ_SHOW_MARKERS | + SEQ_TIMELINE_SHOW_FCURVES | SEQ_ZOOM_TO_FIT | SEQ_SHOW_OVERLAY | + SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE | + SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID; /* Tool header. */ region = MEM_callocN(sizeof(ARegion), "tool header for sequencer"); @@ -699,7 +700,7 @@ static void sequencer_preview_region_draw(const bContext *C, ARegion *region) Scene *scene = CTX_data_scene(C); wmWindowManager *wm = CTX_wm_manager(C); const bool draw_overlay = (scene->ed && (scene->ed->over_flag & SEQ_EDIT_OVERLAY_SHOW) && - (sseq->flag & SEQ_SHOW_STRIP_OVERLAY)); + (sseq->flag & SEQ_SHOW_OVERLAY)); /* XXX temp fix for wrong setting in sseq->mainb */ if (sseq->mainb == SEQ_DRAW_SEQUENCE) { diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index fcc92345bea..a82648aeee0 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -270,7 +270,7 @@ Object *spreadsheet_get_object_eval(const SpaceSpreadsheet *sspreadsheet, return nullptr; } Object *object_orig = (Object *)used_id; - if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) { + if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME, OB_CURVE, OB_FONT)) { return nullptr; } @@ -370,7 +370,7 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) std::unique_ptr<ColumnValues> values_ptr = data_source->get_column_values(*column->id); /* Should have been removed before if it does not exist anymore. */ BLI_assert(values_ptr); - const ColumnValues *values = scope.add(std::move(values_ptr), __func__); + const ColumnValues *values = scope.add(std::move(values_ptr)); const int width = get_column_width_in_pixels(*values); spreadsheet_layout.columns.append({values, width}); diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh index 680da9b6794..97170693cb3 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh @@ -35,6 +35,10 @@ struct CollectionCellValue { const Collection *collection; }; +struct GeometrySetCellValue { + const GeometrySet *geometry_set; +}; + /** * This is a type that can hold the value of a cell in a spreadsheet. This type allows us to * decouple the drawing of individual cells from the code that generates the data to be displayed. @@ -53,6 +57,7 @@ class CellValue { std::optional<ColorGeometry4f> value_color; std::optional<ObjectCellValue> value_object; std::optional<CollectionCellValue> value_collection; + std::optional<GeometrySetCellValue> value_geometry_set; }; } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index e38c70afd0f..78d9f61d8d5 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -45,15 +45,19 @@ namespace blender::ed::spreadsheet { void GeometryDataSource::foreach_default_column_ids( FunctionRef<void(const SpreadsheetColumnID &)> fn) const { - component_->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (meta_data.domain != domain_) { - return true; - } - SpreadsheetColumnID column_id; - column_id.name = (char *)name.c_str(); - fn(column_id); - return true; - }); + component_->attribute_foreach( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (meta_data.domain != domain_) { + return true; + } + if (attribute_id.is_anonymous()) { + return true; + } + SpreadsheetColumnID column_id; + column_id.name = (char *)attribute_id.name().data(); + fn(column_id); + return true; + }); } std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( @@ -65,7 +69,7 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( if (!attribute) { return {}; } - const fn::GVArray *varray = scope_.add(std::move(attribute.varray), __func__); + const fn::GVArray *varray = scope_.add(std::move(attribute.varray)); if (attribute.domain != domain_) { return {}; } @@ -332,6 +336,11 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( r_cell_value.value_collection = CollectionCellValue{&collection}; break; } + case InstanceReference::Type::GeometrySet: { + const GeometrySet &geometry_set = reference.geometry_set(); + r_cell_value.value_geometry_set = GeometrySetCellValue{&geometry_set}; + break; + } case InstanceReference::Type::None: { break; } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc index 8079763a339..1a5eac53306 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -209,6 +209,23 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { 0, nullptr); } + else if (cell_value.value_geometry_set.has_value()) { + uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_MESH_DATA, + "Geometry", + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + } } void draw_float_vector(const CellDrawParams ¶ms, const Span<float> values) const diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc index ae336edfead..1e46fef8d71 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc @@ -328,7 +328,7 @@ Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet, geometry_data_source->apply_selection_filter(rows_included); } - Vector<int64_t> &indices = scope.construct<Vector<int64_t>>(__func__); + Vector<int64_t> &indices = scope.construct<Vector<int64_t>>(); index_vector_from_bools(rows_included, indices); return indices; diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index e3f97dd1c63..3f572bf9d5a 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -1438,7 +1438,7 @@ void VIEW3D_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flags */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_gesture_lasso(ot); @@ -1953,7 +1953,8 @@ static int mixed_bones_object_selectbuffer(ViewContext *vc, const int mval[2], eV3DSelectObjectFilter select_filter, bool do_nearest, - bool do_nearest_xray_if_supported) + bool do_nearest_xray_if_supported, + const bool do_material_slot_selection) { rcti rect; int hits15, hits9 = 0, hits5 = 0; @@ -1972,7 +1973,8 @@ static int mixed_bones_object_selectbuffer(ViewContext *vc, view3d_opengl_select_cache_begin(); BLI_rcti_init_pt_radius(&rect, mval, 14); - hits15 = view3d_opengl_select(vc, buffer, MAXPICKBUF, &rect, select_mode, select_filter); + hits15 = view3d_opengl_select_ex( + vc, buffer, MAXPICKBUF, &rect, select_mode, select_filter, do_material_slot_selection); if (hits15 == 1) { hits = selectbuffer_ret_hits_15(buffer, hits15); goto finally; @@ -2071,7 +2073,8 @@ static int mixed_bones_object_selectbuffer_extended(ViewContext *vc, do_nearest = do_nearest && !enumerate; - int hits = mixed_bones_object_selectbuffer(vc, buffer, mval, select_filter, do_nearest, true); + int hits = mixed_bones_object_selectbuffer( + vc, buffer, mval, select_filter, do_nearest, true, false); return hits; } @@ -2088,12 +2091,14 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, int hits, Base *startbase, bool has_bones, - bool do_nearest) + bool do_nearest, + int *r_sub_selection) { ViewLayer *view_layer = vc->view_layer; View3D *v3d = vc->v3d; Base *base, *basact = NULL; int a; + int sub_selection_id = 0; if (do_nearest) { uint min = 0xFFFFFFFF; @@ -2105,6 +2110,7 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, if (min > buffer[4 * a + 1] && (buffer[4 * a + 3] & 0xFFFF0000)) { min = buffer[4 * a + 1]; selcol = buffer[4 * a + 3] & 0xFFFF; + sub_selection_id = (buffer[4 * a + 3] & 0xFFFF0000) >> 16; } } } @@ -2118,6 +2124,7 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, if (min > buffer[4 * a + 1] && notcol != (buffer[4 * a + 3] & 0xFFFF)) { min = buffer[4 * a + 1]; selcol = buffer[4 * a + 3] & 0xFFFF; + sub_selection_id = (buffer[4 * a + 3] & 0xFFFF0000) >> 16; } } } @@ -2184,11 +2191,16 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, } } + if (basact && r_sub_selection) { + *r_sub_selection = sub_selection_id; + } + return basact; } -/* mval comes from event->mval, only use within region handlers */ -Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) +static Base *ed_view3d_give_base_under_cursor_ex(bContext *C, + const int mval[2], + int *r_material_slot) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewContext vc; @@ -2202,18 +2214,30 @@ Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) ED_view3d_viewcontext_init(C, &vc, depsgraph); const bool do_nearest = !XRAY_ACTIVE(vc.v3d); + const bool do_material_slot_selection = r_material_slot != NULL; const int hits = mixed_bones_object_selectbuffer( - &vc, buffer, mval, VIEW3D_SELECT_FILTER_NOP, do_nearest, false); + &vc, buffer, mval, VIEW3D_SELECT_FILTER_NOP, do_nearest, false, do_material_slot_selection); if (hits > 0) { - const bool has_bones = selectbuffer_has_bones(buffer, hits); - basact = mouse_select_eval_buffer( - &vc, buffer, hits, vc.view_layer->object_bases.first, has_bones, do_nearest); + const bool has_bones = (r_material_slot == NULL) && selectbuffer_has_bones(buffer, hits); + basact = mouse_select_eval_buffer(&vc, + buffer, + hits, + vc.view_layer->object_bases.first, + has_bones, + do_nearest, + r_material_slot); } return basact; } +/* mval comes from event->mval, only use within region handlers */ +Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) +{ + return ed_view3d_give_base_under_cursor_ex(C, mval, NULL); +} + Object *ED_view3d_give_object_under_cursor(bContext *C, const int mval[2]) { Base *base = ED_view3d_give_base_under_cursor(C, mval); @@ -2223,6 +2247,17 @@ Object *ED_view3d_give_object_under_cursor(bContext *C, const int mval[2]) return NULL; } +struct Object *ED_view3d_give_material_slot_under_cursor(struct bContext *C, + const int mval[2], + int *r_material_slot) +{ + Base *base = ed_view3d_give_base_under_cursor_ex(C, mval, r_material_slot); + if (base) { + return base->object; + } + return NULL; +} + bool ED_view3d_is_object_under_cursor(bContext *C, const int mval[2]) { return ED_view3d_give_object_under_cursor(C, mval) != NULL; @@ -2374,7 +2409,8 @@ static bool ed_object_select_pick(bContext *C, } } else { - basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, has_bones, do_nearest); + basact = mouse_select_eval_buffer( + &vc, buffer, hits, startbase, has_bones, do_nearest, NULL); } if (has_bones && basact) { @@ -2436,7 +2472,7 @@ static bool ed_object_select_pick(bContext *C, if (!changed) { /* fallback to regular object selection if no new bundles were selected, * allows to select object parented to reconstruction object */ - basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, 0, do_nearest); + basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, 0, do_nearest, NULL); } } } @@ -2677,7 +2713,7 @@ static int view3d_select_exec(bContext *C, wmOperator *op) uint buffer[MAXPICKBUF]; const int hits = mixed_bones_object_selectbuffer( - &vc, buffer, location, VIEW3D_SELECT_FILTER_NOP, false, true); + &vc, buffer, location, VIEW3D_SELECT_FILTER_NOP, false, true, false); retval = bone_mouse_select_menu(C, buffer, hits, true, extend, deselect, toggle); } if (!retval) { diff --git a/source/blender/editors/space_view3d/view3d_view.c b/source/blender/editors/space_view3d/view3d_view.c index 86a610f8dd9..f5da7c14a88 100644 --- a/source/blender/editors/space_view3d/view3d_view.c +++ b/source/blender/editors/space_view3d/view3d_view.c @@ -137,7 +137,6 @@ void ED_view3d_smooth_view_ex( { RegionView3D *rv3d = region->regiondata; struct SmoothView3DStore sms = {{0}}; - bool ok = false; /* initialize sms */ view3d_smooth_view_state_backup(&sms.dst, v3d, rv3d); @@ -200,92 +199,75 @@ void ED_view3d_smooth_view_ex( sms.to_camera = true; /* restore view3d values in end */ } - /* skip smooth viewing for external render engine draw */ + if ((sview->camera_old == sview->camera) && /* Camera. */ + (sms.dst.dist == rv3d->dist) && /* Distance. */ + (sms.dst.lens == v3d->lens) && /* Lens. */ + equals_v3v3(sms.dst.ofs, rv3d->ofs) && /* Offset. */ + equals_v4v4(sms.dst.quat, rv3d->viewquat) /* Rotation. */ + ) { + /* Early return if nothing changed. */ + return; + } + + /* Skip smooth viewing for external render engine draw. */ if (smooth_viewtx && !(v3d->shading.type == OB_RENDER && rv3d->render_engine)) { - bool changed = false; /* zero means no difference */ - if (sview->camera_old != sview->camera) { - changed = true; - } - else if (sms.dst.dist != rv3d->dist) { - changed = true; - } - else if (sms.dst.lens != v3d->lens) { - changed = true; - } - else if (!equals_v3v3(sms.dst.ofs, rv3d->ofs)) { - changed = true; + /* original values */ + if (sview->camera_old) { + Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old); + if (sview->ofs != NULL) { + sms.src.dist = ED_view3d_offset_distance(ob_camera_old_eval->obmat, sview->ofs, 0.0f); + } + ED_view3d_from_object( + ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens); } - else if (!equals_v4v4(sms.dst.quat, rv3d->viewquat)) { - changed = true; + /* grid draw as floor */ + if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) { + /* use existing if exists, means multiple calls to smooth view + * won't lose the original 'view' setting */ + rv3d->view = RV3D_VIEW_USER; } - /* The new view is different from the old one - * so animate the view */ - if (changed) { - /* original values */ - if (sview->camera_old) { - Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old); - if (sview->ofs != NULL) { - sms.src.dist = ED_view3d_offset_distance(ob_camera_old_eval->obmat, sview->ofs, 0.0f); - } - ED_view3d_from_object( - ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens); - } - /* grid draw as floor */ - if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) { - /* use existing if exists, means multiple calls to smooth view - * won't lose the original 'view' setting */ - rv3d->view = RV3D_VIEW_USER; - } - - sms.time_allowed = (double)smooth_viewtx / 1000.0; - - /* if this is view rotation only - * we can decrease the time allowed by - * the angle between quats - * this means small rotations won't lag */ - if (sview->quat && !sview->ofs && !sview->dist) { - /* scale the time allowed by the rotation */ - /* 180deg == 1.0 */ - sms.time_allowed *= (double)fabsf( - angle_signed_normalized_qtqt(sms.dst.quat, sms.src.quat)) / - M_PI; - } + sms.time_allowed = (double)smooth_viewtx / 1000.0; - /* ensure it shows correct */ - if (sms.to_camera) { - /* use ortho if we move from an ortho view to an ortho camera */ - Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera); - rv3d->persp = (((rv3d->is_persp == false) && (ob_camera_eval->type == OB_CAMERA) && - (((Camera *)ob_camera_eval->data)->type == CAM_ORTHO)) ? - RV3D_ORTHO : - RV3D_PERSP); - } + /* If this is view rotation only we can decrease the time allowed by the angle between quats + * this means small rotations won't lag. */ + if (sview->quat && !sview->ofs && !sview->dist) { + /* scale the time allowed by the rotation */ + /* 180deg == 1.0 */ + sms.time_allowed *= (double)fabsf(angle_signed_normalized_qtqt(sms.dst.quat, sms.src.quat)) / + M_PI; + } - rv3d->rflag |= RV3D_NAVIGATING; + /* ensure it shows correct */ + if (sms.to_camera) { + /* use ortho if we move from an ortho view to an ortho camera */ + Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera); + rv3d->persp = (((rv3d->is_persp == false) && (ob_camera_eval->type == OB_CAMERA) && + (((Camera *)ob_camera_eval->data)->type == CAM_ORTHO)) ? + RV3D_ORTHO : + RV3D_PERSP); + } - /* not essential but in some cases the caller will tag the area for redraw, and in that - * case we can get a flicker of the 'org' user view but we want to see 'src' */ - view3d_smooth_view_state_restore(&sms.src, v3d, rv3d); + rv3d->rflag |= RV3D_NAVIGATING; - /* keep track of running timer! */ - if (rv3d->sms == NULL) { - rv3d->sms = MEM_mallocN(sizeof(struct SmoothView3DStore), "smoothview v3d"); - } - *rv3d->sms = sms; - if (rv3d->smooth_timer) { - WM_event_remove_timer(wm, win, rv3d->smooth_timer); - } - /* #TIMER1 is hard-coded in key-map. */ - rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); + /* not essential but in some cases the caller will tag the area for redraw, and in that + * case we can get a flicker of the 'org' user view but we want to see 'src' */ + view3d_smooth_view_state_restore(&sms.src, v3d, rv3d); - ok = true; + /* keep track of running timer! */ + if (rv3d->sms == NULL) { + rv3d->sms = MEM_mallocN(sizeof(struct SmoothView3DStore), "smoothview v3d"); + } + *rv3d->sms = sms; + if (rv3d->smooth_timer) { + WM_event_remove_timer(wm, win, rv3d->smooth_timer); } + /* #TIMER1 is hard-coded in key-map. */ + rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); } - - /* if we get here nothing happens */ - if (ok == false) { + else { + /* Animation is disabled, apply immediately. */ if (sms.to_camera == false) { copy_v3_v3(rv3d->ofs, sms.dst.ofs); copy_qt_qt(rv3d->viewquat, sms.dst.quat); @@ -300,6 +282,8 @@ void ED_view3d_smooth_view_ex( } ED_region_tag_redraw(region); + + WM_event_add_mousemove(win); } } @@ -320,6 +304,7 @@ void ED_view3d_smooth_view(bContext *C, /* only meant for timer usage */ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, bool sync_boxview) { + wmWindowManager *wm = CTX_wm_manager(C); RegionView3D *rv3d = region->regiondata; struct SmoothView3DStore *sms = rv3d->sms; float step, step_inv; @@ -333,6 +318,7 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b /* end timer */ if (step >= 1.0f) { + wmWindow *win = CTX_wm_window(C); /* if we went to camera, store the original */ if (sms->to_camera) { @@ -355,9 +341,12 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b MEM_freeN(rv3d->sms); rv3d->sms = NULL; - WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), rv3d->smooth_timer); + WM_event_remove_timer(wm, win, rv3d->smooth_timer); rv3d->smooth_timer = NULL; rv3d->rflag &= ~RV3D_NAVIGATING; + + /* Event handling won't know if a UI item has been moved under the pointer. */ + WM_event_add_mousemove(win); } else { /* ease in/out */ @@ -380,12 +369,9 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d); - if (ED_screen_animation_playing(CTX_wm_manager(C))) { + if (ED_screen_animation_playing(wm)) { ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true); } - - /* Event handling won't know if a UI item has been moved under the pointer. */ - WM_event_add_mousemove(CTX_wm_window(C)); } if (sync_boxview && (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW)) { @@ -964,12 +950,13 @@ static bool drw_select_filter_object_mode_lock_for_weight_paint(Object *ob, void * * \note (vc->obedit == NULL) can be set to explicitly skip edit-object selection. */ -int view3d_opengl_select(ViewContext *vc, - uint *buffer, - uint bufsize, - const rcti *input, - eV3DSelectMode select_mode, - eV3DSelectObjectFilter select_filter) +int view3d_opengl_select_ex(ViewContext *vc, + uint *buffer, + uint bufsize, + const rcti *input, + eV3DSelectMode select_mode, + eV3DSelectObjectFilter select_filter, + const bool do_material_slot_selection) { struct bThemeState theme_state; const wmWindowManager *wm = CTX_wm_manager(vc->C); @@ -1119,6 +1106,7 @@ int view3d_opengl_select(ViewContext *vc, use_obedit_skip, draw_surface, use_nearest, + do_material_slot_selection, &rect, drw_select_loop_pass, &drw_select_loop_user_data, @@ -1149,6 +1137,7 @@ int view3d_opengl_select(ViewContext *vc, use_obedit_skip, draw_surface, use_nearest, + do_material_slot_selection, &rect, drw_select_loop_pass, &drw_select_loop_user_data, @@ -1178,6 +1167,16 @@ finally: return hits; } +int view3d_opengl_select(ViewContext *vc, + uint *buffer, + uint bufsize, + const rcti *input, + eV3DSelectMode select_mode, + eV3DSelectObjectFilter select_filter) +{ + return view3d_opengl_select_ex(vc, buffer, bufsize, input, select_mode, select_filter, false); +} + int view3d_opengl_select_with_id_filter(ViewContext *vc, uint *buffer, uint bufsize, diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 013c5faa54a..d1a1937cef1 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -618,9 +618,6 @@ typedef struct TransInfo { O_SET, } orient_curr; - /** backup from view3d, to restore on end. */ - short gizmo_flag; - short prop_mode; /** Value taken as input, either through mouse coordinates or entered as a parameter. */ diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c index 075db30fa61..a6658ae00a3 100644 --- a/source/blender/editors/transform/transform_convert_action.c +++ b/source/blender/editors/transform/transform_convert_action.c @@ -51,7 +51,10 @@ /* helper struct for gp-frame transforms */ typedef struct tGPFtransdata { - float val; /* where transdata writes transform */ + union { + float val; /* where transdata writes transform */ + float loc[3]; /* #td->val and #td->loc share the same pointer. */ + }; int *sdata; /* pointer to gpf->framenum */ } tGPFtransdata; @@ -245,8 +248,8 @@ static int GPLayerToTransData(TransData *td, tfd->val = (float)gpf->framenum; tfd->sdata = &gpf->framenum; - td->val = td->loc = &tfd->val; /* XXX: It's not a 3d array. */ - td->ival = td->iloc[0] = (float)gpf->framenum; + td->val = td->loc = &tfd->val; + td->ival = td->iloc[0] = tfd->val; td->center[0] = td->ival; td->center[1] = ypos; @@ -279,16 +282,15 @@ static int MaskLayerToTransData(TransData *td, masklay_shape = masklay_shape->next) { if (is_prop_edit || (masklay_shape->flag & MASK_SHAPE_SELECT)) { if (FrameOnMouseSide(side, (float)masklay_shape->frame, cfra)) { - /* memory is calloc'ed, so that should zero everything nicely for us */ - td->val = &tfd->val; - td->ival = (float)masklay_shape->frame; + tfd->val = (float)masklay_shape->frame; + tfd->sdata = &masklay_shape->frame; + + td->val = td->loc = &tfd->val; + td->ival = td->iloc[0] = tfd->val; td->center[0] = td->ival; td->center[1] = ypos; - tfd->val = (float)masklay_shape->frame; - tfd->sdata = &masklay_shape->frame; - /* advance td now */ td++; tfd++; diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 9f5e74db501..c493b9bd102 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -249,12 +249,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->view = v3d; t->animtimer = (animscreen) ? animscreen->animtimer : NULL; - /* turn gizmo off during transform */ - if (t->flag & T_MODAL) { - t->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; - } - if (t->scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) { t->flag |= T_V3D_ALIGN; } @@ -742,13 +736,6 @@ void postTrans(bContext *C, TransInfo *t) } } } - else if (t->spacetype == SPACE_VIEW3D) { - View3D *v3d = t->area->spacedata.first; - /* restore gizmo */ - if (t->flag & T_MODAL) { - v3d->gizmo_flag = t->gizmo_flag; - } - } if (t->mouse.data) { MEM_freeN(t->mouse.data); @@ -791,7 +778,7 @@ static void restoreElement(TransData *td) { transdata_restore_basic((TransDataBasic *)td); - if (td->val) { + if (td->val && td->val != td->loc) { *td->val = td->ival; } diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index 8dc4f107837..0fa179c4f74 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -1974,8 +1974,8 @@ void VIEW3D_GGT_xform_gizmo(wmGizmoGroupType *gzgt) gzgt->name = "3D View: Transform Gizmo"; gzgt->idname = "VIEW3D_GGT_xform_gizmo"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; + gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; @@ -2216,8 +2216,8 @@ void VIEW3D_GGT_xform_cage(wmGizmoGroupType *gzgt) gzgt->name = "Transform Cage"; gzgt->idname = "VIEW3D_GGT_xform_cage"; - gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; + gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; @@ -2459,8 +2459,8 @@ void VIEW3D_GGT_xform_shear(wmGizmoGroupType *gzgt) gzgt->name = "Transform Shear"; gzgt->idname = "VIEW3D_GGT_xform_shear"; - gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; + gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; diff --git a/source/blender/editors/transform/transform_ops.c b/source/blender/editors/transform/transform_ops.c index 9638ec8750e..3a4a9342e18 100644 --- a/source/blender/editors/transform/transform_ops.c +++ b/source/blender/editors/transform/transform_ops.c @@ -911,7 +911,8 @@ static void TRANSFORM_OT_bend(struct wmOperatorType *ot) ot->name = "Bend"; ot->description = "Bend selected items between the 3D cursor and the mouse"; ot->idname = OP_BEND; - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + /* Depend on cursor location because the cursor location is used to define the region to bend. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_DEPENDS_ON_CURSOR; /* api callbacks */ ot->invoke = transform_invoke; @@ -1091,7 +1092,7 @@ static void TRANSFORM_OT_edge_slide(struct wmOperatorType *ot) ot->name = "Edge Slide"; ot->description = "Slide an edge loop along a mesh"; ot->idname = OP_EDGE_SLIDE; - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_DEPENDS_ON_CURSOR; /* api callbacks */ ot->invoke = transform_invoke; @@ -1129,7 +1130,7 @@ static void TRANSFORM_OT_vert_slide(struct wmOperatorType *ot) ot->name = "Vertex Slide"; ot->description = "Slide a vertex along a mesh"; ot->idname = OP_VERT_SLIDE; - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_DEPENDS_ON_CURSOR; /* api callbacks */ ot->invoke = transform_invoke; diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index bb04f557074..811f30c96e5 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -501,9 +501,7 @@ static void iter_snap_objects(SnapObjectContext *sctx, } Object *obj_eval = DEG_get_evaluated_object(depsgraph, base->object); - if (obj_eval->transflag & OB_DUPLI || - (obj_eval->runtime.geometry_set_eval != NULL && - BKE_geometry_set_has_instances(obj_eval->runtime.geometry_set_eval))) { + if (obj_eval->transflag & OB_DUPLI || BKE_object_has_geometry_set_instances(obj_eval)) { ListBase *lb = object_duplilist(depsgraph, sctx->scene, obj_eval); for (DupliObject *dupli_ob = lb->first; dupli_ob; dupli_ob = dupli_ob->next) { BLI_assert(DEG_is_evaluated_object(dupli_ob->ob)); diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c index a54149912a9..e82a00bcc77 100644 --- a/source/blender/editors/transform/transform_snap_sequencer.c +++ b/source/blender/editors/transform/transform_snap_sequencer.c @@ -260,7 +260,7 @@ TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) SeqCollection *snap_sources = SEQ_query_selected_strips(seqbase); SeqCollection *snap_targets = query_snap_targets(t, snap_sources); - if (SEQ_collection_len(snap_sources) == 0 || SEQ_collection_len(snap_targets) == 0) { + if (SEQ_collection_len(snap_sources) == 0) { SEQ_collection_free(snap_targets); SEQ_collection_free(snap_sources); MEM_freeN(snap_data); diff --git a/source/blender/editors/undo/ed_undo.c b/source/blender/editors/undo/ed_undo.c index 84d5d3b9aae..22064e04e86 100644 --- a/source/blender/editors/undo/ed_undo.c +++ b/source/blender/editors/undo/ed_undo.c @@ -578,8 +578,8 @@ static bool ed_undo_is_init_poll(bContext *C) * it will be part of the exception when attempting to call undo in background mode. */ CTX_wm_operator_poll_msg_set( C, - "Undo disabled at startup in background-mode. " - "Call `ed.undo_push()` to explicitly initialize the undo-system."); + "Undo disabled at startup in background-mode " + "(call `ed.undo_push()` to explicitly initialize the undo-system)"); return false; } return true; diff --git a/source/blender/editors/uvedit/uvedit_rip.c b/source/blender/editors/uvedit/uvedit_rip.c index 631b831411f..7e4b18340c5 100644 --- a/source/blender/editors/uvedit/uvedit_rip.c +++ b/source/blender/editors/uvedit/uvedit_rip.c @@ -953,7 +953,7 @@ void UV_OT_rip(wmOperatorType *ot) ot->name = "UV Rip"; ot->description = "Rip selected vertices or a selected region"; ot->idname = "UV_OT_rip"; - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* api callbacks */ ot->exec = uv_rip_exec; diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index 5a82cd31112..c0ccf1b7095 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -3450,7 +3450,7 @@ void UV_OT_select_lasso(wmOperatorType *ot) ot->cancel = WM_gesture_lasso_cancel; /* flags */ - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_gesture_lasso(ot); diff --git a/source/blender/freestyle/intern/application/AppConfig.h b/source/blender/freestyle/intern/application/AppConfig.h index 61beff33876..adb6d906e68 100644 --- a/source/blender/freestyle/intern/application/AppConfig.h +++ b/source/blender/freestyle/intern/application/AppConfig.h @@ -115,10 +115,6 @@ static const string OPTIONS_FILE("options.xml"); static const string OPTIONS_CURRENT_DIRS_FILE("current_dirs.xml"); static const string OPTIONS_QGLVIEWER_FILE("qglviewer.xml"); -// Default options -static const real DEFAULT_SPHERE_RADIUS = 1.0; -static const real DEFAULT_DKR_EPSILON = 0.0; - } // namespace Config } /* namespace Freestyle */ diff --git a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp index 7772a30c5f4..c74fd60fe35 100644 --- a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp +++ b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp @@ -65,9 +65,6 @@ using namespace Freestyle; extern "C" { -#define DEFAULT_SPHERE_RADIUS 1.0f -#define DEFAULT_DKR_EPSILON 0.0f - struct FreestyleGlobals g_freestyle; // Freestyle configuration @@ -433,14 +430,8 @@ static void prepare(Render *re, ViewLayer *view_layer, Depsgraph *depsgraph) } // set parameters - if (config->flags & FREESTYLE_ADVANCED_OPTIONS_FLAG) { - controller->setSphereRadius(config->sphere_radius); - controller->setSuggestiveContourKrDerivativeEpsilon(config->dkr_epsilon); - } - else { - controller->setSphereRadius(DEFAULT_SPHERE_RADIUS); - controller->setSuggestiveContourKrDerivativeEpsilon(DEFAULT_DKR_EPSILON); - } + controller->setSphereRadius(config->sphere_radius); + controller->setSuggestiveContourKrDerivativeEpsilon(config->dkr_epsilon); controller->setFaceSmoothness((config->flags & FREESTYLE_FACE_SMOOTHNESS_FLAG) ? true : false); controller->setCreaseAngle(RAD2DEGF(config->crease_angle)); controller->setVisibilityAlgo((config->flags & FREESTYLE_CULLING) ? diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt index f8d2acc74a8..856668f01d7 100644 --- a/source/blender/functions/CMakeLists.txt +++ b/source/blender/functions/CMakeLists.txt @@ -28,14 +28,21 @@ set(INC_SYS set(SRC intern/cpp_types.cc + intern/field.cc intern/generic_vector_array.cc intern/generic_virtual_array.cc intern/generic_virtual_vector_array.cc intern/multi_function.cc intern/multi_function_builder.cc + intern/multi_function_parallel.cc + intern/multi_function_procedure.cc + intern/multi_function_procedure_builder.cc + intern/multi_function_procedure_executor.cc FN_cpp_type.hh FN_cpp_type_make.hh + FN_field.hh + FN_field_cpp_type.hh FN_generic_pointer.hh FN_generic_span.hh FN_generic_value_map.hh @@ -48,6 +55,10 @@ set(SRC FN_multi_function_data_type.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 FN_multi_function_signature.hh ) @@ -55,13 +66,31 @@ set(LIB bf_blenlib ) +if(WITH_TBB) + add_definitions(-DWITH_TBB) + if(WIN32) + # TBB includes Windows.h which will define min/max macros + # that will collide with the stl versions. + add_definitions(-DNOMINMAX) + endif() + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + + list(APPEND LIB + ${TBB_LIBRARIES} + ) +endif() + blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") if(WITH_GTESTS) set(TEST_SRC tests/FN_cpp_type_test.cc + tests/FN_field_test.cc tests/FN_generic_span_test.cc tests/FN_generic_vector_array_test.cc + tests/FN_multi_function_procedure_test.cc tests/FN_multi_function_test.cc ) set(TEST_LIB diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh new file mode 100644 index 00000000000..d4375b625ce --- /dev/null +++ b/source/blender/functions/FN_field.hh @@ -0,0 +1,487 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup fn + * + * A #Field represents a function that outputs a value based on an arbitrary number of inputs. The + * inputs for a specific field evaluation are provided by a #FieldContext. + * + * A typical example is a field that computes a displacement vector for every vertex on a mesh + * based on its position. + * + * Fields can be build, composed and evaluated at run-time. They are stored in a directed tree + * graph data structure, whereby each node is a #FieldNode and edges are dependencies. A #FieldNode + * has an arbitrary number of inputs and at least one output and a #Field references a specific + * output of a #FieldNode. The inputs of a #FieldNode are other fields. + * + * There are two different types of field nodes: + * - #FieldInput: Has no input and exactly one output. It represents an input to the entire field + * when it is evaluated. During evaluation, the value of this input is based on a #FieldContext. + * - #FieldOperation: Has an arbitrary number of field inputs and at least one output. Its main + * use is to compose multiple existing fields into new fields. + * + * When fields are evaluated, they are converted into a multi-function procedure which allows + * efficient computation. In the future, we might support different field evaluation mechanisms for + * e.g. the following scenarios: + * - Latency of a single evaluation is more important than throughput. + * - Evaluation should happen on other hardware like GPUs. + * + * Whenever possible, multiple fields should be evaluated together to avoid duplicate work when + * they share common sub-fields and a common context. + */ + +#include "BLI_function_ref.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "FN_generic_virtual_array.hh" +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_procedure.hh" +#include "FN_multi_function_procedure_builder.hh" +#include "FN_multi_function_procedure_executor.hh" + +namespace blender::fn { + +class FieldInput; + +/** + * A node in a field-tree. It has at least one output that can be referenced by fields. + */ +class FieldNode { + private: + bool is_input_; + /** + * True when this node is a #FieldInput or (potentially indirectly) depends on one. This could + * always be derived again later by traversing the field-tree, but keeping track of it while the + * field is built is cheaper. + * + * If this is false, the field is constant. Note that even when this is true, the field may be + * constant when all inputs are constant. + */ + bool depends_on_input_; + + public: + FieldNode(bool is_input, bool depends_on_input) + : is_input_(is_input), depends_on_input_(depends_on_input) + { + } + + virtual ~FieldNode() = default; + + virtual const CPPType &output_cpp_type(int output_index) const = 0; + + bool is_input() const + { + return is_input_; + } + + bool is_operation() const + { + return !is_input_; + } + + bool depends_on_input() const + { + return depends_on_input_; + } + + /** + * Invoke callback for every field input. It might be called multiple times for the same input. + * The caller is responsible for deduplication if required. + */ + virtual void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const = 0; + + virtual uint64_t hash() const + { + return get_default_hash(this); + } + + friend bool operator==(const FieldNode &a, const FieldNode &b) + { + return a.is_equal_to(b); + } + + friend bool operator!=(const FieldNode &a, const FieldNode &b) + { + return !(a == b); + } + + virtual bool is_equal_to(const FieldNode &other) const + { + return this == &other; + } +}; + +/** + * Common base class for fields to avoid declaring the same methods for #GField and #GFieldRef. + */ +template<typename NodePtr> class GFieldBase { + protected: + NodePtr node_ = nullptr; + int node_output_index_ = 0; + + GFieldBase(NodePtr node, const int node_output_index) + : node_(std::move(node)), node_output_index_(node_output_index) + { + } + + public: + GFieldBase() = default; + + operator bool() const + { + return node_ != nullptr; + } + + friend bool operator==(const GFieldBase &a, const GFieldBase &b) + { + /* Two nodes can compare equal even when their pointer is not the same. For example, two + * "Position" nodes are the same. */ + return *a.node_ == *b.node_ && a.node_output_index_ == b.node_output_index_; + } + + uint64_t hash() const + { + return get_default_hash_2(*node_, node_output_index_); + } + + const fn::CPPType &cpp_type() const + { + return node_->output_cpp_type(node_output_index_); + } + + const FieldNode &node() const + { + return *node_; + } + + int node_output_index() const + { + return node_output_index_; + } +}; + +/** + * A field whose output type is only known at run-time. + */ +class GField : public GFieldBase<std::shared_ptr<FieldNode>> { + public: + GField() = default; + + GField(std::shared_ptr<FieldNode> node, const int node_output_index = 0) + : GFieldBase<std::shared_ptr<FieldNode>>(std::move(node), node_output_index) + { + } +}; + +/** + * Same as #GField but is cheaper to copy/move around, because it does not contain a + * #std::shared_ptr. + */ +class GFieldRef : public GFieldBase<const FieldNode *> { + public: + GFieldRef() = default; + + GFieldRef(const GField &field) + : GFieldBase<const FieldNode *>(&field.node(), field.node_output_index()) + { + } + + GFieldRef(const FieldNode &node, const int node_output_index = 0) + : GFieldBase<const FieldNode *>(&node, node_output_index) + { + } +}; + +/** + * A typed version of #GField. It has the same memory layout as #GField. + */ +template<typename T> class Field : public GField { + public: + Field() = default; + + Field(GField field) : GField(std::move(field)) + { + BLI_assert(this->cpp_type().template is<T>()); + } + + Field(std::shared_ptr<FieldNode> node, const int node_output_index = 0) + : Field(GField(std::move(node), node_output_index)) + { + } +}; + +/** + * A #FieldNode that allows composing existing fields into new fields. + */ +class FieldOperation : public FieldNode { + /** + * The multi-function used by this node. It is optionally owned. + * Multi-functions with mutable or vector parameters are not supported currently. + */ + std::unique_ptr<const MultiFunction> owned_function_; + const MultiFunction *function_; + + /** Inputs to the operation. */ + blender::Vector<GField> inputs_; + + public: + FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {}); + FieldOperation(const MultiFunction &function, Vector<GField> inputs = {}); + + Span<GField> inputs() const + { + return inputs_; + } + + const MultiFunction &multi_function() const + { + return *function_; + } + + const CPPType &output_cpp_type(int output_index) const override + { + int output_counter = 0; + for (const int param_index : function_->param_indices()) { + MFParamType param_type = function_->param_type(param_index); + if (param_type.is_output()) { + if (output_counter == output_index) { + return param_type.data_type().single_type(); + } + output_counter++; + } + } + BLI_assert_unreachable(); + return CPPType::get<float>(); + } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; +}; + +class FieldContext; + +/** + * A #FieldNode that represents an input to the entire field-tree. + */ +class FieldInput : public FieldNode { + protected: + const CPPType *type_; + std::string debug_name_; + + public: + FieldInput(const CPPType &type, std::string debug_name = ""); + + /** + * Get the value of this specific input based on the given context. The returned virtual array, + * should live at least as long as the passed in #scope. May return null. + */ + virtual const GVArray *get_varray_for_context(const FieldContext &context, + IndexMask mask, + ResourceScope &scope) const = 0; + + virtual std::string socket_inspection_name() const + { + return debug_name_; + } + + blender::StringRef debug_name() const + { + return debug_name_; + } + + const CPPType &cpp_type() const + { + return *type_; + } + + const CPPType &output_cpp_type(int output_index) const override + { + BLI_assert(output_index == 0); + UNUSED_VARS_NDEBUG(output_index); + return *type_; + } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; +}; + +/** + * Provides inputs for a specific field evaluation. + */ +class FieldContext { + public: + ~FieldContext() = default; + + virtual const GVArray *get_varray_for_input(const FieldInput &field_input, + IndexMask mask, + ResourceScope &scope) const; +}; + +/** + * Utility class that makes it easier to evaluate fields. + */ +class FieldEvaluator : NonMovable, NonCopyable { + private: + struct OutputPointerInfo { + void *dst = nullptr; + /* When a destination virtual array is provided for an input, this is + * unnecessary, otherwise this is used to construct the required virtual array. */ + void (*set)(void *dst, const GVArray &varray, ResourceScope &scope) = nullptr; + }; + + ResourceScope scope_; + const FieldContext &context_; + const IndexMask mask_; + Vector<GField> fields_to_evaluate_; + Vector<GVMutableArray *> dst_varrays_; + Vector<const GVArray *> evaluated_varrays_; + Vector<OutputPointerInfo> output_pointer_infos_; + bool is_evaluated_ = false; + + public: + /** Takes #mask by pointer because the mask has to live longer than the evaluator. */ + FieldEvaluator(const FieldContext &context, const IndexMask *mask) + : context_(context), mask_(*mask) + { + } + + /** Construct a field evaluator for all indices less than #size. */ + FieldEvaluator(const FieldContext &context, const int64_t size) : context_(context), mask_(size) + { + } + + ~FieldEvaluator() + { + /* While this assert isn't strictly necessary, and could be replaced with a warning, + * it will catch cases where someone forgets to call #evaluate(). */ + BLI_assert(is_evaluated_); + } + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable virtual array that the evaluated result for this field is be written into. + */ + int add_with_destination(GField field, GVMutableArray &dst); + + /** Same as #add_with_destination but typed. */ + template<typename T> int add_with_destination(Field<T> field, VMutableArray<T> &dst) + { + GVMutableArray &varray = scope_.construct<GVMutableArray_For_VMutableArray<T>>(dst); + return this->add_with_destination(GField(std::move(field)), varray); + } + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable span that the evaluated result for this field is be written into. + * \note: When the output may only be used as a single value, the version of this function with + * a virtual array result array should be used. + */ + int add_with_destination(GField field, GMutableSpan dst); + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable span that the evaluated result for this field is be written into. + * \note: When the output may only be used as a single value, the version of this function with + * a virtual array result array should be used. + */ + template<typename T> int add_with_destination(Field<T> field, MutableSpan<T> dst) + { + GVMutableArray &varray = scope_.construct<GVMutableArray_For_MutableSpan<T>>(dst); + return this->add_with_destination(std::move(field), varray); + } + + int add(GField field, const GVArray **varray_ptr); + + /** + * \param field: Field to add to the evaluator. + * \param varray_ptr: Once #evaluate is called, the resulting virtual array will be will be + * assigned to the given position. + * \return Index of the field in the evaluator which can be used in the #get_evaluated methods. + */ + template<typename T> int add(Field<T> field, const VArray<T> **varray_ptr) + { + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(nullptr); + output_pointer_infos_.append( + OutputPointerInfo{varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &scope) { + *(const VArray<T> **)dst = &*scope.construct<GVArray_Typed<T>>(varray); + }}); + return field_index; + } + + /** + * \return Index of the field in the evaluator which can be used in the #get_evaluated methods. + */ + int add(GField field); + + /** + * Evaluate all fields on the evaluator. This can only be called once. + */ + void evaluate(); + + const GVArray &get_evaluated(const int field_index) const + { + BLI_assert(is_evaluated_); + return *evaluated_varrays_[field_index]; + } + + template<typename T> const VArray<T> &get_evaluated(const int field_index) + { + const GVArray &varray = this->get_evaluated(field_index); + GVArray_Typed<T> &typed_varray = scope_.construct<GVArray_Typed<T>>(varray); + return *typed_varray; + } + + /** + * Retrieve the output of an evaluated boolean field and convert it to a mask, which can be used + * to avoid calculations for unnecessary elements later on. The evaluator will own the indices in + * some cases, so it must live at least as long as the returned mask. + */ + IndexMask get_evaluated_as_mask(const int field_index); +}; + +Vector<const GVArray *> evaluate_fields(ResourceScope &scope, + Span<GFieldRef> fields_to_evaluate, + IndexMask mask, + const FieldContext &context, + Span<GVMutableArray *> dst_varrays = {}); + +/* -------------------------------------------------------------------- + * Utility functions for simple field creation and evaluation. + */ + +void evaluate_constant_field(const GField &field, void *r_value); + +template<typename T> T evaluate_constant_field(const Field<T> &field) +{ + T value; + value.~T(); + evaluate_constant_field(field, &value); + return value; +} + +template<typename T> Field<T> make_constant_field(T value) +{ + auto constant_fn = std::make_unique<fn::CustomMF_Constant<T>>(std::forward<T>(value)); + auto operation = std::make_shared<FieldOperation>(std::move(constant_fn)); + return Field<T>{GField{std::move(operation), 0}}; +} + +GField make_field_constant_if_possible(GField field); + +} // namespace blender::fn diff --git a/source/blender/functions/FN_field_cpp_type.hh b/source/blender/functions/FN_field_cpp_type.hh new file mode 100644 index 00000000000..5e6f1b5a585 --- /dev/null +++ b/source/blender/functions/FN_field_cpp_type.hh @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_cpp_type_make.hh" +#include "FN_field.hh" + +namespace blender::fn { + +template<typename T> struct FieldCPPTypeParam { +}; + +class FieldCPPType : public CPPType { + private: + const CPPType &field_type_; + + public: + template<typename T> + FieldCPPType(FieldCPPTypeParam<Field<T>> /* unused */, StringRef debug_name) + : CPPType(CPPTypeParam<Field<T>, CPPTypeFlags::None>(), debug_name), + field_type_(CPPType::get<T>()) + { + } + + const CPPType &field_type() const + { + return field_type_; + } + + /* Ensure that #GField and #Field<T> have the same layout, to enable casting between the two. */ + static_assert(sizeof(Field<int>) == sizeof(GField)); + static_assert(sizeof(Field<int>) == sizeof(Field<std::string>)); + + const GField &get_gfield(const void *field) const + { + return *(const GField *)field; + } + + void construct_from_gfield(void *r_value, const GField &gfield) const + { + new (r_value) GField(gfield); + } +}; + +} // namespace blender::fn + +#define MAKE_FIELD_CPP_TYPE(DEBUG_NAME, FIELD_TYPE) \ + template<> \ + const blender::fn::CPPType &blender::fn::CPPType::get_impl<blender::fn::Field<FIELD_TYPE>>() \ + { \ + static blender::fn::FieldCPPType cpp_type{ \ + blender::fn::FieldCPPTypeParam<blender::fn::Field<FIELD_TYPE>>(), STRINGIFY(DEBUG_NAME)}; \ + return cpp_type; \ + } diff --git a/source/blender/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh index f429243e2de..703118ba23e 100644 --- a/source/blender/functions/FN_generic_virtual_array.hh +++ b/source/blender/functions/FN_generic_virtual_array.hh @@ -911,4 +911,50 @@ template<typename T> class GVMutableArray_Typed { } }; +class GVArray_For_SlicedGVArray : public GVArray { + protected: + const GVArray &varray_; + int64_t offset_; + + public: + GVArray_For_SlicedGVArray(const GVArray &varray, const IndexRange slice) + : GVArray(varray.type(), slice.size()), varray_(varray), offset_(slice.start()) + { + BLI_assert(slice.one_after_last() <= varray.size()); + } + + /* TODO: Add #materialize method. */ + void get_impl(const int64_t index, void *r_value) const override; + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override; +}; + +/** + * Utility class to create the "best" sliced virtual array. + */ +class GVArray_Slice { + private: + const GVArray *varray_; + /* Of these optional virtual arrays, at most one is constructed at any time. */ + std::optional<GVArray_For_GSpan> varray_span_; + std::optional<GVArray_For_SlicedGVArray> varray_any_; + + public: + GVArray_Slice(const GVArray &varray, const IndexRange slice); + + const GVArray &operator*() + { + return *varray_; + } + + const GVArray *operator->() + { + return varray_; + } + + operator const GVArray &() + { + return *varray_; + } +}; + } // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh index f6c4addfb52..98788025558 100644 --- a/source/blender/functions/FN_multi_function.hh +++ b/source/blender/functions/FN_multi_function.hh @@ -121,8 +121,13 @@ class MultiFunction { } }; -inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size) - : MFParamsBuilder(fn.signature(), min_array_size) +inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, int64_t mask_size) + : MFParamsBuilder(fn.signature(), IndexMask(mask_size)) +{ +} + +inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, const IndexMask *mask) + : MFParamsBuilder(fn.signature(), *mask) { } diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh index 7a526bb640b..0ce05cbca30 100644 --- a/source/blender/functions/FN_multi_function_builder.hh +++ b/source/blender/functions/FN_multi_function_builder.hh @@ -326,18 +326,21 @@ template<typename From, typename To> class CustomMF_Convert : public MultiFuncti /** * A multi-function that outputs the same value every time. The value is not owned by an instance - * of this function. The caller is responsible for destructing and freeing the value. + * of this function. If #make_value_copy is false, the caller is responsible for destructing and + * freeing the value. */ class CustomMF_GenericConstant : public MultiFunction { private: const CPPType &type_; const void *value_; MFSignature signature_; + bool owns_value_; template<typename T> friend class CustomMF_Constant; public: - CustomMF_GenericConstant(const CPPType &type, const void *value); + CustomMF_GenericConstant(const CPPType &type, const void *value, bool make_value_copy); + ~CustomMF_GenericConstant(); void call(IndexMask mask, MFParams params, MFContext context) const override; uint64_t hash() const override; bool equals(const MultiFunction &other) const override; @@ -417,4 +420,13 @@ class CustomMF_DefaultOutput : public MultiFunction { void call(IndexMask mask, MFParams params, MFContext context) const override; }; +class CustomMF_GenericCopy : public MultiFunction { + private: + MFSignature signature_; + + public: + CustomMF_GenericCopy(StringRef name, MFDataType data_type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + } // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_parallel.hh b/source/blender/functions/FN_multi_function_parallel.hh new file mode 100644 index 00000000000..84c57efd434 --- /dev/null +++ b/source/blender/functions/FN_multi_function_parallel.hh @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function.hh" + +namespace blender::fn { + +class ParallelMultiFunction : public MultiFunction { + private: + const MultiFunction &fn_; + const int64_t grain_size_; + bool threading_supported_; + + public: + ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size); + + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh index a480287d578..d187985de9d 100644 --- a/source/blender/functions/FN_multi_function_params.hh +++ b/source/blender/functions/FN_multi_function_params.hh @@ -38,6 +38,7 @@ class MFParamsBuilder { private: ResourceScope scope_; const MFSignature *signature_; + IndexMask mask_; int64_t min_array_size_; Vector<const GVArray *> virtual_arrays_; Vector<GMutableSpan> mutable_spans_; @@ -46,35 +47,39 @@ class MFParamsBuilder { friend class MFParams; - public: - MFParamsBuilder(const MFSignature &signature, int64_t min_array_size) - : signature_(&signature), min_array_size_(min_array_size) + MFParamsBuilder(const MFSignature &signature, const IndexMask mask) + : signature_(&signature), mask_(mask), min_array_size_(mask.min_array_size()) { } - MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size); + public: + MFParamsBuilder(const class MultiFunction &fn, int64_t size); + /** + * The indices referenced by the #mask has to live longer than the params builder. This is + * because the it might have to destruct elements for all masked indices in the end. + */ + MFParamsBuilder(const class MultiFunction &fn, const IndexMask *mask); template<typename T> void add_readonly_single_input_value(T value, StringRef expected_name = "") { - T *value_ptr = &scope_.add_value<T>(std::move(value), __func__); + T *value_ptr = &scope_.add_value<T>(std::move(value)); this->add_readonly_single_input(value_ptr, expected_name); } template<typename T> void add_readonly_single_input(const T *value, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>( - __func__, CPPType::get<T>(), min_array_size_, value), - expected_name); + this->add_readonly_single_input( + scope_.construct<GVArray_For_SingleValueRef>(CPPType::get<T>(), min_array_size_, value), + expected_name); } void add_readonly_single_input(const GSpan span, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(__func__, span), - expected_name); + this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(span), expected_name); } void add_readonly_single_input(GPointer value, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>( - __func__, *value.type(), min_array_size_, value.get()), - expected_name); + this->add_readonly_single_input( + scope_.construct<GVArray_For_SingleValueRef>(*value.type(), min_array_size_, value.get()), + expected_name); } void add_readonly_single_input(const GVArray &ref, StringRef expected_name = "") { @@ -85,13 +90,13 @@ class MFParamsBuilder { void add_readonly_vector_input(const GVectorArray &vector_array, StringRef expected_name = "") { - this->add_readonly_vector_input( - scope_.construct<GVVectorArray_For_GVectorArray>(__func__, vector_array), expected_name); + this->add_readonly_vector_input(scope_.construct<GVVectorArray_For_GVectorArray>(vector_array), + expected_name); } void add_readonly_vector_input(const GSpan single_vector, StringRef expected_name = "") { this->add_readonly_vector_input( - scope_.construct<GVVectorArray_For_SingleGSpan>(__func__, single_vector, min_array_size_), + scope_.construct<GVVectorArray_For_SingleGSpan>(single_vector, min_array_size_), expected_name); } void add_readonly_vector_input(const GVVectorArray &ref, StringRef expected_name = "") @@ -112,6 +117,17 @@ class MFParamsBuilder { BLI_assert(ref.size() >= min_array_size_); mutable_spans_.append(ref); } + void add_ignored_single_output(StringRef expected_name = "") + { + this->assert_current_param_name(expected_name); + const int param_index = this->current_param_index(); + const MFParamType ¶m_type = signature_->param_types[param_index]; + BLI_assert(param_type.category() == MFParamType::SingleOutput); + const CPPType &type = param_type.data_type().single_type(); + /* An empty span indicates that this is ignored. */ + const GMutableSpan dummy_span{type}; + mutable_spans_.append(dummy_span); + } void add_vector_output(GVectorArray &vector_array, StringRef expected_name = "") { @@ -176,6 +192,19 @@ class MFParamsBuilder { #endif } + void assert_current_param_name(StringRef expected_name) + { + UNUSED_VARS_NDEBUG(expected_name); +#ifdef DEBUG + if (expected_name.is_empty()) { + return; + } + const int param_index = this->current_param_index(); + StringRef actual_name = signature_->param_names[param_index]; + BLI_assert(actual_name == expected_name); +#endif + } + int current_param_index() const { return virtual_arrays_.size() + mutable_spans_.size() + virtual_vector_arrays_.size() + @@ -195,7 +224,7 @@ class MFParams { template<typename T> const VArray<T> &readonly_single_input(int param_index, StringRef name = "") { const GVArray &array = this->readonly_single_input(param_index, name); - return builder_->scope_.construct<VArray_For_GVArray<T>>(__func__, array); + return builder_->scope_.construct<GVArray_Typed<T>>(array); } const GVArray &readonly_single_input(int param_index, StringRef name = "") { @@ -204,6 +233,19 @@ class MFParams { return *builder_->virtual_arrays_[data_index]; } + /** + * \return True when the caller provided a buffer for this output parameter. This allows the + * called multi-function to skip some computation. It is still valid to call + * #uninitialized_single_output when this returns false. In this case a new temporary buffer is + * allocated. + */ + bool single_output_is_required(int param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleOutput); + int data_index = builder_->signature_->data_index(param_index); + return !builder_->mutable_spans_[data_index].is_empty(); + } + template<typename T> MutableSpan<T> uninitialized_single_output(int param_index, StringRef name = "") { @@ -213,6 +255,36 @@ class MFParams { { this->assert_correct_param(param_index, name, MFParamType::SingleOutput); int data_index = builder_->signature_->data_index(param_index); + GMutableSpan span = builder_->mutable_spans_[data_index]; + if (span.is_empty()) { + /* The output is ignored by the caller, but the multi-function does not handle this case. So + * create a temporary buffer that the multi-function can write to. */ + const CPPType &type = span.type(); + void *buffer = builder_->scope_.linear_allocator().allocate( + builder_->min_array_size_ * type.size(), type.alignment()); + if (!type.is_trivially_destructible()) { + /* Make sure the temporary elements will be destructed in the end. */ + builder_->scope_.add_destruct_call( + [&type, buffer, mask = builder_->mask_]() { type.destruct_indices(buffer, mask); }); + } + span = GMutableSpan{type, buffer, builder_->min_array_size_}; + } + return span; + } + + /** + * Same as #uninitialized_single_output, but returns an empty span when the output is not + * required. + */ + template<typename T> + MutableSpan<T> uninitialized_single_output_if_required(int param_index, StringRef name = "") + { + return this->uninitialized_single_output_if_required(param_index, name).typed<T>(); + } + GMutableSpan uninitialized_single_output_if_required(int param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleOutput); + int data_index = builder_->signature_->data_index(param_index); return builder_->mutable_spans_[data_index]; } @@ -220,7 +292,7 @@ class MFParams { const VVectorArray<T> &readonly_vector_input(int param_index, StringRef name = "") { const GVVectorArray &vector_array = this->readonly_vector_input(param_index, name); - return builder_->scope_.construct<VVectorArray_For_GVVectorArray<T>>(__func__, vector_array); + return builder_->scope_.construct<VVectorArray_For_GVVectorArray<T>>(vector_array); } const GVVectorArray &readonly_vector_input(int param_index, StringRef name = "") { diff --git a/source/blender/functions/FN_multi_function_procedure.hh b/source/blender/functions/FN_multi_function_procedure.hh new file mode 100644 index 00000000000..4c06ce98ee3 --- /dev/null +++ b/source/blender/functions/FN_multi_function_procedure.hh @@ -0,0 +1,539 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function.hh" + +namespace blender::fn { + +class MFVariable; +class MFInstruction; +class MFCallInstruction; +class MFBranchInstruction; +class MFDestructInstruction; +class MFDummyInstruction; +class MFReturnInstruction; +class MFProcedure; + +/** Every instruction has exactly one of these types. */ +enum class MFInstructionType { + Call, + Branch, + Destruct, + Dummy, + Return, +}; + +/** + * An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction + * can be inserted. + */ +class MFInstructionCursor { + public: + enum Type { + None, + Entry, + Call, + Destruct, + Branch, + Dummy, + }; + + private: + Type type_ = None; + MFInstruction *instruction_ = nullptr; + /* Only used when it is a branch instruction. */ + bool branch_output_ = false; + + public: + MFInstructionCursor() = default; + MFInstructionCursor(MFCallInstruction &instruction); + MFInstructionCursor(MFDestructInstruction &instruction); + MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output); + MFInstructionCursor(MFDummyInstruction &instruction); + + static MFInstructionCursor ForEntry(); + + MFInstruction *next(MFProcedure &procedure) const; + void set_next(MFProcedure &procedure, MFInstruction *new_instruction) const; + + MFInstruction *instruction() const; + + Type type() const; + + friend bool operator==(const MFInstructionCursor &a, const MFInstructionCursor &b) + { + return a.type_ == b.type_ && a.instruction_ == b.instruction_ && + a.branch_output_ == b.branch_output_; + } + + friend bool operator!=(const MFInstructionCursor &a, const MFInstructionCursor &b) + { + return !(a == b); + } +}; + +/** + * A variable is similar to a virtual register in other libraries. During evaluation, every is + * either uninitialized or contains a value for every index (remember, a multi-function procedure + * is always evaluated for many indices at the same time). + */ +class MFVariable : NonCopyable, NonMovable { + private: + MFDataType data_type_; + Vector<MFInstruction *> users_; + std::string name_; + int id_; + + friend MFProcedure; + friend MFCallInstruction; + friend MFBranchInstruction; + friend MFDestructInstruction; + + public: + MFDataType data_type() const; + Span<MFInstruction *> users(); + + StringRefNull name() const; + void set_name(std::string name); + + int id() const; +}; + +/** Base class for all instruction types. */ +class MFInstruction : NonCopyable, NonMovable { + protected: + MFInstructionType type_; + Vector<MFInstructionCursor> prev_; + + friend MFProcedure; + friend MFCallInstruction; + friend MFBranchInstruction; + friend MFDestructInstruction; + friend MFDummyInstruction; + friend MFReturnInstruction; + + public: + MFInstructionType type() const; + + /** + * Other instructions that come before this instruction. There can be multiple previous + * instructions when branching is used in the procedure. + */ + Span<MFInstructionCursor> prev() const; +}; + +/** + * References a multi-function that is evaluated when the instruction is executed. It also + * references the variables whose data will be passed into the multi-function. + */ +class MFCallInstruction : public MFInstruction { + private: + const MultiFunction *fn_ = nullptr; + MFInstruction *next_ = nullptr; + MutableSpan<MFVariable *> params_; + + friend MFProcedure; + + public: + const MultiFunction &fn() const; + + MFInstruction *next(); + const MFInstruction *next() const; + void set_next(MFInstruction *instruction); + + void set_param_variable(int param_index, MFVariable *variable); + void set_params(Span<MFVariable *> variables); + + Span<MFVariable *> params(); + Span<const MFVariable *> params() const; +}; + +/** + * What makes a branch instruction special is that it has two successor instructions. One that will + * be used when a condition variable was true, and one otherwise. + */ +class MFBranchInstruction : public MFInstruction { + private: + MFVariable *condition_ = nullptr; + MFInstruction *branch_true_ = nullptr; + MFInstruction *branch_false_ = nullptr; + + friend MFProcedure; + + public: + MFVariable *condition(); + const MFVariable *condition() const; + void set_condition(MFVariable *variable); + + MFInstruction *branch_true(); + const MFInstruction *branch_true() const; + void set_branch_true(MFInstruction *instruction); + + MFInstruction *branch_false(); + const MFInstruction *branch_false() const; + void set_branch_false(MFInstruction *instruction); +}; + +/** + * A destruct instruction destructs a single variable. So the variable value will be uninitialized + * after this instruction. All variables that are not output variables of the procedure, have to be + * destructed before the procedure ends. Destructing early is generally a good thing, because it + * might help with memory buffer reuse, which decreases memory-usage and increases performance. + */ +class MFDestructInstruction : public MFInstruction { + private: + MFVariable *variable_ = nullptr; + MFInstruction *next_ = nullptr; + + friend MFProcedure; + + public: + MFVariable *variable(); + const MFVariable *variable() const; + void set_variable(MFVariable *variable); + + MFInstruction *next(); + const MFInstruction *next() const; + void set_next(MFInstruction *instruction); +}; + +/** + * This instruction does nothing, it just exists to building a procedure simpler in some cases. + */ +class MFDummyInstruction : public MFInstruction { + private: + MFInstruction *next_ = nullptr; + + friend MFProcedure; + + public: + MFInstruction *next(); + const MFInstruction *next() const; + void set_next(MFInstruction *instruction); +}; + +/** + * This instruction ends the procedure. + */ +class MFReturnInstruction : public MFInstruction { +}; + +/** + * Inputs and outputs of the entire procedure network. + */ +struct MFParameter { + MFParamType::InterfaceType type; + MFVariable *variable; +}; + +struct ConstMFParameter { + MFParamType::InterfaceType type; + const MFVariable *variable; +}; + +/** + * A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of + * variables and instructions that operate on those variables. Branching and looping within the + * procedure is supported as well. + * + * Typically, a #MFProcedure should be constructed using a #MFProcedureBuilder, which has many more + * utility methods for common use cases. + */ +class MFProcedure : NonCopyable, NonMovable { + private: + LinearAllocator<> allocator_; + Vector<MFCallInstruction *> call_instructions_; + Vector<MFBranchInstruction *> branch_instructions_; + Vector<MFDestructInstruction *> destruct_instructions_; + Vector<MFDummyInstruction *> dummy_instructions_; + Vector<MFReturnInstruction *> return_instructions_; + Vector<MFVariable *> variables_; + Vector<MFParameter> params_; + MFInstruction *entry_ = nullptr; + + friend class MFProcedureDotExport; + + public: + MFProcedure() = default; + ~MFProcedure(); + + MFVariable &new_variable(MFDataType data_type, std::string name = ""); + MFCallInstruction &new_call_instruction(const MultiFunction &fn); + MFBranchInstruction &new_branch_instruction(); + MFDestructInstruction &new_destruct_instruction(); + MFDummyInstruction &new_dummy_instruction(); + MFReturnInstruction &new_return_instruction(); + + void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable); + + Span<ConstMFParameter> params() const; + + MFInstruction *entry(); + const MFInstruction *entry() const; + void set_entry(MFInstruction &entry); + + Span<MFVariable *> variables(); + Span<const MFVariable *> variables() const; + + std::string to_dot() const; + + bool validate() const; + + private: + bool validate_all_instruction_pointers_set() const; + bool validate_all_params_provided() const; + bool validate_same_variables_in_one_call() const; + bool validate_parameters() const; + bool validate_initialization() const; + + struct InitState { + bool can_be_initialized = false; + bool can_be_uninitialized = false; + }; + + InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction, + const MFVariable &variable) const; +}; + +namespace multi_function_procedure_types { +using MFVariable = fn::MFVariable; +using MFInstruction = fn::MFInstruction; +using MFCallInstruction = fn::MFCallInstruction; +using MFBranchInstruction = fn::MFBranchInstruction; +using MFDestructInstruction = fn::MFDestructInstruction; +using MFProcedure = fn::MFProcedure; +} // namespace multi_function_procedure_types + +/* -------------------------------------------------------------------- + * MFInstructionCursor inline methods. + */ + +inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction) + : type_(Call), instruction_(&instruction) +{ +} + +inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction) + : type_(Destruct), instruction_(&instruction) +{ +} + +inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction, + bool branch_output) + : type_(Branch), instruction_(&instruction), branch_output_(branch_output) +{ +} + +inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction) + : type_(Dummy), instruction_(&instruction) +{ +} + +inline MFInstructionCursor MFInstructionCursor::ForEntry() +{ + MFInstructionCursor cursor; + cursor.type_ = Type::Entry; + return cursor; +} + +inline MFInstruction *MFInstructionCursor::instruction() const +{ + /* This isn't really const correct unfortunately, because to make it correct we'll need a const + * version of #MFInstructionCursor. */ + return instruction_; +} + +inline MFInstructionCursor::Type MFInstructionCursor::type() const +{ + return type_; +} + +/* -------------------------------------------------------------------- + * MFVariable inline methods. + */ + +inline MFDataType MFVariable::data_type() const +{ + return data_type_; +} + +inline Span<MFInstruction *> MFVariable::users() +{ + return users_; +} + +inline StringRefNull MFVariable::name() const +{ + return name_; +} + +inline int MFVariable::id() const +{ + return id_; +} + +/* -------------------------------------------------------------------- + * MFInstruction inline methods. + */ + +inline MFInstructionType MFInstruction::type() const +{ + return type_; +} + +inline Span<MFInstructionCursor> MFInstruction::prev() const +{ + return prev_; +} + +/* -------------------------------------------------------------------- + * MFCallInstruction inline methods. + */ + +inline const MultiFunction &MFCallInstruction::fn() const +{ + return *fn_; +} + +inline MFInstruction *MFCallInstruction::next() +{ + return next_; +} + +inline const MFInstruction *MFCallInstruction::next() const +{ + return next_; +} + +inline Span<MFVariable *> MFCallInstruction::params() +{ + return params_; +} + +inline Span<const MFVariable *> MFCallInstruction::params() const +{ + return params_; +} + +/* -------------------------------------------------------------------- + * MFBranchInstruction inline methods. + */ + +inline MFVariable *MFBranchInstruction::condition() +{ + return condition_; +} + +inline const MFVariable *MFBranchInstruction::condition() const +{ + return condition_; +} + +inline MFInstruction *MFBranchInstruction::branch_true() +{ + return branch_true_; +} + +inline const MFInstruction *MFBranchInstruction::branch_true() const +{ + return branch_true_; +} + +inline MFInstruction *MFBranchInstruction::branch_false() +{ + return branch_false_; +} + +inline const MFInstruction *MFBranchInstruction::branch_false() const +{ + return branch_false_; +} + +/* -------------------------------------------------------------------- + * MFDestructInstruction inline methods. + */ + +inline MFVariable *MFDestructInstruction::variable() +{ + return variable_; +} + +inline const MFVariable *MFDestructInstruction::variable() const +{ + return variable_; +} + +inline MFInstruction *MFDestructInstruction::next() +{ + return next_; +} + +inline const MFInstruction *MFDestructInstruction::next() const +{ + return next_; +} + +/* -------------------------------------------------------------------- + * MFDummyInstruction inline methods. + */ + +inline MFInstruction *MFDummyInstruction::next() +{ + return next_; +} + +inline const MFInstruction *MFDummyInstruction::next() const +{ + return next_; +} + +/* -------------------------------------------------------------------- + * MFProcedure inline methods. + */ + +inline Span<ConstMFParameter> MFProcedure::params() const +{ + static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter)); + return params_.as_span().cast<ConstMFParameter>(); +} + +inline MFInstruction *MFProcedure::entry() +{ + return entry_; +} + +inline const MFInstruction *MFProcedure::entry() const +{ + return entry_; +} + +inline Span<MFVariable *> MFProcedure::variables() +{ + return variables_; +} + +inline Span<const MFVariable *> MFProcedure::variables() const +{ + return variables_; +} + +} // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_procedure_builder.hh b/source/blender/functions/FN_multi_function_procedure_builder.hh new file mode 100644 index 00000000000..e416f7e500d --- /dev/null +++ b/source/blender/functions/FN_multi_function_procedure_builder.hh @@ -0,0 +1,203 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function_procedure.hh" + +namespace blender::fn { + +/** + * Utility class to build a #MFProcedure. + */ +class MFProcedureBuilder { + private: + /** Procedure that is being build. */ + MFProcedure *procedure_ = nullptr; + /** Cursors where the next instruction should be inserted. */ + Vector<MFInstructionCursor> cursors_; + + public: + struct Branch; + struct Loop; + + MFProcedureBuilder(MFProcedure &procedure, + MFInstructionCursor initial_cursor = MFInstructionCursor::ForEntry()); + + MFProcedureBuilder(Span<MFProcedureBuilder *> builders); + + MFProcedureBuilder(Branch &branch); + + void set_cursor(const MFInstructionCursor &cursor); + void set_cursor(Span<MFInstructionCursor> cursors); + void set_cursor(Span<MFProcedureBuilder *> builders); + void set_cursor_after_branch(Branch &branch); + void set_cursor_after_loop(Loop &loop); + + void add_destruct(MFVariable &variable); + void add_destruct(Span<MFVariable *> variables); + + MFReturnInstruction &add_return(); + + Branch add_branch(MFVariable &condition); + + Loop add_loop(); + void add_loop_continue(Loop &loop); + void add_loop_break(Loop &loop); + + MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn); + MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn, + Span<MFVariable *> param_variables); + + Vector<MFVariable *> add_call(const MultiFunction &fn, + Span<MFVariable *> input_and_mutable_variables = {}); + + template<int OutputN> + std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn, + Span<MFVariable *> input_and_mutable_variables = {}); + + void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable); + MFVariable &add_parameter(MFParamType param_type, std::string name = ""); + + MFVariable &add_input_parameter(MFDataType data_type, std::string name = ""); + template<typename T> MFVariable &add_single_input_parameter(std::string name = ""); + template<typename T> MFVariable &add_single_mutable_parameter(std::string name = ""); + + void add_output_parameter(MFVariable &variable); + + private: + void link_to_cursors(MFInstruction *instruction); +}; + +struct MFProcedureBuilder::Branch { + MFProcedureBuilder branch_true; + MFProcedureBuilder branch_false; +}; + +struct MFProcedureBuilder::Loop { + MFInstruction *begin = nullptr; + MFDummyInstruction *end = nullptr; +}; + +/* -------------------------------------------------------------------- + * MFProcedureBuilder inline methods. + */ + +inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch) + : MFProcedureBuilder(*branch.branch_true.procedure_) +{ + this->set_cursor_after_branch(branch); +} + +inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure, + MFInstructionCursor initial_cursor) + : procedure_(&procedure), cursors_({initial_cursor}) +{ +} + +inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders) + : MFProcedureBuilder(*builders[0]->procedure_) +{ + this->set_cursor(builders); +} + +inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor) +{ + cursors_ = {cursor}; +} + +inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors) +{ + cursors_ = cursors; +} + +inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch) +{ + this->set_cursor({&branch.branch_false, &branch.branch_true}); +} + +inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop) +{ + this->set_cursor(MFInstructionCursor{*loop.end}); +} + +inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders) +{ + cursors_.clear(); + for (MFProcedureBuilder *builder : builders) { + cursors_.extend(builder->cursors_); + } +} + +template<int OutputN> +inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call( + const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables) +{ + Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables); + BLI_assert(output_variables.size() == OutputN); + + std::array<MFVariable *, OutputN> output_array; + initialized_copy_n(output_variables.data(), OutputN, output_array.data()); + return output_array; +} + +inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type, + MFVariable &variable) +{ + procedure_->add_parameter(interface_type, variable); +} + +inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name) +{ + MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name)); + this->add_parameter(param_type.interface_type(), variable); + return variable; +} + +inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name) +{ + return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name)); +} + +template<typename T> +inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name) +{ + return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name)); +} + +template<typename T> +inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name) +{ + return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name)); +} + +inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable) +{ + this->add_parameter(MFParamType::Output, variable); +} + +inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction) +{ + for (MFInstructionCursor &cursor : cursors_) { + cursor.set_next(*procedure_, instruction); + } +} + +} // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_procedure_executor.hh b/source/blender/functions/FN_multi_function_procedure_executor.hh new file mode 100644 index 00000000000..9c8b59739b8 --- /dev/null +++ b/source/blender/functions/FN_multi_function_procedure_executor.hh @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function_procedure.hh" + +namespace blender::fn { + +/** A multi-function that executes a procedure internally. */ +class MFProcedureExecutor : public MultiFunction { + private: + MFSignature signature_; + const MFProcedure &procedure_; + + public: + MFProcedureExecutor(std::string name, const MFProcedure &procedure); + + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace blender::fn diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc index 7be34d2a1bf..058fb76af2b 100644 --- a/source/blender/functions/intern/cpp_types.cc +++ b/source/blender/functions/intern/cpp_types.cc @@ -15,6 +15,7 @@ */ #include "FN_cpp_type_make.hh" +#include "FN_field_cpp_type.hh" #include "BLI_color.hh" #include "BLI_float2.hh" @@ -39,4 +40,12 @@ MAKE_CPP_TYPE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType MAKE_CPP_TYPE(string, std::string, CPPTypeFlags::BasicType) +MAKE_FIELD_CPP_TYPE(FloatField, float); +MAKE_FIELD_CPP_TYPE(Float2Field, float2); +MAKE_FIELD_CPP_TYPE(Float3Field, float3); +MAKE_FIELD_CPP_TYPE(ColorGeometry4fField, blender::ColorGeometry4f); +MAKE_FIELD_CPP_TYPE(BoolField, bool); +MAKE_FIELD_CPP_TYPE(Int32Field, int32_t); +MAKE_FIELD_CPP_TYPE(StringField, std::string); + } // namespace blender::fn diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc new file mode 100644 index 00000000000..6a4518ad4a6 --- /dev/null +++ b/source/blender/functions/intern/field.cc @@ -0,0 +1,672 @@ +/* + * 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_map.hh" +#include "BLI_multi_value_map.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_vector_set.hh" + +#include "FN_field.hh" +#include "FN_multi_function_parallel.hh" + +namespace blender::fn { + +/* -------------------------------------------------------------------- + * Field Evaluation. + */ + +struct FieldTreeInfo { + /** + * When fields are built, they only have references to the fields that they depend on. This map + * allows traversal of fields in the opposite direction. So for every field it stores the other + * fields that depend on it directly. + */ + MultiValueMap<GFieldRef, GFieldRef> field_users; + /** + * The same field input may exist in the field tree as as separate nodes due to the way + * the tree is constructed. This set contains every different input only once. + */ + VectorSet<std::reference_wrapper<const FieldInput>> deduplicated_field_inputs; +}; + +/** + * Collects some information from the field tree that is required by later steps. + */ +static FieldTreeInfo preprocess_field_tree(Span<GFieldRef> entry_fields) +{ + FieldTreeInfo field_tree_info; + + Stack<GFieldRef> fields_to_check; + Set<GFieldRef> handled_fields; + + for (GFieldRef field : entry_fields) { + if (handled_fields.add(field)) { + fields_to_check.push(field); + } + } + + while (!fields_to_check.is_empty()) { + GFieldRef field = fields_to_check.pop(); + if (field.node().is_input()) { + const FieldInput &field_input = static_cast<const FieldInput &>(field.node()); + field_tree_info.deduplicated_field_inputs.add(field_input); + continue; + } + BLI_assert(field.node().is_operation()); + const FieldOperation &operation = static_cast<const FieldOperation &>(field.node()); + for (const GFieldRef operation_input : operation.inputs()) { + field_tree_info.field_users.add(operation_input, field); + if (handled_fields.add(operation_input)) { + fields_to_check.push(operation_input); + } + } + } + return field_tree_info; +} + +/** + * Retrieves the data from the context that is passed as input into the field. + */ +static Vector<const GVArray *> get_field_context_inputs( + ResourceScope &scope, + const IndexMask mask, + const FieldContext &context, + const Span<std::reference_wrapper<const FieldInput>> field_inputs) +{ + Vector<const GVArray *> field_context_inputs; + for (const FieldInput &field_input : field_inputs) { + const GVArray *varray = context.get_varray_for_input(field_input, mask, scope); + if (varray == nullptr) { + const CPPType &type = field_input.cpp_type(); + varray = &scope.construct<GVArray_For_SingleValueRef>( + type, mask.min_array_size(), type.default_value()); + } + field_context_inputs.append(varray); + } + return field_context_inputs; +} + +/** + * \return A set that contains all fields from the field tree that depend on an input that varies + * for different indices. + */ +static Set<GFieldRef> find_varying_fields(const FieldTreeInfo &field_tree_info, + Span<const GVArray *> field_context_inputs) +{ + Set<GFieldRef> found_fields; + Stack<GFieldRef> fields_to_check; + + /* The varying fields are the ones that depend on inputs that are not constant. Therefore we + * start the tree search at the non-constant input fields and traverse through all fields that + * depend on them. */ + for (const int i : field_context_inputs.index_range()) { + const GVArray *varray = field_context_inputs[i]; + if (varray->is_single()) { + continue; + } + const FieldInput &field_input = field_tree_info.deduplicated_field_inputs[i]; + const GFieldRef field_input_field{field_input, 0}; + const Span<GFieldRef> users = field_tree_info.field_users.lookup(field_input_field); + for (const GFieldRef &field : users) { + if (found_fields.add(field)) { + fields_to_check.push(field); + } + } + } + while (!fields_to_check.is_empty()) { + GFieldRef field = fields_to_check.pop(); + const Span<GFieldRef> users = field_tree_info.field_users.lookup(field); + for (GFieldRef field : users) { + if (found_fields.add(field)) { + fields_to_check.push(field); + } + } + } + return found_fields; +} + +/** + * Builds the #procedure so that it computes the the fields. + */ +static void build_multi_function_procedure_for_fields(MFProcedure &procedure, + ResourceScope &scope, + const FieldTreeInfo &field_tree_info, + Span<GFieldRef> output_fields) +{ + MFProcedureBuilder builder{procedure}; + /* Every input, intermediate and output field corresponds to a variable in the procedure. */ + Map<GFieldRef, MFVariable *> variable_by_field; + + /* Start by adding the field inputs as parameters to the procedure. */ + for (const FieldInput &field_input : field_tree_info.deduplicated_field_inputs) { + MFVariable &variable = builder.add_input_parameter( + MFDataType::ForSingle(field_input.cpp_type()), field_input.debug_name()); + variable_by_field.add_new({field_input, 0}, &variable); + } + + /* Utility struct that is used to do proper depth first search traversal of the tree below. */ + struct FieldWithIndex { + GFieldRef field; + int current_input_index = 0; + }; + + for (GFieldRef field : output_fields) { + /* We start a new stack for each output field to make sure that a field pushed later to the + * stack does never depend on a field that was pushed before. */ + Stack<FieldWithIndex> fields_to_check; + fields_to_check.push({field, 0}); + while (!fields_to_check.is_empty()) { + FieldWithIndex &field_with_index = fields_to_check.peek(); + const GFieldRef &field = field_with_index.field; + if (variable_by_field.contains(field)) { + /* The field has been handled already. */ + fields_to_check.pop(); + continue; + } + /* Field inputs should already be handled above. */ + BLI_assert(field.node().is_operation()); + + const FieldOperation &operation = static_cast<const FieldOperation &>(field.node()); + const Span<GField> operation_inputs = operation.inputs(); + + if (field_with_index.current_input_index < operation_inputs.size()) { + /* Not all inputs are handled yet. Push the next input field to the stack and increment the + * input index. */ + fields_to_check.push({operation_inputs[field_with_index.current_input_index]}); + field_with_index.current_input_index++; + } + else { + /* All inputs variables are ready, now gather all variables that are used by the function + * and call it. */ + const MultiFunction &multi_function = operation.multi_function(); + Vector<MFVariable *> variables(multi_function.param_amount()); + + int param_input_index = 0; + int param_output_index = 0; + for (const int param_index : multi_function.param_indices()) { + const MFParamType param_type = multi_function.param_type(param_index); + const MFParamType::InterfaceType interface_type = param_type.interface_type(); + if (interface_type == MFParamType::Input) { + const GField &input_field = operation_inputs[param_input_index]; + variables[param_index] = variable_by_field.lookup(input_field); + param_input_index++; + } + else if (interface_type == MFParamType::Output) { + const GFieldRef output_field{operation, param_output_index}; + const bool output_is_ignored = + field_tree_info.field_users.lookup(output_field).is_empty() && + !output_fields.contains(output_field); + if (output_is_ignored) { + /* Ignored outputs don't need a variable. */ + variables[param_index] = nullptr; + } + else { + /* Create a new variable for used outputs. */ + MFVariable &new_variable = procedure.new_variable(param_type.data_type()); + variables[param_index] = &new_variable; + variable_by_field.add_new(output_field, &new_variable); + } + param_output_index++; + } + else { + BLI_assert_unreachable(); + } + } + builder.add_call_with_all_variables(multi_function, variables); + } + } + } + + /* Add output parameters to the procedure. */ + Set<MFVariable *> already_output_variables; + for (const GFieldRef &field : output_fields) { + MFVariable *variable = variable_by_field.lookup(field); + if (!already_output_variables.add(variable)) { + /* One variable can be output at most once. To output the same value twice, we have to make + * a copy first. */ + const MultiFunction ©_fn = scope.construct<CustomMF_GenericCopy>("copy", + variable->data_type()); + variable = builder.add_call<1>(copy_fn, {variable})[0]; + } + builder.add_output_parameter(*variable); + } + + /* Remove the variables that should not be destructed from the map. */ + for (const GFieldRef &field : output_fields) { + variable_by_field.remove(field); + } + /* Add destructor calls for the remaining variables. */ + for (MFVariable *variable : variable_by_field.values()) { + builder.add_destruct(*variable); + } + + builder.add_return(); + + // std::cout << procedure.to_dot() << "\n"; + BLI_assert(procedure.validate()); +} + +/** + * 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. + * + * \param scope: The resource scope that owns data that makes up the output virtual arrays. Make + * sure the scope is not destructed when the output virtual arrays are still used. + * \param fields_to_evaluate: The fields that should be evaluated together. + * \param mask: Determines which indices are computed. The mask may be referenced by the returned + * virtual arrays. So the underlying indices (if applicable) should live longer then #scope. + * \param context: The context that the field is evaluated in. Used to retrieve data from each + * #FieldInput in the field network. + * \param dst_varrays: If provided, the computed data will be written into those virtual arrays + * instead of into newly created ones. That allows making the computed data live longer than + * #scope and is more efficient when the data will be written into those virtual arrays + * later anyway. + * \return The computed virtual arrays for each provided field. If #dst_varrays is passed, the + * provided virtual arrays are returned. + */ +Vector<const GVArray *> evaluate_fields(ResourceScope &scope, + Span<GFieldRef> fields_to_evaluate, + IndexMask mask, + const FieldContext &context, + Span<GVMutableArray *> dst_varrays) +{ + Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr); + const int array_size = mask.min_array_size(); + + /* Destination arrays are optional. Create a small utility method to access them. */ + auto get_dst_varray_if_available = [&](int index) -> GVMutableArray * { + if (dst_varrays.is_empty()) { + return nullptr; + } + BLI_assert(dst_varrays[index] == nullptr || dst_varrays[index]->size() >= array_size); + return dst_varrays[index]; + }; + + /* Traverse the field tree and prepare some data that is used in later steps. */ + FieldTreeInfo field_tree_info = preprocess_field_tree(fields_to_evaluate); + + /* Get inputs that will be passed into the field when evaluated. */ + Vector<const GVArray *> field_context_inputs = get_field_context_inputs( + scope, mask, context, field_tree_info.deduplicated_field_inputs); + + /* Finish fields that output an input varray directly. For those we don't have to do any further + * processing. */ + for (const int out_index : fields_to_evaluate.index_range()) { + const GFieldRef &field = fields_to_evaluate[out_index]; + if (!field.node().is_input()) { + continue; + } + const FieldInput &field_input = static_cast<const FieldInput &>(field.node()); + const int field_input_index = field_tree_info.deduplicated_field_inputs.index_of(field_input); + const GVArray *varray = field_context_inputs[field_input_index]; + r_varrays[out_index] = varray; + } + + Set<GFieldRef> varying_fields = find_varying_fields(field_tree_info, field_context_inputs); + + /* Separate fields into two categories. Those that are constant and need to be evaluated only + * once, and those that need to be evaluated for every index. */ + Vector<GFieldRef> varying_fields_to_evaluate; + Vector<int> varying_field_indices; + Vector<GFieldRef> constant_fields_to_evaluate; + Vector<int> constant_field_indices; + for (const int i : fields_to_evaluate.index_range()) { + if (r_varrays[i] != nullptr) { + /* Already done. */ + continue; + } + GFieldRef field = fields_to_evaluate[i]; + if (varying_fields.contains(field)) { + varying_fields_to_evaluate.append(field); + varying_field_indices.append(i); + } + else { + constant_fields_to_evaluate.append(field); + constant_field_indices.append(i); + } + } + + /* Evaluate varying fields if necessary. */ + if (!varying_fields_to_evaluate.is_empty()) { + /* Build the procedure for those fields. */ + MFProcedure procedure; + build_multi_function_procedure_for_fields( + procedure, scope, field_tree_info, varying_fields_to_evaluate); + MFProcedureExecutor procedure_executor{"Procedure", procedure}; + /* Add multi threading capabilities to the field evaluation. */ + const int grain_size = 10000; + fn::ParallelMultiFunction parallel_procedure_executor{procedure_executor, grain_size}; + /* Utility variable to make easy to switch the executor. */ + const MultiFunction &executor_fn = parallel_procedure_executor; + + MFParamsBuilder mf_params{executor_fn, &mask}; + MFContextBuilder mf_context; + + /* Provide inputs to the procedure executor. */ + for (const GVArray *varray : field_context_inputs) { + mf_params.add_readonly_single_input(*varray); + } + + for (const int i : varying_fields_to_evaluate.index_range()) { + const GFieldRef &field = varying_fields_to_evaluate[i]; + const CPPType &type = field.cpp_type(); + const int out_index = varying_field_indices[i]; + + /* Try to get an existing virtual array that the result should be written into. */ + GVMutableArray *output_varray = get_dst_varray_if_available(out_index); + void *buffer; + if (output_varray == nullptr || !output_varray->is_span()) { + /* 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; + + r_varrays[out_index] = &scope.construct<GVArray_For_GSpan>( + GSpan{type, buffer, array_size}); + } + else { + /* Write the result into the existing span. */ + buffer = output_varray->get_internal_span().data(); + + r_varrays[out_index] = output_varray; + } + + /* Pass output buffer to the procedure executor. */ + const GMutableSpan span{type, buffer, array_size}; + mf_params.add_uninitialized_single_output(span); + } + + executor_fn.call(mask, mf_params, mf_context); + } + + /* Evaluate constant fields if necessary. */ + if (!constant_fields_to_evaluate.is_empty()) { + /* Build the procedure for those fields. */ + MFProcedure procedure; + 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}; + MFContextBuilder mf_context; + + /* Provide inputs to the procedure executor. */ + for (const GVArray *varray : field_context_inputs) { + mf_params.add_readonly_single_input(*varray); + } + + for (const int i : constant_fields_to_evaluate.index_range()) { + const GFieldRef &field = constant_fields_to_evaluate[i]; + const CPPType &type = field.cpp_type(); + /* 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; + + /* Pass output buffer to the procedure executor. */ + mf_params.add_uninitialized_single_output({type, buffer, 1}); + + /* Create virtual array that can be used after the procedure has been executed below. */ + const int out_index = constant_field_indices[i]; + r_varrays[out_index] = &scope.construct<GVArray_For_SingleValueRef>( + type, array_size, buffer); + } + + procedure_executor.call(IndexRange(1), mf_params, mf_context); + } + + /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has + * written the computed data in the right place already. */ + if (!dst_varrays.is_empty()) { + for (const int out_index : fields_to_evaluate.index_range()) { + GVMutableArray *output_varray = get_dst_varray_if_available(out_index); + if (output_varray == nullptr) { + /* Caller did not provide a destination for this output. */ + continue; + } + const GVArray *computed_varray = r_varrays[out_index]; + BLI_assert(computed_varray->type() == output_varray->type()); + if (output_varray == computed_varray) { + /* The result has been written into the destination provided by the caller already. */ + continue; + } + /* Still have to copy over the data in the destination provided by the caller. */ + if (output_varray->is_span()) { + /* Materialize into a span. */ + computed_varray->materialize_to_uninitialized(output_varray->get_internal_span().data()); + } + else { + /* Slower materialize into a different structure. */ + const CPPType &type = computed_varray->type(); + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + for (const int i : mask) { + computed_varray->get_to_uninitialized(i, buffer); + output_varray->set_by_relocate(i, buffer); + } + } + r_varrays[out_index] = output_varray; + } + } + return r_varrays; +} + +void evaluate_constant_field(const GField &field, void *r_value) +{ + ResourceScope scope; + FieldContext context; + Vector<const GVArray *> varrays = evaluate_fields(scope, {field}, IndexRange(1), context); + varrays[0]->get_to_uninitialized(0, r_value); +} + +/** + * If the field depends on some input, the same field is returned. + * Otherwise the field is evaluated and a new field is created that just computes this constant. + * + * Making the field constant has two benefits: + * - The field-tree becomes a single node, which is more efficient when the field is evaluated many + * times. + * - Memory of the input fields may be freed. + */ +GField make_field_constant_if_possible(GField field) +{ + if (field.node().depends_on_input()) { + return field; + } + const CPPType &type = field.cpp_type(); + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + evaluate_constant_field(field, buffer); + auto constant_fn = std::make_unique<CustomMF_GenericConstant>(type, buffer, true); + type.destruct(buffer); + auto operation = std::make_shared<FieldOperation>(std::move(constant_fn)); + return GField{operation, 0}; +} + +const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input, + IndexMask mask, + ResourceScope &scope) const +{ + /* By default ask the field input to create the varray. Another field context might overwrite + * the context here. */ + return field_input.get_varray_for_context(*this, mask, scope); +} + +/* -------------------------------------------------------------------- + * FieldOperation. + */ + +FieldOperation::FieldOperation(std::unique_ptr<const MultiFunction> function, + Vector<GField> inputs) + : FieldOperation(*function, std::move(inputs)) +{ + owned_function_ = std::move(function); +} + +static bool any_field_depends_on_input(Span<GField> fields) +{ + for (const GField &field : fields) { + if (field.node().depends_on_input()) { + return true; + } + } + return false; +} + +FieldOperation::FieldOperation(const MultiFunction &function, Vector<GField> inputs) + : FieldNode(false, any_field_depends_on_input(inputs)), + function_(&function), + inputs_(std::move(inputs)) +{ +} + +void FieldOperation::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const +{ + for (const GField &field : inputs_) { + field.node().foreach_field_input(foreach_fn); + } +} + +/* -------------------------------------------------------------------- + * FieldInput. + */ + +FieldInput::FieldInput(const CPPType &type, std::string debug_name) + : FieldNode(true, true), type_(&type), debug_name_(std::move(debug_name)) +{ +} + +void FieldInput::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const +{ + foreach_fn(*this); +} + +/* -------------------------------------------------------------------- + * FieldEvaluator. + */ + +static Vector<int64_t> indices_from_selection(const VArray<bool> &selection) +{ + /* If the selection is just a single value, it's best to avoid calling this + * function when constructing an IndexMask and use an IndexRange instead. */ + BLI_assert(!selection.is_single()); + + Vector<int64_t> indices; + if (selection.is_span()) { + Span<bool> span = selection.get_internal_span(); + for (const int64_t i : span.index_range()) { + if (span[i]) { + indices.append(i); + } + } + } + else { + for (const int i : selection.index_range()) { + if (selection[i]) { + indices.append(i); + } + } + } + return indices; +} + +int FieldEvaluator::add_with_destination(GField field, GVMutableArray &dst) +{ + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(&dst); + output_pointer_infos_.append({}); + return field_index; +} + +int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst) +{ + GVMutableArray &varray = scope_.construct<GVMutableArray_For_GMutableSpan>(dst); + return this->add_with_destination(std::move(field), varray); +} + +int FieldEvaluator::add(GField field, const GVArray **varray_ptr) +{ + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(nullptr); + output_pointer_infos_.append(OutputPointerInfo{ + varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &UNUSED(scope)) { + *(const GVArray **)dst = &varray; + }}); + return field_index; +} + +int FieldEvaluator::add(GField field) +{ + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(nullptr); + output_pointer_infos_.append({}); + return field_index; +} + +void FieldEvaluator::evaluate() +{ + BLI_assert_msg(!is_evaluated_, "Cannot evaluate fields twice."); + Array<GFieldRef> fields(fields_to_evaluate_.size()); + for (const int i : fields_to_evaluate_.index_range()) { + fields[i] = fields_to_evaluate_[i]; + } + evaluated_varrays_ = evaluate_fields(scope_, fields, mask_, context_, dst_varrays_); + BLI_assert(fields_to_evaluate_.size() == evaluated_varrays_.size()); + for (const int i : fields_to_evaluate_.index_range()) { + OutputPointerInfo &info = output_pointer_infos_[i]; + if (info.dst != nullptr) { + info.set(info.dst, *evaluated_varrays_[i], scope_); + } + } + is_evaluated_ = true; +} + +IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index) +{ + const GVArray &varray = this->get_evaluated(field_index); + GVArray_Typed<bool> typed_varray{varray}; + + if (typed_varray->is_single()) { + if (typed_varray->get_internal_single()) { + return IndexRange(typed_varray.size()); + } + return IndexRange(0); + } + + return scope_.add_value(indices_from_selection(*typed_varray)).as_span(); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/generic_virtual_array.cc b/source/blender/functions/intern/generic_virtual_array.cc index bd033a429de..9a83d8cd497 100644 --- a/source/blender/functions/intern/generic_virtual_array.cc +++ b/source/blender/functions/intern/generic_virtual_array.cc @@ -387,4 +387,47 @@ void GVMutableArray_GSpan::disable_not_applied_warning() show_not_saved_warning_ = false; } +/* -------------------------------------------------------------------- + * GVArray_For_SlicedGVArray. + */ + +void GVArray_For_SlicedGVArray::get_impl(const int64_t index, void *r_value) const +{ + varray_.get(index + offset_, r_value); +} + +void GVArray_For_SlicedGVArray::get_to_uninitialized_impl(const int64_t index, void *r_value) const +{ + varray_.get_to_uninitialized(index + offset_, r_value); +} + +/* -------------------------------------------------------------------- + * GVArray_Slice. + */ + +GVArray_Slice::GVArray_Slice(const GVArray &varray, const IndexRange slice) +{ + if (varray.is_span()) { + /* Create a new virtual for the sliced span. */ + const GSpan span = varray.get_internal_span(); + const GSpan sliced_span = span.slice(slice.start(), slice.size()); + varray_span_.emplace(sliced_span); + varray_ = &*varray_span_; + } + else if (varray.is_single()) { + /* Can just use the existing virtual array, because it's the same value for the indices in the + * slice anyway. */ + varray_ = &varray; + } + else { + /* Generic version when none of the above method works. + * We don't necessarily want to materialize the input varray because there might be + * large distances between the required indices. Then we would materialize many elements that + * are not accessed later on. + */ + varray_any_.emplace(varray, slice); + varray_ = &*varray_any_; + } +} + } // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_builder.cc b/source/blender/functions/intern/multi_function_builder.cc index c6b3b808130..f891f162820 100644 --- a/source/blender/functions/intern/multi_function_builder.cc +++ b/source/blender/functions/intern/multi_function_builder.cc @@ -20,9 +20,18 @@ namespace blender::fn { -CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const void *value) - : type_(type), value_(value) +CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, + const void *value, + bool make_value_copy) + : type_(type), owns_value_(make_value_copy) { + if (make_value_copy) { + void *copied_value = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + type.copy_construct(value, copied_value); + value = copied_value; + } + value_ = value; + MFSignatureBuilder signature{"Constant " + type.name()}; std::stringstream ss; type.print_or_default(value, ss, type.name()); @@ -31,6 +40,14 @@ CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const vo this->set_signature(&signature_); } +CustomMF_GenericConstant::~CustomMF_GenericConstant() +{ + if (owns_value_) { + signature_.param_types[0].data_type().single_type().destruct((void *)value_); + MEM_freeN((void *)value_); + } +} + void CustomMF_GenericConstant::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const @@ -123,4 +140,32 @@ void CustomMF_DefaultOutput::call(IndexMask mask, MFParams params, MFContext UNU } } +CustomMF_GenericCopy::CustomMF_GenericCopy(StringRef name, MFDataType data_type) +{ + MFSignatureBuilder signature{name}; + signature.input("Input", data_type); + signature.output("Output", data_type); + signature_ = signature.build(); + this->set_signature(&signature_); +} + +void CustomMF_GenericCopy::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + const MFDataType data_type = this->param_type(0).data_type(); + switch (data_type.category()) { + case MFDataType::Single: { + const GVArray &inputs = params.readonly_single_input(0, "Input"); + GMutableSpan outputs = params.uninitialized_single_output(1, "Output"); + inputs.materialize_to_uninitialized(mask, outputs.data()); + break; + } + case MFDataType::Vector: { + const GVVectorArray &inputs = params.readonly_vector_input(0, "Input"); + GVectorArray &outputs = params.vector_output(1, "Output"); + outputs.extend(mask, inputs); + break; + } + } +} + } // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_parallel.cc b/source/blender/functions/intern/multi_function_parallel.cc new file mode 100644 index 00000000000..5a8c621f0b3 --- /dev/null +++ b/source/blender/functions/intern/multi_function_parallel.cc @@ -0,0 +1,95 @@ +/* + * 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 "FN_multi_function_parallel.hh" + +#include "BLI_task.hh" + +namespace blender::fn { + +ParallelMultiFunction::ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size) + : fn_(fn), grain_size_(grain_size) +{ + this->set_signature(&fn.signature()); + + threading_supported_ = true; + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + if (param_type.data_type().category() == MFDataType::Vector) { + /* Vector parameters do not support threading yet. */ + threading_supported_ = false; + break; + } + } +} + +void ParallelMultiFunction::call(IndexMask full_mask, MFParams params, MFContext context) const +{ + if (full_mask.size() <= grain_size_ || !threading_supported_) { + fn_.call(full_mask, params, context); + return; + } + + threading::parallel_for(full_mask.index_range(), grain_size_, [&](const IndexRange mask_slice) { + Vector<int64_t> sub_mask_indices; + const IndexMask sub_mask = full_mask.slice_and_offset(mask_slice, sub_mask_indices); + if (sub_mask.is_empty()) { + return; + } + const int64_t input_slice_start = full_mask[mask_slice.first()]; + const int64_t input_slice_size = full_mask[mask_slice.last()] - input_slice_start + 1; + const IndexRange input_slice_range{input_slice_start, input_slice_size}; + + MFParamsBuilder sub_params{fn_, sub_mask.min_array_size()}; + ResourceScope &scope = sub_params.resource_scope(); + + /* All parameters are sliced so that the wrapped multi-function does not have to take care of + * the index offset. */ + for (const int param_index : fn_.param_indices()) { + const MFParamType param_type = fn_.param_type(param_index); + switch (param_type.category()) { + case MFParamType::SingleInput: { + const GVArray &varray = params.readonly_single_input(param_index); + const GVArray &sliced_varray = scope.construct<GVArray_Slice>(varray, input_slice_range); + sub_params.add_readonly_single_input(sliced_varray); + break; + } + case MFParamType::SingleMutable: { + const GMutableSpan span = params.single_mutable(param_index); + const GMutableSpan sliced_span = span.slice(input_slice_start, input_slice_size); + sub_params.add_single_mutable(sliced_span); + break; + } + case MFParamType::SingleOutput: { + const GMutableSpan span = params.uninitialized_single_output(param_index); + const GMutableSpan sliced_span = span.slice(input_slice_start, input_slice_size); + sub_params.add_uninitialized_single_output(sliced_span); + break; + } + case MFParamType::VectorInput: + case MFParamType::VectorMutable: + case MFParamType::VectorOutput: { + BLI_assert_unreachable(); + break; + } + } + } + + fn_.call(sub_mask, sub_params, context); + }); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc new file mode 100644 index 00000000000..fa95e8de71e --- /dev/null +++ b/source/blender/functions/intern/multi_function_procedure.cc @@ -0,0 +1,874 @@ +/* + * 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 "FN_multi_function_procedure.hh" + +#include "BLI_dot_export.hh" +#include "BLI_stack.hh" + +namespace blender::fn { + +void MFInstructionCursor::set_next(MFProcedure &procedure, MFInstruction *new_instruction) const +{ + switch (type_) { + case Type::None: { + break; + } + case Type::Entry: { + procedure.set_entry(*new_instruction); + break; + } + case Type::Call: { + static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction); + break; + } + case Type::Branch: { + MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_); + if (branch_output_) { + branch_instruction.set_branch_true(new_instruction); + } + else { + branch_instruction.set_branch_false(new_instruction); + } + break; + } + case Type::Destruct: { + static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction); + break; + } + case Type::Dummy: { + static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction); + break; + } + } +} + +MFInstruction *MFInstructionCursor::next(MFProcedure &procedure) const +{ + switch (type_) { + case Type::None: + return nullptr; + case Type::Entry: + return procedure.entry(); + case Type::Call: + return static_cast<MFCallInstruction *>(instruction_)->next(); + case Type::Branch: { + MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_); + if (branch_output_) { + return branch_instruction.branch_true(); + } + return branch_instruction.branch_false(); + } + case Type::Destruct: + return static_cast<MFDestructInstruction *>(instruction_)->next(); + case Type::Dummy: + return static_cast<MFDummyInstruction *>(instruction_)->next(); + } + return nullptr; +} + +void MFVariable::set_name(std::string name) +{ + name_ = std::move(name); +} + +void MFCallInstruction::set_next(MFInstruction *instruction) +{ + if (next_ != nullptr) { + next_->prev_.remove_first_occurrence_and_reorder(*this); + } + if (instruction != nullptr) { + instruction->prev_.append(*this); + } + next_ = instruction; +} + +void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable) +{ + if (params_[param_index] != nullptr) { + params_[param_index]->users_.remove_first_occurrence_and_reorder(this); + } + if (variable != nullptr) { + BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type()); + variable->users_.append(this); + } + params_[param_index] = variable; +} + +void MFCallInstruction::set_params(Span<MFVariable *> variables) +{ + BLI_assert(variables.size() == params_.size()); + for (const int i : variables.index_range()) { + this->set_param_variable(i, variables[i]); + } +} + +void MFBranchInstruction::set_condition(MFVariable *variable) +{ + if (condition_ != nullptr) { + condition_->users_.remove_first_occurrence_and_reorder(this); + } + if (variable != nullptr) { + variable->users_.append(this); + } + condition_ = variable; +} + +void MFBranchInstruction::set_branch_true(MFInstruction *instruction) +{ + if (branch_true_ != nullptr) { + branch_true_->prev_.remove_first_occurrence_and_reorder({*this, true}); + } + if (instruction != nullptr) { + instruction->prev_.append({*this, true}); + } + branch_true_ = instruction; +} + +void MFBranchInstruction::set_branch_false(MFInstruction *instruction) +{ + if (branch_false_ != nullptr) { + branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false}); + } + if (instruction != nullptr) { + instruction->prev_.append({*this, false}); + } + branch_false_ = instruction; +} + +void MFDestructInstruction::set_variable(MFVariable *variable) +{ + if (variable_ != nullptr) { + variable_->users_.remove_first_occurrence_and_reorder(this); + } + if (variable != nullptr) { + variable->users_.append(this); + } + variable_ = variable; +} + +void MFDestructInstruction::set_next(MFInstruction *instruction) +{ + if (next_ != nullptr) { + next_->prev_.remove_first_occurrence_and_reorder(*this); + } + if (instruction != nullptr) { + instruction->prev_.append(*this); + } + next_ = instruction; +} + +void MFDummyInstruction::set_next(MFInstruction *instruction) +{ + if (next_ != nullptr) { + next_->prev_.remove_first_occurrence_and_reorder(*this); + } + if (instruction != nullptr) { + instruction->prev_.append(*this); + } + next_ = instruction; +} + +MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name) +{ + MFVariable &variable = *allocator_.construct<MFVariable>().release(); + variable.name_ = std::move(name); + variable.data_type_ = data_type; + variable.id_ = variables_.size(); + variables_.append(&variable); + return variable; +} + +MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn) +{ + MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release(); + instruction.type_ = MFInstructionType::Call; + instruction.fn_ = &fn; + instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount()); + instruction.params_.fill(nullptr); + call_instructions_.append(&instruction); + return instruction; +} + +MFBranchInstruction &MFProcedure::new_branch_instruction() +{ + MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release(); + instruction.type_ = MFInstructionType::Branch; + branch_instructions_.append(&instruction); + return instruction; +} + +MFDestructInstruction &MFProcedure::new_destruct_instruction() +{ + MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release(); + instruction.type_ = MFInstructionType::Destruct; + destruct_instructions_.append(&instruction); + return instruction; +} + +MFDummyInstruction &MFProcedure::new_dummy_instruction() +{ + MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release(); + instruction.type_ = MFInstructionType::Dummy; + dummy_instructions_.append(&instruction); + return instruction; +} + +MFReturnInstruction &MFProcedure::new_return_instruction() +{ + MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release(); + instruction.type_ = MFInstructionType::Return; + return_instructions_.append(&instruction); + return instruction; +} + +void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable) +{ + params_.append({interface_type, &variable}); +} + +void MFProcedure::set_entry(MFInstruction &entry) +{ + if (entry_ != nullptr) { + entry_->prev_.remove_first_occurrence_and_reorder(MFInstructionCursor::ForEntry()); + } + entry_ = &entry; + entry_->prev_.append(MFInstructionCursor::ForEntry()); +} + +MFProcedure::~MFProcedure() +{ + for (MFCallInstruction *instruction : call_instructions_) { + instruction->~MFCallInstruction(); + } + for (MFBranchInstruction *instruction : branch_instructions_) { + instruction->~MFBranchInstruction(); + } + for (MFDestructInstruction *instruction : destruct_instructions_) { + instruction->~MFDestructInstruction(); + } + for (MFDummyInstruction *instruction : dummy_instructions_) { + instruction->~MFDummyInstruction(); + } + for (MFReturnInstruction *instruction : return_instructions_) { + instruction->~MFReturnInstruction(); + } + for (MFVariable *variable : variables_) { + variable->~MFVariable(); + } +} + +bool MFProcedure::validate() const +{ + if (entry_ == nullptr) { + return false; + } + if (!this->validate_all_instruction_pointers_set()) { + return false; + } + if (!this->validate_all_params_provided()) { + return false; + } + if (!this->validate_same_variables_in_one_call()) { + return false; + } + if (!this->validate_parameters()) { + return false; + } + if (!this->validate_initialization()) { + return false; + } + return true; +} + +bool MFProcedure::validate_all_instruction_pointers_set() const +{ + for (const MFCallInstruction *instruction : call_instructions_) { + if (instruction->next_ == nullptr) { + return false; + } + } + for (const MFDestructInstruction *instruction : destruct_instructions_) { + if (instruction->next_ == nullptr) { + return false; + } + } + for (const MFBranchInstruction *instruction : branch_instructions_) { + if (instruction->branch_true_ == nullptr) { + return false; + } + if (instruction->branch_false_ == nullptr) { + return false; + } + } + for (const MFDummyInstruction *instruction : dummy_instructions_) { + if (instruction->next_ == nullptr) { + return false; + } + } + return true; +} + +bool MFProcedure::validate_all_params_provided() const +{ + for (const MFCallInstruction *instruction : call_instructions_) { + const MultiFunction &fn = instruction->fn(); + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + if (param_type.category() == MFParamType::SingleOutput) { + /* Single outputs are optional. */ + continue; + } + const MFVariable *variable = instruction->params_[param_index]; + if (variable == nullptr) { + return false; + } + } + } + for (const MFBranchInstruction *instruction : branch_instructions_) { + if (instruction->condition_ == nullptr) { + return false; + } + } + for (const MFDestructInstruction *instruction : destruct_instructions_) { + if (instruction->variable_ == nullptr) { + return false; + } + } + return true; +} + +bool MFProcedure::validate_same_variables_in_one_call() const +{ + for (const MFCallInstruction *instruction : call_instructions_) { + const MultiFunction &fn = *instruction->fn_; + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + const MFVariable *variable = instruction->params_[param_index]; + if (variable == nullptr) { + continue; + } + for (const int other_param_index : fn.param_indices()) { + if (other_param_index == param_index) { + continue; + } + const MFVariable *other_variable = instruction->params_[other_param_index]; + if (other_variable != variable) { + continue; + } + if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) { + /* When a variable is used as mutable or output parameter, it can only be used once. */ + return false; + } + const MFParamType other_param_type = fn.param_type(other_param_index); + /* A variable is allowed to be used as input more than once. */ + if (other_param_type.interface_type() != MFParamType::Input) { + return false; + } + } + } + } + return true; +} + +bool MFProcedure::validate_parameters() const +{ + Set<const MFVariable *> variables; + for (const MFParameter ¶m : params_) { + /* One variable cannot be used as multiple parameters. */ + if (!variables.add(param.variable)) { + return false; + } + } + return true; +} + +bool MFProcedure::validate_initialization() const +{ + /* TODO: Issue warning when it maybe wrongly initialized. */ + for (const MFDestructInstruction *instruction : destruct_instructions_) { + const MFVariable &variable = *instruction->variable_; + const InitState state = this->find_initialization_state_before_instruction(*instruction, + variable); + if (!state.can_be_initialized) { + return false; + } + } + for (const MFBranchInstruction *instruction : branch_instructions_) { + const MFVariable &variable = *instruction->condition_; + const InitState state = this->find_initialization_state_before_instruction(*instruction, + variable); + if (!state.can_be_initialized) { + return false; + } + } + for (const MFCallInstruction *instruction : call_instructions_) { + const MultiFunction &fn = *instruction->fn_; + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + const MFVariable &variable = *instruction->params_[param_index]; + const InitState state = this->find_initialization_state_before_instruction(*instruction, + variable); + switch (param_type.interface_type()) { + case MFParamType::Input: + case MFParamType::Mutable: { + if (!state.can_be_initialized) { + return false; + } + break; + } + case MFParamType::Output: { + if (!state.can_be_uninitialized) { + return false; + } + break; + } + } + } + } + Set<const MFVariable *> variables_that_should_be_initialized_on_return; + for (const MFParameter ¶m : params_) { + if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) { + variables_that_should_be_initialized_on_return.add_new(param.variable); + } + } + for (const MFReturnInstruction *instruction : return_instructions_) { + for (const MFVariable *variable : variables_) { + const InitState init_state = this->find_initialization_state_before_instruction(*instruction, + *variable); + if (variables_that_should_be_initialized_on_return.contains(variable)) { + if (!init_state.can_be_initialized) { + return false; + } + } + else { + if (!init_state.can_be_uninitialized) { + return false; + } + } + } + } + return true; +} + +MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction( + const MFInstruction &target_instruction, const MFVariable &target_variable) const +{ + InitState state; + + auto check_entry_instruction = [&]() { + bool caller_initialized_variable = false; + for (const MFParameter ¶m : params_) { + if (param.variable == &target_variable) { + if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) { + caller_initialized_variable = true; + break; + } + } + } + if (caller_initialized_variable) { + state.can_be_initialized = true; + } + else { + state.can_be_uninitialized = true; + } + }; + + if (&target_instruction == entry_) { + check_entry_instruction(); + } + + Set<const MFInstruction *> checked_instructions; + Stack<const MFInstruction *> instructions_to_check; + for (const MFInstructionCursor &cursor : target_instruction.prev_) { + if (cursor.instruction() != nullptr) { + instructions_to_check.push(cursor.instruction()); + } + } + + while (!instructions_to_check.is_empty()) { + const MFInstruction &instruction = *instructions_to_check.pop(); + if (!checked_instructions.add(&instruction)) { + /* Skip if the instruction has been checked already. */ + continue; + } + bool state_modified = false; + switch (instruction.type_) { + case MFInstructionType::Call: { + const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>( + instruction); + const MultiFunction &fn = *call_instruction.fn_; + for (const int param_index : fn.param_indices()) { + if (call_instruction.params_[param_index] == &target_variable) { + const MFParamType param_type = fn.param_type(param_index); + if (param_type.interface_type() == MFParamType::Output) { + state.can_be_initialized = true; + state_modified = true; + break; + } + } + } + break; + } + case MFInstructionType::Destruct: { + const MFDestructInstruction &destruct_instruction = + static_cast<const MFDestructInstruction &>(instruction); + if (destruct_instruction.variable_ == &target_variable) { + state.can_be_uninitialized = true; + state_modified = true; + } + break; + } + case MFInstructionType::Branch: + case MFInstructionType::Dummy: + case MFInstructionType::Return: { + /* These instruction types don't change the initialization state of variables. */ + break; + } + } + + if (!state_modified) { + if (&instruction == entry_) { + check_entry_instruction(); + } + for (const MFInstructionCursor &cursor : instruction.prev_) { + if (cursor.instruction() != nullptr) { + instructions_to_check.push(cursor.instruction()); + } + } + } + } + + return state; +} + +class MFProcedureDotExport { + private: + const MFProcedure &procedure_; + dot::DirectedGraph digraph_; + Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_; + Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_; + + public: + MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure) + { + } + + std::string generate() + { + this->create_nodes(); + this->create_edges(); + return digraph_.to_dot_string(); + } + + void create_nodes() + { + Vector<const MFInstruction *> all_instructions; + auto add_instructions = [&](auto instructions) { + all_instructions.extend(instructions.begin(), instructions.end()); + }; + add_instructions(procedure_.call_instructions_); + add_instructions(procedure_.branch_instructions_); + add_instructions(procedure_.destruct_instructions_); + add_instructions(procedure_.dummy_instructions_); + add_instructions(procedure_.return_instructions_); + + Set<const MFInstruction *> handled_instructions; + + for (const MFInstruction *representative : all_instructions) { + if (handled_instructions.contains(representative)) { + continue; + } + Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block( + *representative); + std::stringstream ss; + ss << "<"; + + for (const MFInstruction *current : block_instructions) { + handled_instructions.add_new(current); + switch (current->type()) { + case MFInstructionType::Call: { + this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss); + break; + } + case MFInstructionType::Destruct: { + this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss); + break; + } + case MFInstructionType::Dummy: { + this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss); + break; + } + case MFInstructionType::Return: { + this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss); + break; + } + case MFInstructionType::Branch: { + this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss); + break; + } + } + ss << R"(<br align="left" />)"; + } + ss << ">"; + + dot::Node &dot_node = digraph_.new_node(ss.str()); + dot_node.set_shape(dot::Attr_shape::Rectangle); + dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node); + dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node); + } + } + + void create_edges() + { + auto create_edge = [&](dot::Node &from_node, + const MFInstruction *to_instruction) -> dot::DirectedEdge & { + if (to_instruction == nullptr) { + dot::Node &to_node = digraph_.new_node("missing"); + to_node.set_shape(dot::Attr_shape::Diamond); + return digraph_.new_edge(from_node, to_node); + } + dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction); + return digraph_.new_edge(from_node, to_node); + }; + + for (auto item : dot_nodes_by_end_.items()) { + const MFInstruction &from_instruction = *item.key; + dot::Node &from_node = *item.value; + switch (from_instruction.type()) { + case MFInstructionType::Call: { + const MFInstruction *to_instruction = + static_cast<const MFCallInstruction &>(from_instruction).next(); + create_edge(from_node, to_instruction); + break; + } + case MFInstructionType::Destruct: { + const MFInstruction *to_instruction = + static_cast<const MFDestructInstruction &>(from_instruction).next(); + create_edge(from_node, to_instruction); + break; + } + case MFInstructionType::Dummy: { + const MFInstruction *to_instruction = + static_cast<const MFDummyInstruction &>(from_instruction).next(); + create_edge(from_node, to_instruction); + break; + } + case MFInstructionType::Return: { + break; + } + case MFInstructionType::Branch: { + const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>( + from_instruction); + const MFInstruction *to_true_instruction = branch_instruction.branch_true(); + const MFInstruction *to_false_instruction = branch_instruction.branch_false(); + create_edge(from_node, to_true_instruction).attributes.set("color", "#118811"); + create_edge(from_node, to_false_instruction).attributes.set("color", "#881111"); + break; + } + } + } + + dot::Node &entry_node = this->create_entry_node(); + create_edge(entry_node, procedure_.entry()); + } + + bool has_to_be_block_begin(const MFInstruction &instruction) + { + if (instruction.prev().size() != 1) { + return true; + } + if (ELEM(instruction.prev()[0].type(), + MFInstructionCursor::Type::Branch, + MFInstructionCursor::Type::Entry)) { + return true; + } + return false; + } + + const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative) + { + const MFInstruction *current = &representative; + while (!this->has_to_be_block_begin(*current)) { + current = current->prev()[0].instruction(); + if (current == &representative) { + /* There is a loop without entry or exit, just break it up here. */ + break; + } + } + return *current; + } + + const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction, + const MFInstruction &block_begin) + { + const MFInstruction *next = nullptr; + switch (instruction.type()) { + case MFInstructionType::Call: { + next = static_cast<const MFCallInstruction &>(instruction).next(); + break; + } + case MFInstructionType::Destruct: { + next = static_cast<const MFDestructInstruction &>(instruction).next(); + break; + } + case MFInstructionType::Dummy: { + next = static_cast<const MFDummyInstruction &>(instruction).next(); + break; + } + case MFInstructionType::Return: + case MFInstructionType::Branch: { + break; + } + } + if (next == nullptr) { + return nullptr; + } + if (next == &block_begin) { + return nullptr; + } + if (this->has_to_be_block_begin(*next)) { + return nullptr; + } + return next; + } + + Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative) + { + Vector<const MFInstruction *> instructions; + const MFInstruction &begin = this->get_first_instruction_in_block(representative); + for (const MFInstruction *current = &begin; current != nullptr; + current = this->get_next_instruction_in_block(*current, begin)) { + instructions.append(current); + } + return instructions; + } + + void variable_to_string(const MFVariable *variable, std::stringstream &ss) + { + if (variable == nullptr) { + ss << "null"; + } + else { + ss << "$" << variable->id(); + if (!variable->name().is_empty()) { + ss << "(" << variable->name() << ")"; + } + } + } + + void instruction_name_format(StringRef name, std::stringstream &ss) + { + ss << name; + } + + void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss) + { + const MultiFunction &fn = instruction.fn(); + this->instruction_name_format(fn.name() + ": ", ss); + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + const MFVariable *variable = instruction.params()[param_index]; + ss << R"(<font color="grey30">)"; + switch (param_type.interface_type()) { + case MFParamType::Input: { + ss << "in"; + break; + } + case MFParamType::Mutable: { + ss << "mut"; + break; + } + case MFParamType::Output: { + ss << "out"; + break; + } + } + ss << " </font> "; + variable_to_string(variable, ss); + if (param_index < fn.param_amount() - 1) { + ss << ", "; + } + } + } + + void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss) + { + instruction_name_format("Destruct ", ss); + variable_to_string(instruction.variable(), ss); + } + + void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss) + { + instruction_name_format("Dummy ", ss); + } + + void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss) + { + instruction_name_format("Return ", ss); + + Vector<ConstMFParameter> outgoing_parameters; + for (const ConstMFParameter ¶m : procedure_.params()) { + if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) { + outgoing_parameters.append(param); + } + } + for (const int param_index : outgoing_parameters.index_range()) { + const ConstMFParameter ¶m = outgoing_parameters[param_index]; + variable_to_string(param.variable, ss); + if (param_index < outgoing_parameters.size() - 1) { + ss << ", "; + } + } + } + + void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss) + { + instruction_name_format("Branch ", ss); + variable_to_string(instruction.condition(), ss); + } + + dot::Node &create_entry_node() + { + std::stringstream ss; + ss << "Entry: "; + Vector<ConstMFParameter> incoming_parameters; + for (const ConstMFParameter ¶m : procedure_.params()) { + if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) { + incoming_parameters.append(param); + } + } + for (const int param_index : incoming_parameters.index_range()) { + const ConstMFParameter ¶m = incoming_parameters[param_index]; + variable_to_string(param.variable, ss); + if (param_index < incoming_parameters.size() - 1) { + ss << ", "; + } + } + + dot::Node &node = digraph_.new_node(ss.str()); + node.set_shape(dot::Attr_shape::Ellipse); + return node; + } +}; + +std::string MFProcedure::to_dot() const +{ + MFProcedureDotExport dot_export{*this}; + return dot_export.generate(); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_procedure_builder.cc b/source/blender/functions/intern/multi_function_procedure_builder.cc new file mode 100644 index 00000000000..d30e6c0e14a --- /dev/null +++ b/source/blender/functions/intern/multi_function_procedure_builder.cc @@ -0,0 +1,131 @@ +/* + * 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 "FN_multi_function_procedure_builder.hh" + +namespace blender::fn { + +void MFProcedureBuilder::add_destruct(MFVariable &variable) +{ + MFDestructInstruction &instruction = procedure_->new_destruct_instruction(); + instruction.set_variable(&variable); + this->link_to_cursors(&instruction); + cursors_ = {MFInstructionCursor{instruction}}; +} + +void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables) +{ + for (MFVariable *variable : variables) { + this->add_destruct(*variable); + } +} + +MFReturnInstruction &MFProcedureBuilder::add_return() +{ + MFReturnInstruction &instruction = procedure_->new_return_instruction(); + this->link_to_cursors(&instruction); + cursors_ = {}; + return instruction; +} + +MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn) +{ + MFCallInstruction &instruction = procedure_->new_call_instruction(fn); + this->link_to_cursors(&instruction); + cursors_ = {MFInstructionCursor{instruction}}; + return instruction; +} + +MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables( + const MultiFunction &fn, Span<MFVariable *> param_variables) +{ + MFCallInstruction &instruction = this->add_call_with_no_variables(fn); + instruction.set_params(param_variables); + return instruction; +} + +Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn, + Span<MFVariable *> input_and_mutable_variables) +{ + Vector<MFVariable *> output_variables; + MFCallInstruction &instruction = this->add_call_with_no_variables(fn); + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + switch (param_type.interface_type()) { + case MFParamType::Input: + case MFParamType::Mutable: { + MFVariable *variable = input_and_mutable_variables.first(); + instruction.set_param_variable(param_index, variable); + input_and_mutable_variables = input_and_mutable_variables.drop_front(1); + break; + } + case MFParamType::Output: { + MFVariable &variable = procedure_->new_variable(param_type.data_type(), + fn.param_name(param_index)); + instruction.set_param_variable(param_index, &variable); + output_variables.append(&variable); + break; + } + } + } + /* All passed in variables should have been dropped in the loop above. */ + BLI_assert(input_and_mutable_variables.is_empty()); + return output_variables; +} + +MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition) +{ + MFBranchInstruction &instruction = procedure_->new_branch_instruction(); + instruction.set_condition(&condition); + this->link_to_cursors(&instruction); + /* Clear cursors because this builder ends here. */ + cursors_.clear(); + + Branch branch{*procedure_, *procedure_}; + branch.branch_true.set_cursor(MFInstructionCursor{instruction, true}); + branch.branch_false.set_cursor(MFInstructionCursor{instruction, false}); + return branch; +} + +MFProcedureBuilder::Loop MFProcedureBuilder::add_loop() +{ + MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction(); + MFDummyInstruction &loop_end = procedure_->new_dummy_instruction(); + this->link_to_cursors(&loop_begin); + cursors_ = {MFInstructionCursor{loop_begin}}; + + Loop loop; + loop.begin = &loop_begin; + loop.end = &loop_end; + + return loop; +} + +void MFProcedureBuilder::add_loop_continue(Loop &loop) +{ + this->link_to_cursors(loop.begin); + /* Clear cursors because this builder ends here. */ + cursors_.clear(); +} + +void MFProcedureBuilder::add_loop_break(Loop &loop) +{ + this->link_to_cursors(loop.end); + /* Clear cursors because this builder ends here. */ + cursors_.clear(); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc new file mode 100644 index 00000000000..b97282accdd --- /dev/null +++ b/source/blender/functions/intern/multi_function_procedure_executor.cc @@ -0,0 +1,1227 @@ +/* + * 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 "FN_multi_function_procedure_executor.hh" + +#include "BLI_stack.hh" + +namespace blender::fn { + +MFProcedureExecutor::MFProcedureExecutor(std::string name, const MFProcedure &procedure) + : procedure_(procedure) +{ + MFSignatureBuilder signature(std::move(name)); + + for (const ConstMFParameter ¶m : procedure.params()) { + signature.add(param.variable->name(), MFParamType(param.type, param.variable->data_type())); + } + + signature_ = signature.build(); + this->set_signature(&signature_); +} + +using IndicesSplitVectors = std::array<Vector<int64_t>, 2>; + +namespace { +enum class ValueType { + GVArray = 0, + Span = 1, + GVVectorArray = 2, + GVectorArray = 3, + OneSingle = 4, + OneVector = 5, +}; +constexpr int tot_variable_value_types = 6; +} // namespace + +/** + * During evaluation, a variable may be stored in various different forms, depending on what + * instructions do with the variables. + */ +struct VariableValue { + ValueType type; + + VariableValue(ValueType type) : type(type) + { + } +}; + +/* This variable is the unmodified virtual array from the caller. */ +struct VariableValue_GVArray : public VariableValue { + static inline constexpr ValueType static_type = ValueType::GVArray; + const GVArray &data; + + VariableValue_GVArray(const GVArray &data) : VariableValue(static_type), data(data) + { + } +}; + +/* This variable has a different value for every index. Some values may be uninitialized. The span + * may be owned by the caller. */ +struct VariableValue_Span : public VariableValue { + static inline constexpr ValueType static_type = ValueType::Span; + void *data; + bool owned; + + VariableValue_Span(void *data, bool owned) : VariableValue(static_type), data(data), owned(owned) + { + } +}; + +/* This variable is the unmodified virtual vector array from the caller. */ +struct VariableValue_GVVectorArray : public VariableValue { + static inline constexpr ValueType static_type = ValueType::GVVectorArray; + const GVVectorArray &data; + + VariableValue_GVVectorArray(const GVVectorArray &data) : VariableValue(static_type), data(data) + { + } +}; + +/* This variable has a different vector for every index. */ +struct VariableValue_GVectorArray : public VariableValue { + static inline constexpr ValueType static_type = ValueType::GVectorArray; + GVectorArray &data; + bool owned; + + VariableValue_GVectorArray(GVectorArray &data, bool owned) + : VariableValue(static_type), data(data), owned(owned) + { + } +}; + +/* This variable has the same value for every index. */ +struct VariableValue_OneSingle : public VariableValue { + static inline constexpr ValueType static_type = ValueType::OneSingle; + void *data; + bool is_initialized = false; + + VariableValue_OneSingle(void *data) : VariableValue(static_type), data(data) + { + } +}; + +/* This variable has the same vector for every index. */ +struct VariableValue_OneVector : public VariableValue { + static inline constexpr ValueType static_type = ValueType::OneVector; + GVectorArray &data; + + VariableValue_OneVector(GVectorArray &data) : VariableValue(static_type), data(data) + { + } +}; + +static_assert(std::is_trivially_destructible_v<VariableValue_GVArray>); +static_assert(std::is_trivially_destructible_v<VariableValue_Span>); +static_assert(std::is_trivially_destructible_v<VariableValue_GVVectorArray>); +static_assert(std::is_trivially_destructible_v<VariableValue_GVectorArray>); +static_assert(std::is_trivially_destructible_v<VariableValue_OneSingle>); +static_assert(std::is_trivially_destructible_v<VariableValue_OneVector>); + +class VariableState; + +/** + * The #ValueAllocator is responsible for providing memory for variables and their values. It also + * manages the reuse of buffers to improve performance. + */ +class ValueAllocator : NonCopyable, NonMovable { + private: + /* Allocate with 64 byte alignment for better reusability of buffers and improved cache + * performance. */ + static constexpr inline int min_alignment = 64; + + /* Use stacks so that the most recently used buffers are reused first. This improves cache + * efficiency. */ + std::array<Stack<VariableValue *>, tot_variable_value_types> values_free_lists_; + /* The integer key is the size of one element (e.g. 4 for an integer buffer). All buffers are + * aligned to #min_alignment bytes. */ + Map<int, Stack<void *>> span_buffers_free_list_; + + public: + ValueAllocator() = default; + + ~ValueAllocator() + { + for (Stack<VariableValue *> &stack : values_free_lists_) { + while (!stack.is_empty()) { + MEM_freeN(stack.pop()); + } + } + for (Stack<void *> &stack : span_buffers_free_list_.values()) { + while (!stack.is_empty()) { + MEM_freeN(stack.pop()); + } + } + } + + template<typename... Args> VariableState *obtain_variable_state(Args &&...args); + + void release_variable_state(VariableState *state); + + VariableValue_GVArray *obtain_GVArray(const GVArray &varray) + { + return this->obtain<VariableValue_GVArray>(varray); + } + + VariableValue_GVVectorArray *obtain_GVVectorArray(const GVVectorArray &varray) + { + return this->obtain<VariableValue_GVVectorArray>(varray); + } + + VariableValue_Span *obtain_Span_not_owned(void *buffer) + { + return this->obtain<VariableValue_Span>(buffer, false); + } + + VariableValue_Span *obtain_Span(const CPPType &type, int size) + { + void *buffer = nullptr; + + const int element_size = type.size(); + const int alignment = type.alignment(); + + if (alignment > min_alignment) { + /* In this rare case we fallback to not reusing existing buffers. */ + buffer = MEM_mallocN_aligned(element_size * size, alignment, __func__); + } + else { + Stack<void *> *stack = span_buffers_free_list_.lookup_ptr(element_size); + if (stack == nullptr || stack->is_empty()) { + buffer = MEM_mallocN_aligned(element_size * size, min_alignment, __func__); + } + else { + /* Reuse existing buffer. */ + buffer = stack->pop(); + } + } + + return this->obtain<VariableValue_Span>(buffer, true); + } + + VariableValue_GVectorArray *obtain_GVectorArray_not_owned(GVectorArray &data) + { + return this->obtain<VariableValue_GVectorArray>(data, false); + } + + VariableValue_GVectorArray *obtain_GVectorArray(const CPPType &type, int size) + { + GVectorArray *vector_array = new GVectorArray(type, size); + return this->obtain<VariableValue_GVectorArray>(*vector_array, true); + } + + VariableValue_OneSingle *obtain_OneSingle(const CPPType &type) + { + void *buffer = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + return this->obtain<VariableValue_OneSingle>(buffer); + } + + VariableValue_OneVector *obtain_OneVector(const CPPType &type) + { + GVectorArray *vector_array = new GVectorArray(type, 1); + return this->obtain<VariableValue_OneVector>(*vector_array); + } + + void release_value(VariableValue *value, const MFDataType &data_type) + { + switch (value->type) { + case ValueType::GVArray: { + break; + } + case ValueType::Span: { + auto *value_typed = static_cast<VariableValue_Span *>(value); + if (value_typed->owned) { + const CPPType &type = data_type.single_type(); + /* Assumes all values in the buffer are uninitialized already. */ + Stack<void *> &buffers = span_buffers_free_list_.lookup_or_add_default(type.size()); + buffers.push(value_typed->data); + } + break; + } + case ValueType::GVVectorArray: { + break; + } + case ValueType::GVectorArray: { + auto *value_typed = static_cast<VariableValue_GVectorArray *>(value); + if (value_typed->owned) { + delete &value_typed->data; + } + break; + } + case ValueType::OneSingle: { + auto *value_typed = static_cast<VariableValue_OneSingle *>(value); + if (value_typed->is_initialized) { + const CPPType &type = data_type.single_type(); + type.destruct(value_typed->data); + } + MEM_freeN(value_typed->data); + break; + } + case ValueType::OneVector: { + auto *value_typed = static_cast<VariableValue_OneVector *>(value); + delete &value_typed->data; + break; + } + } + + Stack<VariableValue *> &stack = values_free_lists_[(int)value->type]; + stack.push(value); + } + + private: + template<typename T, typename... Args> T *obtain(Args &&...args) + { + static_assert(std::is_base_of_v<VariableValue, T>); + Stack<VariableValue *> &stack = values_free_lists_[(int)T::static_type]; + if (stack.is_empty()) { + void *buffer = MEM_mallocN(sizeof(T), __func__); + return new (buffer) T(std::forward<Args>(args)...); + } + return new (stack.pop()) T(std::forward<Args>(args)...); + } +}; + +/** + * This class keeps track of a single variable during evaluation. + */ +class VariableState : NonCopyable, NonMovable { + private: + /** The current value of the variable. The storage format may change over time. */ + VariableValue *value_; + /** Number of indices that are currently initialized in this variable. */ + int tot_initialized_; + /* This a non-owning pointer to either span buffer or #GVectorArray or null. */ + void *caller_provided_storage_ = nullptr; + + public: + VariableState(VariableValue &value, int tot_initialized, void *caller_provided_storage = nullptr) + : value_(&value), + tot_initialized_(tot_initialized), + caller_provided_storage_(caller_provided_storage) + { + } + + void destruct_self(ValueAllocator &value_allocator, const MFDataType &data_type) + { + value_allocator.release_value(value_, data_type); + value_allocator.release_variable_state(this); + } + + /* True if this contains only one value for all indices, i.e. the value for all indices is + * the same. */ + bool is_one() const + { + switch (value_->type) { + case ValueType::GVArray: + return this->value_as<VariableValue_GVArray>()->data.is_single(); + case ValueType::Span: + return tot_initialized_ == 0; + case ValueType::GVVectorArray: + return this->value_as<VariableValue_GVVectorArray>()->data.is_single_vector(); + case ValueType::GVectorArray: + return tot_initialized_ == 0; + case ValueType::OneSingle: + return true; + case ValueType::OneVector: + return true; + } + BLI_assert_unreachable(); + return false; + } + + bool is_fully_initialized(const IndexMask full_mask) + { + return tot_initialized_ == full_mask.size(); + } + + bool is_fully_uninitialized(const IndexMask full_mask) + { + UNUSED_VARS(full_mask); + return tot_initialized_ == 0; + } + + void add_as_input(MFParamsBuilder ¶ms, IndexMask mask, const MFDataType &data_type) const + { + /* Sanity check to make sure that enough values are initialized. */ + BLI_assert(mask.size() <= tot_initialized_); + + switch (value_->type) { + case ValueType::GVArray: { + params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data); + break; + } + case ValueType::Span: { + const void *data = this->value_as<VariableValue_Span>()->data; + const GSpan span{data_type.single_type(), data, mask.min_array_size()}; + params.add_readonly_single_input(span); + break; + } + case ValueType::GVVectorArray: { + params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data); + break; + } + case ValueType::GVectorArray: { + params.add_readonly_vector_input(this->value_as<VariableValue_GVectorArray>()->data); + break; + } + case ValueType::OneSingle: { + const auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + const GPointer gpointer{data_type.single_type(), value_typed->data}; + params.add_readonly_single_input(gpointer); + break; + } + case ValueType::OneVector: { + params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data[0]); + break; + } + } + } + + void ensure_is_mutable(IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + if (ELEM(value_->type, ValueType::Span, ValueType::GVectorArray)) { + return; + } + + const int array_size = full_mask.min_array_size(); + + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &type = data_type.single_type(); + VariableValue_Span *new_value = nullptr; + if (caller_provided_storage_ == nullptr) { + new_value = value_allocator.obtain_Span(type, array_size); + } + else { + /* Reuse the storage provided caller when possible. */ + new_value = value_allocator.obtain_Span_not_owned(caller_provided_storage_); + } + if (value_->type == ValueType::GVArray) { + /* Fill new buffer with data from virtual array. */ + this->value_as<VariableValue_GVArray>()->data.materialize_to_uninitialized( + full_mask, new_value->data); + } + else if (value_->type == ValueType::OneSingle) { + auto *old_value_typed_ = this->value_as<VariableValue_OneSingle>(); + if (old_value_typed_->is_initialized) { + /* Fill the buffer with a single value. */ + type.fill_construct_indices(old_value_typed_->data, new_value->data, full_mask); + } + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + case MFDataType::Vector: { + const CPPType &type = data_type.vector_base_type(); + VariableValue_GVectorArray *new_value = nullptr; + if (caller_provided_storage_ == nullptr) { + new_value = value_allocator.obtain_GVectorArray(type, array_size); + } + else { + new_value = value_allocator.obtain_GVectorArray_not_owned( + *(GVectorArray *)caller_provided_storage_); + } + if (value_->type == ValueType::GVVectorArray) { + /* Fill new vector array with data from virtual vector array. */ + new_value->data.extend(full_mask, this->value_as<VariableValue_GVVectorArray>()->data); + } + else if (value_->type == ValueType::OneVector) { + /* Fill all indices with the same value. */ + const GSpan vector = this->value_as<VariableValue_OneVector>()->data[0]; + new_value->data.extend(full_mask, GVVectorArray_For_SingleGSpan{vector, array_size}); + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + } + } + + void add_as_mutable(MFParamsBuilder ¶ms, + IndexMask mask, + IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + /* Sanity check to make sure that enough values are initialized. */ + BLI_assert(mask.size() <= tot_initialized_); + + this->ensure_is_mutable(full_mask, data_type, value_allocator); + + switch (value_->type) { + case ValueType::Span: { + void *data = this->value_as<VariableValue_Span>()->data; + const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()}; + params.add_single_mutable(span); + break; + } + case ValueType::GVectorArray: { + params.add_vector_mutable(this->value_as<VariableValue_GVectorArray>()->data); + break; + } + case ValueType::GVArray: + case ValueType::GVVectorArray: + case ValueType::OneSingle: + case ValueType::OneVector: { + BLI_assert_unreachable(); + break; + } + } + } + + void add_as_output(MFParamsBuilder ¶ms, + IndexMask mask, + IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + /* Sanity check to make sure that enough values are not initialized. */ + BLI_assert(mask.size() <= full_mask.size() - tot_initialized_); + this->ensure_is_mutable(full_mask, data_type, value_allocator); + + switch (value_->type) { + case ValueType::Span: { + void *data = this->value_as<VariableValue_Span>()->data; + const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()}; + params.add_uninitialized_single_output(span); + break; + } + case ValueType::GVectorArray: { + params.add_vector_output(this->value_as<VariableValue_GVectorArray>()->data); + break; + } + case ValueType::GVArray: + case ValueType::GVVectorArray: + case ValueType::OneSingle: + case ValueType::OneVector: { + BLI_assert_unreachable(); + break; + } + } + + tot_initialized_ += mask.size(); + } + + void add_as_input__one(MFParamsBuilder ¶ms, const MFDataType &data_type) const + { + BLI_assert(this->is_one()); + + switch (value_->type) { + case ValueType::GVArray: { + params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data); + break; + } + case ValueType::GVVectorArray: { + params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data); + break; + } + case ValueType::OneSingle: { + const auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + GPointer ptr{data_type.single_type(), value_typed->data}; + params.add_readonly_single_input(ptr); + break; + } + case ValueType::OneVector: { + params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data); + break; + } + case ValueType::Span: + case ValueType::GVectorArray: { + BLI_assert_unreachable(); + break; + } + } + } + + void ensure_is_mutable__one(const MFDataType &data_type, ValueAllocator &value_allocator) + { + BLI_assert(this->is_one()); + if (ELEM(value_->type, ValueType::OneSingle, ValueType::OneVector)) { + return; + } + + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &type = data_type.single_type(); + VariableValue_OneSingle *new_value = value_allocator.obtain_OneSingle(type); + if (value_->type == ValueType::GVArray) { + this->value_as<VariableValue_GVArray>()->data.get_internal_single_to_uninitialized( + new_value->data); + new_value->is_initialized = true; + } + else if (value_->type == ValueType::Span) { + BLI_assert(tot_initialized_ == 0); + /* Nothing to do, the single value is uninitialized already. */ + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + case MFDataType::Vector: { + const CPPType &type = data_type.vector_base_type(); + VariableValue_OneVector *new_value = value_allocator.obtain_OneVector(type); + if (value_->type == ValueType::GVVectorArray) { + const GVVectorArray &old_vector_array = + this->value_as<VariableValue_GVVectorArray>()->data; + new_value->data.extend(IndexRange(1), old_vector_array); + } + else if (value_->type == ValueType::GVectorArray) { + BLI_assert(tot_initialized_ == 0); + /* Nothing to do. */ + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + } + } + + void add_as_mutable__one(MFParamsBuilder ¶ms, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + BLI_assert(this->is_one()); + this->ensure_is_mutable__one(data_type, value_allocator); + + switch (value_->type) { + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + params.add_single_mutable(GMutableSpan{data_type.single_type(), value_typed->data, 1}); + break; + } + case ValueType::OneVector: { + params.add_vector_mutable(this->value_as<VariableValue_OneVector>()->data); + break; + } + case ValueType::GVArray: + case ValueType::Span: + case ValueType::GVVectorArray: + case ValueType::GVectorArray: { + BLI_assert_unreachable(); + break; + } + } + } + + void add_as_output__one(MFParamsBuilder ¶ms, + IndexMask mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + BLI_assert(this->is_one()); + this->ensure_is_mutable__one(data_type, value_allocator); + + switch (value_->type) { + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(!value_typed->is_initialized); + params.add_uninitialized_single_output( + GMutableSpan{data_type.single_type(), value_typed->data, 1}); + /* It becomes initialized when the multi-function is called. */ + value_typed->is_initialized = true; + break; + } + case ValueType::OneVector: { + auto *value_typed = this->value_as<VariableValue_OneVector>(); + BLI_assert(value_typed->data[0].is_empty()); + params.add_vector_output(value_typed->data); + break; + } + case ValueType::GVArray: + case ValueType::Span: + case ValueType::GVVectorArray: + case ValueType::GVectorArray: { + BLI_assert_unreachable(); + break; + } + } + + tot_initialized_ += mask.size(); + } + + void destruct(IndexMask mask, + IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + int new_tot_initialized = tot_initialized_ - mask.size(); + + /* Sanity check to make sure that enough indices can be destructed. */ + BLI_assert(new_tot_initialized >= 0); + + switch (value_->type) { + case ValueType::GVArray: { + if (mask.size() == full_mask.size()) { + /* All elements are destructed. The elements are owned by the caller, so we don't + * actually destruct them. */ + value_allocator.release_value(value_, data_type); + value_ = value_allocator.obtain_OneSingle(data_type.single_type()); + } + else { + /* Not all elements are destructed. Since we can't work on the original array, we have to + * create a copy first. */ + this->ensure_is_mutable(full_mask, data_type, value_allocator); + BLI_assert(value_->type == ValueType::Span); + const CPPType &type = data_type.single_type(); + type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask); + } + break; + } + case ValueType::Span: { + const CPPType &type = data_type.single_type(); + type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask); + if (new_tot_initialized == 0) { + /* Release span when all values are initialized. */ + value_allocator.release_value(value_, data_type); + value_ = value_allocator.obtain_OneSingle(data_type.single_type()); + } + break; + } + case ValueType::GVVectorArray: { + if (mask.size() == full_mask.size()) { + /* All elements are cleared. The elements are owned by the caller, so don't actually + * destruct them. */ + value_allocator.release_value(value_, data_type); + value_ = value_allocator.obtain_OneVector(data_type.vector_base_type()); + } + else { + /* Not all elements are cleared. Since we can't work on the original vector array, we + * have to create a copy first. A possible future optimization is to create the partial + * copy directly. */ + this->ensure_is_mutable(full_mask, data_type, value_allocator); + BLI_assert(value_->type == ValueType::GVectorArray); + this->value_as<VariableValue_GVectorArray>()->data.clear(mask); + } + break; + } + case ValueType::GVectorArray: { + this->value_as<VariableValue_GVectorArray>()->data.clear(mask); + break; + } + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + if (mask.size() == tot_initialized_) { + const CPPType &type = data_type.single_type(); + type.destruct(value_typed->data); + value_typed->is_initialized = false; + } + break; + } + case ValueType::OneVector: { + auto *value_typed = this->value_as<VariableValue_OneVector>(); + if (mask.size() == tot_initialized_) { + value_typed->data.clear({0}); + } + break; + } + } + + tot_initialized_ = new_tot_initialized; + } + + void indices_split(IndexMask mask, IndicesSplitVectors &r_indices) + { + BLI_assert(mask.size() <= tot_initialized_); + + switch (value_->type) { + case ValueType::GVArray: { + const GVArray_Typed<bool> varray{this->value_as<VariableValue_GVArray>()->data}; + for (const int i : mask) { + r_indices[varray[i]].append(i); + } + break; + } + case ValueType::Span: { + const Span<bool> span((bool *)this->value_as<VariableValue_Span>()->data, + mask.min_array_size()); + for (const int i : mask) { + r_indices[span[i]].append(i); + } + break; + } + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + const bool condition = *(bool *)value_typed->data; + r_indices[condition].extend(mask); + break; + } + case ValueType::GVVectorArray: + case ValueType::GVectorArray: + case ValueType::OneVector: { + BLI_assert_unreachable(); + break; + } + } + } + + template<typename T> T *value_as() + { + BLI_assert(value_->type == T::static_type); + return static_cast<T *>(value_); + } + + template<typename T> const T *value_as() const + { + BLI_assert(value_->type == T::static_type); + return static_cast<T *>(value_); + } +}; + +template<typename... Args> VariableState *ValueAllocator::obtain_variable_state(Args &&...args) +{ + return new VariableState(std::forward<Args>(args)...); +} + +void ValueAllocator::release_variable_state(VariableState *state) +{ + delete state; +} + +/** Keeps track of the states of all variables during evaluation. */ +class VariableStates { + private: + ValueAllocator value_allocator_; + Map<const MFVariable *, VariableState *> variable_states_; + IndexMask full_mask_; + + public: + VariableStates(IndexMask full_mask) : full_mask_(full_mask) + { + } + + ~VariableStates() + { + for (auto &&item : variable_states_.items()) { + const MFVariable *variable = item.key; + VariableState *state = item.value; + state->destruct_self(value_allocator_, variable->data_type()); + } + } + + ValueAllocator &value_allocator() + { + return value_allocator_; + } + + const IndexMask &full_mask() const + { + return full_mask_; + } + + void add_initial_variable_states(const MFProcedureExecutor &fn, + const MFProcedure &procedure, + MFParams ¶ms) + { + for (const int param_index : fn.param_indices()) { + MFParamType param_type = fn.param_type(param_index); + const MFVariable *variable = procedure.params()[param_index].variable; + + auto add_state = [&](VariableValue *value, + bool input_is_initialized, + void *caller_provided_storage = nullptr) { + const int tot_initialized = input_is_initialized ? full_mask_.size() : 0; + variable_states_.add_new(variable, + value_allocator_.obtain_variable_state( + *value, tot_initialized, caller_provided_storage)); + }; + + switch (param_type.category()) { + case MFParamType::SingleInput: { + const GVArray &data = params.readonly_single_input(param_index); + add_state(value_allocator_.obtain_GVArray(data), true); + break; + } + case MFParamType::VectorInput: { + const GVVectorArray &data = params.readonly_vector_input(param_index); + add_state(value_allocator_.obtain_GVVectorArray(data), true); + break; + } + case MFParamType::SingleOutput: { + GMutableSpan data = params.uninitialized_single_output(param_index); + add_state(value_allocator_.obtain_Span_not_owned(data.data()), false, data.data()); + break; + } + case MFParamType::VectorOutput: { + GVectorArray &data = params.vector_output(param_index); + add_state(value_allocator_.obtain_GVectorArray_not_owned(data), false, &data); + break; + } + case MFParamType::SingleMutable: { + GMutableSpan data = params.single_mutable(param_index); + add_state(value_allocator_.obtain_Span_not_owned(data.data()), true, data.data()); + break; + } + case MFParamType::VectorMutable: { + GVectorArray &data = params.vector_mutable(param_index); + add_state(value_allocator_.obtain_GVectorArray_not_owned(data), true, &data); + break; + } + } + } + } + + void add_as_param(VariableState &variable_state, + MFParamsBuilder ¶ms, + const MFParamType ¶m_type, + const IndexMask &mask) + { + const MFDataType data_type = param_type.data_type(); + switch (param_type.interface_type()) { + case MFParamType::Input: { + variable_state.add_as_input(params, mask, data_type); + break; + } + case MFParamType::Mutable: { + variable_state.add_as_mutable(params, mask, full_mask_, data_type, value_allocator_); + break; + } + case MFParamType::Output: { + variable_state.add_as_output(params, mask, full_mask_, data_type, value_allocator_); + break; + } + } + } + + void add_as_param__one(VariableState &variable_state, + MFParamsBuilder ¶ms, + const MFParamType ¶m_type, + const IndexMask &mask) + { + const MFDataType data_type = param_type.data_type(); + switch (param_type.interface_type()) { + case MFParamType::Input: { + variable_state.add_as_input__one(params, data_type); + break; + } + case MFParamType::Mutable: { + variable_state.add_as_mutable__one(params, data_type, value_allocator_); + break; + } + case MFParamType::Output: { + variable_state.add_as_output__one(params, mask, data_type, value_allocator_); + break; + } + } + } + + void destruct(const MFVariable &variable, const IndexMask &mask) + { + VariableState &variable_state = this->get_variable_state(variable); + variable_state.destruct(mask, full_mask_, variable.data_type(), value_allocator_); + } + + VariableState &get_variable_state(const MFVariable &variable) + { + return *variable_states_.lookup_or_add_cb( + &variable, [&]() { return this->create_new_state_for_variable(variable); }); + } + + VariableState *create_new_state_for_variable(const MFVariable &variable) + { + MFDataType data_type = variable.data_type(); + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &type = data_type.single_type(); + return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneSingle(type), 0); + } + case MFDataType::Vector: { + const CPPType &type = data_type.vector_base_type(); + return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneVector(type), 0); + } + } + BLI_assert_unreachable(); + return nullptr; + } +}; + +static bool evaluate_as_one(const MultiFunction &fn, + Span<VariableState *> param_variable_states, + const IndexMask &mask, + const IndexMask &full_mask) +{ + if (fn.depends_on_context()) { + return false; + } + if (mask.size() < full_mask.size()) { + return false; + } + for (VariableState *state : param_variable_states) { + if (state != nullptr && !state->is_one()) { + return false; + } + } + return true; +} + +static void execute_call_instruction(const MFCallInstruction &instruction, + IndexMask mask, + VariableStates &variable_states, + const MFContext &context) +{ + const MultiFunction &fn = instruction.fn(); + + Vector<VariableState *> param_variable_states; + param_variable_states.resize(fn.param_amount()); + + for (const int param_index : fn.param_indices()) { + const MFVariable *variable = instruction.params()[param_index]; + if (variable == nullptr) { + param_variable_states[param_index] = nullptr; + } + else { + VariableState &variable_state = variable_states.get_variable_state(*variable); + param_variable_states[param_index] = &variable_state; + } + } + + /* If all inputs to the function are constant, it's enough to call the function only once instead + * of for every index. */ + if (evaluate_as_one(fn, param_variable_states, mask, variable_states.full_mask())) { + MFParamsBuilder params(fn, 1); + + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + VariableState *variable_state = param_variable_states[param_index]; + if (variable_state == nullptr) { + params.add_ignored_single_output(); + } + else { + variable_states.add_as_param__one(*variable_state, params, param_type, mask); + } + } + + fn.call(IndexRange(1), params, context); + } + else { + MFParamsBuilder params(fn, &mask); + + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + VariableState *variable_state = param_variable_states[param_index]; + if (variable_state == nullptr) { + params.add_ignored_single_output(); + } + else { + variable_states.add_as_param(*variable_state, params, param_type, mask); + } + } + + fn.call(mask, params, context); + } +} + +/** An index mask, that might own the indices if necessary. */ +struct InstructionIndices { + bool is_owned; + Vector<int64_t> owned_indices; + IndexMask referenced_indices; + + IndexMask mask() const + { + if (this->is_owned) { + return this->owned_indices.as_span(); + } + return this->referenced_indices; + } +}; + +/** Contains information about the next instruction that should be executed. */ +struct NextInstructionInfo { + const MFInstruction *instruction = nullptr; + InstructionIndices indices; + + IndexMask mask() const + { + return this->indices.mask(); + } + + operator bool() const + { + return this->instruction != nullptr; + } +}; + +/** + * Keeps track of the next instruction for all indices and decides in which order instructions are + * evaluated. + */ +class InstructionScheduler { + private: + Map<const MFInstruction *, Vector<InstructionIndices>> indices_by_instruction_; + + public: + InstructionScheduler() = default; + + void add_referenced_indices(const MFInstruction &instruction, IndexMask mask) + { + if (mask.is_empty()) { + return; + } + InstructionIndices new_indices; + new_indices.is_owned = false; + new_indices.referenced_indices = mask; + indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices)); + } + + void add_owned_indices(const MFInstruction &instruction, Vector<int64_t> indices) + { + if (indices.is_empty()) { + return; + } + BLI_assert(IndexMask::indices_are_valid_index_mask(indices)); + + InstructionIndices new_indices; + new_indices.is_owned = true; + new_indices.owned_indices = std::move(indices); + indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices)); + } + + void add_previous_instruction_indices(const MFInstruction &instruction, + NextInstructionInfo &instr_info) + { + indices_by_instruction_.lookup_or_add_default(&instruction) + .append(std::move(instr_info.indices)); + } + + NextInstructionInfo pop_next() + { + if (indices_by_instruction_.is_empty()) { + return {}; + } + /* TODO: Implement better mechanism to determine next instruction. */ + const MFInstruction *instruction = *indices_by_instruction_.keys().begin(); + + NextInstructionInfo next_instruction_info; + next_instruction_info.instruction = instruction; + next_instruction_info.indices = this->pop_indices_array(instruction); + return next_instruction_info; + } + + private: + InstructionIndices pop_indices_array(const MFInstruction *instruction) + { + Vector<InstructionIndices> *indices = indices_by_instruction_.lookup_ptr(instruction); + if (indices == nullptr) { + return {}; + } + InstructionIndices r_indices = (*indices).pop_last(); + BLI_assert(!r_indices.mask().is_empty()); + if (indices->is_empty()) { + indices_by_instruction_.remove_contained(instruction); + } + return r_indices; + } +}; + +void MFProcedureExecutor::call(IndexMask full_mask, MFParams params, MFContext context) const +{ + BLI_assert(procedure_.validate()); + + LinearAllocator<> allocator; + + VariableStates variable_states{full_mask}; + variable_states.add_initial_variable_states(*this, procedure_, params); + + InstructionScheduler scheduler; + scheduler.add_referenced_indices(*procedure_.entry(), full_mask); + + /* Loop until all indices got to a return instruction. */ + while (NextInstructionInfo instr_info = scheduler.pop_next()) { + const MFInstruction &instruction = *instr_info.instruction; + switch (instruction.type()) { + case MFInstructionType::Call: { + const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>( + instruction); + execute_call_instruction(call_instruction, instr_info.mask(), variable_states, context); + scheduler.add_previous_instruction_indices(*call_instruction.next(), instr_info); + break; + } + case MFInstructionType::Branch: { + const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>( + instruction); + const MFVariable *condition_var = branch_instruction.condition(); + VariableState &variable_state = variable_states.get_variable_state(*condition_var); + + IndicesSplitVectors new_indices; + variable_state.indices_split(instr_info.mask(), new_indices); + scheduler.add_owned_indices(*branch_instruction.branch_false(), new_indices[false]); + scheduler.add_owned_indices(*branch_instruction.branch_true(), new_indices[true]); + break; + } + case MFInstructionType::Destruct: { + const MFDestructInstruction &destruct_instruction = + static_cast<const MFDestructInstruction &>(instruction); + const MFVariable *variable = destruct_instruction.variable(); + variable_states.destruct(*variable, instr_info.mask()); + scheduler.add_previous_instruction_indices(*destruct_instruction.next(), instr_info); + break; + } + case MFInstructionType::Dummy: { + const MFDummyInstruction &dummy_instruction = static_cast<const MFDummyInstruction &>( + instruction); + scheduler.add_previous_instruction_indices(*dummy_instruction.next(), instr_info); + break; + } + case MFInstructionType::Return: { + /* Don't insert the indices back into the scheduler. */ + break; + } + } + } + + for (const int param_index : this->param_indices()) { + const MFParamType param_type = this->param_type(param_index); + const MFVariable *variable = procedure_.params()[param_index].variable; + VariableState &variable_state = variable_states.get_variable_state(*variable); + switch (param_type.interface_type()) { + case MFParamType::Input: { + /* Input variables must be destructed in the end. */ + BLI_assert(variable_state.is_fully_uninitialized(full_mask)); + break; + } + case MFParamType::Mutable: + case MFParamType::Output: { + /* Mutable and output variables must be initialized in the end. */ + BLI_assert(variable_state.is_fully_initialized(full_mask)); + /* Make sure that the data is in the memory provided by the caller. */ + variable_state.ensure_is_mutable( + full_mask, param_type.data_type(), variable_states.value_allocator()); + break; + } + } + } +} + +} // namespace blender::fn diff --git a/source/blender/functions/tests/FN_field_test.cc b/source/blender/functions/tests/FN_field_test.cc new file mode 100644 index 00000000000..041cdbd0f00 --- /dev/null +++ b/source/blender/functions/tests/FN_field_test.cc @@ -0,0 +1,294 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "FN_cpp_type.hh" +#include "FN_field.hh" +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_test_common.hh" + +namespace blender::fn::tests { + +TEST(field, ConstantFunction) +{ + /* TODO: Figure out how to not use another "FieldOperation(" inside of std::make_shared. */ + GField constant_field{std::make_shared<FieldOperation>( + FieldOperation(std::make_unique<CustomMF_Constant<int>>(10), {})), + 0}; + + Array<int> result(4); + + FieldContext context; + FieldEvaluator evaluator{context, 4}; + evaluator.add_with_destination(constant_field, result.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result[0], 10); + EXPECT_EQ(result[1], 10); + EXPECT_EQ(result[2], 10); + EXPECT_EQ(result[3], 10); +} + +class IndexFieldInput final : public FieldInput { + public: + IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") + { + } + + const GVArray *get_varray_for_context(const FieldContext &UNUSED(context), + IndexMask mask, + ResourceScope &scope) const final + { + auto index_func = [](int i) { return i; }; + return &scope.construct< + GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>( + mask.min_array_size(), mask.min_array_size(), index_func); + } +}; + +TEST(field, VArrayInput) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + Array<int> result_1(4); + + FieldContext context; + FieldEvaluator evaluator{context, 4}; + evaluator.add_with_destination(index_field, result_1.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result_1[0], 0); + EXPECT_EQ(result_1[1], 1); + EXPECT_EQ(result_1[2], 2); + EXPECT_EQ(result_1[3], 3); + + /* Evaluate a second time, just to test that the first didn't break anything. */ + Array<int> result_2(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldEvaluator evaluator_2{context, &mask}; + evaluator_2.add_with_destination(index_field, result_2.as_mutable_span()); + evaluator_2.evaluate(); + EXPECT_EQ(result_2[2], 2); + EXPECT_EQ(result_2[4], 4); + EXPECT_EQ(result_2[6], 6); + EXPECT_EQ(result_2[8], 8); +} + +TEST(field, VArrayInputMultipleOutputs) +{ + std::shared_ptr<FieldInput> index_input = std::make_shared<IndexFieldInput>(); + GField field_1{index_input}; + GField field_2{index_input}; + + Array<int> result_1(10); + Array<int> result_2(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(field_1, result_1.as_mutable_span()); + evaluator.add_with_destination(field_2, result_2.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result_1[2], 2); + EXPECT_EQ(result_1[4], 4); + EXPECT_EQ(result_1[6], 6); + EXPECT_EQ(result_1[8], 8); + EXPECT_EQ(result_2[2], 2); + EXPECT_EQ(result_2[4], 4); + EXPECT_EQ(result_2[6], 6); + EXPECT_EQ(result_2[8], 8); +} + +TEST(field, InputAndFunction) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>( + "add", [](int a, int b) { return a + b; }); + GField output_field{std::make_shared<FieldOperation>( + FieldOperation(std::move(add_fn), {index_field, index_field})), + 0}; + + Array<int> result(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(output_field, result.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result[2], 4); + EXPECT_EQ(result[4], 8); + EXPECT_EQ(result[6], 12); + EXPECT_EQ(result[8], 16); +} + +TEST(field, TwoFunctions) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>( + "add", [](int a, int b) { return a + b; }); + GField add_field{std::make_shared<FieldOperation>( + FieldOperation(std::move(add_fn), {index_field, index_field})), + 0}; + + std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>( + "add_10", [](int a) { return a + 10; }); + GField result_field{ + std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {add_field})), 0}; + + Array<int> result(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(result_field, result.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result[2], 14); + EXPECT_EQ(result[4], 18); + EXPECT_EQ(result[6], 22); + EXPECT_EQ(result[8], 26); +} + +class TwoOutputFunction : public MultiFunction { + private: + MFSignature signature_; + + public: + TwoOutputFunction(StringRef name) + { + MFSignatureBuilder signature{name}; + signature.single_input<int>("In1"); + signature.single_input<int>("In2"); + signature.single_output<int>("Add"); + signature.single_output<int>("Add10"); + signature_ = signature.build(); + this->set_signature(&signature_); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + const VArray<int> &in1 = params.readonly_single_input<int>(0, "In1"); + const VArray<int> &in2 = params.readonly_single_input<int>(1, "In2"); + MutableSpan<int> add = params.uninitialized_single_output<int>(2, "Add"); + MutableSpan<int> add_10 = params.uninitialized_single_output<int>(3, "Add10"); + mask.foreach_index([&](const int64_t i) { + add[i] = in1[i] + in2[i]; + add_10[i] = add[i] + 10; + }); + } +}; + +TEST(field, FunctionTwoOutputs) +{ + /* Also use two separate input fields, why not. */ + GField index_field_1{std::make_shared<IndexFieldInput>()}; + GField index_field_2{std::make_shared<IndexFieldInput>()}; + + std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation( + std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field_1, index_field_2})); + + GField result_field_1{fn, 0}; + GField result_field_2{fn, 1}; + + Array<int> result_1(10); + Array<int> result_2(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(result_field_1, result_1.as_mutable_span()); + evaluator.add_with_destination(result_field_2, result_2.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result_1[2], 4); + EXPECT_EQ(result_1[4], 8); + EXPECT_EQ(result_1[6], 12); + EXPECT_EQ(result_1[8], 16); + EXPECT_EQ(result_2[2], 14); + EXPECT_EQ(result_2[4], 18); + EXPECT_EQ(result_2[6], 22); + EXPECT_EQ(result_2[8], 26); +} + +TEST(field, TwoFunctionsTwoOutputs) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation( + std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field, index_field})); + + Array<int64_t> mask_indices = {2, 4, 6, 8}; + IndexMask mask = mask_indices.as_span(); + + Field<int> result_field_1{fn, 0}; + Field<int> intermediate_field{fn, 1}; + + std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>( + "add_10", [](int a) { return a + 10; }); + Field<int> result_field_2{ + std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {intermediate_field})), + 0}; + + FieldContext field_context; + FieldEvaluator field_evaluator{field_context, &mask}; + const VArray<int> *result_1 = nullptr; + const VArray<int> *result_2 = nullptr; + field_evaluator.add(result_field_1, &result_1); + field_evaluator.add(result_field_2, &result_2); + field_evaluator.evaluate(); + + EXPECT_EQ(result_1->get(2), 4); + EXPECT_EQ(result_1->get(4), 8); + EXPECT_EQ(result_1->get(6), 12); + EXPECT_EQ(result_1->get(8), 16); + EXPECT_EQ(result_2->get(2), 24); + EXPECT_EQ(result_2->get(4), 28); + EXPECT_EQ(result_2->get(6), 32); + EXPECT_EQ(result_2->get(8), 36); +} + +TEST(field, SameFieldTwice) +{ + GField constant_field{ + std::make_shared<FieldOperation>(std::make_unique<CustomMF_Constant<int>>(10)), 0}; + + FieldContext field_context; + IndexMask mask{IndexRange(2)}; + ResourceScope scope; + Vector<const GVArray *> results = evaluate_fields( + scope, {constant_field, constant_field}, mask, field_context); + + GVArray_Typed<int> varray1{*results[0]}; + GVArray_Typed<int> varray2{*results[1]}; + + EXPECT_EQ(varray1->get(0), 10); + EXPECT_EQ(varray1->get(1), 10); + EXPECT_EQ(varray2->get(0), 10); + EXPECT_EQ(varray2->get(1), 10); +} + +TEST(field, IgnoredOutput) +{ + static OptionalOutputsFunction fn; + Field<int> field{std::make_shared<FieldOperation>(fn), 0}; + + FieldContext field_context; + FieldEvaluator field_evaluator{field_context, 10}; + const VArray<int> *results = nullptr; + field_evaluator.add(field, &results); + field_evaluator.evaluate(); + + EXPECT_EQ(results->get(0), 5); + EXPECT_EQ(results->get(3), 5); +} + +} // namespace blender::fn::tests diff --git a/source/blender/functions/tests/FN_multi_function_procedure_test.cc b/source/blender/functions/tests/FN_multi_function_procedure_test.cc new file mode 100644 index 00000000000..0b4b88fba3d --- /dev/null +++ b/source/blender/functions/tests/FN_multi_function_procedure_test.cc @@ -0,0 +1,344 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_procedure_builder.hh" +#include "FN_multi_function_procedure_executor.hh" +#include "FN_multi_function_test_common.hh" + +namespace blender::fn::tests { + +TEST(multi_function_procedure, SimpleTest) +{ + /** + * procedure(int var1, int var2, int *var4) { + * int var3 = var1 + var2; + * var4 = var2 + var3; + * var4 += 10; + * } + */ + + CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }}; + CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var1 = &builder.add_single_input_parameter<int>(); + MFVariable *var2 = &builder.add_single_input_parameter<int>(); + auto [var3] = builder.add_call<1>(add_fn, {var1, var2}); + auto [var4] = builder.add_call<1>(add_fn, {var2, var3}); + builder.add_call(add_10_fn, {var4}); + builder.add_destruct({var1, var2, var3}); + builder.add_return(); + builder.add_output_parameter(*var4); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor executor{"My Procedure", procedure}; + + MFParamsBuilder params{executor, 3}; + MFContextBuilder context; + + Array<int> input_array = {1, 2, 3}; + params.add_readonly_single_input(input_array.as_span()); + params.add_readonly_single_input_value(3); + + Array<int> output_array(3); + params.add_uninitialized_single_output(output_array.as_mutable_span()); + + executor.call(IndexRange(3), params, context); + + EXPECT_EQ(output_array[0], 17); + EXPECT_EQ(output_array[1], 18); + EXPECT_EQ(output_array[2], 19); +} + +TEST(multi_function_procedure, BranchTest) +{ + /** + * procedure(int &var1, bool var2) { + * if (var2) { + * var1 += 100; + * } + * else { + * var1 += 10; + * } + * var1 += 10; + * } + */ + + CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }}; + CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var1 = &builder.add_single_mutable_parameter<int>(); + MFVariable *var2 = &builder.add_single_input_parameter<bool>(); + + MFProcedureBuilder::Branch branch = builder.add_branch(*var2); + branch.branch_false.add_call(add_10_fn, {var1}); + branch.branch_true.add_call(add_100_fn, {var1}); + builder.set_cursor_after_branch(branch); + builder.add_call(add_10_fn, {var1}); + builder.add_destruct({var2}); + builder.add_return(); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Condition Test", procedure}; + MFParamsBuilder params(procedure_fn, 5); + + Array<int> values_a = {1, 5, 3, 6, 2}; + Array<bool> values_cond = {true, false, true, true, false}; + + params.add_single_mutable(values_a.as_mutable_span()); + params.add_readonly_single_input(values_cond.as_span()); + + MFContextBuilder context; + procedure_fn.call({1, 2, 3, 4}, params, context); + + EXPECT_EQ(values_a[0], 1); + EXPECT_EQ(values_a[1], 25); + EXPECT_EQ(values_a[2], 113); + EXPECT_EQ(values_a[3], 116); + EXPECT_EQ(values_a[4], 22); +} + +TEST(multi_function_procedure, EvaluateOne) +{ + /** + * procedure(int var1, int *var2) { + * var2 = var1 + 10; + * } + */ + + int tot_evaluations = 0; + CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) { + tot_evaluations++; + return a + 10; + }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var1 = &builder.add_single_input_parameter<int>(); + auto [var2] = builder.add_call<1>(add_10_fn, {var1}); + builder.add_destruct(*var1); + builder.add_return(); + builder.add_output_parameter(*var2); + + MFProcedureExecutor procedure_fn{"Evaluate One", procedure}; + MFParamsBuilder params{procedure_fn, 5}; + + Array<int> values_out = {1, 2, 3, 4, 5}; + params.add_readonly_single_input_value(1); + params.add_uninitialized_single_output(values_out.as_mutable_span()); + + MFContextBuilder context; + procedure_fn.call({0, 1, 3, 4}, params, context); + + EXPECT_EQ(values_out[0], 11); + EXPECT_EQ(values_out[1], 11); + EXPECT_EQ(values_out[2], 3); + EXPECT_EQ(values_out[3], 11); + EXPECT_EQ(values_out[4], 11); + /* We expect only one evaluation, because the input is constant. */ + EXPECT_EQ(tot_evaluations, 1); +} + +TEST(multi_function_procedure, SimpleLoop) +{ + /** + * procedure(int count, int *out) { + * out = 1; + * int index = 0' + * loop { + * if (index >= count) { + * break; + * } + * out *= 2; + * index += 1; + * } + * out += 1000; + * } + */ + + CustomMF_Constant<int> const_1_fn{1}; + CustomMF_Constant<int> const_0_fn{0}; + CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal", + [](int a, int b) { return a >= b; }}; + CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }}; + CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }}; + CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var_count = &builder.add_single_input_parameter<int>("count"); + auto [var_out] = builder.add_call<1>(const_1_fn); + var_out->set_name("out"); + auto [var_index] = builder.add_call<1>(const_0_fn); + var_index->set_name("index"); + + MFProcedureBuilder::Loop loop = builder.add_loop(); + auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count}); + var_condition->set_name("condition"); + MFProcedureBuilder::Branch branch = builder.add_branch(*var_condition); + branch.branch_true.add_destruct(*var_condition); + branch.branch_true.add_loop_break(loop); + branch.branch_false.add_destruct(*var_condition); + builder.set_cursor_after_branch(branch); + builder.add_call(double_fn, {var_out}); + builder.add_call(add_1_fn, {var_index}); + builder.add_loop_continue(loop); + builder.set_cursor_after_loop(loop); + builder.add_call(add_1000_fn, {var_out}); + builder.add_destruct({var_count, var_index}); + builder.add_return(); + builder.add_output_parameter(*var_out); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Simple Loop", procedure}; + MFParamsBuilder params{procedure_fn, 5}; + + Array<int> counts = {4, 3, 7, 6, 4}; + Array<int> results(5, -1); + + params.add_readonly_single_input(counts.as_span()); + params.add_uninitialized_single_output(results.as_mutable_span()); + + MFContextBuilder context; + procedure_fn.call({0, 1, 3, 4}, params, context); + + EXPECT_EQ(results[0], 1016); + EXPECT_EQ(results[1], 1008); + EXPECT_EQ(results[2], -1); + EXPECT_EQ(results[3], 1064); + EXPECT_EQ(results[4], 1016); +} + +TEST(multi_function_procedure, Vectors) +{ + /** + * procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) { + * v1.extend(v2); + * int constant = 5; + * v2.append(constant); + * v2.extend(v1); + * int len = sum(v2); + * v3 = range(len); + * } + */ + + CreateRangeFunction create_range_fn; + ConcatVectorsFunction extend_fn; + GenericAppendFunction append_fn{CPPType::get<int>()}; + SumVectorFunction sum_elements_fn; + CustomMF_Constant<int> constant_5_fn{5}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>()); + MFVariable *var_v2 = &builder.add_parameter(MFParamType::ForMutableVector(CPPType::get<int>())); + builder.add_call(extend_fn, {var_v1, var_v2}); + auto [var_constant] = builder.add_call<1>(constant_5_fn); + builder.add_call(append_fn, {var_v2, var_constant}); + builder.add_destruct(*var_constant); + builder.add_call(extend_fn, {var_v2, var_v1}); + auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2}); + auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len}); + builder.add_destruct({var_v1, var_len}); + builder.add_return(); + builder.add_output_parameter(*var_v3); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Vectors", procedure}; + MFParamsBuilder params{procedure_fn, 5}; + + Array<int> v1 = {5, 2, 3}; + GVectorArray v2{CPPType::get<int>(), 5}; + GVectorArray v3{CPPType::get<int>(), 5}; + + int value_10 = 10; + v2.append(0, &value_10); + v2.append(4, &value_10); + + params.add_readonly_vector_input(v1.as_span()); + params.add_vector_mutable(v2); + params.add_vector_output(v3); + + MFContextBuilder context; + procedure_fn.call({0, 1, 3, 4}, params, context); + + EXPECT_EQ(v2[0].size(), 6); + EXPECT_EQ(v2[1].size(), 4); + EXPECT_EQ(v2[2].size(), 0); + EXPECT_EQ(v2[3].size(), 4); + EXPECT_EQ(v2[4].size(), 6); + + EXPECT_EQ(v3[0].size(), 35); + EXPECT_EQ(v3[1].size(), 15); + EXPECT_EQ(v3[2].size(), 0); + EXPECT_EQ(v3[3].size(), 15); + EXPECT_EQ(v3[4].size(), 35); +} + +TEST(multi_function_procedure, BufferReuse) +{ + /** + * procedure(int a, int *out) { + * int b = a + 10; + * int c = c + 10; + * int d = d + 10; + * int e = d + 10; + * out = e + 10; + * } + */ + + CustomMF_SI_SO<int, int> add_10_fn{"add 10", [](int a) { return a + 10; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var_a = &builder.add_single_input_parameter<int>(); + auto [var_b] = builder.add_call<1>(add_10_fn, {var_a}); + builder.add_destruct(*var_a); + auto [var_c] = builder.add_call<1>(add_10_fn, {var_b}); + builder.add_destruct(*var_b); + auto [var_d] = builder.add_call<1>(add_10_fn, {var_c}); + builder.add_destruct(*var_c); + auto [var_e] = builder.add_call<1>(add_10_fn, {var_d}); + builder.add_destruct(*var_d); + auto [var_out] = builder.add_call<1>(add_10_fn, {var_e}); + builder.add_destruct(*var_e); + builder.add_return(); + builder.add_output_parameter(*var_out); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Buffer Reuse", procedure}; + + Array<int> inputs = {4, 1, 6, 2, 3}; + Array<int> results(5, -1); + + MFParamsBuilder params{procedure_fn, 5}; + params.add_readonly_single_input(inputs.as_span()); + params.add_uninitialized_single_output(results.as_mutable_span()); + + MFContextBuilder context; + procedure_fn.call({0, 2, 3, 4}, params, context); + + EXPECT_EQ(results[0], 54); + EXPECT_EQ(results[1], -1); + EXPECT_EQ(results[2], 56); + EXPECT_EQ(results[3], 52); + EXPECT_EQ(results[4], 53); +} + +} // namespace blender::fn::tests diff --git a/source/blender/functions/tests/FN_multi_function_test.cc b/source/blender/functions/tests/FN_multi_function_test.cc index 91c72a51dd6..d99993b35ac 100644 --- a/source/blender/functions/tests/FN_multi_function_test.cc +++ b/source/blender/functions/tests/FN_multi_function_test.cc @@ -263,7 +263,7 @@ TEST(multi_function, CustomMF_Constant) TEST(multi_function, CustomMF_GenericConstant) { int value = 42; - CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value}; + CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value, false}; EXPECT_EQ(fn.param_name(0), "42"); Array<int> outputs(4, 0); @@ -328,5 +328,32 @@ TEST(multi_function, CustomMF_Convert) EXPECT_EQ(outputs[2], 9); } +TEST(multi_function, IgnoredOutputs) +{ + OptionalOutputsFunction fn; + { + MFParamsBuilder params(fn, 10); + params.add_ignored_single_output("Out 1"); + params.add_ignored_single_output("Out 2"); + MFContextBuilder context; + fn.call(IndexRange(10), params, context); + } + { + Array<int> results_1(10); + Array<std::string> results_2(10, NoInitialization()); + + MFParamsBuilder params(fn, 10); + params.add_uninitialized_single_output(results_1.as_mutable_span(), "Out 1"); + params.add_uninitialized_single_output(results_2.as_mutable_span(), "Out 2"); + MFContextBuilder context; + fn.call(IndexRange(10), params, context); + + EXPECT_EQ(results_1[0], 5); + EXPECT_EQ(results_1[3], 5); + EXPECT_EQ(results_1[9], 5); + EXPECT_EQ(results_2[0], "hello, this is a long string"); + } +} + } // namespace } // namespace blender::fn::tests diff --git a/source/blender/functions/tests/FN_multi_function_test_common.hh b/source/blender/functions/tests/FN_multi_function_test_common.hh index 51c8fac8a96..2a64cc302fe 100644 --- a/source/blender/functions/tests/FN_multi_function_test_common.hh +++ b/source/blender/functions/tests/FN_multi_function_test_common.hh @@ -171,4 +171,33 @@ class SumVectorFunction : public MultiFunction { } }; +class OptionalOutputsFunction : public MultiFunction { + public: + OptionalOutputsFunction() + { + static MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static MFSignature create_signature() + { + MFSignatureBuilder signature{"Optional Outputs"}; + signature.single_output<int>("Out 1"); + signature.single_output<std::string>("Out 2"); + return signature.build(); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + if (params.single_output_is_required(0, "Out 1")) { + MutableSpan<int> values = params.uninitialized_single_output<int>(0, "Out 1"); + values.fill_indices(mask, 5); + } + MutableSpan<std::string> values = params.uninitialized_single_output<std::string>(1, "Out 2"); + for (const int i : mask) { + new (&values[i]) std::string("hello, this is a long string"); + } + } +}; + } // namespace blender::fn::tests diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index ec965c9a29f..adf68e534bb 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -52,6 +52,7 @@ set(SRC intern/MOD_gpencilarray.c intern/MOD_gpencilbuild.c intern/MOD_gpencilcolor.c + intern/MOD_gpencildash.c intern/MOD_gpencilhook.c intern/MOD_gpencillattice.c intern/MOD_gpencillength.c diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h index 18310bd5dff..043186155b7 100644 --- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h +++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h @@ -46,6 +46,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply; extern GpencilModifierTypeInfo modifierType_Gpencil_Texture; extern GpencilModifierTypeInfo modifierType_Gpencil_Weight; extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart; +extern GpencilModifierTypeInfo modifierType_Gpencil_Dash; /* MOD_gpencil_util.c */ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c index 6409c86b6e3..5eb1eeab780 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c @@ -65,6 +65,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]) INIT_GP_TYPE(Texture); INIT_GP_TYPE(Weight); INIT_GP_TYPE(Lineart); + INIT_GP_TYPE(Dash); #undef INIT_GP_TYPE } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c b/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c new file mode 100644 index 00000000000..ba33edd6a94 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c @@ -0,0 +1,387 @@ +/* + * 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) 2021, Blender Foundation + * This is a new part of Blender + */ + +/** \file + * \ingroup modifiers + */ + +#include <stdio.h> +#include <string.h> + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "DNA_defaults.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" + +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_lib_query.h" +#include "BKE_main.h" +#include "BKE_modifier.h" +#include "BKE_screen.h" + +#include "MEM_guardedalloc.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" + +#include "BLT_translation.h" + +#include "MOD_gpencil_modifiertypes.h" +#include "MOD_gpencil_ui_common.h" +#include "MOD_gpencil_util.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" + +static void initData(GpencilModifierData *md) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)md; + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(dmd, modifier)); + + MEMCPY_STRUCT_AFTER(dmd, DNA_struct_default_get(DashGpencilModifierData), modifier); + + DashGpencilModifierSegment *ds = DNA_struct_default_alloc(DashGpencilModifierSegment); + ds->dmd = dmd; + BLI_strncpy(ds->name, DATA_("Segment"), sizeof(ds->name)); + + dmd->segments = ds; +} + +static void copyData(const GpencilModifierData *md, GpencilModifierData *target) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)target; + const DashGpencilModifierData *dmd_src = (const DashGpencilModifierData *)md; + + BKE_gpencil_modifier_copydata_generic(md, target); + + dmd->segments = MEM_dupallocN(dmd_src->segments); +} + +static void freeData(GpencilModifierData *md) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)md; + + MEM_SAFE_FREE(dmd->segments); +} + +/** + * Gap==0 means to start the next segment at the immediate next point, which will leave a visual + * gap of "1 point". This makes the algorithm give the same visual appearance as displayed on the + * UI and also simplifies the check for "no-length" situation where SEG==0 (which will not produce + * any effective dash). + */ +static int real_gap(const DashGpencilModifierSegment *ds) +{ + return ds->gap - 1; +} + +static bool stroke_dash(const bGPDstroke *gps, + const DashGpencilModifierData *dmd, + ListBase *r_strokes) +{ + int new_stroke_offset = 0; + int trim_start = 0; + + for (int i = 0; i < dmd->segments_len; i++) { + if (dmd->segments[i].dash + real_gap(&dmd->segments[i]) < 1) { + BLI_assert_unreachable(); + /* This means there's a part that doesn't have any length, can't do dot-dash. */ + return false; + } + } + + const DashGpencilModifierSegment *const first_segment = &dmd->segments[0]; + const DashGpencilModifierSegment *const last_segment = &dmd->segments[dmd->segments_len - 1]; + const DashGpencilModifierSegment *ds = first_segment; + + /* Determine starting configuration using offset. */ + int offset_trim = dmd->dash_offset; + while (offset_trim < 0) { + ds = (ds == first_segment) ? last_segment : ds - 1; + offset_trim += ds->dash + real_gap(ds); + } + + /* This segment is completely removed from view by the index offset, ignore it. */ + while (ds->dash + real_gap(ds) < offset_trim) { + offset_trim -= ds->dash + real_gap(ds); + ds = (ds == last_segment) ? first_segment : ds + 1; + } + + /* This segment is partially visible at the beginning of the stroke. */ + if (ds->dash > offset_trim) { + trim_start = offset_trim; + } + else { + /* This segment is not visible but the gap immediately after this segment is partially visible, + * use next segment's dash. */ + new_stroke_offset += ds->dash + real_gap(ds) - offset_trim; + ds = (ds == last_segment) ? first_segment : ds + 1; + } + + while (new_stroke_offset < gps->totpoints - 1) { + const int seg = ds->dash - trim_start; + if (!(seg || real_gap(ds))) { + ds = (ds == last_segment) ? first_segment : ds + 1; + continue; + } + + const int size = MIN2(gps->totpoints - new_stroke_offset, seg); + if (size == 0) { + continue; + } + + bGPDstroke *stroke = BKE_gpencil_stroke_new( + ds->mat_nr < 0 ? gps->mat_nr : ds->mat_nr, size, gps->thickness); + + for (int is = 0; is < size; is++) { + bGPDspoint *p = &gps->points[new_stroke_offset + is]; + stroke->points[is].x = p->x; + stroke->points[is].y = p->y; + stroke->points[is].z = p->z; + stroke->points[is].pressure = p->pressure * ds->radius; + stroke->points[is].strength = p->strength * ds->opacity; + } + BLI_addtail(r_strokes, stroke); + + if (gps->dvert) { + BKE_gpencil_dvert_ensure(stroke); + for (int di = 0; di < stroke->totpoints; di++) { + MDeformVert *dv = &gps->dvert[new_stroke_offset + di]; + if (dv && dv->totweight && dv->dw) { + MDeformWeight *dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * dv->totweight, + __func__); + memcpy(dw, dv->dw, sizeof(MDeformWeight) * dv->totweight); + stroke->dvert[di].dw = dw; + stroke->dvert[di].totweight = dv->totweight; + stroke->dvert[di].flag = dv->flag; + } + } + } + + new_stroke_offset += seg + real_gap(ds); + ds = (ds == last_segment) ? first_segment : ds + 1; + trim_start = 0; + } + + return true; +} + +static void apply_dash_for_frame( + Object *ob, bGPDlayer *gpl, bGPdata *gpd, bGPDframe *gpf, DashGpencilModifierData *dmd) +{ + if (dmd->segments_len == 0) { + return; + } + + ListBase result = {NULL, NULL}; + + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + if (is_stroke_affected_by_modifier(ob, + dmd->layername, + dmd->material, + dmd->pass_index, + dmd->layer_pass, + 1, + gpl, + gps, + dmd->flag & GP_LENGTH_INVERT_LAYER, + dmd->flag & GP_LENGTH_INVERT_PASS, + dmd->flag & GP_LENGTH_INVERT_LAYERPASS, + dmd->flag & GP_LENGTH_INVERT_MATERIAL)) { + stroke_dash(gps, dmd, &result); + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + } + bGPDstroke *gps_dash; + while ((gps_dash = BLI_pophead(&result))) { + BLI_addtail(&gpf->strokes, gps_dash); + BKE_gpencil_stroke_geometry_update(gpd, gps_dash); + } +} + +static void bakeModifier(Main *UNUSED(bmain), + Depsgraph *UNUSED(depsgraph), + GpencilModifierData *md, + Object *ob) +{ + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md); + } + } +} + +/* -------------------------------- */ + +/* Generic "generateStrokes" callback */ +static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Object *ob) +{ + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + BKE_gpencil_frame_active_set(depsgraph, gpd); + bGPDframe *gpf = gpl->actframe; + if (gpf == NULL) { + return; + } + apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md); + } +} + +static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + DashGpencilModifierData *mmd = (DashGpencilModifierData *)md; + + walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER); +} + +static void segment_list_item(struct uiList *UNUSED(ui_list), + struct bContext *UNUSED(C), + struct uiLayout *layout, + struct PointerRNA *UNUSED(idataptr), + struct PointerRNA *itemptr, + int UNUSED(icon), + struct PointerRNA *UNUSED(active_dataptr), + const char *UNUSED(active_propname), + int UNUSED(index), + int UNUSED(flt_flag)) +{ + uiLayout *row = uiLayoutRow(layout, true); + uiItemR(row, itemptr, "name", UI_ITEM_R_NO_BG, "", ICON_NONE); +} + +static void panel_draw(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayoutSetPropSep(layout, true); + + uiItemR(layout, ptr, "dash_offset", 0, NULL, ICON_NONE); + + uiLayout *row = uiLayoutRow(layout, false); + uiLayoutSetPropSep(row, false); + + uiTemplateList(row, + (bContext *)C, + "MOD_UL_dash_segment", + "", + ptr, + "segments", + ptr, + "segment_active_index", + NULL, + 3, + 10, + 0, + 1, + UI_TEMPLATE_LIST_FLAG_NONE); + + uiLayout *col = uiLayoutColumn(row, false); + uiLayoutSetContextPointer(col, "modifier", ptr); + + uiLayout *sub = uiLayoutColumn(col, true); + uiItemO(sub, "", ICON_ADD, "GPENCIL_OT_segment_add"); + uiItemO(sub, "", ICON_REMOVE, "GPENCIL_OT_segment_remove"); + uiItemS(col); + sub = uiLayoutColumn(col, true); + uiItemEnumO_string(sub, "", ICON_TRIA_UP, "GPENCIL_OT_segment_move", "type", "UP"); + uiItemEnumO_string(sub, "", ICON_TRIA_DOWN, "GPENCIL_OT_segment_move", "type", "DOWN"); + + DashGpencilModifierData *dmd = ptr->data; + + if (dmd->segment_active_index >= 0 && dmd->segment_active_index < dmd->segments_len) { + PointerRNA ds_ptr; + RNA_pointer_create(ptr->owner_id, + &RNA_DashGpencilModifierSegment, + &dmd->segments[dmd->segment_active_index], + &ds_ptr); + + sub = uiLayoutColumn(layout, true); + uiItemR(sub, &ds_ptr, "dash", 0, NULL, ICON_NONE); + uiItemR(sub, &ds_ptr, "gap", 0, NULL, ICON_NONE); + + sub = uiLayoutColumn(layout, false); + uiItemR(sub, &ds_ptr, "radius", 0, NULL, ICON_NONE); + uiItemR(sub, &ds_ptr, "opacity", 0, NULL, ICON_NONE); + uiItemR(sub, &ds_ptr, "material_index", 0, NULL, ICON_NONE); + } + + gpencil_modifier_panel_end(layout, ptr); +} + +static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + gpencil_modifier_masking_panel_draw(panel, true, false); +} + +static void panelRegister(ARegionType *region_type) +{ + PanelType *panel_type = gpencil_modifier_panel_register( + region_type, eGpencilModifierType_Dash, panel_draw); + gpencil_modifier_subpanel_register( + region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); + + uiListType *list_type = MEM_callocN(sizeof(uiListType), "dash modifier segment uilist"); + strcpy(list_type->idname, "MOD_UL_dash_segment"); + list_type->draw_item = segment_list_item; + WM_uilisttype_add(list_type); +} + +GpencilModifierTypeInfo modifierType_Gpencil_Dash = { + /* name */ "Dot Dash", + /* structName */ "DashGpencilModifierData", + /* structSize */ sizeof(DashGpencilModifierData), + /* type */ eGpencilModifierTypeType_Gpencil, + /* flags */ eGpencilModifierTypeFlag_SupportsEditmode, + + /* copyData */ copyData, + + /* deformStroke */ NULL, + /* generateStrokes */ generateStrokes, + /* bakeModifier */ bakeModifier, + /* remapTime */ NULL, + + /* initData */ initData, + /* freeData */ freeData, + /* isDisabled */ NULL, + /* updateDepsgraph */ NULL, + /* dependsOnTime */ NULL, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ NULL, + /* panelRegister */ panelRegister, +}; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c index 857c683d95a..6aa0e6c152e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c @@ -108,27 +108,6 @@ static void applyLength(LengthGpencilModifierData *lmd, bGPdata *gpd, bGPDstroke } } -static void bakeModifier(Main *UNUSED(bmain), - Depsgraph *UNUSED(depsgraph), - GpencilModifierData *md, - Object *ob) -{ - - bGPdata *gpd = ob->data; - - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md; - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - applyLength(lmd, gpd, gps); - } - } - } -} - -/* -------------------------------- */ - -/* Generic "generateStrokes" callback */ static void deformStroke(GpencilModifierData *md, Depsgraph *UNUSED(depsgraph), Object *ob, @@ -154,6 +133,23 @@ static void deformStroke(GpencilModifierData *md, } } +static void bakeModifier(Main *UNUSED(bmain), + Depsgraph *depsgraph, + GpencilModifierData *md, + Object *ob) +{ + + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + deformStroke(md, depsgraph, ob, gpl, gpf, gps); + } + } + } +} + static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) { LengthGpencilModifierData *mmd = (LengthGpencilModifierData *)md; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index 73ca4b9c529..01488a8b2de 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -390,6 +390,8 @@ static void options_panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(col, ptr, "use_edge_overlap", 0, IFACE_("Overlapping Edges As Contour"), ICON_NONE); uiItemR(col, ptr, "use_object_instances", 0, NULL, ICON_NONE); uiItemR(col, ptr, "use_clip_plane_boundaries", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "use_crease_on_smooth", 0, IFACE_("Crease On Smooth"), ICON_NONE); + uiItemR(layout, ptr, "use_crease_on_sharp", 0, IFACE_("Crease On Sharp"), ICON_NONE); } static void style_panel_draw(const bContext *UNUSED(C), Panel *panel) diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c index 9e9eba3d61e..ae862ce3eda 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c @@ -324,7 +324,7 @@ static void random_panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayoutSetPropSep(layout, true); - uiLayoutSetActive(layout, RNA_boolean_get(ptr, "random")); + uiLayoutSetActive(layout, RNA_boolean_get(ptr, "use_random")); uiItemR(layout, ptr, "step", 0, NULL, ICON_NONE); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c index f444103ae3f..ee5ba7e92ca 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c @@ -76,16 +76,12 @@ static void deformStroke(GpencilModifierData *md, SubdivGpencilModifierData *mmd = (SubdivGpencilModifierData *)md; bGPdata *gpd = ob->data; - /* It makes sense when adding points to a straight line */ - /* e.g. for creating thickness variation in later modifiers. */ - const int minimum_vert = (mmd->flag & GP_SUBDIV_SIMPLE) ? 2 : 3; - if (!is_stroke_affected_by_modifier(ob, mmd->layername, mmd->material, mmd->pass_index, mmd->layer_pass, - minimum_vert, + 2, gpl, gps, mmd->flag & GP_SUBDIV_INVERT_LAYER, @@ -95,7 +91,10 @@ static void deformStroke(GpencilModifierData *md, return; } - BKE_gpencil_stroke_subdivide(gpd, gps, mmd->level, mmd->type); + /* For strokes with less than 3 points, only the Simple Subdivision makes sense. */ + short type = gps->totpoints < 3 ? GP_SUBDIV_SIMPLE : mmd->type; + + BKE_gpencil_stroke_subdivide(gpd, gps, mmd->level, type); /* If the stroke is cyclic, must generate the closing geometry. */ if (gps->flag & GP_STROKE_CYCLIC) { diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 4b71011b99a..134d9707ade 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -304,6 +304,9 @@ typedef struct LineartRenderBuffer { bool filter_face_mark_invert; bool filter_face_mark_boundaries; + bool force_crease; + bool sharp_as_crease; + /* Keep an copy of these data so when line art is running it's self-contained. */ bool cam_is_persp; float cam_obmat[4][4]; diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index c71cde8ec43..725cc0741f0 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -1453,7 +1453,7 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb, LineartTriangle *rt_array, LineartVert *rv_array, float crease_threshold, - bool no_crease, + bool use_auto_smooth, bool use_freestyle_edge, bool use_freestyle_face, BMesh *bm_if_freestyle) @@ -1533,10 +1533,20 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb, edge_flag_result |= LRT_EDGE_FLAG_CONTOUR; } - if (rb->use_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < crease_threshold)) { - if (!no_crease) { + if (rb->use_crease) { + if (rb->sharp_as_crease && !BM_elem_flag_test(e, BM_ELEM_SMOOTH)) { edge_flag_result |= LRT_EDGE_FLAG_CREASE; } + else { + bool do_crease = true; + if (!rb->force_crease && !use_auto_smooth && + (BM_elem_flag_test(ll->f, BM_ELEM_SMOOTH) && BM_elem_flag_test(lr->f, BM_ELEM_SMOOTH))) { + do_crease = false; + } + if (do_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < crease_threshold)) { + edge_flag_result |= LRT_EDGE_FLAG_CREASE; + } + } } if (rb->use_material && (ll->f->mat_nr != lr->f->mat_nr)) { edge_flag_result |= LRT_EDGE_FLAG_MATERIAL; @@ -1746,9 +1756,14 @@ static void lineart_geometry_object_load(LineartObjectInfo *obi, LineartRenderBu eln->object_ref = orig_ob; obi->v_eln = eln; + bool use_auto_smooth = false; if (orig_ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { use_crease = cosf(M_PI - orig_ob->lineart.crease_threshold); } + if (obi->original_me->flag & ME_AUTOSMOOTH) { + use_crease = cosf(obi->original_me->smoothresh); + use_auto_smooth = true; + } else { use_crease = rb->crease_threshold; } @@ -1832,15 +1847,15 @@ static void lineart_geometry_object_load(LineartObjectInfo *obi, LineartRenderBu e = BM_edge_at_index(bm, i); /* Because e->head.hflag is char, so line type flags should not exceed positive 7 bits. */ - char eflag = lineart_identify_feature_line(rb, - e, - ort, - orv, - use_crease, - orig_ob->type == OB_FONT, - can_find_freestyle_edge, - can_find_freestyle_face, - bm); + uint16_t eflag = lineart_identify_feature_line(rb, + e, + ort, + orv, + use_crease, + use_auto_smooth, + can_find_freestyle_edge, + can_find_freestyle_face, + bm); if (eflag) { /* Only allocate for feature lines (instead of all lines) to save memory. * If allow duplicated edges, one edge gets added multiple times if it has multiple types. */ @@ -3057,6 +3072,9 @@ static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene, rb->allow_duplicated_types = (lmd->calculation_flags & LRT_ALLOW_OVERLAP_EDGE_TYPES) != 0; + rb->force_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SMOOTH_SURFACES) != 0; + rb->sharp_as_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SHARP_EDGES) != 0; + int16_t edge_types = lmd->edge_types_override; rb->use_contour = (edge_types & LRT_EDGE_FLAG_CONTOUR) != 0; diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h index f834ee5b234..62b748b7edf 100644 --- a/source/blender/gpu/GPU_shader.h +++ b/source/blender/gpu/GPU_shader.h @@ -54,7 +54,8 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, const char *fragcode, const char *geomcode, const char *libcode, - const char *defines); + const char *defines, + const char *name); GPUShader *GPU_shader_create_ex(const char *vertcode, const char *fragcode, const char *geomcode, @@ -85,6 +86,8 @@ void GPU_shader_free(GPUShader *shader); void GPU_shader_bind(GPUShader *shader); void GPU_shader_unbind(void); +const char *GPU_shader_get_name(GPUShader *shader); + /* Returns true if transform feedback was successfully enabled. */ bool GPU_shader_transform_feedback_enable(GPUShader *shader, struct GPUVertBuf *vertbuf); void GPU_shader_transform_feedback_disable(GPUShader *shader); diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index c754a649924..9340d311472 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -229,7 +229,8 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, const char *fragcode, const char *geomcode, const char *libcode, - const char *defines) + const char *defines, + const char *name) { char *libcodecat = nullptr; @@ -240,6 +241,9 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, libcode = libcodecat = BLI_strdupcat(libcode, datatoc_gpu_shader_colorspace_lib_glsl); } + /* Use pyGPUShader as default name for shader. */ + const char *shname = name != nullptr ? name : "pyGPUShader"; + GPUShader *sh = GPU_shader_create_ex(vertcode, fragcode, geomcode, @@ -249,7 +253,7 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, GPU_SHADER_TFB_NONE, nullptr, 0, - "pyGPUShader"); + shname); MEM_SAFE_FREE(libcodecat); return sh; @@ -369,6 +373,17 @@ void GPU_shader_unbind(void) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Shader name + * \{ */ + +const char *GPU_shader_get_name(GPUShader *shader) +{ + return unwrap(shader)->name_get(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Transform feedback * * TODO(fclem): Should be replaced by compute shaders. diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index 772fc19d919..2855d5078ff 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -460,7 +460,7 @@ void GLBackend::capabilities_init() GCaps.mem_stats_support = GLEW_NVX_gpu_memory_info || GLEW_ATI_meminfo; GCaps.shader_image_load_store_support = GLEW_ARB_shader_image_load_store; - GCaps.compute_shader_support = GLEW_ARB_compute_shader; + GCaps.compute_shader_support = GLEW_ARB_compute_shader && GLEW_VERSION_4_3; if (GCaps.compute_shader_support) { glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &GCaps.max_work_group_count[0]); glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &GCaps.max_work_group_count[1]); diff --git a/source/blender/imbuf/IMB_imbuf.h b/source/blender/imbuf/IMB_imbuf.h index 4ad7aa98484..dd8e6549e24 100644 --- a/source/blender/imbuf/IMB_imbuf.h +++ b/source/blender/imbuf/IMB_imbuf.h @@ -150,6 +150,14 @@ bool IMB_initImBuf( /** * Create a copy of a pixel buffer and wrap it to a new ImBuf + * (transferring ownership to the in imbuf). + * \attention Defined in allocimbuf.c + */ +struct ImBuf *IMB_allocFromBufferOwn( + unsigned int *rect, float *rectf, unsigned int w, unsigned int h, unsigned int channels); + +/** + * Create a copy of a pixel buffer and wrap it to a new ImBuf * \attention Defined in allocimbuf.c */ struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect, diff --git a/source/blender/imbuf/IMB_thumbs.h b/source/blender/imbuf/IMB_thumbs.h index 9dd0cbe895f..e1a315a0bd2 100644 --- a/source/blender/imbuf/IMB_thumbs.h +++ b/source/blender/imbuf/IMB_thumbs.h @@ -52,6 +52,7 @@ typedef enum ThumbSource { #define THUMB_SIZE_MAX (100 * 1024 * 1024) #define PREVIEW_RENDER_DEFAULT_HEIGHT 128 +#define PREVIEW_RENDER_LARGE_HEIGHT 256 /* Note this can also be used as versioning system, * to force refreshing all thumbnails if e.g. we change some thumb generating code or so. diff --git a/source/blender/imbuf/intern/allocimbuf.c b/source/blender/imbuf/intern/allocimbuf.c index 90c863878ff..1369f8cc0f8 100644 --- a/source/blender/imbuf/intern/allocimbuf.c +++ b/source/blender/imbuf/intern/allocimbuf.c @@ -430,6 +430,41 @@ bool imb_addrectImBuf(ImBuf *ibuf) return false; } +/** + * \param take_ownership: When true, the buffers become owned by the resulting image. + */ +struct ImBuf *IMB_allocFromBufferOwn( + unsigned int *rect, float *rectf, unsigned int w, unsigned int h, unsigned int channels) +{ + ImBuf *ibuf = NULL; + + if (!(rect || rectf)) { + return NULL; + } + + ibuf = IMB_allocImBuf(w, h, 32, 0); + + ibuf->channels = channels; + + /* Avoid #MEM_dupallocN since the buffers might not be allocated using guarded-allocation. */ + if (rectf) { + BLI_assert(MEM_allocN_len(rectf) == sizeof(float[4]) * w * h); + ibuf->rect_float = rectf; + + ibuf->flags |= IB_rectfloat; + ibuf->mall |= IB_rectfloat; + } + if (rect) { + BLI_assert(MEM_allocN_len(rect) == sizeof(uchar[4]) * w * h); + ibuf->rect = rect; + + ibuf->flags |= IB_rect; + ibuf->mall |= IB_rect; + } + + return ibuf; +} + struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect, const float *rectf, unsigned int w, @@ -445,13 +480,21 @@ struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect, ibuf = IMB_allocImBuf(w, h, 32, 0); ibuf->channels = channels; + + /* Avoid #MEM_dupallocN since the buffers might not be allocated using guarded-allocation. */ if (rectf) { - ibuf->rect_float = MEM_dupallocN(rectf); + const size_t size = sizeof(float[4]) * w * h; + ibuf->rect_float = MEM_mallocN(size, __func__); + memcpy(ibuf->rect_float, rectf, size); + ibuf->flags |= IB_rectfloat; ibuf->mall |= IB_rectfloat; } if (rect) { - ibuf->rect = MEM_dupallocN(rect); + const size_t size = sizeof(uchar[4]) * w * h; + ibuf->rect = MEM_mallocN(size, __func__); + memcpy(ibuf->rect, rect, size); + ibuf->flags |= IB_rect; ibuf->mall |= IB_rect; } diff --git a/source/blender/imbuf/intern/anim_movie.c b/source/blender/imbuf/intern/anim_movie.c index dbca16ca82b..13f9356751e 100644 --- a/source/blender/imbuf/intern/anim_movie.c +++ b/source/blender/imbuf/intern/anim_movie.c @@ -664,11 +664,6 @@ static int startffmpeg(struct anim *anim) anim->duration_in_frames = (int)(stream_dur * av_q2d(frame_rate) + 0.5f); } - double ctx_start = 0; - if (pFormatCtx->start_time != AV_NOPTS_VALUE) { - ctx_start = (double)pFormatCtx->start_time / AV_TIME_BASE; - } - frs_num = frame_rate.num; frs_den = frame_rate.den; @@ -683,7 +678,7 @@ static int startffmpeg(struct anim *anim) anim->frs_sec_base = frs_den; /* Save the relative start time for the video. IE the start time in relation to where playback * starts. */ - anim->start_offset = video_start - ctx_start; + anim->start_offset = video_start; anim->params = 0; diff --git a/source/blender/imbuf/intern/thumbs.c b/source/blender/imbuf/intern/thumbs.c index a09f06726a6..aa1da65253d 100644 --- a/source/blender/imbuf/intern/thumbs.c +++ b/source/blender/imbuf/intern/thumbs.c @@ -347,7 +347,7 @@ static ImBuf *thumb_create_ex(const char *file_path, tsize = PREVIEW_RENDER_DEFAULT_HEIGHT; break; case THB_LARGE: - tsize = PREVIEW_RENDER_DEFAULT_HEIGHT * 2; + tsize = PREVIEW_RENDER_LARGE_HEIGHT; break; case THB_FAIL: tsize = 1; diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 0a3a43bb21f..0b5e927f02f 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -117,7 +117,9 @@ struct Mesh *ABC_read_mesh(struct CacheReader *reader, struct Mesh *existing_mesh, const float time, const char **err_str, - int read_flags); + const int read_flags, + const char *velocity_name, + const float velocity_scale); bool ABC_mesh_topology_changed(struct CacheReader *reader, struct Object *ob, @@ -133,16 +135,6 @@ struct CacheReader *CacheReader_open_alembic_object(struct CacheArchiveHandle *h struct Object *object, const char *object_path); -bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name); - -/* r_vertex_velocities should point to a preallocated array of num_vertices floats */ -int ABC_read_velocity_cache(struct CacheReader *reader, - const char *velocity_name, - float time, - float velocity_scale, - int num_vertices, - float *r_vertex_velocities); - #ifdef __cplusplus } #endif diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.cc b/source/blender/io/alembic/exporter/abc_writer_abstract.cc index 910e04f3bf5..3665494c046 100644 --- a/source/blender/io/alembic/exporter/abc_writer_abstract.cc +++ b/source/blender/io/alembic/exporter/abc_writer_abstract.cc @@ -118,7 +118,7 @@ void ABCAbstractWriter::update_bounding_box(Object *object) if (!bb) { if (object->type != OB_CAMERA) { - CLOG_WARN(&LOG, "Bounding box is null!\n"); + CLOG_WARN(&LOG, "Bounding box is null!"); } bounding_box_.min.x = bounding_box_.min.y = bounding_box_.min.z = 0; bounding_box_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0; diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index 131b60b90fb..8f410978211 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -25,6 +25,7 @@ #include "BLI_assert.h" #include "BLI_math_vector.h" +#include "BKE_attribute.h" #include "BKE_customdata.h" #include "BKE_lib_id.h" #include "BKE_material.h" @@ -108,9 +109,6 @@ void ABCGenericMeshWriter::create_alembic_objects(const HierarchyContext *contex OBoolProperty type(typeContainer, "meshtype"); type.set(subsurf_modifier_ == nullptr); } - - Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph); - liquid_sim_modifier_ = get_liquid_sim_modifier(scene_eval, context->object); } Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const @@ -144,21 +142,6 @@ bool ABCGenericMeshWriter::export_as_subdivision_surface(Object *ob_eval) const return false; } -ModifierData *ABCGenericMeshWriter::get_liquid_sim_modifier(Scene *scene, Object *ob) -{ - ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluidsim); - - if (md && (BKE_modifier_is_enabled(scene, md, eModifierMode_Render))) { - FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); - - if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) { - return md; - } - } - - return nullptr; -} - bool ABCGenericMeshWriter::is_supported(const HierarchyContext *context) const { if (args_.export_params->visible_objects_only) { @@ -284,8 +267,7 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); } - if (liquid_sim_modifier_ != nullptr) { - get_velocities(mesh, velocities); + if (get_velocities(mesh, velocities)) { mesh_sample.setVelocities(V3fArraySample(velocities)); } @@ -368,11 +350,6 @@ void ABCGenericMeshWriter::write_face_sets(Object *object, struct Mesh *mesh, Sc void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me) { - if (liquid_sim_modifier_ != nullptr) { - /* We don't need anything more for liquid meshes. */ - return; - } - if (frame_has_been_written_ || !args_.export_params->vcolors) { return; } @@ -387,27 +364,28 @@ void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me) write_custom_data(arb_geom_params, m_custom_data_config, &me->ldata, CD_MLOOPCOL); } -void ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) +bool ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) { + /* Export velocity attribute output by fluid sim, sequence cache modifier + * and geometry nodes. */ + CustomDataLayer *velocity_layer = BKE_id_attribute_find( + &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT); + + if (velocity_layer == nullptr) { + return false; + } + const int totverts = mesh->totvert; + const float(*mesh_velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data); vels.clear(); vels.resize(totverts); - FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(liquid_sim_modifier_); - FluidsimSettings *fss = fmd->fss; - - if (fss->meshVelocities) { - float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities); - - for (int i = 0; i < totverts; i++) { - copy_yup_from_zup(vels[i].getValue(), mesh_vels); - mesh_vels += 3; - } - } - else { - std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f)); + for (int i = 0; i < totverts; i++) { + copy_yup_from_zup(vels[i].getValue(), mesh_velocities[i]); } + + return true; } void ABCGenericMeshWriter::get_geo_groups(Object *object, diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.h b/source/blender/io/alembic/exporter/abc_writer_mesh.h index 0e1792b9dab..fb8a01a3bbf 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.h +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.h @@ -45,7 +45,6 @@ class ABCGenericMeshWriter : public ABCAbstractWriter { * exported object. */ bool is_subd_; ModifierData *subsurf_modifier_; - ModifierData *liquid_sim_modifier_; CDStreamConfig m_custom_data_config; @@ -70,10 +69,8 @@ class ABCGenericMeshWriter : public ABCAbstractWriter { void write_subd(HierarchyContext &context, Mesh *mesh); template<typename Schema> void write_face_sets(Object *object, Mesh *mesh, Schema &schema); - ModifierData *get_liquid_sim_modifier(Scene *scene_eval, Object *ob_eval); - void write_arb_geo_params(Mesh *me); - void get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels); + bool get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels); void get_geo_groups(Object *object, Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geo_groups); diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index e9736555ead..4fba6a2f0f7 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -122,6 +122,12 @@ 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_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc index 27ee35d1b39..d2ec7fb84db 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.cc +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -94,7 +94,7 @@ void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSele { Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE); - cu->flag |= CU_DEFORM_FILL | CU_3D; + cu->flag |= CU_3D; cu->actvert = CU_ACT_NONE; cu->resolu = 1; @@ -283,6 +283,8 @@ void AbcCurveReader::read_curve_sample(Curve *cu, Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, int /*read_flag*/, + const char * /*velocity_name*/, + const float /*velocity_scale*/, const char **err_str) { ICurvesSchema::Sample sample; diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h index 075ed5ca6a1..11b23c8a8cf 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.h +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -45,7 +45,9 @@ class AbcCurveReader : public AbcObjectReader { void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str); void read_curve_sample(Curve *cu, diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 77edd4908bd..eab94139f55 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -27,6 +27,7 @@ #include "MEM_guardedalloc.h" +#include "DNA_customdata_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -36,6 +37,7 @@ #include "BLI_listbase.h" #include "BLI_math_geom.h" +#include "BKE_attribute.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" @@ -43,8 +45,10 @@ #include "BKE_object.h" using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::IV3fArrayProperty; using Alembic::Abc::P3fArraySamplePtr; using Alembic::Abc::PropertyHeader; +using Alembic::Abc::V3fArraySamplePtr; using Alembic::AbcGeom::IC3fGeomParam; using Alembic::AbcGeom::IC4fGeomParam; @@ -420,6 +424,52 @@ static void get_weight_and_index(CDStreamConfig &config, config.ceil_index = i1; } +static V3fArraySamplePtr get_velocity_prop(const ICompoundProperty &schema, + const ISampleSelector &selector, + const std::string &name) +{ + for (size_t i = 0; i < schema.getNumProperties(); i++) { + const PropertyHeader &header = schema.getPropertyHeader(i); + + if (header.isCompound()) { + const ICompoundProperty &prop = ICompoundProperty(schema, header.getName()); + + if (has_property(prop, name)) { + const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0); + if (velocity_prop) { + return velocity_prop.getValue(selector); + } + } + } + else if (header.isArray()) { + if (header.getName() == name) { + const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0); + return velocity_prop.getValue(selector); + } + } + } + + return V3fArraySamplePtr(); +} + +static void read_velocity(const V3fArraySamplePtr &velocities, + const CDStreamConfig &config, + const float velocity_scale) +{ + CustomDataLayer *velocity_layer = BKE_id_attribute_new( + &config.mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT, nullptr); + float(*velocity)[3] = (float(*)[3])velocity_layer->data; + + const int num_velocity_vectors = static_cast<int>(velocities->size()); + BLI_assert(num_velocity_vectors == config.mesh->totvert); + + for (int i = 0; i < num_velocity_vectors; i++) { + const Imath::V3f &vel_in = (*velocities)[i]; + copy_zup_from_yup(velocity[i], vel_in.getValue()); + mul_v3_fl(velocity[i], velocity_scale); + } +} + static void read_mesh_sample(const std::string &iobject_full_name, ImportSettings *settings, const IPolyMeshSchema &schema, @@ -458,6 +508,13 @@ static void read_mesh_sample(const std::string &iobject_full_name, if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); } + + if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) { + V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name); + if (velocities) { + read_velocity(velocities, config, settings->velocity_scale); + } + } } CDStreamConfig get_config(Mesh *mesh, const bool use_vertex_interpolation) @@ -563,7 +620,7 @@ void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); m_object->data = mesh; - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr); if (read_mesh != mesh) { /* XXX FIXME: after 2.80; mesh->flag isn't copied by #BKE_mesh_nomain_to_mesh(). */ /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ @@ -630,7 +687,9 @@ bool AbcMeshReader::topology_changed(Mesh *existing_mesh, const ISampleSelector Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str) { IPolyMeshSchema::Sample sample; @@ -673,6 +732,8 @@ Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, /* Only read point data when streaming meshes, unless we need to create new ones. */ ImportSettings settings; settings.read_flag |= read_flag; + settings.velocity_name = velocity_name; + settings.velocity_scale = velocity_scale; if (topology_changed(existing_mesh, sample_sel)) { new_mesh = BKE_mesh_new_nomain_from_template( @@ -829,6 +890,13 @@ static void read_subd_sample(const std::string &iobject_full_name, if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); } + + if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) { + V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name); + if (velocities) { + read_velocity(velocities, config, settings->velocity_scale); + } + } } /* ************************************************************************** */ @@ -876,7 +944,7 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); m_object->data = mesh; - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr); if (read_mesh != mesh) { BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); } @@ -935,7 +1003,9 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str) { ISubDSchema::Sample sample; @@ -962,6 +1032,8 @@ Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, ImportSettings settings; settings.read_flag |= read_flag; + settings.velocity_name = velocity_name; + settings.velocity_scale = velocity_scale; if (existing_mesh->totvert != positions->size()) { new_mesh = BKE_mesh_new_nomain_from_template( diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h index 3329b723b77..d9f89cc8085 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.h +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -42,7 +42,9 @@ class AbcMeshReader : public AbcObjectReader { struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str) override; bool topology_changed(Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel) override; @@ -73,7 +75,9 @@ class AbcSubDReader : public AbcObjectReader { void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str); }; diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc index 9a5ffd04bd1..a6d46c4b42a 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.cc +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -167,6 +167,8 @@ Imath::M44d get_matrix(const IXformSchema &schema, const float time) struct Mesh *AbcObjectReader::read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &UNUSED(sample_sel), int UNUSED(read_flag), + const char *UNUSED(velocity_name), + const float UNUSED(velocity_scale), const char **UNUSED(err_str)) { return existing_mesh; diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h index 89590b26b61..6e97f841a88 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.h +++ b/source/blender/io/alembic/intern/abc_reader_object.h @@ -50,6 +50,10 @@ struct ImportSettings { /* From MeshSeqCacheModifierData.read_flag */ int read_flag; + /* From CacheFile and MeshSeqCacheModifierData */ + std::string velocity_name; + float velocity_scale; + bool validate_meshes; bool always_add_cache_reader; @@ -65,6 +69,8 @@ struct ImportSettings { sequence_len(1), sequence_offset(0), read_flag(0), + velocity_name(""), + velocity_scale(1.0f), validate_meshes(false), always_add_cache_reader(false), cache_file(NULL) @@ -143,7 +149,9 @@ class AbcObjectReader { virtual struct Mesh *read_mesh(struct Mesh *mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str); virtual bool topology_changed(Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel); diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index deb945b767c..63565b902f3 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -786,7 +786,9 @@ Mesh *ABC_read_mesh(CacheReader *reader, Mesh *existing_mesh, const float time, const char **err_str, - int read_flag) + const int read_flag, + const char *velocity_name, + const float velocity_scale) { AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str); if (abc_reader == nullptr) { @@ -794,7 +796,8 @@ Mesh *ABC_read_mesh(CacheReader *reader, } ISampleSelector sample_sel = sample_selector_for_time(time); - return abc_reader->read_mesh(existing_mesh, sample_sel, read_flag, err_str); + return abc_reader->read_mesh( + existing_mesh, sample_sel, read_flag, velocity_name, velocity_scale, err_str); } bool ABC_mesh_topology_changed( @@ -860,136 +863,3 @@ CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle, return reinterpret_cast<CacheReader *>(abc_reader); } - -/* ************************************************************************** */ - -static const PropertyHeader *get_property_header(const IPolyMeshSchema &schema, const char *name) -{ - const PropertyHeader *prop_header = schema.getPropertyHeader(name); - - if (prop_header) { - return prop_header; - } - - ICompoundProperty prop = schema.getArbGeomParams(); - - if (!has_property(prop, name)) { - return nullptr; - } - - return prop.getPropertyHeader(name); -} - -bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name) -{ - AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); - - if (!abc_reader) { - return false; - } - - IObject iobject = abc_reader->iobject(); - - if (!iobject.valid()) { - return false; - } - - const ObjectHeader &header = iobject.getHeader(); - - if (!IPolyMesh::matches(header)) { - return false; - } - - IPolyMesh mesh(iobject, kWrapExisting); - IPolyMeshSchema schema = mesh.getSchema(); - - const PropertyHeader *prop_header = get_property_header(schema, name); - - if (!prop_header) { - return false; - } - - return IV3fArrayProperty::matches(prop_header->getMetaData()); -} - -static V3fArraySamplePtr get_velocity_prop(const IPolyMeshSchema &schema, - const ISampleSelector &iss, - const std::string &name) -{ - const PropertyHeader *prop_header = schema.getPropertyHeader(name); - - if (prop_header) { - const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0); - return velocity_prop.getValue(iss); - } - - ICompoundProperty prop = schema.getArbGeomParams(); - - if (!has_property(prop, name)) { - return V3fArraySamplePtr(); - } - - const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0); - - if (velocity_prop) { - return velocity_prop.getValue(iss); - } - - return V3fArraySamplePtr(); -} - -int ABC_read_velocity_cache(CacheReader *reader, - const char *velocity_name, - const float time, - float velocity_scale, - int num_vertices, - float *r_vertex_velocities) -{ - AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); - - if (!abc_reader) { - return -1; - } - - IObject iobject = abc_reader->iobject(); - - if (!iobject.valid()) { - return -1; - } - - const ObjectHeader &header = iobject.getHeader(); - - if (!IPolyMesh::matches(header)) { - return -1; - } - - IPolyMesh mesh(iobject, kWrapExisting); - IPolyMeshSchema schema = mesh.getSchema(); - ISampleSelector sample_sel(time); - const IPolyMeshSchema::Sample sample = schema.getValue(sample_sel); - - V3fArraySamplePtr velocities = get_velocity_prop(schema, sample_sel, velocity_name); - - if (!velocities) { - return -1; - } - - float vel[3]; - - int num_velocity_vectors = static_cast<int>(velocities->size()); - - if (num_velocity_vectors != num_vertices) { - return -1; - } - - for (size_t i = 0; i < velocities->size(); ++i) { - const Imath::V3f &vel_in = (*velocities)[i]; - copy_zup_from_yup(vel, vel_in.getValue()); - - mul_v3_fl(vel, velocity_scale); - - copy_v3_v3(r_vertex_velocities + i * 3, vel); - } - - return num_vertices; -} diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc index 31ecf27cf7e..12de1d82c72 100644 --- a/source/blender/io/usd/intern/usd_reader_curve.cc +++ b/source/blender/io/usd/intern/usd_reader_curve.cc @@ -46,7 +46,7 @@ void USDCurvesReader::create_object(Main *bmain, const double /* motionSampleTim { curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); - curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->flag |= CU_3D; curve_->actvert = CU_ACT_NONE; curve_->resolu = 2; diff --git a/source/blender/io/usd/intern/usd_reader_instance.cc b/source/blender/io/usd/intern/usd_reader_instance.cc deleted file mode 100644 index e645b0237b9..00000000000 --- a/source/blender/io/usd/intern/usd_reader_instance.cc +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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) 2021 Blender Foundation. - * All rights reserved. - */ - -#include "usd_reader_instance.h" - -#include "BKE_object.h" -#include "DNA_object_types.h" - -#include <iostream> - -namespace blender::io::usd { - -USDInstanceReader::USDInstanceReader(const pxr::UsdPrim &prim, - const USDImportParams &import_params, - const ImportSettings &settings) - : USDXformReader(prim, import_params, settings) -{ -} - -bool USDInstanceReader::valid() const -{ - return prim_.IsValid() && prim_.IsInstance(); -} - -void USDInstanceReader::create_object(Main *bmain, const double /* motionSampleTime */) -{ - this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str()); - this->object_->data = nullptr; - this->object_->transflag |= OB_DUPLICOLLECTION; -} - -void USDInstanceReader::set_instance_collection(Collection *coll) -{ - if (this->object_) { - this->object_->instance_collection = coll; - } -} - -pxr::SdfPath USDInstanceReader::proto_path() const -{ - if (pxr::UsdPrim master = prim_.GetMaster()) { - return master.GetPath(); - } - - return pxr::SdfPath(); -} - -} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.cc b/source/blender/io/usd/intern/usd_reader_nurbs.cc index 9b30b524729..d6977d9c91a 100644 --- a/source/blender/io/usd/intern/usd_reader_nurbs.cc +++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc @@ -62,7 +62,7 @@ void USDNurbsReader::create_object(Main *bmain, const double /* motionSampleTime { curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); - curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->flag |= CU_3D; curve_->actvert = CU_ACT_NONE; curve_->resolu = 2; diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index 233b3d9da4d..8c4cc18a9af 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -20,7 +20,6 @@ #include "usd_reader_stage.h" #include "usd_reader_camera.h" #include "usd_reader_curve.h" -#include "usd_reader_instance.h" #include "usd_reader_light.h" #include "usd_reader_mesh.h" #include "usd_reader_nurbs.h" @@ -34,6 +33,7 @@ #include <pxr/usd/usdGeom/mesh.h> #include <pxr/usd/usdGeom/nurbsCurves.h> #include <pxr/usd/usdGeom/scope.h> +#include <pxr/usd/usdGeom/xform.h> #include <pxr/usd/usdLux/light.h> #include <iostream> diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc index 54316e56867..61b14155dd0 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.cc +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -26,6 +26,7 @@ #include "BLI_assert.h" #include "BLI_math_vector.h" +#include "BKE_attribute.h" #include "BKE_customdata.h" #include "BKE_lib_id.h" #include "BKE_material.h" @@ -219,7 +220,7 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) if (usd_export_context_.export_params.export_normals) { write_normals(mesh, usd_mesh); } - write_surface_velocity(context.object, mesh, usd_mesh); + write_surface_velocity(mesh, usd_mesh); /* TODO(Sybren): figure out what happens when the face groups change. */ if (frame_has_been_written_) { @@ -409,42 +410,25 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_ usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying); } -void USDGenericMeshWriter::write_surface_velocity(Object *object, - const Mesh *mesh, - pxr::UsdGeomMesh usd_mesh) +void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh) { - /* Only velocities from the fluid simulation are exported. This is the most important case, - * though, as the baked mesh changes topology all the time, and thus computing the velocities - * at import time in a post-processing step is hard. */ - ModifierData *md = BKE_modifiers_findby_type(object, eModifierType_Fluidsim); - if (md == nullptr) { - return; - } + /* Export velocity attribute output by fluid sim, sequence cache modifier + * and geometry nodes. */ + CustomDataLayer *velocity_layer = BKE_id_attribute_find( + &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT); - /* Check that the fluid sim modifier is enabled and has useful data. */ - const bool use_render = (DEG_get_mode(usd_export_context_.depsgraph) == DAG_EVAL_RENDER); - const ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime; - const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph); - if (!BKE_modifier_is_enabled(scene, md, required_mode)) { - return; - } - FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); - if (!fsmd->fss || fsmd->fss->type != OB_FLUIDSIM_DOMAIN) { - return; - } - FluidsimSettings *fss = fsmd->fss; - if (!fss->meshVelocities) { + if (velocity_layer == nullptr) { return; } + const float(*velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data); + /* Export per-vertex velocity vectors. */ pxr::VtVec3fArray usd_velocities; usd_velocities.reserve(mesh->totvert); - FluidVertexVelocity *mesh_velocities = fss->meshVelocities; - for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert; - ++vertex_idx, ++mesh_velocities) { - usd_velocities.push_back(pxr::GfVec3f(mesh_velocities->vel)); + for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert; ++vertex_idx) { + usd_velocities.push_back(pxr::GfVec3f(velocities[vertex_idx])); } pxr::UsdTimeCode timecode = get_export_time_code(); diff --git a/source/blender/io/usd/intern/usd_writer_mesh.h b/source/blender/io/usd/intern/usd_writer_mesh.h index 6345f2d4240..d60a6c4a59b 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.h +++ b/source/blender/io/usd/intern/usd_writer_mesh.h @@ -49,7 +49,7 @@ class USDGenericMeshWriter : public USDAbstractWriter { const MaterialFaceGroups &usd_face_groups); void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); - void write_surface_velocity(Object *object, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); + void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); }; class USDMeshWriter : public USDGenericMeshWriter { diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h index 520fc6c1b00..a2433dbbbbd 100644 --- a/source/blender/makesdna/DNA_curve_types.h +++ b/source/blender/makesdna/DNA_curve_types.h @@ -302,8 +302,11 @@ typedef struct Curve { float fsize_realtime; /** - * A pointer to curve data from geometry nodes, currently only set for evaluated - * objects by the dependency graph iterator, and owned by #geometry_set_eval. + * A pointer to curve data from evaluation. Owned by the object's #geometry_set_eval, either as a + * geometry instance or the data of the evaluated #CurveComponent. The curve may also contain + * data in the #nurb list, but for evaluated curves this is the proper place to retrieve data, + * since it also contains the result of geometry nodes evaluation, and isn't just a copy of the + * original object data. */ struct CurveEval *curve_eval; @@ -344,8 +347,7 @@ enum { CU_DS_EXPAND = 1 << 11, /** make use of the path radius if this is enabled (default for new curves) */ CU_PATH_RADIUS = 1 << 12, - /** fill 2d curve after deformation */ - CU_DEFORM_FILL = 1 << 13, + /* CU_DEFORM_FILL = 1 << 13, */ /* DEPRECATED */ /** fill bevel caps */ CU_FILL_CAPS = 1 << 14, /** map taper object to beveled area */ diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index e5282409329..6acea8da15f 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -31,6 +31,8 @@ extern "C" { #endif +struct AnonymousAttributeID; + /** Descriptor and storage for a custom data layer. */ typedef struct CustomDataLayer { /** Type of data in layer. */ @@ -53,6 +55,13 @@ typedef struct CustomDataLayer { char name[64]; /** Layer data. */ void *data; + /** + * Run-time identifier for this layer. If no one has a strong reference to this id anymore, + * the layer can be removed. The custom data layer only has a weak reference to the id, because + * otherwise there will always be a strong reference and the attribute can't be removed + * automatically. + */ + const struct AnonymousAttributeID *anonymous_id; } CustomDataLayer; #define MAX_CUSTOMDATA_LAYER_NAME 64 diff --git a/source/blender/makesdna/DNA_fluid_defaults.h b/source/blender/makesdna/DNA_fluid_defaults.h index 95f5b8b66b0..4135c4d40a8 100644 --- a/source/blender/makesdna/DNA_fluid_defaults.h +++ b/source/blender/makesdna/DNA_fluid_defaults.h @@ -50,7 +50,6 @@ .tex_flags = NULL, \ .tex_range_field = NULL, \ .guide_parent = NULL, \ - .mesh_velocities = NULL, \ .effector_weights = NULL, /* #BKE_effector_add_weights. */ \ .p0 = {0.0f, 0.0f, 0.0f}, \ .p1 = {0.0f, 0.0f, 0.0f}, \ @@ -122,7 +121,6 @@ .mesh_smoothen_pos = 1, \ .mesh_smoothen_neg = 1, \ .mesh_scale = 2, \ - .totvert = 0, \ .mesh_generator = FLUID_DOMAIN_MESH_IMPROVED, \ .particle_type = 0, \ .particle_scale = 1, \ diff --git a/source/blender/makesdna/DNA_fluid_types.h b/source/blender/makesdna/DNA_fluid_types.h index 835af3c6ff8..0cbef540306 100644 --- a/source/blender/makesdna/DNA_fluid_types.h +++ b/source/blender/makesdna/DNA_fluid_types.h @@ -480,10 +480,6 @@ enum { SM_HRES_FULLSAMPLE = 2, }; -typedef struct FluidDomainVertexVelocity { - float vel[3]; -} FluidDomainVertexVelocity; - typedef struct FluidDomainSettings { /* -- Runtime-only fields (from here on). -- */ @@ -509,8 +505,6 @@ typedef struct FluidDomainSettings { struct GPUTexture *tex_flags; struct GPUTexture *tex_range_field; struct Object *guide_parent; - /** Vertex velocities of simulated fluid mesh. */ - struct FluidDomainVertexVelocity *mesh_velocities; struct EffectorWeights *effector_weights; /* Domain object data. */ @@ -607,9 +601,8 @@ typedef struct FluidDomainSettings { int mesh_smoothen_pos; int mesh_smoothen_neg; int mesh_scale; - int totvert; short mesh_generator; - char _pad6[6]; /* Unused. */ + char _pad6[2]; /* Unused. */ /* Secondary particle options. */ int particle_type; diff --git a/source/blender/makesdna/DNA_freestyle_types.h b/source/blender/makesdna/DNA_freestyle_types.h index 4d4fbaed29a..ab1e7aa903c 100644 --- a/source/blender/makesdna/DNA_freestyle_types.h +++ b/source/blender/makesdna/DNA_freestyle_types.h @@ -40,7 +40,7 @@ enum { FREESTYLE_RIDGES_AND_VALLEYS_FLAG = 1 << 1, FREESTYLE_MATERIAL_BOUNDARIES_FLAG = 1 << 2, FREESTYLE_FACE_SMOOTHNESS_FLAG = 1 << 3, - FREESTYLE_ADVANCED_OPTIONS_FLAG = 1 << 4, + /* FREESTYLE_ADVANCED_OPTIONS_FLAG = 1 << 4, */ /* UNUSED */ FREESTYLE_CULLING = 1 << 5, FREESTYLE_VIEW_MAP_CACHE = 1 << 6, FREESTYLE_AS_RENDER_PASS = 1 << 7, diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index 81e9abc4916..450527c7443 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -304,7 +304,7 @@ .opacity = 1.0f, \ .flags = LRT_GPENCIL_MATCH_OUTPUT_VGROUP, \ .crease_threshold = DEG2RAD(140.0f), \ - .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_ALLOW_CLIPPING_BOUNDARIES, \ + .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,\ @@ -319,5 +319,23 @@ .material = NULL,\ } +#define _DNA_DEFAULT_DashGpencilModifierData \ + { \ + .dash_offset = 0, \ + .segments = NULL, \ + .segments_len = 1, \ + .segment_active_index = 0, \ + } + +#define _DNA_DEFAULT_DashGpencilModifierSegment \ + { \ + .name = "", \ + .dash = 2, \ + .gap = 1, \ + .radius = 1.0f, \ + .opacity = 1.0f, \ + .mat_nr = -1, \ + } + /* clang-format off */ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index c91afa58cb1..d3429329ef6 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -56,6 +56,7 @@ typedef enum GpencilModifierType { eGpencilModifierType_Lineart = 19, eGpencilModifierType_Length = 20, eGpencilModifierType_Weight = 21, + eGpencilModifierType_Dash = 22, /* Keep last. */ NUM_GREASEPENCIL_MODIFIER_TYPES, } GpencilModifierType; @@ -507,6 +508,39 @@ typedef enum eLengthGpencil_Type { GP_LENGTH_ABSOLUTE = 1, } eLengthGpencil_Type; +typedef struct DashGpencilModifierSegment { + char name[64]; + /* For path reference. */ + struct DashGpencilModifierData *dmd; + int dash; + int gap; + float radius; + float opacity; + int mat_nr; + int _pad; +} DashGpencilModifierSegment; + +typedef struct DashGpencilModifierData { + GpencilModifierData modifier; + /** Material for filtering. */ + struct Material *material; + /** Layer name. */ + char layername[64]; + /** Custom index for passes. */ + int pass_index; + /** Flags. */ + int flag; + /** Custom index for passes. */ + int layer_pass; + + int dash_offset; + + DashGpencilModifierSegment *segments; + int segments_len; + int segment_active_index; + +} DashGpencilModifierData; + typedef struct MirrorGpencilModifierData { GpencilModifierData modifier; struct Object *object; diff --git a/source/blender/makesdna/DNA_image_types.h b/source/blender/makesdna/DNA_image_types.h index 22408687daf..30ca9540735 100644 --- a/source/blender/makesdna/DNA_image_types.h +++ b/source/blender/makesdna/DNA_image_types.h @@ -94,11 +94,17 @@ typedef struct RenderSlot { struct RenderResult *render; } RenderSlot; -typedef struct ImageTile_Runtime { +typedef struct ImageTile_RuntimeTextureSlot { int tilearray_layer; int _pad; int tilearray_offset[2]; int tilearray_size[2]; +} ImageTile_RuntimeTextureSlot; + +typedef struct ImageTile_Runtime { + /* Data per `eImageTextureResolution`. + * Should match `IMA_TEXTURE_RESOLUTION_LEN` */ + ImageTile_RuntimeTextureSlot slots[2]; } ImageTile_Runtime; typedef struct ImageTile { @@ -132,6 +138,15 @@ typedef enum eGPUTextureTarget { TEXTARGET_COUNT, } eGPUTextureTarget; +/* Resolution variations that can be cached for an image. */ +typedef enum eImageTextureResolution { + IMA_TEXTURE_RESOLUTION_FULL = 0, + IMA_TEXTURE_RESOLUTION_LIMITED, + + /* Not an option, but holds the number of options defined for this struct. */ + IMA_TEXTURE_RESOLUTION_LEN +} eImageTextureResolution; + typedef struct Image { ID id; @@ -140,8 +155,8 @@ typedef struct Image { /** Not written in file. */ struct MovieCache *cache; - /** Not written in file 3 = TEXTARGET_COUNT, 2 = stereo eyes. */ - struct GPUTexture *gputexture[3][2]; + /** Not written in file 3 = TEXTARGET_COUNT, 2 = stereo eyes, 2 = IMA_TEXTURE_RESOLUTION_LEN. */ + struct GPUTexture *gputexture[3][2][2]; /* sources from: */ ListBase anims; @@ -233,12 +248,15 @@ enum { IMA_GPU_PARTIAL_REFRESH = (1 << 1), /** All mipmap levels in OpenGL texture set? */ IMA_GPU_MIPMAP_COMPLETE = (1 << 2), - /** Current texture resolution won't be limited by the GL Texture Limit user preference. */ - IMA_GPU_MAX_RESOLUTION = (1 << 3), + /* Reuse the max resolution textures as they fit in the limited scale. */ + IMA_GPU_REUSE_MAX_RESOLUTION = (1 << 3), + /* Has any limited scale textures been allocated. + * Adds additional checks to reuse max resolution images when they fit inside limited scale. */ + IMA_GPU_HAS_LIMITED_SCALE_TEXTURES = (1 << 4), }; /* Image.source, where the image comes from */ -enum { +typedef enum eImageSource { /* IMA_SRC_CHECK = 0, */ /* UNUSED */ IMA_SRC_FILE = 1, IMA_SRC_SEQUENCE = 2, @@ -246,10 +264,10 @@ enum { IMA_SRC_GENERATED = 4, IMA_SRC_VIEWER = 5, IMA_SRC_TILED = 6, -}; +} eImageSource; /* Image.type, how to handle or generate the image */ -enum { +typedef enum eImageType { IMA_TYPE_IMAGE = 0, IMA_TYPE_MULTILAYER = 1, /* generated */ @@ -257,7 +275,7 @@ enum { /* viewers */ IMA_TYPE_R_RESULT = 4, IMA_TYPE_COMPOSITE = 5, -}; +} eImageType; /* Image.gen_type */ enum { diff --git a/source/blender/makesdna/DNA_lineart_types.h b/source/blender/makesdna/DNA_lineart_types.h index cdb09c3af50..bdc9bcb6980 100644 --- a/source/blender/makesdna/DNA_lineart_types.h +++ b/source/blender/makesdna/DNA_lineart_types.h @@ -47,6 +47,8 @@ typedef enum eLineartMainFlags { LRT_CHAIN_LOOSE_EDGES = (1 << 12), LRT_CHAIN_GEOMETRY_SPACE = (1 << 13), LRT_ALLOW_OVERLAP_EDGE_TYPES = (1 << 14), + LRT_USE_CREASE_ON_SMOOTH_SURFACES = (1 << 15), + LRT_USE_CREASE_ON_SHARP_EDGES = (1 << 16), } eLineartMainFlags; typedef enum eLineartEdgeFlag { diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h index 1b3dbd148df..5b2694f420b 100644 --- a/source/blender/makesdna/DNA_modifier_defaults.h +++ b/source/blender/makesdna/DNA_modifier_defaults.h @@ -419,10 +419,6 @@ .velocity_scale = 1.0f, \ .reader = NULL, \ .reader_object_path = "", \ - .vertex_velocities = NULL, \ - .num_vertices = 0, \ - .velocity_delta = 0.0f, \ - .last_lookup_time = 0.0f, \ } #define _DNA_DEFAULT_MirrorModifierData \ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 13213f70fed..31daa778b03 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -1942,6 +1942,7 @@ typedef struct MeshCacheModifierData { float factor; char deform_mode; + char defgrp_name[64]; char _pad[7]; /* play_mode == MOD_MESHCACHE_PLAY_CFEA */ @@ -1958,6 +1959,11 @@ typedef struct MeshCacheModifierData { char filepath[1024]; } MeshCacheModifierData; +/* MeshCache modifier flags. */ +enum { + MOD_MESHCACHE_INVERT_VERTEX_GROUP = 1 << 0, +}; + enum { MOD_MESHCACHE_TYPE_MDD = 1, MOD_MESHCACHE_TYPE_PC2 = 2, @@ -2138,10 +2144,6 @@ enum { MOD_NORMALEDIT_MIX_MUL = 3, }; -typedef struct MeshCacheVertexVelocity { - float vel[3]; -} MeshCacheVertexVelocity; - typedef struct MeshSeqCacheModifierData { ModifierData modifier; @@ -2157,25 +2159,6 @@ typedef struct MeshSeqCacheModifierData { /* Runtime. */ struct CacheReader *reader; char reader_object_path[1024]; - - /* Vertex velocities read from the cache. The velocities are not automatically read during - * modifier execution, and therefore have to manually be read when needed. This is only used - * through the RNA for now. */ - struct MeshCacheVertexVelocity *vertex_velocities; - - /* The number of vertices of the Alembic mesh, set when the modifier is executed. */ - int num_vertices; - - /* Time (in frames or seconds) between two velocity samples. Automatically computed to - * scale the velocity vectors at render time for generating proper motion blur data. */ - float velocity_delta; - - /* Caches the scene time (in seconds) used to lookup data in the Alembic archive when the - * modifier was last executed. Used to access Alembic samples through the RNA. */ - float last_lookup_time; - - int _pad1; - void *_pad2; } MeshSeqCacheModifierData; /* MeshSeqCacheModifierData.read_flag */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 43dd4b4270e..f4c88333528 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -216,6 +216,16 @@ typedef enum eNodeSocketFlag { SOCK_HIDE_LABEL = (1 << 12), } eNodeSocketFlag; +/** Workaround to forward-declare C++ type in C header. */ +#ifdef __cplusplus +namespace blender::nodes { +class NodeDeclaration; +} +using NodeDeclarationHandle = blender::nodes::NodeDeclaration; +#else +typedef struct NodeDeclarationHandle NodeDeclarationHandle; +#endif + /* TODO: Limit data in bNode to what we want to see saved. */ typedef struct bNode { struct bNode *next, *prev, *new_node; @@ -315,6 +325,26 @@ typedef struct bNode { * needs to be a float to feed GPU_uniform. */ float sss_id; + + /** + * Describes the desired interface of the node. This is run-time data only. + * The actual interface of the node may deviate from the declaration temporarily. + * It's possible to sync the actual state of the node to the desired state. Currently, this is + * only done when a node is created or loaded. + * + * In the future, we may want to keep more data only in the declaration, so that it does not have + * to be synced to other places that are stored in files. That especially applies to data that + * can't be edited by users directly (e.g. min/max values of sockets, tooltips, ...). + * + * The declaration of a node can be recreated at any time when it is used. Caching it here is + * just a bit more efficient when it is used a lot. To make sure that the cache is up-to-date, + * call #nodeDeclarationEnsure before using it. + * + * Currently, the declaration is the same for every node of the same type. Going forward, that is + * intended to change though. Especially when nodes become more dynamic with respect to how many + * sockets they have. + */ + NodeDeclarationHandle *declaration; } bNode; /* node->flag */ @@ -1137,6 +1167,7 @@ typedef struct NodeCryptomatte { typedef struct NodeDenoise { char hdr; + char prefilter; } NodeDenoise; typedef struct NodeAttributeClamp { @@ -1402,7 +1433,7 @@ typedef struct NodeGeometryCurvePrimitiveQuad { } NodeGeometryCurvePrimitiveQuad; typedef struct NodeGeometryCurveResample { - /* GeometryNodeCurveSampleMode. */ + /* GeometryNodeCurveResampleMode. */ uint8_t mode; } NodeGeometryCurveResample; @@ -1412,12 +1443,12 @@ typedef struct NodeGeometryCurveSubdivide { } NodeGeometryCurveSubdivide; typedef struct NodeGeometryCurveTrim { - /* GeometryNodeCurveInterpolateMode. */ + /* GeometryNodeCurveSampleMode. */ uint8_t mode; } NodeGeometryCurveTrim; typedef struct NodeGeometryCurveToPoints { - /* GeometryNodeCurveSampleMode. */ + /* GeometryNodeCurveResampleMode. */ uint8_t mode; } NodeGeometryCurveToPoints; @@ -1441,6 +1472,13 @@ typedef struct NodeGeometryCurveFill { uint8_t mode; } NodeGeometryCurveFill; +typedef struct NodeGeometryAttributeCapture { + /* CustomDataType. */ + int8_t data_type; + /* AttributeDomain. */ + int8_t domain; +} NodeGeometryAttributeCapture; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -1803,6 +1841,14 @@ typedef enum CMPNodeSetAlphaMode { CMP_NODE_SETALPHA_MODE_REPLACE_ALPHA = 1, } CMPNodeSetAlphaMode; +/* Denoise Node. */ +/* `NodeDenoise.prefilter` */ +typedef enum CMPNodeDenoisePrefilter { + CMP_NODE_DENOISE_PREFILTER_FAST = 0, + CMP_NODE_DENOISE_PREFILTER_NONE = 1, + CMP_NODE_DENOISE_PREFILTER_ACCURATE = 2 +} CMPNodeDenoisePrefilter; + #define CMP_NODE_PLANETRACKDEFORM_MBLUR_SAMPLES_MAX 64 /* Point Density shader node */ @@ -1889,6 +1935,7 @@ typedef enum GeometryNodeTriangulateQuads { typedef enum GeometryNodePointInstanceType { GEO_NODE_POINT_INSTANCE_TYPE_OBJECT = 0, GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION = 1, + GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY = 2, } GeometryNodePointInstanceType; typedef enum GeometryNodePointInstanceFlag { @@ -1991,17 +2038,17 @@ typedef enum GeometryNodeCurvePrimitiveBezierSegmentMode { GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_OFFSET = 1, } GeometryNodeCurvePrimitiveBezierSegmentMode; +typedef enum GeometryNodeCurveResampleMode { + GEO_NODE_CURVE_RESAMPLE_COUNT = 0, + GEO_NODE_CURVE_RESAMPLE_LENGTH = 1, + GEO_NODE_CURVE_RESAMPLE_EVALUATED = 2, +} GeometryNodeCurveResampleMode; + typedef enum GeometryNodeCurveSampleMode { - GEO_NODE_CURVE_SAMPLE_COUNT = 0, + GEO_NODE_CURVE_SAMPLE_FACTOR = 0, GEO_NODE_CURVE_SAMPLE_LENGTH = 1, - GEO_NODE_CURVE_SAMPLE_EVALUATED = 2, } GeometryNodeCurveSampleMode; -typedef enum GeometryNodeCurveInterpolateMode { - GEO_NODE_CURVE_INTERPOLATE_FACTOR = 0, - GEO_NODE_CURVE_INTERPOLATE_LENGTH = 1, -} GeometryNodeCurveInterpolateMode; - typedef enum GeometryNodeAttributeTransferMapMode { GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED = 0, GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST = 1, diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 0250d853898..5a88ce7c9f5 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -158,8 +158,7 @@ typedef struct Object_Runtime { struct ID *data_orig; /** * Object data structure created during object evaluation. It has all modifiers applied. - * The type is determined by the type of the original object. For example, for mesh and curve - * objects, this is a mesh. For a volume object, this is a volume. + * The type is determined by the type of the original object. */ struct ID *data_eval; diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 863c53615c1..6505816256c 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -576,6 +576,36 @@ typedef enum eSpaceNla_Flag { /** \name Sequence Editor * \{ */ +typedef struct SequencerPreviewOverlay { + int flag; + char _pad0[4]; +} SequencerPreviewOverlay; + +/* SequencerPreviewOverlay.flag */ +typedef enum eSpaceSeq_SequencerPreviewOverlay_Flag { + SEQ_PREVIEW_SHOW_SAFE_MARGINS = (1 << 3), + SEQ_PREVIEW_SHOW_GPENCIL = (1 << 4), + SEQ_PREVIEW_SHOW_SAFE_CENTER = (1 << 9), + SEQ_PREVIEW_SHOW_METADATA = (1 << 10), +} eSpaceSeq_SequencerPreviewOverlay_Flag; + +typedef struct SequencerTimelineOverlay { + int flag; + char _pad0[4]; +} SequencerTimelineOverlay; + +/* SequencerTimelineOverlay.flag */ +typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag { + SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1), + SEQ_TIMELINE_SHOW_FCURVES = (1 << 5), + SEQ_TIMELINE_ALL_WAVEFORMS = (1 << 7), /* draw all waveforms */ + SEQ_TIMELINE_NO_WAVEFORMS = (1 << 8), /* draw no waveforms */ + SEQ_TIMELINE_SHOW_STRIP_NAME = (1 << 14), + SEQ_TIMELINE_SHOW_STRIP_SOURCE = (1 << 15), + SEQ_TIMELINE_SHOW_STRIP_DURATION = (1 << 16), + SEQ_TIMELINE_SHOW_GRID = (1 << 18), +} eSpaceSeq_SequencerTimelineOverlay_Flag; + /* Sequencer */ typedef struct SpaceSeq { SpaceLink *next, *prev; @@ -612,10 +642,13 @@ typedef struct SpaceSeq { /** Different scoped displayed in space. */ struct SequencerScopes scopes; + struct SequencerPreviewOverlay preview_overlay; + struct SequencerTimelineOverlay timeline_overlay; /** Multiview current eye - for internal use. */ char multiview_eye; char _pad2[7]; + } SpaceSeq; /* SpaceSeq.mainb */ @@ -630,7 +663,7 @@ typedef enum eSpaceSeq_RegionType { /* SpaceSeq.draw_flag */ typedef enum eSpaceSeq_DrawFlag { SEQ_DRAW_BACKDROP = (1 << 0), - SEQ_DRAW_OFFSET_EXT = (1 << 1), + SEQ_DRAW_UNUSED_1 = (1 << 1), SEQ_DRAW_TRANSFORM_PREVIEW = (1 << 2), } eSpaceSeq_DrawFlag; @@ -639,22 +672,19 @@ typedef enum eSpaceSeq_Flag { SEQ_DRAWFRAMES = (1 << 0), SEQ_MARKER_TRANS = (1 << 1), SEQ_DRAW_COLOR_SEPARATED = (1 << 2), - SEQ_SHOW_SAFE_MARGINS = (1 << 3), - SEQ_SHOW_GPENCIL = (1 << 4), - SEQ_SHOW_FCURVES = (1 << 5), - SEQ_USE_ALPHA = (1 << 6), /* use RGBA display mode for preview */ - SEQ_ALL_WAVEFORMS = (1 << 7), /* draw all waveforms */ - SEQ_NO_WAVEFORMS = (1 << 8), /* draw no waveforms */ - SEQ_SHOW_SAFE_CENTER = (1 << 9), - SEQ_SHOW_METADATA = (1 << 10), + SPACE_SEQ_FLAG_UNUSED_3 = (1 << 3), + SPACE_SEQ_FLAG_UNUSED_4 = (1 << 4), + SPACE_SEQ_FLAG_UNUSED_5 = (1 << 5), + SEQ_USE_ALPHA = (1 << 6), /* use RGBA display mode for preview */ + SPACE_SEQ_FLAG_UNUSED_9 = (1 << 9), + SPACE_SEQ_FLAG_UNUSED_10 = (1 << 10), SEQ_SHOW_MARKERS = (1 << 11), /* show markers region */ SEQ_ZOOM_TO_FIT = (1 << 12), - SEQ_SHOW_STRIP_OVERLAY = (1 << 13), - SEQ_SHOW_STRIP_NAME = (1 << 14), - SEQ_SHOW_STRIP_SOURCE = (1 << 15), - SEQ_SHOW_STRIP_DURATION = (1 << 16), + SEQ_SHOW_OVERLAY = (1 << 13), + SPACE_SEQ_FLAG_UNUSED_14 = (1 << 14), + SPACE_SEQ_FLAG_UNUSED_15 = (1 << 15), + SPACE_SEQ_FLAG_UNUSED_16 = (1 << 16), SEQ_USE_PROXIES = (1 << 17), - SEQ_SHOW_GRID = (1 << 18), } eSpaceSeq_Flag; /* SpaceSeq.view */ @@ -913,6 +943,13 @@ enum eFileDetails { #define FILE_MAX_LIBEXTRA (FILE_MAX + MAX_ID_NAME) +/** + * Maximum level of recursions accepted for #FileSelectParams.recursion_level. Rather than a + * completely arbitrary limit or none at all, make it just enough to support the most extreme case + * where the maximal path length is used with single letter directory/file names only. + */ +#define FILE_SELECT_MAX_RECURSIONS (FILE_MAX_LIBEXTRA / 2) + /* filesel types */ typedef enum eFileSelectType { FILE_LOADLIB = 1, @@ -936,13 +973,13 @@ typedef enum eFileSel_Action { * (WM and BLO code area, see #eBLOLibLinkFlags in BLO_readfile.h). */ typedef enum eFileSel_Params_Flag { - FILE_PARAMS_FLAG_UNUSED_1 = (1 << 0), /* cleared */ + FILE_APPEND_SET_FAKEUSER = (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_PARAMS_FLAG_UNUSED_6 = (1 << 6), /* cleared */ + FILE_APPEND_RECURSIVE = (1 << 6), FILE_DIRSEL_ONLY = (1 << 7), FILE_FILTER = (1 << 8), FILE_OBDATA_INSTANCE = (1 << 9), diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 27376432092..4f86201ced2 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -646,7 +646,8 @@ typedef struct UserDef_Experimental { char use_sculpt_tools_tilt; char use_extended_asset_browser; char use_override_templates; - char _pad[5]; + char use_geometry_nodes_fields; + char _pad[4]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; @@ -924,9 +925,10 @@ typedef struct UserDef { short sequencer_proxy_setup; /* eUserpref_SeqProxySetup */ float collection_instance_empty_size; - char _pad10[3]; + char _pad10[2]; - char statusbar_flag; /* eUserpref_StatusBar_Flag */ + char file_preview_type; /* eUserpref_File_Preview_Type */ + char statusbar_flag; /* eUserpref_StatusBar_Flag */ struct WalkNavigation walk_navigation; @@ -996,7 +998,7 @@ typedef enum eUserPref_Flag { USER_NONUMPAD = (1 << 13), USER_ADD_CURSORALIGNED = (1 << 14), USER_FILECOMPRESS = (1 << 15), - USER_SAVE_PREVIEWS = (1 << 16), + USER_FLAG_UNUSED_5 = (1 << 16), /* dirty */ USER_CUSTOM_RANGE = (1 << 17), USER_ADD_EDITMODE = (1 << 18), USER_ADD_VIEWALIGNED = (1 << 19), @@ -1010,6 +1012,14 @@ typedef enum eUserPref_Flag { USER_FLAG_UNUSED_27 = (1 << 27), /* dirty */ } eUserPref_Flag; +/** #UserDef.file_preview_type */ +typedef enum eUserpref_File_Preview_Type { + USER_FILE_PREVIEW_NONE = 0, + USER_FILE_PREVIEW_AUTO, + USER_FILE_PREVIEW_SCREENSHOT, + USER_FILE_PREVIEW_CAMERA, +} eUserpref_File_Preview_Type; + typedef enum eUserPref_PrefFlag { USER_PREF_FLAG_SAVE = (1 << 0), } eUserPref_PrefFlag; @@ -1126,7 +1136,9 @@ typedef enum eUserpref_TableAPI { /** #UserDef.app_flag */ typedef enum eUserpref_APP_Flag { - USER_APP_LOCK_UI_LAYOUT = (1 << 0), + USER_APP_LOCK_CORNER_SPLIT = (1 << 0), + USER_APP_HIDE_REGION_TOGGLE = (1 << 1), + USER_APP_LOCK_EDGE_RESIZE = (1 << 2), } eUserpref_APP_Flag; /** #UserDef.statusbar_flag */ diff --git a/source/blender/makesdna/DNA_uuid_types.h b/source/blender/makesdna/DNA_uuid_types.h new file mode 100644 index 00000000000..30c8beaa628 --- /dev/null +++ b/source/blender/makesdna/DNA_uuid_types.h @@ -0,0 +1,43 @@ +/* + * 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 DNA + */ + +#pragma once + +#include "DNA_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Universally Unique Identifier according to RFC4122. + */ +typedef struct UUID { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi_and_reserved; + uint8_t clock_seq_low; + uint8_t node[6]; +} UUID; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index a573e2f9e8c..4cb8610f6ac 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -68,6 +68,8 @@ * #BLO_update_defaults_startup_blend & #blo_do_versions_userdef. */ +#define DNA_DEPRECATED_ALLOW + #include <limits.h> #include <stdio.h> #include <stdlib.h> @@ -319,6 +321,8 @@ SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(WeightGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierSegment); #undef SDNA_DEFAULT_DECL_STRUCT @@ -547,6 +551,8 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { SDNA_DEFAULT_DECL(WeightGpencilModifierData), SDNA_DEFAULT_DECL(LineartGpencilModifierData), SDNA_DEFAULT_DECL(LengthGpencilModifierData), + SDNA_DEFAULT_DECL(DashGpencilModifierData), + SDNA_DEFAULT_DECL(DashGpencilModifierSegment), }; #undef SDNA_DEFAULT_DECL #undef SDNA_DEFAULT_DECL_EX diff --git a/source/blender/makesdna/intern/makesdna.c b/source/blender/makesdna/intern/makesdna.c index 061c3462a69..9957808b63d 100644 --- a/source/blender/makesdna/intern/makesdna.c +++ b/source/blender/makesdna/intern/makesdna.c @@ -141,6 +141,7 @@ static const char *includefiles[] = { "DNA_volume_types.h", "DNA_simulation_types.h", "DNA_pointcache_types.h", + "DNA_uuid_types.h", "DNA_asset_types.h", /* see comment above before editing! */ @@ -1133,7 +1134,7 @@ static int calculate_struct_sizes(int firststruct, FILE *file_verify, const char * to the struct to resolve the problem. */ if ((size_64 % max_align_64 == 0) && (size_32 % max_align_32 == 4)) { fprintf(stderr, - "Sizeerror in 32 bit struct: %s (add paddding pointer)\n", + "Sizeerror in 32 bit struct: %s (add padding pointer)\n", types[structtype]); } else { @@ -1678,6 +1679,7 @@ int main(int argc, char **argv) #include "DNA_texture_types.h" #include "DNA_tracking_types.h" #include "DNA_userdef_types.h" +#include "DNA_uuid_types.h" #include "DNA_vec_types.h" #include "DNA_vfont_types.h" #include "DNA_view2d_types.h" diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 75057c1a071..ce53e3390e1 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -177,6 +177,7 @@ extern StructRNA RNA_CompositorNodeMixRGB; extern StructRNA RNA_CompositorNodeNormal; extern StructRNA RNA_CompositorNodeNormalize; extern StructRNA RNA_CompositorNodeOutputFile; +extern StructRNA RNA_CompositorNodePosterize; extern StructRNA RNA_CompositorNodePremulKey; extern StructRNA RNA_CompositorNodeRGB; extern StructRNA RNA_CompositorNodeRGBToBW; @@ -221,6 +222,8 @@ extern StructRNA RNA_CurvePoint; extern StructRNA RNA_CurveProfile; extern StructRNA RNA_CurveProfilePoint; extern StructRNA RNA_DampedTrackConstraint; +extern StructRNA RNA_DashGpencilModifierData; +extern StructRNA RNA_DashGpencilModifierSegment; extern StructRNA RNA_DataTransferModifier; extern StructRNA RNA_DecimateModifier; extern StructRNA RNA_Depsgraph; @@ -1117,7 +1120,9 @@ bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop); char *RNA_path_append( const char *path, PointerRNA *ptr, PropertyRNA *prop, int intkey, const char *strkey); +#if 0 /* UNUSED. */ char *RNA_path_back(const char *path); +#endif /* path_resolve() variants only ensure that a valid pointer (and optionally property) exist */ bool RNA_path_resolve(PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop); diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 8f8ad077935..eb887e1881b 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -931,11 +931,10 @@ static void rna_ID_user_remap(ID *id, Main *bmain, ID *new_id) static struct ID *rna_ID_make_local(struct ID *self, Main *bmain, bool clear_proxy) { - BKE_lib_id_make_local( - bmain, self, false, clear_proxy ? 0 : LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); + BKE_lib_id_make_local(bmain, self, clear_proxy ? 0 : LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); ID *ret_id = self->newid ? self->newid : self; - BKE_id_clear_newpoin(self); + BKE_id_newptr_and_tag_clear(self); return ret_id; } diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 0ba5b786187..fceb6d045c3 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -5521,7 +5521,9 @@ char *RNA_path_append( return result; } -char *RNA_path_back(const char *path) +/* Having both path append & back seems like it could be useful, + * this function isn't used at the moment. */ +static UNUSED_FUNCTION_WITH_RETURN_TYPE(char *, RNA_path_back)(const char *path) { char fixedbuf[256]; const char *previous, *current; diff --git a/source/blender/makesrna/intern/rna_access_compare_override.c b/source/blender/makesrna/intern/rna_access_compare_override.c index 2c552970c82..f8a36c1b2e6 100644 --- a/source/blender/makesrna/intern/rna_access_compare_override.c +++ b/source/blender/makesrna/intern/rna_access_compare_override.c @@ -788,7 +788,7 @@ bool RNA_struct_override_matches(Main *bmain, continue; } - CLOG_INFO(&LOG, 5, "Override Checking %s\n", rna_path); + CLOG_INFO(&LOG, 5, "Override Checking %s", rna_path); IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(override, rna_path); if (ignore_overridden && op != NULL) { diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index b4ea70c33ab..49e813e6a6c 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -41,8 +41,8 @@ const EnumPropertyItem rna_enum_attribute_type_items[] = { {CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"}, {CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"}, {CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"}, - {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point precisions"}, - {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit precision"}, + {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point values"}, + {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit values"}, {CD_PROP_STRING, "STRING", 0, "String", "Text string"}, {CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"}, {CD_PROP_FLOAT2, "FLOAT2", 0, "2D Vector", "2D vector with floating-point values"}, @@ -54,8 +54,8 @@ const EnumPropertyItem rna_enum_attribute_type_with_auto_items[] = { {CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"}, {CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"}, {CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"}, - {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point precisions"}, - {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit precision"}, + {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point values"}, + {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit values"}, {CD_PROP_STRING, "STRING", 0, "String", "Text string"}, {CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"}, {CD_PROP_FLOAT2, "FLOAT2", 0, "2D Vector", "2D vector with floating-point values"}, @@ -443,7 +443,7 @@ static void rna_def_attribute_float_vector(BlenderRNA *brna) srna = RNA_def_struct(brna, "FloatVectorAttribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Float Vector Attribute", "Vector geometry attribute, with floating-point precision"); + srna, "Float Vector Attribute", "Vector geometry attribute, with floating-point values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "FloatVectorAttributeValue"); @@ -479,7 +479,7 @@ static void rna_def_attribute_float_color(BlenderRNA *brna) srna = RNA_def_struct(brna, "FloatColorAttribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Float Color Attribute", "Color geometry attribute, with floating-point precision"); + srna, "Float Color Attribute", "Color geometry attribute, with floating-point values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "FloatColorAttributeValue"); @@ -514,7 +514,7 @@ static void rna_def_attribute_byte_color(BlenderRNA *brna) srna = RNA_def_struct(brna, "ByteColorAttribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Byte Color Attribute", "Color geometry attribute, with 8-bit precision"); + srna, "Byte Color Attribute", "Color geometry attribute, with 8-bit values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "ByteColorAttributeValue"); @@ -639,7 +639,7 @@ static void rna_def_attribute_float2(BlenderRNA *brna) srna = RNA_def_struct(brna, "Float2Attribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Float2 Attribute", "2D vector geometry attribute, with floating-point precision"); + srna, "Float2 Attribute", "2D vector geometry attribute, with floating-point values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "Float2AttributeValue"); diff --git a/source/blender/makesrna/intern/rna_color.c b/source/blender/makesrna/intern/rna_color.c index 1ac6dd021e9..5c12fc3a227 100644 --- a/source/blender/makesrna/intern/rna_color.c +++ b/source/blender/makesrna/intern/rna_color.c @@ -189,7 +189,7 @@ static char *rna_ColorRamp_path(PointerRNA *ptr) SH_NODE_VALTORGB, CMP_NODE_VALTORGB, TEX_NODE_VALTORGB, - GEO_NODE_ATTRIBUTE_COLOR_RAMP)) { + GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP)) { if (node->storage == ptr->data) { /* all node color ramp properties called 'color_ramp' * prepend path from ID to the node @@ -320,7 +320,7 @@ static void rna_ColorRamp_update(Main *bmain, Scene *UNUSED(scene), PointerRNA * SH_NODE_VALTORGB, CMP_NODE_VALTORGB, TEX_NODE_VALTORGB, - GEO_NODE_ATTRIBUTE_COLOR_RAMP)) { + GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP)) { ED_node_tag_update_nodetree(bmain, ntree, node); } } diff --git a/source/blender/makesrna/intern/rna_curve.c b/source/blender/makesrna/intern/rna_curve.c index 9c6659a7130..0bfb1200f49 100644 --- a/source/blender/makesrna/intern/rna_curve.c +++ b/source/blender/makesrna/intern/rna_curve.c @@ -1809,12 +1809,6 @@ static void rna_def_curve(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Twist Smooth", "Smoothing iteration for tangents"); RNA_def_property_update(prop, 0, "rna_Curve_update_data"); - prop = RNA_def_property(srna, "use_fill_deform", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", CU_DEFORM_FILL); - RNA_def_property_ui_text( - prop, "Fill Deformed", "Fill curve after applying shape keys and all modifiers"); - RNA_def_property_update(prop, 0, "rna_Curve_update_data"); - prop = RNA_def_property(srna, "use_fill_caps", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", CU_FILL_CAPS); RNA_def_property_ui_text(prop, "Fill Caps", "Fill caps for beveled curves"); diff --git a/source/blender/makesrna/intern/rna_fluid.c b/source/blender/makesrna/intern/rna_fluid.c index 10e899b7ee3..90e77406f23 100644 --- a/source/blender/makesrna/intern/rna_fluid.c +++ b/source/blender/makesrna/intern/rna_fluid.c @@ -1242,22 +1242,6 @@ static void rna_Fluid_flowtype_set(struct PointerRNA *ptr, int value) #else -static void rna_def_fluid_mesh_vertices(BlenderRNA *brna) -{ - StructRNA *srna; - PropertyRNA *prop; - - srna = RNA_def_struct(brna, "FluidDomainVertexVelocity", NULL); - RNA_def_struct_ui_text(srna, "Fluid Mesh Velocity", "Velocity of a simulated fluid mesh"); - RNA_def_struct_ui_icon(srna, ICON_VERTEXSEL); - - prop = RNA_def_property(srna, "velocity", PROP_FLOAT, PROP_VELOCITY); - RNA_def_property_array(prop, 3); - RNA_def_property_float_sdna(prop, NULL, "vel"); - RNA_def_property_ui_text(prop, "Velocity", ""); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); -} - static void rna_def_fluid_domain_settings(BlenderRNA *brna) { StructRNA *srna; @@ -2019,14 +2003,6 @@ static void rna_def_fluid_domain_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Mesh generator", "Which particle level set generator to use"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Fluid_update"); - prop = RNA_def_property(srna, "mesh_vertices", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "mesh_velocities", "totvert"); - RNA_def_property_struct_type(prop, "FluidDomainVertexVelocity"); - RNA_def_property_ui_text( - prop, "Fluid Mesh Vertices", "Vertices of the fluid mesh generated by simulation"); - - rna_def_fluid_mesh_vertices(brna); - prop = RNA_def_property(srna, "use_mesh", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flags", FLUID_DOMAIN_USE_MESH); RNA_def_property_ui_text(prop, "Use Mesh", "Enable fluid mesh (using amplification)"); diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index f06c8a5325c..2bce5f3f4b3 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -843,17 +843,17 @@ static float rna_GPencilStrokePoints_weight_get(bGPDstroke *stroke, return -1.0f; } - if (dvert->totweight <= vertex_group_index || vertex_group_index < 0) { - BKE_report(reports, RPT_ERROR, "Groups: index out of range"); - return -1.0f; - } - if (stroke->totpoints <= point_index || point_index < 0) { BKE_report(reports, RPT_ERROR, "GPencilStrokePoints: index out of range"); return -1.0f; } MDeformVert *pt_dvert = stroke->dvert + point_index; + if ((pt_dvert) && (pt_dvert->totweight <= vertex_group_index || vertex_group_index < 0)) { + BKE_report(reports, RPT_ERROR, "Groups: index out of range"); + return -1.0f; + } + MDeformWeight *dw = BKE_defvert_find_index(pt_dvert, vertex_group_index); if (dw) { return dw->weight; diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 4e95174e42b..4fa33424994 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -35,6 +35,7 @@ #include "BLI_math.h" #include "BLI_rand.h" +#include "BLI_string_utils.h" #include "BLT_translation.h" @@ -68,9 +69,14 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_BUILD, "Build", "Create duplication of strokes"}, + {eGpencilModifierType_Dash, + "GP_DASH", + ICON_MOD_DASH, + "Dot Dash", + "Generate dot-dash styled strokes"}, {eGpencilModifierType_Lineart, "GP_LINEART", - ICON_MOD_EDGESPLIT, /* TODO: Use a proper icon. */ + ICON_MOD_LINEART, "Line Art", "Generate line art strokes from selected source"}, {eGpencilModifierType_Mirror, @@ -116,7 +122,7 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { "Deform strokes using lattice"}, {eGpencilModifierType_Length, "GP_LENGTH", - ICON_MOD_EDGESPLIT, + ICON_MOD_LENGTH, "Length", "Extend or shrink strokes"}, {eGpencilModifierType_Noise, "GP_NOISE", ICON_MOD_NOISE, "Noise", "Add noise to strokes"}, @@ -268,6 +274,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr) return &RNA_TextureGpencilModifier; case eGpencilModifierType_Lineart: return &RNA_LineartGpencilModifier; + case eGpencilModifierType_Dash: + return &RNA_DashGpencilModifierData; /* Default */ case eGpencilModifierType_None: case NUM_GREASEPENCIL_MODIFIER_TYPES: @@ -282,19 +290,19 @@ static void rna_GpencilModifier_name_set(PointerRNA *ptr, const char *value) GpencilModifierData *gmd = ptr->data; char oldname[sizeof(gmd->name)]; - /* make a copy of the old name first */ + /* Make a copy of the old name first. */ BLI_strncpy(oldname, gmd->name, sizeof(gmd->name)); - /* copy the new name into the name slot */ + /* Copy the new name into the name slot. */ BLI_strncpy_utf8(gmd->name, value, sizeof(gmd->name)); - /* make sure the name is truly unique */ + /* Make sure the name is truly unique. */ if (ptr->owner_id) { Object *ob = (Object *)ptr->owner_id; BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, gmd); } - /* fix all the animation data which may link to this */ + /* Fix all the animation data which may link to this. */ BKE_animdata_fix_paths_rename_all(NULL, "grease_pencil_modifiers", oldname, gmd->name); } @@ -674,6 +682,59 @@ static void rna_Lineart_end_level_set(PointerRNA *ptr, int value) lmd->level_start = MIN2(value, lmd->level_start); } +static void rna_GpencilDash_segments_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)ptr->data; + rna_iterator_array_begin( + iter, dmd->segments, sizeof(DashGpencilModifierSegment), dmd->segments_len, false, NULL); +} + +static char *rna_DashGpencilModifierSegment_path(PointerRNA *ptr) +{ + DashGpencilModifierSegment *ds = (DashGpencilModifierSegment *)ptr->data; + + DashGpencilModifierData *dmd = (DashGpencilModifierData *)ds->dmd; + + BLI_assert(dmd != NULL); + + char name_esc[sizeof(dmd->modifier.name) * 2 + 1]; + + BLI_str_escape(name_esc, dmd->modifier.name, sizeof(name_esc)); + + return BLI_sprintfN("grease_pencil_modifiers[\"%s\"].segments[\"%s\"]", name_esc, ds->name); +} + +static bool dash_segment_name_exists_fn(void *arg, const char *name) +{ + const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg; + for (int i = 0; i < dmd->segments_len; i++) { + if (STREQ(dmd->segments[i].name, name)) { + return true; + } + } + return false; +} + +static void rna_DashGpencilModifierSegment_name_set(PointerRNA *ptr, const char *value) +{ + DashGpencilModifierSegment *ds = ptr->data; + + char oldname[sizeof(ds->name)]; + BLI_strncpy(oldname, ds->name, sizeof(ds->name)); + + BLI_strncpy_utf8(ds->name, value, sizeof(ds->name)); + + BLI_assert(ds->dmd != NULL); + BLI_uniquename_cb( + dash_segment_name_exists_fn, ds->dmd, "Segment", '.', ds->name, sizeof(ds->name)); + + char prefix[256]; + sprintf(prefix, "grease_pencil_modifiers[\"%s\"].segments", ds->dmd->modifier.name); + + /* Fix all the animation data which may link to this. */ + BKE_animdata_fix_paths_rename_all(NULL, prefix, oldname, ds->name); +} + #else static void rna_def_modifier_gpencilnoise(BlenderRNA *brna) @@ -2902,7 +2963,7 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) RNA_def_struct_ui_text( srna, "Line Art Modifier", "Generate line art strokes from selected source"); RNA_def_struct_sdna(srna, "LineartGpencilModifierData"); - RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT); + RNA_def_struct_ui_icon(srna, ICON_MOD_LINEART); RNA_define_lib_overridable(true); @@ -3188,6 +3249,18 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Masks", "Mask bits to match from Collection Line Art settings"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "use_crease_on_smooth", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, NULL, "calculation_flags", LRT_USE_CREASE_ON_SMOOTH_SURFACES); + RNA_def_property_ui_text( + prop, "Crease On Smooth Surfaces", "Allow crease edges to show inside smooth surfaces"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_crease_on_sharp", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_USE_CREASE_ON_SHARP_EDGES); + RNA_def_property_ui_text(prop, "Crease On Sharp Edges", "Allow crease to show on sharp edges"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + RNA_define_lib_overridable(false); } @@ -3199,7 +3272,7 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna) srna = RNA_def_struct(brna, "LengthGpencilModifier", "GpencilModifier"); RNA_def_struct_ui_text(srna, "Length Modifier", "Stretch or shrink strokes"); RNA_def_struct_sdna(srna, "LengthGpencilModifierData"); - RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT); + RNA_def_struct_ui_icon(srna, ICON_MOD_LENGTH); RNA_define_lib_overridable(true); @@ -3275,6 +3348,136 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_modifier_gpencildash(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "DashGpencilModifierSegment", NULL); + RNA_def_struct_ui_text(srna, "Dash Modifier Segment", "Configuration for a single dash segment"); + RNA_def_struct_sdna(srna, "DashGpencilModifierSegment"); + RNA_def_struct_path_func(srna, "rna_DashGpencilModifierSegment_path"); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Name", "Name of the dash segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_DashGpencilModifierSegment_name_set"); + RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER | NA_RENAME, NULL); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "dash", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 1, INT16_MAX); + RNA_def_property_ui_text( + prop, + "Dash", + "The number of consecutive points from the original stroke to include in this segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "gap", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 1, INT16_MAX); + RNA_def_property_ui_text(prop, "Gap", "The number of points skipped after this segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "radius", PROP_FLOAT, PROP_FACTOR | PROP_UNSIGNED); + RNA_def_property_ui_range(prop, 0, 1, 0.1, 2); + RNA_def_property_ui_text( + prop, "Radius", "The factor to apply to the original point's radius for the new points"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "opacity", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_ui_range(prop, 0, 1, 0.1, 2); + RNA_def_property_ui_text( + prop, "Opacity", "The factor to apply to the original point's opacity for the new points"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "material_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "mat_nr"); + RNA_def_property_range(prop, -1, INT16_MAX); + RNA_def_property_ui_text( + prop, + "Material Index", + "Use this index on generated segment. -1 means using the existing material"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + srna = RNA_def_struct(brna, "DashGpencilModifierData", "GpencilModifier"); + RNA_def_struct_ui_text(srna, "Dash Modifier", "Create dot-dash effect for strokes"); + RNA_def_struct_sdna(srna, "DashGpencilModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_DASH); + + RNA_define_lib_overridable(true); + + prop = RNA_def_property(srna, "segments", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "DashGpencilModifierSegment"); + RNA_def_property_collection_sdna(prop, NULL, "segments", NULL); + RNA_def_property_collection_funcs(prop, + "rna_GpencilDash_segments_begin", + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_iterator_array_get", + NULL, + NULL, + NULL, + NULL); + RNA_def_property_ui_text(prop, "Segments", ""); + + prop = RNA_def_property(srna, "segment_active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Active Dash Segement Index", "Active index in the segment list"); + + prop = RNA_def_property(srna, "dash_offset", PROP_INT, PROP_NONE); + RNA_def_property_ui_text( + prop, + "Offset", + "Offset into each stroke before the beginning of the dashed segment generation"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + /* Common properties. */ + + prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "layername"); + RNA_def_property_ui_text(prop, "Layer", "Layer name"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "pass_index"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYER); + RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_MATERIAL); + RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_PASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "layer_pass"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Layer pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYERPASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + RNA_define_lib_overridable(false); +} + void RNA_def_greasepencil_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -3352,6 +3555,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna) rna_def_modifier_gpencilweight(brna); rna_def_modifier_gpencillineart(brna); rna_def_modifier_gpencillength(brna); + rna_def_modifier_gpencildash(brna); } #endif diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index e44ddb07d53..4a013dc9bd7 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -404,7 +404,7 @@ static void rna_Image_resolution_set(PointerRNA *ptr, const float *values) static int rna_Image_bindcode_get(PointerRNA *ptr) { Image *ima = (Image *)ptr->data; - GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0]; + GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0][IMA_TEXTURE_RESOLUTION_FULL]; return (tex) ? GPU_texture_opengl_bindcode(tex) : 0; } diff --git a/source/blender/makesrna/intern/rna_material.c b/source/blender/makesrna/intern/rna_material.c index 8d0d3adab8b..22a75c0d992 100644 --- a/source/blender/makesrna/intern/rna_material.c +++ b/source/blender/makesrna/intern/rna_material.c @@ -604,8 +604,10 @@ static void rna_def_material_greasepencil(BlenderRNA *brna) RNA_def_property_float_default(prop, 0.0f); RNA_def_property_range(prop, -DEG2RADF(90.0f), DEG2RADF(90.0f)); RNA_def_property_ui_range(prop, -DEG2RADF(90.0f), DEG2RADF(90.0f), 10, 3); - RNA_def_property_ui_text( - prop, "Rotation", "Additional rotation applied to dots and square strokes"); + RNA_def_property_ui_text(prop, + "Rotation", + "Additional rotation applied to dots and square texture of strokes. " + "Only applies in texture shading mode"); RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialGpencil_update"); /* pass index for future compositing and editing tools */ diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index f11d845c582..c99dfea524f 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -755,6 +755,7 @@ RNA_MOD_VGROUP_NAME_SET(LaplacianDeform, anchor_grp_name); RNA_MOD_VGROUP_NAME_SET(LaplacianSmooth, defgrp_name); RNA_MOD_VGROUP_NAME_SET(Lattice, name); RNA_MOD_VGROUP_NAME_SET(Mask, vgroup); +RNA_MOD_VGROUP_NAME_SET(MeshCache, defgrp_name); RNA_MOD_VGROUP_NAME_SET(MeshDeform, defgrp_name); RNA_MOD_VGROUP_NAME_SET(NormalEdit, defgrp_name); RNA_MOD_VGROUP_NAME_SET(Shrinkwrap, vgroup_name); @@ -1600,51 +1601,6 @@ static bool rna_Modifier_show_expanded_get(PointerRNA *ptr) return md->ui_expand_flag & UI_PANEL_DATA_EXPAND_ROOT; } -static int rna_MeshSequenceCacheModifier_has_velocity_get(PointerRNA *ptr) -{ -# ifdef WITH_ALEMBIC - MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data; - return ABC_has_vec3_array_property_named(mcmd->reader, mcmd->cache_file->velocity_name); -# else - return false; - UNUSED_VARS(ptr); -# endif -} - -static int rna_MeshSequenceCacheModifier_read_velocity_get(PointerRNA *ptr) -{ -# ifdef WITH_ALEMBIC - MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data; - - if (mcmd->num_vertices == 0) { - return 0; - } - - if (mcmd->vertex_velocities) { - MEM_freeN(mcmd->vertex_velocities); - } - - mcmd->vertex_velocities = MEM_mallocN(sizeof(MeshCacheVertexVelocity) * mcmd->num_vertices, - "Mesh Cache Velocities"); - - int num_read = ABC_read_velocity_cache(mcmd->reader, - mcmd->cache_file->velocity_name, - mcmd->last_lookup_time, - mcmd->velocity_scale * mcmd->velocity_delta, - mcmd->num_vertices, - (float *)mcmd->vertex_velocities); - - if (num_read == -1 || num_read != mcmd->num_vertices) { - return false; - } - - return true; -# else - return false; - UNUSED_VARS(ptr); -# endif -} - static bool rna_NodesModifier_node_group_poll(PointerRNA *UNUSED(ptr), PointerRNA value) { bNodeTree *ntree = value.data; @@ -6045,6 +6001,20 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Influence", "Influence of the deformation"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); + prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "defgrp_name"); + RNA_def_property_ui_text( + prop, + "Vertex Group", + "Name of the Vertex Group which determines the influence of the modifier per point"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_MeshCacheModifier_defgrp_name_set"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "invert_vertex_group", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_MESHCACHE_INVERT_VERTEX_GROUP); + RNA_def_property_ui_text(prop, "Invert", "Invert vertex group influence"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + /* -------------------------------------------------------------------- */ /* Axis Conversion */ prop = RNA_def_property(srna, "forward_axis", PROP_ENUM, PROP_NONE); @@ -6103,22 +6073,6 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna) RNA_define_lib_overridable(false); } -static void rna_def_mesh_cache_velocities(BlenderRNA *brna) -{ - StructRNA *srna; - PropertyRNA *prop; - - srna = RNA_def_struct(brna, "MeshCacheVertexVelocity", NULL); - RNA_def_struct_ui_text(srna, "Mesh Cache Velocity", "Velocity attribute of an Alembic mesh"); - RNA_def_struct_ui_icon(srna, ICON_VERTEXSEL); - - prop = RNA_def_property(srna, "velocity", PROP_FLOAT, PROP_VELOCITY); - RNA_def_property_array(prop, 3); - RNA_def_property_float_sdna(prop, NULL, "vel"); - RNA_def_property_ui_text(prop, "Velocity", ""); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); -} - static void rna_def_modifier_meshseqcache(BlenderRNA *brna) { StructRNA *srna; @@ -6175,26 +6129,6 @@ static void rna_def_modifier_meshseqcache(BlenderRNA *brna) "Multiplier used to control the magnitude of the velocity vectors for time effects"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); - /* -------------------------- Velocity Vectors -------------------------- */ - - prop = RNA_def_property(srna, "vertex_velocities", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "vertex_velocities", "num_vertices"); - RNA_def_property_struct_type(prop, "MeshCacheVertexVelocity"); - RNA_def_property_ui_text( - prop, "Fluid Mesh Vertices", "Vertices of the fluid mesh generated by simulation"); - - rna_def_mesh_cache_velocities(brna); - - prop = RNA_def_property(srna, "has_velocity", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_ui_text(prop, "Has Velocity Cache", ""); - RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_has_velocity_get", NULL); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); - - prop = RNA_def_property(srna, "read_velocity", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_ui_text(prop, "Read Velocity Cache", ""); - RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_read_velocity_get", NULL); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); - RNA_define_lib_overridable(false); } diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 9e24a36915e..76e37dbcdbc 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -8890,12 +8890,37 @@ static void def_cmp_denoise(StructRNA *srna) { PropertyRNA *prop; + static const EnumPropertyItem prefilter_items[] = { + {CMP_NODE_DENOISE_PREFILTER_NONE, + "NONE", + 0, + "None", + "No prefiltering, use when guiding passes are noise-free"}, + {CMP_NODE_DENOISE_PREFILTER_FAST, + "FAST", + 0, + "Fast", + "Denoise image and guiding passes together. Improves quality when guiding passes are noisy " + "using least amount of extra processing time"}, + {CMP_NODE_DENOISE_PREFILTER_ACCURATE, + "ACCURATE", + 0, + "Accurate", + "Prefilter noisy guiding passes before denoising image. Improves quality when guiding " + "passes are noisy using extra processing time"}, + {0, NULL, 0, NULL, NULL}}; + RNA_def_struct_sdna_from(srna, "NodeDenoise", "storage"); prop = RNA_def_property(srna, "use_hdr", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "hdr", 0); RNA_def_property_ui_text(prop, "HDR", "Process HDR images"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "prefilter", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prefilter_items); + RNA_def_property_ui_text(prop, "", "Denoising prefilter"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } static void def_cmp_antialiasing(StructRNA *srna) @@ -9295,6 +9320,11 @@ static void def_geo_point_instance(StructRNA *srna) ICON_NONE, "Collection", "Instance an entire collection on all points"}, + {GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY, + "GEOMETRY", + ICON_NONE, + "Geometry", + "Copy geometry to all points"}, {0, NULL, 0, NULL, NULL}, }; @@ -10086,12 +10116,18 @@ static void def_geo_curve_resample(StructRNA *srna) PropertyRNA *prop; static EnumPropertyItem mode_items[] = { - {GEO_NODE_CURVE_SAMPLE_COUNT, + {GEO_NODE_CURVE_RESAMPLE_EVALUATED, + "EVALUATED", + 0, + "Evaluated", + "Output the input spline's evaluated points, based on the resolution attribute for NURBS " + "and Bezier splines. Poly splines are unchanged"}, + {GEO_NODE_CURVE_RESAMPLE_COUNT, "COUNT", 0, "Count", "Sample the specified number of points along each spline"}, - {GEO_NODE_CURVE_SAMPLE_LENGTH, + {GEO_NODE_CURVE_RESAMPLE_LENGTH, "LENGTH", 0, "Length", @@ -10125,18 +10161,18 @@ static void def_geo_curve_to_points(StructRNA *srna) PropertyRNA *prop; static EnumPropertyItem mode_items[] = { - {GEO_NODE_CURVE_SAMPLE_EVALUATED, + {GEO_NODE_CURVE_RESAMPLE_EVALUATED, "EVALUATED", 0, "Evaluated", "Create points from the curve's evaluated points, based on the resolution attribute for " "NURBS and Bezier splines"}, - {GEO_NODE_CURVE_SAMPLE_COUNT, + {GEO_NODE_CURVE_RESAMPLE_COUNT, "COUNT", 0, "Count", "Sample each spline by evenly distributing the specified number of points"}, - {GEO_NODE_CURVE_SAMPLE_LENGTH, + {GEO_NODE_CURVE_RESAMPLE_LENGTH, "LENGTH", 0, "Length", @@ -10157,12 +10193,12 @@ static void def_geo_curve_trim(StructRNA *srna) PropertyRNA *prop; static EnumPropertyItem mode_items[] = { - {GEO_NODE_CURVE_INTERPOLATE_FACTOR, + {GEO_NODE_CURVE_SAMPLE_FACTOR, "FACTOR", 0, "Factor", "Find the endpoint positions using a factor of each spline's length"}, - {GEO_NODE_CURVE_INTERPOLATE_LENGTH, + {GEO_NODE_CURVE_RESAMPLE_LENGTH, "LENGTH", 0, "Length", @@ -10279,6 +10315,26 @@ static void def_geo_curve_fill(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_attribute_capture(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeCapture", "storage"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeFill_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_GeometryNode_socket_update"); + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT); + RNA_def_property_ui_text(prop, "Domain", "Which domain to store the data in"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index d3cd3158db1..99865078cbe 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -224,6 +224,12 @@ static EnumPropertyItem instance_items_empty[] = { INSTANCE_ITEM_COLLECTION, {0, NULL, 0, NULL, NULL}, }; + +static EnumPropertyItem instance_items_font[] = { + {0, "NONE", 0, "None", ""}, + {OB_DUPLIVERTS, "VERTS", 0, "Vertices", "Use Object Font on characters"}, + {0, NULL, 0, NULL, NULL}, +}; #endif #undef INSTANCE_ITEMS_SHARED #undef INSTANCE_ITEM_COLLECTION @@ -762,6 +768,9 @@ static const EnumPropertyItem *rna_Object_instance_type_itemf(bContext *UNUSED(C else if (ob->type == OB_POINTCLOUD) { item = instance_items_pointcloud; } + else if (ob->type == OB_FONT) { + item = instance_items_font; + } else { item = instance_items_nogroup; } diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 0583cdeb1bb..badaaa14aa4 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -2845,7 +2845,7 @@ static void rna_def_tool_settings(BlenderRNA *brna) "IMAGE", ICON_IMAGE_DATA, "Image", - "Strick stroke to the image"}, + "Stick stroke to the image"}, /* Weird, GP_PROJECT_VIEWALIGN is inverted. */ {0, "VIEW", ICON_RESTRICT_VIEW_ON, "View", "Stick stroke to the view"}, {0, NULL, 0, NULL, NULL}, @@ -4597,12 +4597,12 @@ void rna_def_freestyle_settings(BlenderRNA *brna) {FREESTYLE_CONTROL_SCRIPT_MODE, "SCRIPT", 0, - "Python Scripting Mode", + "Python Scripting", "Advanced mode for using style modules written in Python"}, {FREESTYLE_CONTROL_EDITOR_MODE, "EDITOR", 0, - "Parameter Editor Mode", + "Parameter Editor", "Basic mode for interactive style parameter editing"}, {0, NULL, 0, NULL, NULL}, }; @@ -4613,7 +4613,7 @@ void rna_def_freestyle_settings(BlenderRNA *brna) {FREESTYLE_QI_RANGE, "RANGE", 0, - "QI Range", + "Quantitative Invisibility", "Select feature edges within a range of quantitative invisibility (QI) values"}, {0, NULL, 0, NULL, NULL}, }; @@ -4930,14 +4930,6 @@ void rna_def_freestyle_settings(BlenderRNA *brna) prop, "Face Smoothness", "Take face smoothness into account in view map calculation"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update"); - prop = RNA_def_property(srna, "use_advanced_options", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_ADVANCED_OPTIONS_FLAG); - RNA_def_property_ui_text( - prop, - "Advanced Options", - "Enable advanced edge detection options (sphere radius and Kr derivative epsilon)"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update"); - prop = RNA_def_property(srna, "use_view_map_cache", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_VIEW_MAP_CACHE); RNA_def_property_ui_text( @@ -4957,11 +4949,13 @@ void rna_def_freestyle_settings(BlenderRNA *brna) prop = RNA_def_property(srna, "sphere_radius", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "sphere_radius"); + RNA_def_property_float_default(prop, 1.0); RNA_def_property_range(prop, 0.0, 1000.0); RNA_def_property_ui_text(prop, "Sphere Radius", "Sphere radius for computing curvatures"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update"); prop = RNA_def_property(srna, "kr_derivative_epsilon", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_default(prop, 0.0); RNA_def_property_float_sdna(prop, NULL, "dkr_epsilon"); RNA_def_property_range(prop, -1000.0, 1000.0); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index d9837f21833..cd87e4d10c1 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -845,6 +845,17 @@ static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *scene, Pointer DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); } +static void rna_Sequence_pan_range( + PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax) +{ + Scene *scene = (Scene *)ptr->owner_id; + + *min = -FLT_MAX; + *max = FLT_MAX; + *softmax = 1 + (int)(scene->r.ffcodecdata.audio_channels > 2); + *softmin = -*softmax; +} + static int rna_Sequence_input_count_get(PointerRNA *ptr) { Sequence *seq = (Sequence *)(ptr->data); @@ -2559,8 +2570,10 @@ static void rna_def_sound(BlenderRNA *brna) prop = RNA_def_property(srna, "pan", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "pan"); - RNA_def_property_range(prop, -2.0f, 2.0f); + RNA_def_property_range(prop, -FLT_MAX, FLT_MAX); + RNA_def_property_ui_range(prop, -2, 2, 1, 2); RNA_def_property_ui_text(prop, "Pan", "Playback panning of the sound (only for Mono sources)"); + RNA_def_property_float_funcs(prop, NULL, NULL, "rna_Sequence_pan_range"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_audio_update"); prop = RNA_def_property(srna, "show_waveform", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_sequencer_api.c b/source/blender/makesrna/intern/rna_sequencer_api.c index a0564d3435b..b43b57a35be 100644 --- a/source/blender/makesrna/intern/rna_sequencer_api.c +++ b/source/blender/makesrna/intern/rna_sequencer_api.c @@ -323,8 +323,8 @@ static Sequence *rna_Sequences_new_movie(ID *id, SEQ_add_load_data_init(&load_data, name, file, frame_start, channel); load_data.fit_method = fit_method; load_data.allow_invalid_file = true; - double video_start_offset; - Sequence *seq = SEQ_add_movie_strip(bmain, scene, seqbase, &load_data, &video_start_offset); + double start_offset = -1; + Sequence *seq = SEQ_add_movie_strip(bmain, scene, seqbase, &load_data, &start_offset); DEG_relations_tag_update(bmain); DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 7aee6944d8d..8c331bd1911 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -2326,6 +2326,16 @@ static void rna_Sequencer_view_type_update(Main *UNUSED(bmain), ED_area_tag_refresh(area); } +static char *rna_SpaceSequencerPreviewOverlay_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("preview_overlay"); +} + +static char *rna_SpaceSequencerTimelineOverlay_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("timeline_overlay"); +} + /* Space Node Editor */ static void rna_SpaceNodeEditor_node_tree_set(PointerRNA *ptr, @@ -4941,18 +4951,19 @@ static void rna_def_space_view3d(BlenderRNA *brna) prop = RNA_def_property(srna, "lock_rotation", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_LOCK_ROTATION); - RNA_def_property_ui_text(prop, "Lock", "Lock view rotation in side views"); + RNA_def_property_ui_text( + prop, "Lock Rotation", "Lock view rotation of side views to Top/Front/Right"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_update"); prop = RNA_def_property(srna, "show_sync_view", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_BOXVIEW); - RNA_def_property_ui_text(prop, "Box", "Sync view position between side views"); + RNA_def_property_ui_text(prop, "Sync Zoom/Pan", "Sync view position between side views"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_update"); prop = RNA_def_property(srna, "use_box_clip", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_BOXCLIP); RNA_def_property_ui_text( - prop, "Clip", "Clip objects based on what's visible in other side views"); + prop, "Clip Contents", "Clip view contents based on what is visible in other side views"); RNA_def_property_update( prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_clip_update"); @@ -5328,6 +5339,108 @@ static void rna_def_space_image(BlenderRNA *brna) rna_def_space_mask_info(srna, NC_SPACE | ND_SPACE_IMAGE, "rna_SpaceImageEditor_mask_set"); } +static void rna_def_space_sequencer_preview_overlay(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "SequencerPreviewOverlay", NULL); + RNA_def_struct_sdna(srna, "SequencerPreviewOverlay"); + RNA_def_struct_nested(brna, srna, "SpaceSequenceEditor"); + RNA_def_struct_path_func(srna, "rna_SpaceSequencerPreviewOverlay_path"); + RNA_def_struct_ui_text(srna, "Preview Overlay Settings", ""); + + prop = RNA_def_property(srna, "show_safe_areas", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_SAFE_MARGINS); + RNA_def_property_ui_text( + prop, "Safe Areas", "Show TV title safe and action safe areas in preview"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_safe_center", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_SAFE_CENTER); + RNA_def_property_ui_text( + prop, "Center-Cut Safe Areas", "Show safe areas to fit content in a different aspect ratio"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_metadata", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_METADATA); + RNA_def_property_ui_text(prop, "Show Metadata", "Show metadata of first visible strip"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_annotation", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_GPENCIL); + RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); +} + +static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "SequencerTimelineOverlay", NULL); + RNA_def_struct_sdna(srna, "SequencerTimelineOverlay"); + RNA_def_struct_nested(brna, srna, "SpaceSequenceEditor"); + RNA_def_struct_path_func(srna, "rna_SpaceSequencerTimelineOverlay_path"); + RNA_def_struct_ui_text(srna, "Timeline Overlay Settings", ""); + + static const EnumPropertyItem waveform_type_display_items[] = { + {SEQ_TIMELINE_NO_WAVEFORMS, + "NO_WAVEFORMS", + 0, + "Waveforms Off", + "Don't display waveforms for any sound strips"}, + {SEQ_TIMELINE_ALL_WAVEFORMS, + "ALL_WAVEFORMS", + 0, + "Waveforms On", + "Display waveforms for all sound strips"}, + {0, + "DEFAULT_WAVEFORMS", + 0, + "Use Strip Option", + "Display waveforms depending on strip setting"}, + {0, NULL, 0, NULL, NULL}, + }; + + prop = RNA_def_property(srna, "waveform_display_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag"); + RNA_def_property_enum_items(prop, waveform_type_display_items); + RNA_def_property_ui_text(prop, "Waveform Display", "How Waveforms are displayed"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_fcurves", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_FCURVES); + RNA_def_property_ui_text(prop, "Show F-Curves", "Display strip opacity/volume curve"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_strip_name", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_NAME); + RNA_def_property_ui_text(prop, "Show Name", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_strip_source", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_SOURCE); + RNA_def_property_ui_text( + prop, "Show Source", "Display path to source file, or name of source datablock"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_strip_duration", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_DURATION); + RNA_def_property_ui_text(prop, "Show Duration", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_grid", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_GRID); + RNA_def_property_ui_text(prop, "Show Grid", "Show vertical grid lines"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_strip_offset", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_OFFSETS); + RNA_def_property_ui_text(prop, "Show Offsets", "Display strip in/out offsets"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); +} + static void rna_def_space_sequencer(BlenderRNA *brna) { StructRNA *srna; @@ -5368,25 +5481,6 @@ static void rna_def_space_sequencer(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; - static const EnumPropertyItem waveform_type_display_items[] = { - {SEQ_NO_WAVEFORMS, - "NO_WAVEFORMS", - 0, - "Waveforms Off", - "Don't display waveforms for any sound strips"}, - {SEQ_ALL_WAVEFORMS, - "ALL_WAVEFORMS", - 0, - "Waveforms On", - "Display waveforms for all sound strips"}, - {0, - "DEFAULT_WAVEFORMS", - 0, - "Use Strip Option", - "Display waveforms depending on strip setting"}, - {0, NULL, 0, NULL, NULL}, - }; - srna = RNA_def_struct(brna, "SpaceSequenceEditor", "Space"); RNA_def_struct_sdna(srna, "SpaceSeq"); RNA_def_struct_ui_text(srna, "Space Sequence Editor", "Sequence editor space data"); @@ -5427,23 +5521,6 @@ static void rna_def_space_sequencer(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Separate Colors", "Separate color channels in preview"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - prop = RNA_def_property(srna, "show_safe_areas", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_SAFE_MARGINS); - RNA_def_property_ui_text( - prop, "Safe Areas", "Show TV title safe and action safe areas in preview"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - - prop = RNA_def_property(srna, "show_safe_center", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_SAFE_CENTER); - RNA_def_property_ui_text( - prop, "Center-Cut Safe Areas", "Show safe areas to fit content in a different aspect ratio"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - - prop = RNA_def_property(srna, "show_metadata", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_METADATA); - RNA_def_property_ui_text(prop, "Show Metadata", "Show metadata of first visible strip"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - prop = RNA_def_property(srna, "show_seconds", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", SEQ_DRAWFRAMES); RNA_def_property_ui_text(prop, "Show Seconds", "Show timing in seconds not frames"); @@ -5457,11 +5534,6 @@ static void rna_def_space_sequencer(BlenderRNA *brna) "If any exists, show markers in a separate row at the bottom of the editor"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - prop = RNA_def_property(srna, "show_annotation", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_GPENCIL); - RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - prop = RNA_def_property(srna, "display_channel", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "chanshown"); RNA_def_property_ui_text( @@ -5477,12 +5549,6 @@ static void rna_def_space_sequencer(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Display Channels", "Channels of the preview to display"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, "rna_SequenceEditor_update_cache"); - prop = RNA_def_property(srna, "waveform_display_type", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag"); - RNA_def_property_enum_items(prop, waveform_type_display_items); - RNA_def_property_ui_text(prop, "Waveform Display", "How Waveforms are displayed"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - prop = RNA_def_property(srna, "use_zoom_to_fit", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_ZOOM_TO_FIT); RNA_def_property_ui_text( @@ -5532,46 +5598,31 @@ static void rna_def_space_sequencer(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Use Backdrop", "Display result under strips"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - prop = RNA_def_property(srna, "show_strip_offset", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "draw_flag", SEQ_DRAW_OFFSET_EXT); - RNA_def_property_ui_text(prop, "Show Offsets", "Display strip in/out offsets"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - - prop = RNA_def_property(srna, "show_fcurves", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_FCURVES); - RNA_def_property_ui_text(prop, "Show F-Curves", "Display strip opacity/volume curve"); + prop = RNA_def_property(srna, "show_transform_preview", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "draw_flag", SEQ_DRAW_TRANSFORM_PREVIEW); + RNA_def_property_ui_text(prop, "Transform Preview", "Show preview of the transformed frames"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + /* Overlay settings. */ prop = RNA_def_property(srna, "show_strip_overlay", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_STRIP_OVERLAY); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_OVERLAY); RNA_def_property_ui_text(prop, "Show Overlay", ""); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - prop = RNA_def_property(srna, "show_strip_name", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_STRIP_NAME); - RNA_def_property_ui_text(prop, "Show Name", ""); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - - prop = RNA_def_property(srna, "show_strip_source", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_STRIP_SOURCE); - RNA_def_property_ui_text( - prop, "Show Source", "Display path to source file, or name of source datablock"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); - - prop = RNA_def_property(srna, "show_strip_duration", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_STRIP_DURATION); - RNA_def_property_ui_text(prop, "Show Duration", ""); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + prop = RNA_def_property(srna, "preview_overlay", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_struct_type(prop, "SequencerPreviewOverlay"); + RNA_def_property_pointer_sdna(prop, NULL, "preview_overlay"); + RNA_def_property_ui_text(prop, "Preview Overlay Settings", "Settings for display of overlays"); - prop = RNA_def_property(srna, "show_transform_preview", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "draw_flag", SEQ_DRAW_TRANSFORM_PREVIEW); - RNA_def_property_ui_text(prop, "Transform Preview", "Show preview of the transformed frames"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + prop = RNA_def_property(srna, "timeline_overlay", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_struct_type(prop, "SequencerTimelineOverlay"); + RNA_def_property_pointer_sdna(prop, NULL, "timeline_overlay"); + RNA_def_property_ui_text(prop, "Timeline Overlay Settings", "Settings for display of overlays"); - prop = RNA_def_property(srna, "show_grid", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_SHOW_GRID); - RNA_def_property_ui_text(prop, "Show Grid", "Show vertical grid lines"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + rna_def_space_sequencer_preview_overlay(brna); + rna_def_space_sequencer_timeline_overlay(brna); } static void rna_def_space_text(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c index e06cc39a88b..f96b3fc5eee 100644 --- a/source/blender/makesrna/intern/rna_ui_api.c +++ b/source/blender/makesrna/intern/rna_ui_api.c @@ -622,6 +622,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout, PointerRNA *active_dataptr, const char *active_propname, int filter_id_types, + int display_flags, const char *activate_opname, PointerRNA *r_activate_op_properties, const char *drag_opname, @@ -630,6 +631,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout, AssetFilterSettings filter_settings = { .id_types = filter_id_types ? filter_id_types : FILTER_ID_ALL, }; + uiTemplateAssetView(layout, C, list_id, @@ -640,6 +642,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout, active_dataptr, active_propname, &filter_settings, + display_flags, activate_opname, r_activate_op_properties, drag_opname, @@ -878,6 +881,25 @@ void RNA_api_ui_layout(StructRNA *srna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem asset_view_template_options[] = { + {UI_TEMPLATE_ASSET_DRAW_NO_NAMES, + "NO_NAMES", + 0, + "", + "Do not display the name of each asset underneath preview images"}, + {UI_TEMPLATE_ASSET_DRAW_NO_FILTER, + "NO_FILTER", + 0, + "", + "Do not display buttons for filtering the available assets"}, + {UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY, + "NO_LIBRARY", + 0, + "", + "Do not display buttons to choose or refresh an asset library"}, + {0, NULL, 0, NULL, NULL}, + }; + static float node_socket_color_default[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* simple layout specifiers */ @@ -1839,6 +1861,12 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_property_enum_items(parm, DummyRNA_NULL_items); RNA_def_property_enum_funcs(parm, NULL, NULL, "rna_uiTemplateAssetView_filter_id_types_itemf"); RNA_def_property_flag(parm, PROP_ENUM_FLAG); + RNA_def_enum_flag(func, + "display_options", + asset_view_template_options, + 0, + "", + "Displaying options for the asset view"); RNA_def_string(func, "activate_operator", NULL, diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 73811924c23..563c6ea35e0 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -559,6 +559,11 @@ static PointerRNA rna_UserDef_system_get(PointerRNA *ptr) return rna_pointer_inherit_refine(ptr, &RNA_PreferencesSystem, ptr->data); } +static PointerRNA rna_UserDef_apps_get(PointerRNA *ptr) +{ + return rna_pointer_inherit_refine(ptr, &RNA_PreferencesApps, ptr->data); +} + static void rna_UserDef_audio_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *UNUSED(ptr)) { BKE_sound_init(bmain); @@ -4590,12 +4595,6 @@ static void rna_def_userdef_view(BlenderRNA *brna) "Color range used for weight visualization in weight painting mode"); RNA_def_property_update(prop, 0, "rna_UserDef_weight_color_update"); - prop = RNA_def_property(srna, "show_layout_ui", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_UI_LAYOUT); - RNA_def_property_ui_text( - prop, "Editor Corner Splitting", "Split and join editors by dragging from corners"); - RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); - prop = RNA_def_property(srna, "show_navigate_ui", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_SHOW_GIZMO_NAVIGATE); RNA_def_property_ui_text( @@ -6059,6 +6058,14 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem preview_type_items[] = { + {USER_FILE_PREVIEW_NONE, "NONE", 0, "None", "Do not create blend previews"}, + {USER_FILE_PREVIEW_AUTO, "AUTO", 0, "Auto", "Automatically select best preview type"}, + {USER_FILE_PREVIEW_SCREENSHOT, "SCREENSHOT", 0, "Screenshot", "Capture the entire window"}, + {USER_FILE_PREVIEW_CAMERA, "CAMERA", 0, "Camera View", "Workbench render of scene"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "PreferencesFilePaths", NULL); RNA_def_struct_sdna(srna, "UserDef"); RNA_def_struct_nested(brna, srna, "Preferences"); @@ -6066,26 +6073,24 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "File Paths", "Default paths for external files"); prop = RNA_def_property(srna, "show_hidden_files_datablocks", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_DOT); + RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_DOT); RNA_def_property_ui_text(prop, - "Hide Dot Files/Data-Blocks", - "Hide files and data-blocks if their name start with a dot (.*)"); + "Show Hidden Files/Data-Blocks", + "Show files and data-blocks that are normally hidden"); prop = RNA_def_property(srna, "use_filter_files", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_FILTERFILEEXTS); - RNA_def_property_ui_text(prop, - "Filter File Extensions", - "Display only files with extensions in the image select window"); + RNA_def_property_ui_text(prop, "Filter Files", "Enable filtering of files in the File Browser"); - prop = RNA_def_property(srna, "hide_recent_locations", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_RECENT); + prop = RNA_def_property(srna, "show_recent_locations", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_RECENT); RNA_def_property_ui_text( - prop, "Hide Recent Locations", "Hide recent locations in the file selector"); + prop, "Show Recent Locations", "Show Recent locations list in the File Browser"); - prop = RNA_def_property(srna, "hide_system_bookmarks", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_SYSTEM_BOOKMARKS); + prop = RNA_def_property(srna, "show_system_bookmarks", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_SYSTEM_BOOKMARKS); RNA_def_property_ui_text( - prop, "Hide System Bookmarks", "Hide system bookmarks in the file selector"); + prop, "Show System Locations", "Show System locations list in the File Browser"); prop = RNA_def_property(srna, "use_relative_paths", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", USER_RELPATHS); @@ -6214,12 +6219,9 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Recent Files", "Maximum number of recently opened files to remember"); - prop = RNA_def_property(srna, "use_save_preview_images", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", USER_SAVE_PREVIEWS); - RNA_def_property_ui_text(prop, - "Save Preview Images", - "Enables automatic saving of preview images in the .blend file " - "as well as a thumbnail of the .blend"); + prop = RNA_def_property(srna, "file_preview_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, preview_type_items); + RNA_def_property_ui_text(prop, "File Preview Type", "What type of blend preview to create"); rna_def_userdef_filepaths_asset_library(brna); @@ -6228,6 +6230,35 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Asset Libraries", ""); } +static void rna_def_userdef_apps(BlenderRNA *brna) +{ + PropertyRNA *prop; + StructRNA *srna; + + srna = RNA_def_struct(brna, "PreferencesApps", NULL); + RNA_def_struct_sdna(srna, "UserDef"); + RNA_def_struct_nested(brna, srna, "Preferences"); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "Apps", "Preferences that work only for apps"); + + prop = RNA_def_property(srna, "show_corner_split", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_CORNER_SPLIT); + RNA_def_property_ui_text( + prop, "Corner Splitting", "Split and join editors by dragging from corners"); + RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); + + prop = RNA_def_property(srna, "show_edge_resize", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_EDGE_RESIZE); + RNA_def_property_ui_text(prop, "Edge Resize", "Resize editors by dragging from the edges"); + RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); + + prop = RNA_def_property(srna, "show_regions_visibility_toggle", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_HIDE_REGION_TOGGLE); + RNA_def_property_ui_text( + prop, "Regions Visibility Toggle", "Header and side bars visibility toggles"); + RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); +} + static void rna_def_userdef_experimental(BlenderRNA *brna) { StructRNA *srna; @@ -6296,6 +6327,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "use_override_templates", 1); 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"); } static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop) @@ -6439,6 +6474,12 @@ void RNA_def_userdef(BlenderRNA *brna) RNA_def_property_ui_text( prop, "System & OpenGL", "Graphics driver and operating system settings"); + prop = RNA_def_property(srna, "apps", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_struct_type(prop, "PreferencesApps"); + RNA_def_property_pointer_funcs(prop, "rna_UserDef_apps_get", NULL, NULL, NULL); + RNA_def_property_ui_text(prop, "Apps", "Preferences that work only for apps"); + prop = RNA_def_property(srna, "experimental", PROP_POINTER, PROP_NONE); RNA_def_property_flag(prop, PROP_NEVER_NULL); RNA_def_property_struct_type(prop, "PreferencesExperimental"); @@ -6500,6 +6541,7 @@ void RNA_def_userdef(BlenderRNA *brna) rna_def_userdef_studiolights(brna); rna_def_userdef_studiolight(brna); rna_def_userdef_pathcompare(brna); + rna_def_userdef_apps(brna); rna_def_userdef_experimental(brna); USERDEF_TAG_DIRTY_PROPERTY_UPDATE_DISABLE; diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index 21a3c087197..c2d1ac67675 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -440,8 +440,7 @@ const EnumPropertyItem rna_enum_event_type_mask_items[] = { static const EnumPropertyItem keymap_modifiers_items[] = { {KM_ANY, "ANY", 0, "Any", ""}, {0, "NONE", 0, "None", ""}, - {1, "FIRST", 0, "First", ""}, - {2, "SECOND", 0, "Second", ""}, + {KM_MOD_HELD, "HELD", 0, "Held", ""}, {0, NULL, 0, NULL, NULL}, }; #endif @@ -468,6 +467,13 @@ const EnumPropertyItem rna_enum_operator_type_flag_items[] = { "is enabled"}, {OPTYPE_GRAB_CURSOR_X, "GRAB_CURSOR_X", 0, "Grab Pointer X", "Grab, only warping the X axis"}, {OPTYPE_GRAB_CURSOR_Y, "GRAB_CURSOR_Y", 0, "Grab Pointer Y", "Grab, only warping the Y axis"}, + {OPTYPE_DEPENDS_ON_CURSOR, + "DEPENDS_ON_CURSOR", + 0, + "Depends on Cursor", + "The initial cursor location is used, " + "when running from a menus or buttons the user is prompted to place the cursor " + "before beginning the operation"}, {OPTYPE_PRESET, "PRESET", 0, "Preset", "Display a preset button with the operators settings"}, {OPTYPE_INTERNAL, "INTERNAL", 0, "Internal", "Removes the operator from search results"}, {0, NULL, 0, NULL, NULL}, @@ -2725,38 +2731,62 @@ static void rna_def_keyconfig(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Any", "Any modifier keys pressed"); RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); - prop = RNA_def_property(srna, "shift", PROP_BOOLEAN, PROP_NONE); + prop = RNA_def_property(srna, "shift", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "shift"); + RNA_def_property_range(prop, KM_ANY, KM_MOD_HELD); + RNA_def_property_ui_text(prop, "Shift", "Shift key pressed, -1 for any state"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_WINDOWMANAGER); + RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); + + prop = RNA_def_property(srna, "ctrl", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "ctrl"); + RNA_def_property_range(prop, KM_ANY, KM_MOD_HELD); + RNA_def_property_ui_text(prop, "Ctrl", "Control key pressed, -1 for any state"); + RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); + + prop = RNA_def_property(srna, "alt", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "alt"); + RNA_def_property_range(prop, KM_ANY, KM_MOD_HELD); + RNA_def_property_ui_text(prop, "Alt", "Alt key pressed, -1 for any state"); + RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); + + prop = RNA_def_property(srna, "oskey", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "oskey"); + RNA_def_property_range(prop, KM_ANY, KM_MOD_HELD); + RNA_def_property_ui_text(prop, "OS Key", "Operating system key pressed, -1 for any state"); + RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); + + /* XXX(@campbellbarton): the `*_ui` suffix is only for the UI, may be removed, + * since this is only exposed so the UI can show these settings as toggle-buttons. */ + prop = RNA_def_property(srna, "shift_ui", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "shift", 0); RNA_def_property_boolean_funcs(prop, "rna_KeyMapItem_shift_get", NULL); - /* RNA_def_property_enum_sdna(prop, NULL, "shift"); */ /* RNA_def_property_enum_items(prop, keymap_modifiers_items); */ RNA_def_property_ui_text(prop, "Shift", "Shift key pressed"); RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_WINDOWMANAGER); RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); - prop = RNA_def_property(srna, "ctrl", PROP_BOOLEAN, PROP_NONE); + prop = RNA_def_property(srna, "ctrl_ui", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "ctrl", 0); RNA_def_property_boolean_funcs(prop, "rna_KeyMapItem_ctrl_get", NULL); - /* RNA_def_property_enum_sdna(prop, NULL, "ctrl"); */ /* RNA_def_property_enum_items(prop, keymap_modifiers_items); */ RNA_def_property_ui_text(prop, "Ctrl", "Control key pressed"); RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); - prop = RNA_def_property(srna, "alt", PROP_BOOLEAN, PROP_NONE); + prop = RNA_def_property(srna, "alt_ui", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "alt", 0); RNA_def_property_boolean_funcs(prop, "rna_KeyMapItem_alt_get", NULL); - /* RNA_def_property_enum_sdna(prop, NULL, "alt"); */ /* RNA_def_property_enum_items(prop, keymap_modifiers_items); */ RNA_def_property_ui_text(prop, "Alt", "Alt key pressed"); RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); - prop = RNA_def_property(srna, "oskey", PROP_BOOLEAN, PROP_NONE); + prop = RNA_def_property(srna, "oskey_ui", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "oskey", 0); RNA_def_property_boolean_funcs(prop, "rna_KeyMapItem_oskey_get", NULL); - /* RNA_def_property_enum_sdna(prop, NULL, "oskey"); */ /* RNA_def_property_enum_items(prop, keymap_modifiers_items); */ RNA_def_property_ui_text(prop, "OS Key", "Operating system key pressed"); RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); + /* End `_ui` modifiers. */ prop = RNA_def_property(srna, "key_modifier", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "keymodifier"); diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c index e123604cbe9..7c3b119abb9 100644 --- a/source/blender/makesrna/intern/rna_wm_api.c +++ b/source/blender/makesrna/intern/rna_wm_api.c @@ -39,6 +39,8 @@ #include "wm_cursors.h" #include "wm_event_types.h" +#include "WM_types.h" + #include "rna_internal.h" /* own include */ /* confusing 2 enums mixed up here */ @@ -216,49 +218,70 @@ static int rna_Operator_props_popup(bContext *C, wmOperator *op, wmEvent *event) return WM_operator_props_popup(C, op, event); } +static int keymap_item_modifier_flag_from_args(bool any, int shift, int ctrl, int alt, int oskey) +{ + int modifier = 0; + if (any) { + modifier = KM_ANY; + } + else { + if (shift == KM_MOD_HELD) { + modifier |= KM_SHIFT; + } + else if (shift == KM_ANY) { + modifier |= KM_SHIFT_ANY; + } + + if (ctrl == KM_MOD_HELD) { + modifier |= KM_CTRL; + } + else if (ctrl == KM_ANY) { + modifier |= KM_CTRL_ANY; + } + + if (alt == KM_MOD_HELD) { + modifier |= KM_ALT; + } + else if (alt == KM_ANY) { + modifier |= KM_ALT_ANY; + } + + if (oskey == KM_MOD_HELD) { + modifier |= KM_OSKEY; + } + else if (oskey == KM_ANY) { + modifier |= KM_OSKEY_ANY; + } + } + return modifier; +} + static wmKeyMapItem *rna_KeyMap_item_new(wmKeyMap *km, ReportList *reports, const char *idname, int type, int value, bool any, - bool shift, - bool ctrl, - bool alt, - bool oskey, + int shift, + int ctrl, + int alt, + int oskey, int keymodifier, bool repeat, bool head) { - // wmWindowManager *wm = CTX_wm_manager(C); - wmKeyMapItem *kmi = NULL; - char idname_bl[OP_MAX_TYPENAME]; - int modifier = 0; - /* only on non-modal maps */ if (km->flag & KEYMAP_MODAL) { BKE_report(reports, RPT_ERROR, "Not a non-modal keymap"); return NULL; } - WM_operator_bl_idname(idname_bl, idname); - - if (shift) { - modifier |= KM_SHIFT; - } - if (ctrl) { - modifier |= KM_CTRL; - } - if (alt) { - modifier |= KM_ALT; - } - if (oskey) { - modifier |= KM_OSKEY; - } + // wmWindowManager *wm = CTX_wm_manager(C); + wmKeyMapItem *kmi = NULL; + char idname_bl[OP_MAX_TYPENAME]; + const int modifier = keymap_item_modifier_flag_from_args(any, shift, ctrl, alt, oskey); - if (any) { - modifier = KM_ANY; - } + WM_operator_bl_idname(idname_bl, idname); /* create keymap item */ kmi = WM_keymap_add_item(km, idname_bl, type, value, modifier, keymodifier); @@ -305,39 +328,22 @@ static wmKeyMapItem *rna_KeyMap_item_new_modal(wmKeyMap *km, int type, int value, bool any, - bool shift, - bool ctrl, - bool alt, - bool oskey, + int shift, + int ctrl, + int alt, + int oskey, int keymodifier, bool repeat) { - wmKeyMapItem *kmi = NULL; - int modifier = 0; - int propvalue = 0; - /* only modal maps */ if ((km->flag & KEYMAP_MODAL) == 0) { BKE_report(reports, RPT_ERROR, "Not a modal keymap"); return NULL; } - if (shift) { - modifier |= KM_SHIFT; - } - if (ctrl) { - modifier |= KM_CTRL; - } - if (alt) { - modifier |= KM_ALT; - } - if (oskey) { - modifier |= KM_OSKEY; - } - - if (any) { - modifier = KM_ANY; - } + wmKeyMapItem *kmi = NULL; + const int modifier = keymap_item_modifier_flag_from_args(any, shift, ctrl, alt, oskey); + int propvalue = 0; /* not initialized yet, do delayed lookup */ if (!km->modal_items) { @@ -641,7 +647,7 @@ static wmEvent *rna_Window_event_add_simulate(wmWindow *win, e.is_repeat = false; e.x = x; e.y = y; - /* NOTE: KM_MOD_FIRST, KM_MOD_SECOND aren't used anywhere, set as bools. */ + e.shift = shift; e.ctrl = ctrl; e.alt = alt; @@ -1131,10 +1137,10 @@ void RNA_api_keymapitems(StructRNA *srna) parm = RNA_def_enum(func, "value", rna_enum_event_value_all_items, 0, "Value", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); RNA_def_boolean(func, "any", 0, "Any", ""); - RNA_def_boolean(func, "shift", 0, "Shift", ""); - RNA_def_boolean(func, "ctrl", 0, "Ctrl", ""); - RNA_def_boolean(func, "alt", 0, "Alt", ""); - RNA_def_boolean(func, "oskey", 0, "OS Key", ""); + RNA_def_int(func, "shift", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Shift", "", KM_ANY, KM_MOD_HELD); + RNA_def_int(func, "ctrl", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Ctrl", "", KM_ANY, KM_MOD_HELD); + RNA_def_int(func, "alt", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Alt", "", KM_ANY, KM_MOD_HELD); + RNA_def_int(func, "oskey", KM_NOTHING, KM_ANY, KM_MOD_HELD, "OS Key", "", KM_ANY, KM_MOD_HELD); RNA_def_enum(func, "key_modifier", rna_enum_event_type_items, 0, "Key Modifier", ""); RNA_def_boolean(func, "repeat", false, "Repeat", "When set, accept key-repeat events"); RNA_def_boolean(func, @@ -1155,10 +1161,10 @@ void RNA_api_keymapitems(StructRNA *srna) parm = RNA_def_enum(func, "value", rna_enum_event_value_all_items, 0, "Value", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); RNA_def_boolean(func, "any", 0, "Any", ""); - RNA_def_boolean(func, "shift", 0, "Shift", ""); - RNA_def_boolean(func, "ctrl", 0, "Ctrl", ""); - RNA_def_boolean(func, "alt", 0, "Alt", ""); - RNA_def_boolean(func, "oskey", 0, "OS Key", ""); + RNA_def_int(func, "shift", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Shift", "", KM_ANY, KM_MOD_HELD); + RNA_def_int(func, "ctrl", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Ctrl", "", KM_ANY, KM_MOD_HELD); + RNA_def_int(func, "alt", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Alt", "", KM_ANY, KM_MOD_HELD); + RNA_def_int(func, "oskey", KM_NOTHING, KM_ANY, KM_MOD_HELD, "OS Key", "", KM_ANY, KM_MOD_HELD); RNA_def_enum(func, "key_modifier", rna_enum_event_type_items, 0, "Key Modifier", ""); RNA_def_boolean(func, "repeat", false, "Repeat", "When set, accept key-repeat events"); parm = RNA_def_pointer(func, "item", "KeyMapItem", "Item", "Added key map item"); diff --git a/source/blender/makesrna/intern/rna_wm_gizmo.c b/source/blender/makesrna/intern/rna_wm_gizmo.c index febb0e14e07..6a63723d174 100644 --- a/source/blender/makesrna/intern/rna_wm_gizmo.c +++ b/source/blender/makesrna/intern/rna_wm_gizmo.c @@ -1401,7 +1401,12 @@ static void rna_def_gizmogroup(BlenderRNA *brna) "SHOW_MODAL_ALL", 0, "Show Modal All", - "Show all while interacting"}, + "Show all while interacting, as well as this group when another is being interacted with"}, + {WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE, + "EXCLUDE_MODAL", + 0, + "Exclude Modal", + "Show all except this group while interacting"}, {WM_GIZMOGROUPTYPE_TOOL_INIT, "TOOL_INIT", 0, diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index 6a9c9715994..2f0f11ab56d 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -786,7 +786,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, * TODO: we may need to set other dirty flags as well? */ if (use_recalc_normals) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } if (vgroup_start_cap_remap) { diff --git a/source/blender/modifiers/intern/MOD_bevel.c b/source/blender/modifiers/intern/MOD_bevel.c index 8fdd222402e..add95a0d248 100644 --- a/source/blender/modifiers/intern/MOD_bevel.c +++ b/source/blender/modifiers/intern/MOD_bevel.c @@ -243,7 +243,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc index bdb791dc8e7..c5d6902e1bc 100644 --- a/source/blender/modifiers/intern/MOD_boolean.cc +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -161,7 +161,7 @@ static Mesh *get_quick_mesh( mul_m4_v3(omat, mv->co); } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } break; @@ -506,7 +506,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } if (result == nullptr) { @@ -541,7 +541,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } } } diff --git a/source/blender/modifiers/intern/MOD_build.c b/source/blender/modifiers/intern/MOD_build.c index a344a15b0c1..6cd8d70383d 100644 --- a/source/blender/modifiers/intern/MOD_build.c +++ b/source/blender/modifiers/intern/MOD_build.c @@ -281,7 +281,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, struct MEM_freeN(faceMap); if (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } /* TODO(sybren): also copy flags & tags? */ diff --git a/source/blender/modifiers/intern/MOD_decimate.c b/source/blender/modifiers/intern/MOD_decimate.c index faad1175f3a..56fcbbd8b7c 100644 --- a/source/blender/modifiers/intern/MOD_decimate.c +++ b/source/blender/modifiers/intern/MOD_decimate.c @@ -222,7 +222,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * TIMEIT_END(decim); #endif - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_edgesplit.c b/source/blender/modifiers/intern/MOD_edgesplit.c index 82a6e169a7a..b21a536ad8a 100644 --- a/source/blender/modifiers/intern/MOD_edgesplit.c +++ b/source/blender/modifiers/intern/MOD_edgesplit.c @@ -115,7 +115,7 @@ Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd) result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_mask.cc b/source/blender/modifiers/intern/MOD_mask.cc index 306e79aa647..9a8af35109a 100644 --- a/source/blender/modifiers/intern/MOD_mask.cc +++ b/source/blender/modifiers/intern/MOD_mask.cc @@ -814,8 +814,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *UNUSED(ctx) } BKE_mesh_calc_edges_loose(result); - /* Tag to recalculate normals later. */ - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_meshcache.c b/source/blender/modifiers/intern/MOD_meshcache.c index 6ef64ad8bc9..74f9887a973 100644 --- a/source/blender/modifiers/intern/MOD_meshcache.c +++ b/source/blender/modifiers/intern/MOD_meshcache.c @@ -36,8 +36,11 @@ #include "DNA_screen_types.h" #include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" +#include "BKE_mesh_wrapper.h" #include "BKE_scene.h" #include "BKE_screen.h" @@ -53,6 +56,7 @@ #include "MOD_meshcache_util.h" /* utility functions */ #include "MOD_modifiertypes.h" #include "MOD_ui_common.h" +#include "MOD_util.h" static void initData(ModifierData *md) { @@ -84,11 +88,16 @@ static bool isDisabled(const struct Scene *UNUSED(scene), static void meshcache_do(MeshCacheModifierData *mcmd, Scene *scene, Object *ob, + Mesh *mesh, float (*vertexCos_Real)[3], int numVerts) { const bool use_factor = mcmd->factor < 1.0f; - float(*vertexCos_Store)[3] = (use_factor || + int influence_group_index; + MDeformVert *dvert; + MOD_get_vgroup(ob, mesh, mcmd->defgrp_name, &dvert, &influence_group_index); + + float(*vertexCos_Store)[3] = (use_factor || influence_group_index != -1 || (mcmd->deform_mode == MOD_MESHCACHE_DEFORM_INTEGRATE)) ? MEM_malloc_arrayN( numVerts, sizeof(*vertexCos_Store), __func__) : @@ -256,7 +265,29 @@ static void meshcache_do(MeshCacheModifierData *mcmd, if (vertexCos_Store) { if (ok) { - if (use_factor) { + if (influence_group_index != -1) { + const float global_factor = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ? + -mcmd->factor : + mcmd->factor; + const float global_offset = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ? + mcmd->factor : + 0.0f; + if (mesh->dvert != NULL) { + for (int i = 0; i < numVerts; i++) { + /* For each vertex, compute its blending factor between the mesh cache (for `fac = 0`) + * and the former position of the vertex (for `fac = 1`). */ + const MDeformVert *currentIndexDVert = dvert + i; + const float local_vertex_fac = global_offset + + BKE_defvert_find_weight(currentIndexDVert, + influence_group_index) * + global_factor; + interp_v3_v3v3( + vertexCos_Real[i], vertexCos_Real[i], vertexCos_Store[i], local_vertex_fac); + } + } + } + else if (use_factor) { + /* Influence_group_index is -1. */ interp_vn_vn(*vertexCos_Real, *vertexCos_Store, mcmd->factor, numVerts * 3); } else { @@ -270,34 +301,59 @@ static void meshcache_do(MeshCacheModifierData *mcmd, static void deformVerts(ModifierData *md, const ModifierEvalContext *ctx, - Mesh *UNUSED(mesh), + Mesh *mesh, float (*vertexCos)[3], int numVerts) { MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md; Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); - meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts); + Mesh *mesh_src = NULL; + + if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') { + /* `mesh_src` is only needed for vertex groups. */ + mesh_src = MOD_deform_mesh_eval_get(ctx->object, NULL, mesh, NULL, numVerts, false, false); + } + meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts); + + if (!ELEM(mesh_src, NULL, mesh)) { + BKE_id_free(NULL, mesh_src); + } } static void deformVertsEM(ModifierData *md, const ModifierEvalContext *ctx, - struct BMEditMesh *UNUSED(editData), - Mesh *UNUSED(mesh), + struct BMEditMesh *editData, + Mesh *mesh, float (*vertexCos)[3], int numVerts) { MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md; Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); - meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts); + Mesh *mesh_src = NULL; + + if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') { + /* `mesh_src` is only needed for vertex groups. */ + mesh_src = MOD_deform_mesh_eval_get(ctx->object, editData, mesh, NULL, numVerts, false, false); + } + if (mesh_src != NULL) { + BKE_mesh_wrapper_ensure_mdata(mesh_src); + } + + meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts); + + if (!ELEM(mesh_src, NULL, mesh)) { + BKE_id_free(NULL, mesh_src); + } } static void panel_draw(const bContext *UNUSED(C), Panel *panel) { uiLayout *layout = panel->layout; - PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); + PointerRNA ob_ptr; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); uiLayoutSetPropSep(layout, true); @@ -307,6 +363,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(layout, ptr, "factor", UI_ITEM_R_SLIDER, NULL, ICON_NONE); uiItemR(layout, ptr, "deform_mode", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "interpolation", 0, NULL, ICON_NONE); + modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", NULL); modifier_panel_end(layout, ptr); } diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.c index 259c1cb2417..bcaf294ec8b 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.c +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.c @@ -105,10 +105,6 @@ static void freeData(ModifierData *md) mcmd->reader_object_path[0] = '\0'; BKE_cachefile_reader_free(mcmd->cache_file, &mcmd->reader); } - - if (mcmd->vertex_velocities) { - MEM_freeN(mcmd->vertex_velocities); - } } static bool isDisabled(const struct Scene *UNUSED(scene), @@ -233,11 +229,26 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * Mesh *result = NULL; switch (cache_file->type) { - case CACHEFILE_TYPE_ALEMBIC: + case CACHEFILE_TYPE_ALEMBIC: { # ifdef WITH_ALEMBIC - result = ABC_read_mesh(mcmd->reader, ctx->object, mesh, time, &err_str, mcmd->read_flag); + /* Time (in frames or seconds) between two velocity samples. Automatically computed to + * scale the velocity vectors at render time for generating proper motion blur data. */ + float velocity_scale = mcmd->velocity_scale; + if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_FRAME) { + velocity_scale *= FPS; + } + + result = ABC_read_mesh(mcmd->reader, + ctx->object, + mesh, + time, + &err_str, + mcmd->read_flag, + mcmd->cache_file->velocity_name, + velocity_scale); # endif break; + } case CACHEFILE_TYPE_USD: # ifdef WITH_USD result = USD_read_mesh( @@ -248,17 +259,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * break; } - mcmd->velocity_delta = 1.0f; - if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_SECOND) { - mcmd->velocity_delta /= FPS; - } - - mcmd->last_lookup_time = time; - - if (result != NULL) { - mcmd->num_vertices = result->totvert; - } - if (err_str) { BKE_modifier_set_error(ctx->object, md, "%s", err_str); } diff --git a/source/blender/modifiers/intern/MOD_mirror.c b/source/blender/modifiers/intern/MOD_mirror.c index 6116cf8146a..7fd90c71c9f 100644 --- a/source/blender/modifiers/intern/MOD_mirror.c +++ b/source/blender/modifiers/intern/MOD_mirror.c @@ -117,7 +117,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = mirrorModifier__doMirror(mmd, ctx->object, mesh); if (result != mesh) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } return result; } diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 9d8630b21e7..6b976b016e1 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -68,6 +68,8 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "WM_types.h" + #include "RNA_access.h" #include "RNA_enum_types.h" @@ -85,8 +87,10 @@ #include "NOD_geometry.h" #include "NOD_geometry_nodes_eval_log.hh" +#include "FN_field.hh" #include "FN_multi_function.hh" +using blender::ColorGeometry4f; using blender::destruct_ptr; using blender::float3; using blender::FunctionRef; @@ -289,6 +293,17 @@ static bool logging_enabled(const ModifierEvalContext *ctx) return true; } +static const std::string use_attribute_suffix = "_use_attribute"; +static const std::string attribute_name_suffix = "_attribute_name"; + +/** + * \return Whether using an attribute to input values of this type is supported. + */ +static bool socket_type_has_attribute_toggle(const bNodeSocket &socket) +{ + return ELEM(socket.type, SOCK_FLOAT, SOCK_VECTOR, SOCK_BOOLEAN, SOCK_RGBA, SOCK_INT); +} + static IDProperty *id_property_create_from_socket(const bNodeSocket &socket) { switch (socket.type) { @@ -329,8 +344,24 @@ static IDProperty *id_property_create_from_socket(const bNodeSocket &socket) ui_data->max = ui_data->soft_max = (double)value->max; ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default"); ui_data->default_array_len = 3; - for (int i = 3; i < 3; i++) { - ui_data->default_array[i] = (double)value->value[i]; + for (const int i : IndexRange(3)) { + ui_data->default_array[i] = double(value->value[i]); + } + return property; + } + case SOCK_RGBA: { + bNodeSocketValueRGBA *value = (bNodeSocketValueRGBA *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.array.len = 4; + idprop.array.type = IDP_FLOAT; + IDProperty *property = IDP_New(IDP_ARRAY, &idprop, socket.identifier); + copy_v4_v4((float *)IDP_Array(property), value->value); + IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property); + ui_data->base.rna_subtype = PROP_COLOR; + ui_data->default_array = (double *)MEM_mallocN(sizeof(double[4]), __func__); + ui_data->default_array_len = 4; + for (const int i : IndexRange(4)) { + ui_data->default_array[i] = double(value->value[i]); } return property; } @@ -390,6 +421,8 @@ static bool id_property_type_matches_socket(const bNodeSocket &socket, const IDP return property.type == IDP_INT; case SOCK_VECTOR: return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3; + case SOCK_RGBA: + return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 4; case SOCK_BOOLEAN: return property.type == IDP_INT; case SOCK_STRING: @@ -410,28 +443,42 @@ static void init_socket_cpp_value_from_property(const IDProperty &property, { switch (socket_value_type) { case SOCK_FLOAT: { + float value = 0.0f; if (property.type == IDP_FLOAT) { - *(float *)r_value = IDP_Float(&property); + value = IDP_Float(&property); } else if (property.type == IDP_DOUBLE) { - *(float *)r_value = (float)IDP_Double(&property); + value = (float)IDP_Double(&property); } + new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value)); break; } case SOCK_INT: { - *(int *)r_value = IDP_Int(&property); + int value = IDP_Int(&property); + new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value)); break; } case SOCK_VECTOR: { - copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); + float3 value; + copy_v3_v3(value, (const float *)IDP_Array(&property)); + new (r_value) blender::fn::Field<float3>(blender::fn::make_constant_field(value)); + break; + } + case SOCK_RGBA: { + blender::ColorGeometry4f value; + copy_v4_v4((float *)value, (const float *)IDP_Array(&property)); + new (r_value) blender::fn::Field<ColorGeometry4f>(blender::fn::make_constant_field(value)); break; } case SOCK_BOOLEAN: { - *(bool *)r_value = IDP_Int(&property) != 0; + bool value = IDP_Int(&property) != 0; + new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value)); break; } case SOCK_STRING: { - new (r_value) std::string(IDP_String(&property)); + std::string value = IDP_String(&property); + new (r_value) + blender::fn::Field<std::string>(blender::fn::make_constant_field(std::move(value))); break; } case SOCK_OBJECT: { @@ -512,6 +559,32 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) new_prop->ui_data = ui_data; } } + + if (socket_type_has_attribute_toggle(*socket)) { + const std::string use_attribute_id = socket->identifier + use_attribute_suffix; + const std::string attribute_name_id = socket->identifier + attribute_name_suffix; + + IDPropertyTemplate idprop = {0}; + IDProperty *use_attribute_prop = IDP_New(IDP_INT, &idprop, use_attribute_id.c_str()); + IDP_AddToGroup(nmd->settings.properties, use_attribute_prop); + + IDProperty *attribute_prop = IDP_New(IDP_STRING, &idprop, attribute_name_id.c_str()); + IDP_AddToGroup(nmd->settings.properties, attribute_prop); + + if (old_properties != nullptr) { + IDProperty *old_prop_use_attribute = IDP_GetPropertyFromGroup(old_properties, + use_attribute_id.c_str()); + if (old_prop_use_attribute != nullptr) { + IDP_CopyPropertyContent(use_attribute_prop, old_prop_use_attribute); + } + + IDProperty *old_attribute_name_prop = IDP_GetPropertyFromGroup(old_properties, + attribute_name_id.c_str()); + if (old_attribute_name_prop != nullptr) { + IDP_CopyPropertyContent(attribute_prop, old_attribute_name_prop); + } + } + } } if (old_properties != nullptr) { @@ -567,8 +640,33 @@ static void initialize_group_input(NodesModifierData &nmd, return; } - init_socket_cpp_value_from_property( - *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + if (!socket_type_has_attribute_toggle(socket)) { + init_socket_cpp_value_from_property( + *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + return; + } + + const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup( + 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()); + if (property_use_attribute == nullptr || property_attribute_name == nullptr) { + init_socket_cpp_value_from_property( + *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + return; + } + + const bool use_attribute = IDP_Int(property_use_attribute) != 0; + 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()); + 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); + } } static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain) @@ -878,13 +976,13 @@ static void modifyGeometrySet(ModifierData *md, * the node socket identifier for the property names, since they are unique, but also having * the correct label displayed in the UI. */ static void draw_property_for_socket(uiLayout *layout, + NodesModifierData *nmd, PointerRNA *bmain_ptr, PointerRNA *md_ptr, - const IDProperty *modifier_props, const bNodeSocket &socket) { /* The property should be created in #MOD_nodes_update_interface with the correct type. */ - IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier); + IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, socket.identifier); /* IDProperties can be removed with python, so there could be a situation where * there isn't a property for a socket or it doesn't have the correct type. */ @@ -925,8 +1023,38 @@ static void draw_property_for_socket(uiLayout *layout, uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "textures", socket.name, ICON_TEXTURE); break; } - default: - uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); + default: { + if (socket_type_has_attribute_toggle(socket) && + USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) { + 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) + + attribute_name_suffix + "\"]"; + + uiLayout *row = uiLayoutRow(layout, true); + const int use_attribute = RNA_int_get(md_ptr, rna_path_use_attribute.c_str()) != 0; + if (use_attribute) { + uiItemR(row, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE); + } + else { + uiItemR(row, md_ptr, rna_path, 0, socket.name, ICON_NONE); + } + PointerRNA props; + uiItemFullO(row, + "object.geometry_nodes_input_attribute_toggle", + "", + ICON_SPREADSHEET, + nullptr, + WM_OP_INVOKE_DEFAULT, + 0, + &props); + RNA_string_set(&props, "modifier_name", nmd->modifier.name); + RNA_string_set(&props, "prop_path", rna_path_use_attribute.c_str()); + } + else { + uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); + } + } } } @@ -957,7 +1085,7 @@ static void panel_draw(const bContext *C, Panel *panel) RNA_main_pointer_create(bmain, &bmain_ptr); LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { - draw_property_for_socket(layout, &bmain_ptr, ptr, nmd->settings.properties, *socket); + draw_property_for_socket(layout, nmd, &bmain_ptr, ptr, *socket); } } @@ -1041,9 +1169,10 @@ ModifierTypeInfo modifierType_Nodes = { /* srna */ &RNA_NodesModifier, /* type */ eModifierTypeType_Constructive, /* flags */ - static_cast<ModifierTypeFlag>( - eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode | - eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping), + static_cast<ModifierTypeFlag>(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs | + eModifierTypeFlag_SupportsEditmode | + eModifierTypeFlag_EnableInEditmode | + eModifierTypeFlag_SupportsMapping), /* icon */ ICON_NODETREE, /* copyData */ copyData, diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index 5646e37707c..56de0f87ed8 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -21,6 +21,8 @@ #include "DEG_depsgraph_query.h" +#include "FN_field.hh" +#include "FN_field_cpp_type.hh" #include "FN_generic_value_map.hh" #include "FN_multi_function.hh" @@ -33,6 +35,9 @@ namespace blender::modifiers::geometry_nodes { using fn::CPPType; +using fn::Field; +using fn::FieldCPPType; +using fn::GField; using fn::GValueMap; using nodes::GeoNodeExecParams; using namespace fn::multi_function_types; @@ -316,6 +321,18 @@ static const CPPType *get_socket_cpp_type(const DSocket socket) static void get_socket_value(const SocketRef &socket, void *r_value) { + const bNodeSocket &bsocket = *socket.bsocket(); + /* This is not supposed to be a long term solution. Eventually we want that nodes can specify + * 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; + } + } const bNodeSocketType *typeinfo = socket.typeinfo(); typeinfo->get_geometry_nodes_cpp_value(*socket.bsocket(), r_value); } @@ -858,11 +875,10 @@ class GeometryNodesEvaluator { const MultiFunction &fn, NodeState &node_state) { - MFContextBuilder fn_context; - MFParamsBuilder fn_params{fn, 1}; LinearAllocator<> &allocator = local_allocators_.local(); /* Prepare the inputs for the multi function. */ + Vector<GField> input_fields; for (const int i : node->inputs().index_range()) { const InputSocketRef &socket_ref = node->input(i); if (!socket_ref.is_available()) { @@ -873,24 +889,12 @@ class GeometryNodesEvaluator { BLI_assert(input_state.was_ready_for_execution); SingleInputValue &single_value = *input_state.value.single; BLI_assert(single_value.value != nullptr); - fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value}); - } - /* Prepare the outputs for the multi function. */ - Vector<GMutablePointer> outputs; - for (const int i : node->outputs().index_range()) { - const OutputSocketRef &socket_ref = node->output(i); - if (!socket_ref.is_available()) { - continue; - } - const CPPType &type = *get_socket_cpp_type(socket_ref); - void *buffer = allocator.allocate(type.size(), type.alignment()); - fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1}); - outputs.append({type, buffer}); + input_fields.append(std::move(*(GField *)single_value.value)); } - fn.call(IndexRange(1), fn_params, fn_context); + auto operation = std::make_shared<fn::FieldOperation>(fn, std::move(input_fields)); - /* Forward the computed outputs. */ + /* Forward outputs. */ int output_index = 0; for (const int i : node->outputs().index_range()) { const OutputSocketRef &socket_ref = node->output(i); @@ -899,8 +903,11 @@ class GeometryNodesEvaluator { } OutputState &output_state = node_state.outputs[i]; const DOutputSocket socket{node.context(), &socket_ref}; - GMutablePointer value = outputs[output_index]; - this->forward_output(socket, value); + const CPPType *cpp_type = get_socket_cpp_type(socket_ref); + GField new_field{operation, output_index}; + new_field = fn::make_field_constant_if_possible(std::move(new_field)); + GField &field_to_forward = *allocator.construct<GField>(std::move(new_field)).release(); + this->forward_output(socket, {cpp_type, &field_to_forward}); output_state.has_been_computed = true; output_index++; } @@ -922,7 +929,7 @@ class GeometryNodesEvaluator { OutputState &output_state = node_state.outputs[socket->index()]; output_state.has_been_computed = true; void *buffer = allocator.allocate(type->size(), type->alignment()); - type->copy_construct(type->default_value(), buffer); + this->construct_default_value(*type, buffer); this->forward_output({node.context(), socket}, {*type, buffer}); } } @@ -1389,14 +1396,42 @@ class GeometryNodesEvaluator { return; } + const FieldCPPType *from_field_type = dynamic_cast<const FieldCPPType *>(&from_type); + const FieldCPPType *to_field_type = dynamic_cast<const FieldCPPType *>(&to_type); + + if (from_field_type != nullptr && to_field_type != nullptr) { + const CPPType &from_base_type = from_field_type->field_type(); + const CPPType &to_base_type = to_field_type->field_type(); + if (conversions_.is_convertible(from_base_type, to_base_type)) { + const MultiFunction &fn = *conversions_.get_conversion_multi_function( + MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type)); + const GField &from_field = *(const GField *)from_value; + auto operation = std::make_shared<fn::FieldOperation>(fn, Vector<GField>{from_field}); + new (to_value) GField(std::move(operation), 0); + return; + } + } if (conversions_.is_convertible(from_type, to_type)) { /* Do the conversion if possible. */ conversions_.convert_to_uninitialized(from_type, to_type, from_value, to_value); } else { /* Cannot convert, use default value instead. */ - to_type.copy_construct(to_type.default_value(), to_value); + this->construct_default_value(to_type, to_value); + } + } + + void construct_default_value(const CPPType &type, void *r_value) + { + if (const FieldCPPType *field_cpp_type = dynamic_cast<const FieldCPPType *>(&type)) { + const CPPType &base_type = field_cpp_type->field_type(); + auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>( + base_type, base_type.default_value(), false); + auto operation = std::make_shared<fn::FieldOperation>(std::move(constant_fn)); + new (r_value) GField(std::move(operation), 0); + return; } + type.copy_construct(type.default_value(), r_value); } NodeState &get_node_state(const DNode node) diff --git a/source/blender/modifiers/intern/MOD_normal_edit.c b/source/blender/modifiers/intern/MOD_normal_edit.c index 1dbdcf87d63..db2eedf9c02 100644 --- a/source/blender/modifiers/intern/MOD_normal_edit.c +++ b/source/blender/modifiers/intern/MOD_normal_edit.c @@ -450,7 +450,7 @@ static void normalEditModifier_do_directional(NormalEditModifierData *enmd, if (do_polynors_fix && polygons_check_flip(mloop, nos, &mesh->ldata, mpoly, polynors, num_polys)) { - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } BKE_mesh_normals_loop_custom_set(mvert, diff --git a/source/blender/modifiers/intern/MOD_ocean.c b/source/blender/modifiers/intern/MOD_ocean.c index 1c502b94bdb..ff1055eff3b 100644 --- a/source/blender/modifiers/intern/MOD_ocean.c +++ b/source/blender/modifiers/intern/MOD_ocean.c @@ -317,7 +317,7 @@ static Mesh *generate_ocean_geometry(OceanModifierData *omd, Mesh *mesh_orig, co } } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } @@ -510,7 +510,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = doOcean(md, ctx, mesh); if (result != mesh) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } return result; diff --git a/source/blender/modifiers/intern/MOD_particleinstance.c b/source/blender/modifiers/intern/MOD_particleinstance.c index 49b5dabe72d..4fffa7c93f3 100644 --- a/source/blender/modifiers/intern/MOD_particleinstance.c +++ b/source/blender/modifiers/intern/MOD_particleinstance.c @@ -545,7 +545,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * MEM_SAFE_FREE(vert_part_index); MEM_SAFE_FREE(vert_part_value); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_remesh.c b/source/blender/modifiers/intern/MOD_remesh.c index df3db894f4e..fef1f76c051 100644 --- a/source/blender/modifiers/intern/MOD_remesh.c +++ b/source/blender/modifiers/intern/MOD_remesh.c @@ -220,7 +220,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *UNUSED(ctx) BKE_mesh_copy_parameters_for_eval(result, mesh); BKE_mesh_calc_edges(result, true, false); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_screw.c b/source/blender/modifiers/intern/MOD_screw.c index 0819b314e32..f24f6951690 100644 --- a/source/blender/modifiers/intern/MOD_screw.c +++ b/source/blender/modifiers/intern/MOD_screw.c @@ -1135,12 +1135,12 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * ob_axis != NULL ? mtx_tx[3] : NULL, ltmd->merge_dist); if (result != result_prev) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } } if ((ltmd->flag & MOD_SCREW_NORMAL_CALC) == 0) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } return result; diff --git a/source/blender/modifiers/intern/MOD_skin.c b/source/blender/modifiers/intern/MOD_skin.c index 543cee18868..7d90935f678 100644 --- a/source/blender/modifiers/intern/MOD_skin.c +++ b/source/blender/modifiers/intern/MOD_skin.c @@ -1960,7 +1960,7 @@ static Mesh *base_skin(Mesh *origmesh, SkinModifierData *smd, eSkinErrorFlag *r_ result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, origmesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); skin_set_orig_indices(result); diff --git a/source/blender/modifiers/intern/MOD_solidify_extrude.c b/source/blender/modifiers/intern/MOD_solidify_extrude.c index 00fa6e24a64..8f9aa86e561 100644 --- a/source/blender/modifiers/intern/MOD_solidify_extrude.c +++ b/source/blender/modifiers/intern/MOD_solidify_extrude.c @@ -988,7 +988,7 @@ Mesh *MOD_solidify_extrude_modifyMesh(ModifierData *md, const ModifierEvalContex /* must recalculate normals with vgroups since they can displace unevenly T26888. */ if ((mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) || do_rim || dvert) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } else if (do_shell) { uint i; diff --git a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c index 5b4716a1a43..f654b69841e 100644 --- a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c +++ b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c @@ -1955,7 +1955,7 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, } } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); /* Make edges. */ { diff --git a/source/blender/modifiers/intern/MOD_triangulate.c b/source/blender/modifiers/intern/MOD_triangulate.c index ef633494c7b..52d5f3e97ef 100644 --- a/source/blender/modifiers/intern/MOD_triangulate.c +++ b/source/blender/modifiers/intern/MOD_triangulate.c @@ -107,7 +107,7 @@ Mesh *triangulate_mesh(Mesh *mesh, me->flag |= ME_EDGEDRAW | ME_EDGERENDER; } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c index 5b97d0eb259..d57e92b4b35 100644 --- a/source/blender/modifiers/intern/MOD_util.c +++ b/source/blender/modifiers/intern/MOD_util.c @@ -216,7 +216,6 @@ Mesh *MOD_deform_mesh_eval_get(Object *ob, * we really need vertexCos here. */ else if (vertexCos) { BKE_mesh_vert_coords_apply(mesh, vertexCos); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; } if (use_orco) { diff --git a/source/blender/modifiers/intern/MOD_weld.c b/source/blender/modifiers/intern/MOD_weld.c index b1fa2a7d912..503297d5985 100644 --- a/source/blender/modifiers/intern/MOD_weld.c +++ b/source/blender/modifiers/intern/MOD_weld.c @@ -1979,8 +1979,7 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, BLI_assert(loop_cur == result_nloops); /* is this needed? */ - /* recalculate normals */ - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); weld_mesh_context_free(&weld_mesh); } diff --git a/source/blender/modifiers/intern/MOD_wireframe.c b/source/blender/modifiers/intern/MOD_wireframe.c index e188a61e975..706960182cf 100644 --- a/source/blender/modifiers/intern/MOD_wireframe.c +++ b/source/blender/modifiers/intern/MOD_wireframe.c @@ -109,7 +109,7 @@ static Mesh *WireframeModifier_do(WireframeModifierData *wmd, Object *ob, Mesh * result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 29a1de381b5..b0fc55fab0c 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -104,6 +104,7 @@ set(SRC 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 @@ -140,7 +141,11 @@ set(SRC function/nodes/node_fn_random_float.cc function/node_function_util.cc + geometry/nodes/legacy/node_geo_material_assign.cc + geometry/nodes/legacy/node_geo_select_by_material.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 @@ -186,10 +191,14 @@ set(SRC geometry/nodes/node_geo_delete_geometry.cc geometry/nodes/node_geo_edge_split.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_index.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc geometry/nodes/node_geo_material_assign.cc geometry/nodes/node_geo_material_replace.cc + geometry/nodes/node_geo_material_selection.cc geometry/nodes/node_geo_mesh_primitive_circle.cc geometry/nodes/node_geo_mesh_primitive_cone.cc geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -209,8 +218,9 @@ set(SRC 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_select_by_material.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_subdivision_surface.cc geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc @@ -286,16 +296,16 @@ set(SRC shader/nodes/node_shader_tex_checker.c shader/nodes/node_shader_tex_coord.c shader/nodes/node_shader_tex_environment.c - shader/nodes/node_shader_tex_gradient.c + shader/nodes/node_shader_tex_gradient.cc shader/nodes/node_shader_tex_image.c shader/nodes/node_shader_tex_magic.c - shader/nodes/node_shader_tex_musgrave.c - shader/nodes/node_shader_tex_noise.c + shader/nodes/node_shader_tex_musgrave.cc + shader/nodes/node_shader_tex_noise.cc shader/nodes/node_shader_tex_pointdensity.c shader/nodes/node_shader_tex_sky.c - shader/nodes/node_shader_tex_voronoi.c + shader/nodes/node_shader_tex_voronoi.cc shader/nodes/node_shader_tex_wave.c - shader/nodes/node_shader_tex_white_noise.c + shader/nodes/node_shader_tex_white_noise.cc shader/nodes/node_shader_uvAlongStroke.c shader/nodes/node_shader_uvmap.c shader/nodes/node_shader_valToRgb.cc diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h index 258e4c961c9..2cbbd31c97a 100644 --- a/source/blender/nodes/NOD_composite.h +++ b/source/blender/nodes/NOD_composite.h @@ -80,6 +80,7 @@ void register_node_type_cmp_despeckle(void); void register_node_type_cmp_defocus(void); void register_node_type_cmp_denoise(void); void register_node_type_cmp_antialiasing(void); +void register_node_type_cmp_posterize(void); void register_node_type_cmp_valtorgb(void); void register_node_type_cmp_rgbtobw(void); diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 00062400eee..0d31ae2143a 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -29,6 +29,9 @@ 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_material_assign(void); +void register_node_type_geo_legacy_select_by_material(void); + void register_node_type_geo_align_rotation_to_vector(void); void register_node_type_geo_attribute_clamp(void); void register_node_type_geo_attribute_color_ramp(void); @@ -37,6 +40,7 @@ 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); @@ -71,11 +75,15 @@ 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_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_is_viewport(void); void register_node_type_geo_join_geometry(void); void register_node_type_geo_material_assign(void); void register_node_type_geo_material_replace(void); +void register_node_type_geo_material_selection(void); void register_node_type_geo_mesh_primitive_circle(void); void register_node_type_geo_mesh_primitive_cone(void); void register_node_type_geo_mesh_primitive_cube(void); @@ -95,10 +103,11 @@ void register_node_type_geo_point_separate(void); void register_node_type_geo_point_translate(void); void register_node_type_geo_points_to_volume(void); void register_node_type_geo_raycast(void); +void register_node_type_geo_realize_instances(void); void register_node_type_geo_sample_texture(void); void register_node_type_geo_select_by_handle_type(void); -void register_node_type_geo_select_by_material(void); void register_node_type_geo_separate_components(void); +void register_node_type_geo_set_position(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 d6a23051c0b..dbb5f8b240d 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -16,7 +16,8 @@ #pragma once -#include "FN_generic_value_map.hh" +#include "FN_field.hh" +#include "FN_multi_function_builder.hh" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.hh" @@ -32,17 +33,24 @@ struct ModifierData; namespace blender::nodes { +using bke::AttributeIDRef; using bke::geometry_set_realize_instances; +using bke::GeometryComponentFieldContext; using bke::OutputAttribute; using bke::OutputAttribute_Typed; using bke::ReadAttributeLookup; +using bke::StrongAnonymousAttributeID; +using bke::WeakAnonymousAttributeID; using bke::WriteAttributeLookup; using fn::CPPType; +using fn::Field; +using fn::FieldInput; +using fn::FieldOperation; +using fn::GField; using fn::GMutablePointer; using fn::GMutableSpan; using fn::GPointer; using fn::GSpan; -using fn::GValueMap; using fn::GVArray; using fn::GVArray_GSpan; using fn::GVArray_Span; @@ -121,6 +129,14 @@ class GeoNodeExecParams { { } + template<typename T> + static inline constexpr bool is_stored_as_field_v = std::is_same_v<T, float> || + std::is_same_v<T, int> || + std::is_same_v<T, bool> || + std::is_same_v<T, ColorGeometry4f> || + std::is_same_v<T, float3> || + std::is_same_v<T, std::string>; + /** * Get the input value for the input socket with the given identifier. * @@ -142,11 +158,17 @@ class GeoNodeExecParams { */ template<typename T> T extract_input(StringRef identifier) { + if constexpr (is_stored_as_field_v<T>) { + Field<T> field = this->extract_input<Field<T>>(identifier); + return fn::evaluate_constant_field(field); + } + else { #ifdef DEBUG - this->check_input_access(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - GMutablePointer gvalue = this->extract_input(identifier); - return gvalue.relocate_out<T>(); + GMutablePointer gvalue = this->extract_input(identifier); + return gvalue.relocate_out<T>(); + } } /** @@ -159,7 +181,13 @@ class GeoNodeExecParams { Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier); Vector<T> values; for (GMutablePointer gvalue : gvalues) { - values.append(gvalue.relocate_out<T>()); + if constexpr (is_stored_as_field_v<T>) { + const Field<T> field = gvalue.relocate_out<Field<T>>(); + values.append(fn::evaluate_constant_field(field)); + } + else { + values.append(gvalue.relocate_out<T>()); + } } return values; } @@ -167,14 +195,20 @@ class GeoNodeExecParams { /** * Get the input value for the input socket with the given identifier. */ - template<typename T> const T &get_input(StringRef identifier) const + template<typename T> const T get_input(StringRef identifier) const { + if constexpr (is_stored_as_field_v<T>) { + const Field<T> &field = this->get_input<Field<T>>(identifier); + return fn::evaluate_constant_field(field); + } + else { #ifdef DEBUG - this->check_input_access(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - GPointer gvalue = provider_->get_input(identifier); - BLI_assert(gvalue.is_type<T>()); - return *(const T *)gvalue.get(); + GPointer gvalue = provider_->get_input(identifier); + BLI_assert(gvalue.is_type<T>()); + return *(const T *)gvalue.get(); + } } /** @@ -183,13 +217,19 @@ class GeoNodeExecParams { template<typename T> void set_output(StringRef identifier, T &&value) { using StoredT = std::decay_t<T>; - const CPPType &type = CPPType::get<std::decay_t<T>>(); + if constexpr (is_stored_as_field_v<StoredT>) { + this->set_output<Field<StoredT>>(identifier, + fn::make_constant_field<StoredT>(std::forward<T>(value))); + } + else { + const CPPType &type = CPPType::get<StoredT>(); #ifdef DEBUG - this->check_output_access(identifier, type); + this->check_output_access(identifier, type); #endif - GMutablePointer gvalue = provider_->alloc_output_value(type); - new (gvalue.get()) StoredT(std::forward<T>(value)); - provider_->set_output(identifier, gvalue); + GMutablePointer gvalue = provider_->alloc_output_value(type); + new (gvalue.get()) StoredT(std::forward<T>(value)); + provider_->set_output(identifier, gvalue); + } } /** diff --git a/source/blender/nodes/NOD_multi_function.hh b/source/blender/nodes/NOD_multi_function.hh index 2f4b104fb4c..58816544dc1 100644 --- a/source/blender/nodes/NOD_multi_function.hh +++ b/source/blender/nodes/NOD_multi_function.hh @@ -114,7 +114,7 @@ inline void NodeMultiFunctionBuilder::set_matching_fn(const MultiFunction &fn) template<typename T, typename... Args> inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...args) { - const T &fn = resource_scope_.construct<T>(__func__, std::forward<Args>(args)...); + const T &fn = resource_scope_.construct<T>(std::forward<Args>(args)...); this->set_matching_fn(&fn); } diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index 52f4ac291d2..d64b76ccbb9 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -27,12 +27,19 @@ namespace blender::nodes { class NodeDeclarationBuilder; +/** + * Describes a single input or output socket. This is subclassed for different socket types. + */ class SocketDeclaration { protected: std::string name_; std::string identifier_; + bool hide_label_ = false; + bool hide_value_ = false; + bool is_multi_input_ = false; friend NodeDeclarationBuilder; + template<typename SocketDecl> friend class SocketDeclarationBuilder; public: virtual ~SocketDeclaration() = default; @@ -43,6 +50,49 @@ class SocketDeclaration { StringRefNull name() const; StringRefNull identifier() const; + + protected: + void set_common_flags(bNodeSocket &socket) const; + bool matches_common_data(const bNodeSocket &socket) const; +}; + +class BaseSocketDeclarationBuilder { + public: + virtual ~BaseSocketDeclarationBuilder() = default; +}; + +/** + * Wraps a #SocketDeclaration and provides methods to set it up correctly. + * This is separate from #SocketDeclaration, because it allows separating the API used by nodes to + * declare themselves from how the declaration is stored internally. + */ +template<typename SocketDecl> +class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder { + protected: + using Self = typename SocketDecl::Builder; + static_assert(std::is_base_of_v<SocketDeclaration, SocketDecl>); + SocketDecl *decl_; + + friend class NodeDeclarationBuilder; + + public: + Self &hide_label(bool value = true) + { + decl_->hide_label_ = value; + return *(Self *)this; + } + + Self &hide_value(bool value = true) + { + decl_->hide_value_ = value; + return *(Self *)this; + } + + Self &multi_input(bool value = true) + { + decl_->is_multi_input_ = value; + return *(Self *)this; + } }; using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>; @@ -60,17 +110,28 @@ class NodeDeclaration { Span<SocketDeclarationPtr> inputs() const; Span<SocketDeclarationPtr> outputs() const; + + MEM_CXX_CLASS_ALLOC_FUNCS("NodeDeclaration") }; class NodeDeclarationBuilder { private: NodeDeclaration &declaration_; + Vector<std::unique_ptr<BaseSocketDeclarationBuilder>> builders_; public: NodeDeclarationBuilder(NodeDeclaration &declaration); - template<typename DeclType> DeclType &add_input(StringRef name, StringRef identifier = ""); - template<typename DeclType> DeclType &add_output(StringRef name, StringRef identifier = ""); + template<typename DeclType> + typename DeclType::Builder &add_input(StringRef name, StringRef identifier = ""); + template<typename DeclType> + typename DeclType::Builder &add_output(StringRef name, StringRef identifier = ""); + + private: + template<typename DeclType> + typename DeclType::Builder &add_socket(StringRef name, + StringRef identifier, + Vector<SocketDeclarationPtr> &r_decls); }; /* -------------------------------------------------------------------- @@ -97,27 +158,34 @@ inline NodeDeclarationBuilder::NodeDeclarationBuilder(NodeDeclaration &declarati } template<typename DeclType> -inline DeclType &NodeDeclarationBuilder::add_input(StringRef name, StringRef identifier) +inline typename DeclType::Builder &NodeDeclarationBuilder::add_input(StringRef name, + StringRef identifier) { - static_assert(std::is_base_of_v<SocketDeclaration, DeclType>); - std::unique_ptr<DeclType> socket_decl = std::make_unique<DeclType>(); - DeclType &ref = *socket_decl; - ref.name_ = name; - ref.identifier_ = identifier.is_empty() ? name : identifier; - declaration_.inputs_.append(std::move(socket_decl)); - return ref; + return this->add_socket<DeclType>(name, identifier, declaration_.inputs_); +} + +template<typename DeclType> +inline typename DeclType::Builder &NodeDeclarationBuilder::add_output(StringRef name, + StringRef identifier) +{ + return this->add_socket<DeclType>(name, identifier, declaration_.outputs_); } template<typename DeclType> -inline DeclType &NodeDeclarationBuilder::add_output(StringRef name, StringRef identifier) +inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket( + StringRef name, StringRef identifier, Vector<SocketDeclarationPtr> &r_decls) { static_assert(std::is_base_of_v<SocketDeclaration, DeclType>); + using Builder = typename DeclType::Builder; std::unique_ptr<DeclType> socket_decl = std::make_unique<DeclType>(); - DeclType &ref = *socket_decl; - ref.name_ = name; - ref.identifier_ = identifier.is_empty() ? name : identifier; - declaration_.outputs_.append(std::move(socket_decl)); - return ref; + std::unique_ptr<Builder> socket_decl_builder = std::make_unique<Builder>(); + socket_decl_builder->decl_ = &*socket_decl; + socket_decl->name_ = name; + socket_decl->identifier_ = identifier.is_empty() ? name : identifier; + r_decls.append(std::move(socket_decl)); + Builder &socket_decl_builder_ref = *socket_decl_builder; + builders_.append(std::move(socket_decl_builder)); + return socket_decl_builder_ref; } /* -------------------------------------------------------------------- diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh index 639d2c12d73..3d0cfdb5d5d 100644 --- a/source/blender/nodes/NOD_socket_declarations.hh +++ b/source/blender/nodes/NOD_socket_declarations.hh @@ -25,6 +25,8 @@ namespace blender::nodes::decl { +class FloatBuilder; + class Float : public SocketDeclaration { private: float default_value_ = 0.0f; @@ -32,36 +34,45 @@ class Float : public SocketDeclaration { float soft_max_value_ = FLT_MAX; PropertySubType subtype_ = PROP_NONE; + friend FloatBuilder; + public: - Float &min(const float value) + using Builder = FloatBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; +}; + +class FloatBuilder : public SocketDeclarationBuilder<Float> { + public: + FloatBuilder &min(const float value) { - soft_min_value_ = value; + decl_->soft_min_value_ = value; return *this; } - Float &max(const float value) + FloatBuilder &max(const float value) { - soft_max_value_ = value; + decl_->soft_max_value_ = value; return *this; } - Float &default_value(const float value) + FloatBuilder &default_value(const float value) { - default_value_ = value; + decl_->default_value_ = value; return *this; } - Float &subtype(PropertySubType subtype) + FloatBuilder &subtype(PropertySubType subtype) { - subtype_ = subtype; + decl_->subtype_ = subtype; return *this; } - - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; - bool matches(const bNodeSocket &socket) const override; - bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; }; +class IntBuilder; + class Int : public SocketDeclaration { private: int default_value_ = 0; @@ -69,169 +80,198 @@ class Int : public SocketDeclaration { int soft_max_value_ = INT32_MAX; PropertySubType subtype_ = PROP_NONE; + friend IntBuilder; + + public: + using Builder = IntBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; +}; + +class IntBuilder : public SocketDeclarationBuilder<Int> { public: - Int &min(const int value) + IntBuilder &min(const int value) { - soft_min_value_ = value; + decl_->soft_min_value_ = value; return *this; } - Int &max(const int value) + IntBuilder &max(const int value) { - soft_max_value_ = value; + decl_->soft_max_value_ = value; return *this; } - Int &default_value(const int value) + IntBuilder &default_value(const int value) { - default_value_ = value; + decl_->default_value_ = value; return *this; } - Int &subtype(PropertySubType subtype) + IntBuilder &subtype(PropertySubType subtype) { - subtype_ = subtype; + decl_->subtype_ = subtype; return *this; } - - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; - bool matches(const bNodeSocket &socket) const override; - bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; }; +class VectorBuilder; + class Vector : public SocketDeclaration { private: float3 default_value_ = {0, 0, 0}; + float soft_min_value_ = -FLT_MAX; + float soft_max_value_ = FLT_MAX; PropertySubType subtype_ = PROP_NONE; + friend VectorBuilder; + + public: + using Builder = VectorBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; +}; + +class VectorBuilder : public SocketDeclarationBuilder<Vector> { public: - Vector &default_value(const float3 value) + VectorBuilder &default_value(const float3 value) { - default_value_ = value; + decl_->default_value_ = value; return *this; } - Vector &subtype(PropertySubType subtype) + VectorBuilder &subtype(PropertySubType subtype) { - subtype_ = subtype; + decl_->subtype_ = subtype; return *this; } - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; - bool matches(const bNodeSocket &socket) const override; - bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; + VectorBuilder &min(const float min) + { + decl_->soft_min_value_ = min; + return *this; + } + + VectorBuilder &max(const float max) + { + decl_->soft_max_value_ = max; + return *this; + } }; +class BoolBuilder; + class Bool : public SocketDeclaration { private: bool default_value_ = false; + friend BoolBuilder; public: - Bool &default_value(const bool value) - { - default_value_ = value; - return *this; - } + using Builder = BoolBuilder; bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; +class BoolBuilder : public SocketDeclarationBuilder<Bool> { + public: + BoolBuilder &default_value(const bool value) + { + decl_->default_value_ = value; + return *this; + } +}; + +class ColorBuilder; + class Color : public SocketDeclaration { private: ColorGeometry4f default_value_; + friend ColorBuilder; + public: - Color &default_value(const ColorGeometry4f value) - { - default_value_ = value; - return *this; - } + using Builder = ColorBuilder; bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; +class ColorBuilder : public SocketDeclarationBuilder<Color> { + public: + ColorBuilder &default_value(const ColorGeometry4f value) + { + decl_->default_value_ = value; + return *this; + } +}; + class String : public SocketDeclaration { public: + using Builder = SocketDeclarationBuilder<String>; + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; -namespace detail { -struct CommonIDSocketData { - const char *idname; - bool hide_label = false; -}; - -bNodeSocket &build_id_socket(bNodeTree &ntree, - bNode &node, - eNodeSocketInOut in_out, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier); -bool matches_id_socket(const bNodeSocket &socket, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier); - -template<typename Subtype> class IDSocketDeclaration : public SocketDeclaration { +class IDSocketDeclaration : public SocketDeclaration { private: - CommonIDSocketData data_; + const char *idname_; public: - IDSocketDeclaration(const char *idname) : data_({idname}) - { - } - - Subtype &hide_label(bool value) - { - data_.hide_label = value; - return *(Subtype *)this; - } - - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override + IDSocketDeclaration(const char *idname) : idname_(idname) { - return build_id_socket(ntree, node, in_out, data_, name_, identifier_); } - bool matches(const bNodeSocket &socket) const override - { - return matches_id_socket(socket, data_, name_, identifier_); - } + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; }; -} // namespace detail -class Object : public detail::IDSocketDeclaration<Object> { +class Object : public IDSocketDeclaration { public: - Object() : detail::IDSocketDeclaration<Object>("NodeSocketObject") + using Builder = SocketDeclarationBuilder<Object>; + + Object() : IDSocketDeclaration("NodeSocketObject") { } }; -class Material : public detail::IDSocketDeclaration<Material> { +class Material : public IDSocketDeclaration { public: - Material() : detail::IDSocketDeclaration<Material>("NodeSocketMaterial") + using Builder = SocketDeclarationBuilder<Material>; + + Material() : IDSocketDeclaration("NodeSocketMaterial") { } }; -class Collection : public detail::IDSocketDeclaration<Collection> { +class Collection : public IDSocketDeclaration { public: - Collection() : detail::IDSocketDeclaration<Collection>("NodeSocketCollection") + using Builder = SocketDeclarationBuilder<Collection>; + + Collection() : IDSocketDeclaration("NodeSocketCollection") { } }; -class Texture : public detail::IDSocketDeclaration<Texture> { +class Texture : public IDSocketDeclaration { public: - Texture() : detail::IDSocketDeclaration<Texture>("NodeSocketTexture") + using Builder = SocketDeclarationBuilder<Texture>; + + Texture() : IDSocketDeclaration("NodeSocketTexture") { } }; class Geometry : public SocketDeclaration { public: + using Builder = SocketDeclarationBuilder<Geometry>; + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 8028350418a..b2f1fa5e83a 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -225,6 +225,7 @@ DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE_LEGACY, def_cmp_cryptomatte_legacy, DefNode(CompositorNode, CMP_NODE_DENOISE, def_cmp_denoise, "DENOISE", Denoise, "Denoise", "" ) DefNode(CompositorNode, CMP_NODE_EXPOSURE, 0, "EXPOSURE", Exposure, "Exposure", "" ) DefNode(CompositorNode, CMP_NODE_ANTIALIASING, def_cmp_antialiasing, "ANTIALIASING", AntiAliasing, "Anti-Aliasing", "" ) +DefNode(CompositorNode, CMP_NODE_POSTERIZE, 0, "POSTERIZE", Posterize, "Posterize", "" ) DefNode(TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" ) DefNode(TextureNode, TEX_NODE_CHECKER, 0, "CHECKER", Checker, "Checker", "" ) @@ -268,25 +269,44 @@ DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_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(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "ATTRIBUTE_COLOR_RAMP", AttributeColorRamp, "Attribute Color Ramp", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") +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_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", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "LEGACY_ATTRIBUTE_CONVERT", LegacyAttributeConvert, "Attribute Convert", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "LEGACY_ATTRIBUTE_CURVE_MAP", LegacyAttributeCurveMap, "Attribute Curve Map", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_FILL, def_geo_attribute_fill, "LEGACY_ATTRIBUTE_FILL", LegacyAttributeFill, "Attribute Fill", "") +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_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_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_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_DELETE_GEOMETRY, 0, "LEGACY_DELETE_GEOMETRY", LegacyDeleteGeometry, "Delete Geometry", "") +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", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_INSTANCE, def_geo_point_instance, "LEGACY_POINT_INSTANCE", LegacyPointInstance, "Point Instance", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_ROTATE, def_geo_point_rotate, "LEGACY_POINT_ROTATE", LegacyRotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SCALE, def_geo_point_scale, "LEGACY_POINT_SCALE", LegacyPointScale, "Point Scale", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SEPARATE, 0, "LEGACY_POINT_SEPARATE", LegacyPointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_TRANSLATE, def_geo_point_translate, "LEGACY_POINT_TRANSLATE", LegacyPointTranslate, "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_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_SAMPLE_TEXTURE, 0, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "ATTRIBUTE_VECTOR_MATH", AttributeVectorMath, "Attribute Vector Math", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "ATTRIBUTE_VECTOR_ROTATE", AttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") 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", "") @@ -302,21 +322,19 @@ 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_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "CURVE_SELECT_HANDLES", CurveSelectHandles, "Select by Handle Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "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_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "") DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +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_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", "") DefNode(GeometryNode, GEO_NODE_MATERIAL_REPLACE, 0, "MATERIAL_REPLACE", MaterialReplace, "Material Replace", "") +DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Mesh Circle", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") @@ -326,18 +344,10 @@ 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_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "") DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") -DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") -DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") -DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") -DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") -DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") -DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") -DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") -DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "") -DefNode(GeometryNode, GEO_NODE_SELECT_BY_MATERIAL, 0, "SELECT_BY_MATERIAL", SelectByMaterial, "Select by Material", "") +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_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") diff --git a/source/blender/nodes/composite/nodes/node_composite_denoise.c b/source/blender/nodes/composite/nodes/node_composite_denoise.c index 040b350627e..e2c7c7b995f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_denoise.c +++ b/source/blender/nodes/composite/nodes/node_composite_denoise.c @@ -36,6 +36,7 @@ static void node_composit_init_denonise(bNodeTree *UNUSED(ntree), bNode *node) { NodeDenoise *ndg = 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/io/usd/intern/usd_reader_instance.h b/source/blender/nodes/composite/nodes/node_composite_posterize.c index efc1c69a7dd..5093e581cdc 100644 --- a/source/blender/io/usd/intern/usd_reader_instance.h +++ b/source/blender/nodes/composite/nodes/node_composite_posterize.c @@ -16,32 +16,31 @@ * The Original Code is Copyright (C) 2021 Blender Foundation. * All rights reserved. */ -#pragma once -#include "usd_reader_xform.h" - -#include <pxr/usd/usdGeom/xform.h> - -struct Collection; - -namespace blender::io::usd { - -/* Wraps the UsdGeomXform schema. Creates a Blender Empty object. */ - -class USDInstanceReader : public USDXformReader { +/** \file + * \ingroup cmpnodes + */ - public: - USDInstanceReader(const pxr::UsdPrim &prim, - const USDImportParams &import_params, - const ImportSettings &settings); +#include "node_composite_util.h" - bool valid() const override; +/* **************** Posterize ******************** */ - void create_object(Main *bmain, double motionSampleTime) override; +static bNodeSocketTemplate cmp_node_posterize_in[] = { + {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, + {SOCK_FLOAT, N_("Steps"), 8.0f, 8.0f, 8.0f, 8.0f, 2.0f, 1024.0f, PROP_NONE}, + {-1, ""}, +}; +static bNodeSocketTemplate cmp_node_posterize_out[] = { + {SOCK_RGBA, N_("Image")}, + {-1, ""}, +}; - void set_instance_collection(Collection *coll); +void register_node_type_cmp_posterize(void) +{ + static bNodeType ntype; - pxr::SdfPath proto_path() const; -}; + cmp_node_type_base(&ntype, CMP_NODE_POSTERIZE, "Posterize", NODE_CLASS_OP_COLOR, 0); + node_type_socket_templates(&ntype, cmp_node_posterize_in, cmp_node_posterize_out); -} // namespace blender::io::usd + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 6a69c3d24ec..015ac0de002 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -84,7 +84,7 @@ struct CurveToPointsResults { MutableSpan<float> radii; MutableSpan<float> tilts; - Map<std::string, GMutableSpan> point_attributes; + Map<AttributeIDRef, GMutableSpan> point_attributes; MutableSpan<float3> tangents; MutableSpan<float3> normals; diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc new file mode 100644 index 00000000000..7d3481c1067 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc @@ -0,0 +1,95 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +namespace blender::nodes { + +static void geo_node_legacy_material_assign_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Material>("Material").hide_label(true); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Geometry"); +} + +static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) +{ + int new_material_index = -1; + for (const int i : IndexRange(mesh.totcol)) { + Material *other_material = mesh.mat[i]; + if (other_material == material) { + new_material_index = i; + break; + } + } + if (new_material_index == -1) { + /* Append a new material index. */ + new_material_index = mesh.totcol; + BKE_id_material_eval_assign(&mesh.id, new_material_index + 1, material); + } + + mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly); + for (const int i : IndexRange(mesh.totpoly)) { + if (face_mask[i]) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; + } + } +} + +static void geo_node_legacy_material_assign_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + const std::string mask_name = params.extract_input<std::string>("Selection"); + + 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(); + if (mesh != nullptr) { + GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>( + mask_name, ATTR_DOMAIN_FACE, true); + assign_material_to_faces(*mesh, face_mask, material); + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_material_assign() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_legacy_material_assign_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_material_assign_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc index 79b93502103..eabdd2bcd5a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc @@ -28,10 +28,10 @@ namespace blender::nodes { -static void geo_node_select_by_material_declare(NodeDeclarationBuilder &b) +static void geo_node_legacy_select_by_material_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Material").hide_label(true); + b.add_input<decl::Material>("Material").hide_label(); b.add_input<decl::String>("Selection"); b.add_output<decl::Geometry>("Geometry"); } @@ -54,7 +54,7 @@ static void select_mesh_by_material(const Mesh &mesh, }); } -static void geo_node_select_by_material_exec(GeoNodeExecParams params) +static void geo_node_legacy_select_by_material_exec(GeoNodeExecParams params) { Material *material = params.extract_input<Material *>("Material"); const std::string selection_name = params.extract_input<std::string>("Selection"); @@ -80,13 +80,13 @@ static void geo_node_select_by_material_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_select_by_material() +void register_node_type_geo_legacy_select_by_material() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_select_by_material_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_select_by_material_exec; + &ntype, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_legacy_select_by_material_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_select_by_material_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc index b76556b6c6c..d0bb906e8af 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc @@ -226,7 +226,7 @@ void register_node_type_geo_align_rotation_to_vector() static bNodeType ntype; geo_node_type_base(&ntype, - GEO_NODE_ALIGN_ROTATION_TO_VECTOR, + GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, "Align Rotation to Vector", NODE_CLASS_GEOMETRY, 0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc new file mode 100644 index 00000000000..c8a33205de4 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -0,0 +1,210 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "BKE_attribute_math.hh" + +#include "node_geometry_util.hh" + +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_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"); +} + +static void geo_node_attribute_capture_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); +} + +static void geo_node_attribute_capture_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryAttributeCapture *data = (NodeGeometryAttributeCapture *)MEM_callocN( + sizeof(NodeGeometryAttributeCapture), __func__); + data->data_type = CD_PROP_FLOAT; + data->domain = ATTR_DOMAIN_POINT; + + node->storage = data; +} + +static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *) + node->storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + bNodeSocket *socket_value_geometry = (bNodeSocket *)node->inputs.first; + bNodeSocket *socket_value_vector = socket_value_geometry->next; + bNodeSocket *socket_value_float = socket_value_vector->next; + bNodeSocket *socket_value_color4f = socket_value_float->next; + bNodeSocket *socket_value_boolean = socket_value_color4f->next; + bNodeSocket *socket_value_int32 = socket_value_boolean->next; + + nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(socket_value_int32, data_type == CD_PROP_INT32); + + bNodeSocket *out_socket_value_geometry = (bNodeSocket *)node->outputs.first; + bNodeSocket *out_socket_value_vector = out_socket_value_geometry->next; + bNodeSocket *out_socket_value_float = out_socket_value_vector->next; + bNodeSocket *out_socket_value_color4f = out_socket_value_float->next; + bNodeSocket *out_socket_value_boolean = out_socket_value_color4f->next; + bNodeSocket *out_socket_value_int32 = out_socket_value_boolean->next; + + nodeSetSocketAvailability(out_socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(out_socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(out_socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(out_socket_value_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(out_socket_value_int32, data_type == CD_PROP_INT32); +} + +static void try_capture_field_on_geometry(GeometryComponent &component, + const AttributeIDRef &attribute_id, + const AttributeDomain domain, + const GField &field) +{ + GeometryComponentFieldContext field_context{component, domain}; + const int domain_size = component.attribute_domain_size(domain); + const IndexMask mask{IndexMask(domain_size)}; + + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(field.cpp_type()); + OutputAttribute output_attribute = component.attribute_try_get_for_output_only( + attribute_id, domain, data_type); + + fn::FieldEvaluator evaluator{field_context, &mask}; + evaluator.add_with_destination(field, output_attribute.varray()); + evaluator.evaluate(); + + output_attribute.save(); +} + +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; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); + + GField field; + switch (data_type) { + case CD_PROP_FLOAT: + field = params.get_input<Field<float>>("Value_001"); + break; + case CD_PROP_FLOAT3: + field = params.get_input<Field<float3>>("Value"); + break; + case CD_PROP_COLOR: + field = params.get_input<Field<ColorGeometry4f>>("Value_002"); + break; + case CD_PROP_BOOL: + field = params.get_input<Field<bool>>("Value_003"); + break; + case CD_PROP_INT32: + field = params.get_input<Field<int>>("Value_004"); + break; + default: + break; + } + + WeakAnonymousAttributeID anonymous_id{"Attribute Capture"}; + const CPPType &type = field.cpp_type(); + + static const Array<GeometryComponentType> types = { + GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; + for (const GeometryComponentType type : types) { + if (geometry_set.has(type)) { + GeometryComponent &component = geometry_set.get_component_for_write(type); + try_capture_field_on_geometry(component, anonymous_id.get(), domain, field); + } + } + + GField output_field{ + std::make_shared<bke::AnonymousAttributeFieldInput>(std::move(anonymous_id), type)}; + + switch (data_type) { + case CD_PROP_FLOAT: { + params.set_output("Attribute_001", Field<float>(output_field)); + break; + } + case CD_PROP_FLOAT3: { + params.set_output("Attribute", Field<float3>(output_field)); + break; + } + case CD_PROP_COLOR: { + params.set_output("Attribute_002", Field<ColorGeometry4f>(output_field)); + break; + } + case CD_PROP_BOOL: { + params.set_output("Attribute_003", Field<bool>(output_field)); + break; + } + case CD_PROP_INT32: { + params.set_output("Attribute_004", Field<int>(output_field)); + break; + } + default: + break; + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_capture() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_CAPTURE, "Attribute Capture", NODE_CLASS_ATTRIBUTE, 0); + node_type_storage(&ntype, + "NodeGeometryAttributeCapture", + node_free_standard_storage, + node_copy_standard_storage); + node_type_init(&ntype, blender::nodes::geo_node_attribute_capture_init); + node_type_update(&ntype, blender::nodes::geo_node_attribute_capture_update); + ntype.declare = blender::nodes::geo_node_attribute_capture_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_capture_exec; + ntype.draw_buttons = blender::nodes::geo_node_attribute_capture_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc index 3211cdbc5a7..97070184609 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc @@ -268,7 +268,8 @@ void register_node_type_geo_attribute_clamp() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LECAGY_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/node_geo_attribute_color_ramp.cc index aae906f2e5e..aa054af3acd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -125,8 +125,11 @@ void register_node_type_geo_attribute_color_ramp() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_COLOR_RAMP, "Attribute Color Ramp", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, + "Attribute Color Ramp", + NODE_CLASS_ATTRIBUTE, + 0); node_type_storage( &ntype, "NodeAttributeColorRamp", node_free_standard_storage, node_copy_standard_storage); node_type_init(&ntype, blender::nodes::geo_node_attribute_color_ramp_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc index 1638793525a..569d5a824ca 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc @@ -136,8 +136,11 @@ void register_node_type_geo_attribute_combine_xyz() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, "Attribute Combine XYZ", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ, + "Attribute Combine XYZ", + NODE_CLASS_ATTRIBUTE, + 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_combine_xyz_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_combine_xyz_update); node_type_storage( diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc index 75c88a6953a..0b9708dae14 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc @@ -348,7 +348,7 @@ void register_node_type_geo_attribute_compare() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0); ntype.declare = blender::nodes::geo_node_attribute_compare_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_compare_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_compare_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc index 72e1930952a..a2382aa9d25 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc @@ -182,7 +182,7 @@ void register_node_type_geo_attribute_convert() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0); ntype.declare = blender::nodes::geo_node_attribute_convert_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_convert_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_convert_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc index cb86dfa90a8..b9621b4ae92 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc @@ -211,7 +211,7 @@ void register_node_type_geo_attribute_curve_map() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); node_type_update(&ntype, blender::nodes::geo_node_attribute_curve_map_update); node_type_init(&ntype, blender::nodes::geo_node_attribute_curve_map_init); node_type_size_preset(&ntype, NODE_SIZE_LARGE); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc index 632e4f86572..3c50ae5c837 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc @@ -152,7 +152,8 @@ void register_node_type_geo_attribute_fill() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_fill_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_fill_update); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_fill_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc index 4cb452a97cd..0ea3bbe1e45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc @@ -420,7 +420,7 @@ void register_node_type_geo_attribute_map_range() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_map_range_exec; node_type_init(&ntype, blender::nodes::geo_node_attribute_map_range_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_map_range_update); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc index de1d5ca815d..efa09215b45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -301,7 +301,8 @@ void register_node_type_geo_attribute_math() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE, 0); ntype.declare = blender::nodes::geo_node_attribute_math_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_math_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_math_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index 582b1a88221..74e05cb997d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -31,7 +31,11 @@ static void geo_node_mix_attribute_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); b.add_input<decl::String>("Factor"); - b.add_input<decl::Float>("Factor").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Factor", "Factor_001") + .default_value(0.5f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR); b.add_input<decl::String>("A"); b.add_input<decl::Float>("A", "A_001"); b.add_input<decl::Vector>("A", "A_002"); @@ -239,7 +243,8 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params) void register_node_type_geo_attribute_mix() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_mix_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_mix_update); ntype.declare = blender::nodes::geo_node_mix_attribute_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc index 9ca19f70be6..0cf411343cf 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc @@ -237,7 +237,7 @@ void register_node_type_geo_attribute_proximity() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_attribute_proximity_init); node_type_storage(&ntype, "NodeGeometryAttributeProximity", diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index 64035c4fc16..60b9910399c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -331,7 +331,7 @@ void register_node_type_geo_attribute_randomize() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); + &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); 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 e4f3230ebb9..21a9a338857 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -16,28 +16,15 @@ #include "node_geometry_util.hh" -static bNodeSocketTemplate geo_node_attribute_remove_in[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_STRING, - N_("Attribute"), - 0.0f, - 0.0f, - 0.0f, - 1.0f, - -1.0f, - 1.0f, - PROP_NONE, - SOCK_MULTI_INPUT}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_attribute_remove_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; - namespace blender::nodes { +static void geo_node_attribute_remove_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::String>("Attribute").multi_input(); + b.add_output<decl::Geometry>("Geometry"); +} + static void remove_attribute(GeometryComponent &component, GeoNodeExecParams ¶ms, Span<std::string> attribute_names) @@ -85,7 +72,7 @@ void register_node_type_geo_attribute_remove() geo_node_type_base( &ntype, GEO_NODE_ATTRIBUTE_REMOVE, "Attribute Remove", NODE_CLASS_ATTRIBUTE, 0); - node_type_socket_templates(&ntype, geo_node_attribute_remove_in, geo_node_attribute_remove_out); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_remove_exec; + ntype.declare = blender::nodes::geo_node_attribute_remove_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index c78a3d956e3..52f97475941 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -33,7 +33,7 @@ namespace blender::nodes { static void geo_node_attribute_sample_texture_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Texture>("Texture").hide_label(true); + b.add_input<decl::Texture>("Texture").hide_label(); b.add_input<decl::String>("Mapping"); b.add_input<decl::String>("Result"); b.add_output<decl::Geometry>("Geometry"); @@ -126,7 +126,7 @@ void register_node_type_geo_sample_texture() static bNodeType ntype; geo_node_type_base(&ntype, - GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, + GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, "Attribute Sample Texture", NODE_CLASS_ATTRIBUTE, 0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc index b1ac608faad..de0090406c6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc @@ -157,8 +157,11 @@ void register_node_type_geo_attribute_separate_xyz() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, "Attribute Separate XYZ", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ, + "Attribute Separate XYZ", + NODE_CLASS_ATTRIBUTE, + 0); ntype.declare = blender::nodes::geo_node_attribute_separate_xyz_declare; node_type_init(&ntype, blender::nodes::geo_node_attribute_separate_xyz_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_separate_xyz_update); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc index 6cef09aca67..874350cd714 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc @@ -516,7 +516,7 @@ void register_node_type_geo_attribute_transfer() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init); node_type_storage(&ntype, "NodeGeometryAttributeTransfer", diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc index 47f2cf39d51..59903050f88 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc @@ -555,8 +555,11 @@ void register_node_type_geo_attribute_vector_math() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_VECTOR_MATH, "Attribute Vector Math", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH, + "Attribute Vector Math", + NODE_CLASS_ATTRIBUTE, + 0); ntype.declare = blender::nodes::geo_node_attribute_vector_math_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_math_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_vector_math_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc index da753dfc11b..adaa4de3029 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -21,27 +21,26 @@ #include "UI_interface.h" #include "UI_resources.h" -static bNodeSocketTemplate geo_node_attribute_vector_rotate_in[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_STRING, N_("Vector")}, - {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, - {SOCK_STRING, N_("Center")}, - {SOCK_VECTOR, N_("Center"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, - {SOCK_STRING, N_("Axis")}, - {SOCK_VECTOR, N_("Axis"), 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, PROP_XYZ, PROP_NONE}, - {SOCK_STRING, N_("Angle")}, - {SOCK_FLOAT, N_("Angle"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_ANGLE, PROP_NONE}, - {SOCK_STRING, N_("Rotation")}, - {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER}, - {SOCK_BOOLEAN, N_("Invert")}, - {SOCK_STRING, N_("Result")}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_attribute_vector_rotate_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void geo_node_attribute_vector_rotate_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::String>("Vector"); + b.add_input<decl::Vector>("Vector", "Vector_001").min(0.0f).max(1.0f).hide_value(); + b.add_input<decl::String>("Center"); + b.add_input<decl::Vector>("Center", "Center_001").subtype(PROP_XYZ); + b.add_input<decl::String>("Axis"); + b.add_input<decl::Vector>("Axis", "Axis_001").min(-1.0f).max(1.0f).subtype(PROP_XYZ); + b.add_input<decl::String>("Angle"); + b.add_input<decl::Float>("Angle", "Angle_001").subtype(PROP_ANGLE); + b.add_input<decl::String>("Rotation"); + b.add_input<decl::Vector>("Rotation", "Rotation_001").subtype(PROP_EULER); + b.add_input<decl::Bool>("Invert"); + b.add_input<decl::String>("Result"); + + b.add_output<decl::Geometry>("Geometry"); +} static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, bContext *UNUSED(C), @@ -71,8 +70,6 @@ static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, } } -namespace blender::nodes { - static void geo_node_attribute_vector_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) { const NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)node->storage; @@ -339,14 +336,13 @@ void register_node_type_geo_attribute_vector_rotate() "Attribute Vector Rotate", NODE_CLASS_ATTRIBUTE, 0); - node_type_socket_templates( - &ntype, geo_node_attribute_vector_rotate_in, geo_node_attribute_vector_rotate_out); node_type_update(&ntype, blender::nodes::geo_node_attribute_vector_rotate_update); node_type_init(&ntype, blender::nodes::geo_node_attribute_vector_rotate_init); node_type_size(&ntype, 165, 100, 600); node_type_storage( &ntype, "NodeAttributeVectorRotate", node_free_standard_storage, node_copy_standard_storage); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_rotate_exec; - ntype.draw_buttons = geo_node_attribute_vector_rotate_layout; + ntype.draw_buttons = blender::nodes::geo_node_attribute_vector_rotate_layout; + ntype.declare = blender::nodes::geo_node_attribute_vector_rotate_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index d8029ea1eeb..2a1c43a89fe 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -23,27 +23,16 @@ #include "node_geometry_util.hh" -static bNodeSocketTemplate geo_node_boolean_in[] = { - {SOCK_GEOMETRY, N_("Geometry 1")}, - {SOCK_GEOMETRY, - N_("Geometry 2"), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - PROP_NONE, - SOCK_MULTI_INPUT}, - {SOCK_BOOLEAN, N_("Self Intersection")}, - {SOCK_BOOLEAN, N_("Hole Tolerant")}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_boolean_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void geo_node_boolean_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry 1"); + b.add_input<decl::Geometry>("Geometry 2").multi_input(); + b.add_input<decl::Bool>("Self Intersection"); + b.add_input<decl::Bool>("Hole Tolerant"); + b.add_output<decl::Geometry>("Geometry"); +} static void geo_node_boolean_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { @@ -77,8 +66,6 @@ static void geo_node_boolean_init(bNodeTree *UNUSED(tree), bNode *node) node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE; } -namespace blender::nodes { - static void geo_node_boolean_exec(GeoNodeExecParams params) { GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1; @@ -138,10 +125,10 @@ void register_node_type_geo_boolean() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", NODE_CLASS_GEOMETRY, 0); - node_type_socket_templates(&ntype, geo_node_boolean_in, geo_node_boolean_out); - ntype.draw_buttons = geo_node_boolean_layout; - ntype.updatefunc = geo_node_boolean_update; - node_type_init(&ntype, geo_node_boolean_init); + ntype.declare = blender::nodes::geo_node_boolean_declare; + ntype.draw_buttons = blender::nodes::geo_node_boolean_layout; + ntype.updatefunc = blender::nodes::geo_node_boolean_update; + node_type_init(&ntype, blender::nodes::geo_node_boolean_init); ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec; nodeRegisterType(&ntype); } 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 e731b4c0cdc..f4c295b06fb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -27,7 +27,7 @@ namespace blender::nodes { static void geo_node_collection_info_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Collection>("Collection").hide_label(true); + b.add_input<decl::Collection>("Collection").hide_label(); b.add_output<decl::Geometry>("Geometry"); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc index 699fcc5e983..7853c5aa04a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc @@ -56,25 +56,26 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, Span<int> offsets, PointCloudComponent &points) { - curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (meta_data.domain != ATTR_DOMAIN_CURVE) { - return true; - } - GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( - name, ATTR_DOMAIN_CURVE, meta_data.data_type); - - OutputAttribute result_attribute = points.attribute_try_get_for_output_only( - name, ATTR_DOMAIN_POINT, meta_data.data_type); - GMutableSpan result = result_attribute.as_span(); - - /* Only copy the attributes of splines in the offsets. */ - for (const int i : offsets.index_range()) { - spline_attribute->get(offsets[i], result[i]); - } - - result_attribute.save(); - return true; - }); + curve_component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (meta_data.domain != ATTR_DOMAIN_CURVE) { + return true; + } + GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( + attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type); + + OutputAttribute result_attribute = points.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type); + GMutableSpan result = result_attribute.as_span(); + + /* Only copy the attributes of splines in the offsets. */ + for (const int i : offsets.index_range()) { + spline_attribute->get(offsets[i], result[i]); + } + + result_attribute.save(); + return true; + }); } /** @@ -124,20 +125,20 @@ static void copy_endpoint_attributes(Span<SplinePtr> splines, /* Copy the point attribute data over. */ for (const auto &item : start_data.point_attributes.items()) { - const StringRef name = item.key; + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); blender::fn::GVArray_For_GSpan(spline_span).get(0, point_span[i]); } for (const auto &item : end_data.point_attributes.items()) { - const StringRef name = item.key; + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); blender::fn::GVArray_For_GSpan(spline_span).get(spline.size() - 1, point_span[i]); } } 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 169a169bb1c..0803d43e5c3 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 @@ -41,7 +41,7 @@ static std::unique_ptr<CurveEval> create_spiral_curve(const float rotations, std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); - const int totalpoints = resolution * 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; 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 0b71a8cb03a..208525f17f6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -42,7 +42,7 @@ static void geo_node_curve_resample_declare(NodeDeclarationBuilder &b) static void geo_node_curve_resample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); } static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node) @@ -50,102 +50,117 @@ static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node) NodeGeometryCurveResample *data = (NodeGeometryCurveResample *)MEM_callocN( sizeof(NodeGeometryCurveResample), __func__); - data->mode = GEO_NODE_CURVE_SAMPLE_COUNT; + data->mode = GEO_NODE_CURVE_RESAMPLE_COUNT; node->storage = data; } static void geo_node_curve_resample_update(bNodeTree *UNUSED(ntree), bNode *node) { NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)node->storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *length_socket = count_socket->next; - nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_COUNT); - nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); + nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_RESAMPLE_COUNT); + nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); } struct SampleModeParam { - GeometryNodeCurveSampleMode mode; + GeometryNodeCurveResampleMode mode; std::optional<float> length; std::optional<int> count; }; -static SplinePtr resample_spline(const Spline &input_spline, const int count) +static SplinePtr resample_spline(const Spline &src, const int count) { - std::unique_ptr<PolySpline> output_spline = std::make_unique<PolySpline>(); - output_spline->set_cyclic(input_spline.is_cyclic()); - output_spline->normal_mode = input_spline.normal_mode; - - if (input_spline.evaluated_edges_size() < 1 || count == 1) { - output_spline->add_point(input_spline.positions().first(), - input_spline.tilts().first(), - input_spline.radii().first()); - output_spline->attributes.reallocate(1); - input_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = input_spline.attributes.get_for_read(name); - BLI_assert(src); - if (!output_spline->attributes.create(name, meta_data.data_type)) { - BLI_assert_unreachable(); - return false; + std::unique_ptr<PolySpline> dst = std::make_unique<PolySpline>(); + Spline::copy_base_settings(src, *dst); + + if (src.evaluated_edges_size() < 1 || count == 1) { + dst->add_point(src.positions().first(), src.tilts().first(), src.radii().first()); + dst->attributes.reallocate(1); + src.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id); + if (dst->attributes.create(attribute_id, meta_data.data_type)) { + std::optional<GMutableSpan> dst_attribute = dst->attributes.get_for_write( + attribute_id); + if (dst_attribute) { + src_attribute->type().copy_assign(src_attribute->data(), dst_attribute->data()); + return true; + } } - std::optional<GMutableSpan> dst = output_spline->attributes.get_for_write(name); - if (!dst) { - BLI_assert_unreachable(); - return false; - } - src->type().copy_assign(src->data(), dst->data()); - return true; + BLI_assert_unreachable(); + return false; }, ATTR_DOMAIN_POINT); - return output_spline; + return dst; } - output_spline->resize(count); + dst->resize(count); - Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count); + Array<float> uniform_samples = src.sample_uniform_index_factors(count); - input_spline.sample_with_index_factors<float3>( - input_spline.evaluated_positions(), uniform_samples, output_spline->positions()); + src.sample_with_index_factors<float3>( + src.evaluated_positions(), uniform_samples, dst->positions()); - input_spline.sample_with_index_factors<float>( - input_spline.interpolate_to_evaluated(input_spline.radii()), - uniform_samples, - output_spline->radii()); + src.sample_with_index_factors<float>( + src.interpolate_to_evaluated(src.radii()), uniform_samples, dst->radii()); - input_spline.sample_with_index_factors<float>( - input_spline.interpolate_to_evaluated(input_spline.tilts()), - uniform_samples, - output_spline->tilts()); + src.sample_with_index_factors<float>( + src.interpolate_to_evaluated(src.tilts()), uniform_samples, dst->tilts()); - output_spline->attributes.reallocate(count); - input_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name); - BLI_assert(input_attribute); - if (!output_spline->attributes.create(name, meta_data.data_type)) { - BLI_assert_unreachable(); - return false; - } - std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write( - name); - if (!output_attribute) { - BLI_assert_unreachable(); - return false; + src.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> input_attribute = src.attributes.get_for_read(attribute_id); + if (dst->attributes.create(attribute_id, meta_data.data_type)) { + std::optional<GMutableSpan> output_attribute = dst->attributes.get_for_write( + attribute_id); + if (output_attribute) { + src.sample_with_index_factors(*src.interpolate_to_evaluated(*input_attribute), + uniform_samples, + *output_attribute); + return true; + } } - input_spline.sample_with_index_factors( - *input_spline.interpolate_to_evaluated(*input_attribute), - uniform_samples, - *output_attribute); + BLI_assert_unreachable(); + return false; + }, + ATTR_DOMAIN_POINT); + + return dst; +} + +static SplinePtr resample_spline_evaluated(const Spline &src) +{ + std::unique_ptr<PolySpline> dst = std::make_unique<PolySpline>(); + Spline::copy_base_settings(src, *dst); + dst->resize(src.evaluated_points_size()); + + dst->positions().copy_from(src.evaluated_positions()); + dst->positions().copy_from(src.evaluated_positions()); + src.interpolate_to_evaluated(src.radii())->materialize(dst->radii()); + src.interpolate_to_evaluated(src.tilts())->materialize(dst->tilts()); + + src.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id); + if (dst->attributes.create(attribute_id, meta_data.data_type)) { + std::optional<GMutableSpan> dst_attribute = dst->attributes.get_for_write(attribute_id); + if (dst_attribute) { + src.interpolate_to_evaluated(*src_attribute)->materialize(dst_attribute->data()); + return true; + } + } + BLI_assert_unreachable(); return true; }, ATTR_DOMAIN_POINT); - return output_spline; + return dst; } static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, @@ -157,7 +172,7 @@ static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, output_curve->resize(input_splines.size()); MutableSpan<SplinePtr> output_splines = output_curve->splines(); - if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { BLI_assert(mode_param.count); @@ -165,15 +180,22 @@ static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, } }); } - else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { 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); + const int count = std::max(int(length / *mode_param.length) + 1, 1); output_splines[i] = resample_spline(*input_splines[i], count); } }); } + else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_EVALUATED) { + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + output_splines[i] = resample_spline_evaluated(*input_splines[i]); + } + }); + } output_curve->attributes = input_curve.attributes; @@ -193,10 +215,10 @@ static void geo_node_resample_exec(GeoNodeExecParams params) const CurveEval &input_curve = *geometry_set.get_curve_for_read(); NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)params.node().storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; SampleModeParam mode_param; mode_param.mode = mode; - if (mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + if (mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { const int count = params.extract_input<int>("Count"); if (count < 1) { params.set_output("Geometry", GeometrySet()); @@ -204,7 +226,7 @@ static void geo_node_resample_exec(GeoNodeExecParams params) } mode_param.count.emplace(count); } - else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + 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); mode_param.length.emplace(resolution); 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 b826e1c6510..32bcbe2c608 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -29,31 +29,6 @@ static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Curve"); } -/** - * Reverse the data in a MutableSpan object. - */ -template<typename T> static void reverse_data(MutableSpan<T> r_data) -{ - const int size = r_data.size(); - for (const int i : IndexRange(size / 2)) { - std::swap(r_data[size - 1 - i], r_data[i]); - } -} - -/** - * Reverse and Swap the data between 2 MutableSpans. - */ -template<typename T> static void reverse_data(MutableSpan<T> left, MutableSpan<T> right) -{ - BLI_assert(left.size() == right.size()); - const int size = left.size(); - - for (const int i : IndexRange(size / 2 + size % 2)) { - std::swap(left[i], right[size - 1 - i]); - std::swap(right[i], left[size - 1 - i]); - } -} - static void geo_node_curve_reverse_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); @@ -74,42 +49,9 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { - if (!selection[i]) { - continue; - } - - reverse_data<float3>(splines[i]->positions()); - reverse_data<float>(splines[i]->radii()); - reverse_data<float>(splines[i]->tilts()); - - splines[i]->attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<blender::fn::GMutableSpan> output_attribute = - splines[i]->attributes.get_for_write(name); - if (!output_attribute) { - BLI_assert_unreachable(); - return false; - } - attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - reverse_data(output_attribute->typed<T>()); - }); - return true; - }, - ATTR_DOMAIN_POINT); - - /* Deal with extra info on derived types. */ - if (BezierSpline *spline = dynamic_cast<BezierSpline *>(splines[i].get())) { - reverse_data<BezierSpline::HandleType>(spline->handle_types_left()); - reverse_data<BezierSpline::HandleType>(spline->handle_types_right()); - reverse_data<float3>(spline->handle_positions_left(), spline->handle_positions_right()); + if (selection[i]) { + splines[i]->reverse(); } - else if (NURBSpline *spline = dynamic_cast<NURBSpline *>(splines[i].get())) { - reverse_data<float>(spline->weights()); - } - /* Nothing to do for poly splines. */ - - splines[i]->mark_cache_invalid(); } }); @@ -121,7 +63,8 @@ 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_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); + 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/node_geo_curve_select_by_handle_type.cc index bf33cf0294e..dfcae2e65b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc @@ -127,8 +127,11 @@ void register_node_type_geo_select_by_handle_type() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_CURVE_SELECT_HANDLES, "Select by Handle Type", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, + "Select by Handle Type", + NODE_CLASS_GEOMETRY, + 0); ntype.declare = blender::nodes::geo_node_select_by_handle_type_declare; ntype.geometry_node_execute = blender::nodes::geo_node_select_by_handle_type_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_select_by_handle_type_init); 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 0b5be7addaf..31c13134f79 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 @@ -130,7 +130,7 @@ void register_node_type_geo_curve_set_handles() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); + &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); 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 6a093f442cb..0ef107fd8a4 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 @@ -74,14 +74,14 @@ template<typename CopyFn> static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn) { input_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = input_spline.attributes.get_for_read(name); + [&](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(name, meta_data.data_type)) { + 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(name); + std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id); if (!dst) { BLI_assert_unreachable(); return false; @@ -288,7 +288,7 @@ void register_node_type_geo_curve_spline_type() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_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 958d83e2967..0522f2b8981 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -283,16 +283,16 @@ static void subdivide_dynamic_attributes(const Spline &src_spline, { const bool is_cyclic = src_spline.is_cyclic(); src_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = src_spline.attributes.get_for_read(name); + [&](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(name, meta_data.data_type)) { + 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(name); + 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) { @@ -377,7 +377,8 @@ void register_node_type_geo_curve_subdivide() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); + 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, 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 b0c763c7d06..b8bdb3d71d6 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 @@ -39,6 +39,20 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Mesh"); } +/** Information about the creation of one curve spline and profile spline combination. */ +struct ResultInfo { + const Spline &spline; + const Spline &profile; + int vert_offset; + int edge_offset; + int loop_offset; + int poly_offset; + int spline_vert_len; + int spline_edge_len; + int profile_vert_len; + int profile_edge_len; +}; + static void vert_extrude_to_mesh_data(const Spline &spline, const float3 profile_vert, MutableSpan<MVert> r_verts, @@ -75,44 +89,33 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges) } } -static void spline_extrude_to_mesh_data(const Spline &spline, - const Spline &profile_spline, - const int vert_offset, - const int edge_offset, - const int loop_offset, - const int poly_offset, +static void spline_extrude_to_mesh_data(const ResultInfo &info, MutableSpan<MVert> r_verts, MutableSpan<MEdge> r_edges, MutableSpan<MLoop> r_loops, MutableSpan<MPoly> r_polys) { - const int spline_vert_len = spline.evaluated_points_size(); - const int spline_edge_len = spline.evaluated_edges_size(); - const int profile_vert_len = profile_spline.evaluated_points_size(); - const int profile_edge_len = profile_spline.evaluated_edges_size(); - if (spline_vert_len == 0) { - return; - } - - if (profile_vert_len == 1) { + const Spline &spline = info.spline; + const Spline &profile = info.profile; + if (info.profile_vert_len == 1) { vert_extrude_to_mesh_data(spline, - profile_spline.evaluated_positions()[0], + profile.evaluated_positions()[0], r_verts, r_edges, - vert_offset, - edge_offset); + info.vert_offset, + info.edge_offset); return; } /* Add the edges running along the length of the curve, starting at each profile vertex. */ - const int spline_edges_start = edge_offset; - for (const int i_profile : IndexRange(profile_vert_len)) { - const int profile_edge_offset = spline_edges_start + i_profile * spline_edge_len; - for (const int i_ring : IndexRange(spline_edge_len)) { - const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + const int spline_edges_start = info.edge_offset; + for (const int i_profile : IndexRange(info.profile_vert_len)) { + const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; - const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; + const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; MEdge &edge = r_edges[profile_edge_offset + i_ring]; edge.v1 = ring_vert_offset + i_profile; @@ -122,13 +125,14 @@ static void spline_extrude_to_mesh_data(const Spline &spline, } /* Add the edges running along each profile ring. */ - const int profile_edges_start = spline_edges_start + profile_vert_len * spline_edge_len; - for (const int i_ring : IndexRange(spline_vert_len)) { - const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int profile_edges_start = spline_edges_start + + info.profile_vert_len * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - const int ring_edge_offset = profile_edges_start + i_ring * profile_edge_len; - for (const int i_profile : IndexRange(profile_edge_len)) { - const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len; + for (const int i_profile : IndexRange(info.profile_edge_len)) { + const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; MEdge &edge = r_edges[ring_edge_offset + i_profile]; edge.v1 = ring_vert_offset + i_profile; @@ -138,24 +142,25 @@ static void spline_extrude_to_mesh_data(const Spline &spline, } /* Calculate poly and corner indices. */ - for (const int i_ring : IndexRange(spline_edge_len)) { - const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; - const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; + const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; - const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring; - const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring; + const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring; + const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring; - const int ring_poly_offset = poly_offset + i_ring * profile_edge_len; - const int ring_loop_offset = loop_offset + i_ring * profile_edge_len * 4; + const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len; + const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4; - for (const int i_profile : IndexRange(profile_edge_len)) { + for (const int i_profile : IndexRange(info.profile_edge_len)) { const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4; - const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; - const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile; - const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile; + const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile; + const int next_spline_edge_start = spline_edges_start + + info.spline_edge_len * i_next_profile; MPoly &poly = r_polys[ring_poly_offset + i_profile]; poly.loopstart = ring_segment_loop_offset; @@ -181,29 +186,30 @@ static void spline_extrude_to_mesh_data(const Spline &spline, Span<float3> positions = spline.evaluated_positions(); Span<float3> tangents = spline.evaluated_tangents(); Span<float3> normals = spline.evaluated_normals(); - Span<float3> profile_positions = profile_spline.evaluated_positions(); + Span<float3> profile_positions = profile.evaluated_positions(); GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii()); - for (const int i_ring : IndexRange(spline_vert_len)) { + for (const int i_ring : IndexRange(info.spline_vert_len)) { float4x4 point_matrix = float4x4::from_normalized_axis_data( positions[i_ring], normals[i_ring], tangents[i_ring]); point_matrix.apply_scale(radii[i_ring]); - const int ring_vert_start = vert_offset + i_ring * profile_vert_len; - for (const int i_profile : IndexRange(profile_vert_len)) { + const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + for (const int i_profile : IndexRange(info.profile_vert_len)) { MVert &vert = r_verts[ring_vert_start + i_profile]; copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); } } /* Mark edge loops from sharp vector control points sharp. */ - if (profile_spline.type() == Spline::Type::Bezier) { - const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline); + if (profile.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile); Span<int> control_point_offsets = bezier_spline.control_point_offsets(); for (const int i : IndexRange(bezier_spline.size())) { if (bezier_spline.point_is_sharp(i)) { - mark_edges_sharp(r_edges.slice( - spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len)); + mark_edges_sharp( + r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i], + info.spline_edge_len)); } } } @@ -272,6 +278,372 @@ static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<Spl return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; } +static AttributeDomain get_result_attribute_domain(const MeshComponent &component, + const AttributeIDRef &attribute_id) +{ + /* Only use a different domain if it is builtin and must only exist on one domain. */ + if (!component.attribute_is_builtin(attribute_id)) { + return ATTR_DOMAIN_POINT; + } + + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id); + if (!meta_data) { + /* This function has to return something in this case, but it shouldn't be used, + * so return an output that will assert later if the code attempts to handle it. */ + return ATTR_DOMAIN_AUTO; + } + + return meta_data->domain; +} + +/** + * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling + * `as_span()` for every single profile and curve spline combination, and for readability. + */ +struct ResultAttributeData { + GMutableSpan data; + AttributeDomain domain; +}; + +static std::optional<ResultAttributeData> create_attribute_and_get_span( + MeshComponent &component, + const AttributeIDRef &attribute_id, + AttributeMetaData meta_data, + Vector<OutputAttribute> &r_attributes) +{ + const AttributeDomain domain = get_result_attribute_domain(component, attribute_id); + OutputAttribute attribute = component.attribute_try_get_for_output_only( + attribute_id, domain, meta_data.data_type); + if (!attribute) { + return std::nullopt; + } + + GMutableSpan span = attribute.as_span(); + r_attributes.append(std::move(attribute)); + return std::make_optional<ResultAttributeData>({span, domain}); +} + +/** + * Store the references to the attribute data from the curve and profile inputs. Here we rely on + * the invariants of the storage of curve attributes, that the order will be consistent between + * splines, and all splines will have the same attributes. + */ +struct ResultAttributes { + /** + * Result attributes on the mesh corresponding to each attribute on the curve input, in the same + * order. The data is optional only in case the attribute does not exist on the mesh for some + * reason, like "shade_smooth" when the result has no faces. + */ + Vector<std::optional<ResultAttributeData>> curve_point_attributes; + Vector<std::optional<ResultAttributeData>> curve_spline_attributes; + + /** + * Result attributes corresponding the attributes on the profile input, in the same order. The + * attributes are optional in case the attribute names correspond to a names used by the curve + * input, in which case the curve input attributes take precedence. + */ + Vector<std::optional<ResultAttributeData>> profile_point_attributes; + Vector<std::optional<ResultAttributeData>> profile_spline_attributes; + + /** + * Because some builtin attributes are not stored contiguously, and the curve inputs might have + * attributes with those names, it's necessary to keep OutputAttributes around to give access to + * the result data in a contiguous array. + */ + Vector<OutputAttribute> attributes; +}; +static ResultAttributes create_result_attributes(const CurveEval &curve, + const CurveEval &profile, + Mesh &mesh) +{ + MeshComponent mesh_component; + mesh_component.replace(&mesh, GeometryOwnershipType::Editable); + Set<AttributeIDRef> curve_attributes; + + /* In order to prefer attributes on the main curve input when there are name collisions, first + * check the attributes on the curve, then add attributes on the profile that are not also on the + * main curve input. */ + ResultAttributes result; + curve.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + curve_attributes.add_new(id); + result.curve_point_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + return true; + }, + ATTR_DOMAIN_POINT); + curve.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + curve_attributes.add_new(id); + result.curve_spline_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + return true; + }, + ATTR_DOMAIN_CURVE); + profile.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (curve_attributes.contains(id)) { + result.profile_point_attributes.append({}); + } + else { + result.profile_point_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + } + return true; + }, + ATTR_DOMAIN_POINT); + profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (curve_attributes.contains(id)) { + result.profile_spline_attributes.append({}); + } + else { + result.profile_spline_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + } + return true; + }, + ATTR_DOMAIN_CURVE); + + return result; +} + +template<typename T> +static void copy_curve_point_data_to_mesh_verts(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]); + } +} + +template<typename T> +static void copy_curve_point_data_to_mesh_edges(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_edge_start = edges_start + info.profile_edge_len * i_ring; + dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]); + } +} + +template<typename T> +static void copy_curve_point_data_to_mesh_faces(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring; + dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]); + } +} + +static void copy_curve_point_attribute_to_mesh(const GSpan src, + const ResultInfo &info, + ResultAttributeData &dst) +{ + GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src); + GSpan interpolated = interpolated_gvarray->get_internal_span(); + + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst.domain) { + case ATTR_DOMAIN_POINT: + copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + /* Unsupported for now, since there are no builtin attributes to convert into. */ + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +template<typename T> +static void copy_profile_point_data_to_mesh_verts(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + for (const int i_profile : IndexRange(info.profile_vert_len)) { + dst[profile_vert_start + i_profile] = src[i_profile]; + } + } +} + +template<typename T> +static void copy_profile_point_data_to_mesh_edges(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_profile : IndexRange(info.profile_vert_len)) { + const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len; + dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]); + } +} + +template<typename T> +static void copy_profile_point_data_to_mesh_faces(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len; + for (const int i_profile : IndexRange(info.profile_edge_len)) { + dst[profile_face_start + i_profile] = src[i_profile]; + } + } +} + +static void copy_profile_point_attribute_to_mesh(const GSpan src, + const ResultInfo &info, + ResultAttributeData &dst) +{ + GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src); + GSpan interpolated = interpolated_gvarray->get_internal_span(); + + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst.domain) { + case ATTR_DOMAIN_POINT: + copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + /* Unsupported for now, since there are no builtin attributes to convert into. */ + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +static void copy_point_domain_attributes_to_mesh(const ResultInfo &info, + ResultAttributes &attributes) +{ + if (!attributes.curve_point_attributes.is_empty()) { + int i = 0; + info.spline.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.curve_point_attributes[i]) { + copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id), + info, + *attributes.curve_point_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_POINT); + } + if (!attributes.profile_point_attributes.is_empty()) { + int i = 0; + info.profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.profile_point_attributes[i]) { + copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id), + info, + *attributes.profile_point_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_POINT); + } +} + +template<typename T> +static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst) +{ + for (const int i : IndexRange(src.size())) { + dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]); + } +} + +/** + * Since the offsets for each combination of curve and profile spline are stored for every mesh + * domain, and this just needs to fill the chunks corresponding to each combination, we can use + * the same function for all mesh domains. + */ +static void copy_spline_attribute_to_mesh(const GSpan src, + const ResultOffsets &offsets, + ResultAttributeData &dst_attribute) +{ + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst_attribute.domain) { + case ATTR_DOMAIN_POINT: + copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>()); + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve, + const CurveEval &profile, + const ResultOffsets &offsets, + ResultAttributes &attributes) +{ + if (!attributes.curve_spline_attributes.is_empty()) { + int i = 0; + curve.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.curve_spline_attributes[i]) { + copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id), + offsets, + *attributes.curve_spline_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_CURVE); + } + if (!attributes.profile_spline_attributes.is_empty()) { + int i = 0; + profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.profile_spline_attributes[i]) { + copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id), + offsets, + *attributes.profile_spline_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_CURVE); + } +} + /** * \note Normal calculation is by far the slowest part of calculations relating to the result mesh. * Although it would be a sensible decision to use the better topology information available while @@ -294,30 +666,52 @@ static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &pr BKE_id_material_eval_ensure_default_slot(&mesh->id); mesh->flag |= ME_AUTOSMOOTH; mesh->smoothresh = DEG2RADF(180.0f); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); + + ResultAttributes attributes = create_result_attributes(curve, profile, *mesh); threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { for (const int i_spline : curves_range) { + const Spline &spline = *curves[i_spline]; + if (spline.evaluated_points_size() == 0) { + continue; + } const int spline_start_index = i_spline * profiles.size(); threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) { for (const int i_profile : profiles_range) { + const Spline &profile = *profiles[i_profile]; const int i_mesh = spline_start_index + i_profile; - spline_extrude_to_mesh_data(*curves[i_spline], - *profiles[i_profile], - offsets.vert[i_mesh], - offsets.edge[i_mesh], - offsets.loop[i_mesh], - offsets.poly[i_mesh], + ResultInfo info{ + spline, + profile, + offsets.vert[i_mesh], + offsets.edge[i_mesh], + offsets.loop[i_mesh], + offsets.poly[i_mesh], + spline.evaluated_points_size(), + spline.evaluated_edges_size(), + profile.evaluated_points_size(), + profile.evaluated_edges_size(), + }; + + spline_extrude_to_mesh_data(info, {mesh->mvert, mesh->totvert}, {mesh->medge, mesh->totedge}, {mesh->mloop, mesh->totloop}, {mesh->mpoly, mesh->totpoly}); + + copy_point_domain_attributes_to_mesh(info, attributes); } }); } }); + copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes); + + for (OutputAttribute &output_attribute : attributes.attributes) { + output_attribute.save(); + } + return mesh; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc index 052eb92d269..1e66b340f5c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -46,20 +46,20 @@ static void geo_node_curve_to_points_init(bNodeTree *UNUSED(tree), bNode *node) NodeGeometryCurveToPoints *data = (NodeGeometryCurveToPoints *)MEM_callocN( sizeof(NodeGeometryCurveToPoints), __func__); - data->mode = GEO_NODE_CURVE_SAMPLE_COUNT; + data->mode = GEO_NODE_CURVE_RESAMPLE_COUNT; node->storage = data; } static void geo_node_curve_to_points_update(bNodeTree *UNUSED(ntree), bNode *node) { NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)node->storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *length_socket = count_socket->next; - nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_COUNT); - nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); + nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_RESAMPLE_COUNT); + nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); } /** @@ -77,13 +77,13 @@ static void evaluate_splines(Span<SplinePtr> splines) } static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, - const GeometryNodeCurveSampleMode mode, + const GeometryNodeCurveResampleMode mode, const CurveEval &curve, const Span<SplinePtr> splines) { const int size = curve.splines().size(); switch (mode) { - case GEO_NODE_CURVE_SAMPLE_COUNT: { + case GEO_NODE_CURVE_RESAMPLE_COUNT: { const int count = params.extract_input<int>("Count"); if (count < 1) { return {0}; @@ -94,19 +94,19 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, } return offsets; } - case GEO_NODE_CURVE_SAMPLE_LENGTH: { + case 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); Array<int> offsets(size + 1); int offset = 0; for (const int i : IndexRange(size)) { offsets[i] = offset; - offset += splines[i]->length() / resolution; + offset += splines[i]->length() / resolution + 1; } offsets.last() = offset; return offsets; } - case GEO_NODE_CURVE_SAMPLE_EVALUATED: { + case GEO_NODE_CURVE_RESAMPLE_EVALUATED: { return curve.evaluated_point_offsets(); } } @@ -115,21 +115,21 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, } static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points, - const StringRef name, + const AttributeIDRef &attribute_id, const CustomDataType data_type) { - points.attribute_try_create(name, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); - WriteAttributeLookup attribute = points.attribute_try_get_for_write(name); + points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); + WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id); BLI_assert(attribute); return attribute.varray->get_internal_span(); } template<typename T> static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points, - const StringRef name) + const AttributeIDRef &attribute_id) { GMutableSpan attribute = create_attribute_and_retrieve_span( - points, name, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); + points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); return attribute.typed<T>(); } @@ -147,9 +147,10 @@ CurveToPointsResults curve_to_points_create_result_attributes(PointCloudComponen /* Because of the invariants of the curve component, we use the attributes of the * first spline as a representative for the attribute meta data all splines. */ curve.splines().first()->attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { attributes.point_attributes.add_new( - name, create_attribute_and_retrieve_span(points, name, meta_data.data_type)); + attribute_id, + create_attribute_and_retrieve_span(points, attribute_id, meta_data.data_type)); return true; }, ATTR_DOMAIN_POINT); @@ -179,12 +180,12 @@ static void copy_evaluated_point_attributes(Span<SplinePtr> splines, spline.interpolate_to_evaluated(spline.radii())->materialize(data.radii.slice(offset, size)); spline.interpolate_to_evaluated(spline.tilts())->materialize(data.tilts.slice(offset, size)); - for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) { + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); spline.interpolate_to_evaluated(spline_span) ->materialize(point_span.slice(offset, size).data()); @@ -222,12 +223,12 @@ static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines, uniform_samples, data.tilts.slice(offset, size)); - for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) { + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); spline.sample_with_index_factors(*spline.interpolate_to_evaluated(spline_span), uniform_samples, @@ -257,31 +258,32 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, Span<int> offsets, PointCloudComponent &points) { - curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (meta_data.domain != ATTR_DOMAIN_CURVE) { - return true; - } - GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( - name, ATTR_DOMAIN_CURVE, meta_data.data_type); - const CPPType &type = spline_attribute->type(); - - OutputAttribute result_attribute = points.attribute_try_get_for_output_only( - name, ATTR_DOMAIN_POINT, meta_data.data_type); - GMutableSpan result = result_attribute.as_span(); - - for (const int i : IndexRange(spline_attribute->size())) { - const int offset = offsets[i]; - const int size = offsets[i + 1] - offsets[i]; - if (size != 0) { - BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); - spline_attribute->get(i, buffer); - type.fill_assign_n(buffer, result[offset], size); - } - } - - result_attribute.save(); - return true; - }); + curve_component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (meta_data.domain != ATTR_DOMAIN_CURVE) { + return true; + } + GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( + attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type); + const CPPType &type = spline_attribute->type(); + + OutputAttribute result_attribute = points.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type); + GMutableSpan result = result_attribute.as_span(); + + for (const int i : IndexRange(spline_attribute->size())) { + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + if (size != 0) { + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + spline_attribute->get(i, buffer); + type.fill_assign_n(buffer, result[offset], size); + } + } + + result_attribute.save(); + return true; + }); } void curve_create_default_rotation_attribute(Span<float3> tangents, @@ -299,7 +301,7 @@ void curve_create_default_rotation_attribute(Span<float3> tangents, static void geo_node_curve_to_points_exec(GeoNodeExecParams params) { NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)params.node().storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); geometry_set = bke::geometry_set_realize_instances(geometry_set); @@ -329,11 +331,11 @@ static void geo_node_curve_to_points_exec(GeoNodeExecParams params) CurveToPointsResults new_attributes = curve_to_points_create_result_attributes(point_component, curve); switch (mode) { - case GEO_NODE_CURVE_SAMPLE_COUNT: - case GEO_NODE_CURVE_SAMPLE_LENGTH: + case GEO_NODE_CURVE_RESAMPLE_COUNT: + case GEO_NODE_CURVE_RESAMPLE_LENGTH: copy_uniform_sample_point_attributes(splines, offsets, new_attributes); break; - case GEO_NODE_CURVE_SAMPLE_EVALUATED: + case GEO_NODE_CURVE_RESAMPLE_EVALUATED: copy_evaluated_point_attributes(splines, offsets, new_attributes); break; } 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 578f0298a19..2b6d25b6bf3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -30,9 +30,9 @@ static void geo_node_curve_trim_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); b.add_input<decl::Float>("Start").min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Float>("End").min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("End").min(0.0f).max(1.0f).default_value(1.0f).subtype(PROP_FACTOR); b.add_input<decl::Float>("Start", "Start_001").min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("End", "End_001").min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("End", "End_001").min(0.0f).default_value(1.0f).subtype(PROP_DISTANCE); b.add_output<decl::Geometry>("Curve"); } @@ -46,25 +46,24 @@ static void geo_node_curve_trim_init(bNodeTree *UNUSED(tree), bNode *node) NodeGeometryCurveTrim *data = (NodeGeometryCurveTrim *)MEM_callocN(sizeof(NodeGeometryCurveTrim), __func__); - data->mode = GEO_NODE_CURVE_INTERPOLATE_FACTOR; + data->mode = GEO_NODE_CURVE_SAMPLE_FACTOR; node->storage = data; } static void geo_node_curve_trim_update(bNodeTree *UNUSED(ntree), bNode *node) { const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)node->storage; - const GeometryNodeCurveInterpolateMode mode = (GeometryNodeCurveInterpolateMode) - node_storage.mode; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; bNodeSocket *start_fac = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *end_fac = start_fac->next; bNodeSocket *start_len = end_fac->next; bNodeSocket *end_len = start_len->next; - nodeSetSocketAvailability(start_fac, mode == GEO_NODE_CURVE_INTERPOLATE_FACTOR); - nodeSetSocketAvailability(end_fac, mode == GEO_NODE_CURVE_INTERPOLATE_FACTOR); - nodeSetSocketAvailability(start_len, mode == GEO_NODE_CURVE_INTERPOLATE_LENGTH); - nodeSetSocketAvailability(end_len, mode == GEO_NODE_CURVE_INTERPOLATE_LENGTH); + nodeSetSocketAvailability(start_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); + nodeSetSocketAvailability(end_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); + nodeSetSocketAvailability(start_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); + nodeSetSocketAvailability(end_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); } struct TrimLocation { @@ -158,8 +157,8 @@ static void trim_poly_spline(Spline &spline, linear_trim_data<float>(start, end, spline.tilts()); spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) { - std::optional<GMutableSpan> src = spline.attributes.get_for_write(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id); BLI_assert(src); attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { using T = decltype(dummy); @@ -193,14 +192,14 @@ static PolySpline trim_nurbs_spline(const Spline &spline, /* Copy generic attribute data. */ spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = spline.attributes.get_for_read(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id); BLI_assert(src); - if (!new_spline.attributes.create(name, meta_data.data_type)) { + if (!new_spline.attributes.create(attribute_id, meta_data.data_type)) { BLI_assert_unreachable(); return false; } - std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(name); + std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id); BLI_assert(dst); attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { @@ -249,8 +248,8 @@ static void trim_bezier_spline(Spline &spline, linear_trim_data<float>(start, end, bezier_spline.radii()); linear_trim_data<float>(start, end, bezier_spline.tilts()); spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) { - std::optional<GMutableSpan> src = spline.attributes.get_for_write(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id); BLI_assert(src); attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { using T = decltype(dummy); @@ -324,8 +323,7 @@ static void trim_bezier_spline(Spline &spline, static void geo_node_curve_trim_exec(GeoNodeExecParams params) { const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)params.node().storage; - const GeometryNodeCurveInterpolateMode mode = (GeometryNodeCurveInterpolateMode) - node_storage.mode; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); geometry_set = bke::geometry_set_realize_instances(geometry_set); @@ -338,12 +336,11 @@ static void geo_node_curve_trim_exec(GeoNodeExecParams params) CurveEval &curve = *curve_component.get_for_write(); MutableSpan<SplinePtr> splines = curve.splines(); - const float start = mode == GEO_NODE_CURVE_INTERPOLATE_FACTOR ? + 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_INTERPOLATE_FACTOR ? - params.extract_input<float>("End") : - params.extract_input<float>("End_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) { @@ -362,11 +359,11 @@ static void geo_node_curve_trim_exec(GeoNodeExecParams params) } const Spline::LookupResult start_lookup = - (mode == GEO_NODE_CURVE_INTERPOLATE_LENGTH) ? + (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) ? spline.lookup_evaluated_length(std::clamp(start, 0.0f, spline.length())) : spline.lookup_evaluated_factor(std::clamp(start, 0.0f, 1.0f)); const Spline::LookupResult end_lookup = - (mode == GEO_NODE_CURVE_INTERPOLATE_LENGTH) ? + (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) ? spline.lookup_evaluated_length(std::clamp(end, 0.0f, spline.length())) : spline.lookup_evaluated_factor(std::clamp(end, 0.0f, 1.0f)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 6bc0ab49959..1e2f652cd78 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -93,17 +93,17 @@ static void copy_dynamic_attributes(const CustomDataAttributes &src, const IndexMask mask) { src.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src_attribute = src.get_for_read(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.get_for_read(attribute_id); BLI_assert(src_attribute); - if (!dst.create(name, meta_data.data_type)) { + if (!dst.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> new_attribute = dst.get_for_write(name); + std::optional<GMutableSpan> new_attribute = dst.get_for_write(attribute_id); BLI_assert(new_attribute); attribute_math::convert_to_static_type(new_attribute->type(), [&](auto dummy) { @@ -559,7 +559,7 @@ static Mesh *delete_mesh_selection(const Mesh &mesh_in, mesh_in, *result, vertex_map, edge_map, selected_poly_indices, new_loop_starts); BKE_mesh_calc_edges_loose(result); /* Tag to recalculate normals later. */ - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } @@ -668,7 +668,8 @@ void register_node_type_geo_delete_geometry() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_delete_geometry_declare; ntype.geometry_node_execute = blender::nodes::geo_node_delete_geometry_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc new file mode 100644 index 00000000000..c52ff3d448e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc @@ -0,0 +1,60 @@ +/* + * 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" + +namespace blender::nodes { + +static void geo_node_input_index_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>("Index"); +} + +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>()}; + params.set_output("Index", std::move(index_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_index() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_INDEX, "Index", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_index_exec; + ntype.declare = blender::nodes::geo_node_input_index_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc new file mode 100644 index 00000000000..07818f2a3ad --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -0,0 +1,211 @@ +/* + * 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_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_input_normal_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Vector>("Normal"); +} + +static GVArrayPtr mesh_face_normals(const Mesh &mesh, + const Span<MVert> verts, + const Span<MPoly> polys, + const Span<MLoop> loops, + const IndexMask mask) +{ + /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ + if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) && + CustomData_has_layer(&mesh.pdata, CD_NORMAL)) { + const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); + + return std::make_unique<fn::GVArray_For_Span<float3>>( + Span<float3>((const float3 *)data, polys.size())); + } + + auto normal_fn = [verts, polys, loops](const int i) -> float3 { + float3 normal; + const MPoly &poly = polys[i]; + BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal); + return normal; + }; + + return std::make_unique< + fn::GVArray_For_EmbeddedVArray<float3, VArray_For_Func<float3, decltype(normal_fn)>>>( + mask.min_array_size(), mask.min_array_size(), normal_fn); +} + +static GVArrayPtr mesh_vertex_normals(const Mesh &mesh, + const Span<MVert> verts, + const Span<MPoly> polys, + const Span<MLoop> loops, + const IndexMask mask) +{ + /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ + if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) && + CustomData_has_layer(&mesh.vdata, CD_NORMAL)) { + const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); + + return std::make_unique<fn::GVArray_For_Span<float3>>( + Span<float3>((const float3 *)data, mesh.totvert)); + } + + /* If the normals are dirty, they must be recalculated for the output of this node's field + * source. Ideally vertex normals could be calculated lazily on a const mesh, but that's not + * possible at the moment, so we take ownership of the results. Sadly we must also create a copy + * of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy + * calculation of normals on meshes. + * + * Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */ + Array<MVert> temp_verts(verts); + Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */ + BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(), + mask.min_array_size(), + loops.data(), + loops.size(), + polys.data(), + polys.size(), + nullptr, + (float(*)[3])normals.data()); + + return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); +} + +static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_component, + const Mesh &mesh, + const IndexMask mask, + const AttributeDomain domain, + ResourceScope &scope) +{ + Span<MVert> verts{mesh.mvert, mesh.totvert}; + Span<MEdge> edges{mesh.medge, mesh.totedge}; + Span<MPoly> polys{mesh.mpoly, mesh.totpoly}; + Span<MLoop> loops{mesh.mloop, mesh.totloop}; + + switch (domain) { + case ATTR_DOMAIN_FACE: { + return scope.add_value(mesh_face_normals(mesh, verts, polys, loops, mask)).get(); + } + case ATTR_DOMAIN_POINT: { + return scope.add_value(mesh_vertex_normals(mesh, verts, polys, loops, mask)).get(); + } + case ATTR_DOMAIN_EDGE: { + /* In this case, start with vertex normals and convert to the edge domain, since the + * conversion from edges to vertices is very simple. Use the full mask since the edges + * might use the vertex normal from any index. */ + GVArrayPtr vert_normals = mesh_vertex_normals( + mesh, verts, polys, loops, IndexRange(verts.size())); + Span<float3> vert_normals_span = vert_normals->get_internal_span().typed<float3>(); + Array<float3> edge_normals(mask.min_array_size()); + + /* Use "manual" domain interpolation instead of the GeometryComponent API to avoid + * calculating unnecessary values and to allow normalizing the result much more simply. */ + for (const int i : mask) { + const MEdge &edge = edges[i]; + edge_normals[i] = float3::interpolate( + vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f) + .normalized(); + } + + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>( + std::move(edge_normals)); + } + case ATTR_DOMAIN_CORNER: { + /* The normals on corners are just the mesh's face normals, so start with the face normal + * array and copy the face normal for each of its corners. */ + GVArrayPtr face_normals = mesh_face_normals( + mesh, verts, polys, loops, IndexRange(polys.size())); + + /* In this case using the mesh component's generic domain interpolation is fine, the data + * will still be normalized, since the face normal is just copied to every corner. */ + GVArrayPtr loop_normals = mesh_component.attribute_try_adapt_domain( + std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); + return scope.add_value(std::move(loop_normals)).get(); + } + default: + return nullptr; + } +} + +class NormalFieldInput final : public fn::FieldInput { + public: + NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask 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_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return nullptr; + } + + return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope); + } + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + /* TODO: Add curve normals support. */ + return nullptr; + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 669605641; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const NormalFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_input_normal_exec(GeoNodeExecParams params) +{ + Field<float3> normal_field{std::make_shared<NormalFieldInput>()}; + params.set_output("Normal", std::move(normal_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_normal() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_NORMAL, "Normal", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_normal_exec; + ntype.declare = blender::nodes::geo_node_input_normal_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc new file mode 100644 index 00000000000..c6365bf6809 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc @@ -0,0 +1,43 @@ +/* + * 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" + +namespace blender::nodes { + +static void geo_node_input_position_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Vector>("Position"); +} + +static void geo_node_input_position_exec(GeoNodeExecParams params) +{ + Field<float3> position_field{ + std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>())}; + params.set_output("Position", std::move(position_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_position() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_POSITION, "Position", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_position_exec; + ntype.declare = blender::nodes::geo_node_input_position_declare; + 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 730cf08feaa..93643298f92 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -29,27 +29,14 @@ using blender::fn::GVArray_For_GSpan; -static bNodeSocketTemplate geo_node_join_geometry_in[] = { - {SOCK_GEOMETRY, - N_("Geometry"), - 0.0f, - 0.0f, - 0.0f, - 1.0f, - -1.0f, - 1.0f, - PROP_NONE, - SOCK_MULTI_INPUT}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_join_geometry_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; - namespace blender::nodes { +static void geo_node_join_geometry_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry").multi_input(); + b.add_output<decl::Geometry>("Geometry"); +} + static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent *> src_components) { int totverts = 0; @@ -161,34 +148,35 @@ static Array<const GeometryComponent *> to_base_components(Span<const Component return components; } -static Map<std::string, AttributeMetaData> get_final_attribute_info( +static Map<AttributeIDRef, AttributeMetaData> get_final_attribute_info( Span<const GeometryComponent *> components, Span<StringRef> ignored_attributes) { - Map<std::string, AttributeMetaData> info; + Map<AttributeIDRef, AttributeMetaData> info; for (const GeometryComponent *component : components) { - component->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (ignored_attributes.contains(name)) { - return true; - } - info.add_or_modify( - name, - [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; }, - [&](AttributeMetaData *meta_data_final) { - meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity( - {meta_data_final->data_type, meta_data.data_type}); - meta_data_final->domain = blender::bke::attribute_domain_highest_priority( - {meta_data_final->domain, meta_data.domain}); - }); - return true; - }); + component->attribute_foreach( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) { + return true; + } + info.add_or_modify( + attribute_id, + [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; }, + [&](AttributeMetaData *meta_data_final) { + meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity( + {meta_data_final->data_type, meta_data.data_type}); + meta_data_final->domain = blender::bke::attribute_domain_highest_priority( + {meta_data_final->domain, meta_data.domain}); + }); + return true; + }); } return info; } static void fill_new_attribute(Span<const GeometryComponent *> src_components, - StringRef attribute_name, + const AttributeIDRef &attribute_id, const CustomDataType data_type, const AttributeDomain domain, GMutableSpan dst_span) @@ -203,7 +191,7 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components, continue; } GVArrayPtr read_attribute = component->attribute_get_for_read( - attribute_name, domain, data_type, nullptr); + attribute_id, domain, data_type, nullptr); GVArray_GSpan src_span{*read_attribute}; const void *src_buffer = src_span.data(); @@ -218,20 +206,21 @@ static void join_attributes(Span<const GeometryComponent *> src_components, GeometryComponent &result, Span<StringRef> ignored_attributes = {}) { - const Map<std::string, AttributeMetaData> info = get_final_attribute_info(src_components, - ignored_attributes); + const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(src_components, + ignored_attributes); - for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) { + const AttributeIDRef attribute_id = item.key; const AttributeMetaData &meta_data = item.value; OutputAttribute write_attribute = result.attribute_try_get_for_output_only( - name, meta_data.domain, meta_data.data_type); + attribute_id, meta_data.domain, meta_data.data_type); if (!write_attribute) { continue; } GMutableSpan dst_span = write_attribute.as_span(); - fill_new_attribute(src_components, name, meta_data.data_type, meta_data.domain, dst_span); + fill_new_attribute( + src_components, attribute_id, meta_data.data_type, meta_data.domain, dst_span); write_attribute.save(); } } @@ -306,7 +295,7 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet * \note This takes advantage of the fact that creating attributes on joined curves never * changes a point attribute into a spline attribute; it is always the other way around. */ -static void ensure_control_point_attribute(const StringRef name, +static void ensure_control_point_attribute(const AttributeIDRef &attribute_id, const CustomDataType data_type, Span<CurveComponent *> src_components, CurveEval &result) @@ -321,7 +310,7 @@ static void ensure_control_point_attribute(const StringRef name, const CurveEval *current_curve = src_components[src_component_index]->get_for_read(); for (SplinePtr &spline : splines) { - std::optional<GSpan> attribute = spline->attributes.get_for_read(name); + std::optional<GSpan> attribute = spline->attributes.get_for_read(attribute_id); if (attribute) { if (attribute->type() != type) { @@ -334,22 +323,22 @@ static void ensure_control_point_attribute(const StringRef name, conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), type) ->materialize(converted_buffer); - spline->attributes.remove(name); - spline->attributes.create_by_move(name, data_type, converted_buffer); + spline->attributes.remove(attribute_id); + spline->attributes.create_by_move(attribute_id, data_type, converted_buffer); } } else { - spline->attributes.create(name, data_type); + spline->attributes.create(attribute_id, data_type); - if (current_curve->attributes.get_for_read(name)) { + if (current_curve->attributes.get_for_read(attribute_id)) { /* In this case the attribute did not exist, but there is a spline domain attribute * we can retrieve a value from, as a spline to point domain conversion. So fill the * new attribute with the value for this spline. */ GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read( - name, data_type, nullptr); + attribute_id, data_type, nullptr); - BLI_assert(spline->attributes.get_for_read(name)); - std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(name); + BLI_assert(spline->attributes.get_for_read(attribute_id)); + std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(attribute_id); BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); current_curve_attribute->get(spline_index_in_component, buffer); @@ -371,15 +360,15 @@ static void ensure_control_point_attribute(const StringRef name, /** * Fill data for an attribute on the new curve based on all source curves. */ -static void ensure_spline_attribute(const StringRef name, +static void ensure_spline_attribute(const AttributeIDRef &attribute_id, const CustomDataType data_type, Span<CurveComponent *> src_components, CurveEval &result) { const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - result.attributes.create(name, data_type); - GMutableSpan result_attribute = *result.attributes.get_for_write(name); + result.attributes.create(attribute_id, data_type); + GMutableSpan result_attribute = *result.attributes.get_for_write(attribute_id); int offset = 0; for (const CurveComponent *component : src_components) { @@ -388,7 +377,7 @@ static void ensure_spline_attribute(const StringRef name, if (size == 0) { continue; } - GVArrayPtr read_attribute = curve.attributes.get_for_read(name, data_type, nullptr); + GVArrayPtr read_attribute = curve.attributes.get_for_read(attribute_id, data_type, nullptr); GVArray_GSpan src_span{*read_attribute}; const void *src_buffer = src_span.data(); @@ -406,19 +395,19 @@ static void ensure_spline_attribute(const StringRef name, * \warning Splines have been moved out of the source components at this point, so it * is important to only read curve-level data (spline domain attributes) from them. */ -static void join_curve_attributes(const Map<std::string, AttributeMetaData> &info, +static void join_curve_attributes(const Map<AttributeIDRef, AttributeMetaData> &info, Span<CurveComponent *> src_components, CurveEval &result) { - for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) { + const AttributeIDRef attribute_id = item.key; const AttributeMetaData meta_data = item.value; if (meta_data.domain == ATTR_DOMAIN_CURVE) { - ensure_spline_attribute(name, meta_data.data_type, src_components, result); + ensure_spline_attribute(attribute_id, meta_data.data_type, src_components, result); } else { - ensure_control_point_attribute(name, meta_data.data_type, src_components, result); + ensure_control_point_attribute(attribute_id, meta_data.data_type, src_components, result); } } } @@ -446,7 +435,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<std::string, AttributeMetaData> info = get_final_attribute_info( + const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info( {(const GeometryComponent **)src_components.data(), src_components.size()}, {"position", "radius", "tilt", "cyclic", "resolution"}); @@ -506,7 +495,7 @@ void register_node_type_geo_join_geometry() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_JOIN_GEOMETRY, "Join Geometry", NODE_CLASS_GEOMETRY, 0); - node_type_socket_templates(&ntype, geo_node_join_geometry_in, geo_node_join_geometry_out); ntype.geometry_node_execute = blender::nodes::geo_node_join_geometry_exec; + ntype.declare = blender::nodes::geo_node_join_geometry_declare; nodeRegisterType(&ntype); } 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 4d0b4cfecbc..43818947272 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc @@ -29,12 +29,12 @@ namespace blender::nodes { static void geo_node_material_assign_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Material").hide_label(true); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Material>("Material").hide_label(); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value(); b.add_output<decl::Geometry>("Geometry"); } -static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) +static void assign_material_to_faces(Mesh &mesh, const IndexMask selection, Material *material) { int new_material_index = -1; for (const int i : IndexRange(mesh.totcol)) { @@ -51,18 +51,16 @@ static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, } mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly); - for (const int i : IndexRange(mesh.totpoly)) { - if (face_mask[i]) { - MPoly &poly = mesh.mpoly[i]; - poly.mat_nr = new_material_index; - } + for (const int i : selection) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; } } static void geo_node_material_assign_exec(GeoNodeExecParams params) { Material *material = params.extract_input<Material *>("Material"); - const std::string mask_name = params.extract_input<std::string>("Selection"); + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -72,9 +70,15 @@ static void geo_node_material_assign_exec(GeoNodeExecParams params) MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); Mesh *mesh = mesh_component.get_for_write(); if (mesh != nullptr) { - GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>( - mask_name, ATTR_DOMAIN_FACE, true); - assign_material_to_faces(*mesh, face_mask, material); + + 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); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc new file mode 100644 index 00000000000..22c24e34314 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc @@ -0,0 +1,131 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_task.hh" + +#include "BKE_material.h" + +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"); +} + +static void select_mesh_by_material(const Mesh &mesh, + const Material *material, + const IndexMask mask, + const MutableSpan<bool> r_selection) +{ + BLI_assert(mesh.totpoly >= r_selection.size()); + Vector<int> material_indices; + for (const int i : IndexRange(mesh.totcol)) { + if (mesh.mat[i] == material) { + material_indices.append(i); + } + } + threading::parallel_for(mask.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + const int face_index = mask[i]; + r_selection[i] = material_indices.contains(mesh.mpoly[face_index].mat_nr); + } + }); +} + +class MaterialSelectionFieldInput final : public fn::FieldInput { + Material *material_; + + public: + MaterialSelectionFieldInput(Material *material) + : fn::FieldInput(CPPType::get<bool>(), "Material Selection"), material_(material) + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask 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_MESH) { + return nullptr; + } + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_FACE) { + Array<bool> selection(mask.min_array_size()); + select_mesh_by_material(*mesh, material_, mask, selection); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<bool>>>(std::move(selection)); + } + + Array<bool> selection(mesh->totpoly); + select_mesh_by_material(*mesh, material_, IndexMask(mesh->totpoly), selection); + GVArrayPtr face_selection = std::make_unique<fn::GVArray_For_ArrayContainer<Array<bool>>>( + std::move(selection)); + GVArrayPtr final_selection = mesh_component.attribute_try_adapt_domain( + std::move(face_selection), ATTR_DOMAIN_FACE, domain); + return scope.add_value(std::move(final_selection)).get(); + } + + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 91619626; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const MaterialSelectionFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_material_selection_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + Field<bool> material_field{std::make_shared<MaterialSelectionFieldInput>(material)}; + params.set_output("Selection", std::move(material_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_material_selection() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_MATERIAL_SELECTION, "Material Selection", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_material_selection_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_material_selection_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc index f5d9ed2d6a9..af8ce02b3c1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -26,7 +26,7 @@ namespace blender::nodes { static void geo_node_mesh_primitive_cube_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Vector>("Size").default_value({1.0f, 1.0f, 1.0f}).subtype(PROP_TRANSLATION); + b.add_input<decl::Vector>("Size").default_value(float3(1)).min(0.0f).subtype(PROP_TRANSLATION); b.add_input<decl::Int>("Vertices X").default_value(2).min(2).max(1000); b.add_input<decl::Int>("Vertices Y").default_value(2).min(2).max(1000); b.add_input<decl::Int>("Vertices Z").default_value(2).min(2).max(1000); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc index d77551367a9..5ea7165ac31 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc @@ -44,7 +44,7 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius) BMO_op_callf(bm, BMO_FLAG_DEFAULTS, - "create_icosphere subdivisions=%i diameter=%f matrix=%m4 calc_uvs=%b", + "create_icosphere subdivisions=%i radius=%f matrix=%m4 calc_uvs=%b", subdivisions, std::abs(radius), transform.values, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index 2cea60ea112..11349dc7d42 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -52,10 +52,10 @@ static void copy_attributes_to_points(CurveEval &curve, Span<Vector<int>> point_to_vert_maps) { MutableSpan<SplinePtr> splines = curve.splines(); - Set<std::string> source_attribute_names = mesh_component.attribute_names(); + Set<AttributeIDRef> source_attribute_ids = mesh_component.attribute_ids(); /* Copy builtin control point attributes. */ - if (source_attribute_names.contains_as("tilt")) { + if (source_attribute_ids.contains("tilt")) { const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>( "tilt", ATTR_DOMAIN_POINT, 0.0f); threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { @@ -64,9 +64,9 @@ static void copy_attributes_to_points(CurveEval &curve, *tilt_attribute, point_to_vert_maps[i], splines[i]->tilts()); } }); - source_attribute_names.remove_contained_as("tilt"); + source_attribute_ids.remove_contained("tilt"); } - if (source_attribute_names.contains_as("radius")) { + if (source_attribute_ids.contains("radius")) { const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>( "radius", ATTR_DOMAIN_POINT, 1.0f); threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { @@ -75,15 +75,15 @@ static void copy_attributes_to_points(CurveEval &curve, *radius_attribute, point_to_vert_maps[i], splines[i]->radii()); } }); - source_attribute_names.remove_contained_as("radius"); + source_attribute_ids.remove_contained("radius"); } /* Don't copy other builtin control point attributes. */ - source_attribute_names.remove_as("position"); + source_attribute_ids.remove("position"); /* Copy dynamic control point attributes. */ - for (const StringRef name : source_attribute_names) { - const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(name, + for (const AttributeIDRef &attribute_id : source_attribute_ids) { + const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id, ATTR_DOMAIN_POINT); /* Some attributes might not exist if they were builtin attribute on domains that don't * have any elements, i.e. a face attribute on the output of the line primitive node. */ @@ -96,8 +96,9 @@ static void copy_attributes_to_points(CurveEval &curve, threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { /* Create attribute on the spline points. */ - splines[i]->attributes.create(name, data_type); - std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(name); + splines[i]->attributes.create(attribute_id, data_type); + std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write( + attribute_id); BLI_assert(spline_attribute); /* Copy attribute based on the map for this spline. */ @@ -305,7 +306,8 @@ void register_node_type_geo_mesh_to_curve() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_mesh_to_curve_declare; ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_curve_exec; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc index ab99c9bb3f8..389acc40f0f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -25,7 +25,7 @@ namespace blender::nodes { static void geo_node_object_info_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Object>("Object").hide_label(true); + b.add_input<decl::Object>("Object").hide_label(); b.add_output<decl::Vector>("Location"); b.add_output<decl::Vector>("Rotation"); b.add_output<decl::Vector>("Scale"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc index cf874bea718..04b4003daed 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -277,17 +277,17 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, BLI_NOINLINE static void interpolate_existing_attributes( Span<GeometryInstanceGroup> set_groups, Span<int> instance_start_offsets, - const Map<std::string, AttributeKind> &attributes, + const Map<AttributeIDRef, AttributeKind> &attributes, GeometryComponent &component, Span<Vector<float3>> bary_coords_array, Span<Vector<int>> looptri_indices_array) { - for (Map<std::string, AttributeKind>::Item entry : attributes.items()) { - StringRef attribute_name = entry.key; + 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 = component.attribute_try_get_for_output_only( - attribute_name, ATTR_DOMAIN_POINT, output_data_type); + attribute_id, ATTR_DOMAIN_POINT, output_data_type); if (!attribute_out) { continue; } @@ -301,7 +301,7 @@ BLI_NOINLINE static void interpolate_existing_attributes( const Mesh &mesh = *source_component.get_for_read(); std::optional<AttributeMetaData> attribute_info = component.attribute_get_meta_data( - attribute_name); + attribute_id); if (!attribute_info) { i_instance += set_group.transforms.size(); continue; @@ -309,7 +309,7 @@ BLI_NOINLINE static void interpolate_existing_attributes( const AttributeDomain source_domain = attribute_info->domain; GVArrayPtr source_attribute = source_component.attribute_get_for_read( - attribute_name, source_domain, output_data_type, nullptr); + attribute_id, source_domain, output_data_type, nullptr); if (!source_attribute) { i_instance += set_group.transforms.size(); continue; @@ -406,7 +406,7 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> BLI_NOINLINE static void add_remaining_point_attributes( Span<GeometryInstanceGroup> set_groups, Span<int> instance_start_offsets, - const Map<std::string, AttributeKind> &attributes, + const Map<AttributeIDRef, AttributeKind> &attributes, GeometryComponent &component, Span<Vector<float3>> bary_coords_array, Span<Vector<int>> looptri_indices_array) @@ -629,7 +629,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) PointCloudComponent &point_component = geometry_set_out.get_component_for_write<PointCloudComponent>(); - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; bke::geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes); add_remaining_point_attributes(set_groups, @@ -649,7 +649,7 @@ void register_node_type_geo_point_distribute() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); node_type_update(&ntype, blender::nodes::node_point_distribute_update); ntype.declare = blender::nodes::geo_node_point_distribute_declare; ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc index 36017307739..fb45c22ced4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -29,15 +29,16 @@ namespace blender::nodes { static void geo_node_point_instance_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Object>("Object").hide_label(true); - b.add_input<decl::Collection>("Collection").hide_label(true); + b.add_input<decl::Object>("Object").hide_label(); + b.add_input<decl::Collection>("Collection").hide_label(); + b.add_input<decl::Geometry>("Instance Geometry"); b.add_input<decl::Int>("Seed").min(-10000).max(10000); b.add_output<decl::Geometry>("Geometry"); } static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "instance_type", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "instance_type", 0, "", ICON_NONE); if (RNA_enum_get(ptr, "instance_type") == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION) { uiItemR(layout, ptr, "use_whole_collection", 0, nullptr, ICON_NONE); } @@ -56,7 +57,8 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) { bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1); bNodeSocket *collection_socket = object_socket->next; - bNodeSocket *seed_socket = collection_socket->next; + bNodeSocket *instance_geometry_socket = collection_socket->next; + bNodeSocket *seed_socket = instance_geometry_socket->next; NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node->storage; GeometryNodePointInstanceType type = (GeometryNodePointInstanceType)node_storage->instance_type; @@ -65,6 +67,8 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) nodeSetSocketAvailability(object_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT); nodeSetSocketAvailability(collection_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION); + nodeSetSocketAvailability(instance_geometry_socket, + type == GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY); nodeSetSocketAvailability( seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection); } @@ -114,6 +118,13 @@ static Vector<InstanceReference> get_instance_references__collection(GeoNodeExec return references; } +static Vector<InstanceReference> get_instance_references__geometry(GeoNodeExecParams ¶ms) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Instance Geometry"); + geometry_set.ensure_owns_direct_data(); + return {std::move(geometry_set)}; +} + static Vector<InstanceReference> get_instance_references(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); @@ -128,6 +139,9 @@ static Vector<InstanceReference> get_instance_references(GeoNodeExecParams ¶ case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: { return get_instance_references__collection(params); } + case GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY: { + return get_instance_references__geometry(params); + } } return {}; } @@ -245,7 +259,8 @@ void register_node_type_geo_point_instance() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_point_instance_init); node_type_storage( &ntype, "NodeGeometryPointInstance", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc index 4d77bcc132f..60c82360007 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc @@ -219,7 +219,7 @@ void register_node_type_geo_point_rotate() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_point_rotate_init); node_type_update(&ntype, blender::nodes::geo_node_point_rotate_update); node_type_storage( diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc index 1a7ab9817d9..99adce149e9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc @@ -126,7 +126,7 @@ void register_node_type_geo_point_scale() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_point_scale_declare; node_type_init(&ntype, blender::nodes::geo_node_point_scale_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc index 1b0061346c4..48b6676c1dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc @@ -53,8 +53,8 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, Span<bool> masks, const bool invert) { - for (const std::string &name : in_component.attribute_names()) { - ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name); + for (const AttributeIDRef &attribute_id : in_component.attribute_ids()) { + ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type()); /* Only copy point attributes. Theoretically this could interpolate attributes on other @@ -65,7 +65,7 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, } OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( - name, ATTR_DOMAIN_POINT, data_type); + attribute_id, ATTR_DOMAIN_POINT, data_type); attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); @@ -164,7 +164,8 @@ void register_node_type_geo_point_separate() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_point_instance_declare; ntype.geometry_node_execute = blender::nodes::geo_node_point_separate_exec; ntype.geometry_node_execute_supports_laziness = true; diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc index d187bf0fa71..f2fce45c57b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc @@ -95,7 +95,8 @@ void register_node_type_geo_point_translate() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_point_translate_init); node_type_update(&ntype, blender::nodes::geo_node_point_translate_update); node_type_storage(&ntype, diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index 8f34fff9f66..d920c8de9f0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -263,7 +263,7 @@ void register_node_type_geo_points_to_volume() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0); node_type_storage(&ntype, "NodeGeometryPointsToVolume", node_free_standard_storage, diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc index ed7ed87fa46..401a478f04c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -308,7 +308,7 @@ void register_node_type_geo_raycast() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0); node_type_size_preset(&ntype, NODE_SIZE_LARGE); node_type_init(&ntype, blender::nodes::geo_node_raycast_init); node_type_update(&ntype, blender::nodes::geo_node_raycast_update); diff --git a/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc new file mode 100644 index 00000000000..3be79d5ba3b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc @@ -0,0 +1,48 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes { + +static void geo_node_realize_instances_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_output<decl::Geometry>("Geometry"); +} + +static void geo_node_realize_instances_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_realize_instances() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_REALIZE_INSTANCES, "Realize Instances", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_realize_instances_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_realize_instances_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc new file mode 100644 index 00000000000..c5e10b788ac --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc @@ -0,0 +1,80 @@ +/* + * 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 "DEG_depsgraph_query.h" + +#include "node_geometry_util.hh" + +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_output<decl::Geometry>("Geometry"); +} + +static void set_position_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<float3> &position_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + + 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); + + OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + fn::FieldEvaluator position_evaluator{field_context, &selection}; + position_evaluator.add_with_destination(position_field, positions.varray()); + position_evaluator.evaluate(); + positions.save(); +} + +static void geo_node_set_position_exec(GeoNodeExecParams params) +{ + GeometrySet geometry = params.extract_input<GeometrySet>("Geometry"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<float3> position_field = params.extract_input<Field<float3>>("Position"); + + for (const GeometryComponentType type : {GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES}) { + if (geometry.has(type)) { + set_position_in_component( + geometry.get_component_for_write(type), selection_field, position_field); + } + } + + params.set_output("Geometry", std::move(geometry)); +} + +} // namespace blender::nodes + +void register_node_type_geo_set_position() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_POSITION, "Set Position", NODE_CLASS_GEOMETRY, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_set_position_exec; + ntype.declare = blender::nodes::geo_node_set_position_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc index d127f7dc0ba..4541bf3569f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -37,14 +37,13 @@ static void geo_node_subdivision_surface_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { -#ifndef WITH_OPENSUBDIV - UNUSED_VARS(ptr); - uiItemL(layout, IFACE_("Disabled, built without OpenSubdiv"), ICON_ERROR); -#else +#ifdef WITH_OPENSUBDIV uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "uv_smooth", 0, nullptr, ICON_NONE); uiItemR(layout, ptr, "boundary_smooth", 0, nullptr, ICON_NONE); +#else + UNUSED_VARS(layout, ptr); #endif } diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index d7423aa6d32..d5eb067cad0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -69,8 +69,7 @@ void transform_mesh(Mesh *mesh, else { const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); BKE_mesh_transform(mesh, matrix.values, false); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } } diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc index 7487f11d77d..3b3b643d0ae 100644 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc @@ -161,8 +161,10 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful { bke::geometry_set_instances_attribute_foreach( geometry_set, - [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) { - this->attributes_.append({attribute_name, meta_data.domain, meta_data.data_type}); + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_named()) { + this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); + } return true; }, 8); diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index dff92d5884f..f6b6cc49b2e 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -16,6 +16,8 @@ #include "NOD_node_declaration.hh" +#include "BKE_node.h" + namespace blender::nodes { void NodeDeclaration::build(bNodeTree &ntree, bNode &node) const @@ -62,4 +64,31 @@ bNodeSocket &SocketDeclaration::update_or_build(bNodeTree &ntree, return this->build(ntree, node, (eNodeSocketInOut)socket.in_out); } +void SocketDeclaration::set_common_flags(bNodeSocket &socket) const +{ + SET_FLAG_FROM_TEST(socket.flag, hide_value_, SOCK_HIDE_VALUE); + SET_FLAG_FROM_TEST(socket.flag, hide_label_, SOCK_HIDE_LABEL); + SET_FLAG_FROM_TEST(socket.flag, is_multi_input_, SOCK_MULTI_INPUT); +} + +bool SocketDeclaration::matches_common_data(const bNodeSocket &socket) const +{ + if (socket.name != name_) { + return false; + } + if (socket.identifier != identifier_) { + return false; + } + if (((socket.flag & SOCK_HIDE_VALUE) != 0) != hide_value_) { + return false; + } + if (((socket.flag & SOCK_HIDE_LABEL) != 0) != hide_label_) { + return false; + } + if (((socket.flag & SOCK_MULTI_INPUT) != 0) != is_multi_input_) { + return false; + } + return true; +} + } // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index d386781e3ce..31260f95242 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -48,6 +48,7 @@ #include "NOD_socket.h" #include "FN_cpp_type_make.hh" +#include "FN_field.hh" using namespace blender; using blender::nodes::SocketDeclarationPtr; @@ -268,11 +269,9 @@ void node_verify_sockets(bNodeTree *ntree, bNode *node, bool do_id_user) return; } if (ntype->declare != nullptr) { - blender::nodes::NodeDeclaration node_decl; - blender::nodes::NodeDeclarationBuilder builder{node_decl}; - ntype->declare(builder); - if (!node_decl.matches(*node)) { - refresh_node(*ntree, *node, node_decl, do_id_user); + nodeDeclarationEnsure(ntree, node); + if (!node->declaration->matches(*node)) { + refresh_node(*ntree, *node, *node->declaration, do_id_user); } return; } @@ -701,8 +700,14 @@ static bNodeSocketType *make_socket_type_bool() socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(bool *)r_value = ((bNodeSocketValueBoolean *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<bool>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + bool value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -713,8 +718,14 @@ static bNodeSocketType *make_socket_type_float(PropertySubType subtype) socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(float *)r_value = ((bNodeSocketValueFloat *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<float>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + float value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -725,8 +736,14 @@ static bNodeSocketType *make_socket_type_int(PropertySubType subtype) socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(int *)r_value = ((bNodeSocketValueInt *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<int>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + int value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -737,8 +754,14 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype) socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(blender::float3 *)r_value = ((bNodeSocketValueVector *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<blender::float3>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + blender::float3 value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<blender::float3>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -751,8 +774,15 @@ static bNodeSocketType *make_socket_type_rgba() socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(blender::ColorGeometry4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<blender::ColorGeometry4f>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + blender::ColorGeometry4f value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) + blender::fn::Field<blender::ColorGeometry4f>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -763,8 +793,15 @@ static bNodeSocketType *make_socket_type_string() socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { new (r_value) std::string(((bNodeSocketValueString *)socket.default_value)->value); }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<std::string>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + std::string value; + value.~basic_string(); + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<std::string>(blender::fn::make_constant_field(value)); + }; return socktype; } diff --git a/source/blender/nodes/intern/node_socket_declarations.cc b/source/blender/nodes/intern/node_socket_declarations.cc index 418fed146fb..4b0dbad3cff 100644 --- a/source/blender/nodes/intern/node_socket_declarations.cc +++ b/source/blender/nodes/intern/node_socket_declarations.cc @@ -38,6 +38,7 @@ bNodeSocket &Float::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_FLOAT, subtype_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -47,16 +48,13 @@ bNodeSocket &Float::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out bool Float::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_FLOAT) { - return false; - } - if (socket.typeinfo->subtype != subtype_) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { + if (socket.type != SOCK_FLOAT) { return false; } - if (socket.identifier != identifier_) { + if (socket.typeinfo->subtype != subtype_) { return false; } bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value; @@ -77,6 +75,7 @@ bNodeSocket &Float::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket & if (socket.typeinfo->subtype != subtype_) { modify_subtype_except_for_storage(socket, subtype_); } + this->set_common_flags(socket); bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -92,6 +91,7 @@ bNodeSocket &Int::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_INT, subtype_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -101,16 +101,13 @@ bNodeSocket &Int::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) bool Int::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_INT) { - return false; - } - if (socket.typeinfo->subtype != subtype_) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { + if (socket.type != SOCK_INT) { return false; } - if (socket.identifier != identifier_) { + if (socket.typeinfo->subtype != subtype_) { return false; } bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value; @@ -131,6 +128,7 @@ bNodeSocket &Int::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &so if (socket.typeinfo->subtype != subtype_) { modify_subtype_except_for_storage(socket, subtype_); } + this->set_common_flags(socket); bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -146,23 +144,23 @@ bNodeSocket &Vector::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ou { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_VECTOR, subtype_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueVector &value = *(bNodeSocketValueVector *)socket.default_value; copy_v3_v3(value.value, default_value_); + value.min = soft_min_value_; + value.max = soft_max_value_; return socket; } bool Vector::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_VECTOR) { - return false; - } - if (socket.typeinfo->subtype != subtype_) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { + if (socket.type != SOCK_VECTOR) { return false; } - if (socket.identifier != identifier_) { + if (socket.typeinfo->subtype != subtype_) { return false; } return true; @@ -176,6 +174,7 @@ bNodeSocket &Vector::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket if (socket.typeinfo->subtype != subtype_) { modify_subtype_except_for_storage(socket, subtype_); } + this->set_common_flags(socket); bNodeSocketValueVector &value = *(bNodeSocketValueVector *)socket.default_value; value.subtype = subtype_; STRNCPY(socket.name, name_.c_str()); @@ -190,6 +189,7 @@ bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_BOOLEAN, PROP_NONE, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueBoolean &value = *(bNodeSocketValueBoolean *)socket.default_value; value.value = default_value_; return socket; @@ -197,13 +197,10 @@ bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) bool Bool::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_BOOLEAN) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { - return false; - } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_BOOLEAN) { return false; } return true; @@ -217,6 +214,7 @@ bNodeSocket &Color::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_RGBA, PROP_NONE, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueRGBA &value = *(bNodeSocketValueRGBA *)socket.default_value; copy_v4_v4(value.value, default_value_); return socket; @@ -224,13 +222,15 @@ bNodeSocket &Color::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out bool Color::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_RGBA) { - return false; - } - if (socket.name != name_) { - return false; + if (!this->matches_common_data(socket)) { + if (socket.name != name_) { + return false; + } + if (socket.identifier != identifier_) { + return false; + } } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_RGBA) { return false; } return true; @@ -244,18 +244,16 @@ bNodeSocket &String::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ou { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_STRING, PROP_NONE, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); return socket; } bool String::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_STRING) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { - return false; - } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_STRING) { return false; } return true; @@ -265,42 +263,37 @@ bool String::matches(const bNodeSocket &socket) const * IDSocketDeclaration. */ -namespace detail { -bNodeSocket &build_id_socket(bNodeTree &ntree, - bNode &node, - eNodeSocketInOut in_out, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier) +bNodeSocket &IDSocketDeclaration::build(bNodeTree &ntree, + bNode &node, + eNodeSocketInOut in_out) const { bNodeSocket &socket = *nodeAddSocket( - &ntree, &node, in_out, data.idname, name.c_str(), identifier.c_str()); - if (data.hide_label) { - socket.flag |= SOCK_HIDE_LABEL; - } + &ntree, &node, in_out, idname_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); return socket; } -bool matches_id_socket(const bNodeSocket &socket, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier) +bool IDSocketDeclaration::matches(const bNodeSocket &socket) const { - if (!STREQ(socket.idname, data.idname)) { - return false; - } - if (data.hide_label != ((socket.flag & SOCK_HIDE_LABEL) != 0)) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name) { - return false; - } - if (socket.identifier != identifier) { + if (!STREQ(socket.idname, idname_)) { return false; } return true; } -} // namespace detail + +bNodeSocket &IDSocketDeclaration::update_or_build(bNodeTree &ntree, + bNode &node, + bNodeSocket &socket) const +{ + if (StringRef(socket.idname) != idname_) { + return this->build(ntree, node, (eNodeSocketInOut)socket.in_out); + } + this->set_common_flags(socket); + return socket; +} /* -------------------------------------------------------------------- * Geometry. @@ -310,18 +303,16 @@ bNodeSocket &Geometry::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ { bNodeSocket &socket = *nodeAddSocket( &ntree, &node, in_out, "NodeSocketGeometry", identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); return socket; } bool Geometry::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_GEOMETRY) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { - return false; - } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_GEOMETRY) { return false; } return true; diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_gradient.c b/source/blender/nodes/shader/nodes/node_shader_tex_gradient.cc index e3d4bad2bf8..0c0d75179a9 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_gradient.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_gradient.cc @@ -43,7 +43,8 @@ static bNodeSocketTemplate sh_node_tex_gradient_out[] = { static void node_shader_init_tex_gradient(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTexGradient *tex = MEM_callocN(sizeof(NodeTexGradient), "NodeTexGradient"); + NodeTexGradient *tex = (NodeTexGradient *)MEM_callocN(sizeof(NodeTexGradient), + "NodeTexGradient"); BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); BKE_texture_colormapping_default(&tex->base.color_mapping); tex->gradient_type = SHD_BLEND_LINEAR; diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.c b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc index 420c5b75926..f5e9aef3aad 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc @@ -49,7 +49,8 @@ static bNodeSocketTemplate sh_node_tex_musgrave_out[] = { static void node_shader_init_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTexMusgrave *tex = MEM_callocN(sizeof(NodeTexMusgrave), "NodeTexMusgrave"); + NodeTexMusgrave *tex = (NodeTexMusgrave *)MEM_callocN(sizeof(NodeTexMusgrave), + "NodeTexMusgrave"); BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); BKE_texture_colormapping_default(&tex->base.color_mapping); tex->musgrave_type = SHD_MUSGRAVE_FBM; @@ -58,6 +59,41 @@ static void node_shader_init_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node) node->storage = tex; } +static const char *gpu_shader_name_get(const int type, const int dimensions) +{ + BLI_assert(type >= 0 && type < 5); + BLI_assert(dimensions > 0 && dimensions < 5); + + switch (type) { + case SHD_MUSGRAVE_MULTIFRACTAL: + return std::array{"node_tex_musgrave_multi_fractal_1d", + "node_tex_musgrave_multi_fractal_2d", + "node_tex_musgrave_multi_fractal_3d", + "node_tex_musgrave_multi_fractal_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_FBM: + return std::array{"node_tex_musgrave_fBm_1d", + "node_tex_musgrave_fBm_2d", + "node_tex_musgrave_fBm_3d", + "node_tex_musgrave_fBm_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_HYBRID_MULTIFRACTAL: + return std::array{"node_tex_musgrave_hybrid_multi_fractal_1d", + "node_tex_musgrave_hybrid_multi_fractal_2d", + "node_tex_musgrave_hybrid_multi_fractal_3d", + "node_tex_musgrave_hybrid_multi_fractal_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_RIDGED_MULTIFRACTAL: + return std::array{"node_tex_musgrave_ridged_multi_fractal_1d", + "node_tex_musgrave_ridged_multi_fractal_2d", + "node_tex_musgrave_ridged_multi_fractal_3d", + "node_tex_musgrave_ridged_multi_fractal_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_HETERO_TERRAIN: + return std::array{"node_tex_musgrave_hetero_terrain_1d", + "node_tex_musgrave_hetero_terrain_2d", + "node_tex_musgrave_hetero_terrain_3d", + "node_tex_musgrave_hetero_terrain_4d"}[dimensions - 1]; + } + return nullptr; +} + static int node_shader_gpu_tex_musgrave(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), @@ -71,53 +107,9 @@ static int node_shader_gpu_tex_musgrave(GPUMaterial *mat, int dimensions = tex->dimensions; int type = tex->musgrave_type; - static const char *names[][5] = { - [SHD_MUSGRAVE_MULTIFRACTAL] = - { - "", - "node_tex_musgrave_multi_fractal_1d", - "node_tex_musgrave_multi_fractal_2d", - "node_tex_musgrave_multi_fractal_3d", - "node_tex_musgrave_multi_fractal_4d", - }, - [SHD_MUSGRAVE_FBM] = - { - "", - "node_tex_musgrave_fBm_1d", - "node_tex_musgrave_fBm_2d", - "node_tex_musgrave_fBm_3d", - "node_tex_musgrave_fBm_4d", - }, - [SHD_MUSGRAVE_HYBRID_MULTIFRACTAL] = - { - "", - "node_tex_musgrave_hybrid_multi_fractal_1d", - "node_tex_musgrave_hybrid_multi_fractal_2d", - "node_tex_musgrave_hybrid_multi_fractal_3d", - "node_tex_musgrave_hybrid_multi_fractal_4d", - }, - [SHD_MUSGRAVE_RIDGED_MULTIFRACTAL] = - { - "", - "node_tex_musgrave_ridged_multi_fractal_1d", - "node_tex_musgrave_ridged_multi_fractal_2d", - "node_tex_musgrave_ridged_multi_fractal_3d", - "node_tex_musgrave_ridged_multi_fractal_4d", - }, - [SHD_MUSGRAVE_HETERO_TERRAIN] = - { - "", - "node_tex_musgrave_hetero_terrain_1d", - "node_tex_musgrave_hetero_terrain_2d", - "node_tex_musgrave_hetero_terrain_3d", - "node_tex_musgrave_hetero_terrain_4d", - }, - }; - - BLI_assert(type >= 0 && type < 5); - BLI_assert(dimensions > 0 && dimensions < 5); + const char *name = gpu_shader_name_get(type, dimensions); - return GPU_stack_link(mat, node, names[type][dimensions], in, out); + return GPU_stack_link(mat, node, name, in, out); } static void node_shader_update_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_noise.c b/source/blender/nodes/shader/nodes/node_shader_tex_noise.c deleted file mode 100644 index 7b67c2d1f2e..00000000000 --- a/source/blender/nodes/shader/nodes/node_shader_tex_noise.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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) 2005 Blender Foundation. - * All rights reserved. - */ - -#include "../node_shader_util.h" - -/* **************** NOISE ******************** */ - -static bNodeSocketTemplate sh_node_tex_noise_in[] = { - {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, - {SOCK_FLOAT, N_("W"), 0.0f, 0.0f, 0.0f, 0.0f, -1000.0f, 1000.0f}, - {SOCK_FLOAT, N_("Scale"), 5.0f, 0.0f, 0.0f, 0.0f, -1000.0f, 1000.0f}, - {SOCK_FLOAT, N_("Detail"), 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 16.0f}, - {SOCK_FLOAT, N_("Roughness"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Distortion"), 0.0f, 0.0f, 0.0f, 0.0f, -1000.0f, 1000.0f}, - {-1, ""}, -}; - -static bNodeSocketTemplate sh_node_tex_noise_out[] = { - {SOCK_FLOAT, - N_("Fac"), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f, - PROP_FACTOR, - SOCK_NO_INTERNAL_LINK}, - {SOCK_RGBA, N_("Color"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_NO_INTERNAL_LINK}, - {-1, ""}, -}; - -static void node_shader_init_tex_noise(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeTexNoise *tex = MEM_callocN(sizeof(NodeTexNoise), "NodeTexNoise"); - BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); - BKE_texture_colormapping_default(&tex->base.color_mapping); - tex->dimensions = 3; - - node->storage = tex; -} - -static int node_shader_gpu_tex_noise(GPUMaterial *mat, - bNode *node, - bNodeExecData *UNUSED(execdata), - GPUNodeStack *in, - GPUNodeStack *out) -{ - node_shader_gpu_default_tex_coord(mat, node, &in[0].link); - node_shader_gpu_tex_mapping(mat, node, in, out); - - NodeTexNoise *tex = (NodeTexNoise *)node->storage; - static const char *names[] = { - "", - "node_noise_texture_1d", - "node_noise_texture_2d", - "node_noise_texture_3d", - "node_noise_texture_4d", - }; - return GPU_stack_link(mat, node, names[tex->dimensions], in, out); -} - -static void node_shader_update_tex_noise(bNodeTree *UNUSED(ntree), bNode *node) -{ - bNodeSocket *sockVector = nodeFindSocket(node, SOCK_IN, "Vector"); - bNodeSocket *sockW = nodeFindSocket(node, SOCK_IN, "W"); - - NodeTexNoise *tex = (NodeTexNoise *)node->storage; - nodeSetSocketAvailability(sockVector, tex->dimensions != 1); - nodeSetSocketAvailability(sockW, tex->dimensions == 1 || tex->dimensions == 4); -} - -/* node type definition */ -void register_node_type_sh_tex_noise(void) -{ - static bNodeType ntype; - - sh_node_type_base(&ntype, SH_NODE_TEX_NOISE, "Noise Texture", NODE_CLASS_TEXTURE, 0); - node_type_socket_templates(&ntype, sh_node_tex_noise_in, sh_node_tex_noise_out); - node_type_init(&ntype, node_shader_init_tex_noise); - node_type_storage( - &ntype, "NodeTexNoise", node_free_standard_storage, node_copy_standard_storage); - node_type_gpu(&ntype, node_shader_gpu_tex_noise); - node_type_update(&ntype, node_shader_update_tex_noise); - - nodeRegisterType(&ntype); -} diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc new file mode 100644 index 00000000000..c0deb232b2d --- /dev/null +++ b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc @@ -0,0 +1,264 @@ +/* + * 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) 2005 Blender Foundation. + * All rights reserved. + */ + +#include "../node_shader_util.h" + +#include "BLI_noise.hh" + +/* **************** NOISE ******************** */ + +static bNodeSocketTemplate sh_node_tex_noise_in[] = { + {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, + {SOCK_FLOAT, N_("W"), 0.0f, 0.0f, 0.0f, 0.0f, -1000.0f, 1000.0f}, + {SOCK_FLOAT, N_("Scale"), 5.0f, 0.0f, 0.0f, 0.0f, -1000.0f, 1000.0f}, + {SOCK_FLOAT, N_("Detail"), 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 16.0f}, + {SOCK_FLOAT, N_("Roughness"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, + {SOCK_FLOAT, N_("Distortion"), 0.0f, 0.0f, 0.0f, 0.0f, -1000.0f, 1000.0f}, + {-1, ""}, +}; + +static bNodeSocketTemplate sh_node_tex_noise_out[] = { + {SOCK_FLOAT, + N_("Fac"), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + PROP_FACTOR, + SOCK_NO_INTERNAL_LINK}, + {SOCK_RGBA, N_("Color"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_NO_INTERNAL_LINK}, + {-1, ""}, +}; + +static void node_shader_init_tex_noise(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeTexNoise *tex = (NodeTexNoise *)MEM_callocN(sizeof(NodeTexNoise), "NodeTexNoise"); + BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); + BKE_texture_colormapping_default(&tex->base.color_mapping); + tex->dimensions = 3; + + node->storage = tex; +} + +static const char *gpu_shader_get_name(const int dimensions) +{ + BLI_assert(dimensions >= 1 && dimensions <= 4); + return std::array{"node_noise_texture_1d", + "node_noise_texture_2d", + "node_noise_texture_3d", + "node_noise_texture_4d"}[dimensions - 1]; + return nullptr; +} + +static int node_shader_gpu_tex_noise(GPUMaterial *mat, + bNode *node, + bNodeExecData *UNUSED(execdata), + GPUNodeStack *in, + GPUNodeStack *out) +{ + node_shader_gpu_default_tex_coord(mat, node, &in[0].link); + node_shader_gpu_tex_mapping(mat, node, in, out); + + NodeTexNoise *tex = (NodeTexNoise *)node->storage; + const char *name = gpu_shader_get_name(tex->dimensions); + return GPU_stack_link(mat, node, name, in, out); +} + +static void node_shader_update_tex_noise(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sockVector = nodeFindSocket(node, SOCK_IN, "Vector"); + bNodeSocket *sockW = nodeFindSocket(node, SOCK_IN, "W"); + + NodeTexNoise *tex = (NodeTexNoise *)node->storage; + nodeSetSocketAvailability(sockVector, tex->dimensions != 1); + nodeSetSocketAvailability(sockW, tex->dimensions == 1 || tex->dimensions == 4); +} + +namespace blender::nodes { + +class NoiseFunction : public fn::MultiFunction { + private: + int dimensions_; + + public: + NoiseFunction(int dimensions) : dimensions_(dimensions) + { + BLI_assert(dimensions >= 1 && dimensions <= 4); + static std::array<fn::MFSignature, 4> signatures{ + create_signature(1), + create_signature(2), + create_signature(3), + create_signature(4), + }; + this->set_signature(&signatures[dimensions - 1]); + } + + static fn::MFSignature create_signature(int dimensions) + { + fn::MFSignatureBuilder signature{"Noise"}; + + if (ELEM(dimensions, 2, 3, 4)) { + signature.single_input<float3>("Vector"); + } + if (ELEM(dimensions, 1, 4)) { + signature.single_input<float>("W"); + } + + signature.single_input<float>("Scale"); + signature.single_input<float>("Detail"); + signature.single_input<float>("Roughness"); + signature.single_input<float>("Distortion"); + + signature.single_output<float>("Fac"); + signature.single_output<ColorGeometry4f>("Color"); + + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + int param = ELEM(dimensions_, 2, 3, 4) + ELEM(dimensions_, 1, 4); + const VArray<float> &scale = params.readonly_single_input<float>(param++, "Scale"); + const VArray<float> &detail = params.readonly_single_input<float>(param++, "Detail"); + const VArray<float> &roughness = params.readonly_single_input<float>(param++, "Roughness"); + const VArray<float> &distortion = params.readonly_single_input<float>(param++, "Distortion"); + + MutableSpan<float> r_factor = params.uninitialized_single_output_if_required<float>(param++, + "Fac"); + MutableSpan<ColorGeometry4f> r_color = + params.uninitialized_single_output_if_required<ColorGeometry4f>(param++, "Color"); + + const bool compute_factor = !r_factor.is_empty(); + const bool compute_color = !r_color.is_empty(); + + switch (dimensions_) { + case 1: { + const VArray<float> &w = params.readonly_single_input<float>(0, "W"); + if (compute_factor) { + for (int64_t i : mask) { + const float position = w[i] * scale[i]; + r_factor[i] = noise::perlin_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + } + } + if (compute_color) { + for (int64_t i : mask) { + const float position = w[i] * scale[i]; + const float3 c = noise::perlin_float3_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + r_color[i] = ColorGeometry4f(c[0], c[1], c[2], 1.0f); + } + } + break; + } + case 2: { + const VArray<float3> &vector = params.readonly_single_input<float3>(0, "Vector"); + if (compute_factor) { + for (int64_t i : mask) { + const float2 position = vector[i] * scale[i]; + r_factor[i] = noise::perlin_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + } + } + if (compute_color) { + for (int64_t i : mask) { + const float2 position = vector[i] * scale[i]; + const float3 c = noise::perlin_float3_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + r_color[i] = ColorGeometry4f(c[0], c[1], c[2], 1.0f); + } + } + break; + } + case 3: { + const VArray<float3> &vector = params.readonly_single_input<float3>(0, "Vector"); + if (compute_factor) { + for (int64_t i : mask) { + const float3 position = vector[i] * scale[i]; + r_factor[i] = noise::perlin_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + } + } + if (compute_color) { + for (int64_t i : mask) { + const float3 position = vector[i] * scale[i]; + const float3 c = noise::perlin_float3_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + r_color[i] = ColorGeometry4f(c[0], c[1], c[2], 1.0f); + } + } + break; + } + case 4: { + const VArray<float3> &vector = params.readonly_single_input<float3>(0, "Vector"); + const VArray<float> &w = params.readonly_single_input<float>(1, "W"); + if (compute_factor) { + for (int64_t i : mask) { + const float3 position_vector = vector[i] * scale[i]; + const float position_w = w[i] * scale[i]; + const float4 position{ + position_vector[0], position_vector[1], position_vector[2], position_w}; + r_factor[i] = noise::perlin_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + } + } + if (compute_color) { + for (int64_t i : mask) { + const float3 position_vector = vector[i] * scale[i]; + const float position_w = w[i] * scale[i]; + const float4 position{ + position_vector[0], position_vector[1], position_vector[2], position_w}; + const float3 c = noise::perlin_float3_fractal_distorted( + position, detail[i], roughness[i], distortion[i]); + r_color[i] = ColorGeometry4f(c[0], c[1], c[2], 1.0f); + } + } + break; + } + } + } +}; + +static void sh_node_noise_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder) +{ + bNode &node = builder.node(); + NodeTexNoise *tex = (NodeTexNoise *)node.storage; + builder.construct_and_set_matching_fn<NoiseFunction>(tex->dimensions); +} + +} // namespace blender::nodes + +/* node type definition */ +void register_node_type_sh_tex_noise(void) +{ + static bNodeType ntype; + + sh_fn_node_type_base(&ntype, SH_NODE_TEX_NOISE, "Noise Texture", NODE_CLASS_TEXTURE, 0); + node_type_socket_templates(&ntype, sh_node_tex_noise_in, sh_node_tex_noise_out); + node_type_init(&ntype, node_shader_init_tex_noise); + node_type_storage( + &ntype, "NodeTexNoise", node_free_standard_storage, node_copy_standard_storage); + node_type_gpu(&ntype, node_shader_gpu_tex_noise); + node_type_update(&ntype, node_shader_update_tex_noise); + ntype.build_multi_function = blender::nodes::sh_node_noise_build_multi_function; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.c b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc index 64dc44fc67d..1cc715d99ea 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc @@ -69,7 +69,7 @@ static bNodeSocketTemplate sh_node_tex_voronoi_out[] = { static void node_shader_init_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTexVoronoi *tex = MEM_callocN(sizeof(NodeTexVoronoi), "NodeTexVoronoi"); + NodeTexVoronoi *tex = (NodeTexVoronoi *)MEM_callocN(sizeof(NodeTexVoronoi), "NodeTexVoronoi"); BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); BKE_texture_colormapping_default(&tex->base.color_mapping); tex->dimensions = 3; @@ -79,6 +79,51 @@ static void node_shader_init_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node) node->storage = tex; } +static const char *gpu_shader_get_name(const int feature, const int dimensions) +{ + BLI_assert(feature >= 0 && feature < 5); + BLI_assert(dimensions > 0 && dimensions < 5); + + switch (feature) { + case SHD_VORONOI_F1: + return std::array{ + "node_tex_voronoi_f1_1d", + "node_tex_voronoi_f1_2d", + "node_tex_voronoi_f1_3d", + "node_tex_voronoi_f1_4d", + }[dimensions - 1]; + case SHD_VORONOI_F2: + return std::array{ + "node_tex_voronoi_f2_1d", + "node_tex_voronoi_f2_2d", + "node_tex_voronoi_f2_3d", + "node_tex_voronoi_f2_4d", + }[dimensions - 1]; + case SHD_VORONOI_SMOOTH_F1: + return std::array{ + "node_tex_voronoi_smooth_f1_1d", + "node_tex_voronoi_smooth_f1_2d", + "node_tex_voronoi_smooth_f1_3d", + "node_tex_voronoi_smooth_f1_4d", + }[dimensions - 1]; + case SHD_VORONOI_DISTANCE_TO_EDGE: + return std::array{ + "node_tex_voronoi_distance_to_edge_1d", + "node_tex_voronoi_distance_to_edge_2d", + "node_tex_voronoi_distance_to_edge_3d", + "node_tex_voronoi_distance_to_edge_4d", + }[dimensions - 1]; + case SHD_VORONOI_N_SPHERE_RADIUS: + return std::array{ + "node_tex_voronoi_n_sphere_radius_1d", + "node_tex_voronoi_n_sphere_radius_2d", + "node_tex_voronoi_n_sphere_radius_3d", + "node_tex_voronoi_n_sphere_radius_4d", + }[dimensions - 1]; + } + return nullptr; +} + static int node_shader_gpu_tex_voronoi(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), @@ -88,57 +133,12 @@ static int node_shader_gpu_tex_voronoi(GPUMaterial *mat, node_shader_gpu_default_tex_coord(mat, node, &in[0].link); node_shader_gpu_tex_mapping(mat, node, in, out); - static const char *names[][5] = { - [SHD_VORONOI_F1] = - { - "", - "node_tex_voronoi_f1_1d", - "node_tex_voronoi_f1_2d", - "node_tex_voronoi_f1_3d", - "node_tex_voronoi_f1_4d", - }, - [SHD_VORONOI_F2] = - { - "", - "node_tex_voronoi_f2_1d", - "node_tex_voronoi_f2_2d", - "node_tex_voronoi_f2_3d", - "node_tex_voronoi_f2_4d", - }, - [SHD_VORONOI_SMOOTH_F1] = - { - "", - "node_tex_voronoi_smooth_f1_1d", - "node_tex_voronoi_smooth_f1_2d", - "node_tex_voronoi_smooth_f1_3d", - "node_tex_voronoi_smooth_f1_4d", - }, - [SHD_VORONOI_DISTANCE_TO_EDGE] = - { - "", - "node_tex_voronoi_distance_to_edge_1d", - "node_tex_voronoi_distance_to_edge_2d", - "node_tex_voronoi_distance_to_edge_3d", - "node_tex_voronoi_distance_to_edge_4d", - }, - [SHD_VORONOI_N_SPHERE_RADIUS] = - { - "", - "node_tex_voronoi_n_sphere_radius_1d", - "node_tex_voronoi_n_sphere_radius_2d", - "node_tex_voronoi_n_sphere_radius_3d", - "node_tex_voronoi_n_sphere_radius_4d", - }, - }; - NodeTexVoronoi *tex = (NodeTexVoronoi *)node->storage; float metric = tex->distance; - BLI_assert(tex->feature >= 0 && tex->feature < 5); - BLI_assert(tex->dimensions > 0 && tex->dimensions < 5); + const char *name = gpu_shader_get_name(tex->feature, tex->dimensions); - return GPU_stack_link( - mat, node, names[tex->feature][tex->dimensions], in, out, GPU_constant(&metric)); + return GPU_stack_link(mat, node, name, in, out, GPU_constant(&metric)); } static void node_shader_update_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.c b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc index 60a3392c761..6e973189065 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc @@ -37,25 +37,23 @@ static void node_shader_init_tex_white_noise(bNodeTree *UNUSED(ntree), bNode *no node->custom1 = 3; } +static const char *gpu_shader_get_name(const int dimensions) +{ + BLI_assert(dimensions >= 1 && dimensions <= 4); + return std::array{"node_white_noise_1d", + "node_white_noise_2d", + "node_white_noise_3d", + "node_white_noise_4d"}[dimensions - 1]; +} + static int gpu_shader_tex_white_noise(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), GPUNodeStack *in, GPUNodeStack *out) { - static const char *names[] = { - "", - "node_white_noise_1d", - "node_white_noise_2d", - "node_white_noise_3d", - "node_white_noise_4d", - }; - - if (node->custom1 < ARRAY_SIZE(names) && names[node->custom1]) { - return GPU_stack_link(mat, node, names[node->custom1], in, out); - } - - return 0; + const char *name = gpu_shader_get_name(node->custom1); + return GPU_stack_link(mat, node, name, in, out); } static void node_shader_update_tex_white_noise(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/python/generic/bpy_threads.c b/source/blender/python/generic/bpy_threads.c index bd707f728a1..8aa8c5c5d92 100644 --- a/source/blender/python/generic/bpy_threads.c +++ b/source/blender/python/generic/bpy_threads.c @@ -29,8 +29,12 @@ /* analogue of PyEval_SaveThread() */ BPy_ThreadStatePtr BPY_thread_save(void) { - /* The thread-state can be NULL when quitting Blender. */ - if (_PyThreadState_UncheckedGet()) { + /* Use `_PyThreadState_UncheckedGet()` instead of `PyThreadState_Get()`, to avoid a fatal error + * issued when a thread state is NULL (the thread state can be NULL when quitting Blender). + * + * `PyEval_SaveThread()` will release the GIL, so this thread has to have the GIL to begin with + * or badness will ensue. */ + if (_PyThreadState_UncheckedGet() && PyGILState_Check()) { return (BPy_ThreadStatePtr)PyEval_SaveThread(); } return NULL; diff --git a/source/blender/python/generic/idprop_py_api.c b/source/blender/python/generic/idprop_py_api.c index 4296474c011..8dc0d2fb857 100644 --- a/source/blender/python/generic/idprop_py_api.c +++ b/source/blender/python/generic/idprop_py_api.c @@ -533,6 +533,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) for (i = 0; i < val.array.len; i++) { item = ob_seq_fast_items[i]; if (((prop_data[i] = PyFloat_AsDouble(item)) == -1.0) && PyErr_Occurred()) { + IDP_FreeProperty(prop); return NULL; } } @@ -545,6 +546,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) for (i = 0; i < val.array.len; i++) { item = ob_seq_fast_items[i]; if (((prop_data[i] = PyC_Long_AsI32(item)) == -1) && PyErr_Occurred()) { + IDP_FreeProperty(prop); return NULL; } } @@ -555,6 +557,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) for (i = 0; i < val.array.len; i++) { item = ob_seq_fast_items[i]; if (BPy_IDProperty_Map_ValidateAndCreate(NULL, prop, item) == false) { + IDP_FreeProperty(prop); return NULL; } } diff --git a/source/blender/python/generic/idprop_py_ui_api.c b/source/blender/python/generic/idprop_py_ui_api.c index 42856d88472..7827bd48dfe 100644 --- a/source/blender/python/generic/idprop_py_ui_api.c +++ b/source/blender/python/generic/idprop_py_ui_api.c @@ -468,7 +468,7 @@ static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict) Py_DECREF(list); } else { - PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->step)); + PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->default_value)); Py_DECREF(item); } } @@ -499,7 +499,7 @@ static void idprop_ui_data_to_dict_float(IDProperty *property, PyObject *dict) Py_DECREF(list); } else { - PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->step)); + PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->default_value)); Py_DECREF(item); } } diff --git a/source/blender/python/gpu/gpu_py_buffer.c b/source/blender/python/gpu/gpu_py_buffer.c index 0fef59d6352..abfde7b48c8 100644 --- a/source/blender/python/gpu/gpu_py_buffer.c +++ b/source/blender/python/gpu/gpu_py_buffer.c @@ -485,7 +485,7 @@ static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, int i, PyObject *v) case GPU_DATA_UINT: case GPU_DATA_UINT_24_8: case GPU_DATA_10_11_11_REV: - return PyArg_Parse(v, "b:Expected ints", &self->buf.as_uint[i]) ? 0 : -1; + return PyArg_Parse(v, "I:Expected unsigned ints", &self->buf.as_uint[i]) ? 0 : -1; default: return 0; /* should never happen */ } diff --git a/source/blender/python/gpu/gpu_py_shader.c b/source/blender/python/gpu/gpu_py_shader.c index 95e505b1343..1bdf9766c1b 100644 --- a/source/blender/python/gpu/gpu_py_shader.c +++ b/source/blender/python/gpu/gpu_py_shader.c @@ -105,12 +105,13 @@ static PyObject *pygpu_shader__tp_new(PyTypeObject *UNUSED(type), PyObject *args const char *geocode; const char *libcode; const char *defines; + const char *name; } params = {0}; static const char *_keywords[] = { - "vertexcode", "fragcode", "geocode", "libcode", "defines", NULL}; + "vertexcode", "fragcode", "geocode", "libcode", "defines", "name", NULL}; - static _PyArg_Parser _parser = {"ss|$sss:GPUShader.__new__", _keywords, 0}; + static _PyArg_Parser _parser = {"ss|$ssss:GPUShader.__new__", _keywords, 0}; if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, @@ -118,12 +119,17 @@ static PyObject *pygpu_shader__tp_new(PyTypeObject *UNUSED(type), PyObject *args ¶ms.fragcode, ¶ms.geocode, ¶ms.libcode, - ¶ms.defines)) { + ¶ms.defines, + ¶ms.name)) { return NULL; } - GPUShader *shader = GPU_shader_create_from_python( - params.vertexcode, params.fragcode, params.geocode, params.libcode, params.defines); + GPUShader *shader = GPU_shader_create_from_python(params.vertexcode, + params.fragcode, + params.geocode, + params.libcode, + params.defines, + params.name); if (shader == NULL) { PyErr_SetString(PyExc_Exception, "Shader Compile Error, see console for more details"); @@ -639,6 +645,13 @@ static struct PyMethodDef pygpu_shader__tp_methods[] = { {NULL, NULL, 0, NULL}, }; +PyDoc_STRVAR(pygpu_shader_name_doc, + "The name of the shader object for debugging purposes (read-only).\n\n:type: str"); +static PyObject *pygpu_shader_name(BPyGPUShader *self) +{ + return PyUnicode_FromString(GPU_shader_get_name(self->shader)); +} + PyDoc_STRVAR( pygpu_shader_program_doc, "The name of the program object for use by the OpenGL API (read-only).\n\n:type: int"); @@ -649,6 +662,7 @@ static PyObject *pygpu_shader_program_get(BPyGPUShader *self, void *UNUSED(closu static PyGetSetDef pygpu_shader__tp_getseters[] = { {"program", (getter)pygpu_shader_program_get, (setter)NULL, pygpu_shader_program_doc, NULL}, + {"name", (getter)pygpu_shader_name, (setter)NULL, pygpu_shader_name_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; @@ -662,7 +676,8 @@ static void pygpu_shader__tp_dealloc(BPyGPUShader *self) PyDoc_STRVAR( pygpu_shader__tp_doc, - ".. class:: GPUShader(vertexcode, fragcode, geocode=None, libcode=None, defines=None)\n" + ".. class:: GPUShader(vertexcode, fragcode, geocode=None, libcode=None, defines=None, " + "name='pyGPUShader')\n" "\n" " GPUShader combines multiple GLSL shaders into a program used for drawing.\n" " It must contain at least a vertex and fragment shaders.\n" @@ -688,6 +703,8 @@ PyDoc_STRVAR( " :param libcode: Code with functions and presets to be shared between shaders.\n" " :type value: str\n" " :param defines: Preprocessor directives.\n" + " :type value: str\n" + " :param name: Name of shader code, for debugging purposes.\n" " :type value: str\n"); PyTypeObject BPyGPUShader_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUShader", diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 68731a91dc9..7a93a076621 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -753,7 +753,7 @@ int BPY_context_member_get(bContext *C, const char *member, bContextDataResult * CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not a valid type", member); } else { - CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found\n", member); + CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found", member); } } else { diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c index c29ab342ed7..6cb6aabe885 100644 --- a/source/blender/render/intern/render_result.c +++ b/source/blender/render/intern/render_result.c @@ -250,6 +250,9 @@ 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; + return rpass; } diff --git a/source/blender/sequencer/SEQ_add.h b/source/blender/sequencer/SEQ_add.h index 4025f1a4a04..d2a731d9953 100644 --- a/source/blender/sequencer/SEQ_add.h +++ b/source/blender/sequencer/SEQ_add.h @@ -88,7 +88,7 @@ struct Sequence *SEQ_add_movie_strip(struct Main *bmain, struct Scene *scene, struct ListBase *seqbase, struct SeqLoadData *load_data, - double *r_video_start_offset); + double *r_start_offset); struct Sequence *SEQ_add_scene_strip(struct Scene *scene, struct ListBase *seqbase, struct SeqLoadData *load_data); diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 80314d34360..4448db013fe 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -3188,7 +3188,7 @@ float seq_speed_effect_target_frame_get(Scene *scene, case SEQ_SPEED_STRETCH: { /* Only right handle controls effect speed! */ const float target_content_length = seq_effect_speed_get_strip_content_length(source) - - source->startofs; + source->startofs; const float speed_effetct_length = seq_speed->enddisp - seq_speed->startdisp; const float ratio = frame_index / speed_effetct_length; target_frame = target_content_length * ratio; diff --git a/source/blender/sequencer/intern/strip_add.c b/source/blender/sequencer/intern/strip_add.c index 9081c655d2f..3cf7a4ebf4d 100644 --- a/source/blender/sequencer/intern/strip_add.c +++ b/source/blender/sequencer/intern/strip_add.c @@ -403,26 +403,8 @@ Sequence *SEQ_add_sound_strip(Main *bmain, return NULL; } - /* If this sound it part of a video, then the sound might start after the video. - * In this case we need to then offset the start frame of the audio so it syncs up - * properly with the video. - */ - int start_frame_offset = info.start_offset * FPS; - double start_frame_offset_remainer = (info.start_offset * FPS - start_frame_offset) / FPS; - - if (start_frame_offset_remainer > FLT_EPSILON) { - /* We can't represent a fraction of a frame, so skip the first frame fraction of sound so we - * start on a "whole" frame. - */ - start_frame_offset++; - } - - sound->offset_time += start_frame_offset_remainer; - - Sequence *seq = SEQ_sequence_alloc(seqbase, - load_data->start_frame + start_frame_offset, - load_data->channel, - SEQ_TYPE_SOUND_RAM); + Sequence *seq = SEQ_sequence_alloc( + seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_SOUND_RAM); seq->sound = sound; seq->scene_sound = NULL; @@ -508,7 +490,7 @@ Sequence *SEQ_add_movie_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqLoadData *load_data, - double *r_video_start_offset) + double *r_start_offset) { char path[sizeof(load_data->path)]; BLI_strncpy(path, load_data->path, sizeof(path)); @@ -554,8 +536,40 @@ Sequence *SEQ_add_movie_strip(Main *bmain, return NULL; } + int video_frame_offset = 0; + float video_fps = 0.0f; + + if (anim_arr[0] != NULL) { + short fps_denom; + float fps_num; + + IMB_anim_get_fps(anim_arr[0], &fps_denom, &fps_num, true); + + video_fps = fps_denom / fps_num; + + /* Adjust scene's frame rate settings to match. */ + if (load_data->flags & SEQ_LOAD_MOVIE_SYNC_FPS) { + scene->r.frs_sec = fps_denom; + scene->r.frs_sec_base = fps_num; + } + + double video_start_offset = IMD_anim_get_offset(anim_arr[0]); + int minimum_frame_offset; + + if (*r_start_offset >= 0) { + minimum_frame_offset = MIN2(video_start_offset, *r_start_offset) * FPS; + } + else { + minimum_frame_offset = video_start_offset * FPS; + } + + video_frame_offset = video_start_offset * FPS - minimum_frame_offset; + + *r_start_offset = video_start_offset; + } + Sequence *seq = SEQ_sequence_alloc( - seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_MOVIE); + seqbase, load_data->start_frame + video_frame_offset, load_data->channel, SEQ_TYPE_MOVIE); /* Multiview settings. */ if (load_data->use_multiview) { @@ -579,27 +593,11 @@ Sequence *SEQ_add_movie_strip(Main *bmain, seq->blend_mode = SEQ_TYPE_CROSS; /* so alpha adjustment fade to the strip below */ - float video_fps = 0.0f; - if (anim_arr[0] != NULL) { seq->len = IMB_anim_get_duration(anim_arr[0], IMB_TC_RECORD_RUN); - *r_video_start_offset = IMD_anim_get_offset(anim_arr[0]); IMB_anim_load_metadata(anim_arr[0]); - short fps_denom; - float fps_num; - - IMB_anim_get_fps(anim_arr[0], &fps_denom, &fps_num, true); - - video_fps = fps_denom / fps_num; - - /* Adjust scene's frame rate settings to match. */ - if (load_data->flags & SEQ_LOAD_MOVIE_SYNC_FPS) { - scene->r.frs_sec = fps_denom; - scene->r.frs_sec_base = fps_num; - } - /* Set initial scale based on load_data->fit_method. */ orig_width = IMB_anim_get_image_width(anim_arr[0]); orig_height = IMB_anim_get_image_height(anim_arr[0]); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 7ecbcad886d..189a231616e 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -472,6 +472,12 @@ int WM_operator_call_py(struct bContext *C, struct ReportList *reports, const bool is_undo); +void WM_operator_name_call_ptr_with_depends_on_cursor(struct bContext *C, + wmOperatorType *ot, + short opcontext, + PointerRNA *properties, + const char *drawstr); + /* Used for keymap and macro items. */ void WM_operator_properties_alloc(struct PointerRNA **ptr, struct IDProperty **properties, diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 8a1ff67b37c..df6dc3af3cb 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -184,6 +184,17 @@ enum { OPTYPE_LOCK_BYPASS = (1 << 9), /** Special type of undo which doesn't store itself multiple times. */ OPTYPE_UNDO_GROUPED = (1 << 10), + + /** + * Depends on the cursor location, when activated from a menu wait for mouse press. + * + * In practice these operators often end up being accessed: + * - Directly from key bindings. + * - As tools in the toolbar. + * + * Even so, accessing from the menu should behave usefully. + */ + OPTYPE_DEPENDS_ON_CURSOR = (1 << 11), }; /** For #WM_cursor_grab_enable wrap axis. */ @@ -228,16 +239,16 @@ typedef enum eOperatorPropTags { #define KM_CTRL 2 #define KM_ALT 4 #define KM_OSKEY 8 -/* means modifier should be pressed 2nd */ -#define KM_SHIFT2 16 -#define KM_CTRL2 32 -#define KM_ALT2 64 -#define KM_OSKEY2 128 + +/* Used for key-map item creation function arguments (never stored in DNA). */ +#define KM_SHIFT_ANY 16 +#define KM_CTRL_ANY 32 +#define KM_ALT_ANY 64 +#define KM_OSKEY_ANY 128 /* KM_MOD_ flags for `wmKeyMapItem` and `wmEvent.alt/shift/oskey/ctrl`. */ /* note that KM_ANY and KM_NOTHING are used with these defines too */ -#define KM_MOD_FIRST 1 -#define KM_MOD_SECOND 2 +#define KM_MOD_HELD 1 /* type: defined in wm_event_types.c */ #define KM_TEXTINPUT -2 @@ -925,7 +936,7 @@ typedef struct wmDragID { } wmDragID; typedef struct wmDragAsset { - /* Note: Can't store the AssetHandle here, since the FileDirEntry it wraps may be freed while + /* NOTE: Can't store the #AssetHandle here, since the #FileDirEntry it wraps may be freed while * dragging. So store necessary data here directly. */ char name[64]; /* MAX_NAME */ diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_types.h b/source/blender/windowmanager/gizmo/WM_gizmo_types.h index eab62ffce4c..a1edc4196dc 100644 --- a/source/blender/windowmanager/gizmo/WM_gizmo_types.h +++ b/source/blender/windowmanager/gizmo/WM_gizmo_types.h @@ -115,8 +115,15 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { WM_GIZMOGROUPTYPE_SELECT = (1 << 3), /** The gizmo group is to be kept (not removed on loading a new file for eg). */ WM_GIZMOGROUPTYPE_PERSISTENT = (1 << 4), - /** Show all other gizmos when interacting. */ + /** + * Show all other gizmos when interacting. + * Also show this group when another group is being interacted with. + */ WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL = (1 << 5), + + /** Don't draw this gizmo group when it is modal. */ + WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE = (1 << 6), + /** * When used with tool, only run when activating the tool, * instead of linking the gizmo while the tool is active. @@ -127,7 +134,7 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { * when a tool can activate multiple operators based on the key-map. * We could even move the options into the key-map item. * ~ campbell. */ - WM_GIZMOGROUPTYPE_TOOL_INIT = (1 << 6), + WM_GIZMOGROUPTYPE_TOOL_INIT = (1 << 7), /** * This gizmo type supports using the fallback tools keymap. @@ -135,7 +142,7 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { * * Often useful in combination with #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK */ - WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP = (1 << 7), + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP = (1 << 8), /** * Use this from a gizmos refresh callback so we can postpone the refresh operation @@ -146,14 +153,14 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { * for selection operations. This means gizmos that use this check don't interfere * with click drag events by popping up under the cursor and catching the tweak event. */ - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK = (1 << 8), + WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK = (1 << 9), /** * Cause continuous redraws, i.e. set the region redraw flag on every main loop iteration. This * should really be avoided by using proper region redraw tagging, notifiers and the message-bus, * however for VR it's sometimes needed. */ - WM_GIZMOGROUPTYPE_VR_REDRAWS = (1 << 9), + WM_GIZMOGROUPTYPE_VR_REDRAWS = (1 << 10), } eWM_GizmoFlagGroupTypeFlag; /** diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c index 6f6a2402d89..295196c701b 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c @@ -381,31 +381,31 @@ static void gizmomap_prepare_drawing(wmGizmoMap *gzmap, wmGizmo *gz_modal = gzmap->gzmap_context.modal; - /* only active gizmo needs updating */ - if (gz_modal) { - if ((gz_modal->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL) == 0) { - if ((gz_modal->parent_gzgroup->hide.any == 0) && - wm_gizmogroup_is_visible_in_drawstep(gz_modal->parent_gzgroup, drawstep)) { - if (gizmo_prepare_drawing(gzmap, gz_modal, C, draw_gizmos, drawstep)) { - gzmap->update_flag[drawstep] &= ~GIZMOMAP_IS_PREPARE_DRAW; - } - } - /* don't draw any other gizmos */ - return; - } - } - /* Allow refresh functions to ask to be refreshed again, clear before the loop below. */ const bool do_refresh = gzmap->update_flag[drawstep] & GIZMOMAP_IS_REFRESH_CALLBACK; gzmap->update_flag[drawstep] &= ~GIZMOMAP_IS_REFRESH_CALLBACK; LISTBASE_FOREACH (wmGizmoGroup *, gzgroup, &gzmap->groups) { /* check group visibility - drawstep first to avoid unnecessary call of group poll callback */ - if (!wm_gizmogroup_is_visible_in_drawstep(gzgroup, drawstep) || - !WM_gizmo_group_type_poll(C, gzgroup->type)) { + if (!wm_gizmogroup_is_visible_in_drawstep(gzgroup, drawstep)) { continue; } + if (gz_modal && (gzgroup == gz_modal->parent_gzgroup)) { + if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE) { + continue; + } + } + else { /* Don't poll modal gizmo since some poll functions unlink. */ + if (!WM_gizmo_group_type_poll(C, gzgroup->type)) { + continue; + } + /* When modal only show other gizmo groups tagged with #WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL. */ + if (gz_modal && ((gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL) == 0)) { + continue; + } + } + /* Needs to be initialized on first draw. */ /* XXX weak: Gizmo-group may skip refreshing if it's invisible * (map gets untagged nevertheless). */ diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index e11ef52eb84..0b7d5e5f1f4 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -266,8 +266,7 @@ IDTypeInfo IDType_ID_WM = { .name = "WindowManager", .name_plural = "window_managers", .translation_context = BLT_I18NCONTEXT_ID_WINDOWMANAGER, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL | - IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/windowmanager/intern/wm_cursors.c b/source/blender/windowmanager/intern/wm_cursors.c index 50d3a856cbe..9c020b16234 100644 --- a/source/blender/windowmanager/intern/wm_cursors.c +++ b/source/blender/windowmanager/intern/wm_cursors.c @@ -1146,5 +1146,31 @@ void wm_init_cursor_data(void) BlenderCursor[WM_CURSOR_ZOOM_OUT] = &ZoomOutCursor; END_CURSOR_BLOCK; + /********************** Area Pick Cursor ***********************/ + BEGIN_CURSOR_BLOCK; + + static char pick_area_bitmap[] = { + 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0xbf, 0x00, 0x81, 0x00, 0x81, + 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x80, 0x00, 0xff, + }; + + static char pick_area_mask[] = { + 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0xff, 0x01, 0xff, 0x01, 0xff, + 0x01, 0x38, 0x00, 0xb8, 0x7f, 0xb8, 0xff, 0x80, 0xc1, 0x80, 0xc1, + 0x80, 0xc1, 0x80, 0xc1, 0x80, 0xc1, 0x80, 0xff, 0x00, 0xff, + }; + + static BCursor PickAreaCursor = { + pick_area_bitmap, + pick_area_mask, + 4, + 4, + false, + }; + + BlenderCursor[WM_CURSOR_PICK_AREA] = &PickAreaCursor; + END_CURSOR_BLOCK; + /********************** Put the cursors in the array ***********************/ } diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 1f47b152a2b..ae09786356a 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -1673,6 +1673,172 @@ int WM_operator_call_py(bContext *C, /** \} */ /* -------------------------------------------------------------------- */ +/** \name Operator Wait For Input + * + * Delay executing operators that depend on cursor location. + * + * See: #OPTYPE_DEPENDS_ON_CURSOR doc-string for more information. + * \{ */ + +typedef struct uiOperatorWaitForInput { + ScrArea *area; + wmOperatorCallParams optype_params; + bContextStore *context; +} uiOperatorWaitForInput; + +static void ui_handler_wait_for_input_remove(bContext *C, void *userdata) +{ + uiOperatorWaitForInput *opwait = userdata; + if (opwait->optype_params.opptr) { + if (opwait->optype_params.opptr->data) { + IDP_FreeProperty(opwait->optype_params.opptr->data); + } + MEM_freeN(opwait->optype_params.opptr); + } + if (opwait->context) { + CTX_store_free(opwait->context); + } + + if (opwait->area != NULL) { + ED_area_status_text(opwait->area, NULL); + } + else { + ED_workspace_status_text(C, NULL); + } + + MEM_freeN(opwait); +} + +static int ui_handler_wait_for_input(bContext *C, const wmEvent *event, void *userdata) +{ + uiOperatorWaitForInput *opwait = userdata; + enum { CONTINUE = 0, EXECUTE, CANCEL } state = CONTINUE; + state = CONTINUE; + + switch (event->type) { + case LEFTMOUSE: { + if (event->val == KM_PRESS) { + state = EXECUTE; + } + break; + } + /* Useful if the operator isn't convenient to access while the mouse button is held. + * If it takes numeric input for example. */ + case EVT_SPACEKEY: + case EVT_RETKEY: { + if (event->val == KM_PRESS) { + state = EXECUTE; + } + break; + } + case RIGHTMOUSE: { + if (event->val == KM_PRESS) { + state = CANCEL; + } + break; + } + case EVT_ESCKEY: { + if (event->val == KM_PRESS) { + state = CANCEL; + } + break; + } + } + + if (state != CONTINUE) { + wmWindow *win = CTX_wm_window(C); + WM_cursor_modal_restore(win); + + if (state == EXECUTE) { + CTX_store_set(C, opwait->context); + WM_operator_name_call_ptr(C, + opwait->optype_params.optype, + opwait->optype_params.opcontext, + opwait->optype_params.opptr); + CTX_store_set(C, NULL); + } + + WM_event_remove_ui_handler(&win->modalhandlers, + ui_handler_wait_for_input, + ui_handler_wait_for_input_remove, + opwait, + false); + + ui_handler_wait_for_input_remove(C, opwait); + + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +void WM_operator_name_call_ptr_with_depends_on_cursor( + bContext *C, wmOperatorType *ot, short opcontext, PointerRNA *properties, const char *drawstr) +{ + int flag = ot->flag; + + LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &ot->macro) { + wmOperatorType *otm = WM_operatortype_find(macro->idname, 0); + if (otm != NULL) { + flag |= otm->flag; + } + } + + if ((flag & OPTYPE_DEPENDS_ON_CURSOR) == 0) { + WM_operator_name_call_ptr(C, ot, opcontext, properties); + return; + } + + wmWindow *win = CTX_wm_window(C); + ScrArea *area = CTX_wm_area(C); + + { + char header_text[UI_MAX_DRAW_STR]; + SNPRINTF(header_text, + "%s %s", + IFACE_("Input pending "), + (drawstr && drawstr[0]) ? drawstr : CTX_IFACE_(ot->translation_context, ot->name)); + if (area != NULL) { + ED_area_status_text(area, header_text); + } + else { + ED_workspace_status_text(C, header_text); + } + } + + WM_cursor_modal_set(win, WM_CURSOR_PICK_AREA); + + uiOperatorWaitForInput *opwait = MEM_callocN(sizeof(*opwait), __func__); + opwait->optype_params.optype = ot; + opwait->optype_params.opcontext = opcontext; + opwait->optype_params.opptr = properties; + + opwait->area = area; + + if (properties) { + opwait->optype_params.opptr = MEM_mallocN(sizeof(*opwait->optype_params.opptr), __func__); + *opwait->optype_params.opptr = *properties; + if (properties->data != NULL) { + opwait->optype_params.opptr->data = IDP_CopyProperty(properties->data); + } + } + + bContextStore *store = CTX_store_get(C); + if (store) { + opwait->context = CTX_store_copy(store); + } + + WM_event_add_ui_handler(C, + &win->modalhandlers, + ui_handler_wait_for_input, + ui_handler_wait_for_input_remove, + opwait, + WM_HANDLER_BLOCKING); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Handler Types * * General API for different handler types. @@ -3749,7 +3915,7 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm, const char *keymap_id = NULL; /* Support for the gizmo owning the tool keymap. */ - if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\n') { + if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\0') { wmGizmoMap *gzmap = NULL; wmGizmoGroup *gzgroup = NULL; LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { @@ -4692,47 +4858,27 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void case EVT_LEFTSHIFTKEY: case EVT_RIGHTSHIFTKEY: if (event.val == KM_PRESS) { - if (event_state->ctrl || event_state->alt || event_state->oskey) { - keymodifier = (KM_MOD_FIRST | KM_MOD_SECOND); - } - else { - keymodifier = KM_MOD_FIRST; - } + keymodifier = KM_MOD_HELD; } event.shift = event_state->shift = keymodifier; break; case EVT_LEFTCTRLKEY: case EVT_RIGHTCTRLKEY: if (event.val == KM_PRESS) { - if (event_state->shift || event_state->alt || event_state->oskey) { - keymodifier = (KM_MOD_FIRST | KM_MOD_SECOND); - } - else { - keymodifier = KM_MOD_FIRST; - } + keymodifier = KM_MOD_HELD; } event.ctrl = event_state->ctrl = keymodifier; break; case EVT_LEFTALTKEY: case EVT_RIGHTALTKEY: if (event.val == KM_PRESS) { - if (event_state->ctrl || event_state->shift || event_state->oskey) { - keymodifier = (KM_MOD_FIRST | KM_MOD_SECOND); - } - else { - keymodifier = KM_MOD_FIRST; - } + keymodifier = KM_MOD_HELD; } event.alt = event_state->alt = keymodifier; break; case EVT_OSKEY: if (event.val == KM_PRESS) { - if (event_state->ctrl || event_state->alt || event_state->shift) { - keymodifier = (KM_MOD_FIRST | KM_MOD_SECOND); - } - else { - keymodifier = KM_MOD_FIRST; - } + keymodifier = KM_MOD_HELD; } event.oskey = event_state->oskey = keymodifier; break; diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index f83511e76f0..2ce2bcc2f3c 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -1515,14 +1515,68 @@ static void wm_history_file_update(void) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Save Main Blend-File (internal) +/** \name Save Main Blend-File (internal) Screen-Shot + * + * Screen-shot the active window. + * \{ */ + +static ImBuf *blend_file_thumb_from_screenshot(bContext *C, BlendThumbnail **thumb_pt) +{ + if (*thumb_pt) { + /* We are given a valid thumbnail data, so just generate image from it. */ + return BKE_main_thumbnail_to_imbuf(NULL, *thumb_pt); + } + + /* The window to capture should be a main window (without parent). */ + wmWindow *win = CTX_wm_window(C); + while (win && win->parent) { + win = win->parent; + } + + int win_size[2]; + uint *buffer = WM_window_pixels_read(CTX_wm_manager(C), win, win_size); + ImBuf *ibuf = IMB_allocFromBufferOwn(buffer, NULL, win_size[0], win_size[1], 24); + + if (ibuf) { + int ex, ey; + if (ibuf->x > ibuf->y) { + ex = BLEN_THUMB_SIZE; + ey = max_ii(1, (int)(((float)ibuf->y / (float)ibuf->x) * BLEN_THUMB_SIZE)); + } + else { + ex = max_ii(1, (int)(((float)ibuf->x / (float)ibuf->y) * BLEN_THUMB_SIZE)); + ey = BLEN_THUMB_SIZE; + } + + /* File-system thumbnail image can be 256x256. */ + IMB_scaleImBuf(ibuf, ex * 2, ey * 2); + + /* Thumbnail inside blend should be 128x128. */ + ImBuf *thumb_ibuf = IMB_dupImBuf(ibuf); + IMB_scaleImBuf(thumb_ibuf, ex, ey); + + BlendThumbnail *thumb = BKE_main_thumbnail_from_imbuf(NULL, thumb_ibuf); + IMB_freeImBuf(thumb_ibuf); + *thumb_pt = thumb; + } + + /* Must be freed by caller. */ + return ibuf; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Save Main Blend-File (internal) Camera View + * + * Render the current scene with the active camera. * \{ */ /* screen can be NULL */ -static ImBuf *blend_file_thumb(const bContext *C, - Scene *scene, - bScreen *screen, - BlendThumbnail **thumb_pt) +static ImBuf *blend_file_thumb_from_camera(const bContext *C, + Scene *scene, + bScreen *screen, + BlendThumbnail **thumb_pt) { /* will be scaled down, but gives some nice oversampling */ ImBuf *ibuf; @@ -1548,10 +1602,9 @@ static ImBuf *blend_file_thumb(const bContext *C, return NULL; } - if ((scene->camera == NULL) && (screen != NULL)) { + if (screen != NULL) { area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0); - region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - if (region) { + if (area) { v3d = area->spacedata.first; } } @@ -1570,13 +1623,14 @@ static ImBuf *blend_file_thumb(const bContext *C, if (scene->camera) { ibuf = ED_view3d_draw_offscreen_imbuf_simple(depsgraph, scene, - NULL, - OB_SOLID, + (v3d) ? &v3d->shading : NULL, + (v3d) ? v3d->shading.type : OB_SOLID, scene->camera, - BLEN_THUMB_SIZE * 2, - BLEN_THUMB_SIZE * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, IB_rect, - V3D_OFSDRAW_NONE, + (v3d) ? V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS : + V3D_OFSDRAW_NONE, R_ALPHAPREMUL, NULL, NULL, @@ -1588,8 +1642,8 @@ static ImBuf *blend_file_thumb(const bContext *C, OB_SOLID, v3d, region, - BLEN_THUMB_SIZE * 2, - BLEN_THUMB_SIZE * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, IB_rect, R_ALPHAPREMUL, NULL, @@ -1610,8 +1664,14 @@ static ImBuf *blend_file_thumb(const bContext *C, if (ibuf) { /* dirty oversampling */ - IMB_scaleImBuf(ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE); - thumb = BKE_main_thumbnail_from_imbuf(NULL, ibuf); + ImBuf *thumb_ibuf; + thumb_ibuf = IMB_dupImBuf(ibuf); + /* BLEN_THUMB_SIZE is size of thumbnail inside blend file: 128x128. */ + IMB_scaleImBuf(thumb_ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE); + thumb = BKE_main_thumbnail_from_imbuf(NULL, thumb_ibuf); + IMB_freeImBuf(thumb_ibuf); + /* Thumbnail saved to file-system should be 256x256. */ + IMB_scaleImBuf(ibuf, PREVIEW_RENDER_LARGE_HEIGHT, PREVIEW_RENDER_LARGE_HEIGHT); } else { /* '*thumb_pt' needs to stay NULL to prevent a bad thumbnail from being handled */ @@ -1693,13 +1753,42 @@ static bool wm_file_write(bContext *C, /* Enforce full override check/generation on file save. */ BKE_lib_override_library_main_operations_create(bmain, true); + if (!G.background) { + /* Redraw to remove menus that might be open. */ + WM_redraw_windows(C); + } + + /* don't forget not to return without! */ + WM_cursor_wait(true); + /* blend file thumbnail */ /* Save before exit_editmode, otherwise derivedmeshes for shared data corrupt T27765. */ /* Main now can store a '.blend' thumbnail, useful for background mode * or thumbnail customization. */ main_thumb = thumb = bmain->blen_thumb; - if ((U.flag & USER_SAVE_PREVIEWS) && BLI_thread_is_main()) { - ibuf_thumb = blend_file_thumb(C, CTX_data_scene(C), CTX_wm_screen(C), &thumb); + if (BLI_thread_is_main() && U.file_preview_type != USER_FILE_PREVIEW_NONE) { + + int file_preview_type = U.file_preview_type; + + if (file_preview_type == USER_FILE_PREVIEW_AUTO) { + Scene *scene = CTX_data_scene(C); + bool do_render = (scene != NULL && scene->camera != NULL && + (BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_VIEW3D, 0) != NULL)); + file_preview_type = do_render ? USER_FILE_PREVIEW_CAMERA : USER_FILE_PREVIEW_SCREENSHOT; + } + + switch (file_preview_type) { + case USER_FILE_PREVIEW_SCREENSHOT: { + ibuf_thumb = blend_file_thumb_from_screenshot(C, &thumb); + break; + } + case USER_FILE_PREVIEW_CAMERA: { + ibuf_thumb = blend_file_thumb_from_camera(C, CTX_data_scene(C), CTX_wm_screen(C), &thumb); + break; + } + default: + BLI_assert_unreachable(); + } } /* operator now handles overwrite checks */ @@ -1708,9 +1797,6 @@ static bool wm_file_write(bContext *C, BKE_packedfile_pack_all(bmain, reports, false); } - /* don't forget not to return without! */ - WM_cursor_wait(true); - ED_editors_flush_edits(bmain); /* First time saving. */ diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index 606c9252ff9..d0117d9d57a 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -35,7 +35,9 @@ #include "MEM_guardedalloc.h" #include "DNA_ID.h" +#include "DNA_collection_types.h" #include "DNA_key_types.h" +#include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_windowmanager_types.h" @@ -50,15 +52,21 @@ #include "BLO_readfile.h" +#include "BKE_armature.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_key.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_main.h" +#include "BKE_material.h" +#include "BKE_object.h" #include "BKE_report.h" +#include "BKE_rigidbody.h" +#include "BKE_scene.h" #include "BKE_idtype.h" @@ -137,6 +145,14 @@ static short wm_link_append_flag(wmOperator *op) if (RNA_boolean_get(op->ptr, "link")) { flag |= FILE_LINK; } + else { + if (RNA_boolean_get(op->ptr, "use_recursive")) { + flag |= FILE_APPEND_RECURSIVE; + } + if (RNA_boolean_get(op->ptr, "set_fake")) { + flag |= FILE_APPEND_SET_FAKEUSER; + } + } if (RNA_boolean_get(op->ptr, "instance_collections")) { flag |= FILE_COLLECTION_INSTANCE; } @@ -153,6 +169,10 @@ typedef struct WMLinkAppendDataItem { *libraries; /* All libs (from WMLinkAppendData.libraries) to try to load this ID from. */ short idcode; + /** Type of action to do to append this item, and other append-specific information. */ + char append_action; + char append_tag; + ID *new_id; void *customdata; } WMLinkAppendDataItem; @@ -167,10 +187,32 @@ typedef struct WMLinkAppendData { */ int flag; + /** Allows to easily find an existing items from an ID pointer. Used by append code. */ + GHash *new_id_to_item; + /* Internal 'private' data */ MemArena *memarena; } WMLinkAppendData; +typedef struct WMLinkAppendDataCallBack { + WMLinkAppendData *lapp_data; + WMLinkAppendDataItem *item; + ReportList *reports; + +} WMLinkAppendDataCallBack; + +enum { + WM_APPEND_ACT_UNSET = 0, + WM_APPEND_ACT_KEEP_LINKED, + WM_APPEND_ACT_REUSE_LOCAL, + WM_APPEND_ACT_MAKE_LOCAL, + WM_APPEND_ACT_COPY_LOCAL, +}; + +enum { + WM_APPEND_TAG_INDIRECT = 1 << 0, +}; + static WMLinkAppendData *wm_link_append_data_new(const int flag) { MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); @@ -184,6 +226,10 @@ static WMLinkAppendData *wm_link_append_data_new(const int flag) static void wm_link_append_data_free(WMLinkAppendData *lapp_data) { + if (lapp_data->new_id_to_item != NULL) { + BLI_ghash_free(lapp_data->new_id_to_item, NULL, NULL); + } + BLI_memarena_free(lapp_data->memarena); } @@ -213,6 +259,7 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp item->libraries = BLI_BITMAP_NEW_MEMARENA(lapp_data->memarena, lapp_data->num_libraries); item->new_id = NULL; + item->append_action = WM_APPEND_ACT_UNSET; item->customdata = customdata; BLI_linklist_append_arena(&lapp_data->items, item, lapp_data->memarena); @@ -221,6 +268,568 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp return item; } +/* -------------------------------------------------------------------- */ +/** \name Library appending helper functions. + * + * FIXME: Deduplicate code with similar one in readfile.c + * \{ */ + +static bool object_in_any_scene(Main *bmain, Object *ob) +{ + LISTBASE_FOREACH (Scene *, sce, &bmain->scenes) { + if (BKE_scene_object_find(sce, ob)) { + return true; + } + } + + return false; +} + +static bool object_in_any_collection(Main *bmain, Object *ob) +{ + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + if (BKE_collection_has_object(collection, ob)) { + return true; + } + } + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->master_collection != NULL && + BKE_collection_has_object(scene->master_collection, ob)) { + return true; + } + } + + return false; +} + +/** + * Shared operations to perform on the object's base after adding it to the scene. + */ +static void wm_append_loose_data_instantiate_object_base_instance_init( + Object *ob, bool set_selected, bool set_active, ViewLayer *view_layer, const View3D *v3d) +{ + Base *base = BKE_view_layer_base_find(view_layer, ob); + + if (v3d != NULL) { + base->local_view_bits |= v3d->local_view_uuid; + } + + if (set_selected) { + if (base->flag & BASE_SELECTABLE) { + base->flag |= BASE_SELECTED; + } + } + + if (set_active) { + view_layer->basact = base; + } + + BKE_scene_object_base_flag_sync_from_base(base); +} + +static ID *wm_append_loose_data_instantiate_process_check(WMLinkAppendDataItem *item) +{ + /* We consider that if we either kept it linked, or re-used already local data, instantiation + * status of those should not be modified. */ + if (!ELEM(item->append_action, WM_APPEND_ACT_COPY_LOCAL, WM_APPEND_ACT_MAKE_LOCAL)) { + return NULL; + } + + ID *id = item->new_id; + if (id == NULL) { + return NULL; + } + + if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) { + BLI_assert(ID_IS_LINKED(id)); + id = id->newid; + if (id == NULL) { + return NULL; + } + + BLI_assert(!ID_IS_LINKED(id)); + return id; + } + + BLI_assert(!ID_IS_LINKED(id)); + return id; +} + +static void wm_append_loose_data_instantiate_ensure_active_collection( + WMLinkAppendData *lapp_data, + Main *bmain, + Scene *scene, + ViewLayer *view_layer, + Collection **r_active_collection) +{ + /* Find or add collection as needed. */ + if (*r_active_collection == NULL) { + if (lapp_data->flag & FILE_ACTIVE_COLLECTION) { + LayerCollection *lc = BKE_layer_collection_get_active(view_layer); + *r_active_collection = lc->collection; + } + else { + *r_active_collection = BKE_collection_add(bmain, scene->master_collection, NULL); + } + } +} + +/* TODO: De-duplicate this code with the one in readfile.c, think we need some utils code for that + * in BKE. */ +static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, + Main *bmain, + Scene *scene, + ViewLayer *view_layer, + const View3D *v3d) +{ + if (scene == NULL) { + /* In some cases, like the asset drag&drop e.g., the caller code manages instantiation itself. + */ + return; + } + + LinkNode *itemlink; + Collection *active_collection = NULL; + const bool do_obdata = (lapp_data->flag & FILE_OBDATA_INSTANCE) != 0; + + const bool object_set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0; + /* Do NOT make base active here! screws up GUI stuff, + * if you want it do it at the editor level. */ + const bool object_set_active = false; + + /* First pass on obdata to enable their instantiation by default, then do a second pass on + * objects to clear it for any obdata already in use. */ + if (do_obdata) { + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL) { + continue; + } + const ID_Type idcode = GS(id->name); + if (!OB_DATA_SUPPORT_ID(idcode)) { + continue; + } + + id->tag |= LIB_TAG_DOIT; + } + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL || GS(id->name) != ID_OB) { + continue; + } + + Object *ob = (Object *)id; + Object *new_ob = (Object *)id->newid; + if (ob->data != NULL) { + ((ID *)(ob->data))->tag &= ~LIB_TAG_DOIT; + } + if (new_ob != NULL && new_ob->data != NULL) { + ((ID *)(new_ob->data))->tag &= ~LIB_TAG_DOIT; + } + } + } + + /* First do collections, then objects, then obdata. */ + + /* NOTE: For collections we only view_layer-instantiate duplicated collections that have + * non-instantiated objects in them. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL || GS(id->name) != ID_GR) { + continue; + } + + /* We do not want to force instantiation of indirectly appended collections. Users can now + * easily instantiate collections (and their objects) as needed by themselves. See T67032. */ + /* We need to check that objects in that collections are already instantiated in a scene. + * Otherwise, it's better to add the collection to the scene's active collection, than to + * instantiate its objects in active scene's collection directly. See T61141. + * + * NOTE: We only check object directly into that collection, not recursively into its + * children. + */ + Collection *collection = (Collection *)id; + bool do_add_collection = false; + LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { + Object *ob = coll_ob->ob; + if (!object_in_any_scene(bmain, ob)) { + do_add_collection = true; + break; + } + } + if (do_add_collection) { + wm_append_loose_data_instantiate_ensure_active_collection( + lapp_data, bmain, scene, view_layer, &active_collection); + + /* 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 && + (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); + ob->type = OB_EMPTY; + ob->empty_drawsize = U.collection_instance_empty_size; + + BKE_collection_object_add(bmain, active_collection, ob); + + const bool set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0; + /* TODO: why is it OK to make this active here but not in other situations? + * See other callers of #object_base_instance_init */ + const bool set_active = set_selected; + wm_append_loose_data_instantiate_object_base_instance_init( + ob, set_selected, set_active, view_layer, v3d); + + /* Assign the collection. */ + ob->instance_collection = collection; + id_us_plus(&collection->id); + ob->transflag |= OB_DUPLICOLLECTION; + copy_v3_v3(ob->loc, scene->cursor.location); + } + else { + /* Add collection as child of active collection. */ + BKE_collection_child_add(bmain, active_collection, collection); + + if ((lapp_data->flag & FILE_AUTOSELECT) != 0) { + LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { + Object *ob = coll_ob->ob; + Base *base = BKE_view_layer_base_find(view_layer, ob); + if (base) { + base->flag |= BASE_SELECTED; + BKE_scene_object_base_flag_sync_from_base(base); + } + } + } + } + } + } + + /* NOTE: For objects we only view_layer-instantiate duplicated objects that are not yet used + * anywhere. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL || GS(id->name) != ID_OB) { + continue; + } + + Object *ob = (Object *)id; + + if (object_in_any_collection(bmain, ob)) { + continue; + } + + wm_append_loose_data_instantiate_ensure_active_collection( + lapp_data, bmain, scene, view_layer, &active_collection); + + CLAMP_MIN(ob->id.us, 0); + ob->mode = OB_MODE_OBJECT; + + BKE_collection_object_add(bmain, active_collection, ob); + + wm_append_loose_data_instantiate_object_base_instance_init( + ob, object_set_selected, object_set_active, view_layer, v3d); + } + + if (!do_obdata) { + return; + } + + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL) { + continue; + } + const ID_Type idcode = GS(id->name); + if (!OB_DATA_SUPPORT_ID(idcode)) { + continue; + } + if ((id->tag & LIB_TAG_DOIT) == 0) { + continue; + } + + wm_append_loose_data_instantiate_ensure_active_collection( + lapp_data, bmain, scene, view_layer, &active_collection); + + const int type = BKE_object_obdata_to_type(id); + BLI_assert(type != -1); + Object *ob = BKE_object_add_only_object(bmain, type, id->name + 2); + ob->data = id; + id_us_plus(id); + BKE_object_materials_test(bmain, ob, ob->data); + + BKE_collection_object_add(bmain, active_collection, ob); + + wm_append_loose_data_instantiate_object_base_instance_init( + ob, object_set_selected, object_set_active, view_layer, v3d); + + copy_v3_v3(ob->loc, scene->cursor.location); + } +} + +/** \} */ + +static int foreach_libblock_append_callback(LibraryIDLinkCallbackData *cb_data) +{ + if (cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_INTERNAL | IDWALK_CB_LOOPBACK)) { + return IDWALK_RET_NOP; + } + + WMLinkAppendDataCallBack *data = cb_data->user_data; + ID *id = *cb_data->id_pointer; + + if (id == NULL) { + return IDWALK_RET_NOP; + } + + if (!BKE_idtype_idcode_is_linkable(GS(id->name))) { + return IDWALK_RET_NOP; + } + + WMLinkAppendDataItem *item = BLI_ghash_lookup(data->lapp_data->new_id_to_item, id); + if (item == NULL) { + item = wm_link_append_data_item_add(data->lapp_data, id->name, GS(id->name), NULL); + item->new_id = id; + /* Since we did not have an item for that ID yet, we now user did not selected it explicitly, + * it was rather linked indirectly. This info is important for instantiation of collections. */ + item->append_tag |= WM_APPEND_TAG_INDIRECT; + BLI_ghash_insert(data->lapp_data->new_id_to_item, id, item); + } + + /* NOTE: currently there is no need to do anything else here, but in the future this would be + * the place to add specific per-usage decisions on how to append an ID. */ + + return IDWALK_RET_NOP; +} + +/* Perform append operation, using modern ID usage looper to detect which ID should be kept linked, + * made local, duplicated as local, re-used from local etc. + * + * TODO: Expose somehow this logic to the two other parts of code performing actual append + * (i.e. copy/paste and `bpy` link/append API). + * Then we can heavily simplify #BKE_library_make_local(). */ +static void wm_append_do(WMLinkAppendData *lapp_data, + ReportList *reports, + Main *bmain, + Scene *scene, + ViewLayer *view_layer, + const View3D *v3d) +{ + 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; + + LinkNode *itemlink; + + /* Generate a mapping between newly linked IDs and their items. */ + lapp_data->new_id_to_item = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_ghash_insert(lapp_data->new_id_to_item, id, item); + } + + /* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect + * dependencies), this list will grow and we will process those IDs later, leading to a flatten + * recursive processing of all the linked dependencies. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(item->customdata == NULL); + + /* Clear tag previously used to mark IDs needing post-processing (instantiation of loose + * objects etc.). */ + id->tag &= ~LIB_TAG_DOIT; + + if (item->append_action != WM_APPEND_ACT_UNSET) { + /* Already set, pass. */ + } + if (GS(id->name) == ID_OB && ((Object *)id)->proxy_from != NULL) { + CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name); + item->append_action = WM_APPEND_ACT_KEEP_LINKED; + } + else if (id->tag & LIB_TAG_PRE_EXISTING) { + CLOG_INFO(&LOG, 3, "Appended ID '%s' was already linked, need to copy it...", id->name); + item->append_action = WM_APPEND_ACT_COPY_LOCAL; + } + else { + /* In future we could search for already existing matching local ID etc. */ + CLOG_INFO(&LOG, 3, "Appended ID '%s' will be made local...", id->name); + item->append_action = WM_APPEND_ACT_MAKE_LOCAL; + } + + /* Only check dependencies if we are not keeping linked data, nor re-using existing local data. + */ + if (do_recursive && + !ELEM(item->append_action, WM_APPEND_ACT_KEEP_LINKED, WM_APPEND_ACT_REUSE_LOCAL)) { + WMLinkAppendDataCallBack cb_data = { + .lapp_data = lapp_data, .item = item, .reports = reports}; + BKE_library_foreach_ID_link( + bmain, id, foreach_libblock_append_callback, &cb_data, IDWALK_NOP); + } + } + + /* Effectively perform required operation on every linked ID. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + + ID *local_appended_new_id = NULL; + switch (item->append_action) { + case WM_APPEND_ACT_COPY_LOCAL: { + BKE_lib_id_make_local( + bmain, id, LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_COPY); + local_appended_new_id = id->newid; + break; + } + case WM_APPEND_ACT_MAKE_LOCAL: + BKE_lib_id_make_local(bmain, + id, + LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_LOCAL | + LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); + BLI_assert(id->newid == NULL); + local_appended_new_id = id; + break; + case WM_APPEND_ACT_KEEP_LINKED: + /* Nothing to do here. */ + break; + case WM_APPEND_ACT_REUSE_LOCAL: + /* We only need to set `newid` to ID found in previous loop, for proper remapping. */ + ID_NEW_SET(id, item->customdata); + /* This is not a 'new' local appended id, do not set `local_appended_new_id` here. */ + break; + case WM_APPEND_ACT_UNSET: + CLOG_ERROR( + &LOG, "Unexpected unset append action for '%s' ID, assuming 'keep link'", id->name); + break; + default: + BLI_assert(0); + } + + if (local_appended_new_id != NULL) { + if (GS(local_appended_new_id->name) == ID_OB) { + BKE_rigidbody_ensure_local_object(bmain, (Object *)local_appended_new_id); + } + if (set_fakeuser) { + if (!ELEM(GS(local_appended_new_id->name), ID_OB, ID_GR)) { + /* Do not set fake user on objects nor collections (instancing). */ + id_fake_user_set(local_appended_new_id); + } + } + } + } + + /* Remap IDs as needed. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + + if (item->append_action == WM_APPEND_ACT_KEEP_LINKED) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) { + BLI_assert(ID_IS_LINKED(id)); + id = id->newid; + if (id == NULL) { + continue; + } + } + + BLI_assert(!ID_IS_LINKED(id)); + + BKE_libblock_relink_to_newid_new(bmain, id); + } + + /* Instantiate newly created (duplicated) IDs as needed. */ + wm_append_loose_data_instantiate(lapp_data, bmain, scene, view_layer, v3d); + + /* Attempt to deal with object proxies. + * + * NOTE: Copied from `BKE_library_make_local`, but this is not really working (as in, not + * producing any useful result in any known use case), neither here nor in + * `BKE_library_make_local` currently. + * Proxies are end of life anyway, so not worth spending time on this. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + + if (item->append_action != WM_APPEND_ACT_COPY_LOCAL) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(ID_IS_LINKED(id)); + + /* Attempt to re-link copied proxy objects. This allows appending of an entire scene + * from another blend file into this one, even when that blend file contains proxified + * armatures that have local references. Since the proxified object needs to be linked + * (not local), this will only work when the "Localize all" checkbox is disabled. + * TL;DR: this is a dirty hack on top of an already weak feature (proxies). */ + if (GS(id->name) == ID_OB && ((Object *)id)->proxy != NULL) { + Object *ob = (Object *)id; + Object *ob_new = (Object *)id->newid; + bool is_local = false, is_lib = false; + + /* Proxies only work when the proxified object is linked-in from a library. */ + if (!ID_IS_LINKED(ob->proxy)) { + CLOG_WARN(&LOG, + "Proxy object %s will lose its link to %s, because the " + "proxified object is local", + id->newid->name, + ob->proxy->id.name); + continue; + } + + BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); + + /* We can only switch the proxy'ing to a made-local proxy if it is no longer + * referred to from a library. Not checking for local use; if new local proxy + * was not used locally would be a nasty bug! */ + if (is_local || is_lib) { + CLOG_WARN(&LOG, + "Made-local proxy object %s will lose its link to %s, " + "because the linked-in proxy is referenced (is_local=%i, is_lib=%i)", + id->newid->name, + ob->proxy->id.name, + is_local, + is_lib); + } + else { + /* we can switch the proxy'ing from the linked-in to the made-local proxy. + * BKE_object_make_proxy() shouldn't be used here, as it allocates memory that + * was already allocated by object_make_local() (which called BKE_object_copy). */ + ob_new->proxy = ob->proxy; + ob_new->proxy_group = ob->proxy_group; + ob_new->proxy_from = ob->proxy_from; + ob_new->proxy->proxy_from = ob_new; + ob->proxy = ob->proxy_from = ob->proxy_group = NULL; + } + } + } + + BKE_main_id_newptr_and_tag_clear(bmain); +} + static void wm_link_do(WMLinkAppendData *lapp_data, ReportList *reports, Main *bmain, @@ -263,6 +872,11 @@ static void wm_link_do(WMLinkAppendData *lapp_data, struct LibraryLink_Params liblink_params; BLO_library_link_params_init_with_context( &liblink_params, bmain, flag, id_tag_extra, scene, view_layer, v3d); + /* In case of append, do not handle instantiation in linking process, but during append phase + * (see #wm_append_loose_data_instantiate ). */ + if ((flag & FILE_LINK) == 0) { + liblink_params.flag &= ~BLO_LIBLINK_NEEDS_ID_TAG_DOIT; + } mainl = BLO_library_link_begin(&bh, libname, &liblink_params); lib = mainl->curlib; @@ -325,9 +939,8 @@ static bool wm_link_append_item_poll(ReportList *reports, idcode = BKE_idtype_idcode_from_name(group); - /* XXX For now, we do a nasty exception for workspace, forbid linking them. - * Not nice, ultimately should be solved! */ - if (!BKE_idtype_idcode_is_linkable(idcode) && (do_append || idcode != ID_WS)) { + if (!BKE_idtype_idcode_is_linkable(idcode) || + (!do_append && BKE_idtype_idcode_is_only_appendable(idcode))) { if (reports) { if (do_append) { BKE_reportf(reports, @@ -501,28 +1114,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) /* append, rather than linking */ if (do_append) { - const bool set_fake = RNA_boolean_get(op->ptr, "set_fake"); - const bool use_recursive = RNA_boolean_get(op->ptr, "use_recursive"); - - if (use_recursive) { - BKE_library_make_local(bmain, NULL, NULL, true, set_fake); - } - else { - LinkNode *itemlink; - GSet *done_libraries = BLI_gset_new_ex( - BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__, lapp_data->num_libraries); - - for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { - ID *new_id = ((WMLinkAppendDataItem *)(itemlink->link))->new_id; - - if (new_id && !BLI_gset_haskey(done_libraries, new_id->lib)) { - BKE_library_make_local(bmain, new_id->lib, NULL, true, set_fake); - BLI_gset_insert(done_libraries, new_id->lib); - } - } - - BLI_gset_free(done_libraries, NULL); - } + wm_append_do(lapp_data, op->reports, bmain, scene, view_layer, CTX_wm_view3d(C)); } wm_link_append_data_free(lapp_data); @@ -652,20 +1244,20 @@ void WM_OT_append(wmOperatorType *ot) * * \{ */ -static ID *wm_file_link_datablock_ex(Main *bmain, - Scene *scene, - ViewLayer *view_layer, - View3D *v3d, - const char *filepath, - const short id_code, - const char *id_name, - bool clear_pre_existing_flag) +static ID *wm_file_link_append_datablock_ex(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + View3D *v3d, + const char *filepath, + const short id_code, + const char *id_name, + const bool do_append) { /* 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(0); + WMLinkAppendData *lapp_data = wm_link_append_data_new(do_append ? FILE_APPEND_RECURSIVE : 0); wm_link_append_data_library_add(lapp_data, filepath); WMLinkAppendDataItem *item = wm_link_append_data_item_add(lapp_data, id_name, id_code, NULL); @@ -676,15 +1268,22 @@ static ID *wm_file_link_datablock_ex(Main *bmain, /* Get linked datablock and free working data. */ ID *id = item->new_id; - wm_link_append_data_free(lapp_data); - if (clear_pre_existing_flag) { - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + if (do_append) { + wm_append_do(lapp_data, NULL, bmain, scene, view_layer, v3d); } + wm_link_append_data_free(lapp_data); + + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + return id; } +/* + * NOTE: `scene` (and related `view_layer` and `v3d`) pointers may be NULL, in which case no + * instantiation of linked objects, collections etc. will be performed. + */ ID *WM_file_link_datablock(Main *bmain, Scene *scene, ViewLayer *view_layer, @@ -693,10 +1292,14 @@ ID *WM_file_link_datablock(Main *bmain, const short id_code, const char *id_name) { - return wm_file_link_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, true); + return wm_file_link_append_datablock_ex( + bmain, scene, view_layer, v3d, filepath, id_code, id_name, false); } +/* + * NOTE: `scene` (and related `view_layer` and `v3d`) pointers may be NULL, in which case no + * instantiation of appended objects, collections etc. will be performed. + */ ID *WM_file_append_datablock(Main *bmain, Scene *scene, ViewLayer *view_layer, @@ -705,14 +1308,8 @@ ID *WM_file_append_datablock(Main *bmain, const short id_code, const char *id_name) { - ID *id = wm_file_link_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, false); - - /* Make datablock local. */ - BKE_library_make_local(bmain, NULL, NULL, true, false); - - /* Clear pre existing tag. */ - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + ID *id = wm_file_link_append_datablock_ex( + bmain, scene, view_layer, v3d, filepath, id_code, id_name, true); return id; } diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c index 1c736647084..cc376d8f201 100644 --- a/source/blender/windowmanager/intern/wm_gesture_ops.c +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -818,6 +818,8 @@ void WM_OT_lasso_gesture(wmOperatorType *ot) ot->poll = WM_operator_winactive; + ot->flag = OPTYPE_DEPENDS_ON_CURSOR; + prop = RNA_def_property(ot->srna, "path", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_runtime(ot->srna, prop, &RNA_OperatorMousePath); } @@ -954,8 +956,9 @@ int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *ev break; } case GESTURE_MODAL_FLIP: { - /* Toggle snapping on/off. */ + /* Toggle flipping on/off. */ gesture->use_flip = !gesture->use_flip; + gesture_straightline_apply(C, op); break; } case GESTURE_MODAL_SELECT: { @@ -993,6 +996,7 @@ int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *ev if (gesture->use_snap) { wm_gesture_straightline_do_angle_snap(rect); + gesture_straightline_apply(C, op); } wm_gesture_tag_redraw(win); diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index 25bcf1967ea..f955abaed53 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -460,8 +460,11 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap) if (UNLIKELY(BLI_listbase_is_empty(&keymap->items))) { /* Empty key-maps may be missing more there may be a typo in the name. - * Warn early to avoid losing time investigating each case. */ - CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname); + * Warn early to avoid losing time investigating each case. + * When developing a customized Blender though you may want empty keymaps. */ + if (!U.app_template[0]) { + CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname); + } } if (keymap->poll != NULL) { @@ -481,13 +484,20 @@ static void keymap_event_set( kmi->shift = kmi->ctrl = kmi->alt = kmi->oskey = KM_ANY; } else { - kmi->shift = (modifier & KM_SHIFT) ? KM_MOD_FIRST : - ((modifier & KM_SHIFT2) ? KM_MOD_SECOND : false); - kmi->ctrl = (modifier & KM_CTRL) ? KM_MOD_FIRST : - ((modifier & KM_CTRL2) ? KM_MOD_SECOND : false); - kmi->alt = (modifier & KM_ALT) ? KM_MOD_FIRST : ((modifier & KM_ALT2) ? KM_MOD_SECOND : false); - kmi->oskey = (modifier & KM_OSKEY) ? KM_MOD_FIRST : - ((modifier & KM_OSKEY2) ? KM_MOD_SECOND : false); + /* Only one of the flags should be set. */ + BLI_assert(((modifier & (KM_SHIFT | KM_SHIFT_ANY)) != (KM_SHIFT | KM_SHIFT_ANY)) && + ((modifier & (KM_CTRL | KM_CTRL_ANY)) != (KM_CTRL | KM_CTRL_ANY)) && + ((modifier & (KM_ALT | KM_ALT_ANY)) != (KM_ALT | KM_ALT_ANY)) && + ((modifier & (KM_OSKEY | KM_OSKEY_ANY)) != (KM_OSKEY | KM_OSKEY_ANY))); + + kmi->shift = ((modifier & KM_SHIFT) ? KM_MOD_HELD : + ((modifier & KM_SHIFT_ANY) ? KM_ANY : KM_NOTHING)); + kmi->ctrl = ((modifier & KM_CTRL) ? KM_MOD_HELD : + ((modifier & KM_CTRL_ANY) ? KM_ANY : KM_NOTHING)); + kmi->alt = ((modifier & KM_ALT) ? KM_MOD_HELD : + ((modifier & KM_ALT_ANY) ? KM_ANY : KM_NOTHING)); + kmi->oskey = ((modifier & KM_OSKEY) ? KM_MOD_HELD : + ((modifier & KM_OSKEY_ANY) ? KM_ANY : KM_NOTHING)); } } @@ -1161,7 +1171,6 @@ int WM_keymap_item_raw_to_string(const short shift, buf[0] = '\0'; - /* TODO: support order (KM_SHIFT vs. KM_SHIFT2) ? */ if (shift == KM_ANY && ctrl == KM_ANY && alt == KM_ANY && oskey == KM_ANY) { /* Don't show anything for any mapping. */ } diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index df051328990..81dcc5ccea0 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -552,37 +552,38 @@ static const char *wm_context_member_from_ptr(bContext *C, const PointerRNA *ptr const View3D *v3d = (View3D *)space_data; const View3DShading *shading = &v3d->shading; - TEST_PTR_DATA_TYPE("space_data", RNA_View3DOverlay, ptr, v3d); - TEST_PTR_DATA_TYPE("space_data", RNA_View3DShading, ptr, shading); + TEST_PTR_DATA_TYPE("space_data.overlay", RNA_View3DOverlay, ptr, v3d); + TEST_PTR_DATA_TYPE("space_data.shading", RNA_View3DShading, ptr, shading); break; } case SPACE_GRAPH: { const SpaceGraph *sipo = (SpaceGraph *)space_data; const bDopeSheet *ads = sipo->ads; - TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads); + TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads); break; } case SPACE_FILE: { const SpaceFile *sfile = (SpaceFile *)space_data; const FileSelectParams *params = ED_fileselect_get_active_params(sfile); - TEST_PTR_DATA_TYPE("space_data", RNA_FileSelectParams, ptr, params); + TEST_PTR_DATA_TYPE("space_data.params", RNA_FileSelectParams, ptr, params); break; } case SPACE_IMAGE: { const SpaceImage *sima = (SpaceImage *)space_data; - TEST_PTR_DATA_TYPE("space_data", RNA_SpaceUVEditor, ptr, sima); + TEST_PTR_DATA_TYPE("space_data.overlay", RNA_SpaceImageOverlay, ptr, sima); + TEST_PTR_DATA_TYPE("space_data.uv_editor", RNA_SpaceUVEditor, ptr, sima); break; } case SPACE_NLA: { const SpaceNla *snla = (SpaceNla *)space_data; const bDopeSheet *ads = snla->ads; - TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads); + TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads); break; } case SPACE_ACTION: { const SpaceAction *sact = (SpaceAction *)space_data; const bDopeSheet *ads = &sact->ads; - TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads); + TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads); break; } } diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 004a845c667..887aed7ffc7 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -808,16 +808,17 @@ wmWindow *WM_window_open(bContext *C, /* changes rect to fit within desktop */ wm_window_check_size(&rect); - /* Reuse temporary windows when they share the same title. */ + /* Reuse temporary windows when they share the same single area. */ wmWindow *win = NULL; if (temp) { LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) { - if (WM_window_is_temp_screen(win_iter)) { - char *wintitle = GHOST_GetTitle(win_iter->ghostwin); - if (STREQ(title, wintitle)) { + const bScreen *screen = WM_window_get_active_screen(win_iter); + if (screen && screen->temp && BLI_listbase_is_single(&screen->areabase)) { + ScrArea *area = screen->areabase.first; + if (space_type == (area->butspacetype ? area->butspacetype : area->spacetype)) { win = win_iter; + break; } - free(wintitle); } } } @@ -1196,7 +1197,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr #ifdef USE_WIN_ACTIVATE else { if (keymodifier & KM_SHIFT) { - win->eventstate->shift = KM_MOD_FIRST; + win->eventstate->shift = KM_MOD_HELD; } } #endif @@ -1209,7 +1210,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr #ifdef USE_WIN_ACTIVATE else { if (keymodifier & KM_CTRL) { - win->eventstate->ctrl = KM_MOD_FIRST; + win->eventstate->ctrl = KM_MOD_HELD; } } #endif @@ -1222,7 +1223,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr #ifdef USE_WIN_ACTIVATE else { if (keymodifier & KM_ALT) { - win->eventstate->alt = KM_MOD_FIRST; + win->eventstate->alt = KM_MOD_HELD; } } #endif @@ -1235,7 +1236,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr #ifdef USE_WIN_ACTIVATE else { if (keymodifier & KM_OSKEY) { - win->eventstate->oskey = KM_MOD_FIRST; + win->eventstate->oskey = KM_MOD_HELD; } } #endif diff --git a/source/blender/windowmanager/wm_cursors.h b/source/blender/windowmanager/wm_cursors.h index 2842538ebf1..d1694454490 100644 --- a/source/blender/windowmanager/wm_cursors.h +++ b/source/blender/windowmanager/wm_cursors.h @@ -74,6 +74,8 @@ typedef enum WMCursorType { WM_CURSOR_NONE, WM_CURSOR_MUTE, + WM_CURSOR_PICK_AREA, + /* --- ALWAYS LAST ----- */ WM_CURSOR_NUM, } WMCursorType; diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c index 297205d1e79..8891840cb75 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr.c +++ b/source/blender/windowmanager/xr/intern/wm_xr.c @@ -35,6 +35,8 @@ #include "GHOST_C-api.h" +#include "GPU_platform.h" + #include "WM_api.h" #include "wm_surface.h" @@ -91,6 +93,11 @@ bool wm_xr_init(wmWindowManager *wm) if (G.debug & G_DEBUG_XR_TIME) { create_info.context_flag |= GHOST_kXrContextDebugTime; } +#ifdef WIN32 + if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_WIN, GPU_DRIVER_ANY)) { + create_info.context_flag |= GHOST_kXrContextGpuNVIDIA; + } +#endif if (!(context = GHOST_XrContextCreate(&create_info))) { return false; diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index dc15b579e9d..88bf3ff453c 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -705,7 +705,7 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data, } if (failure) { - CLOG_ERROR(&LOG, "Failed to get buffer, %s\n", err_out); + CLOG_ERROR(&LOG, "Failed to get buffer, %s", err_out); return false; } diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c index 85ba4eca307..943646daa81 100644 --- a/source/creator/creator_args.c +++ b/source/creator/creator_args.c @@ -1920,7 +1920,7 @@ static int arg_handle_python_use_system_env_set(int UNUSED(argc), static const char arg_handle_addons_set_doc[] = "<addon(s)>\n" - "\tComma separated list of add-ons (no spaces)."; + "\tComma separated list (no spaces) of add-ons to enable in addition to any default add-ons."; static int arg_handle_addons_set(int argc, const char **argv, void *data) { /* workaround for scripts not getting a bpy.context.scene, causes internal errors elsewhere */ diff --git a/source/tools b/source/tools -Subproject 5cf2fc3e5dc28025394b57d8743401295528f31 +Subproject c8579c5cf43229843df505da9644b5b0b720197 diff --git a/tests/performance/api/config.py b/tests/performance/api/config.py index d3a79eede14..b5e2b390aa3 100644 --- a/tests/performance/api/config.py +++ b/tests/performance/api/config.py @@ -25,6 +25,7 @@ class TestEntry: category: str = '' revision: str = '' git_hash: str = '' + environment: Dict = field(default_factory=dict) executable: str = '' date: int = 0 device_type: str = 'CPU' @@ -160,7 +161,13 @@ class TestConfig: def read_blender_executables(env, name) -> List: config = TestConfig._read_config_module(env.base_dir / name) builds = getattr(config, 'builds', {}) - return [pathlib.Path(build) for build in builds.values()] + executables = [] + + for executable in builds.values(): + executable, _ = TestConfig._split_environment_variables(executable) + executables.append(pathlib.Path(executable)) + + return executables @staticmethod def _read_config_module(base_dir: pathlib.Path) -> None: @@ -191,9 +198,10 @@ class TestConfig: # Get entries for specified commits, tags and branches. for revision_name, revision_commit in self.revisions.items(): + revision_commit, environment = self._split_environment_variables(revision_commit) git_hash = env.resolve_git_hash(revision_commit) date = env.git_hash_date(git_hash) - entries += self._get_entries(revision_name, git_hash, '', date) + entries += self._get_entries(revision_name, git_hash, '', environment, date) # Optimization to avoid rebuilds. revisions_to_build = set() @@ -204,6 +212,7 @@ class TestConfig: # Get entries for revisions based on existing builds. for revision_name, executable in self.builds.items(): + executable, environment = self._split_environment_variables(executable) executable_path = env._blender_executable_from_path(pathlib.Path(executable)) if not executable_path: sys.stderr.write(f'Error: build {executable} not found\n') @@ -214,7 +223,7 @@ class TestConfig: env.set_default_blender_executable() mtime = executable_path.stat().st_mtime - entries += self._get_entries(revision_name, git_hash, executable, mtime) + entries += self._get_entries(revision_name, git_hash, executable, environment, mtime) # Detect number of categories for more compact printing. categories = set() @@ -229,6 +238,7 @@ class TestConfig: revision_name: str, git_hash: str, executable: pathlib.Path, + environment: str, date: int) -> None: entries = [] for test in self.tests.tests: @@ -241,10 +251,12 @@ class TestConfig: # Test if revision hash or executable changed. if entry.git_hash != git_hash or \ entry.executable != executable or \ + entry.environment != environment or \ entry.benchmark_type != self.benchmark_type or \ entry.date != date: # Update existing entry. entry.git_hash = git_hash + entry.environment = environment entry.executable = executable entry.benchmark_type = self.benchmark_type entry.date = date @@ -256,6 +268,7 @@ class TestConfig: revision=revision_name, git_hash=git_hash, executable=executable, + environment=environment, date=date, test=test_name, category=test_category, @@ -266,3 +279,10 @@ class TestConfig: entries.append(entry) return entries + + @staticmethod + def _split_environment_variables(revision): + if isinstance(revision, str): + return revision, {} + else: + return revision[0], revision[1] diff --git a/tests/performance/api/environment.py b/tests/performance/api/environment.py index 76c731b6118..eec92cc7b6b 100644 --- a/tests/performance/api/environment.py +++ b/tests/performance/api/environment.py @@ -98,15 +98,18 @@ class TestEnvironment: try: self.call([self.cmake_executable, '.'] + self.cmake_options, self.build_dir) self.call([self.cmake_executable, '--build', '.', '-j', jobs, '--target', 'install'], self.build_dir) + except KeyboardInterrupt as e: + raise e except: return False self._init_default_blender_executable() return True - def set_blender_executable(self, executable_path: pathlib.Path) -> None: + def set_blender_executable(self, executable_path: pathlib.Path, environment: Dict = {}) -> None: # Run all Blender commands with this executable. self.blender_executable = executable_path + self.blender_executable_environment = environment def _blender_executable_name(self) -> pathlib.Path: if platform.system() == "Windows": @@ -150,6 +153,7 @@ class TestEnvironment: def set_default_blender_executable(self) -> None: self.blender_executable = self.default_blender_executable + self.blender_executable_environment = {} def set_log_file(self, filepath: pathlib.Path, clear=True) -> None: # Log all commands and output to this file. @@ -161,7 +165,7 @@ class TestEnvironment: def unset_log_file(self) -> None: self.log_file = None - def call(self, args: List[str], cwd: pathlib.Path, silent=False) -> List[str]: + def call(self, args: List[str], cwd: pathlib.Path, silent: bool=False, environment: Dict={}) -> List[str]: # Execute command with arguments in specified directory, # and return combined stdout and stderr output. @@ -173,7 +177,13 @@ class TestEnvironment: f = open(self.log_file, 'a') f.write('\n' + ' '.join([str(arg) for arg in args]) + '\n\n') - proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + env = os.environ + if len(environment): + env = env.copy() + for key, value in environment.items(): + env[key] = value + + proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) # Read line by line lines = [] @@ -185,17 +195,13 @@ class TestEnvironment: lines.append(line_str) if f: f.write(line_str) - except KeyboardInterrupt: + except KeyboardInterrupt as e: # Avoid processes that keep running when interrupting. proc.terminate() + raise e - if f: - f.close() - - # Print command output on error + # Raise error on failure if proc.returncode != 0 and not silent: - for line in lines: - print(line.rstrip()) raise Exception("Error executing command") return lines @@ -208,7 +214,8 @@ class TestEnvironment: else: common_args += ['--background'] - return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir) + return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir, + environment=self.blender_executable_environment) def run_in_blender(self, function: Callable[[Dict], Dict], diff --git a/tests/performance/api/graph.py b/tests/performance/api/graph.py index 4ee5ae7cf0e..e54adc194de 100644 --- a/tests/performance/api/graph.py +++ b/tests/performance/api/graph.py @@ -42,7 +42,7 @@ class TestGraph: # Generate one graph for every device x category x result key combination. for category, category_entries in categories.items(): - entries = sorted(category_entries, key=lambda entry: (entry.revision, entry.test)) + entries = sorted(category_entries, key=lambda entry: (entry.date, entry.revision, entry.test)) outputs = set() for entry in entries: @@ -58,8 +58,6 @@ class TestGraph: self.json = json.dumps(data, indent=2) def chart(self, device_name: str, chart_name: str, entries: List, chart_type: str, output: str) -> Dict: - entries = sorted(entries, key=lambda entry: entry.date) - # Gather used tests. tests = {} for entry in entries: diff --git a/tests/performance/benchmark b/tests/performance/benchmark index ad1e07d0ef3..a58c339e9f8 100755 --- a/tests/performance/benchmark +++ b/tests/performance/benchmark @@ -83,15 +83,20 @@ def match_entry(entry: api.TestEntry, args: argparse.Namespace): entry.test.find(args.test) != -1 or \ entry.category.find(args.test) != -1 -def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry: api.TestEntry): +def run_entry(env: api.TestEnvironment, + config: api.TestConfig, + row: List, + entry: api.TestEntry, + update_only: bool): # Check if entry needs to be run. - if entry.status not in ('queued', 'outdated'): + if update_only and entry.status not in ('queued', 'outdated'): print_row(config, row, end='\r') return False # Run test entry. revision = entry.revision git_hash = entry.git_hash + environment = entry.environment testname = entry.test testcategory = entry.category device_type = entry.device_type @@ -116,13 +121,15 @@ def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry print_row(config, row, end='\r') executable_ok = True if len(entry.executable): - env.set_blender_executable(pathlib.Path(entry.executable)) + env.set_blender_executable(pathlib.Path(entry.executable), environment) else: env.checkout(git_hash) executable_ok = env.build() if not executable_ok: entry.status = 'failed' entry.error_msg = 'Failed to build' + else: + env.set_blender_executable(env.blender_executable, environment) # Run test and update output and status. if executable_ok: @@ -134,6 +141,8 @@ def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry if not entry.output: raise Exception("Test produced no output") entry.status = 'done' + except KeyboardInterrupt as e: + raise e except Exception as e: entry.status = 'failed' entry.error_msg = str(e) @@ -219,7 +228,7 @@ def cmd_reset(env: api.TestEnvironment, argv: List): config.queue.write() -def cmd_run(env: api.TestEnvironment, argv: List): +def cmd_run(env: api.TestEnvironment, argv: List, update_only: bool): # Run tests. parser = argparse.ArgumentParser() parser.add_argument('config', nargs='?', default=None) @@ -229,17 +238,26 @@ def cmd_run(env: api.TestEnvironment, argv: List): configs = env.get_configs(args.config) for config in configs: updated = False + cancel = False print_header(config) for row in config.queue.rows(use_revision_columns(config)): if match_entry(row[0], args): for entry in row: - if run_entry(env, config, row, entry): - updated = True - # Write queue every time in case running gets interrupted, - # so it can be resumed. - config.queue.write() + try: + if run_entry(env, config, row, entry, update_only): + updated = True + # Write queue every time in case running gets interrupted, + # so it can be resumed. + config.queue.write() + except KeyboardInterrupt as e: + cancel = True + break + print_row(config, row) + if cancel: + break + if updated: # Generate graph if test were run. json_filepath = config.base_dir / "results.json" @@ -268,8 +286,9 @@ def main(): ' \n' ' list List available tests, devices and configurations\n' ' \n' - ' run [<config>] [<test>] Execute tests for configuration\n' - ' reset [<config>] [<test>] Clear tests results from config, for re-running\n' + ' run [<config>] [<test>] Execute all tests in configuration\n' + ' update [<config>] [<test>] Execute only queued and outdated tests\n' + ' reset [<config>] [<test>] Clear tests results in configuration\n' ' status [<config>] [<test>] List configurations and their tests\n' ' \n' ' graph a.json b.json... -o out.html Create graph from results in JSON files\n') @@ -304,7 +323,9 @@ def main(): if args.command == 'list': cmd_list(env, argv) elif args.command == 'run': - cmd_run(env, argv) + cmd_run(env, argv, update_only=False) + elif args.command == 'update': + cmd_run(env, argv, update_only=True) elif args.command == 'reset': cmd_reset(env, argv) elif args.command == 'status': diff --git a/tests/performance/tests/animation.py b/tests/performance/tests/animation.py index 1a92f1a9718..c1a5c77860f 100644 --- a/tests/performance/tests/animation.py +++ b/tests/performance/tests/animation.py @@ -9,14 +9,20 @@ def _run(args): import time start_time = time.time() + elapsed_time = 0.0 + num_frames = 0 - scene = bpy.context.scene - for i in range(scene.frame_start, scene.frame_end): - scene.frame_set(scene.frame_start) + while elapsed_time < 10.0: + scene = bpy.context.scene + for i in range(scene.frame_start, scene.frame_end + 1): + scene.frame_set(i) - elapsed_time = time.time() - start_time + num_frames += scene.frame_end + 1 - scene.frame_start + elapsed_time = time.time() - start_time - result = {'time': elapsed_time} + time_per_frame = elapsed_time / num_frames + + result = {'time': time_per_frame} return result @@ -32,10 +38,10 @@ class AnimationTest(api.Test): def run(self, env, device_id): args = {} - result, _ = env.run_in_blender(_run, args) + result, _ = env.run_in_blender(_run, args, [self.filepath]) return result def generate(env): - filepaths = env.find_blend_files('animation') + filepaths = env.find_blend_files('animation/*') return [AnimationTest(filepath) for filepath in filepaths] diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 79632e49c1f..a1b94abc317 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -766,7 +766,7 @@ foreach(geo_node_test ${geo_node_tests}) ) endforeach() else() - MESSAGE(STATUS "No directory named ${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/ found, disabling test.") + MESSAGE(STATUS "Directory named ${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/ Not Found, disabling test.") endif() endforeach() diff --git a/tests/python/bl_blendfile_io.py b/tests/python/bl_blendfile_io.py index 38b3a93bbbc..4123f06b7c4 100644 --- a/tests/python/bl_blendfile_io.py +++ b/tests/python/bl_blendfile_io.py @@ -73,7 +73,7 @@ def main(): args = argparse_create().parse_args() # Don't write thumbnails into the home directory. - bpy.context.preferences.filepaths.use_save_preview_images = False + bpy.context.preferences.filepaths.file_preview_type = 'NONE' for Test in TESTS: Test(args).run_all_tests() diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py index 1d076d66913..992bf6b89d9 100644 --- a/tests/python/bl_blendfile_liblink.py +++ b/tests/python/bl_blendfile_liblink.py @@ -212,7 +212,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): assert(len(bpy.data.meshes) == 1) # This one fails currently, for unclear reasons. - # ~ assert(bpy.data.meshes[0].library is not None) + assert(bpy.data.meshes[0].library is not None) assert(bpy.data.meshes[0].users == 1) assert(len(bpy.data.objects) == 1) assert(bpy.data.objects[0].library is None) @@ -278,7 +278,7 @@ def main(): args = argparse_create().parse_args() # Don't write thumbnails into the home directory. - bpy.context.preferences.filepaths.use_save_preview_images = False + bpy.context.preferences.filepaths.file_preview_type = 'NONE' for Test in TESTS: Test(args).run_all_tests() diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py index b44e4d48564..3c7c77ce339 100644 --- a/tests/python/bl_blendfile_library_overrides.py +++ b/tests/python/bl_blendfile_library_overrides.py @@ -208,7 +208,7 @@ def main(): args = argparse_create().parse_args() # Don't write thumbnails into the home directory. - bpy.context.preferences.filepaths.use_save_preview_images = False + bpy.context.preferences.filepaths.file_preview_type = 'NONE' bpy.context.preferences.experimental.use_override_templates = True for Test in TESTS: |