diff options
206 files changed, 8544 insertions, 2890 deletions
diff --git a/build_files/cmake/platform/platform_apple.cmake b/build_files/cmake/platform/platform_apple.cmake index 43ce23081af..cdc9aa91a53 100644 --- a/build_files/cmake/platform/platform_apple.cmake +++ b/build_files/cmake/platform/platform_apple.cmake @@ -214,7 +214,7 @@ if(WITH_SDL) find_package(SDL2) set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS}) set(SDL_LIBRARY ${SDL2_LIBRARIES}) - string(APPEND PLATFORM_LINKFLAGS " -framework ForceFeedback") + string(APPEND PLATFORM_LINKFLAGS " -framework ForceFeedback -framework GameController -framework CoreHaptics") endif() set(PNG_ROOT ${LIBDIR}/png) diff --git a/doc/python_api/rst/info_gotcha.rst b/doc/python_api/rst/info_gotcha.rst index bef76a5e479..d3067eb0518 100644 --- a/doc/python_api/rst/info_gotcha.rst +++ b/doc/python_api/rst/info_gotcha.rst @@ -93,7 +93,7 @@ Consider the calculations that might contribute to the object's final transforma - Animation function curves. - Drivers and their Python expressions. - Constraints -- Parent objects and all of their F-curves, constraints, etc. +- Parent objects and all of their F-Curves, constraints, etc. To avoid expensive recalculations every time a property is modified, Blender defers the evaluation until the results are needed. @@ -802,7 +802,7 @@ Removing Data ------------- **Any** data that you remove shouldn't be modified or accessed afterwards, -this includes: F-curves, drivers, render layers, timeline markers, modifiers, constraints +this includes: F-Curves, drivers, render layers, timeline markers, modifiers, constraints along with objects, scenes, collections, bones, etc. The ``remove()`` API calls will invalidate the data they free to prevent common mistakes. diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index b074ce77a39..477d2196666 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -573,7 +573,7 @@ def example_extract_docstring(filepath): line_no += 1 file.close() - return "\n".join(text), line_no, line_no_has_content + return "\n".join(text).rstrip("\n"), line_no, line_no_has_content def title_string(text, heading_char, double=False): @@ -593,9 +593,13 @@ def write_example_ref(ident, fw, example_id, ext="py"): filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath) text, line_no, line_no_has_content = example_extract_docstring(filepath_full) + if text: + # Ensure a blank line, needed since in some cases the indentation doesn't match the previous line. + # which causes Sphinx not to warn about bad indentation. + fw("\n") + for line in text.split("\n"): + fw("%s\n" % (ident + line).rstrip()) - for line in text.split("\n"): - fw("%s\n" % (ident + line).rstrip()) fw("\n") # Some files only contain a doc-string. @@ -1147,6 +1151,9 @@ def pycontext2sphinx(basepath): fw("Note that all context values are readonly,\n") fw("but may be modified through the data API or by running operators\n\n") + # Track all unique properties to properly use `noindex`. + unique = set() + def write_contex_cls(): fw(title_string("Global Context", "-")) @@ -1164,9 +1171,11 @@ def pycontext2sphinx(basepath): # First write RNA for prop in sorted_struct_properties: - # support blacklisting props + # Support blacklisting props. if prop.identifier in struct_blacklist: continue + # No need to check if there are duplicates yet as it's known there wont be. + unique.add(prop.identifier) type_descr = prop.get_type_description( class_fmt=":class:`bpy.types.%s`", collection_id=_BPY_PROP_COLLECTION_ID) @@ -1204,7 +1213,8 @@ def pycontext2sphinx(basepath): "file_context_dir", ) - unique = set() + # Track unique for `context_strings` to validate `context_type_map`. + unique_context_strings = set() blend_cdll = ctypes.CDLL("") for ctx_str in context_strings: subsection = "%s Context" % ctx_str.split("_")[0].title() @@ -1216,22 +1226,32 @@ def pycontext2sphinx(basepath): i = 0 while char_array[i] is not None: member = ctypes.string_at(char_array[i]).decode(encoding="ascii") - fw(".. data:: %s\n\n" % member) + unique_all_len = len(unique) + unique.add(member) + member_visited = unique_all_len == len(unique) + + unique_context_strings.add(member) + + fw(".. data:: %s\n" % member) + # Avoid warnings about the member being included multiple times. + if member_visited: + fw(" :noindex:\n") + fw("\n") + try: member_type, is_seq = context_type_map[member] except KeyError: raise SystemExit("Error: context key %r not found in context_type_map; update %s" % (member, __file__)) from None fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type)) - unique.add(member) i += 1 # generate typemap... - # for member in sorted(unique): + # for member in sorted(unique_context_strings): # print(' "%s": ("", False),' % member) - if len(context_type_map) > len(unique): + if len(context_type_map) > len(unique_context_strings): warnings.warn( "Some types are not used: %s" % - str([member for member in context_type_map if member not in unique])) + str([member for member in context_type_map if member not in unique_context_strings])) else: pass # will have raised an error above diff --git a/extern/fast_float/LICENSE-MIT b/extern/fast_float/LICENSE-MIT new file mode 100644 index 00000000000..2fb2a37ad7f --- /dev/null +++ b/extern/fast_float/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2021 The fast_float authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/extern/fast_float/README.blender b/extern/fast_float/README.blender new file mode 100644 index 00000000000..a584a0511ee --- /dev/null +++ b/extern/fast_float/README.blender @@ -0,0 +1,7 @@ +Project: fast_float +URL: https://github.com/fastfloat/fast_float +License: MIT +Upstream version: 3.4.0 (b7f9d6c) +Local modifications: + +- Took only the fast_float.h header and the license/readme files diff --git a/extern/fast_float/README.md b/extern/fast_float/README.md new file mode 100644 index 00000000000..1e1c06d0a3e --- /dev/null +++ b/extern/fast_float/README.md @@ -0,0 +1,218 @@ +## fast_float number parsing library: 4x faster than strtod + +![Ubuntu 20.04 CI (GCC 9)](https://github.com/lemire/fast_float/workflows/Ubuntu%2020.04%20CI%20(GCC%209)/badge.svg) +![Ubuntu 18.04 CI (GCC 7)](https://github.com/lemire/fast_float/workflows/Ubuntu%2018.04%20CI%20(GCC%207)/badge.svg) +![Alpine Linux](https://github.com/lemire/fast_float/workflows/Alpine%20Linux/badge.svg) +![MSYS2-CI](https://github.com/lemire/fast_float/workflows/MSYS2-CI/badge.svg) +![VS16-CLANG-CI](https://github.com/lemire/fast_float/workflows/VS16-CLANG-CI/badge.svg) +[![VS16-CI](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.yml) + +The fast_float library provides fast header-only implementations for the C++ from_chars +functions for `float` and `double` types. These functions convert ASCII strings representing +decimal values (e.g., `1.3e10`) into binary types. We provide exact rounding (including +round to even). In our experience, these `fast_float` functions many times faster than comparable number-parsing functions from existing C++ standard libraries. + +Specifically, `fast_float` provides the following two functions with a C++17-like syntax (the library itself only requires C++11): + +```C++ +from_chars_result from_chars(const char* first, const char* last, float& value, ...); +from_chars_result from_chars(const char* first, const char* last, double& value, ...); +``` + +The return type (`from_chars_result`) is defined as the struct: +```C++ +struct from_chars_result { + const char* ptr; + std::errc ec; +}; +``` + +It parses the character sequence [first,last) for a number. It parses floating-point numbers expecting +a locale-independent format equivalent to the C++17 from_chars function. +The resulting floating-point value is the closest floating-point values (using either float or double), +using the "round to even" convention for values that would otherwise fall right in-between two values. +That is, we provide exact parsing according to the IEEE standard. + + +Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the +parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned +`ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + +The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + +It will parse infinity and nan values. + +Example: + +``` C++ +#include "fast_float/fast_float.h" +#include <iostream> + +int main() { + const std::string input = "3.1416 xyz "; + double result; + auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); + if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + std::cout << "parsed the number " << result << std::endl; + return EXIT_SUCCESS; +} +``` + + +Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of +the type `fast_float::chars_format`. It is a bitset value: we check whether +`fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set +to determine whether we allow the fixed point and scientific notation respectively. +The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + +The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification. +* The `from_chars` function does not skip leading white-space characters. +* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is forbidden. +* It is generally impossible to represent a decimal value exactly as binary floating-point number (`float` and `double` types). We seek the nearest value. We round to an even mantissa when we are in-between two binary floating-point numbers. + +Furthermore, we have the following restrictions: +* We only support `float` and `double` types at this time. +* We only support the decimal format: we do not support hexadecimal strings. +* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value. + +We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems. + + + +## Using commas as decimal separator + + +The C++ standard stipulate that `from_chars` has to be locale-independent. In +particular, the decimal separator has to be the period (`.`). However, +some users still want to use the `fast_float` library with in a locale-dependent +manner. Using a separate function called `from_chars_advanced`, we allow the users +to pass a `parse_options` instance which contains a custom decimal separator (e.g., +the comma). You may use it as follows. + +```C++ +#include "fast_float/fast_float.h" +#include <iostream> + +int main() { + const std::string input = "3,1416 xyz "; + double result; + fast_float::parse_options options{fast_float::chars_format::general, ','}; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + std::cout << "parsed the number " << result << std::endl; + return EXIT_SUCCESS; +} +``` + + +## Reference + +- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Pratice and Experience 51 (8), 2021. + +## Other programming languages + +- [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called `rcppfastfloat`. +- [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`. +- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`. +- [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`. + + +## Relation With Other Work + +The fastfloat algorithm is part of the [LLVM standard libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba). + +The fast_float library provides a performance similar to that of the [fast_double_parser](https://github.com/lemire/fast_double_parser) library but using an updated algorithm reworked from the ground up, and while offering an API more in line with the expectations of C++ programmers. The fast_double_parser library is part of the [Microsoft LightGBM machine-learning framework](https://github.com/microsoft/LightGBM). + +## Users + +The fast_float library is used by [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied the number parsing speed by two or three times. It is also used by [Yandex ClickHouse](https://github.com/ClickHouse/ClickHouse) and by [Google Jsonnet](https://github.com/google/jsonnet). + + +## How fast is it? + +It can parse random floating-point numbers at a speed of 1 GB/s on some systems. We find that it is often twice as fast as the best available competitor, and many times faster than many standard-library implementations. + +<img src="http://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png" width="400"> + +``` +$ ./build/benchmarks/benchmark +# parsing random integers in the range [0,1) +volume = 2.09808 MB +netlib : 271.18 MB/s (+/- 1.2 %) 12.93 Mfloat/s +doubleconversion : 225.35 MB/s (+/- 1.2 %) 10.74 Mfloat/s +strtod : 190.94 MB/s (+/- 1.6 %) 9.10 Mfloat/s +abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfloat/s +fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s +``` + +See https://github.com/lemire/simple_fastfloat_benchmark for our benchmarking code. + + +## Video + +[![Go Systems 2020](http://img.youtube.com/vi/AVXgvlMeIm4/0.jpg)](http://www.youtube.com/watch?v=AVXgvlMeIm4)<br /> + +## Using as a CMake dependency + +This library is header-only by design. The CMake file provides the `fast_float` target +which is merely a pointer to the `include` directory. + +If you drop the `fast_float` repository in your CMake project, you should be able to use +it in this manner: + +```cmake +add_subdirectory(fast_float) +target_link_libraries(myprogram PUBLIC fast_float) +``` + +Or you may want to retrieve the dependency automatically if you have a sufficiently recent version of CMake (3.11 or better at least): + +```cmake +FetchContent_Declare( + fast_float + GIT_REPOSITORY https://github.com/lemire/fast_float.git + GIT_TAG tags/v1.1.2 + GIT_SHALLOW TRUE) + +FetchContent_MakeAvailable(fast_float) +target_link_libraries(myprogram PUBLIC fast_float) + +``` + +You should change the `GIT_TAG` line so that you recover the version you wish to use. + +## Using as single header + +The script `script/amalgamate.py` may be used to generate a single header +version of the library if so desired. +Just run the script from the root directory of this repository. +You can customize the license type and output file if desired as described in +the command line help. + +You may directly download automatically generated single-header files: + +https://github.com/fastfloat/fast_float/releases/download/v1.1.2/fast_float.h + +## Credit + +Though this work is inspired by many different people, this work benefited especially from exchanges with +Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided +invaluable feedback. Rémy Oudompheng first implemented a fast path we use in the case of long digits. + +The library includes code adapted from Google Wuffs (written by Nigel Tao) which was originally published +under the Apache 2.0 license. + +## License + +<sup> +Licensed under either of <a href="LICENSE-APACHE">Apache License, Version +2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. +</sup> + +<br> + +<sub> +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this repository by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. +</sub> diff --git a/extern/fast_float/fast_float.h b/extern/fast_float/fast_float.h new file mode 100644 index 00000000000..2482dfdbd6c --- /dev/null +++ b/extern/fast_float/fast_float.h @@ -0,0 +1,2979 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License at your option. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// 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 +// + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + +#include <system_error> + +namespace fast_float { +enum chars_format { + scientific = 1<<0, + fixed = 1<<2, + hex = 1<<3, + general = fixed | scientific +}; + + +struct from_chars_result { + const char *ptr; + std::errc ec; +}; + +struct parse_options { + constexpr explicit parse_options(chars_format fmt = chars_format::general, + char dot = '.') + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + char decimal_point; +}; + +/** + * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting + * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. + * The resulting floating-point value is the closest floating-point values (using either float or double), + * using the "round to even" convention for values that would otherwise fall right in-between two values. + * That is, we provide exact parsing according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the + * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned + * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of + * the type `fast_float::chars_format`. It is a bitset value: we check whether + * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set + * to determine whether we allowe the fixed point and scientific notation respectively. + * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + */ +template<typename T> +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template<typename T> +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept; + +} +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include <cfloat> +#include <cstdint> +#include <cassert> +#include <cstring> +#include <type_traits> + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ + || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ + || defined(__MINGW64__) \ + || defined(__s390x__) \ + || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ + || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_64BIT +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ + || defined(__arm__) || defined(_M_ARM) \ + || defined(__MINGW32__)) +#define FASTFLOAT_32BIT +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. + // We can never tell the register width, but the SIZE_MAX is a good approximation. + // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. + #if SIZE_MAX == 0xffff + #error Unknown platform (16-bit, unsupported) + #elif SIZE_MAX == 0xffffffff + #define FASTFLOAT_32BIT + #elif SIZE_MAX == 0xffffffffffffffff + #define FASTFLOAT_64BIT + #else + #error Unknown platform (not 32-bit, not 64-bit?) + #endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) +#include <intrin.h> +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#ifdef _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include <machine/endian.h> +#elif defined(sun) || defined(__sun) +#include <sys/byteorder.h> +#else +#include <endian.h> +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#include <cassert> +#define FASTFLOAT_DEBUG_ASSERT(x) assert(x) +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) { if (!(x)) return false; } + +namespace fast_float { + +// Compares two ASCII strings in a case insensitive manner. +inline bool fastfloat_strncasecmp(const char *input1, const char *input2, + size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; i++) { + running_diff |= (input1[i] ^ input2[i]); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template <typename T> +struct span { + const T* ptr; + size_t length; + span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} + span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { + return length; + } + + const T& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + value128() : low(0), high(0) {} +}; + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline int leading_zeroes(uint64_t input_num) { + assert(input_num > 0); +#ifdef FASTFLOAT_VISUAL_STUDIO + #if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); + #else + int last_bit = 0; + if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32; + if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16; + if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8; + if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4; + if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2; + if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1; + return 63 - last_bit; + #endif +#else + return __builtin_clzll(input_num); +#endif +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, + uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + + +// compute 64-bit a*b +fastfloat_really_inline value128 full_multiplication(uint64_t a, + uint64_t b) { + value128 answer; +#ifdef _M_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emulate + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + #error Not implemented +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +constexpr static double powers_of_ten_double[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; +constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, + 1e6, 1e7, 1e8, 1e9, 1e10}; + +template <typename T> struct binary_format { + using equiv_uint = typename std::conditional<sizeof(T) == 4, uint32_t, uint64_t>::type; + + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int min_exponent_fast_path(); + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(); + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); + static inline constexpr size_t max_digits(); + static inline constexpr equiv_uint exponent_mask(); + static inline constexpr equiv_uint mantissa_mask(); + static inline constexpr equiv_uint hidden_bit_mask(); +}; + +template <> inline constexpr int binary_format<double>::mantissa_explicit_bits() { + return 52; +} +template <> inline constexpr int binary_format<float>::mantissa_explicit_bits() { + return 23; +} + +template <> inline constexpr int binary_format<double>::max_exponent_round_to_even() { + return 23; +} + +template <> inline constexpr int binary_format<float>::max_exponent_round_to_even() { + return 10; +} + +template <> inline constexpr int binary_format<double>::min_exponent_round_to_even() { + return -4; +} + +template <> inline constexpr int binary_format<float>::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format<double>::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format<float>::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format<double>::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format<float>::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format<double>::sign_index() { return 63; } +template <> inline constexpr int binary_format<float>::sign_index() { return 31; } + +template <> inline constexpr int binary_format<double>::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} +template <> inline constexpr int binary_format<float>::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> inline constexpr int binary_format<double>::max_exponent_fast_path() { + return 22; +} +template <> inline constexpr int binary_format<float>::max_exponent_fast_path() { + return 10; +} + +template <> inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr double binary_format<double>::exact_power_of_ten(int64_t power) { + return powers_of_ten_double[power]; +} +template <> +inline constexpr float binary_format<float>::exact_power_of_ten(int64_t power) { + + return powers_of_ten_float[power]; +} + + +template <> +inline constexpr int binary_format<double>::largest_power_of_ten() { + return 308; +} +template <> +inline constexpr int binary_format<float>::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format<double>::smallest_power_of_ten() { + return -342; +} +template <> +inline constexpr int binary_format<float>::smallest_power_of_ten() { + return -65; +} + +template <> inline constexpr size_t binary_format<double>::max_digits() { + return 769; +} +template <> inline constexpr size_t binary_format<float>::max_digits() { + return 114; +} + +template <> inline constexpr binary_format<float>::equiv_uint + binary_format<float>::exponent_mask() { + return 0x7F800000; +} +template <> inline constexpr binary_format<double>::equiv_uint + binary_format<double>::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> inline constexpr binary_format<float>::equiv_uint + binary_format<float>::mantissa_mask() { + return 0x007FFFFF; +} +template <> inline constexpr binary_format<double>::equiv_uint + binary_format<double>::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> inline constexpr binary_format<float>::equiv_uint + binary_format<float>::hidden_bit_mask() { + return 0x00800000; +} +template <> inline constexpr binary_format<double>::equiv_uint + binary_format<double>::hidden_bit_mask() { + return 0x0010000000000000; +} + +template<typename T> +fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { + uint64_t word = am.mantissa; + word |= uint64_t(am.power2) << binary_format<T>::mantissa_explicit_bits(); + word = negative + ? word | (uint64_t(1) << binary_format<T>::sign_index()) : word; +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + if (std::is_same<T, float>::value) { + ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian + } else { + ::memcpy(&value, &word, sizeof(T)); + } +#else + // For little-endian systems: + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include <cctype> +#include <cstdint> +#include <cstring> +#include <iterator> + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +typedef span<const char> byte_span; + +struct parsed_number_string { + int64_t exponent{0}; + uint64_t mantissa{0}; + const char *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + byte_span integer{}; // non-nullable + byte_span fraction{}; // nullable +}; + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = byte_span(start_digits, size_t(digit_count)); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char* before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = byte_span(before, size_t(p - before)); + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + const char* int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + const char* frac_end = p + answer.fraction.len(); + while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +#include <cstdint> + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^−1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template <class unused = void> +struct powers_template { + +constexpr static int smallest_power_of_five = binary_format<double>::smallest_power_of_ten(); +constexpr static int largest_power_of_five = binary_format<double>::largest_power_of_ten(); +constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); +// Powers of five from 5^-342 all the way to 5^308 rounded toward one. +static const uint64_t power_of_five_128[number_of_entries]; +}; + +template <class unused> +const uint64_t powers_template<unused>::power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a,0x113faa2906a13b3f, + 0x9558b4661b6565f8,0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, + 0xe95a99df8ace6f53,0xf4d82c2c107973dc, + 0x91d8a02bb6c10594,0x79071b9b8a4be869, + 0xb64ec836a47146f9,0x9748e2826cdee284, + 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f,0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723,0xad2c788035e61382, + 0x8b16fb203055ac76,0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78,0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b,0x8672648c40e5ad68, + 0xa9c98d8ccb009506,0x680efdaf511f18c2, + 0xd43bf0effdc0ba48,0x212bd1b2566def2, + 0x84a57695fe98746d,0x14bb630f7604b57, + 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, + 0xcf42894a5dce35ea,0x52064cac828675b9, + 0x818995ce7aa0e1b2,0x7343efebd1940993, + 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6,0xd41a26e077774ef6, + 0xfd00b897478238d0,0x8920b098955522b4, + 0x9e20735e8cb16382,0x55b46e5f5d5535b0, + 0xc5a890362fddbc62,0xeb2189f734aa831d, + 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d,0x47b233c92125366e, + 0xc1069cd4eabe89f8,0x999ec0bb696e840a, + 0xf148440a256e2c76,0xc00670ea43ca250d, + 0x96cd2a865764dbca,0x380406926a5e5728, + 0xbc807527ed3e12bc,0xc605083704f5ecf2, + 0xeba09271e88d976b,0xf7864a44c633682e, + 0x93445b8731587ea3,0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c,0x5960ea05bad82964, + 0xe61acf033d1a45df,0x6fb92487298e33bd, + 0x8fd0c16206306bab,0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696,0x8f48a4899877186c, + 0xe0b62e2929aba83c,0x331acdabfe94de87, + 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, + 0x892731ac9faf056e,0xbe311c083a225cd2, + 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, + 0xd64d3d9db981787d,0x92cbbccdad5b108, + 0x85f0468293f0eb4e,0x25bbf56008c58ea5, + 0xa76c582338ed2621,0xaf2af2b80af6f24e, + 0xd1476e2c07286faa,0x1af5af660db4aee1, + 0x82cca4db847945ca,0x50d98d9fc890ed4d, + 0xa37fce126597973c,0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1,0x77b191618c54e9ac, + 0xc795830d75038c1d,0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, + 0x9becce62836ac577,0x4ee367f9430aec32, + 0xc2e801fb244576d5,0x229c41f793cda73f, + 0xf3a20279ed56d48a,0x6b43527578c1110f, + 0x9845418c345644d6,0x830a13896b78aaa9, + 0xbe5691ef416bd60c,0x23cc986bc656d553, + 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, + 0x91376c36d99995be,0x23100809b9c21fa1, + 0xb58547448ffffb2d,0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9,0x16c90c8f323f516c, + 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, + 0xb1442798f49ffb4a,0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d,0x40405643d711d583, + 0x8a7d3eef7f1cfc52,0x482835ea666b2572, + 0xad1c8eab5ee43b66,0xda3243650005eecf, + 0xd863b256369d4a40,0x90bed43e40076a82, + 0x873e4f75e2224e68,0x5a7744a6e804a291, + 0xa90de3535aaae202,0x711515d0a205cb36, + 0xd3515c2831559a83,0xd5a5b44ca873e03, + 0x8412d9991ed58091,0xe858790afe9486c2, + 0xa5178fff668ae0b6,0x626e974dbe39a872, + 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, + 0xa139029f6a239f72,0x1c1fffc1ebc44e80, + 0xc987434744ac874e,0xa327ffb266b56220, + 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, + 0xc4ce17b399107c22,0xcb550fb4384d21d3, + 0xf6019da07f549b2b,0x7e2a53a146606a48, + 0x99c102844f94e0fb,0x2eda7444cbfc426d, + 0xc0314325637a1939,0xfa911155fefb5308, + 0xf03d93eebc589f88,0x793555ab7eba27ca, + 0x96267c7535b763b5,0x4bc1558b2f3458de, + 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb,0x465e15a979c1cadc, + 0x92a1958a7675175f,0xbfacd89ec191ec9, + 0xb749faed14125d36,0xcef980ec671f667b, + 0xe51c79a85916f484,0x82b7e12780e7401a, + 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9,0x67a791e093e1d49a, + 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d,0x58fae9f773886e18, + 0xda7f5bf590966848,0xaf39a475506a899e, + 0x888f99797a5e012d,0x6d8406c952429603, + 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26,0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, + 0xd0601d8efc57b08b,0xf13b94daf124da26, + 0x823c12795db6ce57,0x76c53d08d6b70858, + 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02,0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a,0x359ab6419ca1091b, + 0xf867241c8cc6d4c0,0xc30163d203c94b62, + 0x9b407691d7fc44f8,0x79e0de63425dcf1d, + 0xc21094364dfb5636,0x985915fc12f542e4, + 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, + 0xbd8430bd08277231,0x50c6ff782a838353, + 0xece53cec4a314ebd,0xa4f8bf5635246428, + 0x940f4613ae5ed136,0x871b7795e136be99, + 0xb913179899f68584,0x28e2557b59846e3f, + 0xe757dd7ec07426e5,0x331aeada2fe589cf, + 0x9096ea6f3848984f,0x3ff0d2c85def7621, + 0xb4bca50b065abe63,0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, + 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, + 0xb080392cc4349dec,0xbd8d794d96aacfb3, + 0xdca04777f541c567,0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60,0xf41686c49db57244, + 0xac5d37d5b79b6239,0x311c2875c522ced5, + 0xd77485cb25823ac7,0x7d633293366b828b, + 0x86a8d39ef77164bc,0xae5dff9c02033197, + 0xa8530886b54dbdeb,0xd9f57f830283fdfc, + 0xd267caa862a12d66,0xd072df63c324fd7b, + 0x8380dea93da4bc60,0x4247cb9e59f71e6d, + 0xa46116538d0deb78,0x52d9be85f074e608, + 0xcd795be870516656,0x67902e276c921f8b, + 0x806bd9714632dff6,0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c,0x796b805720085f81, + 0x9cc3a6eec6311a63,0xcbe3303674053bb0, + 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b,0xee92fb5515482d44, + 0x991711052d8bf3c5,0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6,0xd262d45a78a0635d, + 0xef340a98172aace4,0x86fb897116c87c34, + 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, + 0xbae0a846d2195712,0x8974836059cca109, + 0xe998d258869facd7,0x2bd1a438703fc94b, + 0x91ff83775423cc06,0x7b6306a34627ddcf, + 0xb67f6455292cbf08,0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, + 0x8e938662882af53e,0x547eb47b7282ee9c, + 0xb23867fb2a35b28d,0xe99e619a4f23aa43, + 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, + 0xae0b158b4738705e,0x9624ab50b148d445, + 0xd98ddaee19068c76,0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b,0x7647c3200069671f, + 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, + 0xa5fb0a17c777cf09,0xf468107100525890, + 0xcf79cc9db955c2cc,0x7182148d4066eeb4, + 0x81ac1fe293d599bf,0xc6f14cd848405530, + 0xa21727db38cb002f,0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, + 0xfd442e4688bd304a,0x908f4a166d1da663, + 0x9e4a9cec15763e2e,0x9a598e4e043287fe, + 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, + 0xf7549530e188c128,0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, + 0xc13a148e3032d6e7,0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, + 0xebdf661791d60f56,0x111b495b3464ad21, + 0x936b9fcebb25c995,0xcab10dd900beec34, + 0xb84687c269ef3bfb,0x3d5d514f40eea742, + 0xe65829b3046b0afa,0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, + 0xb3f4e093db73a093,0x59ed216765690f56, + 0xe0f218b8d25088b8,0x306869c13ec3532c, + 0x8c974f7383725573,0x1e414218c73a13fb, + 0xafbd2350644eeacf,0xe5d1929ef90898fa, + 0xdbac6c247d62a583,0xdf45f746b74abf39, + 0x894bc396ce5da772,0x6b8bba8c328eb783, + 0xab9eb47c81f5114f,0x66ea92f3f326564, + 0xd686619ba27255a2,0xc80a537b0efefebd, + 0x8613fd0145877585,0xbd06742ce95f5f36, + 0xa798fc4196e952e7,0x2c48113823b73704, + 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, + 0x82ef85133de648c4,0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3,0x318df905079926a8, + 0xffbbcfe994e5c61f,0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d,0x6bea10ca65c084e, + 0xc31bfa0fe5698db8,0x486e494fcff30a62, + 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7,0xf89629465a75e01c, + 0xbe89523386091465,0xf6bbb397f1135823, + 0xee2ba6c0678b597f,0x746aa07ded582e2c, + 0x94db483840b717ef,0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb,0x92f34d62616ce413, + 0xe896a0d7e51e1566,0x77b020baf9c81d17, + 0x915e2486ef32cd60,0xace1474dc1d122e, + 0xb5b5ada8aaff80b8,0xd819992132456ba, + 0xe3231912d5bf60e6,0x10e1fff697ed6c69, + 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d,0x86c16c98d2c953c6, + 0xd89d64d57a607744,0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b,0x11471cd764ad4972, + 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, + 0xd389b47879823479,0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb,0xcedf722a585139ba, + 0xa54394fe1eedb8fe,0xc2974eb4ee658828, + 0xce947a3da6a9273e,0x733d226229feea32, + 0x811ccc668829b887,0x806357d5a3f525f, + 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052,0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67,0xbbac2078d443ace2, + 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, + 0xc5029163f384a931,0xa9e795e65d4df11, + 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e,0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, + 0xf07da27a82c37088,0x5d767327bb4e5a4c, + 0x964e858c91ba2655,0x3a6a07f8d510f86f, + 0xbbe226efb628afea,0x890489f70a55368b, + 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb,0x9ce6ebb40173744, + 0xe55990879ddcaabd,0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6,0x9fa946824a12232d, + 0xb32df8e9f3546564,0x47939822dc96abf9, + 0xdff9772470297ebd,0x59787e2b93bc56f7, + 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, + 0xaefae51477a06b03,0xede622920b6b23f1, + 0xdab99e59958885c4,0xe95fab368e45eced, + 0x88b402f7fd75539b,0x11dbcb0218ebb414, + 0xaae103b5fcd2a881,0xd652bdc29f26a119, + 0xd59944a37c0752a2,0x4be76d3346f0495f, + 0x857fcae62d8493a5,0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2,0x7e2000a41346a7a7, + 0x825ecc24c873782f,0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b,0x728900802f0f32fa, + 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc,0xe2f610c84987bfa8, + 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143,0x91503d1c79720dbb, + 0xf8a95fcf88747d94,0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, + 0xc24452da229b021b,0xfbe85badce996168, + 0xf2d56790ab41c2a2,0xfae27299423fb9c3, + 0x97c560ba6b0919a5,0xdccd879fc967d41a, + 0xbdb6b8e905cb600f,0x5400e987bbc1c920, + 0xed246723473e3813,0x290123e9aab23b68, + 0x9436c0760c86e30b,0xf9a0b6720aaf6521, + 0xb94470938fa89bce,0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2,0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232,0x25c6da63c38de1b0, + 0x8d590723948a535f,0x579c487e5a38ad0e, + 0xb0af48ec79ace837,0x2d835a9df0c6d851, + 0xdcdb1b2798182244,0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5,0xe272467e3d222f3f, + 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea,0x98e947129fc2b4e9, + 0xa87fea27a539e9a5,0x3f2398d747b36224, + 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89,0x1953cf68300424ac, + 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, + 0xcdb02555653131b6,0x3792f412cb06794d, + 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b,0xf245825a5a445275, + 0xfb158592be068d2e,0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d,0x55464dd69685606b, + 0xc428d05aa4751e4c,0xaa97e14c3c26b886, + 0xf53304714d9265df,0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab,0xe546a8038efe4029, + 0xbf8fdb78849a5f96,0xde98520472bdd033, + 0xef73d256a5c0f77c,0x963e66858f6d4440, + 0x95a8637627989aad,0xdde7001379a44aa8, + 0xbb127c53b17ec159,0x5560c018580d5d52, + 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, + 0x9226712162ab070d,0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, + 0xb267ed1940f1c61c,0x55f038b237591ed3, + 0xdf01e85f912e37a3,0x6b6c46dec52f6688, + 0x8b61313bbabce2c6,0x2323ac4b3b3da015, + 0xae397d8aa96c1b77,0xabec975e0a0d081a, + 0xd9c7dced53c72255,0x96e7bd358c904a21, + 0x881cea14545c7575,0x7e50d64177da2e54, + 0xaa242499697392d2,0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, + 0x84ec3c97da624ab4,0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba,0x67de18eda5814af2, + 0x81ceb32c4b43fcf4,0x80eacf948770ced7, + 0xa2425ff75e14fc31,0xa1258379a94d028d, + 0xcad2f7f5359a3b3e,0x96ee45813a04330, + 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a819e, + 0xf79687aed3eec551,0x3a83ddbd83f52205, + 0x9abe14cd44753b52,0xc4926a9672793543, + 0xc16d9a0095928a27,0x75b7053c0f178294, + 0xf1c90080baf72cb1,0x5324c68b12dd6339, + 0x971da05074da7bee,0xd3f6fc16ebca5e04, + 0xbce5086492111aea,0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07,0x3aff322e62439fd0, + 0xb877aa3236a4b449,0x9befeb9fad487c3, + 0xe69594bec44de15b,0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9,0xf9d37014bf60a11, + 0xb424dc35095cd80f,0x538484c19ef38c95, + 0xe12e13424bb40e13,0x2865a5f206b06fba, + 0x8cbccc096f5088cb,0xf93f87b7442e45d4, + 0xafebff0bcb24aafe,0xf78f69a51539d749, + 0xdbe6fecebdedd5be,0xb573440e5a884d1c, + 0x89705f4136b4a597,0x31680a88f8953031, + 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc,0x3d32907604691b4d, + 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, + 0xa7c5ac471b478423,0xfcf80dc33721d54, + 0xd1b71758e219652b,0xd3c36113404ea4a9, + 0x83126e978d4fdf3b,0x645a1cac083126ea, + 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, + 0xcccccccccccccccc,0xcccccccccccccccd, + 0x8000000000000000,0x0, + 0xa000000000000000,0x0, + 0xc800000000000000,0x0, + 0xfa00000000000000,0x0, + 0x9c40000000000000,0x0, + 0xc350000000000000,0x0, + 0xf424000000000000,0x0, + 0x9896800000000000,0x0, + 0xbebc200000000000,0x0, + 0xee6b280000000000,0x0, + 0x9502f90000000000,0x0, + 0xba43b74000000000,0x0, + 0xe8d4a51000000000,0x0, + 0x9184e72a00000000,0x0, + 0xb5e620f480000000,0x0, + 0xe35fa931a0000000,0x0, + 0x8e1bc9bf04000000,0x0, + 0xb1a2bc2ec5000000,0x0, + 0xde0b6b3a76400000,0x0, + 0x8ac7230489e80000,0x0, + 0xad78ebc5ac620000,0x0, + 0xd8d726b7177a8000,0x0, + 0x878678326eac9000,0x0, + 0xa968163f0a57b400,0x0, + 0xd3c21bcecceda100,0x0, + 0x84595161401484a0,0x0, + 0xa56fa5b99019a5c8,0x0, + 0xcecb8f27f4200f3a,0x0, + 0x813f3978f8940984,0x4000000000000000, + 0xa18f07d736b90be5,0x5000000000000000, + 0xc9f2c9cd04674ede,0xa400000000000000, + 0xfc6f7c4045812296,0x4d00000000000000, + 0x9dc5ada82b70b59d,0xf020000000000000, + 0xc5371912364ce305,0x6c28000000000000, + 0xf684df56c3e01bc6,0xc732000000000000, + 0x9a130b963a6c115c,0x3c7f400000000000, + 0xc097ce7bc90715b3,0x4b9f100000000000, + 0xf0bdc21abb48db20,0x1e86d40000000000, + 0x96769950b50d88f4,0x1314448000000000, + 0xbc143fa4e250eb31,0x17d955a000000000, + 0xeb194f8e1ae525fd,0x5dcfab0800000000, + 0x92efd1b8d0cf37be,0x5aa1cae500000000, + 0xb7abc627050305ad,0xf14a3d9e40000000, + 0xe596b7b0c643c719,0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f,0xe4820023a2000000, + 0xb35dbf821ae4f38b,0xdda2802c8a800000, + 0xe0352f62a19e306e,0xd50b2037ad200000, + 0x8c213d9da502de45,0x4526f422cc340000, + 0xaf298d050e4395d6,0x9670b12b7f410000, + 0xdaf3f04651d47b4c,0x3c0cdd765f114000, + 0x88d8762bf324cd0f,0xa5880a69fb6ac800, + 0xab0e93b6efee0053,0x8eea0d047a457a00, + 0xd5d238a4abe98068,0x72a4904598d6d880, + 0x85a36366eb71f041,0x47a6da2b7f864750, + 0xa70c3c40a64e6c51,0x999090b65f67d924, + 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, + 0x82818f1281ed449f,0xbff8f10e7a8921a4, + 0xa321f2d7226895c7,0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, + 0xfee50b7025c36a08,0x2f236d04753d5b4, + 0x9f4f2726179a2245,0x1d762422c946590, + 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, + 0x9b934c3b330c8577,0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a,0x8bef464e3945ef7a, + 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, + 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436,0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44,0x60dbbca87196b616, + 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, + 0xb51d13aea4a488dd,0x6babab6398bdbe41, + 0xe264589a4dcdab14,0xc696963c7eed2dd1, + 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8,0x3b25a55f43294bcb, + 0xdd15fe86affad912,0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab,0x6e3569326c784337, + 0xacb92ed9397bf996,0x49c2c37f07965404, + 0xd7e77a8f87daf7fb,0xdc33745ec97be906, + 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, + 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b,0xf50a3fa490c30190, + 0x83c7088e1aab65db,0x792667c6da79e0fa, + 0xa4b8cab1a1563f52,0x577001b891185938, + 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, + 0x80b05e5ac60b6178,0x544f8158315b05b4, + 0xa0dc75f1778e39d6,0x696361ae3db1c721, + 0xc913936dd571c84c,0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f,0x4ab48a04065c723, + 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, + 0xc45d1df942711d9a,0x3ba5d0bd324f8394, + 0xf5746577930d6500,0xca8f44ec7ee36479, + 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5,0xbba1f1d158724a12, + 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, + 0xea1575143cf97226,0xf52d09d71a3293bd, + 0x924d692ca61be758,0x593c2626705f9c56, + 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, + 0xe498f455c38b997a,0xb6dfb9c0f956447, + 0x8edf98b59a373fec,0x4724bd4189bd5eac, + 0xb2977ee300c50fe7,0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, + 0x8b865b215899f46c,0xbd79e0d20082ee74, + 0xae67f1e9aec07187,0xecd8590680a3aa11, + 0xda01ee641a708de9,0xe80e6f4820cc9495, + 0x884134fe908658b2,0x3109058d147fdcdd, + 0xaa51823e34a7eede,0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, + 0x850fadc09923329e,0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45,0x84db8346b786151c, + 0xcfe87f7cef46ff16,0xe612641865679a63, + 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749,0xe3be5e330f38f09d, + 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, + 0xc646d63501a1511d,0xb281e1fd541501b8, + 0xf7d88bc24209a565,0x1f225a7ca91a4226, + 0x9ae757596946075f,0x3375788de9b06958, + 0xc1a12d2fc3978937,0x52d6b1641c83ae, + 0xf209787bb47d6b84,0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332,0xf840b7ba963646e0, + 0xbd176620a501fbff,0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf,0xc66f336c36b10137, + 0xb8a8d9bbe123f017,0xb80b0047445d4184, + 0xe6d3102ad96cec1d,0xa60dc059157491e5, + 0x9043ea1ac7e41392,0x87c89837ad68db2f, + 0xb454e4a179dd1877,0x29babe4598c311fb, + 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d,0x1899e4a65f58660c, + 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d,0x76707543f4fa1f73, + 0x899504ae72497eba,0x6a06494a791c53a8, + 0xabfa45da0edbde69,0x487db9d17636892, + 0xd6f8d7509292d603,0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, + 0xa7f26836f282b732,0x8e6cac7768d7141e, + 0xd1ef0244af2364ff,0x3207d795430cd926, + 0x8335616aed761f1f,0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, + 0xcd036837130890a1,0x36dba887c37a8c0f, + 0x802221226be55a64,0xc2494954da2c9789, + 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d,0x6f92829494e5acc7, + 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, + 0x9c69a97284b578d7,0xff2a760414536efb, + 0xc38413cf25e2d70d,0xfef5138519684aba, + 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, + 0x98bf2f79d5993802,0xef2f773ffbd97a61, + 0xbeeefb584aff8603,0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2,0xdd945a747bf26183, + 0xba756174393d88df,0x94f971119aeef9e4, + 0xe912b9d1478ceb17,0x7a37cd5601aab85d, + 0x91abb422ccb812ee,0xac62e055c10ab33a, + 0xb616a12b7fe617aa,0x577b986b314d6009, + 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d,0x14588f13be847307, + 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee,0x25de7bb9480d5854, + 0xada72ccc20054ae9,0xaf561aa79a10ae6a, + 0xd910f7ff28069da4,0x1b2ba1518094da04, + 0x87aa9aff79042286,0x90fb44d2f05d0842, + 0xa99541bf57452b28,0x353a1607ac744a53, + 0xd3fa922f2d1675f2,0x42889b8997915ce8, + 0x847c9b5d7c2e09b7,0x69956135febada11, + 0xa59bc234db398c25,0x43fab9837e699095, + 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, + 0x8161afb94b44f57d,0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc,0x6462d92a69731732, + 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78,0x5cda735244c3d43e, + 0x9defbf01b061adab,0x3a0888136afa64a7, + 0xc56baec21c7a1916,0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b,0x8aad549e57273d45, + 0x9a3c2087a63f6399,0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, + 0x969eb7c47859e743,0x9f644ae5a4b1b325, + 0xbc4665b596706114,0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8,0x9a7f12442d588f2, + 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, + 0x8fa475791a569d10,0xf96e017d694487bc, + 0xb38d92d760ec4455,0x37c981dcc395a9ac, + 0xe070f78d3927556a,0x85bbe253f47b1417, + 0x8c469ab843b89562,0x93956d7478ccec8e, + 0xaf58416654a6babb,0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, + 0x88fcf317f22241e2,0x441fece3bdf81f03, + 0xab3c2fddeeaad25a,0xd527e81cad7626c3, + 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, + 0x85c7056562757456,0xf6872d5667844e49, + 0xa738c6bebb12d16c,0xb428f8ac016561db, + 0xd106f86e69d785c7,0xe13336d701beba52, + 0x82a45b450226b39c,0xecc0024661173473, + 0xa34d721642b06084,0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, + 0xff290242c83396ce,0x7e67047175a15271, + 0x9f79a169bd203e41,0xf0062c6e984d386, + 0xc75809c42c684dd1,0x52c07b78a3e60868, + 0xf92e0c3537826145,0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb,0x88a66076400bb691, + 0xc2abf989935ddbfe,0x6acff893d00ea435, + 0xf356f7ebf83552fe,0x583f6b8c4124d43, + 0x98165af37b2153de,0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, + 0xeda2ee1c7064130c,0x1162def06f79df73, + 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, + 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0,0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, + 0x8da471a9de737e24,0x5ceaecfed289e5d2, + 0xb10d8e1456105dad,0x7425a83e872c5f47, + 0xdd50f1996b947518,0xd12f124e28f77719, + 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b,0x636cc64d1001550b, + 0xd8210befd30efa5a,0x3c47f7e05401aa4e, + 0x8714a775e3e95c78,0x65acfaec34810a71, + 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, + 0xd31045a8341ca07c,0x1ede48111209a050, + 0x83ea2b892091e44d,0x934aed0aab460432, + 0xa4e4b66b68b65d60,0xf81da84d5617853f, + 0xce1de40642e3f4b9,0x36251260ab9d668e, + 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, + 0xa1075a24e4421730,0xb24cf65b8612f81f, + 0xc94930ae1d529cfc,0xdee033f26797b627, + 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, + 0x9d412e0806e88aa5,0x8e1f289560ee864e, + 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2,0xae10af696774b1db, + 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f,0x17fd090a58d32af3, + 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, + 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513,0x84c86189216dc5ed, + 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515,0xfabaf3feaa5334a, + 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8,0x743e20e9ef511012, + 0xdf78e4b2bd342cf6,0x914da9246b255416, + 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, + 0xae9672aba3d0c320,0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, + 0x8865899617fb1871,0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, + 0xd51ea6fa85785631,0x552a74227f3ea565, + 0x8533285c936b35de,0xd53a88958f87275f, + 0xa67ff273b8460356,0x8a892abaf368f137, + 0xd01fef10a657842c,0x2d2b7569b0432d85, + 0x8213f56a67f6b29b,0x9c3b29620e29fc73, + 0xa298f2c501f45f42,0x8349f3ba91b47b8f, + 0xcb3f2f7642717713,0x241c70a936219a73, + 0xfe0efb53d30dd4d7,0xed238cd383aa0110, + 0x9ec95d1463e8a506,0xf4363804324a40aa, + 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da,0xdd94b7868e94050a, + 0x9b10a4e5e9913128,0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf,0xbc633b39673c8cec, + 0x976e41088617ca01,0xd5be0503e085d813, + 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, + 0xec9c459d51852ba2,0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45,0xcabb90e5c942b503, + 0xb8da1662e7b00a17,0x3d6a751f3b936243, + 0xe7109bfba19c0c9d,0xcc512670a783ad4, + 0x906a617d450187e2,0x27fb2b80668b24c5, + 0xb484f9dc9641e9da,0xb1f9f660802dedf6, + 0xe1a63853bbd26451,0x5e7873f8a0396973, + 0x8d07e33455637eb2,0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7,0x7641a140cc7810fb, + 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, + 0xac2820d9623bf429,0x546345fa9fbdcd44, + 0xd732290fbacaf133,0xa97c177947ad4095, + 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, + 0xa81f301449ee8c70,0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c,0x73832eec6fff3111, + 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, + 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, + 0xa0555e361951c366,0xd7e105bcc332621f, + 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, + 0xfa856334878fc150,0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07,0xa862f80ec4700c8, + 0xf4a642e14c6262c8,0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, + 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, + 0xeeea5d5004981478,0x1858ccfce06cac74, + 0x95527a5202df0ccb,0xf37801e0c43ebc8, + 0xbaa718e68396cffd,0xd30560258f54e6ba, + 0xe950df20247c83fd,0x47c6b82ef32a2069, + 0x91d28b7416cdd27e,0x4cdc331d57fa5441, + 0xb6472e511c81471d,0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5,0x58180fddd97723a6, + 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; +using powers = powers_template<>; + +} + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include <cfloat> +#include <cinttypes> +#include <cmath> +#include <cstdint> +#include <cstdlib> +#include <cstring> + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating +// the result, with the "high" part corresponding to the most significant bits and the +// low part corresponding to the least significant bits. +// +template <int bit_precision> +fastfloat_really_inline +value128 compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact because + // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); + // gives the exact answer. + value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); + constexpr uint64_t precision_mask = (bit_precision < 64) ? + (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. + value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if(secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ + constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; + } +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template <typename binary> +fastfloat_really_inline +adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template <typename binary> +fastfloat_really_inline +adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); + return compute_error_scaled<binary>(q, product.high, lz); +} + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be packed. +// However, in some very rare cases, the computation will fail. In such cases, we +// return an adjusted_mantissa with a negative power of 2: the caller should recompute +// in such cases. +template <typename binary> +fastfloat_really_inline +adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) + + value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); + if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further + // In some very rare cases, this could happen, in which case we might need a more accurate + // computation that what we can provide cheaply. This is very, very unlikely. + // + const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, + // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. + if(!inside_safe_exponent) { + return compute_error_scaled<binary>(q, product.high, lz); + } + } + // The "compute_product_approximation" function can be slightly slower than a branchless approach: + // value128 product = compute_product(q, w); + // but in practice, we can win big with the compute_product_approximation if its additional branch + // is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + + answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go back!!! + if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include <algorithm> +#include <cstdint> +#include <climits> +#include <cstring> + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span<limb> limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template <uint16_t size> +struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(const stackvec &) = delete; + stackvec &operator=(const stackvec &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + limb& operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + const limb& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + // index from the end of the container + const limb& rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + void set_len(size_t len) noexcept { + length = uint16_t(len); + } + constexpr size_t len() const noexcept { + return length; + } + constexpr bool is_empty() const noexcept { + return length == 0; + } + constexpr size_t capacity() const noexcept { + return size; + } + // append item to vector, without bounds checking + void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + // append item to vector, returning if item was added + bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + // add items to the vector, from a span, without bounds checking + void extend_unchecked(limb_span s) noexcept { + limb* ptr = data + length; + ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len()); + set_len(len() + s.len()); + } + // try to add items to the vector, returning if items were added + bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb* first = data + len(); + limb* last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + // try to resize the vector, returning if the vector was resized. + bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + // normalize the big integer, so most-significant zero limbs are removed. + void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline +uint64_t empty_hi64(bool& truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline +uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline +uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline +limb scalar_add(limb x, limb y, bool& overflow) noexcept { + limb z; + +// gcc and clang +#if defined(__has_builtin) + #if __has_builtin(__builtin_add_overflow) + overflow = __builtin_add_overflow(x, y, &z); + return z; + #endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline +limb scalar_mul(limb x, limb y, limb& carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + #if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); + #else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; + #endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template <uint16_t size> +inline bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template <uint16_t size> +fastfloat_really_inline bool small_add(stackvec<size>& vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template <uint16_t size> +inline bool small_mul(stackvec<size>& vec, limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template <uint16_t size> +bool large_add_from(stackvec<size>& x, limb_span y, size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template <uint16_t size> +fastfloat_really_inline bool large_add_from(stackvec<size>& x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template <uint16_t size> +bool long_mul(stackvec<size>& x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec<size> z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec<size> zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template <uint16_t size> +bool large_mul(stackvec<size>& x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint { + // storage of the limbs, in little-endian order. + stackvec<bigint_limbs> vec; + + bigint(): vec() {} + bigint(const bigint &) = delete; + bigint &operator=(const bigint &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + bigint(uint64_t value): vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + uint64_t hi64(bool& truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + int compare(const bigint& other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb* dst = vec.data + n; + const limb* src = vec.data; + ::memmove(dst, src, sizeof(limb) * vec.len()); + // fill in empty limbs + limb* first = vec.data; + limb* last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + bool mul(limb y) noexcept { + return small_mul(vec, y); + } + + bool add(limb y) noexcept { + return small_add(vec, y); + } + + // multiply as if by 2 raised to a power. + bool pow2(uint32_t exp) noexcept { + return shl(exp); + } + + // multiply as if by 5 raised to a power. + bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, + 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, + 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, + 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, + 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, + 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp]))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include <cctype> +#include <cstdint> +#include <cstring> +#include <iterator> + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +typedef span<const char> byte_span; + +struct parsed_number_string { + int64_t exponent{0}; + uint64_t mantissa{0}; + const char *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + byte_span integer{}; // non-nullable + byte_span fraction{}; // nullable +}; + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = byte_span(start_digits, size_t(digit_count)); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char* before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = byte_span(before, size_t(p - before)); + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + const char* int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + const char* frac_end = p + answer.fraction.len(); + while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include <algorithm> +#include <cstdint> +#include <cstring> +#include <iterator> + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = { + 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, + 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, + 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, + 1000000000000000000UL, 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template <typename T> +fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { + using equiv_uint = typename binary_format<T>::equiv_uint; + constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent(); + equiv_uint bits; + ::memcpy(&bits, &value, sizeof(T)); + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> binary_format<T>::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template <typename T> +fastfloat_really_inline adjusted_mantissa to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template <typename T, typename callback> +fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) ? 0 : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); + if (am.power2 >= binary_format<T>::infinite_power()) { + am.power2 = binary_format<T>::infinite_power(); + am.mantissa = 0; + } +} + +template <typename callback> +fastfloat_really_inline +void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { + uint64_t mask; + uint64_t halfway; + if (shift == 64) { + mask = UINT64_MAX; + } else { + mask = (uint64_t(1) << shift) - 1; + } + if (shift == 0) { + halfway = 0; + } else { + halfway = uint64_t(1) << (shift - 1); + } + uint64_t truncated_bits = am.mantissa & mask; + uint64_t is_above = truncated_bits > halfway; + uint64_t is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline void round_down(adjusted_mantissa& am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} + +fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept { + uint64_t val; + while (std::distance(first, last) >= 8) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != 0x3030303030303030) { + break; + } + first += 8; + } + while (first != last) { + if (*first != '0') { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (std::distance(first, last) >= 8) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != 0x3030303030303030) { + return true; + } + first += 8; + } + while (first != last) { + if (*first != '0') { + return true; + } + first++; + } + return false; +} + +fastfloat_really_inline bool is_truncated(byte_span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +fastfloat_really_inline +void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +fastfloat_really_inline +void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 10 + limb(*p - '0'); + p++; + counter++; + count++; +} + +fastfloat_really_inline +void add_native(bigint& big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + const char* p = num.integer.ptr; + const char* pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template <typename T> +inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round<T>(answer, [truncated](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template <typename T> +inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint& real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. + round<T>(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round<T>(answer, [ord](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template <typename T> +inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format<T>::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp<T>(bigmant, exponent); + } else { + return negative_digit_comp<T>(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +#include <cmath> +#include <cstring> +#include <limits> +#include <system_error> + +namespace fast_float { + + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template <typename T> +from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { + from_chars_result answer; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, "nan", 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if(first != last && *first == '(') { + for(const char* ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == ')') { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } + else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, "inf", 3)) { + if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +} // namespace detail + +template<typename T> +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_advanced(first, last, value, parse_options{fmt}); +} + +template<typename T> +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept { + + static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported"); + + + from_chars_result answer; + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string pns = parse_number_string(first, last, options); + if (!pns.valid) { + return detail::parse_infnan(first, last, value); + } + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // Next is Clinger's fast path. + if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && pns.mantissa <=binary_format<T>::max_mantissa_fast_path() && !pns.too_many_digits) { + value = T(pns.mantissa); + if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); } + else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); } + if (pns.negative) { value = -value; } + return answer; + } + adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa); + if(pns.too_many_digits && am.power2 >= 0) { + if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) { + am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), + // then we need to go the long way around again. This is very uncommon. + if(am.power2 < 0) { am = digit_comp<T>(pns, am); } + to_float(pns.negative, am, value); + return answer; +} + +} // namespace fast_float + +#endif + diff --git a/intern/cycles/device/metal/kernel.mm b/intern/cycles/device/metal/kernel.mm index 1434b297ddd..9555ca03c8e 100644 --- a/intern/cycles/device/metal/kernel.mm +++ b/intern/cycles/device/metal/kernel.mm @@ -459,7 +459,7 @@ bool MetalDeviceKernels::load(MetalDevice *device, int kernel_type) tbb::task_arena local_arena(max_mtlcompiler_threads); local_arena.execute([&]() { - tbb::parallel_for(int(0), int(DEVICE_KERNEL_NUM), [&](int i) { + parallel_for(int(0), int(DEVICE_KERNEL_NUM), [&](int i) { /* skip megakernel */ if (i == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) { return; diff --git a/intern/cycles/integrator/pass_accessor_cpu.cpp b/intern/cycles/integrator/pass_accessor_cpu.cpp index 509190c8a7e..02260a54bf4 100644 --- a/intern/cycles/integrator/pass_accessor_cpu.cpp +++ b/intern/cycles/integrator/pass_accessor_cpu.cpp @@ -44,7 +44,7 @@ inline void PassAccessorCPU::run_get_pass_kernel_processor_float( const int pixel_stride = destination.pixel_stride ? destination.pixel_stride : destination.num_components; - tbb::parallel_for(0, buffer_params.window_height, [&](int64_t y) { + parallel_for(0, buffer_params.window_height, [&](int64_t y) { const float *buffer = window_data + y * buffer_row_stride; float *pixel = destination.pixels + (y * buffer_params.width + destination.offset) * pixel_stride; @@ -69,7 +69,7 @@ inline void PassAccessorCPU::run_get_pass_kernel_processor_half_rgba( const int destination_stride = destination.stride != 0 ? destination.stride : buffer_params.width; - tbb::parallel_for(0, buffer_params.window_height, [&](int64_t y) { + parallel_for(0, buffer_params.window_height, [&](int64_t y) { const float *buffer = window_data + y * buffer_row_stride; half4 *pixel = dst_start + y * destination_stride; func(kfilm_convert, buffer, pixel, buffer_params.window_width, pass_stride); diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index f1e70b7f28f..4ecd3b829e8 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -334,7 +334,7 @@ void PathTrace::init_render_buffers(const RenderWork &render_work) /* Handle initialization scheduled by the render scheduler. */ if (render_work.init_render_buffers) { - tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { path_trace_work->zero_render_buffers(); }); @@ -355,10 +355,9 @@ void PathTrace::path_trace(RenderWork &render_work) const int num_works = path_trace_works_.size(); - tbb::task_group_context *tbb_ctx = tbb::task::self().group(); - tbb_ctx->capture_fp_settings(); + thread_capture_fp_settings(); - tbb::parallel_for(0, num_works, [&](int i) { + parallel_for(0, num_works, [&](int i) { const double work_start_time = time_dt(); const int num_samples = render_work.path_trace.num_samples; @@ -408,7 +407,7 @@ void PathTrace::adaptive_sample(RenderWork &render_work) const double start_time = time_dt(); uint num_active_pixels = 0; - tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { const uint num_active_pixels_in_work = path_trace_work->adaptive_sampling_converge_filter_count_active( render_work.adaptive_sampling.threshold, render_work.adaptive_sampling.reset); @@ -486,7 +485,7 @@ void PathTrace::cryptomatte_postprocess(const RenderWork &render_work) } VLOG(3) << "Perform cryptomatte work."; - tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { path_trace_work->cryptomatte_postproces(); }); } @@ -539,7 +538,7 @@ void PathTrace::denoise(const RenderWork &render_work) if (multi_device_buffers) { multi_device_buffers->copy_from_device(); - tbb::parallel_for_each( + parallel_for_each( path_trace_works_, [&multi_device_buffers](unique_ptr<PathTraceWork> &path_trace_work) { path_trace_work->copy_from_denoised_render_buffers(multi_device_buffers.get()); }); @@ -809,7 +808,7 @@ void PathTrace::tile_buffer_read() } /* Read buffers back from device. */ - tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { path_trace_work->copy_render_buffers_from_device(); }); @@ -817,7 +816,7 @@ void PathTrace::tile_buffer_read() PathTraceTile tile(*this); if (output_driver_->read_render_tile(tile)) { /* Copy buffers to device again. */ - tbb::parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) { path_trace_work->copy_render_buffers_to_device(); }); } @@ -881,20 +880,20 @@ void PathTrace::progress_set_status(const string &status, const string &substatu void PathTrace::copy_to_render_buffers(RenderBuffers *render_buffers) { - tbb::parallel_for_each(path_trace_works_, - [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) { - path_trace_work->copy_to_render_buffers(render_buffers); - }); + parallel_for_each(path_trace_works_, + [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) { + path_trace_work->copy_to_render_buffers(render_buffers); + }); render_buffers->copy_to_device(); } void PathTrace::copy_from_render_buffers(RenderBuffers *render_buffers) { render_buffers->copy_from_device(); - tbb::parallel_for_each(path_trace_works_, - [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) { - path_trace_work->copy_from_render_buffers(render_buffers); - }); + parallel_for_each(path_trace_works_, + [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) { + path_trace_work->copy_from_render_buffers(render_buffers); + }); } bool PathTrace::copy_render_tile_from_device() @@ -906,7 +905,7 @@ bool PathTrace::copy_render_tile_from_device() bool success = true; - tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { if (!success) { return; } @@ -1007,7 +1006,7 @@ bool PathTrace::get_render_tile_pixels(const PassAccessor &pass_accessor, bool success = true; - tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { if (!success) { return; } @@ -1024,7 +1023,7 @@ bool PathTrace::set_render_tile_pixels(PassAccessor &pass_accessor, { bool success = true; - tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { + parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { if (!success) { return; } diff --git a/intern/cycles/integrator/path_trace_work_cpu.cpp b/intern/cycles/integrator/path_trace_work_cpu.cpp index 147e273284b..518ef3185f9 100644 --- a/intern/cycles/integrator/path_trace_work_cpu.cpp +++ b/intern/cycles/integrator/path_trace_work_cpu.cpp @@ -73,7 +73,7 @@ void PathTraceWorkCPU::render_samples(RenderStatistics &statistics, tbb::task_arena local_arena = local_tbb_arena_create(device_); local_arena.execute([&]() { - tbb::parallel_for(int64_t(0), total_pixels_num, [&](int64_t work_index) { + parallel_for(int64_t(0), total_pixels_num, [&](int64_t work_index) { if (is_cancel_requested()) { return; } @@ -219,7 +219,7 @@ int PathTraceWorkCPU::adaptive_sampling_converge_filter_count_active(float thres /* Check convergency and do x-filter in a single `parallel_for`, to reduce threading overhead. */ local_arena.execute([&]() { - tbb::parallel_for(full_y, full_y + height, [&](int y) { + parallel_for(full_y, full_y + height, [&](int y) { CPUKernelThreadGlobals *kernel_globals = &kernel_thread_globals_[0]; bool row_converged = true; @@ -243,7 +243,7 @@ int PathTraceWorkCPU::adaptive_sampling_converge_filter_count_active(float thres if (num_active_pixels) { local_arena.execute([&]() { - tbb::parallel_for(full_x, full_x + width, [&](int x) { + parallel_for(full_x, full_x + width, [&](int x) { CPUKernelThreadGlobals *kernel_globals = &kernel_thread_globals_[0]; kernels_.adaptive_sampling_filter_y( kernel_globals, render_buffer, x, full_y, height, offset, stride); @@ -265,7 +265,7 @@ void PathTraceWorkCPU::cryptomatte_postproces() /* Check convergency and do x-filter in a single `parallel_for`, to reduce threading overhead. */ local_arena.execute([&]() { - tbb::parallel_for(0, height, [&](int y) { + parallel_for(0, height, [&](int y) { CPUKernelThreadGlobals *kernel_globals = &kernel_thread_globals_[0]; int pixel_index = y * width; diff --git a/intern/cycles/integrator/shader_eval.cpp b/intern/cycles/integrator/shader_eval.cpp index f5036b4020d..92b9d1c662d 100644 --- a/intern/cycles/integrator/shader_eval.cpp +++ b/intern/cycles/integrator/shader_eval.cpp @@ -92,7 +92,7 @@ bool ShaderEval::eval_cpu(Device *device, tbb::task_arena local_arena(device->info.cpu_threads); local_arena.execute([&]() { - tbb::parallel_for(int64_t(0), work_size, [&](int64_t work_index) { + parallel_for(int64_t(0), work_size, [&](int64_t work_index) { /* TODO: is this fast enough? */ if (progress_.get_cancel()) { success = false; diff --git a/intern/cycles/kernel/svm/blackbody.h b/intern/cycles/kernel/svm/blackbody.h index 1618341b655..af59c2fe747 100644 --- a/intern/cycles/kernel/svm/blackbody.h +++ b/intern/cycles/kernel/svm/blackbody.h @@ -23,7 +23,7 @@ ccl_device_noinline void svm_node_blackbody(KernelGlobals kg, /* Input */ float temperature = stack_load_float(stack, temperature_offset); - float3 color_rgb = svm_math_blackbody_color(temperature); + float3 color_rgb = rec709_to_rgb(kg, svm_math_blackbody_color_rec709(temperature)); stack_store_float3(stack, col_offset, color_rgb); } diff --git a/intern/cycles/kernel/svm/closure.h b/intern/cycles/kernel/svm/closure.h index 88b44cdbacf..305bd404d27 100644 --- a/intern/cycles/kernel/svm/closure.h +++ b/intern/cycles/kernel/svm/closure.h @@ -1111,7 +1111,8 @@ ccl_device_noinline int svm_node_principled_volume(KernelGlobals kg, if (intensity > CLOSURE_WEIGHT_CUTOFF) { float3 blackbody_tint = stack_load_float3(stack, node.w); - float3 bb = blackbody_tint * intensity * svm_math_blackbody_color(T); + float3 bb = blackbody_tint * intensity * + rec709_to_rgb(kg, svm_math_blackbody_color_rec709(T)); emission_setup(sd, bb); } } diff --git a/intern/cycles/kernel/svm/math_util.h b/intern/cycles/kernel/svm/math_util.h index 2a496aee1e1..9f2d9561e26 100644 --- a/intern/cycles/kernel/svm/math_util.h +++ b/intern/cycles/kernel/svm/math_util.h @@ -189,10 +189,8 @@ ccl_device float svm_math(NodeMathType type, float a, float b, float c) } } -ccl_device float3 svm_math_blackbody_color(float t) +ccl_device float3 svm_math_blackbody_color_rec709(float t) { - /* TODO(lukas): Reimplement in XYZ. */ - /* Calculate color in range 800..12000 using an approximation * a/x+bx+c for R and G and ((at + b)t + c)t + d) for B * Max absolute error for RGB is (0.00095, 0.00077, 0.00057), diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index db499a1e1bc..422285cd346 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -1117,13 +1117,18 @@ typedef struct KernelFilm { float4 xyz_to_g; float4 xyz_to_b; float4 rgb_to_y; + /* Rec709 to rendering color space. */ + float4 rec709_to_r; + float4 rec709_to_g; + float4 rec709_to_b; + int is_rec709; int pass_bake_primitive; int pass_bake_differential; int use_approximate_shadow_catcher; - int pad1, pad2; + int pad1; } KernelFilm; static_assert_align(KernelFilm, 16); diff --git a/intern/cycles/kernel/util/color.h b/intern/cycles/kernel/util/color.h index 95b6b33795d..28978d873d6 100644 --- a/intern/cycles/kernel/util/color.h +++ b/intern/cycles/kernel/util/color.h @@ -14,6 +14,15 @@ ccl_device float3 xyz_to_rgb(KernelGlobals kg, float3 xyz) dot(float4_to_float3(kernel_data.film.xyz_to_b), xyz)); } +ccl_device float3 rec709_to_rgb(KernelGlobals kg, float3 rec709) +{ + return (kernel_data.film.is_rec709) ? + rec709 : + make_float3(dot(float4_to_float3(kernel_data.film.rec709_to_r), rec709), + dot(float4_to_float3(kernel_data.film.rec709_to_g), rec709), + dot(float4_to_float3(kernel_data.film.rec709_to_b), rec709)); +} + ccl_device float linear_rgb_to_gray(KernelGlobals kg, float3 c) { return dot(c, float4_to_float3(kernel_data.film.rgb_to_y)); diff --git a/intern/cycles/scene/shader.cpp b/intern/cycles/scene/shader.cpp index 8a08f2a5be9..e1af92ea8cf 100644 --- a/intern/cycles/scene/shader.cpp +++ b/intern/cycles/scene/shader.cpp @@ -579,6 +579,10 @@ void ShaderManager::device_update_common(Device * /*device*/, kfilm->xyz_to_g = float3_to_float4(xyz_to_g); kfilm->xyz_to_b = float3_to_float4(xyz_to_b); kfilm->rgb_to_y = float3_to_float4(rgb_to_y); + kfilm->rec709_to_r = float3_to_float4(rec709_to_r); + kfilm->rec709_to_g = float3_to_float4(rec709_to_g); + kfilm->rec709_to_b = float3_to_float4(rec709_to_b); + kfilm->is_rec709 = is_rec709; } void ShaderManager::device_free_common(Device *, DeviceScene *dscene, Scene *scene) @@ -740,6 +744,11 @@ float ShaderManager::linear_rgb_to_gray(float3 c) return dot(c, rgb_to_y); } +float3 ShaderManager::rec709_to_scene_linear(float3 c) +{ + return make_float3(dot(rec709_to_r, c), dot(rec709_to_g, c), dot(rec709_to_b, c)); +} + string ShaderManager::get_cryptomatte_materials(Scene *scene) { string manifest = "{"; @@ -802,11 +811,29 @@ void ShaderManager::init_xyz_transforms() { /* Default to ITU-BT.709 in case no appropriate transform found. * Note XYZ here is defined as having a D65 white point. */ - xyz_to_r = make_float3(3.2404542f, -1.5371385f, -0.4985314f); - xyz_to_g = make_float3(-0.9692660f, 1.8760108f, 0.0415560f); - xyz_to_b = make_float3(0.0556434f, -0.2040259f, 1.0572252f); + const Transform xyz_to_rec709 = make_transform(3.2404542f, + -1.5371385f, + -0.4985314f, + 0.0f, + -0.9692660f, + 1.8760108f, + 0.0415560f, + 0.0f, + 0.0556434f, + -0.2040259f, + 1.0572252f, + 0.0f); + + xyz_to_r = float4_to_float3(xyz_to_rec709.x); + xyz_to_g = float4_to_float3(xyz_to_rec709.y); + xyz_to_b = float4_to_float3(xyz_to_rec709.z); rgb_to_y = make_float3(0.2126729f, 0.7151522f, 0.0721750f); + rec709_to_r = make_float3(1.0f, 0.0f, 0.0f); + rec709_to_g = make_float3(0.0f, 1.0f, 0.0f); + rec709_to_b = make_float3(0.0f, 0.0f, 1.0f); + is_rec709 = true; + #ifdef WITH_OCIO /* Get from OpenColorO config if it has the required roles. */ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); @@ -857,6 +884,12 @@ void ShaderManager::init_xyz_transforms() const Transform rgb_to_xyz = transform_inverse(xyz_to_rgb); rgb_to_y = float4_to_float3(rgb_to_xyz.y); + + const Transform rec709_to_rgb = xyz_to_rgb * transform_inverse(xyz_to_rec709); + rec709_to_r = float4_to_float3(rec709_to_rgb.x); + rec709_to_g = float4_to_float3(rec709_to_rgb.y); + rec709_to_b = float4_to_float3(rec709_to_rgb.z); + is_rec709 = transform_equal_threshold(xyz_to_rgb, xyz_to_rec709, 0.0001f); #endif } diff --git a/intern/cycles/scene/shader.h b/intern/cycles/scene/shader.h index cbe331d8ec2..274bb9b4fa1 100644 --- a/intern/cycles/scene/shader.h +++ b/intern/cycles/scene/shader.h @@ -208,6 +208,7 @@ class ShaderManager { static void free_memory(); float linear_rgb_to_gray(float3 c); + float3 rec709_to_scene_linear(float3 c); string get_cryptomatte_materials(Scene *scene); @@ -239,6 +240,10 @@ class ShaderManager { float3 xyz_to_g; float3 xyz_to_b; float3 rgb_to_y; + float3 rec709_to_r; + float3 rec709_to_g; + float3 rec709_to_b; + bool is_rec709; }; CCL_NAMESPACE_END diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp index a951a558731..95fccf725f3 100644 --- a/intern/cycles/scene/shader_nodes.cpp +++ b/intern/cycles/scene/shader_nodes.cpp @@ -5763,7 +5763,9 @@ BlackbodyNode::BlackbodyNode() : ShaderNode(get_node_type()) void BlackbodyNode::constant_fold(const ConstantFolder &folder) { if (folder.all_inputs_constant()) { - folder.make_constant(svm_math_blackbody_color(temperature)); + const float3 rgb_rec709 = svm_math_blackbody_color_rec709(temperature); + const float3 rgb = folder.scene->shader_manager->rec709_to_scene_linear(rgb_rec709); + folder.make_constant(rgb); } } diff --git a/intern/cycles/util/tbb.h b/intern/cycles/util/tbb.h index 7105ddda0f8..948bf2b3e0e 100644 --- a/intern/cycles/util/tbb.h +++ b/intern/cycles/util/tbb.h @@ -25,6 +25,17 @@ CCL_NAMESPACE_BEGIN using tbb::blocked_range; using tbb::enumerable_thread_specific; using tbb::parallel_for; +using tbb::parallel_for_each; + +static inline void thread_capture_fp_settings() +{ +#if TBB_INTERFACE_VERSION_MAJOR >= 12 + tbb::task_group_context *ctx = tbb::task::current_context(); +#else + tbb::task_group_context *ctx = tbb::task::self().group(); +#endif + ctx->capture_fp_settings(); +} static inline void parallel_for_cancel() { diff --git a/intern/cycles/util/transform.h b/intern/cycles/util/transform.h index 371dbb0f4aa..477272f0ba6 100644 --- a/intern/cycles/util/transform.h +++ b/intern/cycles/util/transform.h @@ -285,6 +285,21 @@ ccl_device_inline bool operator!=(const Transform &A, const Transform &B) return !(A == B); } +ccl_device_inline bool transform_equal_threshold(const Transform &A, + const Transform &B, + const float threshold) +{ + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 4; y++) { + if (fabsf(A[x][y] - B[x][y]) > threshold) { + return false; + } + } + } + + return true; +} + ccl_device_inline float3 transform_get_column(const Transform *t, int column) { return make_float3(t->x[column], t->y[column], t->z[column]); diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index a82f634183d..ec641938f1f 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -30,8 +30,10 @@ extern GHOST_SystemHandle GHOST_CreateSystem(void); /** * Specifies whether debug messages are to be enabled for the specific system handle. + * \param systemhandle: The handle to the system. + * \param debug: Flag for systems to debug. */ -extern void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, int is_debug_enabled); +extern void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, GHOST_Debug debug); /** * Disposes the one and only system. diff --git a/intern/ghost/GHOST_ISystem.h b/intern/ghost/GHOST_ISystem.h index ed193ee7e5d..bb91abbadec 100644 --- a/intern/ghost/GHOST_ISystem.h +++ b/intern/ghost/GHOST_ISystem.h @@ -452,8 +452,9 @@ class GHOST_ISystem { /** * Specify whether debug messages are to be shown. + * \param debug: Flag for systems to debug. */ - virtual void initDebug(bool is_debug_enabled) = 0; + virtual void initDebug(GHOST_Debug debug) = 0; /** * Check whether debug messages are to be shown. diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index c654367072f..85913fbd10c 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -573,6 +573,16 @@ typedef struct { uint32_t frequency; } GHOST_DisplaySetting; +typedef enum { + /** Axis that cursor grab will wrap. */ + GHOST_kDebugDefault = (1 << 1), + GHOST_kDebugWintab = (1 << 2), +} GHOST_TDebugFlags; + +typedef struct { + int flags; +} GHOST_Debug; + #ifdef _WIN32 typedef void *GHOST_TEmbedderWindowID; #endif // _WIN32 diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index e3d01c24283..93e94893162 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -30,11 +30,11 @@ GHOST_SystemHandle GHOST_CreateSystem(void) return (GHOST_SystemHandle)system; } -void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, int is_debug_enabled) +void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, GHOST_Debug debug) { GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; - system->initDebug(is_debug_enabled); + system->initDebug(debug); } GHOST_TSuccess GHOST_DisposeSystem(GHOST_SystemHandle systemhandle) diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp index 3df85e18bc7..0d0d41972fd 100644 --- a/intern/ghost/intern/GHOST_System.cpp +++ b/intern/ghost/intern/GHOST_System.cpp @@ -390,9 +390,9 @@ void GHOST_System::useWindowFocus(const bool use_focus) m_windowFocus = use_focus; } -void GHOST_System::initDebug(bool is_debug_enabled) +void GHOST_System::initDebug(GHOST_Debug debug) { - m_is_debug_enabled = is_debug_enabled; + m_is_debug_enabled = debug.flags & GHOST_kDebugDefault; } bool GHOST_System::isDebugEnabled() diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h index 0e1e3f734ae..4a3cded1fbd 100644 --- a/intern/ghost/intern/GHOST_System.h +++ b/intern/ghost/intern/GHOST_System.h @@ -334,8 +334,9 @@ class GHOST_System : public GHOST_ISystem { /** * Specify whether debug messages are to be shown. + * \param debug: Flag for systems to debug. */ - virtual void initDebug(bool is_debug_enabled); + virtual void initDebug(GHOST_Debug debug); /** * Check whether debug messages are to be shown. diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index e588c7485b4..83869188b65 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -872,6 +872,13 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type, int msgPosY = GET_Y_LPARAM(msgPos); system->pushEvent(new GHOST_EventCursor( ::GetMessageTime(), GHOST_kEventCursorMove, window, msgPosX, msgPosY, td)); + + if (type == GHOST_kEventButtonDown) { + WINTAB_PRINTF("HWND %p OS button down\n", window->getHWND()); + } + else if (type == GHOST_kEventButtonUp) { + WINTAB_PRINTF("HWND %p OS button up\n", window->getHWND()); + } } window->updateMouseCapture(type == GHOST_kEventButtonDown ? MousePressed : MouseReleased); @@ -914,6 +921,8 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window) break; } case GHOST_kEventButtonDown: { + WINTAB_PRINTF("HWND %p Wintab button down", window->getHWND()); + UINT message; switch (info.button) { case GHOST_kButtonMaskLeft: @@ -939,9 +948,12 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window) /* Test for Win32/Wintab button down match. */ useWintabPos = wt->testCoordinates(msg.pt.x, msg.pt.y, info.x, info.y); if (!useWintabPos) { + WINTAB_PRINTF(" ... but associated system button mismatched position\n"); continue; } + WINTAB_PRINTF(" ... associated to system button\n"); + /* Steal the Win32 event which was previously peeked. */ PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD); @@ -958,9 +970,14 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window) mouseMoveHandled = true; break; } + else { + WINTAB_PRINTF(" ... but no system button\n"); + } } case GHOST_kEventButtonUp: { + WINTAB_PRINTF("HWND %p Wintab button up", window->getHWND()); if (!useWintabPos) { + WINTAB_PRINTF(" ... but Wintab position isn't trusted\n"); continue; } @@ -986,10 +1003,14 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window) if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) && msg.message != WM_QUIT) { + WINTAB_PRINTF(" ... associated to system button\n"); window->updateMouseCapture(MouseReleased); system->pushEvent( new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData)); } + else { + WINTAB_PRINTF(" ... but no system button\n"); + } break; } default: @@ -1318,6 +1339,12 @@ void GHOST_SystemWin32::setTabletAPI(GHOST_TTabletAPI api) } } +void GHOST_SystemWin32::initDebug(GHOST_Debug debug) +{ + GHOST_System::initDebug(debug); + GHOST_Wintab::setDebug(debug.flags & GHOST_kDebugWintab); +} + void GHOST_SystemWin32::processMinMaxInfo(MINMAXINFO *minmax) { minmax->ptMinTrackSize.x = 320; @@ -1593,6 +1620,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, // Wintab events, processed //////////////////////////////////////////////////////////////////////// case WT_CSRCHANGE: { + WINTAB_PRINTF("HWND %p HCTX %p WT_CSRCHANGE\n", window->getHWND(), (void *)lParam); GHOST_Wintab *wt = window->getWintab(); if (wt) { wt->updateCursorInfo(); @@ -1601,6 +1629,20 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, break; } case WT_PROXIMITY: { + WINTAB_PRINTF("HWND %p HCTX %p WT_PROXIMITY\n", window->getHWND(), (void *)wParam); + if (LOWORD(lParam)) { + WINTAB_PRINTF(" Cursor entering context.\n"); + } + else { + WINTAB_PRINTF(" Cursor leaving context.\n"); + } + if (HIWORD(lParam)) { + WINTAB_PRINTF(" Cursor entering or leaving hardware proximity.\n"); + } + else { + WINTAB_PRINTF(" Cursor neither entering nor leaving hardware proximity.\n"); + } + GHOST_Wintab *wt = window->getWintab(); if (wt) { bool inRange = LOWORD(lParam); @@ -1616,6 +1658,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, break; } case WT_INFOCHANGE: { + WINTAB_PRINTF("HWND %p HCTX %p WT_INFOCHANGE\n", window->getHWND(), (void *)wParam); GHOST_Wintab *wt = window->getWintab(); if (wt) { wt->processInfoChange(lParam); @@ -1632,6 +1675,32 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, eventHandled = true; break; //////////////////////////////////////////////////////////////////////// + // Wintab events, debug + //////////////////////////////////////////////////////////////////////// + case WT_CTXOPEN: + WINTAB_PRINTF("HWND %p HCTX %p WT_CTXOPEN\n", window->getHWND(), (void *)wParam); + break; + case WT_CTXCLOSE: + WINTAB_PRINTF("HWND %p HCTX %p WT_CTXCLOSE\n", window->getHWND(), (void *)wParam); + break; + case WT_CTXUPDATE: + WINTAB_PRINTF("HWND %p HCTX %p WT_CTXUPDATE\n", window->getHWND(), (void *)wParam); + break; + case WT_CTXOVERLAP: + WINTAB_PRINTF("HWND %p HCTX %p WT_CTXOVERLAP", window->getHWND(), (void *)wParam); + switch (lParam) { + case CXS_DISABLED: + WINTAB_PRINTF(" CXS_DISABLED\n"); + break; + case CXS_OBSCURED: + WINTAB_PRINTF(" CXS_OBSCURED\n"); + break; + case CXS_ONTOP: + WINTAB_PRINTF(" CXS_ONTOP\n"); + break; + } + break; + //////////////////////////////////////////////////////////////////////// // Pointer events, processed //////////////////////////////////////////////////////////////////////// case WM_POINTERUPDATE: @@ -1692,6 +1761,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, break; case WM_MOUSEMOVE: if (!window->m_mousePresent) { + WINTAB_PRINTF("HWND %p mouse enter\n", window->getHWND()); TRACKMOUSEEVENT tme = {sizeof(tme)}; tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; @@ -1740,6 +1810,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, } break; case WM_MOUSELEAVE: { + WINTAB_PRINTF("HWND %p mouse leave\n", window->getHWND()); window->m_mousePresent = false; if (window->getTabletData().Active == GHOST_kTabletModeNone) { event = processCursorEvent(window); diff --git a/intern/ghost/intern/GHOST_SystemWin32.h b/intern/ghost/intern/GHOST_SystemWin32.h index 16ad5f041ca..9f8d52f9ca3 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.h +++ b/intern/ghost/intern/GHOST_SystemWin32.h @@ -259,6 +259,16 @@ class GHOST_SystemWin32 : public GHOST_System { */ void setTabletAPI(GHOST_TTabletAPI api) override; + /*************************************************************************************** + ** Debug Info + ***************************************************************************************/ + + /** + * Specify which debug messages are to be shown. + * \param debug: Flag for systems to debug. + */ + void initDebug(GHOST_Debug debug) override; + protected: /** * Initializes the system. diff --git a/intern/ghost/intern/GHOST_WindowWin32.cpp b/intern/ghost/intern/GHOST_WindowWin32.cpp index 11a3c097958..2ce224b666b 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cpp +++ b/intern/ghost/intern/GHOST_WindowWin32.cpp @@ -960,6 +960,7 @@ GHOST_Wintab *GHOST_WindowWin32::getWintab() const void GHOST_WindowWin32::loadWintab(bool enable) { if (!m_wintab) { + WINTAB_PRINTF("Loading Wintab for window %p\n", m_hWnd); if (m_wintab = GHOST_Wintab::loadWintab(m_hWnd)) { if (enable) { m_wintab->enable(); @@ -982,6 +983,7 @@ void GHOST_WindowWin32::loadWintab(bool enable) void GHOST_WindowWin32::closeWintab() { + WINTAB_PRINTF("Closing Wintab for window %p\n", m_hWnd); delete m_wintab; m_wintab = NULL; } diff --git a/intern/ghost/intern/GHOST_Wintab.cpp b/intern/ghost/intern/GHOST_Wintab.cpp index 2547a38c0d1..be1a0a4b314 100644 --- a/intern/ghost/intern/GHOST_Wintab.cpp +++ b/intern/ghost/intern/GHOST_Wintab.cpp @@ -11,7 +11,6 @@ GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd) { /* Load Wintab library if available. */ - auto handle = unique_hmodule(::LoadLibrary("Wintab32.dll"), &::FreeLibrary); if (!handle) { return nullptr; @@ -116,6 +115,11 @@ GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd) } } + int sanityQueueSize = queueSizeGet(hctx.get()); + WINTAB_PRINTF("HCTX %p %s queueSize: %d, queueSizeGet: %d\n", hctx.get(), __func__, queueSize, sanityQueueSize); + + WINTAB_PRINTF("Loaded Wintab context %p\n", hctx.get()); + return new GHOST_Wintab(std::move(handle), info, get, @@ -183,7 +187,17 @@ GHOST_Wintab::GHOST_Wintab(unique_hmodule handle, m_pkts{queueSize} { m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices); + WINTAB_PRINTF("Wintab Devices: %d\n", m_numDevices); + updateCursorInfo(); + + /* Debug info. */ + printContextDebugInfo(); +} + +GHOST_Wintab::~GHOST_Wintab() +{ + WINTAB_PRINTF("Closing Wintab context %p\n", m_context.get()); } void GHOST_Wintab::enable() @@ -249,6 +263,7 @@ void GHOST_Wintab::updateCursorInfo() BOOL pressureSupport = m_fpInfo(WTI_DEVICES, DVC_NPRESSURE, &Pressure); m_maxPressure = pressureSupport ? Pressure.axMax : 0; + WINTAB_PRINTF("HCTX %p %s maxPressure: %d\n", m_context.get(), __func__, m_maxPressure); BOOL tiltSupport = m_fpInfo(WTI_DEVICES, DVC_ORIENTATION, &Orientation); /* Check if tablet supports azimuth [0] and altitude [1], encoded in axResolution. */ @@ -259,6 +274,7 @@ void GHOST_Wintab::updateCursorInfo() else { m_maxAzimuth = m_maxAltitude = 0; } + WINTAB_PRINTF("HCTX %p %s maxAzimuth: %d, maxAltitude: %d\n", m_context.get(), __func__, m_maxAzimuth, m_maxAltitude); } void GHOST_Wintab::processInfoChange(LPARAM lParam) @@ -266,6 +282,7 @@ void GHOST_Wintab::processInfoChange(LPARAM lParam) /* Update number of connected Wintab digitizers. */ if (LOWORD(lParam) == WTI_INTERFACE && HIWORD(lParam) == IFC_NDEVICES) { m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices); + WINTAB_PRINTF("HCTX %p %s numDevices: %d\n", m_context.get(), __func__, m_numDevices); } } @@ -456,3 +473,144 @@ bool GHOST_Wintab::testCoordinates(int sysX, int sysY, int wtX, int wtY) return false; } } + +bool GHOST_Wintab::m_debug = false; + +void GHOST_Wintab::setDebug(bool debug) +{ + m_debug = debug; +} + +bool GHOST_Wintab::getDebug() +{ + return m_debug; +} + +void GHOST_Wintab::printContextDebugInfo() +{ + if (!m_debug) { + return; + } + + /* Print button maps. */ + BYTE logicalButtons[32] = {0}; + BYTE systemButtons[32] = {0}; + for (int i = 0; i < 3; i++) { + printf("initializeWintab cursor %d buttons\n", i); + UINT lbut = m_fpInfo(WTI_CURSORS + i, CSR_BUTTONMAP, &logicalButtons); + if (lbut) { + printf("%d", logicalButtons[0]); + for (int j = 1; j < lbut; j++) { + printf(", %d", logicalButtons[j]); + } + printf("\n"); + } + else { + printf("logical button error\n"); + } + UINT sbut = m_fpInfo(WTI_CURSORS + i, CSR_SYSBTNMAP, &systemButtons); + if (sbut) { + printf("%d", systemButtons[0]); + for (int j = 1; j < sbut; j++) { + printf(", %d", systemButtons[j]); + } + printf("\n"); + } + else { + printf("system button error\n"); + } + } + + /* Print context information. */ + + /* Print open context constraints. */ + UINT maxcontexts, opencontexts; + m_fpInfo(WTI_INTERFACE, IFC_NCONTEXTS, &maxcontexts); + m_fpInfo(WTI_STATUS, STA_CONTEXTS, &opencontexts); + printf("%u max contexts, %u open contexts\n", maxcontexts, opencontexts); + + /* Print system information. */ + printf("left: %d, top: %d, width: %d, height: %d\n", + ::GetSystemMetrics(SM_XVIRTUALSCREEN), + ::GetSystemMetrics(SM_YVIRTUALSCREEN), + ::GetSystemMetrics(SM_CXVIRTUALSCREEN), + ::GetSystemMetrics(SM_CYVIRTUALSCREEN)); + + auto printContextRanges = [](LOGCONTEXT &lc) { + printf("lcInOrgX: %d, lcInOrgY: %d, lcInExtX: %d, lcInExtY: %d\n", + lc.lcInOrgX, + lc.lcInOrgY, + lc.lcInExtX, + lc.lcInExtY); + printf("lcOutOrgX: %d, lcOutOrgY: %d, lcOutExtX: %d, lcOutExtY: %d\n", + lc.lcOutOrgX, + lc.lcOutOrgY, + lc.lcOutExtX, + lc.lcOutExtY); + printf("lcSysOrgX: %d, lcSysOrgY: %d, lcSysExtX: %d, lcSysExtY: %d\n", + lc.lcSysOrgX, + lc.lcSysOrgY, + lc.lcSysExtX, + lc.lcSysExtY); + }; + + LOGCONTEXT lc; + + /* Print system context. */ + m_fpInfo(WTI_DEFSYSCTX, 0, &lc); + printf("WTI_DEFSYSCTX\n"); + printContextRanges(lc); + + /* Print system context, manually populated. */ + m_fpInfo(WTI_DEFSYSCTX, CTX_INORGX, &lc.lcInOrgX); + m_fpInfo(WTI_DEFSYSCTX, CTX_INORGY, &lc.lcInOrgY); + m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTX, &lc.lcInExtX); + m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTY, &lc.lcInExtY); + m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGX, &lc.lcOutOrgX); + m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGY, &lc.lcOutOrgY); + m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTX, &lc.lcOutExtX); + m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTY, &lc.lcOutExtY); + m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGX, &lc.lcSysOrgX); + m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGY, &lc.lcSysOrgY); + m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTX, &lc.lcSysExtX); + m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTY, &lc.lcSysExtY); + printf("WTI_DEFSYSCTX CTX_*\n"); + printContextRanges(lc); + + for (unsigned int i = 0; i < m_numDevices; i++) { + /* Print individual device system context. */ + m_fpInfo(WTI_DSCTXS + i, 0, &lc); + printf("WTI_DSCTXS %u\n", i); + printContextRanges(lc); + + /* Print individual device system context, manually populated. */ + m_fpInfo(WTI_DSCTXS + i, CTX_INORGX, &lc.lcInOrgX); + m_fpInfo(WTI_DSCTXS + i, CTX_INORGY, &lc.lcInOrgY); + m_fpInfo(WTI_DSCTXS + i, CTX_INEXTX, &lc.lcInExtX); + m_fpInfo(WTI_DSCTXS + i, CTX_INEXTY, &lc.lcInExtY); + m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGX, &lc.lcOutOrgX); + m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGY, &lc.lcOutOrgY); + m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTX, &lc.lcOutExtX); + m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTY, &lc.lcOutExtY); + m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGX, &lc.lcSysOrgX); + m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGY, &lc.lcSysOrgY); + m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTX, &lc.lcSysExtX); + m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTY, &lc.lcSysExtY); + printf("WTI_DSCTX %u CTX_*\n", i); + printContextRanges(lc); + + /* Print device axis. */ + AXIS axis_x, axis_y; + m_fpInfo(WTI_DEVICES + i, DVC_X, &axis_x); + m_fpInfo(WTI_DEVICES + i, DVC_Y, &axis_y); + printf("WTI_DEVICES %u axis_x org: %d, axis_y org: %d axis_x ext: %d, axis_y ext: %d\n", + i, + axis_x.axMin, + axis_y.axMin, + axis_x.axMax - axis_x.axMin + 1, + axis_y.axMax - axis_y.axMin + 1); + } + + /* Other stuff while we have a logcontext. */ + printf("sysmode %d\n", lc.lcSysMode); +}
\ No newline at end of file diff --git a/intern/ghost/intern/GHOST_Wintab.h b/intern/ghost/intern/GHOST_Wintab.h index a793d2d8f63..86a0143ecc0 100644 --- a/intern/ghost/intern/GHOST_Wintab.h +++ b/intern/ghost/intern/GHOST_Wintab.h @@ -13,6 +13,7 @@ #pragma once #include <memory> +#include <stdio.h> #include <vector> #include <wtypes.h> @@ -25,6 +26,14 @@ #define PACKETMODE 0 #include <pktdef.h> +#define WINTAB_PRINTF(x, ...) \ + { \ + if (GHOST_Wintab::getDebug()) { \ + printf(x, __VA_ARGS__); \ + } \ + } \ + (void)0 + /* Typedefs for Wintab functions to allow dynamic loading. */ typedef UINT(API *GHOST_WIN32_WTInfo)(UINT, UINT, LPVOID); typedef BOOL(API *GHOST_WIN32_WTGet)(HCTX, LPLOGCONTEXTA); @@ -55,9 +64,12 @@ class GHOST_Wintab { /** * Loads Wintab if available. * \param hwnd: Window to attach Wintab context to. + * \return Pointer to the initialized GHOST_Wintab object, or null if initialization failed. */ static GHOST_Wintab *loadWintab(HWND hwnd); + ~GHOST_Wintab(); + /** * Enables Wintab context. */ @@ -146,6 +158,16 @@ class GHOST_Wintab { */ GHOST_TabletData getLastTabletData(); + /* Sets Wintab debugging. + * \param debug: True to enable Wintab debugging. + */ + static void setDebug(bool debug); + + /* Returns whether Wintab logging should occur. + * \return True if Wintab logging should occur. + */ + static bool getDebug(); + private: /** Wintab DLL handle. */ unique_hmodule m_handle; @@ -200,6 +222,9 @@ class GHOST_Wintab { /** Most recently received tablet data, or none if pen is not in range. */ GHOST_TabletData m_lastTabletData = GHOST_TABLET_DATA_NONE; + /** Whether Wintab logging is enabled. */ + static bool m_debug; + GHOST_Wintab(unique_hmodule handle, GHOST_WIN32_WTInfo info, GHOST_WIN32_WTGet get, @@ -233,4 +258,7 @@ class GHOST_Wintab { * \param system: System coordinates. */ static void extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &system); + + /* Prints Wintab Context information. */ + void printContextDebugInfo(); }; diff --git a/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat b/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat Binary files differindex b785bb51431..6e17f520282 100644 --- a/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat +++ b/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat diff --git a/release/datafiles/icons/ops.sculpt.color_filter.dat b/release/datafiles/icons/ops.sculpt.color_filter.dat Binary files differindex d589b15a124..8a65043eb5f 100644 --- a/release/datafiles/icons/ops.sculpt.color_filter.dat +++ b/release/datafiles/icons/ops.sculpt.color_filter.dat diff --git a/release/datafiles/icons/ops.sculpt.mask_by_color.dat b/release/datafiles/icons/ops.sculpt.mask_by_color.dat Binary files differindex 637c47d2d84..6194ce172d5 100644 --- a/release/datafiles/icons/ops.sculpt.mask_by_color.dat +++ b/release/datafiles/icons/ops.sculpt.mask_by_color.dat diff --git a/release/datafiles/locale b/release/datafiles/locale -Subproject 716dc02ec30c0810513f7b4adc4ae865ae50c4e +Subproject 63699f968344db7dc853d2c5972325beea44900 diff --git a/release/scripts/addons b/release/scripts/addons -Subproject 787ea78f7fa6f0373d80ba1247768402df93f8a +Subproject baa581415c7ed23d7c45ef87363174813567268 diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index be60a1711aa..b3f0c055769 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -371,13 +371,8 @@ class _GenericBone: """ return (self.tail - self.head) - @property - def children(self): - """A list of all the bones children. - - .. note:: Takes ``O(len(bones))`` time.""" - return [child for child in self._other_bones if child.parent == self] - + # NOTE: each bone type is responsible for implementing `children`. + # This is done since `Bone` has direct access to this data in RNA. @property def children_recursive(self): """A list of all children from this bone. @@ -457,10 +452,19 @@ class PoseBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): class Bone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): __slots__ = () + # NOTE: `children` is implemented in RNA. + class EditBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): __slots__ = () + @property + def children(self): + """A list of all the bones children. + + .. note:: Takes ``O(len(bones))`` time.""" + return [child for child in self._other_bones if child.parent == self] + def align_orientation(self, other): """ Align this bone to another by moving its tail and settings its roll diff --git a/release/scripts/modules/rna_info.py b/release/scripts/modules/rna_info.py index a8814759840..b009cc4fefe 100644 --- a/release/scripts/modules/rna_info.py +++ b/release/scripts/modules/rna_info.py @@ -198,7 +198,11 @@ class InfoStructRNA: for identifier, attr in self._get_py_visible_attrs(): # methods may be python wrappers to C functions attr_func = getattr(attr, "__func__", attr) - if type(attr_func) in {types.BuiltinMethodType, types.BuiltinFunctionType}: + if ( + (type(attr_func) in {types.BuiltinMethodType, types.BuiltinFunctionType}) or + # Without the `objclass` check, many inherited methods are included. + (type(attr_func) == types.MethodDescriptorType and attr_func.__objclass__ == self.py_class) + ): functions.append((identifier, attr)) return functions diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index fc86d02b83e..6408873f13e 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -7370,7 +7370,7 @@ def km_3d_view_tool_sculpt_mask_by_color(params): "3D View Tool: Sculpt, Mask by Color", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.mask_by_color", {"type": params.tool_mouse, "value": 'CLICK'}, None) + ("sculpt.mask_by_color", {"type": params.tool_mouse, "value": 'PRESS'}, None) ]}, ) diff --git a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py index 544b32700a3..4863a78c07a 100644 --- a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py +++ b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py @@ -5,6 +5,7 @@ import bpy from bpy.app.handlers import persistent + def update_factory_startup_screens(): # 2D Animation. screen = bpy.data.screens["2D Animation"] diff --git a/release/scripts/startup/bl_operators/constraint.py b/release/scripts/startup/bl_operators/constraint.py index a05936c875c..444452ad2c5 100644 --- a/release/scripts/startup/bl_operators/constraint.py +++ b/release/scripts/startup/bl_operators/constraint.py @@ -70,8 +70,8 @@ class CONSTRAINT_OT_normalize_target_weights(Operator): class CONSTRAINT_OT_disable_keep_transform(Operator): """Set the influence of this constraint to zero while """ \ - """trying to maintain the object's transformation. Other active """ \ - """constraints can still influence the final transformation""" + """trying to maintain the object's transformation. Other active """ \ + """constraints can still influence the final transformation""" bl_idname = "constraint.disable_keep_transform" bl_label = "Disable and Keep Transform" diff --git a/release/scripts/startup/bl_operators/file.py b/release/scripts/startup/bl_operators/file.py index cfe03b4760c..0fafb09f672 100644 --- a/release/scripts/startup/bl_operators/file.py +++ b/release/scripts/startup/bl_operators/file.py @@ -247,7 +247,7 @@ class WM_OT_previews_batch_clear(Operator): class WM_OT_blend_strings_utf8_validate(Operator): """Check and fix all strings in current .blend file to be valid UTF-8 Unicode """ \ - """(needed for some old, 2.4x area files)""" + """(needed for some old, 2.4x area files)""" bl_idname = "wm.blend_strings_utf8_validate" bl_label = "Validate .blend strings" bl_options = {'REGISTER'} diff --git a/release/scripts/startup/bl_operators/geometry_nodes.py b/release/scripts/startup/bl_operators/geometry_nodes.py index f62fed79438..ea4d40bb778 100644 --- a/release/scripts/startup/bl_operators/geometry_nodes.py +++ b/release/scripts/startup/bl_operators/geometry_nodes.py @@ -26,7 +26,6 @@ def geometry_node_group_empty_new(): def geometry_modifier_poll(context): ob = context.object - # Test object support for geometry node modifier if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME', 'CURVE', 'FONT', 'CURVES'}: return False @@ -83,32 +82,7 @@ class NewGeometryNodeTreeAssign(Operator): return {'FINISHED'} -class CopyGeometryNodeTreeAssign(Operator): - """Copy the active geometry node group and assign it to the active modifier""" - - bl_idname = "node.copy_geometry_node_group_assign" - bl_label = "Copy Geometry Node Group" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return geometry_modifier_poll(context) - - def execute(self, context): - modifier = context.object.modifiers.active - if modifier is None: - return {'CANCELLED'} - - group = modifier.node_group - if group is None: - return {'CANCELLED'} - - modifier.node_group = group.copy() - return {'FINISHED'} - - classes = ( NewGeometryNodesModifier, NewGeometryNodeTreeAssign, - CopyGeometryNodeTreeAssign, ) diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py index f5ea2d0f85b..6857670c31d 100644 --- a/release/scripts/startup/bl_operators/object.py +++ b/release/scripts/startup/bl_operators/object.py @@ -937,7 +937,7 @@ class LoadReferenceImage(LoadImageAsEmpty, Operator): class OBJECT_OT_assign_property_defaults(Operator): """Assign the current values of custom properties as their defaults, """ \ - """for use as part of the rest pose state in NLA track mixing""" + """for use as part of the rest pose state in NLA track mixing""" bl_idname = "object.assign_property_defaults" bl_label = "Assign Custom Property Values as Default" bl_options = {'UNDO', 'REGISTER'} diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index d6e5604fde0..0f063da40fb 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -1317,7 +1317,7 @@ class WM_OT_properties_edit(Operator): name="Array Length", default=3, min=1, - max=32, # 32 is the maximum size for RNA array properties. + max=32, # 32 is the maximum size for RNA array properties. ) # Integer properties. @@ -1511,7 +1511,7 @@ class WM_OT_properties_edit(Operator): elif self.property_type == 'STRING': self.default_string = rna_data["default"] - if self.property_type in { 'FLOAT_ARRAY', 'INT_ARRAY'}: + if self.property_type in {'FLOAT_ARRAY', 'INT_ARRAY'}: self.array_length = len(item[name]) # The dictionary does not contain the description if it was empty. @@ -2940,9 +2940,9 @@ class WM_MT_splash_quick_setup(Menu): layout.label(text="Quick Setup") - split = layout.split(factor=0.14) # Left margin. + split = layout.split(factor=0.14) # Left margin. split.label() - split = split.split(factor=0.73) # Content width. + split = split.split(factor=0.73) # Content width. col = split.column() diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py index 63e7e838cf4..b3b4ab8e52c 100644 --- a/release/scripts/startup/bl_ui/__init__.py +++ b/release/scripts/startup/bl_ui/__init__.py @@ -239,4 +239,5 @@ class UI_MT_list_item_context_menu(bpy.types.Menu): # context menu items. pass + bpy.utils.register_class(UI_MT_list_item_context_menu) diff --git a/release/scripts/startup/bl_ui/properties_constraint.py b/release/scripts/startup/bl_ui/properties_constraint.py index 77f454362a4..bd63368bdfd 100644 --- a/release/scripts/startup/bl_ui/properties_constraint.py +++ b/release/scripts/startup/bl_ui/properties_constraint.py @@ -1162,6 +1162,7 @@ class ConstraintButtonsSubPanel: # Child Of Constraint + class OBJECT_PT_bChildOfConstraint(ObjectConstraintPanel, ConstraintButtonsPanel, Panel): def draw(self, context): self.draw_childof(context) diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index 1ba7a4a5413..d7c885cf870 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -65,6 +65,7 @@ class MESH_MT_shape_key_context_menu(Menu): layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move to Top").type = 'TOP' layout.operator("object.shape_key_move", icon='TRIA_DOWN_BAR', text="Move to Bottom").type = 'BOTTOM' + class MESH_MT_attribute_context_menu(Menu): bl_label = "Attribute Specials" @@ -134,6 +135,7 @@ class MESH_UL_uvmaps(UIList): layout.alignment = 'CENTER' layout.label(text="", icon_value=icon) + class MeshButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -419,6 +421,7 @@ class DATA_PT_uv_texture(MeshButtonsPanel, Panel): col.operator("mesh.uv_texture_add", icon='ADD', text="") col.operator("mesh.uv_texture_remove", icon='REMOVE', text="") + class DATA_PT_remesh(MeshButtonsPanel, Panel): bl_label = "Remesh" bl_options = {'DEFAULT_CLOSED'} @@ -605,7 +608,7 @@ class MESH_UL_color_attributes(UIList, ColorAttributesListBase): sub.label(text="%s â–¶ %s" % (domain_name, data_type.name)) active_render = _index == data.color_attributes.render_color_index - + row = layout.row() row.emboss = 'NONE' prop = row.operator( @@ -650,6 +653,7 @@ class DATA_PT_vertex_colors(DATA_PT_mesh_attributes, Panel): self.draw_attribute_warnings(context, layout) + classes = ( MESH_MT_vertex_group_context_menu, MESH_MT_shape_key_context_menu, diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index 4144cf8c8f8..07ec0cd85a6 100644 --- a/release/scripts/startup/bl_ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -838,6 +838,7 @@ classes = ( ASSETBROWSER_MT_context_menu, ) + def asset_path_str_get(_self): asset_file_handle = bpy.context.asset_file_handle if asset_file_handle is None: diff --git a/release/scripts/startup/bl_ui/space_graph.py b/release/scripts/startup/bl_ui/space_graph.py index 706e228c5d9..a78ad72cada 100644 --- a/release/scripts/startup/bl_ui/space_graph.py +++ b/release/scripts/startup/bl_ui/space_graph.py @@ -324,6 +324,7 @@ class GRAPH_MT_key_snap(Menu): layout.operator("graph.frame_jump", text="Cursor to Selection") layout.operator("graph.snap_cursor_value", text="Cursor Value to Selection") + class GRAPH_MT_slider(Menu): bl_label = "Slider Operators" diff --git a/release/scripts/startup/bl_ui/space_nla.py b/release/scripts/startup/bl_ui/space_nla.py index f0e991c768d..27c8cb754a7 100644 --- a/release/scripts/startup/bl_ui/space_nla.py +++ b/release/scripts/startup/bl_ui/space_nla.py @@ -166,7 +166,6 @@ class NLA_MT_marker_select(Menu): layout.operator("marker.select_leftright", text="After Current Frame").mode = 'RIGHT' - class NLA_MT_edit(Menu): bl_label = "Edit" @@ -214,8 +213,10 @@ class NLA_MT_edit(Menu): layout.operator("nla.tweakmode_exit", text="Stop Tweaking Strip Actions") else: layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True - layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True - layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False + layout.operator("nla.tweakmode_enter", + text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True + layout.operator("nla.tweakmode_enter", + text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False class NLA_MT_add(Menu): @@ -289,8 +290,10 @@ class NLA_MT_context_menu(Menu): layout.operator("nla.tweakmode_exit", text="Stop Tweaking Strip Actions") else: layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True - layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True - layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False + layout.operator("nla.tweakmode_enter", + text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True + layout.operator("nla.tweakmode_enter", + text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False layout.separator() diff --git a/release/scripts/startup/bl_ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py index 1a6e9f57e75..cc673a8bc39 100644 --- a/release/scripts/startup/bl_ui/space_node.py +++ b/release/scripts/startup/bl_ui/space_node.py @@ -149,7 +149,7 @@ class NODE_HT_header(Header): active_modifier = ob.modifiers.active if active_modifier and active_modifier.type == 'NODES': if active_modifier.node_group: - row.template_ID(active_modifier, "node_group", new="node.copy_geometry_node_group_assign") + row.template_ID(active_modifier, "node_group", new="object.geometry_node_tree_copy_assign") else: row.template_ID(active_modifier, "node_group", new="node.new_geometry_node_group_assign") else: @@ -776,7 +776,7 @@ class NodeTreeInterfacePanel: "node.tree_socket_change_type", "socket_type", text=active_socket.bl_label if active_socket.bl_label else active_socket.bl_idname - ) + ) props.in_out = in_out layout.use_property_split = True @@ -816,6 +816,7 @@ class NODE_PT_node_tree_interface_inputs(NodeTreeInterfacePanel, Panel): def draw(self, context): self.draw_socket_list(context, "IN", "inputs", "active_input") + class NODE_PT_node_tree_interface_outputs(NodeTreeInterfacePanel, Panel): bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 56a63215dcb..bbf9548a973 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -1959,7 +1959,6 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel): split.prop(strip, "show_waveform") - class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel): bl_label = "Compositing" bl_category = "Strip" diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 158e07659bc..e00bd2edef9 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2322,7 +2322,7 @@ class _defs_curves_sculpt: context, idname_prefix="builtin_brush.", icon_prefix="ops.curves.sculpt_", - type= bpy.types.Brush, + type=bpy.types.Brush, attr="curves_sculpt_tool", ) @@ -2527,7 +2527,8 @@ class _defs_sequencer_generic: icon="ops.transform.transform", widget="SEQUENCER_GGT_gizmo2d", # No keymap default action, only for gizmo! - ) + ) + class _defs_sequencer_select: @ToolDef.from_fn diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index b954d726ca3..eaa0fd87bce 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -2281,6 +2281,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel): ), ) + # Keep this as tweaks can be useful to restore. """ class USERPREF_PT_experimental_tweaks(ExperimentalPanel, Panel): @@ -2295,6 +2296,7 @@ class USERPREF_PT_experimental_tweaks(ExperimentalPanel, Panel): """ + class USERPREF_PT_experimental_debugging(ExperimentalPanel, Panel): bl_label = "Debugging" diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 74f20aca072..fc518e929d9 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2835,6 +2835,7 @@ class VIEW3D_MT_object_cleanup(Menu): layout.operator("object.material_slot_remove_unused", text="Remove Unused Material Slots") + class VIEW3D_MT_object_asset(Menu): bl_label = "Asset" @@ -6500,7 +6501,6 @@ class VIEW3D_PT_overlay_edit_mesh_normals(Panel): row.prop(overlay, "use_normals_constant_screen_size", text="", icon='FIXED_SIZE') - class VIEW3D_PT_overlay_edit_mesh_freestyle(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'HEADER' diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 332933be68a..10dfd182836 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -483,15 +483,19 @@ class SelectPaintSlotHelper: match getattr(mode_settings, self.canvas_source_attr_name): case 'MATERIAL': if len(ob.material_slots) > 1: - layout.template_list("MATERIAL_UL_matslots", "layers", - ob, "material_slots", - ob, "active_material_index", rows=2) + layout.template_list( + "MATERIAL_UL_matslots", "layers", + ob, "material_slots", + ob, "active_material_index", rows=2, + ) mat = ob.active_material if mat and mat.texture_paint_images: row = layout.row() - row.template_list("TEXTURE_UL_texpaintslots", "", - mat, "texture_paint_slots", - mat, "paint_active_slot", rows=2) + row.template_list( + "TEXTURE_UL_texpaintslots", "", + mat, "texture_paint_slots", + mat, "paint_active_slot", rows=2, + ) if mat.texture_paint_slots: slot = mat.texture_paint_slots[mat.paint_active_slot] @@ -517,7 +521,7 @@ class SelectPaintSlotHelper: else: layout.menu("VIEW3D_MT_tools_projectpaint_uvlayer", text=uv_text, translate=False) have_image = getattr(settings, self.canvas_image_attr_name) is not None - + self.draw_image_interpolation(layout=layout, mode_settings=mode_settings) case 'COLOR_ATTRIBUTE': @@ -562,7 +566,6 @@ class VIEW3D_PT_slots_projectpaint(SelectPaintSlotHelper, View3DPanel, Panel): layout.prop(mode_settings, "interpolation", text="") - class VIEW3D_PT_slots_paint_canvas(SelectPaintSlotHelper, View3DPanel, Panel): bl_category = "Tool" bl_context = ".sculpt_mode" # dot on purpose (access from topbar) diff --git a/release/scripts/startup/keyingsets_builtins.py b/release/scripts/startup/keyingsets_builtins.py index 486137f4247..fb287183c99 100644 --- a/release/scripts/startup/keyingsets_builtins.py +++ b/release/scripts/startup/keyingsets_builtins.py @@ -201,7 +201,7 @@ class BUILTIN_KSI_BendyBones(KeyingSetInfo): # VisualLocation class BUILTIN_KSI_VisualLoc(KeyingSetInfo): """Insert a keyframe on each of the location channels, """ \ - """taking into account effects of constraints and relationships""" + """taking into account effects of constraints and relationships""" bl_label = "Visual Location" bl_options = {'INSERTKEY_VISUAL'} @@ -219,7 +219,7 @@ class BUILTIN_KSI_VisualLoc(KeyingSetInfo): # VisualRotation class BUILTIN_KSI_VisualRot(KeyingSetInfo): """Insert a keyframe on each of the rotation channels, """ \ - """taking into account effects of constraints and relationships""" + """taking into account effects of constraints and relationships""" bl_label = "Visual Rotation" bl_options = {'INSERTKEY_VISUAL'} @@ -237,7 +237,7 @@ class BUILTIN_KSI_VisualRot(KeyingSetInfo): # VisualScaling class BUILTIN_KSI_VisualScaling(KeyingSetInfo): """Insert a keyframe on each of the scale channels, """ \ - """taking into account effects of constraints and relationships""" + """taking into account effects of constraints and relationships""" bl_label = "Visual Scale" bl_options = {'INSERTKEY_VISUAL'} @@ -255,7 +255,7 @@ class BUILTIN_KSI_VisualScaling(KeyingSetInfo): # VisualLocRot class BUILTIN_KSI_VisualLocRot(KeyingSetInfo): """Insert a keyframe on each of the location and rotation channels, """ \ - """taking into account effects of constraints and relationships""" + """taking into account effects of constraints and relationships""" bl_label = "Visual Location & Rotation" bl_options = {'INSERTKEY_VISUAL'} @@ -277,7 +277,7 @@ class BUILTIN_KSI_VisualLocRot(KeyingSetInfo): # VisualLocScale class BUILTIN_KSI_VisualLocScale(KeyingSetInfo): """Insert a keyframe on each of the location and scale channels, """ \ - """taking into account effects of constraints and relationships""" + """taking into account effects of constraints and relationships""" bl_label = "Visual Location & Scale" bl_options = {'INSERTKEY_VISUAL'} @@ -299,7 +299,7 @@ class BUILTIN_KSI_VisualLocScale(KeyingSetInfo): # VisualLocRotScale class BUILTIN_KSI_VisualLocRotScale(KeyingSetInfo): """Insert a keyframe on each of the location, """ \ - """rotation and scale channels, taking into account effects of constraints and relationships""" + """rotation and scale channels, taking into account effects of constraints and relationships""" bl_label = "Visual Location, Rotation & Scale" bl_options = {'INSERTKEY_VISUAL'} @@ -323,7 +323,7 @@ class BUILTIN_KSI_VisualLocRotScale(KeyingSetInfo): # VisualRotScale class BUILTIN_KSI_VisualRotScale(KeyingSetInfo): """Insert a keyframe on each of the rotation and scale channels, """ \ - """taking into account effects of constraints and relationships""" + """taking into account effects of constraints and relationships""" bl_label = "Visual Rotation & Scale" bl_options = {'INSERTKEY_VISUAL'} @@ -525,14 +525,14 @@ class WholeCharacterMixin: class BUILTIN_KSI_WholeCharacter(WholeCharacterMixin, KeyingSetInfo): """Insert a keyframe for all properties that are likely to get animated in a character rig """ \ - """(useful when blocking out a shot)""" + """(useful when blocking out a shot)""" bl_idname = ANIM_KS_WHOLE_CHARACTER_ID bl_label = "Whole Character" class BUILTIN_KSI_WholeCharacterSelected(WholeCharacterMixin, KeyingSetInfo): """Insert a keyframe for all properties that are likely to get animated in a character rig """ \ - """(only selected bones)""" + """(only selected bones)""" bl_idname = ANIM_KS_WHOLE_CHARACTER_SELECTED_ID bl_label = "Whole Character (Selected Bones Only)" diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index e7c00f4915e..737ea9350b3 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -64,7 +64,8 @@ node_tree_group_type = { 'GeometryNodeTree': 'GeometryNodeGroup', } -# Custom Menu for Geometry Node Curves + +# Custom Menu for Geometry Node Curves. def curve_node_items(context): if context is None: return @@ -100,7 +101,8 @@ def curve_node_items(context): yield NodeItem("GeometryNodeSetSplineResolution") yield NodeItem("GeometryNodeCurveSplineType") -# Custom Menu for Geometry Node Mesh + +# Custom Menu for Geometry Node Mesh. def mesh_node_items(context): if context is None: return @@ -131,7 +133,8 @@ def mesh_node_items(context): yield NodeItemCustom(draw=lambda self, layout, context: layout.separator()) yield NodeItem("GeometryNodeSetShadeSmooth") -# Custom Menu for Geometry Nodes "Geometry" category + +# Custom Menu for Geometry Nodes "Geometry" category. def geometry_node_items(context): if context is None: return @@ -154,7 +157,8 @@ def geometry_node_items(context): yield NodeItem("GeometryNodeSetID") yield NodeItem("GeometryNodeSetPosition") -# Custom Menu for Geometry Node Input Nodes + +# Custom Menu for Geometry Node Input Nodes. def geometry_input_node_items(context): if context is None: return @@ -181,7 +185,8 @@ def geometry_input_node_items(context): yield NodeItem("GeometryNodeInputRadius") yield NodeItem("GeometryNodeInputSceneTime") -# Custom Menu for Material Nodes + +# Custom Menu for Material Nodes. def geometry_material_node_items(context): if context is None: return @@ -196,7 +201,8 @@ def geometry_material_node_items(context): yield NodeItem("GeometryNodeSetMaterial") yield NodeItem("GeometryNodeSetMaterialIndex") -# Custom Menu for Geometry Node Points + +# Custom Menu for Geometry Node Points. def point_node_items(context): if context is None: return @@ -210,7 +216,8 @@ def point_node_items(context): yield NodeItemCustom(draw=lambda self, layout, context: layout.separator()) yield NodeItem("GeometryNodeSetPointRadius") -# generic node group items generator for shader, compositor, geometry and texture node groups + +# Generic node group items generator for shader, compositor, geometry and texture node groups. def node_group_items(context): if context is None: return diff --git a/source/blender/blenkernel/BKE_curve_to_mesh.hh b/source/blender/blenkernel/BKE_curve_to_mesh.hh index a49cb6eb7f5..6e657542e0f 100644 --- a/source/blender/blenkernel/BKE_curve_to_mesh.hh +++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh @@ -2,7 +2,7 @@ #pragma once -struct CurveEval; +struct CurvesGeometry; struct Mesh; /** \file @@ -21,11 +21,13 @@ namespace blender::bke { * changed anyway in a way that affects the normals. So currently this code uses the safer / * simpler solution of deferring normal calculation to the rest of Blender. */ -Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, bool fill_caps); +Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, + const CurvesGeometry &profile, + bool fill_caps); /** * Create a loose-edge mesh based on the evaluated path of the curve's splines. * Transfer curve attributes to the mesh. */ -Mesh *curve_to_wire_mesh(const CurveEval &curve); +Mesh *curve_to_wire_mesh(const CurvesGeometry &curve); } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 06971a2243a..282e2a40bd0 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -183,6 +183,13 @@ class CurvesGeometry : public ::CurvesGeometry { MutableSpan<int> resolution_for_write(); /** + * The angle used to rotate evaluated normals around the tangents after their calculation. + * Call #tag_normals_changed after changes. + */ + VArray<float> tilt() const; + MutableSpan<float> tilt_for_write(); + + /** * Which method to use for calculating the normals of evaluated points (#NormalMode). * Call #tag_normals_changed after changes. */ @@ -321,6 +328,10 @@ class CurvesGeometry : public ::CurvesGeometry { * calculated. That can be ensured with #ensure_evaluated_offsets. */ void interpolate_to_evaluated(int curve_index, GSpan src, GMutableSpan dst) const; + /** + * Evaluate generic data for curve control points to the standard evaluated points of the curves. + */ + void interpolate_to_evaluated(GSpan src, GMutableSpan dst) const; private: /** @@ -439,6 +450,13 @@ bool segment_is_vector(Span<int8_t> handle_types_left, bool last_cylic_segment_is_vector(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right); /** + * Return true if the handle types at the index are free (#BEZIER_HANDLE_FREE) or vector + * (#BEZIER_HANDLE_VECTOR). In these cases, directional continuitity from the previous and next + * evaluated segments is assumed not to be desired. + */ +bool point_is_sharp(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right, int index); + +/** * Calculate offsets into the curve's evaluated points for each control point. While most control * point edges generate the number of edges specified by the resolution, vector segments only * generate one edge. @@ -708,4 +726,22 @@ inline float CurvesGeometry::evaluated_length_total_for_curve(const int curve_in /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Bezier Inline Methods + * \{ */ + +namespace curves::bezier { + +inline bool point_is_sharp(const Span<int8_t> handle_types_left, + const Span<int8_t> handle_types_right, + const int index) +{ + return ELEM(handle_types_left[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE) || + ELEM(handle_types_right[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE); +} + +} // namespace curves::bezier + +/** \} */ + } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index 382bfd5b7db..6784a1296e9 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -264,7 +264,7 @@ struct FCurve *id_data_find_fcurve( ID *id, void *data, struct StructRNA *type, const char *prop_name, int index, bool *r_driven); /** - * Get list of LinkData's containing pointers to the F-curves + * Get list of LinkData's containing pointers to the F-Curves * which control the types of data indicated. * e.g. `numMatches = BKE_fcurves_filter(matches, &act->curves, "pose.bones[", "MyFancyBone");` * diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h index d82e7460071..2070584a8a0 100644 --- a/source/blender/blenkernel/BKE_global.h +++ b/source/blender/blenkernel/BKE_global.h @@ -195,12 +195,13 @@ enum { G_DEBUG_XR = (1 << 19), /* XR/OpenXR messages */ G_DEBUG_XR_TIME = (1 << 20), /* XR/OpenXR timing messages */ - G_DEBUG_GHOST = (1 << 21), /* Debug GHOST module. */ + G_DEBUG_GHOST = (1 << 21), /* Debug GHOST module. */ + G_DEBUG_WINTAB = (1 << 22), /* Debug Wintab. */ }; #define G_DEBUG_ALL \ (G_DEBUG | G_DEBUG_FFMPEG | G_DEBUG_PYTHON | G_DEBUG_EVENTS | G_DEBUG_WM | G_DEBUG_JOBS | \ - G_DEBUG_FREESTYLE | G_DEBUG_DEPSGRAPH | G_DEBUG_IO | G_DEBUG_GHOST) + G_DEBUG_FREESTYLE | G_DEBUG_DEPSGRAPH | G_DEBUG_IO | G_DEBUG_GHOST | G_DEBUG_WINTAB) /** #Global.fileflags */ enum { diff --git a/source/blender/blenkernel/BKE_layer.h b/source/blender/blenkernel/BKE_layer.h index cad6f6d6645..3e4f2fe154e 100644 --- a/source/blender/blenkernel/BKE_layer.h +++ b/source/blender/blenkernel/BKE_layer.h @@ -594,9 +594,9 @@ void BKE_view_layer_rename_lightgroup(ViewLayer *view_layer, ViewLayerLightgroup *lightgroup, const char *name); -void BKE_lightgroup_membership_get(struct LightgroupMembership *lgm, char *value); +void BKE_lightgroup_membership_get(struct LightgroupMembership *lgm, char *name); int BKE_lightgroup_membership_length(struct LightgroupMembership *lgm); -void BKE_lightgroup_membership_set(struct LightgroupMembership **lgm, const char *value); +void BKE_lightgroup_membership_set(struct LightgroupMembership **lgm, const char *name); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index 41a02545591..881e86ccc54 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -491,7 +491,6 @@ struct Object *BKE_modifiers_is_deformed_by_lattice(struct Object *ob); struct Object *BKE_modifiers_is_deformed_by_curve(struct Object *ob); bool BKE_modifiers_uses_multires(struct Object *ob); bool BKE_modifiers_uses_armature(struct Object *ob, struct bArmature *arm); -bool BKE_modifiers_uses_subsurf_facedots(const struct Scene *scene, struct Object *ob); bool BKE_modifiers_is_correctable_deformed(const struct Scene *scene, struct Object *ob); void BKE_modifier_free_temporary_data(struct ModifierData *md); diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index db773d34cdc..4ae37095411 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -30,7 +30,9 @@ struct EdgeSet; struct EnumPropertyItem; struct GHash; struct GridPaintMask; +struct Image; struct ImagePool; +struct ImageUser; struct ListBase; struct MLoop; struct MLoopTri; @@ -650,6 +652,11 @@ typedef struct SculptSession { */ bool sticky_shading_color; + /** + * Last used painting canvas key. + */ + char *last_paint_canvas_key; + } SculptSession; void BKE_sculptsession_free(struct Object *ob); @@ -727,8 +734,16 @@ enum { }; /* paint_canvas.cc */ -struct Image *BKE_paint_canvas_image_get(const struct PaintModeSettings *settings, - struct Object *ob); +/** + * Create a key that can be used to compare with previous ones to identify changes. + * The resulting 'string' is owned by the caller. + */ +char *BKE_paint_canvas_key_get(struct PaintModeSettings *settings, struct Object *ob); + +bool BKE_paint_canvas_image_get(struct PaintModeSettings *settings, + struct Object *ob, + struct Image **r_image, + struct ImageUser **r_image_user); int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings, struct Object *ob); diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h index 775847f27f8..bb918fcfdcb 100644 --- a/source/blender/blenkernel/BKE_pbvh.h +++ b/source/blender/blenkernel/BKE_pbvh.h @@ -31,10 +31,13 @@ struct MLoopTri; struct MPoly; struct MVert; struct Mesh; +struct MeshElemMap; struct PBVH; struct PBVHNode; struct SubdivCCG; struct TaskParallelSettings; +struct Image; +struct ImageUser; struct MeshElemMap; typedef struct PBVH PBVH; @@ -48,6 +51,15 @@ typedef struct { float (*color)[4]; } PBVHColorBufferNode; +typedef struct PBVHPixelsNode { + /** + * Contains triangle/pixel data used during texture painting. + * + * Contains #blender::bke::pbvh::pixels::NodeData. + */ + void *node_data; +} PBVHPixelsNode; + typedef enum { PBVH_Leaf = 1 << 0, @@ -66,6 +78,8 @@ typedef enum { PBVH_UpdateTopology = 1 << 13, PBVH_UpdateColor = 1 << 14, + PBVH_RebuildPixels = 1 << 15, + } PBVHNodeFlags; typedef struct PBVHFrustumPlanes { @@ -127,6 +141,12 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh, struct BMLog *log, int cd_vert_node_offset, int cd_face_node_offset); + +void BKE_pbvh_build_pixels(PBVH *pbvh, + const struct MLoop *mloop, + struct CustomData *ldata, + struct Image *image, + struct ImageUser *image_user); void BKE_pbvh_free(PBVH *pbvh); /* Hierarchical Search in the BVH, two methods: @@ -287,6 +307,7 @@ bool BKE_pbvh_node_fully_masked_get(PBVHNode *node); void BKE_pbvh_node_fully_unmasked_set(PBVHNode *node, int fully_masked); bool BKE_pbvh_node_fully_unmasked_get(PBVHNode *node); +void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh); void BKE_pbvh_vert_mark_update(PBVH *pbvh, int index); void BKE_pbvh_node_get_grids(PBVH *pbvh, diff --git a/source/blender/blenkernel/BKE_pbvh_pixels.hh b/source/blender/blenkernel/BKE_pbvh_pixels.hh new file mode 100644 index 00000000000..35eb340d0a1 --- /dev/null +++ b/source/blender/blenkernel/BKE_pbvh_pixels.hh @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#pragma once + +#include "BLI_math.h" +#include "BLI_math_vec_types.hh" +#include "BLI_rect.h" +#include "BLI_vector.hh" + +#include "DNA_image_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_image.h" +#include "BKE_image_wrappers.hh" + +#include "IMB_imbuf_types.h" + +namespace blender::bke::pbvh::pixels { + +struct TrianglePaintInput { + int3 vert_indices; + /** + * Delta barycentric coordinates between 2 neighbouring UV's in the U direction. + * + * Only the first two coordinates are stored. The third should be recalculated + */ + float2 delta_barycentric_coord_u; + + /** + * Initially only the vert indices are known. + * + * delta_barycentric_coord_u is initialized in a later stage as it requires image tile + * dimensions. + */ + TrianglePaintInput(const int3 vert_indices) + : vert_indices(vert_indices), delta_barycentric_coord_u(0.0f, 0.0f) + { + } +}; + +/** + * Data shared between pixels that belong to the same triangle. + * + * Data is stored as a list of structs, grouped by usage to improve performance (improves CPU + * cache prefetching). + */ +struct Triangles { + /** Data accessed by the inner loop of the painting brush. */ + Vector<TrianglePaintInput> paint_input; + + public: + void append(const int3 vert_indices) + { + this->paint_input.append(TrianglePaintInput(vert_indices)); + } + + TrianglePaintInput &get_paint_input(const int index) + { + return paint_input[index]; + } + + const TrianglePaintInput &get_paint_input(const int index) const + { + return paint_input[index]; + } + + void clear() + { + paint_input.clear(); + } + + uint64_t size() const + { + return paint_input.size(); + } + + uint64_t mem_size() const + { + return paint_input.size() * sizeof(TrianglePaintInput); + } +}; + +/** + * Encode sequential pixels to reduce memory footprint. + */ +struct PackedPixelRow { + /** Barycentric coordinate of the first pixel. */ + float2 start_barycentric_coord; + /** Image coordinate starting of the first pixel. */ + ushort2 start_image_coordinate; + /** Number of sequential pixels encoded in this package. */ + ushort num_pixels; + /** Reference to the pbvh triangle index. */ + ushort triangle_index; +}; + +/** + * Node pixel data containing the pixels for a single UDIM tile. + */ +struct UDIMTilePixels { + /** UDIM Tile number. */ + short tile_number; + + struct { + bool dirty : 1; + } flags; + + /* Dirty region of the tile in image space. */ + rcti dirty_region; + + Vector<PackedPixelRow> pixel_rows; + + UDIMTilePixels() + { + flags.dirty = false; + BLI_rcti_init_minmax(&dirty_region); + } + + void mark_dirty(const PackedPixelRow &pixel_row) + { + int2 start_image_coord(pixel_row.start_image_coordinate.x, pixel_row.start_image_coordinate.y); + BLI_rcti_do_minmax_v(&dirty_region, start_image_coord); + BLI_rcti_do_minmax_v(&dirty_region, start_image_coord + int2(pixel_row.num_pixels + 1, 0)); + flags.dirty = true; + } + + void clear_dirty() + { + BLI_rcti_init_minmax(&dirty_region); + flags.dirty = false; + } +}; + +struct NodeData { + struct { + bool dirty : 1; + } flags; + + Vector<UDIMTilePixels> tiles; + Triangles triangles; + + NodeData() + { + flags.dirty = false; + } + + UDIMTilePixels *find_tile_data(const image::ImageTileWrapper &image_tile) + { + for (UDIMTilePixels &tile : tiles) { + if (tile.tile_number == image_tile.get_tile_number()) { + return &tile; + } + } + return nullptr; + } + + void mark_region(Image &image, const image::ImageTileWrapper &image_tile, ImBuf &image_buffer) + { + UDIMTilePixels *tile = find_tile_data(image_tile); + if (tile && tile->flags.dirty) { + BKE_image_partial_update_mark_region( + &image, image_tile.image_tile, &image_buffer, &tile->dirty_region); + tile->clear_dirty(); + } + } + + void clear_data() + { + tiles.clear(); + triangles.clear(); + } + + static void free_func(void *instance) + { + NodeData *node_data = static_cast<NodeData *>(instance); + MEM_delete(node_data); + } +}; + +NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node); +void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user); + +} // namespace blender::bke::pbvh::pixels diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index ce4131a0627..710e2900d78 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -244,6 +244,7 @@ set(SRC intern/pbvh.cc intern/pbvh.c intern/pbvh_bmesh.c + intern/pbvh_pixels.cc intern/pointcache.c intern/pointcloud.cc intern/preferences.c diff --git a/source/blender/blenkernel/intern/action_mirror.c b/source/blender/blenkernel/intern/action_mirror.c index bb988605923..7ef561c8216 100644 --- a/source/blender/blenkernel/intern/action_mirror.c +++ b/source/blender/blenkernel/intern/action_mirror.c @@ -37,21 +37,21 @@ * That could be useful if a user for example only has 2x rotation channels set. * In practice users typically keyframe all rotation channels or none. * - * - F-curve modifiers are disabled for evaluation, + * - F-Curve modifiers are disabled for evaluation, * so the values written back to the keyframes don't include modifier offsets. * * - Sub-frame key-frames aren't supported, * this could be added if needed without much trouble. * - * - F-curves must have a #FCurve.bezt array (sampled curves aren't supported). + * - F-Curves must have a #FCurve.bezt array (sampled curves aren't supported). * \{ */ /** - * This structure is created for each pose channels F-curve, + * This structure is created for each pose channels F-Curve, * an action be evaluated and stored in `fcurve_eval`, * with the mirrored values written into `bezt_array`. * - * Store F-curve evaluated values, constructed with a sorted array of rounded keyed-frames, + * Store F-Curve evaluated values, constructed with a sorted array of rounded keyed-frames, * passed to #action_flip_pchan_cache_init. */ struct FCurve_KeyCache { @@ -60,7 +60,7 @@ struct FCurve_KeyCache { */ FCurve *fcurve; /** - * Cached evaluated F-curve values (without modifiers). + * Cached evaluated F-Curve values (without modifiers). */ float *fcurve_eval; /** @@ -117,7 +117,7 @@ static void action_flip_pchan_cache_init(struct FCurve_KeyCache *fkc, { BLI_assert(fkc->fcurve != NULL); - /* Cache the F-curve values for `keyed_frames`. */ + /* Cache the F-Curve values for `keyed_frames`. */ const int fcurve_flag = fkc->fcurve->flag; fkc->fcurve->flag |= FCURVE_MOD_OFF; fkc->fcurve_eval = MEM_mallocN(sizeof(float) * keyed_frames_len, __func__); @@ -175,7 +175,7 @@ static void action_flip_pchan(Object *ob_arm, * unavailable channels are left NULL. */ /** - * Structure to store transformation F-curves corresponding to a pose bones transformation. + * Structure to store transformation F-Curves corresponding to a pose bones transformation. * Match struct member names from #bPoseChannel so macros avoid repetition. * * \note There is no need to read values unless they influence the 4x4 transform matrix, @@ -210,7 +210,7 @@ static void action_flip_pchan(Object *ob_arm, #undef FCURVE_ASSIGN_VALUE #undef FCURVE_ASSIGN_ARRAY - /* Array of F-curves, for convenient access. */ + /* Array of F-Curves, for convenient access. */ #define FCURVE_CHANNEL_LEN (sizeof(fkc_pchan) / sizeof(struct FCurve_KeyCache)) FCurve *fcurve_array[FCURVE_CHANNEL_LEN]; int fcurve_array_len = 0; @@ -232,7 +232,7 @@ static void action_flip_pchan(Object *ob_arm, const float *keyed_frames = BKE_fcurves_calc_keyed_frames( fcurve_array, fcurve_array_len, &keyed_frames_len); - /* Initialize the pose channel curve cache from the F-curve. */ + /* Initialize the pose channel curve cache from the F-Curve. */ for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) { struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan; if (fkc->fcurve == NULL) { @@ -256,7 +256,7 @@ static void action_flip_pchan(Object *ob_arm, float arm_mat_inv[4][4]; invert_m4_m4(arm_mat_inv, pchan_flip ? pchan_flip->bone->arm_mat : pchan->bone->arm_mat); - /* Now flip the transformation & write it back to the F-curves in `fkc_pchan`. */ + /* Now flip the transformation & write it back to the F-Curves in `fkc_pchan`. */ for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) { @@ -329,7 +329,7 @@ static void action_flip_pchan(Object *ob_arm, BKE_pchan_apply_mat4(&pchan_temp, chan_mat, false); - /* Write the values back to the F-curves. */ + /* Write the values back to the F-Curves. */ #define WRITE_VALUE_FLT(id) \ if (fkc_pchan.id.fcurve_eval != NULL) { \ BezTriple *bezt = fkc_pchan.id.bezt_array[frame_index]; \ @@ -348,7 +348,7 @@ static void action_flip_pchan(Object *ob_arm, } \ ((void)0) - /* Write the values back the F-curves. */ + /* Write the values back the F-Curves. */ WRITE_ARRAY_FLT(loc); WRITE_ARRAY_FLT(eul); WRITE_ARRAY_FLT(quat); diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index 0a6ef8f0f01..0ed322d386c 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -2480,7 +2480,7 @@ void BKE_pose_where_is_bone(struct Depsgraph *depsgraph, float ctime, bool do_extra) { - /* This gives a chan_mat with actions (F-curve) results. */ + /* This gives a chan_mat with actions (F-Curve) results. */ if (do_extra) { BKE_pchan_calc_mat(pchan); } diff --git a/source/blender/blenkernel/intern/curve.cc b/source/blender/blenkernel/intern/curve.cc index 6815b4d7486..bad70d4ccc6 100644 --- a/source/blender/blenkernel/intern/curve.cc +++ b/source/blender/blenkernel/intern/curve.cc @@ -3182,7 +3182,7 @@ static void calchandleNurb_intern(BezTriple *bezt, len *= 2.5614f; if (len != 0.0f) { - /* Only for F-curves. */ + /* Only for F-Curves. */ bool leftviolate = false, rightviolate = false; if (!is_fcurve || fcurve_smoothing == FCURVE_SMOOTH_NONE) { diff --git a/source/blender/blenkernel/intern/curve_poly.cc b/source/blender/blenkernel/intern/curve_poly.cc index b0ed62d38dd..2db7cd71ad3 100644 --- a/source/blender/blenkernel/intern/curve_poly.cc +++ b/source/blender/blenkernel/intern/curve_poly.cc @@ -6,7 +6,7 @@ #include <algorithm> -#include "BLI_math_vector.h" +#include "BLI_math_rotation.hh" #include "BLI_math_vector.hh" #include "BKE_curves.hh" @@ -54,20 +54,6 @@ void calculate_tangents(const Span<float3> positions, } } -static float3 rotate_direction_around_axis(const float3 &direction, - const float3 &axis, - const float angle) -{ - BLI_ASSERT_UNIT_V3(direction); - BLI_ASSERT_UNIT_V3(axis); - - const float3 axis_scaled = axis * math::dot(direction, axis); - const float3 diff = direction - axis_scaled; - const float3 cross = math::cross(axis, diff); - - return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); -} - void calculate_normals_z_up(const Span<float3> tangents, MutableSpan<float3> normals) { BLI_assert(normals.size() == tangents.size()); @@ -98,7 +84,7 @@ static float3 calculate_next_normal(const float3 &last_normal, const float angle = angle_normalized_v3v3(last_tangent, current_tangent); if (angle != 0.0) { const float3 axis = math::normalize(math::cross(last_tangent, current_tangent)); - return rotate_direction_around_axis(last_normal, axis, angle); + return math::rotate_direction_around_axis(last_normal, axis, angle); } return last_normal; } @@ -147,7 +133,7 @@ void calculate_normals_minimum(const Span<float3> tangents, const float angle_step = correction_angle / normals.size(); for (const int i : normals.index_range()) { const float angle = angle_step * i; - normals[i] = rotate_direction_around_axis(normals[i], tangents[i], angle); + normals[i] = math::rotate_direction_around_axis(normals[i], tangents[i], angle); } } diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index 9b22a4c9726..c48d155f5ce 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -9,65 +9,15 @@ #include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" +#include "BKE_curves.hh" #include "BKE_geometry_set.hh" #include "BKE_material.h" #include "BKE_mesh.h" -#include "BKE_spline.hh" #include "BKE_curve_to_mesh.hh" namespace blender::bke { -/** 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, - MutableSpan<MEdge> r_edges, - const int vert_offset, - const int edge_offset) -{ - const int eval_size = spline.evaluated_points_size(); - for (const int i : IndexRange(eval_size - 1)) { - MEdge &edge = r_edges[edge_offset + i]; - edge.v1 = vert_offset + i; - edge.v2 = vert_offset + i + 1; - edge.flag = ME_LOOSEEDGE; - } - - if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { - MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1]; - edge.v1 = vert_offset + eval_size - 1; - edge.v2 = vert_offset; - edge.flag = ME_LOOSEEDGE; - } - - Span<float3> positions = spline.evaluated_positions(); - Span<float3> tangents = spline.evaluated_tangents(); - Span<float3> normals = spline.evaluated_normals(); - VArray<float> radii = spline.interpolate_to_evaluated(spline.radii()); - for (const int i : IndexRange(eval_size)) { - float4x4 point_matrix = float4x4::from_normalized_axis_data( - positions[i], normals[i], tangents[i]); - point_matrix.apply_scale(radii[i]); - - MVert &vert = r_verts[vert_offset + i]; - copy_v3_v3(vert.co, point_matrix * profile_vert); - } -} - static void mark_edges_sharp(MutableSpan<MEdge> edges) { for (MEdge &edge : edges) { @@ -75,36 +25,50 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges) } } -static void spline_extrude_to_mesh_data(const ResultInfo &info, - const bool fill_caps, - MutableSpan<MVert> r_verts, - MutableSpan<MEdge> r_edges, - MutableSpan<MLoop> r_loops, - MutableSpan<MPoly> r_polys) +static void fill_mesh_topology(const int vert_offset, + const int edge_offset, + const int poly_offset, + const int loop_offset, + const int main_point_num, + const int profile_point_num, + const bool main_cyclic, + const bool profile_cyclic, + const bool fill_caps, + MutableSpan<MEdge> edges, + MutableSpan<MLoop> loops, + MutableSpan<MPoly> polys) { - const Spline &spline = info.spline; - const Spline &profile = info.profile; - if (info.profile_vert_len == 1) { - vert_extrude_to_mesh_data(spline, - profile.evaluated_positions()[0], - r_verts, - r_edges, - info.vert_offset, - info.edge_offset); + const int main_segment_num = curves::curve_segment_size(main_point_num, main_cyclic); + const int profile_segment_num = curves::curve_segment_size(profile_point_num, profile_cyclic); + + if (profile_point_num == 1) { + for (const int i : IndexRange(main_point_num - 1)) { + MEdge &edge = edges[edge_offset + i]; + edge.v1 = vert_offset + i; + edge.v2 = vert_offset + i + 1; + edge.flag = ME_LOOSEEDGE; + } + + if (main_cyclic && main_segment_num > 1) { + MEdge &edge = edges[edge_offset + main_segment_num - 1]; + edge.v1 = vert_offset + main_point_num - 1; + edge.v2 = vert_offset; + edge.flag = ME_LOOSEEDGE; + } return; } /* Add the edges running along the length of the curve, starting at each profile vertex. */ - 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 main_edges_start = edge_offset; + for (const int i_profile : IndexRange(profile_point_num)) { + const int profile_edge_offset = main_edges_start + i_profile * main_segment_num; + for (const int i_ring : IndexRange(main_segment_num)) { + const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1; - 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_vert_offset = vert_offset + profile_point_num * i_ring; + const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring; - MEdge &edge = r_edges[profile_edge_offset + i_ring]; + MEdge &edge = edges[profile_edge_offset + i_ring]; edge.v1 = ring_vert_offset + i_profile; edge.v2 = next_ring_vert_offset + i_profile; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; @@ -112,16 +76,15 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info, } /* Add the edges running along each profile 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 profile_edges_start = main_edges_start + profile_point_num * main_segment_num; + for (const int i_ring : IndexRange(main_point_num)) { + const int ring_vert_offset = vert_offset + profile_point_num * i_ring; - 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; + const int ring_edge_offset = profile_edges_start + i_ring * profile_segment_num; + for (const int i_profile : IndexRange(profile_segment_num)) { + const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1; - MEdge &edge = r_edges[ring_edge_offset + i_profile]; + MEdge &edge = edges[ring_edge_offset + i_profile]; edge.v1 = ring_vert_offset + i_profile; edge.v2 = ring_vert_offset + i_next_profile; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; @@ -129,368 +92,410 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info, } /* Calculate poly and corner indices. */ - 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; + for (const int i_ring : IndexRange(main_segment_num)) { + const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1; - 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_vert_offset = vert_offset + profile_point_num * i_ring; + const int next_ring_vert_offset = vert_offset + profile_point_num * 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_edge_start = profile_edges_start + profile_segment_num * i_ring; + const int next_ring_edge_offset = profile_edges_start + profile_segment_num * i_next_ring; - 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; + const int ring_poly_offset = poly_offset + i_ring * profile_segment_num; + const int ring_loop_offset = loop_offset + i_ring * profile_segment_num * 4; - for (const int i_profile : IndexRange(info.profile_edge_len)) { + for (const int i_profile : IndexRange(profile_segment_num)) { const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4; - const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; + const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1; - 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; + const int main_edge_start = main_edges_start + main_segment_num * i_profile; + const int next_main_edge_start = main_edges_start + main_segment_num * i_next_profile; - MPoly &poly = r_polys[ring_poly_offset + i_profile]; + MPoly &poly = polys[ring_poly_offset + i_profile]; poly.loopstart = ring_segment_loop_offset; poly.totloop = 4; poly.flag = ME_SMOOTH; - MLoop &loop_a = r_loops[ring_segment_loop_offset]; + MLoop &loop_a = loops[ring_segment_loop_offset]; loop_a.v = ring_vert_offset + i_profile; loop_a.e = ring_edge_start + i_profile; - MLoop &loop_b = r_loops[ring_segment_loop_offset + 1]; + MLoop &loop_b = loops[ring_segment_loop_offset + 1]; loop_b.v = ring_vert_offset + i_next_profile; - loop_b.e = next_spline_edge_start + i_ring; - MLoop &loop_c = r_loops[ring_segment_loop_offset + 2]; + loop_b.e = next_main_edge_start + i_ring; + MLoop &loop_c = loops[ring_segment_loop_offset + 2]; loop_c.v = next_ring_vert_offset + i_next_profile; loop_c.e = next_ring_edge_offset + i_profile; - MLoop &loop_d = r_loops[ring_segment_loop_offset + 3]; + MLoop &loop_d = loops[ring_segment_loop_offset + 3]; loop_d.v = next_ring_vert_offset + i_profile; - loop_d.e = spline_edge_start + i_ring; + loop_d.e = main_edge_start + i_ring; } } - const bool has_caps = fill_caps && profile.is_cyclic() && !spline.is_cyclic(); + const bool has_caps = fill_caps && !main_cyclic && profile_cyclic; if (has_caps) { - const int poly_size = info.spline_edge_len * info.profile_edge_len; - const int cap_loop_offset = info.loop_offset + poly_size * 4; - const int cap_poly_offset = info.poly_offset + poly_size; + const int poly_size = main_segment_num * profile_segment_num; + const int cap_loop_offset = loop_offset + poly_size * 4; + const int cap_poly_offset = poly_offset + poly_size; - MPoly &poly_start = r_polys[cap_poly_offset]; + MPoly &poly_start = polys[cap_poly_offset]; poly_start.loopstart = cap_loop_offset; - poly_start.totloop = info.profile_edge_len; - MPoly &poly_end = r_polys[cap_poly_offset + 1]; - poly_end.loopstart = cap_loop_offset + info.profile_edge_len; - poly_end.totloop = info.profile_edge_len; - - const int last_ring_index = info.spline_vert_len - 1; - const int last_ring_vert_offset = info.vert_offset + info.profile_vert_len * last_ring_index; - const int last_ring_edge_offset = profile_edges_start + - info.profile_edge_len * last_ring_index; - - for (const int i : IndexRange(info.profile_edge_len)) { - const int i_inv = info.profile_edge_len - i - 1; - MLoop &loop_start = r_loops[cap_loop_offset + i]; - loop_start.v = info.vert_offset + i_inv; - loop_start.e = profile_edges_start + ((i == (info.profile_edge_len - 1)) ? - (info.profile_edge_len - 1) : - (i_inv - 1)); - MLoop &loop_end = r_loops[cap_loop_offset + info.profile_edge_len + i]; + poly_start.totloop = profile_segment_num; + MPoly &poly_end = polys[cap_poly_offset + 1]; + poly_end.loopstart = cap_loop_offset + profile_segment_num; + poly_end.totloop = profile_segment_num; + + const int last_ring_index = main_point_num - 1; + const int last_ring_vert_offset = vert_offset + profile_point_num * last_ring_index; + const int last_ring_edge_offset = profile_edges_start + profile_segment_num * last_ring_index; + + for (const int i : IndexRange(profile_segment_num)) { + const int i_inv = profile_segment_num - i - 1; + MLoop &loop_start = loops[cap_loop_offset + i]; + loop_start.v = vert_offset + i_inv; + loop_start.e = profile_edges_start + + ((i == (profile_segment_num - 1)) ? (profile_segment_num - 1) : (i_inv - 1)); + MLoop &loop_end = loops[cap_loop_offset + profile_segment_num + i]; loop_end.v = last_ring_vert_offset + i; loop_end.e = last_ring_edge_offset + i; } - mark_edges_sharp(r_edges.slice(profile_edges_start, info.profile_edge_len)); - mark_edges_sharp(r_edges.slice(last_ring_edge_offset, info.profile_edge_len)); + mark_edges_sharp(edges.slice(profile_edges_start, profile_segment_num)); + mark_edges_sharp(edges.slice(last_ring_edge_offset, profile_segment_num)); } +} - /* Calculate the positions of each profile ring profile along the spline. */ - Span<float3> positions = spline.evaluated_positions(); - Span<float3> tangents = spline.evaluated_tangents(); - Span<float3> normals = spline.evaluated_normals(); - Span<float3> profile_positions = profile.evaluated_positions(); - - VArray<float> radii = spline.interpolate_to_evaluated(spline.radii()); - 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 = 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]); - } +static void mark_bezier_vector_edges_sharp(const int profile_point_num, + const int main_segment_num, + const Span<int> control_point_offsets, + const Span<int8_t> handle_types_left, + const Span<int8_t> handle_types_right, + MutableSpan<MEdge> edges) +{ + const int main_edges_start = 0; + if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, 0)) { + mark_edges_sharp(edges.slice(main_edges_start, main_segment_num)); } - /* Mark edge loops from sharp vector control points sharp. */ - if (profile.type() == CURVE_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 + info.spline_edge_len * control_point_offsets[i], - info.spline_edge_len)); - } + for (const int i : IndexRange(profile_point_num).drop_front(1)) { + if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, i)) { + mark_edges_sharp(edges.slice( + main_edges_start + main_segment_num * control_point_offsets[i - 1], main_segment_num)); } } } -static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile) +static void fill_mesh_positions(const int main_point_num, + const int profile_point_num, + const Span<float3> main_positions, + const Span<float3> profile_positions, + const Span<float3> tangents, + const Span<float3> normals, + const Span<float> radii, + MutableSpan<MVert> mesh_positions) { - return curve.evaluated_points_size() * profile.evaluated_points_size(); -} + if (profile_point_num == 1) { + for (const int i_ring : IndexRange(main_point_num)) { + float4x4 point_matrix = float4x4::from_normalized_axis_data( + main_positions[i_ring], normals[i_ring], tangents[i_ring]); + if (!radii.is_empty()) { + point_matrix.apply_scale(radii[i_ring]); + } -static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile) -{ - /* Add the ring edges, with one ring for every curve vertex, and the edge loops - * that run along the length of the curve, starting on the first profile. */ - return curve.evaluated_points_size() * profile.evaluated_edges_size() + - curve.evaluated_edges_size() * profile.evaluated_points_size(); -} + MVert &vert = mesh_positions[i_ring]; + copy_v3_v3(vert.co, point_matrix * profile_positions.first()); + } + } + else { + for (const int i_ring : IndexRange(main_point_num)) { + float4x4 point_matrix = float4x4::from_normalized_axis_data( + main_positions[i_ring], normals[i_ring], tangents[i_ring]); + if (!radii.is_empty()) { + point_matrix.apply_scale(radii[i_ring]); + } -static inline int spline_extrude_loop_size(const Spline &curve, - const Spline &profile, - const bool fill_caps) -{ - const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; - const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic(); - const int caps = has_caps ? profile.evaluated_edges_size() * 2 : 0; - return tube + caps; + const int ring_vert_start = i_ring * profile_point_num; + for (const int i_profile : IndexRange(profile_point_num)) { + MVert &vert = mesh_positions[ring_vert_start + i_profile]; + copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); + } + } + } } -static inline int spline_extrude_poly_size(const Spline &curve, - const Spline &profile, - const bool fill_caps) +struct CurvesInfo { + const CurvesGeometry &main; + const CurvesGeometry &profile; + + /* Make sure these are spans because they are potentially accessed many times. */ + VArray_Span<bool> main_cyclic; + VArray_Span<bool> profile_cyclic; + + /* TODO: Remove once these are cached on #CurvesGeometry. */ + std::array<int, CURVE_TYPES_NUM> main_type_counts; + std::array<int, CURVE_TYPES_NUM> profile_type_counts; +}; +static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile) { - const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size(); - const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic(); - const int caps = has_caps ? 2 : 0; - return tube + caps; + return {main, + profile, + main.cyclic(), + profile.cyclic(), + main.count_curve_types(), + profile.count_curve_types()}; } struct ResultOffsets { + /** The total number of curve combinations. */ + int total; + + /** Offsets into the result mesh for each combination. */ Array<int> vert; Array<int> edge; Array<int> loop; Array<int> poly; + + /* The indices of the main and profile curves that form each combination. */ + Array<int> main_indices; + Array<int> profile_indices; }; -static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, - Span<SplinePtr> curves, - const bool fill_caps) +static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps) { - const int total = profiles.size() * curves.size(); - Array<int> vert(total + 1); - Array<int> edge(total + 1); - Array<int> loop(total + 1); - Array<int> poly(total + 1); + ResultOffsets result; + result.total = info.main.curves_num() * info.profile.curves_num(); + result.vert.reinitialize(result.total + 1); + result.edge.reinitialize(result.total + 1); + result.loop.reinitialize(result.total + 1); + result.poly.reinitialize(result.total + 1); + + result.main_indices.reinitialize(result.total); + result.profile_indices.reinitialize(result.total); + + info.main.ensure_evaluated_offsets(); + info.profile.ensure_evaluated_offsets(); int mesh_index = 0; int vert_offset = 0; int edge_offset = 0; int loop_offset = 0; int poly_offset = 0; - for (const int i_spline : curves.index_range()) { - for (const int i_profile : profiles.index_range()) { - vert[mesh_index] = vert_offset; - edge[mesh_index] = edge_offset; - loop[mesh_index] = loop_offset; - poly[mesh_index] = poly_offset; - vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]); - edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]); - loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile], fill_caps); - poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile], fill_caps); + for (const int i_main : info.main.curves_range()) { + const bool main_cyclic = info.main_cyclic[i_main]; + const int main_point_num = info.main.evaluated_points_for_curve(i_main).size(); + const int main_segment_num = curves::curve_segment_size(main_point_num, main_cyclic); + for (const int i_profile : info.profile.curves_range()) { + result.vert[mesh_index] = vert_offset; + result.edge[mesh_index] = edge_offset; + result.loop[mesh_index] = loop_offset; + result.poly[mesh_index] = poly_offset; + + result.main_indices[mesh_index] = i_main; + result.profile_indices[mesh_index] = i_profile; + + const bool profile_cyclic = info.profile_cyclic[i_profile]; + const int profile_point_num = info.profile.evaluated_points_for_curve(i_profile).size(); + const int profile_segment_num = curves::curve_segment_size(profile_point_num, + profile_cyclic); + + const bool has_caps = fill_caps && !main_cyclic && profile_cyclic; + const int tube_face_num = main_segment_num * profile_segment_num; + + vert_offset += main_point_num * profile_point_num; + + /* Add the ring edges, with one ring for every curve vertex, and the edge loops + * that run along the length of the curve, starting on the first profile. */ + edge_offset += main_point_num * profile_segment_num + main_segment_num * profile_point_num; + + /* Add two cap N-gons for every ending. */ + poly_offset += tube_face_num + (has_caps ? 2 : 0); + + /* All faces on the tube are quads, and all cap faces are N-gons with an edge for each + * profile edge. */ + loop_offset += tube_face_num * 4 + (has_caps ? profile_segment_num * 2 : 0); + mesh_index++; } } - vert.last() = vert_offset; - edge.last() = edge_offset; - loop.last() = loop_offset; - poly.last() = poly_offset; - return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; + result.vert.last() = vert_offset; + result.edge.last() = edge_offset; + result.loop.last() = loop_offset; + result.poly.last() = poly_offset; + + return result; } -static AttributeDomain get_result_attribute_domain(const MeshComponent &component, - const AttributeIDRef &attribute_id) +static AttributeDomain get_attribute_domain_for_mesh(const MeshComponent &mesh, + 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)) { + if (!mesh.attribute_is_builtin(attribute_id)) { return ATTR_DOMAIN_POINT; } - std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id); + std::optional<AttributeMetaData> meta_data = mesh.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 ATTR_DOMAIN_POINT; } 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) +static bool should_add_attribute_to_mesh(const CurveComponent &curve_component, + const MeshComponent &mesh_component, + const AttributeIDRef &id) { - 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; + /* The position attribute has special non-generic evaluation. */ + if (id.is_named() && id.name() == "position") { + return false; + } + /* Don't propagate built-in curves attributes that are not built-in on meshes. */ + if (curve_component.attribute_is_builtin(id) && !mesh_component.attribute_is_builtin(id)) { + return false; } + if (!id.should_be_kept()) { + return false; + } + return true; +} - GMutableSpan span = attribute.as_span(); - r_attributes.append(std::move(attribute)); - return std::make_optional<ResultAttributeData>({span, domain}); +static GSpan evaluated_attribute_if_necessary(const GVArray &src, + const CurvesGeometry &curves, + const Span<int> type_counts, + Vector<std::byte> &buffer) +{ + if (type_counts[CURVE_TYPE_POLY] == curves.curves_num() && src.is_span()) { + return src.get_internal_span(); + } + buffer.reinitialize(curves.evaluated_points_num() * src.type().size()); + GMutableSpan eval{src.type(), buffer.data(), curves.evaluated_points_num()}; + curves.interpolate_to_evaluated(src.get_internal_span(), eval); + return eval; } -/** - * 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; +/** Information at a specific combination of main and profile curves. */ +struct CombinationInfo { + int i_main; + int i_profile; + + IndexRange main_points; + IndexRange profile_points; + + bool main_cyclic; + bool profile_cyclic; + + int main_segment_num; + int profile_segment_num; + + IndexRange vert_range; + IndexRange edge_range; + IndexRange poly_range; + IndexRange loop_range; }; -static ResultAttributes create_result_attributes(const CurveEval &curve, - const CurveEval &profile, - MeshComponent &mesh_component) +template<typename Fn> +static void foreach_curve_combination(const CurvesInfo &info, + const ResultOffsets &offsets, + const Fn &fn) { - 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; + threading::parallel_for(IndexRange(offsets.total), 512, [&](IndexRange range) { + for (const int i : range) { + const int i_main = offsets.main_indices[i]; + const int i_profile = offsets.profile_indices[i]; + + const IndexRange main_points = info.main.evaluated_points_for_curve(i_main); + const IndexRange profile_points = info.profile.evaluated_points_for_curve(i_profile); + + const bool main_cyclic = info.main_cyclic[i_main]; + const bool profile_cyclic = info.profile_cyclic[i_profile]; + + /* Pass all information in a struct to avoid repeating arguments in many lambdas. + * The idea is that inlining `fn` will help avoid accessing unnecessary information, + * though that may or may not happen in practice. */ + fn(CombinationInfo{i_main, + i_profile, + main_points, + profile_points, + main_cyclic, + profile_cyclic, + curves::curve_segment_size(main_points.size(), main_cyclic), + curves::curve_segment_size(profile_points.size(), profile_cyclic), + offsets_to_range(offsets.vert.as_span(), i), + offsets_to_range(offsets.edge.as_span(), i), + offsets_to_range(offsets.poly.as_span(), i), + offsets_to_range(offsets.loop.as_span(), i)}); + } + }); } template<typename T> -static void copy_curve_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) +static void copy_main_point_data_to_mesh_verts(const Span<T> src, + const int profile_point_num, + 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]); + for (const int i_ring : src.index_range()) { + const int ring_vert_start = i_ring * profile_point_num; + dst.slice(ring_vert_start, profile_point_num).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) +static void copy_main_point_data_to_mesh_edges(const Span<T> src, + const int profile_point_num, + const int main_segment_num, + const int profile_segment_num, + 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]); + const int edges_start = profile_point_num * main_segment_num; + for (const int i_ring : src.index_range()) { + const int ring_edge_start = edges_start + profile_segment_num * i_ring; + dst.slice(ring_edge_start, profile_segment_num).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) +static void copy_main_point_data_to_mesh_faces(const Span<T> src, + const int main_segment_num, + const int profile_segment_num, + 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]); + for (const int i_ring : IndexRange(main_segment_num)) { + const int ring_face_start = profile_segment_num * i_ring; + dst.slice(ring_face_start, profile_segment_num).fill(src[i_ring]); } } -static void copy_curve_point_attribute_to_mesh(const GSpan src, - const ResultInfo &info, - ResultAttributeData &dst) +static void copy_main_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, + const ResultOffsets &offsets, + const AttributeDomain dst_domain, + const GSpan src_all, + GMutableSpan dst_all) { - GVArray 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) { + attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) { using T = decltype(dummy); - switch (dst.domain) { + const Span<T> src = src_all.typed<T>(); + MutableSpan<T> dst = dst_all.typed<T>(); + switch (dst_domain) { case ATTR_DOMAIN_POINT: - copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + copy_main_point_data_to_mesh_verts( + src.slice(info.main_points), info.profile_points.size(), dst.slice(info.vert_range)); + }); break; case ATTR_DOMAIN_EDGE: - copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + copy_main_point_data_to_mesh_edges(src.slice(info.main_points), + info.profile_points.size(), + info.main_segment_num, + info.profile_segment_num, + dst.slice(info.edge_range)); + }); break; case ATTR_DOMAIN_FACE: - copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + copy_main_point_data_to_mesh_faces(src.slice(info.main_points), + info.main_segment_num, + info.profile_segment_num, + dst.slice(info.poly_range)); + }); break; case ATTR_DOMAIN_CORNER: /* Unsupported for now, since there are no builtin attributes to convert into. */ @@ -504,12 +509,12 @@ static void copy_curve_point_attribute_to_mesh(const GSpan src, template<typename T> static void copy_profile_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, + const int main_point_num, 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)) { + for (const int i_ring : IndexRange(main_point_num)) { + const int profile_vert_start = i_ring * src.size(); + for (const int i_profile : src.index_range()) { dst[profile_vert_start + i_profile] = src[i_profile]; } } @@ -517,46 +522,59 @@ static void copy_profile_point_data_to_mesh_verts(const Span<T> src, template<typename T> static void copy_profile_point_data_to_mesh_edges(const Span<T> src, - const ResultInfo &info, + const int main_segment_num, 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]); + for (const int i_profile : src.index_range()) { + const int profile_edge_offset = i_profile * main_segment_num; + dst.slice(profile_edge_offset, main_segment_num).fill(src[i_profile]); } } template<typename T> static void copy_profile_point_data_to_mesh_faces(const Span<T> src, - const ResultInfo &info, + const int main_segment_num, + const int profile_segment_num, 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)) { + for (const int i_ring : IndexRange(main_segment_num)) { + const int profile_face_start = i_ring * profile_segment_num; + for (const int i_profile : IndexRange(profile_segment_num)) { 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) +static void copy_profile_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, + const ResultOffsets &offsets, + const AttributeDomain dst_domain, + const GSpan src_all, + GMutableSpan dst_all) { - GVArray 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) { + attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) { using T = decltype(dummy); - switch (dst.domain) { + const Span<T> src = src_all.typed<T>(); + MutableSpan<T> dst = dst_all.typed<T>(); + switch (dst_domain) { case ATTR_DOMAIN_POINT: - copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + copy_profile_point_data_to_mesh_verts( + src.slice(info.profile_points), info.main_points.size(), dst.slice(info.vert_range)); + }); break; case ATTR_DOMAIN_EDGE: - copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + copy_profile_point_data_to_mesh_edges( + src.slice(info.profile_points), info.main_segment_num, dst.slice(info.edge_range)); + }); break; case ATTR_DOMAIN_FACE: - copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + copy_profile_point_data_to_mesh_faces(src.slice(info.profile_points), + info.main_segment_num, + info.profile_segment_num, + dst.slice(info.poly_range)); + }); break; case ATTR_DOMAIN_CORNER: /* Unsupported for now, since there are no builtin attributes to convert into. */ @@ -568,198 +586,236 @@ static void copy_profile_point_attribute_to_mesh(const GSpan src, }); } -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) +static void copy_indices_to_offset_ranges(const VArray<T> &src, + const Span<int> curve_indices, + const Span<int> mesh_offsets, + MutableSpan<T> dst) { - for (const int i : IndexRange(src.size())) { - dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]); - } + /* This unnecessarily instantiates the "is single" case (which should be handled elsewhere if + * it's ever used for attributes), but the alternative is duplicating the function for spans and + * other virtual arrays. */ + devirtualize_varray(src, [&](const auto &src) { + threading::parallel_for(curve_indices.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + dst.slice(offsets_to_range(mesh_offsets, i)).fill(src[curve_indices[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) +static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offsets, + const Span<int> curve_indices, + const AttributeDomain dst_domain, + const GVArray &src, + GMutableSpan dst) { + Span<int> offsets; + switch (dst_domain) { + case ATTR_DOMAIN_POINT: + offsets = mesh_offsets.vert; + break; + case ATTR_DOMAIN_EDGE: + offsets = mesh_offsets.edge; + break; + case ATTR_DOMAIN_FACE: + offsets = mesh_offsets.poly; + break; + case ATTR_DOMAIN_CORNER: + offsets = mesh_offsets.loop; + break; + default: + BLI_assert_unreachable(); + return; + } 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; - } + copy_indices_to_offset_ranges(src.typed<T>(), curve_indices, offsets, dst.typed<T>()); }); } -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); - } -} - -Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, const bool fill_caps) +Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, + const CurvesGeometry &profile, + const bool fill_caps) { - Span<SplinePtr> profiles = profile.splines(); - Span<SplinePtr> curves = curve.splines(); + const CurvesInfo curves_info = get_curves_info(main, profile); - const ResultOffsets offsets = calculate_result_offsets(profiles, curves, fill_caps); + const ResultOffsets offsets = calculate_result_offsets(curves_info, fill_caps); if (offsets.vert.last() == 0) { return nullptr; } Mesh *mesh = BKE_mesh_new_nomain( offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last()); - BKE_id_material_eval_ensure_default_slot(&mesh->id); mesh->flag |= ME_AUTOSMOOTH; mesh->smoothresh = DEG2RADF(180.0f); BKE_mesh_normals_tag_dirty(mesh); + MutableSpan<MVert> verts(mesh->mvert, mesh->totvert); + MutableSpan<MEdge> edges(mesh->medge, mesh->totedge); + MutableSpan<MLoop> loops(mesh->mloop, mesh->totloop); + MutableSpan<MPoly> polys(mesh->mpoly, mesh->totpoly); + + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + fill_mesh_topology(info.vert_range.start(), + info.edge_range.start(), + info.poly_range.start(), + info.loop_range.start(), + info.main_points.size(), + info.profile_points.size(), + info.main_cyclic, + info.profile_cyclic, + fill_caps, + edges, + loops, + polys); + }); + + const Span<float3> main_positions = main.evaluated_positions(); + const Span<float3> tangents = main.evaluated_tangents(); + const Span<float3> normals = main.evaluated_normals(); + const Span<float3> profile_positions = profile.evaluated_positions(); + + Vector<std::byte> eval_buffer; + + Curves main_id = {nullptr}; + main_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(main); + CurveComponent main_component; + main_component.replace(&main_id, GeometryOwnershipType::Editable); + + Curves profile_id = {nullptr}; + profile_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(profile); + CurveComponent profile_component; + profile_component.replace(&profile_id, GeometryOwnershipType::Editable); + + Span<float> radii = {}; + if (main_component.attribute_exists("radius")) { + radii = evaluated_attribute_if_necessary( + main_component.attribute_get_for_read<float>("radius", ATTR_DOMAIN_POINT, 1.0f), + main, + curves_info.main_type_counts, + eval_buffer) + .typed<float>(); + } + + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + fill_mesh_positions(info.main_points.size(), + info.profile_points.size(), + main_positions.slice(info.main_points), + profile_positions.slice(info.profile_points), + tangents.slice(info.main_points), + normals.slice(info.main_points), + radii.is_empty() ? radii : radii.slice(info.main_points), + verts.slice(info.vert_range)); + }); + + if (curves_info.profile_type_counts[CURVE_TYPE_BEZIER] > 0) { + const VArray<int8_t> curve_types = profile.curve_types(); + const VArray_Span<int8_t> handle_types_left{profile.handle_types_left()}; + const VArray_Span<int8_t> handle_types_right{profile.handle_types_right()}; + + foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { + if (curve_types[info.i_profile] == CURVE_TYPE_BEZIER) { + const IndexRange points = profile.points_for_curve(info.i_profile); + mark_bezier_vector_edges_sharp(points.size(), + info.main_segment_num, + profile.bezier_evaluated_offsets_for_curve(info.i_profile), + handle_types_left.slice(points), + handle_types_right.slice(points), + edges.slice(info.edge_range)); + } + }); + } + + Set<AttributeIDRef> main_attributes; - /* Create the mesh component for retrieving attributes at this scope, since output attributes - * can keep a reference to the component for updating after retrieving write access. */ MeshComponent mesh_component; mesh_component.replace(mesh, GeometryOwnershipType::Editable); - ResultAttributes attributes = create_result_attributes(curve, profile, mesh_component); - 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; - 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, - fill_caps, - {mesh->mvert, mesh->totvert}, - {mesh->medge, mesh->totedge}, - {mesh->mloop, mesh->totloop}, - {mesh->mpoly, mesh->totpoly}); - - copy_point_domain_attributes_to_mesh(info, attributes); - } - }); + main_component.attribute_foreach([&](const AttributeIDRef &id, + const AttributeMetaData meta_data) { + if (!should_add_attribute_to_mesh(main_component, mesh_component, id)) { + return true; } + main_attributes.add_new(id); + + const AttributeDomain src_domain = meta_data.domain; + const CustomDataType type = meta_data.data_type; + GVArray src = main_component.attribute_try_get_for_read(id, src_domain, type); + + const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id); + OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(id, dst_domain, type); + if (!dst) { + return true; + } + + if (src_domain == ATTR_DOMAIN_POINT) { + copy_main_point_domain_attribute_to_mesh( + curves_info, + offsets, + dst_domain, + evaluated_attribute_if_necessary(src, main, curves_info.main_type_counts, eval_buffer), + dst.as_span()); + } + else if (src_domain == ATTR_DOMAIN_CURVE) { + copy_curve_domain_attribute_to_mesh( + offsets, offsets.main_indices, dst_domain, src, dst.as_span()); + } + + dst.save(); + return true; }); - copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes); + profile_component.attribute_foreach([&](const AttributeIDRef &id, + const AttributeMetaData meta_data) { + if (main_attributes.contains(id)) { + return true; + } + if (!should_add_attribute_to_mesh(profile_component, mesh_component, id)) { + return true; + } + const AttributeDomain src_domain = meta_data.domain; + const CustomDataType type = meta_data.data_type; + GVArray src = profile_component.attribute_try_get_for_read(id, src_domain, type); + + const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id); + OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(id, dst_domain, type); + if (!dst) { + return true; + } + + if (src_domain == ATTR_DOMAIN_POINT) { + copy_profile_point_domain_attribute_to_mesh( + curves_info, + offsets, + dst_domain, + evaluated_attribute_if_necessary( + src, profile, curves_info.profile_type_counts, eval_buffer), + dst.as_span()); + } + else if (src_domain == ATTR_DOMAIN_CURVE) { + copy_curve_domain_attribute_to_mesh( + offsets, offsets.profile_indices, dst_domain, src, dst.as_span()); + } - for (OutputAttribute &output_attribute : attributes.attributes) { - output_attribute.save(); - } + dst.save(); + return true; + }); return mesh; } -static CurveEval get_curve_single_vert() +static CurvesGeometry get_curve_single_vert() { - CurveEval curve; - std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); - spline->resize(1.0f); - spline->positions().fill(float3(0)); - spline->radii().fill(1.0f); - spline->tilts().fill(0.0f); - curve.add_spline(std::move(spline)); - - return curve; + CurvesGeometry curves(1, 1); + curves.offsets_for_write().last() = 1; + curves.positions_for_write().fill(float3(0)); + + return curves; } -Mesh *curve_to_wire_mesh(const CurveEval &curve) +Mesh *curve_to_wire_mesh(const CurvesGeometry &curve) { - static const CurveEval vert_curve = get_curve_single_vert(); + static const CurvesGeometry vert_curve = get_curve_single_vert(); return curve_to_mesh_sweep(curve, vert_curve, false); } diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index bdd8b3fc3d0..8e97884516c 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -12,6 +12,7 @@ #include "BLI_bounds.hh" #include "BLI_index_mask_ops.hh" #include "BLI_length_parameterize.hh" +#include "BLI_math_rotation.hh" #include "DNA_curves_types.h" @@ -22,6 +23,7 @@ namespace blender::bke { static const std::string ATTR_POSITION = "position"; static const std::string ATTR_RADIUS = "radius"; +static const std::string ATTR_TILT = "tilt"; static const std::string ATTR_CURVE_TYPE = "curve_type"; static const std::string ATTR_CYCLIC = "cyclic"; static const std::string ATTR_RESOLUTION = "resolution"; @@ -330,6 +332,15 @@ MutableSpan<int8_t> CurvesGeometry::normal_mode_for_write() return get_mutable_attribute<int8_t>(*this, ATTR_DOMAIN_CURVE, ATTR_NORMAL_MODE); } +VArray<float> CurvesGeometry::tilt() const +{ + return get_varray_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_TILT, 0.0f); +} +MutableSpan<float> CurvesGeometry::tilt_for_write() +{ + return get_mutable_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_TILT); +} + VArray<int8_t> CurvesGeometry::handle_types_left() const { return get_varray_attribute<int8_t>(*this, ATTR_DOMAIN_POINT, ATTR_HANDLE_TYPE_LEFT, 0); @@ -717,6 +728,15 @@ Span<float3> CurvesGeometry::evaluated_tangents() const return this->runtime->evaluated_tangent_cache; } +static void rotate_directions_around_axes(MutableSpan<float3> directions, + const Span<float3> axes, + const Span<float> angles) +{ + for (const int i : directions.index_range()) { + directions[i] = math::rotate_direction_around_axis(directions[i], axes[i], angles[i]); + } +} + Span<float3> CurvesGeometry::evaluated_normals() const { if (!this->runtime->normal_cache_dirty) { @@ -733,11 +753,16 @@ Span<float3> CurvesGeometry::evaluated_normals() const const Span<float3> evaluated_tangents = this->evaluated_tangents(); const VArray<bool> cyclic = this->cyclic(); const VArray<int8_t> normal_mode = this->normal_mode(); + const VArray<int8_t> types = this->curve_types(); + const VArray<float> tilt = this->tilt(); this->runtime->evaluated_normal_cache.resize(this->evaluated_points_num()); MutableSpan<float3> evaluated_normals = this->runtime->evaluated_normal_cache; threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) { + /* Reuse a buffer for the evaluated tilts. */ + Vector<float> evaluated_tilts; + for (const int curve_index : curves_range) { const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index); if (UNLIKELY(evaluated_points.is_empty())) { @@ -754,6 +779,27 @@ Span<float3> CurvesGeometry::evaluated_normals() const evaluated_normals.slice(evaluated_points)); break; } + + /* If the "tilt" attribute exists, rotate the normals around the tangents by the + * evaluated angles. We can avoid copying the tilts to evaluate them for poly curves. */ + if (!(tilt.is_single() && tilt.get_internal_single() == 0.0f)) { + const IndexRange points = this->points_for_curve(curve_index); + Span<float> curve_tilt = tilt.get_internal_span().slice(points); + if (types[curve_index] == CURVE_TYPE_POLY) { + rotate_directions_around_axes(evaluated_normals.slice(evaluated_points), + evaluated_tangents.slice(evaluated_points), + curve_tilt); + } + else { + evaluated_tilts.clear(); + evaluated_tilts.resize(evaluated_points.size()); + this->interpolate_to_evaluated( + curve_index, curve_tilt, evaluated_tilts.as_mutable_span()); + rotate_directions_around_axes(evaluated_normals.slice(evaluated_points), + evaluated_tangents.slice(evaluated_points), + evaluated_tilts.as_span()); + } + } } }); }); @@ -794,6 +840,48 @@ void CurvesGeometry::interpolate_to_evaluated(const int curve_index, BLI_assert_unreachable(); } +void CurvesGeometry::interpolate_to_evaluated(const GSpan src, GMutableSpan dst) const +{ + BLI_assert(!this->runtime->offsets_cache_dirty); + BLI_assert(!this->runtime->nurbs_basis_cache_dirty); + const VArray<int8_t> types = this->curve_types(); + const VArray<int> resolution = this->resolution(); + const VArray<bool> cyclic = this->cyclic(); + const VArray<int8_t> nurbs_orders = this->nurbs_orders(); + const Span<float> nurbs_weights = this->nurbs_weights(); + + threading::parallel_for(this->curves_range(), 512, [&](IndexRange curves_range) { + for (const int curve_index : curves_range) { + const IndexRange points = this->points_for_curve(curve_index); + const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index); + switch (types[curve_index]) { + case CURVE_TYPE_CATMULL_ROM: + curves::catmull_rom::interpolate_to_evaluated(src.slice(points), + cyclic[curve_index], + resolution[curve_index], + dst.slice(evaluated_points)); + continue; + case CURVE_TYPE_POLY: + dst.slice(evaluated_points).copy_from(src.slice(points)); + continue; + case CURVE_TYPE_BEZIER: + curves::bezier::interpolate_to_evaluated( + src.slice(points), + this->runtime->bezier_evaluated_offsets.as_span().slice(points), + dst.slice(evaluated_points)); + continue; + case CURVE_TYPE_NURBS: + curves::nurbs::interpolate_to_evaluated(this->runtime->nurbs_basis_cache[curve_index], + nurbs_orders[curve_index], + nurbs_weights.slice(points), + src.slice(points), + dst.slice(evaluated_points)); + continue; + } + } + }); +} + void CurvesGeometry::ensure_evaluated_lengths() const { if (!this->runtime->length_cache_dirty) { diff --git a/source/blender/blenkernel/intern/fcurve_cache.c b/source/blender/blenkernel/intern/fcurve_cache.c index d67a0ad0e1b..e3b9c4aa10b 100644 --- a/source/blender/blenkernel/intern/fcurve_cache.c +++ b/source/blender/blenkernel/intern/fcurve_cache.c @@ -71,7 +71,7 @@ struct FCurvePathCache *BKE_fcurve_pathcache_create(ListBase *list) } qsort(fcurve_array, fcurve_array_len, sizeof(FCurve *), fcurve_cmp_for_cache); - /* Allow for the case no F-curves share an RNA-path, otherwise this is over-allocated. + /* Allow for the case no F-Curves share an RNA-path, otherwise this is over-allocated. * Although in practice it's likely to only be 3-4x as large as is needed * (with transform channels for e.g.). */ struct FCurvePathCache_Span *span_table = MEM_mallocN(sizeof(*span_table) * fcurve_array_len, diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc index e1a79986719..041696fa8d3 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.cc +++ b/source/blender/blenkernel/intern/gpencil_geom.cc @@ -981,7 +981,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo * \{ */ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, - int i, + int point_index, float influence, int iterations, const bool smooth_caps, @@ -995,7 +995,7 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, /* Overview of the algorithm here and in the following smooth functions: * The smooth functions return the new attribute in question for a single point. - * The result is stored in r_gps->points[i], while the data is read from gps. + * The result is stored in r_gps->points[point_index], while the data is read from gps. * To get a correct result, duplicate the stroke point data and read from the copy, * while writing to the real stroke. Not doing that will result in acceptable, but * asymmetric results. @@ -1004,16 +1004,16 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, * the parameter "iterations" set to 1 or 2. (2 matches the old algorithm). */ - const bGPDspoint *pt = &gps->points[i]; + const bGPDspoint *pt = &gps->points[point_index]; const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0; /* If smooth_caps is false, the caps will not be translated by smoothing. */ - if (!smooth_caps && !is_cyclic && ELEM(i, 0, gps->totpoints - 1)) { - copy_v3_v3(&r_gps->points[i].x, &pt->x); + if (!smooth_caps && !is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) { + copy_v3_v3(&r_gps->points[point_index].x, &pt->x); return true; } /* This function uses a binomial kernel, which is the discrete version of gaussian blur. - * The weight for a vertex at the relative index i is + * The weight for a vertex at the relative index point_index is * w = nCr(n, j + n/2) / 2^n = (n/1 * (n-1)/2 * ... * (n-j-n/2)/(j+n/2)) / 2^n * All weights together sum up to 1 * This is equivalent to doing multiple iterations of averaging neighbors, @@ -1044,8 +1044,8 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, 0.0; double total_w = 0.0; for (int step = iterations; step > 0; step--) { - int before = i - step; - int after = i + step; + int before = point_index - step; + int after = point_index + step; float w_before = (float)(w - w2); float w_after = (float)(w - w2); @@ -1056,13 +1056,13 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, else { if (before < 0) { if (!smooth_caps) { - w_before *= -before / (float)i; + w_before *= -before / (float)point_index; } before = 0; } if (after > gps->totpoints - 1) { if (!smooth_caps) { - w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - i); + w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - point_index); } after = gps->totpoints - 1; } @@ -1089,7 +1089,7 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, add_v3_v3(sco, &pt->x); /* Based on influence factor, blend between original and optimal smoothed coordinate. */ - interp_v3_v3v3(&r_gps->points[i].x, &pt->x, sco, influence); + interp_v3_v3v3(&r_gps->points[point_index].x, &pt->x, sco, influence); return true; } @@ -1101,7 +1101,7 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, * \{ */ bool BKE_gpencil_stroke_smooth_strength( - bGPDstroke *gps, int i, float influence, int iterations, bGPDstroke *r_gps) + bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps) { /* If nothing to do, return early */ if (gps->totpoints <= 2 || iterations <= 0) { @@ -1110,15 +1110,15 @@ bool BKE_gpencil_stroke_smooth_strength( /* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */ - const bGPDspoint *pt = &gps->points[i]; + const bGPDspoint *pt = &gps->points[point_index]; const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0; float strength = 0.0f; const int n_half = (iterations * iterations) / 4 + iterations; double w = 1.0; double total_w = 0.0; for (int step = iterations; step > 0; step--) { - int before = i - step; - int after = i + step; + int before = point_index - step; + int after = point_index + step; float w_before = (float)w; float w_after = (float)w; @@ -1147,7 +1147,7 @@ bool BKE_gpencil_stroke_smooth_strength( strength /= total_w; /* Based on influence factor, blend between original and optimal smoothed value. */ - r_gps->points[i].strength = pt->strength + strength * influence; + r_gps->points[point_index].strength = pt->strength + strength * influence; return true; } @@ -1159,7 +1159,7 @@ bool BKE_gpencil_stroke_smooth_strength( * \{ */ bool BKE_gpencil_stroke_smooth_thickness( - bGPDstroke *gps, int i, float influence, int iterations, bGPDstroke *r_gps) + bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps) { /* If nothing to do, return early */ if (gps->totpoints <= 2 || iterations <= 0) { @@ -1168,15 +1168,15 @@ bool BKE_gpencil_stroke_smooth_thickness( /* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */ - const bGPDspoint *pt = &gps->points[i]; + const bGPDspoint *pt = &gps->points[point_index]; const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0; float pressure = 0.0f; const int n_half = (iterations * iterations) / 4 + iterations; double w = 1.0; double total_w = 0.0; for (int step = iterations; step > 0; step--) { - int before = i - step; - int after = i + step; + int before = point_index - step; + int after = point_index + step; float w_before = (float)w; float w_after = (float)w; @@ -1205,7 +1205,7 @@ bool BKE_gpencil_stroke_smooth_thickness( pressure /= total_w; /* Based on influence factor, blend between original and optimal smoothed value. */ - r_gps->points[i].pressure = pt->pressure + pressure * influence; + r_gps->points[point_index].pressure = pt->pressure + pressure * influence; return true; } @@ -1216,8 +1216,11 @@ bool BKE_gpencil_stroke_smooth_thickness( /** \name Stroke Smooth UV * \{ */ -bool BKE_gpencil_stroke_smooth_uv( - struct bGPDstroke *gps, int i, float influence, int iterations, struct bGPDstroke *r_gps) +bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, + int point_index, + float influence, + int iterations, + struct bGPDstroke *r_gps) { /* If nothing to do, return early */ if (gps->totpoints <= 2 || iterations <= 0) { @@ -1226,13 +1229,13 @@ bool BKE_gpencil_stroke_smooth_uv( /* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */ - const bGPDspoint *pt = &gps->points[i]; + const bGPDspoint *pt = &gps->points[point_index]; const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0; /* If don't change the caps. */ - if (!is_cyclic && ELEM(i, 0, gps->totpoints - 1)) { - r_gps->points[i].uv_rot = pt->uv_rot; - r_gps->points[i].uv_fac = pt->uv_fac; + if (!is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) { + r_gps->points[point_index].uv_rot = pt->uv_rot; + r_gps->points[point_index].uv_fac = pt->uv_fac; return true; } @@ -1242,8 +1245,8 @@ bool BKE_gpencil_stroke_smooth_uv( double w = 1.0; double total_w = 0.0; for (int step = iterations; step > 0; step--) { - int before = i - step; - int after = i + step; + int before = point_index - step; + int after = point_index + step; float w_before = (float)w; float w_after = (float)w; @@ -1253,11 +1256,11 @@ bool BKE_gpencil_stroke_smooth_uv( } else { if (before < 0) { - w_before *= -before / (float)i; + w_before *= -before / (float)point_index; before = 0; } if (after > gps->totpoints - 1) { - w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - i); + w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - point_index); after = gps->totpoints - 1; } } @@ -1281,8 +1284,8 @@ bool BKE_gpencil_stroke_smooth_uv( uv_fac /= total_w; /* Based on influence factor, blend between original and optimal smoothed value. */ - r_gps->points[i].uv_rot = pt->uv_rot + uv_rot * influence; - r_gps->points[i].uv_fac = pt->uv_fac + uv_fac * influence; + r_gps->points[point_index].uv_rot = pt->uv_rot + uv_rot * influence; + r_gps->points[point_index].uv_fac = pt->uv_fac + uv_fac * influence; return true; } diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index 482537d7fa9..53ec148fd2d 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -3106,7 +3106,7 @@ bool BKE_image_get_tile_info(char *filepath, ListBase *tiles, int *r_tile_start, eUDIM_TILE_FORMAT tile_format; char *udim_pattern = BKE_image_get_tile_strformat(filename, &tile_format); - bool is_udim = true; + bool all_valid_udim = true; int min_udim = IMA_UDIM_MAX + 1; int max_udim = 0; int id; @@ -3124,7 +3124,7 @@ bool BKE_image_get_tile_info(char *filepath, ListBase *tiles, int *r_tile_start, } if (id < 1001 || id > IMA_UDIM_MAX) { - is_udim = false; + all_valid_udim = false; break; } @@ -3135,7 +3135,10 @@ bool BKE_image_get_tile_info(char *filepath, ListBase *tiles, int *r_tile_start, BLI_filelist_free(dirs, dirs_num); MEM_SAFE_FREE(udim_pattern); - if (is_udim && min_udim <= IMA_UDIM_MAX) { + /* Ensure that all discovered UDIMs are valid and that there's at least 2 files in total. + * Downstream code checks the range value to determine tiled-ness; it's important we match that + * expectation here too (T97366). */ + if (all_valid_udim && min_udim <= IMA_UDIM_MAX && max_udim > min_udim) { BLI_join_dirfile(filepath, FILE_MAX, dirname, filename); *r_tile_start = min_udim; diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index bc569956f66..4caaf314888 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -1423,13 +1423,15 @@ static bool fill_texpaint_slots_cb(bNode *node, void *userdata) case SH_NODE_TEX_IMAGE: { TexPaintSlot *slot = &ma->texpaintslot[index]; slot->ima = (Image *)node->id; - slot->interp = ((NodeTexImage *)node->storage)->interpolation; + NodeTexImage *storage = (NodeTexImage *)node->storage; + slot->interp = storage->interpolation; + slot->image_user = &storage->iuser; /* for new renderer, we need to traverse the treeback in search of a UV node */ bNode *uvnode = nodetree_uv_node_recursive(node); if (uvnode) { - NodeShaderUVMap *storage = (NodeShaderUVMap *)uvnode->storage; - slot->uvname = storage->uv_map; + NodeShaderUVMap *uv_storage = (NodeShaderUVMap *)uvnode->storage; + slot->uvname = uv_storage->uv_map; /* set a value to index so UI knows that we have a valid pointer for the mesh */ slot->valid = true; } diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index defca433968..ff953ef5b46 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -25,6 +25,7 @@ #include "BLI_utildefines.h" #include "BKE_DerivedMesh.h" +#include "BKE_curves.hh" #include "BKE_deform.h" #include "BKE_displist.h" #include "BKE_editmesh.h" @@ -970,8 +971,7 @@ static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_o } const Curves *curves = get_evaluated_curves_from_object(evaluated_object); if (curves) { - std::unique_ptr<CurveEval> curve = curves_to_curve_eval(*curves); - return blender::bke::curve_to_wire_mesh(*curve); + return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry)); } return nullptr; } diff --git a/source/blender/blenkernel/intern/mesh_iterators.c b/source/blender/blenkernel/intern/mesh_iterators.c index e164ad9721b..77e62918441 100644 --- a/source/blender/blenkernel/intern/mesh_iterators.c +++ b/source/blender/blenkernel/intern/mesh_iterators.c @@ -310,6 +310,8 @@ void BKE_mesh_foreach_mapped_subdiv_face_center( BKE_mesh_vertex_normals_ensure(mesh) : NULL; const int *index = CustomData_get_layer(&mesh->pdata, CD_ORIGINDEX); + const BLI_bitmap *facedot_tags = mesh->runtime.subsurf_face_dot_tags; + BLI_assert(facedot_tags != NULL); if (index) { for (int i = 0; i < mesh->totpoly; i++, mp++) { @@ -320,8 +322,7 @@ void BKE_mesh_foreach_mapped_subdiv_face_center( ml = &mesh->mloop[mp->loopstart]; for (int j = 0; j < mp->totloop; j++, ml++) { mv = &mesh->mvert[ml->v]; - if (mv->flag & ME_VERT_FACEDOT) { - + if (BLI_BITMAP_TEST(facedot_tags, ml->v)) { func(userData, orig, mv->co, @@ -335,7 +336,7 @@ void BKE_mesh_foreach_mapped_subdiv_face_center( ml = &mesh->mloop[mp->loopstart]; for (int j = 0; j < mp->totloop; j++, ml++) { mv = &mesh->mvert[ml->v]; - if (mv->flag & ME_VERT_FACEDOT) { + if (BLI_BITMAP_TEST(facedot_tags, ml->v)) { func(userData, i, mv->co, (flag & MESH_FOREACH_USE_NORMAL) ? vert_normals[ml->v] : NULL); } } diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index b06e867cf37..90e9a2a2ff6 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -86,6 +86,7 @@ void BKE_mesh_runtime_reset_on_copy(Mesh *mesh, const int UNUSED(flag)) runtime->looptris = blender::dna::shallow_zero_initialize(); runtime->bvh_cache = nullptr; runtime->shrinkwrap_data = nullptr; + runtime->subsurf_face_dot_tags = nullptr; runtime->vert_normals_dirty = true; runtime->poly_normals_dirty = true; @@ -254,6 +255,8 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh) mesh->runtime.subdiv_ccg = nullptr; } BKE_shrinkwrap_discard_boundary_data(mesh); + + MEM_SAFE_FREE(mesh->runtime.subsurf_face_dot_tags); } /** \} */ diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 395deeda4ad..f4703b32582 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -845,41 +845,6 @@ bool BKE_modifiers_uses_armature(Object *ob, bArmature *arm) return false; } -bool BKE_modifiers_uses_subsurf_facedots(const struct Scene *scene, Object *ob) -{ - /* Search (backward) in the modifier stack to find if we have a subsurf modifier (enabled) before - * the last modifier displayed on cage (or if the subsurf is the last). */ - VirtualModifierData virtualModifierData; - ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); - int cage_index = BKE_modifiers_get_cage_index(scene, ob, NULL, 1); - if (cage_index == -1) { - return false; - } - /* Find first modifier enabled on cage. */ - for (int i = 0; md && i < cage_index; i++) { - md = md->next; - } - /* Now from this point, search for subsurf modifier. */ - for (; md; md = md->prev) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - if (md->type == eModifierType_Subsurf) { - ModifierMode mode = eModifierMode_Realtime | eModifierMode_Editmode; - if (BKE_modifier_is_enabled(scene, md, mode)) { - return true; - } - } - else if (mti->type == eModifierTypeType_OnlyDeform) { - /* These modifiers do not reset the subdiv flag nor change the topology. - * We can still search for a subsurf modifier. */ - } - else { - /* Other modifiers may reset the subdiv facedot flag or create. */ - return false; - } - } - return false; -} - bool BKE_modifier_is_correctable_deformed(ModifierData *md) { const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index eb3f47760fc..5fd7984ea90 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1511,6 +1511,8 @@ void BKE_sculptsession_free(Object *ob) BKE_sculptsession_free_vwpaint_data(ob->sculpt); + MEM_SAFE_FREE(ss->last_paint_canvas_key); + MEM_freeN(ss); ob->sculpt = NULL; @@ -1771,6 +1773,24 @@ static void sculpt_update_object(Depsgraph *depsgraph, } } + /* + * We should rebuild the PBVH_pixels when painting canvas changes. + * + * The relevant changes are stored/encoded in the paint canvas key. + * These include the active uv map, and resolutions. + */ + if (U.experimental.use_sculpt_texture_paint && ss->pbvh) { + char *paint_canvas_key = BKE_paint_canvas_key_get(&scene->toolsettings->paint_mode, ob); + if (ss->last_paint_canvas_key == NULL || !STREQ(paint_canvas_key, ss->last_paint_canvas_key)) { + MEM_SAFE_FREE(ss->last_paint_canvas_key); + ss->last_paint_canvas_key = paint_canvas_key; + BKE_pbvh_mark_rebuild_pixels(ss->pbvh); + } + else { + MEM_freeN(paint_canvas_key); + } + } + /* We could be more precise when we have access to the active tool. */ const bool use_paint_slots = (ob->mode & OB_MODE_SCULPT) != 0; if (use_paint_slots) { @@ -1849,6 +1869,10 @@ void BKE_sculpt_color_layer_create_if_needed(struct Object *object) BKE_id_attributes_active_color_set(&orig_me->id, layer); DEG_id_tag_update(&orig_me->id, ID_RECALC_GEOMETRY_ALL_MODES); + + if (object->sculpt && object->sculpt->pbvh) { + BKE_pbvh_update_active_vcol(object->sculpt->pbvh, orig_me); + } } void BKE_sculpt_update_object_for_edit( diff --git a/source/blender/blenkernel/intern/paint_canvas.cc b/source/blender/blenkernel/intern/paint_canvas.cc index c1145164642..b72418d88c0 100644 --- a/source/blender/blenkernel/intern/paint_canvas.cc +++ b/source/blender/blenkernel/intern/paint_canvas.cc @@ -6,9 +6,12 @@ #include "DNA_scene_types.h" #include "BKE_customdata.h" +#include "BKE_image.h" #include "BKE_material.h" #include "BKE_paint.h" +#include "IMB_imbuf_types.h" + namespace blender::bke::paint::canvas { static TexPaintSlot *get_active_slot(Object *ob) { @@ -33,22 +36,35 @@ extern "C" { using namespace blender::bke::paint::canvas; -Image *BKE_paint_canvas_image_get(const struct PaintModeSettings *settings, struct Object *ob) +bool BKE_paint_canvas_image_get(PaintModeSettings *settings, + Object *ob, + Image **r_image, + ImageUser **r_image_user) { + *r_image = nullptr; + *r_image_user = nullptr; + switch (settings->canvas_source) { case PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE: - return nullptr; + break; + case PAINT_CANVAS_SOURCE_IMAGE: - return settings->canvas_image; + *r_image = settings->canvas_image; + *r_image_user = &settings->image_user; + break; + case PAINT_CANVAS_SOURCE_MATERIAL: { TexPaintSlot *slot = get_active_slot(ob); if (slot == nullptr) { break; } - return slot->ima; + + *r_image = slot->ima; + *r_image_user = slot->image_user; + break; } } - return nullptr; + return *r_image != nullptr; } int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings, @@ -87,4 +103,29 @@ int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *setti } return -1; } + +char *BKE_paint_canvas_key_get(struct PaintModeSettings *settings, struct Object *ob) +{ + std::stringstream ss; + int active_uv_map_layer_index = BKE_paint_canvas_uvmap_layer_index_get(settings, ob); + ss << "UV_MAP:" << active_uv_map_layer_index; + + Image *image; + ImageUser *image_user; + if (BKE_paint_canvas_image_get(settings, ob, &image, &image_user)) { + ImageUser tile_user = *image_user; + LISTBASE_FOREACH (ImageTile *, image_tile, &image->tiles) { + tile_user.tile = image_tile->tile_number; + ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &tile_user, nullptr); + if (!image_buffer) { + continue; + } + ss << ",TILE_" << image_tile->tile_number; + ss << "(" << image_buffer->x << "," << image_buffer->y << ")"; + BKE_image_release_ibuf(image, image_buffer, nullptr); + } + } + + return BLI_strdup(ss.str().c_str()); +} } diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index 5d307697208..e91f360ef22 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -686,6 +686,8 @@ void BKE_pbvh_free(PBVH *pbvh) if (node->bm_other_verts) { BLI_gset_free(node->bm_other_verts, NULL); } + + pbvh_pixels_free(node); } } @@ -1769,7 +1771,7 @@ BMesh *BKE_pbvh_get_bmesh(PBVH *pbvh) void BKE_pbvh_node_mark_update(PBVHNode *node) { node->flag |= PBVH_UpdateNormals | PBVH_UpdateBB | PBVH_UpdateOriginalBB | - PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw; + PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_RebuildPixels; } void BKE_pbvh_node_mark_update_mask(PBVHNode *node) @@ -1782,6 +1784,16 @@ void BKE_pbvh_node_mark_update_color(PBVHNode *node) node->flag |= PBVH_UpdateColor | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw; } +void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh) +{ + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if (node->flag & PBVH_Leaf) { + node->flag |= PBVH_RebuildPixels; + } + } +} + void BKE_pbvh_node_mark_update_visibility(PBVHNode *node) { node->flag |= PBVH_UpdateVisibility | PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h index ea1f0632f32..77bd00da50a 100644 --- a/source/blender/blenkernel/intern/pbvh_intern.h +++ b/source/blender/blenkernel/intern/pbvh_intern.h @@ -6,6 +6,10 @@ * \ingroup bke */ +#ifdef __cplusplus +extern "C" { +#endif + /* Axis-aligned bounding box */ typedef struct { float bmin[3], bmax[3]; @@ -111,6 +115,7 @@ struct PBVHNode { /* Used to store the brush color during a stroke and composite it over the original color */ PBVHColorBufferNode color_buffer; + PBVHPixelsNode pixels; }; typedef enum { @@ -260,3 +265,11 @@ bool pbvh_bmesh_node_nearest_to_ray(PBVHNode *node, bool use_original); void pbvh_bmesh_normals_update(PBVHNode **nodes, int totnode); + +/* pbvh_pixels.hh */ +void pbvh_pixels_free(PBVHNode *node); +void pbvh_pixels_free_brush_test(PBVHNode *node); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/intern/pbvh_pixels.cc b/source/blender/blenkernel/intern/pbvh_pixels.cc new file mode 100644 index 00000000000..d8dd2f4b382 --- /dev/null +++ b/source/blender/blenkernel/intern/pbvh_pixels.cc @@ -0,0 +1,393 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "BKE_customdata.h" +#include "BKE_mesh_mapping.h" +#include "BKE_pbvh.h" +#include "BKE_pbvh_pixels.hh" + +#include "DNA_image_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BLI_math.h" +#include "BLI_task.h" + +#include "BKE_image_wrappers.hh" + +#include "bmesh.h" + +#include "pbvh_intern.h" + +namespace blender::bke::pbvh::pixels { + +/** Durind debugging this check could be enabled. It will write to each image pixel that is covered + * by the pbvh. */ +constexpr bool USE_WATERTIGHT_CHECK = false; + +/** + * Calculate the delta of two neighbour uv coordinates in the given image buffer. + */ +static float2 calc_barycentric_delta(const float2 uvs[3], + const float2 start_uv, + const float2 end_uv) +{ + + float3 start_barycentric; + barycentric_weights_v2(uvs[0], uvs[1], uvs[2], start_uv, start_barycentric); + float3 end_barycentric; + barycentric_weights_v2(uvs[0], uvs[1], uvs[2], end_uv, end_barycentric); + float3 barycentric = end_barycentric - start_barycentric; + return float2(barycentric.x, barycentric.y); +} + +static float2 calc_barycentric_delta_x(const ImBuf *image_buffer, + const float2 uvs[3], + const int x, + const int y) +{ + const float2 start_uv(float(x) / image_buffer->x, float(y) / image_buffer->y); + const float2 end_uv(float(x + 1) / image_buffer->x, float(y) / image_buffer->y); + return calc_barycentric_delta(uvs, start_uv, end_uv); +} + +static void extract_barycentric_pixels(UDIMTilePixels &tile_data, + const ImBuf *image_buffer, + const int triangle_index, + const float2 uvs[3], + const int minx, + const int miny, + const int maxx, + const int maxy) +{ + for (int y = miny; y < maxy; y++) { + bool start_detected = false; + PackedPixelRow pixel_row; + pixel_row.triangle_index = triangle_index; + pixel_row.num_pixels = 0; + int x; + + for (x = minx; x < maxx; x++) { + float2 uv(float(x) / image_buffer->x, float(y) / image_buffer->y); + float3 barycentric_weights; + barycentric_weights_v2(uvs[0], uvs[1], uvs[2], uv, barycentric_weights); + + const bool is_inside = barycentric_inside_triangle_v2(barycentric_weights); + if (!start_detected && is_inside) { + start_detected = true; + pixel_row.start_image_coordinate = ushort2(x, y); + pixel_row.start_barycentric_coord = float2(barycentric_weights.x, barycentric_weights.y); + } + else if (start_detected && !is_inside) { + break; + } + } + + if (!start_detected) { + continue; + } + pixel_row.num_pixels = x - pixel_row.start_image_coordinate.x; + tile_data.pixel_rows.append(pixel_row); + } +} + +static void init_triangles(PBVH *pbvh, PBVHNode *node, NodeData *node_data, const MLoop *mloop) +{ + for (int i = 0; i < node->totprim; i++) { + const MLoopTri *lt = &pbvh->looptri[node->prim_indices[i]]; + node_data->triangles.append( + int3(mloop[lt->tri[0]].v, mloop[lt->tri[1]].v, mloop[lt->tri[2]].v)); + } +} + +struct EncodePixelsUserData { + Image *image; + ImageUser *image_user; + PBVH *pbvh; + Vector<PBVHNode *> *nodes; + MLoopUV *ldata_uv; +}; + +static void do_encode_pixels(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + EncodePixelsUserData *data = static_cast<EncodePixelsUserData *>(userdata); + Image *image = data->image; + ImageUser image_user = *data->image_user; + PBVH *pbvh = data->pbvh; + PBVHNode *node = (*data->nodes)[n]; + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + LISTBASE_FOREACH (ImageTile *, tile, &data->image->tiles) { + image::ImageTileWrapper image_tile(tile); + image_user.tile = image_tile.get_tile_number(); + ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user, nullptr); + if (image_buffer == nullptr) { + continue; + } + + float2 tile_offset = float2(image_tile.get_tile_offset()); + UDIMTilePixels tile_data; + + Triangles &triangles = node_data->triangles; + for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) { + const MLoopTri *lt = &pbvh->looptri[node->prim_indices[triangle_index]]; + float2 uvs[3] = { + float2(data->ldata_uv[lt->tri[0]].uv) - tile_offset, + float2(data->ldata_uv[lt->tri[1]].uv) - tile_offset, + float2(data->ldata_uv[lt->tri[2]].uv) - tile_offset, + }; + + const float minv = clamp_f(min_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f); + const int miny = floor(minv * image_buffer->y); + const float maxv = clamp_f(max_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f); + const int maxy = min_ii(ceil(maxv * image_buffer->y), image_buffer->y); + const float minu = clamp_f(min_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f); + const int minx = floor(minu * image_buffer->x); + const float maxu = clamp_f(max_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f); + const int maxx = min_ii(ceil(maxu * image_buffer->x), image_buffer->x); + + TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index); + triangle.delta_barycentric_coord_u = calc_barycentric_delta_x(image_buffer, uvs, minx, miny); + extract_barycentric_pixels( + tile_data, image_buffer, triangle_index, uvs, minx, miny, maxx, maxy); + } + + BKE_image_release_ibuf(image, image_buffer, nullptr); + + if (tile_data.pixel_rows.is_empty()) { + continue; + } + + tile_data.tile_number = image_tile.get_tile_number(); + node_data->tiles.append(tile_data); + } +} + +static bool should_pixels_be_updated(PBVHNode *node) +{ + if ((node->flag & PBVH_Leaf) == 0) { + return false; + } + if ((node->flag & PBVH_RebuildPixels) != 0) { + return true; + } + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + if (node_data != nullptr) { + return false; + } + return true; +} + +static int64_t count_nodes_to_update(PBVH *pbvh) +{ + int64_t result = 0; + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if (should_pixels_be_updated(node)) { + result++; + } + } + return result; +} + +/** + * Find the nodes that needs to be updated. + * + * The nodes that require updated are added to the r_nodes_to_update parameter. + * Will fill in r_visited_polygons with polygons that are owned by nodes that do not require + * updates. + * + * returns if there were any nodes found (true). + */ +static bool find_nodes_to_update(PBVH *pbvh, Vector<PBVHNode *> &r_nodes_to_update) +{ + int64_t nodes_to_update_len = count_nodes_to_update(pbvh); + if (nodes_to_update_len == 0) { + return false; + } + + r_nodes_to_update.reserve(nodes_to_update_len); + + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if (!should_pixels_be_updated(node)) { + continue; + } + r_nodes_to_update.append(node); + node->flag = static_cast<PBVHNodeFlags>(node->flag | PBVH_RebuildPixels); + + if (node->pixels.node_data == nullptr) { + NodeData *node_data = MEM_new<NodeData>(__func__); + node->pixels.node_data = node_data; + } + else { + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + node_data->clear_data(); + } + } + + return true; +} + +static void apply_watertight_check(PBVH *pbvh, Image *image, ImageUser *image_user) +{ + ImageUser watertight = *image_user; + LISTBASE_FOREACH (ImageTile *, tile_data, &image->tiles) { + image::ImageTileWrapper image_tile(tile_data); + watertight.tile = image_tile.get_tile_number(); + ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &watertight, nullptr); + if (image_buffer == nullptr) { + continue; + } + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if ((node->flag & PBVH_Leaf) == 0) { + continue; + } + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + UDIMTilePixels *tile_node_data = node_data->find_tile_data(image_tile); + if (tile_node_data == nullptr) { + continue; + } + + for (PackedPixelRow &pixel_row : tile_node_data->pixel_rows) { + int pixel_offset = pixel_row.start_image_coordinate.y * image_buffer->x + + pixel_row.start_image_coordinate.x; + for (int x = 0; x < pixel_row.num_pixels; x++) { + if (image_buffer->rect_float) { + copy_v4_fl(&image_buffer->rect_float[pixel_offset * 4], 1.0); + } + if (image_buffer->rect) { + uint8_t *dest = static_cast<uint8_t *>( + static_cast<void *>(&image_buffer->rect[pixel_offset])); + copy_v4_uchar(dest, 255); + } + pixel_offset += 1; + } + } + } + BKE_image_release_ibuf(image, image_buffer, nullptr); + } + BKE_image_partial_update_mark_full_update(image); +} + +static void update_pixels(PBVH *pbvh, + const struct MLoop *mloop, + struct CustomData *ldata, + struct Image *image, + struct ImageUser *image_user) +{ + Vector<PBVHNode *> nodes_to_update; + + if (!find_nodes_to_update(pbvh, nodes_to_update)) { + return; + } + + MLoopUV *ldata_uv = static_cast<MLoopUV *>(CustomData_get_layer(ldata, CD_MLOOPUV)); + if (ldata_uv == nullptr) { + return; + } + + for (PBVHNode *node : nodes_to_update) { + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + init_triangles(pbvh, node, node_data, mloop); + } + + EncodePixelsUserData user_data; + user_data.pbvh = pbvh; + user_data.image = image; + user_data.image_user = image_user; + user_data.ldata_uv = ldata_uv; + user_data.nodes = &nodes_to_update; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, nodes_to_update.size()); + BLI_task_parallel_range(0, nodes_to_update.size(), &user_data, do_encode_pixels, &settings); + if (USE_WATERTIGHT_CHECK) { + apply_watertight_check(pbvh, image, image_user); + } + + /* Clear the UpdatePixels flag. */ + for (PBVHNode *node : nodes_to_update) { + node->flag = static_cast<PBVHNodeFlags>(node->flag & ~PBVH_RebuildPixels); + } + +//#define DO_PRINT_STATISTICS +#ifdef DO_PRINT_STATISTICS + /* Print some statistics about compression ratio. */ + { + int64_t compressed_data_len = 0; + int64_t num_pixels = 0; + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if ((node->flag & PBVH_Leaf) == 0) { + continue; + } + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + compressed_data_len += node_data->triangles.mem_size(); + for (const UDIMTilePixels &tile_data : node_data->tiles) { + compressed_data_len += tile_data.encoded_pixels.size() * sizeof(PackedPixelRow); + for (const PackedPixelRow &encoded_pixels : tile_data.encoded_pixels) { + num_pixels += encoded_pixels.num_pixels; + } + } + } + printf("Encoded %lld pixels in %lld bytes (%f bytes per pixel)\n", + num_pixels, + compressed_data_len, + float(compressed_data_len) / num_pixels); + } +#endif +} + +NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node) +{ + BLI_assert(node.pixels.node_data != nullptr); + NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data); + return *node_data; +} + +void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user) +{ + BLI_assert(node.pixels.node_data != nullptr); + NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data); + if (node_data->flags.dirty) { + ImageUser local_image_user = image_user; + LISTBASE_FOREACH (ImageTile *, tile, &image.tiles) { + image::ImageTileWrapper image_tile(tile); + local_image_user.tile = image_tile.get_tile_number(); + ImBuf *image_buffer = BKE_image_acquire_ibuf(&image, &local_image_user, nullptr); + if (image_buffer == nullptr) { + continue; + } + + node_data->mark_region(image, image_tile, *image_buffer); + BKE_image_release_ibuf(&image, image_buffer, nullptr); + } + node_data->flags.dirty = false; + } +} + +} // namespace blender::bke::pbvh::pixels + +extern "C" { +using namespace blender::bke::pbvh::pixels; + +void BKE_pbvh_build_pixels(PBVH *pbvh, + const struct MLoop *mloop, + struct CustomData *ldata, + struct Image *image, + struct ImageUser *image_user) +{ + update_pixels(pbvh, mloop, ldata, image, image_user); +} + +void pbvh_pixels_free(PBVHNode *node) +{ + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + MEM_delete(node_data); + node->pixels.node_data = nullptr; +} +} diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c index 38239e7ddd6..83e4336e3b1 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_mesh.c @@ -15,6 +15,7 @@ #include "DNA_meshdata_types.h" #include "BLI_alloca.h" +#include "BLI_bitmap.h" #include "BLI_math_vector.h" #include "BKE_customdata.h" @@ -464,6 +465,9 @@ static bool subdiv_mesh_topology_info(const SubdivForeachContext *foreach_contex subdiv_context->coarse_mesh, num_vertices, num_edges, 0, num_loops, num_polygons, mask); subdiv_mesh_ctx_cache_custom_data_layers(subdiv_context); subdiv_mesh_prepare_accumulator(subdiv_context, num_vertices); + MEM_SAFE_FREE(subdiv_context->subdiv_mesh->runtime.subsurf_face_dot_tags); + subdiv_context->subdiv_mesh->runtime.subsurf_face_dot_tags = BLI_BITMAP_NEW(num_vertices, + __func__); return true; } @@ -527,7 +531,7 @@ static void evaluate_vertex_and_apply_displacement_copy(const SubdivMeshContext /* Apply displacement. */ add_v3_v3(subdiv_vert->co, D); /* Remove facedot flag. This can happen if there is more than one subsurf modifier. */ - subdiv_vert->flag &= ~ME_VERT_FACEDOT; + BLI_BITMAP_DISABLE(ctx->subdiv_mesh->runtime.subsurf_face_dot_tags, subdiv_vertex_index); } static void evaluate_vertex_and_apply_displacement_interpolate( @@ -687,12 +691,13 @@ static bool subdiv_mesh_is_center_vertex(const MPoly *coarse_poly, const float u } static void subdiv_mesh_tag_center_vertex(const MPoly *coarse_poly, - MVert *subdiv_vert, + const int subdiv_vertex_index, const float u, - const float v) + const float v, + Mesh *subdiv_mesh) { if (subdiv_mesh_is_center_vertex(coarse_poly, u, v)) { - subdiv_vert->flag |= ME_VERT_FACEDOT; + BLI_BITMAP_ENABLE(subdiv_mesh->runtime.subsurf_face_dot_tags, subdiv_vertex_index); } } @@ -717,7 +722,7 @@ static void subdiv_mesh_vertex_inner(const SubdivForeachContext *foreach_context subdiv_mesh_ensure_vertex_interpolation(ctx, tls, coarse_poly, coarse_corner); subdiv_vertex_data_interpolate(ctx, subdiv_vert, &tls->vertex_interpolation, u, v); BKE_subdiv_eval_final_point(subdiv, ptex_face_index, u, v, subdiv_vert->co); - subdiv_mesh_tag_center_vertex(coarse_poly, subdiv_vert, u, v); + subdiv_mesh_tag_center_vertex(coarse_poly, subdiv_vertex_index, u, v, subdiv_mesh); } /** \} */ diff --git a/source/blender/blenkernel/intern/tracking_stabilize.c b/source/blender/blenkernel/intern/tracking_stabilize.c index 882f7b87597..d86a0c10f01 100644 --- a/source/blender/blenkernel/intern/tracking_stabilize.c +++ b/source/blender/blenkernel/intern/tracking_stabilize.c @@ -198,7 +198,7 @@ static void use_values_from_fcurves(StabContext *ctx, bool toggle) } /* Prepare per call private working area. - * Used for access to possibly animated values: retrieve available F-curves. + * Used for access to possibly animated values: retrieve available F-Curves. */ static StabContext *init_stabilization_working_context(MovieClip *clip) { diff --git a/source/blender/blenkernel/nla_private.h b/source/blender/blenkernel/nla_private.h index cef9e543a59..41d1eef733c 100644 --- a/source/blender/blenkernel/nla_private.h +++ b/source/blender/blenkernel/nla_private.h @@ -258,7 +258,7 @@ void nlasnapshot_blend_strip(PointerRNA *ptr, void nlasnapshot_blend_strip_get_inverted_lower_snapshot( PointerRNA *ptr, - NlaEvalData *eval_data, + NlaEvalData *channels, ListBase *modifiers, NlaEvalStrip *nes, NlaEvalSnapshot *snapshot, diff --git a/source/blender/blenlib/BLI_math_rotation.hh b/source/blender/blenlib/BLI_math_rotation.hh new file mode 100644 index 00000000000..e8b746b34df --- /dev/null +++ b/source/blender/blenlib/BLI_math_rotation.hh @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + */ + +#include "BLI_math_vec_types.hh" + +namespace blender::math { + +/** + * Rotate the unit-length \a direction around the unit-length \a axis by the \a angle. + */ +float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, float angle); + +} // namespace blender::math diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh index da9ab9c313e..acf47f67168 100644 --- a/source/blender/blenlib/BLI_vector.hh +++ b/source/blender/blenlib/BLI_vector.hh @@ -387,6 +387,16 @@ class Vector { } /** + * Reset the size of the vector so that it contains new_size elements. + * All existing elements are destructed, and not copied if the data must be reallocated. + */ + void reinitialize(const int64_t new_size) + { + this->clear(); + this->resize(new_size); + } + + /** * Afterwards the vector has 0 elements, but will still have * memory to be refilled again. */ diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 99e07264276..e8a3851e082 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -94,6 +94,7 @@ set(SRC intern/math_interp.c intern/math_matrix.c intern/math_rotation.c + intern/math_rotation.cc intern/math_solvers.c intern/math_statistics.c intern/math_time.c @@ -251,6 +252,7 @@ set(SRC BLI_math_matrix.h BLI_math_mpq.hh BLI_math_rotation.h + BLI_math_rotation.hh BLI_math_solvers.h BLI_math_statistics.h BLI_math_time.h diff --git a/source/blender/blenlib/intern/math_rotation.cc b/source/blender/blenlib/intern/math_rotation.cc new file mode 100644 index 00000000000..74300d55954 --- /dev/null +++ b/source/blender/blenlib/intern/math_rotation.cc @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bli + */ + +#include "BLI_math_base.h" +#include "BLI_math_rotation.hh" +#include "BLI_math_vector.h" +#include "BLI_math_vector.hh" + +namespace blender::math { + +float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, const float angle) +{ + BLI_ASSERT_UNIT_V3(direction); + BLI_ASSERT_UNIT_V3(axis); + + const float3 axis_scaled = axis * math::dot(direction, axis); + const float3 diff = direction - axis_scaled; + const float3 cross = math::cross(axis, diff); + + return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); +} + +} // namespace blender::math diff --git a/source/blender/blenlib/tests/BLI_math_rotation_test.cc b/source/blender/blenlib/tests/BLI_math_rotation_test.cc index a10e441cfbe..a283118bea2 100644 --- a/source/blender/blenlib/tests/BLI_math_rotation_test.cc +++ b/source/blender/blenlib/tests/BLI_math_rotation_test.cc @@ -4,6 +4,8 @@ #include "BLI_math_base.h" #include "BLI_math_rotation.h" +#include "BLI_math_rotation.hh" +#include "BLI_math_vector.hh" #include <cmath> @@ -147,3 +149,23 @@ TEST(math_rotation, quat_split_swing_and_twist_negative) EXPECT_V4_NEAR(swing, expected_swing, FLT_EPSILON); EXPECT_V4_NEAR(twist, expected_twist, FLT_EPSILON); } + +namespace blender::math::tests { + +TEST(math_rotation, RotateDirectionAroundAxis) +{ + const float3 a = rotate_direction_around_axis({1, 0, 0}, {0, 0, 1}, M_PI_2); + EXPECT_NEAR(a.x, 0.0f, FLT_EPSILON); + EXPECT_NEAR(a.y, 1.0f, FLT_EPSILON); + EXPECT_NEAR(a.z, 0.0f, FLT_EPSILON); + const float3 b = rotate_direction_around_axis({1, 0, 0}, {0, 0, 1}, M_PI); + EXPECT_NEAR(b.x, -1.0f, FLT_EPSILON); + EXPECT_NEAR(b.y, 0.0f, FLT_EPSILON); + EXPECT_NEAR(b.z, 0.0f, FLT_EPSILON); + const float3 c = rotate_direction_around_axis({0, 0, 1}, {0, 0, 1}, 0.0f); + EXPECT_NEAR(c.x, 0.0f, FLT_EPSILON); + EXPECT_NEAR(c.y, 0.0f, FLT_EPSILON); + EXPECT_NEAR(c.z, 1.0f, FLT_EPSILON); +} + +} // namespace blender::math::tests diff --git a/source/blender/draw/engines/eevee/eevee_shaders_extra.cc b/source/blender/draw/engines/eevee/eevee_shaders_extra.cc index 1d3e07217b5..bb1a0b0abe4 100644 --- a/source/blender/draw/engines/eevee/eevee_shaders_extra.cc +++ b/source/blender/draw/engines/eevee/eevee_shaders_extra.cc @@ -92,7 +92,7 @@ void eevee_shader_material_create_info_amend(GPUMaterial *gpumat, const StageInterfaceInfo &iface = *info.vertex_out_interfaces_.first(); /* Globals the attrib_load() can write to when it is in the fragment shader. */ attr_load << "struct " << iface.name << " {\n"; - for (auto &inout : iface.inouts) { + for (const auto &inout : iface.inouts) { attr_load << " " << inout.type << " " << inout.name << ";\n"; } attr_load << "};\n"; diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c index e6f34d3dd0d..c4fa60ef51d 100644 --- a/source/blender/draw/intern/draw_cache_impl_mesh.c +++ b/source/blender/draw/intern/draw_cache_impl_mesh.c @@ -2121,8 +2121,7 @@ void DRW_mesh_batch_cache_create_requested(struct TaskGraph *task_graph, MDEPS_ASSERT_MAP_INDEX(TRIS_PER_MAT_INDEX); - /* Meh loose Scene const correctness here. */ - const bool use_subsurf_fdots = scene ? BKE_modifiers_uses_subsurf_facedots(scene, ob) : false; + const bool use_subsurf_fdots = me->runtime.subsurf_face_dot_tags != NULL; if (do_uvcage) { mesh_buffer_cache_create_requested(task_graph, diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index ec3a5b3a7b1..88d05fcd928 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -1286,6 +1286,10 @@ void DRW_notify_view_update(const DRWUpdateContext *update_ctx) const bool gpencil_engine_needed = drw_gpencil_engine_needed(depsgraph, v3d); + if (G.is_rendering) { + return; + } + /* XXX Really nasty locking. But else this could * be executed by the material previews thread * while rendering a viewport. */ diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc index 27149a80f9b..43aa52f08c8 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc @@ -5,9 +5,7 @@ * \ingroup draw */ -#include "BLI_vector.hh" - -#include "MEM_guardedalloc.h" +#include "BLI_bitmap.h" #include "extract_mesh.h" @@ -538,7 +536,8 @@ static void extract_edituv_fdots_iter_poly_mesh(const MeshRenderData *mr, { MeshExtract_EditUvElem_Data *data = static_cast<MeshExtract_EditUvElem_Data *>(_data); if (mr->use_subsurf_fdots) { - /* Check #ME_VERT_FACEDOT. */ + const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags; + const MLoop *mloop = mr->mloop; const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { @@ -546,8 +545,7 @@ static void extract_edituv_fdots_iter_poly_mesh(const MeshRenderData *mr, const bool real_fdot = (mr->extract_type == MR_EXTRACT_MAPPED && mr->p_origindex && mr->p_origindex[mp_index] != ORIGINDEX_NONE); - const bool subd_fdot = (!mr->use_subsurf_fdots || - (mr->mvert[ml->v].flag & ME_VERT_FACEDOT) != 0); + const bool subd_fdot = BLI_BITMAP_TEST(facedot_tags, ml->v); edituv_facedot_add(data, ((mp->flag & ME_HIDE) != 0) || !real_fdot || !subd_fdot, (mp->flag & ME_FACE_SEL) != 0, diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc index 8db1660b9d0..4bf732caf0a 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc @@ -5,9 +5,7 @@ * \ingroup draw */ -#include "BLI_vector.hh" - -#include "MEM_guardedalloc.h" +#include "BLI_bitmap.h" #include "extract_mesh.h" @@ -46,13 +44,13 @@ static void extract_fdots_iter_poly_mesh(const MeshRenderData *mr, { GPUIndexBufBuilder *elb = static_cast<GPUIndexBufBuilder *>(_userdata); if (mr->use_subsurf_fdots) { - /* Check #ME_VERT_FACEDOT. */ + const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags; + const MLoop *mloop = mr->mloop; const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { const MLoop *ml = &mloop[ml_index]; - const MVert *mv = &mr->mvert[ml->v]; - if ((mv->flag & ME_VERT_FACEDOT) && !(mr->use_hide && (mp->flag & ME_HIDE))) { + if (BLI_BITMAP_TEST(facedot_tags, ml->v) && !(mr->use_hide && (mp->flag & ME_HIDE))) { GPU_indexbuf_set_point_vert(elb, mp_index, mp_index); return; } diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc index 0a34cfa012d..c2b4d389b7c 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc @@ -5,6 +5,8 @@ * \ingroup draw */ +#include "BLI_bitmap.h" + #include "extract_mesh.h" #include "draw_subdivision.h" @@ -75,14 +77,14 @@ static void extract_fdots_pos_iter_poly_mesh(const MeshRenderData *mr, const MVert *mvert = mr->mvert; const MLoop *mloop = mr->mloop; + const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags; const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { const MLoop *ml = &mloop[ml_index]; if (mr->use_subsurf_fdots) { - const MVert *mv = &mr->mvert[ml->v]; - if (mv->flag & ME_VERT_FACEDOT) { - copy_v3_v3(center[mp_index], mv->co); + if (BLI_BITMAP_TEST(facedot_tags, ml->v)) { + copy_v3_v3(center[mp_index], mvert[ml->v].co); break; } } diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc index 7a3c108b75a..26f0b07f676 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc @@ -5,6 +5,8 @@ * \ingroup draw */ +#include "BLI_bitmap.h" + #include "extract_mesh.h" namespace blender::draw { @@ -72,13 +74,14 @@ static void extract_fdots_uv_iter_poly_mesh(const MeshRenderData *mr, void *_data) { MeshExtract_FdotUV_Data *data = static_cast<MeshExtract_FdotUV_Data *>(_data); + const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags; + const MLoop *mloop = mr->mloop; const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { const MLoop *ml = &mloop[ml_index]; if (mr->use_subsurf_fdots) { - const MVert *mv = &mr->mvert[ml->v]; - if (mv->flag & ME_VERT_FACEDOT) { + if (BLI_BITMAP_TEST(facedot_tags, ml->v)) { copy_v2_v2(data->vbo_data[mp_index], data->uv_data[ml_index].uv); } } diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c index eb7f8e8ad83..9722de0ce3c 100644 --- a/source/blender/editors/animation/anim_channels_defines.c +++ b/source/blender/editors/animation/anim_channels_defines.c @@ -5187,7 +5187,7 @@ void ANIM_channel_draw_widgets(const bContext *C, } /* Visibility toggle. */ if (acf->has_setting(ac, ale, ACHANNEL_SETTING_VISIBLE)) { - /* For F-curves, add the extra space for the color bands. */ + /* For F-Curves, add the extra space for the color bands. */ if (ELEM(ale->type, ANIMTYPE_FCURVE, ANIMTYPE_NLACURVE)) { offset += GRAPH_ICON_VISIBILITY_OFFSET; } diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c index d42efcd81e5..5b742ddf272 100644 --- a/source/blender/editors/animation/keyframing.c +++ b/source/blender/editors/animation/keyframing.c @@ -1206,8 +1206,13 @@ static float *get_keyframe_values(ReportList *reports, anim_eval_context, r_force_all, *r_successful_remaps); - get_keyframe_values_create_reports( - reports, ptr, prop, index, *r_count, *r_force_all, *r_successful_remaps); + get_keyframe_values_create_reports(reports, + ptr, + prop, + index, + *r_count, + r_force_all ? *r_force_all : false, + *r_successful_remaps); return values; } diff --git a/source/blender/editors/include/ED_armature.h b/source/blender/editors/include/ED_armature.h index 01885911ac4..d969277fef5 100644 --- a/source/blender/editors/include/ED_armature.h +++ b/source/blender/editors/include/ED_armature.h @@ -363,7 +363,7 @@ void ED_mesh_deform_bind_callback(struct Object *object, struct MeshDeformModifierData *mmd, struct Mesh *cagemesh, float *vertexcos, - int totvert, + int verts_num, float cagemat[4][4]); /* Pose backups, pose_backup.c */ diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h index 3100c135db4..54d67c50d5c 100644 --- a/source/blender/editors/include/ED_sculpt.h +++ b/source/blender/editors/include/ED_sculpt.h @@ -17,6 +17,9 @@ struct UndoType; struct ViewContext; struct bContext; struct rcti; +struct wmMsgSubscribeKey; +struct wmMsgSubscribeValue; +struct wmRegionMessageSubscribeParams; /* sculpt.c */ diff --git a/source/blender/editors/io/io_obj.c b/source/blender/editors/io/io_obj.c index 97f1e08fdff..beed4abd52b 100644 --- a/source/blender/editors/io/io_obj.c +++ b/source/blender/editors/io/io_obj.c @@ -378,6 +378,7 @@ static int wm_obj_import_exec(bContext *C, wmOperator *op) import_params.clamp_size = RNA_float_get(op->ptr, "clamp_size"); import_params.forward_axis = RNA_enum_get(op->ptr, "forward_axis"); import_params.up_axis = RNA_enum_get(op->ptr, "up_axis"); + import_params.validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes"); OBJ_import(C, &import_params); @@ -388,8 +389,8 @@ static void ui_obj_import_settings(uiLayout *layout, PointerRNA *imfptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); - uiLayout *box = uiLayoutBox(layout); + uiLayout *box = uiLayoutBox(layout); uiItemL(box, IFACE_("Transform"), ICON_OBJECT_DATA); uiLayout *col = uiLayoutColumn(box, false); uiLayout *sub = uiLayoutColumn(col, false); @@ -397,6 +398,11 @@ static void ui_obj_import_settings(uiLayout *layout, PointerRNA *imfptr) sub = uiLayoutColumn(col, false); uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Axis Forward"), ICON_NONE); uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up"), ICON_NONE); + + box = uiLayoutBox(layout); + uiItemL(box, IFACE_("Options"), ICON_EXPORT); + col = uiLayoutColumn(box, false); + uiItemR(col, imfptr, "validate_meshes", 0, NULL, ICON_NONE); } static void wm_obj_import_draw(bContext *C, wmOperator *op) @@ -442,4 +448,9 @@ void WM_OT_obj_import(struct wmOperatorType *ot) "Forward Axis", ""); RNA_def_enum(ot->srna, "up_axis", io_obj_transform_axis_up, OBJ_AXIS_Y_UP, "Up Axis", ""); + RNA_def_boolean(ot->srna, + "validate_meshes", + false, + "Validate Meshes", + "Check imported mesh objects for invalid data (slow)"); } diff --git a/source/blender/editors/io/io_usd.c b/source/blender/editors/io/io_usd.c index bff1bdd0ed8..e0616a0cec3 100644 --- a/source/blender/editors/io/io_usd.c +++ b/source/blender/editors/io/io_usd.c @@ -215,47 +215,43 @@ void WM_OT_usd_export(struct wmOperatorType *ot) "selected_objects_only", false, "Selection Only", - "Only selected objects are exported. Unselected parents of selected objects are " + "Only export selected objects. Unselected parents of selected objects are " "exported as empty transform"); RNA_def_boolean(ot->srna, "visible_objects_only", true, "Visible Only", - "Only visible objects are exported. Invisible parents of exported objects are " - "exported as empty transform"); + "Only export visible objects. Invisible parents of exported objects are " + "exported as empty transforms"); - RNA_def_boolean(ot->srna, - "export_animation", - false, - "Animation", - "When checked, the render frame range is exported. When false, only the current " - "frame is exported"); RNA_def_boolean( - ot->srna, "export_hair", false, "Hair", "When checked, hair is exported as USD curves"); - RNA_def_boolean(ot->srna, - "export_uvmaps", - true, - "UV Maps", - "When checked, all UV maps of exported meshes are included in the export"); + ot->srna, + "export_animation", + false, + "Animation", + "Export all frames in the render frame range, rather than only the current frame"); + RNA_def_boolean( + ot->srna, "export_hair", false, "Hair", "Export hair particle systems as USD curves"); + RNA_def_boolean( + ot->srna, "export_uvmaps", true, "UV Maps", "Include all mesh UV maps in the export"); RNA_def_boolean(ot->srna, "export_normals", true, "Normals", - "When checked, normals of exported meshes are included in the export"); + "Include normals of exported meshes in the export"); RNA_def_boolean(ot->srna, "export_materials", true, "Materials", - "When checked, the viewport settings of materials are exported as USD preview " - "materials, and material assignments are exported as geometry subsets"); + "Export viewport settings of materials as USD preview materials, and export " + "material assignments as geometry subsets"); RNA_def_boolean(ot->srna, "use_instancing", false, "Instancing", - "When checked, instanced objects are exported as references in USD. " - "When unchecked, instanced objects are exported as real objects"); + "Export instanced objects as references in USD rather than real objects"); RNA_def_enum(ot->srna, "evaluation_mode", diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 02feccc211a..97376a495c1 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -43,7 +43,7 @@ set(SRC object_gpencil_modifier.c object_hook.c object_modes.c - object_modifier.c + object_modifier.cc object_ops.c object_random.c object_relations.c diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index eae08e89104..fb61200be9d 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -201,6 +201,7 @@ 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); +void OBJECT_OT_geometry_node_tree_copy_assign(struct wmOperatorType *ot); /* object_gpencil_modifiers.c */ diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.cc index 6a230669056..f7543c97903 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.cc @@ -5,9 +5,9 @@ * \ingroup edobj */ -#include <math.h> -#include <stdio.h> -#include <stdlib.h> +#include <cmath> +#include <cstdio> +#include <cstdlib> #include "MEM_guardedalloc.h" @@ -142,19 +142,19 @@ static void object_force_modifier_bind_simple_options(Depsgraph *depsgraph, ModifierData *ED_object_modifier_add( ReportList *reports, Main *bmain, Scene *scene, Object *ob, const char *name, int type) { - ModifierData *md = NULL, *new_md = NULL; - const ModifierTypeInfo *mti = BKE_modifier_get_info(type); + ModifierData *md = nullptr, *new_md = nullptr; + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)type); /* Check compatibility of modifier [T25291, T50373]. */ if (!BKE_object_support_modifier_type_check(ob, type)) { BKE_reportf(reports, RPT_WARNING, "Modifiers cannot be added to object '%s'", ob->id.name + 2); - return NULL; + return nullptr; } if (mti->flags & eModifierTypeFlag_Single) { - if (BKE_modifiers_findby_type(ob, type)) { + if (BKE_modifiers_findby_type(ob, (ModifierType)type)) { BKE_report(reports, RPT_WARNING, "Only one modifier of this type is allowed"); - return NULL; + return nullptr; } } @@ -169,9 +169,10 @@ ModifierData *ED_object_modifier_add( new_md = BKE_modifier_new(type); if (mti->flags & eModifierTypeFlag_RequiresOriginalData) { - md = ob->modifiers.first; + md = static_cast<ModifierData *>(ob->modifiers.first); - while (md && BKE_modifier_get_info(md->type)->type == eModifierTypeType_OnlyDeform) { + while (md && + BKE_modifier_get_info((ModifierType)md->type)->type == eModifierTypeType_OnlyDeform) { md = md->next; } @@ -217,7 +218,7 @@ ModifierData *ED_object_modifier_add( } else if (type == eModifierType_Skin) { /* ensure skin-node customdata exists */ - BKE_mesh_ensure_skin_customdata(ob->data); + BKE_mesh_ensure_skin_customdata(static_cast<Mesh *>(ob->data)); } } @@ -248,7 +249,7 @@ bool ED_object_iter_other(Main *bmain, bool (*callback)(Object *ob, void *callback_data), void *callback_data) { - ID *ob_data_id = orig_ob->data; + ID *ob_data_id = static_cast<ID *>(orig_ob->data); int users = ob_data_id->us; if (ob_data_id->flag & LIB_FAKEUSER) { @@ -260,7 +261,8 @@ bool ED_object_iter_other(Main *bmain, Object *ob; int totfound = include_orig ? 0 : 1; - for (ob = bmain->objects.first; ob && totfound < users; ob = ob->id.next) { + for (ob = static_cast<Object *>(bmain->objects.first); ob && totfound < users; + ob = reinterpret_cast<Object *>(ob->id.next)) { if (((ob != orig_ob) || include_orig) && (ob->data == orig_ob->data)) { if (callback(ob, callback_data)) { return true; @@ -281,7 +283,7 @@ static bool object_has_modifier_cb(Object *ob, void *data) { ModifierType type = *((ModifierType *)data); - return object_has_modifier(ob, NULL, type); + return object_has_modifier(ob, nullptr, type); } bool ED_object_multires_update_totlevels_cb(Object *ob, void *totlevel_v) @@ -342,7 +344,7 @@ static bool object_modifier_remove( else if (md->type == eModifierType_Multires) { /* Delete MDisps layer if not used by another multires modifier */ if (object_modifier_safe_to_delete(bmain, ob, md, eModifierType_Multires)) { - multires_customdata_delete(ob->data); + multires_customdata_delete(static_cast<Mesh *>(ob->data)); } } else if (md->type == eModifierType_Skin) { @@ -384,7 +386,7 @@ bool ED_object_modifier_remove( void ED_object_modifier_clear(Main *bmain, Scene *scene, Object *ob) { - ModifierData *md = ob->modifiers.first; + ModifierData *md = static_cast<ModifierData *>(ob->modifiers.first); bool sort_depsgraph = false; if (!md) { @@ -406,10 +408,10 @@ void ED_object_modifier_clear(Main *bmain, Scene *scene, Object *ob) bool ED_object_modifier_move_up(ReportList *reports, Object *ob, ModifierData *md) { if (md->prev) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (mti->type != eModifierTypeType_OnlyDeform) { - const ModifierTypeInfo *nmti = BKE_modifier_get_info(md->prev->type); + const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md->prev->type); if (nmti->flags & eModifierTypeFlag_RequiresOriginalData) { BKE_report(reports, RPT_WARNING, "Cannot move above a modifier requiring original data"); @@ -430,10 +432,10 @@ bool ED_object_modifier_move_up(ReportList *reports, Object *ob, ModifierData *m bool ED_object_modifier_move_down(ReportList *reports, Object *ob, ModifierData *md) { if (md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (mti->flags & eModifierTypeFlag_RequiresOriginalData) { - const ModifierTypeInfo *nmti = BKE_modifier_get_info(md->next->type); + const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md->next->type); if (nmti->type != eModifierTypeType_OnlyDeform) { BKE_report(reports, RPT_WARNING, "Cannot move beyond a non-deforming modifier"); @@ -456,7 +458,7 @@ bool ED_object_modifier_move_to_index(ReportList *reports, ModifierData *md, const int index) { - BLI_assert(md != NULL); + BLI_assert(md != nullptr); BLI_assert(index >= 0); if (index >= BLI_listbase_count(&ob->modifiers)) { BKE_report(reports, RPT_WARNING, "Cannot move modifier beyond the end of the stack"); @@ -534,7 +536,7 @@ bool ED_object_modifier_convert(ReportList *UNUSED(reports), return false; } ParticleSystem *psys_eval = psys_eval_get(depsgraph, ob, psys_orig); - if (psys_eval->pathcache == NULL) { + if (psys_eval->pathcache == nullptr) { return false; } @@ -572,15 +574,15 @@ bool ED_object_modifier_convert(ReportList *UNUSED(reports), } /* add new mesh */ - Object *obn = BKE_object_add(bmain, view_layer, OB_MESH, NULL); - Mesh *me = obn->data; + Object *obn = BKE_object_add(bmain, view_layer, OB_MESH, nullptr); + Mesh *me = static_cast<Mesh *>(obn->data); me->totvert = verts_num; me->totedge = edges_num; - me->mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, verts_num); - me->medge = CustomData_add_layer(&me->edata, CD_MEDGE, CD_CALLOC, NULL, edges_num); - me->mface = CustomData_add_layer(&me->fdata, CD_MFACE, CD_CALLOC, NULL, 0); + me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, verts_num); + me->medge = (MEdge *)CustomData_add_layer(&me->edata, CD_MEDGE, CD_CALLOC, nullptr, edges_num); + me->mface = (MFace *)CustomData_add_layer(&me->fdata, CD_MFACE, CD_CALLOC, nullptr, 0); MVert *mvert = me->mvert; MEdge *medge = me->medge; @@ -650,9 +652,9 @@ static bool modifier_apply_shape(Main *bmain, Object *ob, ModifierData *md_eval) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md_eval->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_eval->type); - if (mti->isDisabled && mti->isDisabled(scene, md_eval, 0)) { + if (mti->isDisabled && mti->isDisabled(scene, md_eval, false)) { BKE_report(reports, RPT_ERROR, "Modifier is disabled, skipping apply"); return false; } @@ -668,7 +670,7 @@ static bool modifier_apply_shape(Main *bmain, * we can look into supporting them. */ if (ob->type == OB_MESH) { - Mesh *me = ob->data; + Mesh *me = static_cast<Mesh *>(ob->data); Key *key = me->key; if (!BKE_modifier_is_same_topology(md_eval) || mti->type == eModifierTypeType_NonGeometrical) { @@ -683,19 +685,19 @@ static bool modifier_apply_shape(Main *bmain, return false; } - if (key == NULL) { + if (key == nullptr) { key = me->key = BKE_key_add(bmain, (ID *)me); key->type = KEY_RELATIVE; /* if that was the first key block added, then it was the basis. * Initialize it with the mesh, and add another for the modifier */ - KeyBlock *kb = BKE_keyblock_add(key, NULL); + KeyBlock *kb = BKE_keyblock_add(key, nullptr); BKE_keyblock_convert_from_mesh(me, key, kb); } KeyBlock *kb = BKE_keyblock_add(key, md_eval->name); BKE_mesh_nomain_to_meshkey(mesh_applied, me, kb); - BKE_id_free(NULL, mesh_applied); + BKE_id_free(nullptr, mesh_applied); } else { /* TODO: implement for curves, point clouds and volumes. */ @@ -708,15 +710,15 @@ static bool modifier_apply_shape(Main *bmain, static bool modifier_apply_obdata( ReportList *reports, Depsgraph *depsgraph, Scene *scene, Object *ob, ModifierData *md_eval) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md_eval->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_eval->type); - if (mti->isDisabled && mti->isDisabled(scene, md_eval, 0)) { + if (mti->isDisabled && mti->isDisabled(scene, md_eval, false)) { BKE_report(reports, RPT_ERROR, "Modifier is disabled, skipping apply"); return false; } if (ob->type == OB_MESH) { - Mesh *me = ob->data; + Mesh *me = static_cast<Mesh *>(ob->data); MultiresModifierData *mmd = find_multires_modifier_before(scene, md_eval); if (me->key && mti->type != eModifierTypeType_NonGeometrical) { @@ -757,9 +759,9 @@ static bool modifier_apply_obdata( } else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); - Curve *curve = ob->data; - Curve *curve_eval = (Curve *)object_eval->data; - ModifierEvalContext mectx = {depsgraph, object_eval, 0}; + Curve *curve = static_cast<Curve *>(ob->data); + Curve *curve_eval = static_cast<Curve *>(object_eval->data); + ModifierEvalContext mectx = {depsgraph, object_eval, (ModifierApplyFlag)0}; if (ELEM(mti->type, eModifierTypeType_Constructive, eModifierTypeType_Nonconstructive)) { BKE_report( @@ -773,7 +775,7 @@ static bool modifier_apply_obdata( int verts_num; float(*vertexCos)[3] = BKE_curve_nurbs_vert_coords_alloc(&curve_eval->nurb, &verts_num); - mti->deformVerts(md_eval, &mectx, NULL, vertexCos, verts_num); + mti->deformVerts(md_eval, &mectx, nullptr, vertexCos, verts_num); BKE_curve_nurbs_vert_coords_apply(&curve->nurb, vertexCos, false); MEM_freeN(vertexCos); @@ -782,8 +784,8 @@ static bool modifier_apply_obdata( } else if (ob->type == OB_LATTICE) { Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); - Lattice *lattice = ob->data; - ModifierEvalContext mectx = {depsgraph, object_eval, 0}; + Lattice *lattice = static_cast<Lattice *>(ob->data); + ModifierEvalContext mectx = {depsgraph, object_eval, (ModifierApplyFlag)0}; if (ELEM(mti->type, eModifierTypeType_Constructive, eModifierTypeType_Nonconstructive)) { BKE_report(reports, RPT_ERROR, "Constructive modifiers cannot be applied"); @@ -792,7 +794,7 @@ static bool modifier_apply_obdata( int verts_num; float(*vertexCos)[3] = BKE_lattice_vert_coords_alloc(lattice, &verts_num); - mti->deformVerts(md_eval, &mectx, NULL, vertexCos, verts_num); + mti->deformVerts(md_eval, &mectx, nullptr, vertexCos, verts_num); BKE_lattice_vert_coords_apply(lattice, vertexCos); MEM_freeN(vertexCos); @@ -918,7 +920,7 @@ static int modifier_add_exec(bContext *C, wmOperator *op) Object *ob = ED_object_active_context(C); int type = RNA_enum_get(op->ptr, "type"); - if (!ED_object_modifier_add(op->reports, bmain, scene, ob, NULL, type)) { + if (!ED_object_modifier_add(op->reports, bmain, scene, ob, nullptr, type)) { return OPERATOR_CANCELLED; } @@ -938,15 +940,15 @@ static const EnumPropertyItem *modifier_add_itemf(bContext *C, return rna_enum_object_modifier_type_items; } - EnumPropertyItem *items = NULL; + EnumPropertyItem *items = nullptr; int totitem = 0; - const EnumPropertyItem *group_item = NULL; + const EnumPropertyItem *group_item = nullptr; for (int a = 0; rna_enum_object_modifier_type_items[a].identifier; a++) { const EnumPropertyItem *md_item = &rna_enum_object_modifier_type_items[a]; if (md_item->identifier[0]) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md_item->value); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_item->value); if (mti->flags & eModifierTypeFlag_NoUserAdd) { continue; @@ -963,7 +965,7 @@ static const EnumPropertyItem *modifier_add_itemf(bContext *C, if (group_item) { RNA_enum_item_add(&items, &totitem, group_item); - group_item = NULL; + group_item = nullptr; } RNA_enum_item_add(&items, &totitem, md_item); @@ -1016,9 +1018,9 @@ bool edit_modifier_poll_generic(bContext *C, Main *bmain = CTX_data_main(C); PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", rna_type); Object *ob = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C); - ModifierData *mod = ptr.data; /* May be NULL. */ + ModifierData *mod = static_cast<ModifierData *>(ptr.data); /* May be nullptr. */ - if (mod == NULL && ob != NULL) { + if (mod == nullptr && ob != nullptr) { mod = BKE_object_active_modifier(ob); } @@ -1038,7 +1040,7 @@ bool edit_modifier_poll_generic(bContext *C, return false; } - if (!is_editmode_allowed && CTX_data_edit_object(C) != NULL) { + if (!is_editmode_allowed && CTX_data_edit_object(C) != nullptr) { CTX_wm_operator_poll_msg_set(C, "This modifier operation is not allowed from Edit mode"); return false; } @@ -1061,7 +1063,7 @@ static bool edit_modifier_liboverride_allowed_poll(bContext *C) void edit_modifier_properties(wmOperatorType *ot) { PropertyRNA *prop = RNA_def_string( - ot->srna, "modifier", NULL, MAX_NAME, "Modifier", "Name of the modifier to edit"); + ot->srna, "modifier", nullptr, MAX_NAME, "Modifier", "Name of the modifier to edit"); RNA_def_property_flag(prop, PROP_HIDDEN); } @@ -1087,8 +1089,8 @@ bool edit_modifier_invoke_properties(bContext *C, wmOperator *op) } PointerRNA ctx_ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier); - if (ctx_ptr.data != NULL) { - ModifierData *md = ctx_ptr.data; + if (ctx_ptr.data != nullptr) { + ModifierData *md = static_cast<ModifierData *>(ctx_ptr.data); RNA_string_set(op->ptr, "modifier", md->name); return true; } @@ -1112,14 +1114,14 @@ static bool edit_modifier_invoke_properties_with_hover(bContext *C, /* Note that the context pointer is *not* the active modifier, it is set in UI layouts. */ PointerRNA ctx_ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier); - if (ctx_ptr.data != NULL) { - ModifierData *md = ctx_ptr.data; + if (ctx_ptr.data != nullptr) { + ModifierData *md = static_cast<ModifierData *>(ctx_ptr.data); RNA_string_set(op->ptr, "modifier", md->name); return true; } PointerRNA *panel_ptr = UI_region_panel_custom_data_under_cursor(C, event); - if (panel_ptr == NULL || RNA_pointer_is_null(panel_ptr)) { + if (panel_ptr == nullptr || RNA_pointer_is_null(panel_ptr)) { *r_retval = OPERATOR_CANCELLED; return false; } @@ -1132,7 +1134,7 @@ static bool edit_modifier_invoke_properties_with_hover(bContext *C, return false; } - const ModifierData *md = panel_ptr->data; + const ModifierData *md = static_cast<const ModifierData *>(panel_ptr->data); RNA_string_set(op->ptr, "modifier", md->name); return true; } @@ -1145,7 +1147,7 @@ ModifierData *edit_modifier_property_get(wmOperator *op, Object *ob, int type) ModifierData *md = BKE_modifiers_findby_name(ob, modifier_name); if (md && type != 0 && md->type != type) { - md = NULL; + md = nullptr; } return md; @@ -1166,7 +1168,7 @@ static int modifier_remove_exec(bContext *C, wmOperator *op) ModifierData *md = edit_modifier_property_get(op, ob, 0); int mode_orig = ob->mode; - if (md == NULL) { + if (md == nullptr) { return OPERATOR_CANCELLED; } @@ -1184,7 +1186,7 @@ static int modifier_remove_exec(bContext *C, wmOperator *op) if (mode_orig & OB_MODE_PARTICLE_EDIT) { if ((ob->mode & OB_MODE_PARTICLE_EDIT) == 0) { if (ob == OBACT(view_layer)) { - WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, nullptr); } } } @@ -1371,14 +1373,14 @@ static bool modifier_apply_poll(bContext *C) Scene *scene = CTX_data_scene(C); PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier); - Object *ob = (ptr.owner_id != NULL) ? (Object *)ptr.owner_id : ED_object_active_context(C); - ModifierData *md = ptr.data; /* May be NULL. */ + Object *ob = (ptr.owner_id != nullptr) ? (Object *)ptr.owner_id : ED_object_active_context(C); + ModifierData *md = static_cast<ModifierData *>(ptr.data); /* May be nullptr. */ - if (ID_IS_OVERRIDE_LIBRARY(ob) || ((ob->data != NULL) && ID_IS_OVERRIDE_LIBRARY(ob->data))) { + if (ID_IS_OVERRIDE_LIBRARY(ob) || ((ob->data != nullptr) && ID_IS_OVERRIDE_LIBRARY(ob->data))) { CTX_wm_operator_poll_msg_set(C, "Modifiers cannot be applied on override data"); return false; } - if (md != NULL) { + if (md != nullptr) { if ((ob->mode & OB_MODE_SCULPT) && (find_multires_modifier_before(scene, md)) && (BKE_modifier_is_same_topology(md) == false)) { CTX_wm_operator_poll_msg_set( @@ -1399,14 +1401,14 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo const bool do_report = RNA_boolean_get(op->ptr, "report"); const bool do_single_user = RNA_boolean_get(op->ptr, "single_user"); - if (md == NULL) { + if (md == nullptr) { return OPERATOR_CANCELLED; } if (do_single_user && ID_REAL_USERS(ob->data) > 1) { ED_object_single_obdata_user(bmain, scene, ob); BKE_main_id_newptr_and_tag_clear(bmain); - WM_event_add_notifier(C, NC_WINDOW, NULL); + WM_event_add_notifier(C, NC_WINDOW, nullptr); DEG_relations_tag_update(bmain); } @@ -1447,9 +1449,9 @@ static int modifier_apply_invoke(bContext *C, wmOperator *op, const wmEvent *eve int retval; if (edit_modifier_invoke_properties_with_hover(C, op, event, &retval)) { PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier); - Object *ob = (ptr.owner_id != NULL) ? (Object *)ptr.owner_id : ED_object_active_context(C); + Object *ob = (ptr.owner_id != nullptr) ? (Object *)ptr.owner_id : ED_object_active_context(C); - if ((ob->data != NULL) && ID_REAL_USERS(ob->data) > 1) { + if ((ob->data != nullptr) && ID_REAL_USERS(ob->data) > 1) { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "single_user"); if (!RNA_property_is_set(op->ptr, prop)) { RNA_property_boolean_set(op->ptr, prop, true); @@ -1485,7 +1487,7 @@ void OBJECT_OT_modifier_apply(wmOperatorType *ot) false, "Make Data Single User", "Make the object's data single user if needed"); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE)); } /** \} */ @@ -1525,7 +1527,7 @@ static char *modifier_apply_as_shapekey_get_description(struct bContext *UNUSED( return BLI_strdup(TIP_("Apply modifier as a new shapekey and keep it in the stack")); } - return NULL; + return nullptr; } void OBJECT_OT_modifier_apply_as_shapekey(wmOperatorType *ot) @@ -1703,7 +1705,7 @@ static int modifier_copy_to_selected_exec(bContext *C, wmOperator *op) } int num_copied = 0; - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { if (ob == obact) { @@ -1721,7 +1723,7 @@ static int modifier_copy_to_selected_exec(bContext *C, wmOperator *op) } if (mti->flags & eModifierTypeFlag_Single) { - if (BKE_modifiers_findby_type(ob, md->type)) { + if (BKE_modifiers_findby_type(ob, (ModifierType)md->type)) { BKE_reportf(op->reports, RPT_WARNING, "Modifier can only be added once to object '%s'", @@ -1768,12 +1770,12 @@ static bool modifier_copy_to_selected_poll(bContext *C) { PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier); Object *obact = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C); - ModifierData *md = ptr.data; + ModifierData *md = static_cast<ModifierData *>(ptr.data); /* This just mirrors the check in #BKE_object_copy_modifier, * but there is no reasoning for it there. */ if (md && ELEM(md->type, eModifierType_Hook, eModifierType_Collision)) { - CTX_wm_operator_poll_msg_set(C, "Not supported for \"Collision\" or \"Hook\" modifiers"); + CTX_wm_operator_poll_msg_set(C, R"(Not supported for "Collision" or "Hook" modifiers)"); return false; } @@ -1909,7 +1911,7 @@ static EnumPropertyItem prop_multires_subdivide_mode_type[] = { 0, "Linear", "Create a new level using linear interpolation of the sculpted displacement"}, - {0, NULL, 0, NULL, NULL}, + {0, nullptr, 0, nullptr, nullptr}, }; static int multires_subdivide_exec(bContext *C, wmOperator *op) @@ -1978,7 +1980,7 @@ void OBJECT_OT_multires_subdivide(wmOperatorType *ot) static int multires_reshape_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Object *ob = ED_object_active_context(C), *secondob = NULL; + Object *ob = ED_object_active_context(C), *secondob = nullptr; MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get( op, ob, eModifierType_Multires); @@ -2048,7 +2050,7 @@ static int multires_external_save_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Object *ob = ED_object_active_context(C); - Mesh *me = (ob) ? ob->data : op->customdata; + Mesh *me = (ob) ? static_cast<Mesh *>(ob->data) : static_cast<Mesh *>(op->customdata); char path[FILE_MAX]; const bool relative = RNA_boolean_get(op->ptr, "relative_path"); @@ -2075,7 +2077,7 @@ static int multires_external_save_exec(bContext *C, wmOperator *op) static int multires_external_save_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { Object *ob = ED_object_active_context(C); - Mesh *me = ob->data; + Mesh *me = static_cast<Mesh *>(ob->data); char path[FILE_MAX]; if (!edit_modifier_invoke_properties(C, op)) { @@ -2140,7 +2142,7 @@ void OBJECT_OT_multires_external_save(wmOperatorType *ot) static int multires_external_pack_exec(bContext *C, wmOperator *UNUSED(op)) { Object *ob = ED_object_active_context(C); - Mesh *me = ob->data; + Mesh *me = static_cast<Mesh *>(ob->data); if (!CustomData_external_test(&me->ldata, CD_MDISPS)) { return OPERATOR_CANCELLED; @@ -2334,7 +2336,7 @@ void OBJECT_OT_multires_rebuild_subdiv(wmOperatorType *ot) static void modifier_skin_customdata_delete(Object *ob) { - Mesh *me = ob->data; + Mesh *me = static_cast<Mesh *>(ob->data); BMEditMesh *em = me->edit_mesh; if (em) { @@ -2353,7 +2355,7 @@ static bool skin_poll(bContext *C) static bool skin_edit_poll(bContext *C) { Object *ob = CTX_data_edit_object(C); - return (ob != NULL && + return (ob != nullptr && edit_modifier_poll_generic(C, &RNA_SkinModifier, (1 << OB_MESH), true, false) && !ID_IS_OVERRIDE_LIBRARY(ob) && !ID_IS_OVERRIDE_LIBRARY(ob->data)); } @@ -2367,7 +2369,7 @@ static void skin_root_clear(BMVert *bm_vert, GSet *visited, const int cd_vert_sk BMVert *v2 = BM_edge_other_vert(bm_edge, bm_vert); if (BLI_gset_add(visited, v2)) { - MVertSkin *vs = BM_ELEM_CD_GET_VOID_P(v2, cd_vert_skin_offset); + MVertSkin *vs = static_cast<MVertSkin *>(BM_ELEM_CD_GET_VOID_P(v2, cd_vert_skin_offset)); /* clear vertex root flag and add to visited set */ vs->flag &= ~MVERT_SKIN_ROOT; @@ -2385,7 +2387,7 @@ static int skin_root_mark_exec(bContext *C, wmOperator *UNUSED(op)) GSet *visited = BLI_gset_ptr_new(__func__); - BKE_mesh_ensure_skin_customdata(ob->data); + BKE_mesh_ensure_skin_customdata(static_cast<Mesh *>(ob->data)); const int cd_vert_skin_offset = CustomData_get_offset(&bm->vdata, CD_MVERT_SKIN); @@ -2393,7 +2395,8 @@ static int skin_root_mark_exec(bContext *C, wmOperator *UNUSED(op)) BMIter bm_iter; BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT) && BLI_gset_add(visited, bm_vert)) { - MVertSkin *vs = BM_ELEM_CD_GET_VOID_P(bm_vert, cd_vert_skin_offset); + MVertSkin *vs = static_cast<MVertSkin *>( + BM_ELEM_CD_GET_VOID_P(bm_vert, cd_vert_skin_offset)); /* mark vertex as root and add to visited set */ vs->flag |= MVERT_SKIN_ROOT; @@ -2403,7 +2406,7 @@ static int skin_root_mark_exec(bContext *C, wmOperator *UNUSED(op)) } } - BLI_gset_free(visited, NULL); + BLI_gset_free(visited, nullptr); DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); @@ -2424,17 +2427,17 @@ void OBJECT_OT_skin_root_mark(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -typedef enum { +enum SkinLooseAction { SKIN_LOOSE_MARK, SKIN_LOOSE_CLEAR, -} SkinLooseAction; +}; static int skin_loose_mark_clear_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; - SkinLooseAction action = RNA_enum_get(op->ptr, "action"); + SkinLooseAction action = static_cast<SkinLooseAction>(RNA_enum_get(op->ptr, "action")); if (!CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) { return OPERATOR_CANCELLED; @@ -2444,7 +2447,8 @@ static int skin_loose_mark_clear_exec(bContext *C, wmOperator *op) BMIter bm_iter; BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT)) { - MVertSkin *vs = CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN); + MVertSkin *vs = static_cast<MVertSkin *>( + CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN)); switch (action) { case SKIN_LOOSE_MARK: @@ -2468,7 +2472,7 @@ void OBJECT_OT_skin_loose_mark_clear(wmOperatorType *ot) static const EnumPropertyItem action_items[] = { {SKIN_LOOSE_MARK, "MARK", 0, "Mark", "Mark selected vertices as loose"}, {SKIN_LOOSE_CLEAR, "CLEAR", 0, "Clear", "Set selected vertices as not loose"}, - {0, NULL, 0, NULL, NULL}, + {0, nullptr, 0, nullptr, nullptr}, }; ot->name = "Skin Mark/Clear Loose"; @@ -2481,7 +2485,7 @@ void OBJECT_OT_skin_loose_mark_clear(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - RNA_def_enum(ot->srna, "action", action_items, SKIN_LOOSE_MARK, "Action", NULL); + RNA_def_enum(ot->srna, "action", action_items, SKIN_LOOSE_MARK, "Action", nullptr); } static int skin_radii_equalize_exec(bContext *C, wmOperator *UNUSED(op)) @@ -2498,7 +2502,8 @@ static int skin_radii_equalize_exec(bContext *C, wmOperator *UNUSED(op)) BMIter bm_iter; BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT)) { - MVertSkin *vs = CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN); + MVertSkin *vs = static_cast<MVertSkin *>( + CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN)); float avg = (vs->radius[0] + vs->radius[1]) * 0.5f; vs->radius[0] = vs->radius[1] = avg; @@ -2548,7 +2553,7 @@ static void skin_armature_bone_create(Object *skin_ob, EditBone *bone = ED_armature_ebone_add(arm, "Bone"); bone->parent = parent_bone; - if (parent_bone != NULL) { + if (parent_bone != nullptr) { bone->flag |= BONE_CONNECTED; } @@ -2559,7 +2564,7 @@ static void skin_armature_bone_create(Object *skin_ob, /* add bDeformGroup */ bDeformGroup *dg = BKE_object_defgroup_add_name(skin_ob, bone->name); - if (dg != NULL) { + if (dg != nullptr) { ED_vgroup_vert_add(skin_ob, dg, parent_v, 1, WEIGHT_REPLACE); ED_vgroup_vert_add(skin_ob, dg, v, 1, WEIGHT_REPLACE); } @@ -2570,7 +2575,7 @@ static void skin_armature_bone_create(Object *skin_ob, static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, Object *skin_ob) { - Mesh *me = skin_ob->data; + Mesh *me = static_cast<Mesh *>(skin_ob->data); Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); Object *ob_eval = DEG_get_evaluated_object(depsgraph, skin_ob); @@ -2579,18 +2584,19 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, MVert *mvert = me_eval_deform->mvert; /* add vertex weights to original mesh */ - CustomData_add_layer(&me->vdata, CD_MDEFORMVERT, CD_CALLOC, NULL, me->totvert); + CustomData_add_layer(&me->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, me->totvert); ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); - Object *arm_ob = BKE_object_add(bmain, view_layer, OB_ARMATURE, NULL); + Object *arm_ob = BKE_object_add(bmain, view_layer, OB_ARMATURE, nullptr); BKE_object_transform_copy(arm_ob, skin_ob); - bArmature *arm = arm_ob->data; + bArmature *arm = static_cast<bArmature *>(arm_ob->data); arm->layer = 1; arm_ob->dtx |= OB_DRAW_IN_FRONT; arm->drawtype = ARM_LINE; - arm->edbo = MEM_callocN(sizeof(ListBase), "edbo armature"); + arm->edbo = MEM_cnew<ListBase>("edbo armature"); - MVertSkin *mvert_skin = CustomData_get_layer(&me->vdata, CD_MVERT_SKIN); + MVertSkin *mvert_skin = static_cast<MVertSkin *>( + CustomData_get_layer(&me->vdata, CD_MVERT_SKIN)); int *emap_mem; MeshElemMap *emap; BKE_mesh_vert_edge_map_create(&emap, &emap_mem, me->medge, me->totvert, me->totedge); @@ -2601,7 +2607,7 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, * edit-armature functions to convert back to regular bones */ for (int v = 0; v < me->totvert; v++) { if (mvert_skin[v].flag & MVERT_SKIN_ROOT) { - EditBone *bone = NULL; + EditBone *bone = nullptr; /* Unless the skin root has just one adjacent edge, create * a fake root bone (have it going off in the Y direction @@ -2637,7 +2643,7 @@ static int skin_armature_create_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *ob = CTX_data_active_object(C); - Mesh *me = ob->data; + Mesh *me = static_cast<Mesh *>(ob->data); ModifierData *skin_md; if (!CustomData_has_layer(&me->vdata, CD_MVERT_SKIN)) { @@ -2716,7 +2722,7 @@ static int correctivesmooth_bind_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - const bool is_bind = (csmd->bind_coords != NULL); + const bool is_bind = (csmd->bind_coords != nullptr); MEM_SAFE_FREE(csmd->bind_coords); MEM_SAFE_FREE(csmd->delta_cache.deltas); @@ -2785,11 +2791,11 @@ static int meshdeform_bind_exec(bContext *C, wmOperator *op) MeshDeformModifierData *mmd = (MeshDeformModifierData *)edit_modifier_property_get( op, ob, eModifierType_MeshDeform); - if (mmd == NULL) { + if (mmd == nullptr) { return OPERATOR_CANCELLED; } - if (mmd->bindcagecos != NULL) { + if (mmd->bindcagecos != nullptr) { MEM_SAFE_FREE(mmd->bindcagecos); MEM_SAFE_FREE(mmd->dyngrid); MEM_SAFE_FREE(mmd->dyninfluences); @@ -2809,7 +2815,7 @@ static int meshdeform_bind_exec(bContext *C, wmOperator *op) depsgraph, ob, &mmd->modifier); mmd_eval->bindfunc = ED_mesh_deform_bind_callback; object_force_modifier_bind_simple_options(depsgraph, ob, &mmd->modifier); - mmd_eval->bindfunc = NULL; + mmd_eval->bindfunc = nullptr; } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); @@ -2905,7 +2911,7 @@ static bool ocean_bake_poll(bContext *C) return edit_modifier_poll_generic(C, &RNA_OceanModifier, 0, true, false); } -typedef struct OceanBakeJob { +struct OceanBakeJob { /* from wmJob */ struct Object *owner; short *stop, *do_update; @@ -2914,12 +2920,12 @@ typedef struct OceanBakeJob { struct OceanCache *och; struct Ocean *ocean; struct OceanModifierData *omd; -} OceanBakeJob; +}; static void oceanbake_free(void *customdata) { - OceanBakeJob *oj = customdata; - MEM_freeN(oj); + OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata); + MEM_delete(oj); } /* called by oceanbake, only to check job 'stop' value */ @@ -2937,7 +2943,7 @@ static int oceanbake_breakjob(void *UNUSED(customdata)) /* called by oceanbake, wmJob sends notifier */ static void oceanbake_update(void *customdata, float progress, int *cancel) { - OceanBakeJob *oj = customdata; + OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata); if (oceanbake_breakjob(oj)) { *cancel = 1; @@ -2949,7 +2955,7 @@ static void oceanbake_update(void *customdata, float progress, int *cancel) static void oceanbake_startjob(void *customdata, short *stop, short *do_update, float *progress) { - OceanBakeJob *oj = customdata; + OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata); oj->stop = stop; oj->do_update = do_update; @@ -2965,11 +2971,11 @@ static void oceanbake_startjob(void *customdata, short *stop, short *do_update, static void oceanbake_endjob(void *customdata) { - OceanBakeJob *oj = customdata; + OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata); if (oj->ocean) { BKE_ocean_free(oj->ocean); - oj->ocean = NULL; + oj->ocean = nullptr; } oj->omd->oceancache = oj->och; @@ -3009,7 +3015,7 @@ static int ocean_bake_exec(bContext *C, wmOperator *op) omd->foam_fade, omd->resolution); - och->time = MEM_mallocN(och->duration * sizeof(float), "foam bake time"); + och->time = static_cast<float *>(MEM_mallocN(och->duration * sizeof(float), "foam bake time")); int cfra = scene->r.cfra; @@ -3057,7 +3063,7 @@ static int ocean_bake_exec(bContext *C, wmOperator *op) "Ocean Simulation", WM_JOB_PROGRESS, WM_JOB_TYPE_OBJECT_SIM_OCEAN); - OceanBakeJob *oj = MEM_callocN(sizeof(OceanBakeJob), "ocean bake job"); + OceanBakeJob *oj = MEM_cnew<OceanBakeJob>("ocean bake job"); oj->owner = ob; oj->ocean = ocean; oj->och = och; @@ -3065,7 +3071,7 @@ static int ocean_bake_exec(bContext *C, wmOperator *op) WM_jobs_customdata_set(wm_job, oj, oceanbake_free); WM_jobs_timer(wm_job, 0.1, NC_OBJECT | ND_MODIFIER, NC_OBJECT | ND_MODIFIER); - WM_jobs_callbacks(wm_job, oceanbake_startjob, NULL, NULL, oceanbake_endjob); + WM_jobs_callbacks(wm_job, oceanbake_startjob, nullptr, nullptr, oceanbake_endjob); WM_jobs_start(CTX_wm_manager(C), wm_job); @@ -3115,7 +3121,7 @@ static int laplaciandeform_bind_exec(bContext *C, wmOperator *op) LaplacianDeformModifierData *lmd = (LaplacianDeformModifierData *)edit_modifier_property_get( op, ob, eModifierType_LaplacianDeform); - if (lmd == NULL) { + if (lmd == nullptr) { return OPERATOR_CANCELLED; } @@ -3137,11 +3143,11 @@ static int laplaciandeform_bind_exec(bContext *C, wmOperator *op) /* This is hard to know from the modifier itself whether the evaluation is * happening for binding or not. So we copy all the required data here. */ lmd->verts_num = lmd_eval->verts_num; - if (lmd_eval->vertexco == NULL) { + if (lmd_eval->vertexco == nullptr) { MEM_SAFE_FREE(lmd->vertexco); } else { - lmd->vertexco = MEM_dupallocN(lmd_eval->vertexco); + lmd->vertexco = static_cast<float *>(MEM_dupallocN(lmd_eval->vertexco)); } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); @@ -3192,7 +3198,7 @@ static int surfacedeform_bind_exec(bContext *C, wmOperator *op) SurfaceDeformModifierData *smd = (SurfaceDeformModifierData *)edit_modifier_property_get( op, ob, eModifierType_SurfaceDeform); - if (smd == NULL) { + if (smd == nullptr) { return OPERATOR_CANCELLED; } @@ -3258,7 +3264,7 @@ static int geometry_nodes_input_attribute_toggle_exec(bContext *C, wmOperator *o 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) { + if (nmd == nullptr) { return OPERATOR_CANCELLED; } @@ -3288,8 +3294,55 @@ void OBJECT_OT_geometry_nodes_input_attribute_toggle(wmOperatorType *ot) 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", ""); + RNA_def_string(ot->srna, "prop_path", nullptr, 0, "Prop Path", ""); + RNA_def_string(ot->srna, "modifier_name", nullptr, MAX_NAME, "Modifier Name", ""); +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Copy and Assign Geometry Node Group operator + * \{ */ + +static int geometry_node_tree_copy_assign_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + Object *ob = ED_object_active_context(C); + + ModifierData *md = BKE_object_active_modifier(ob); + if (md->type != eModifierType_Nodes) { + return OPERATOR_CANCELLED; + } + + NodesModifierData *nmd = (NodesModifierData *)md; + bNodeTree *tree = nmd->node_group; + if (tree == nullptr) { + return OPERATOR_CANCELLED; + } + + bNodeTree *new_tree = (bNodeTree *)BKE_id_copy_ex( + bmain, &tree->id, nullptr, LIB_ID_COPY_ACTIONS | LIB_ID_COPY_DEFAULT); + + if (new_tree == nullptr) { + return OPERATOR_CANCELLED; + } + + nmd->node_group = new_tree; + id_us_min(&tree->id); + + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + return OPERATOR_FINISHED; +} + +void OBJECT_OT_geometry_node_tree_copy_assign(wmOperatorType *ot) +{ + ot->name = "Copy Geometry Node Group"; + ot->description = "Copy the active geometry node group and assign it to the active modifier"; + ot->idname = "OBJECT_OT_geometry_node_tree_copy_assign"; + + ot->exec = geometry_node_tree_copy_assign_exec; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index ad0d6b88123..9b21dabb4bb 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -133,6 +133,7 @@ void ED_operatortypes_object(void) 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); + WM_operatortype_append(OBJECT_OT_geometry_node_tree_copy_assign); /* grease pencil modifiers */ WM_operatortype_append(OBJECT_OT_gpencil_modifier_add); diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc index c0ec6c6678e..976fe683f95 100644 --- a/source/blender/editors/object/object_transform.cc +++ b/source/blender/editors/object/object_transform.cc @@ -597,7 +597,7 @@ static bool apply_objects_internal_can_multiuser(bContext *C) { Object *obact = CTX_data_active_object(C); - if (ELEM(NULL, obact, obact->data)) { + if (ELEM(nullptr, obact, obact->data)) { return false; } @@ -1176,7 +1176,7 @@ void OBJECT_OT_transform_apply(wmOperatorType *ot) static int object_parent_inverse_apply_exec(bContext *C, wmOperator *UNUSED(op)) { CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) { - if (ob->parent == NULL) { + if (ob->parent == nullptr) { continue; } @@ -1185,7 +1185,7 @@ static int object_parent_inverse_apply_exec(bContext *C, wmOperator *UNUSED(op)) } CTX_DATA_END; - WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL); + WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, nullptr); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index b89cbcf87fa..abdae5c44f9 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -72,6 +72,7 @@ set(SRC sculpt_multiplane_scrape.c sculpt_ops.c sculpt_paint_color.c + sculpt_paint_image.cc sculpt_pose.c sculpt_smooth.c sculpt_transform.c diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 24c5a9fce52..bbbed32e316 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -2188,7 +2188,8 @@ void SCULPT_calc_area_normal_and_center( static float brush_strength(const Sculpt *sd, const StrokeCache *cache, const float feather, - const UnifiedPaintSettings *ups) + const UnifiedPaintSettings *ups, + const PaintModeSettings *UNUSED(paint_mode_settings)) { const Scene *scene = cache->vc->scene; const Brush *brush = BKE_paint_brush((Paint *)&sd->paint); @@ -2750,6 +2751,41 @@ static void update_brush_local_mat(Sculpt *sd, Object *ob) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Texture painting + * \{ */ + +static bool sculpt_needs_pbvh_pixels(PaintModeSettings *paint_mode_settings, + const Brush *brush, + Object *ob) +{ + if (brush->sculpt_tool == SCULPT_TOOL_PAINT && U.experimental.use_sculpt_texture_paint) { + Image *image; + ImageUser *image_user; + return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user); + } + + return false; +} + +static void sculpt_pbvh_update_pixels(PaintModeSettings *paint_mode_settings, + SculptSession *ss, + Object *ob) +{ + BLI_assert(ob->type == OB_MESH); + Mesh *mesh = (Mesh *)ob->data; + + Image *image; + ImageUser *image_user; + if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) { + return; + } + + BKE_pbvh_build_pixels(ss->pbvh, mesh->mloop, &mesh->ldata, image, image_user); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Generic Brush Plane & Symmetry Utilities * \{ */ @@ -3075,7 +3111,8 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) static void sculpt_topology_update(Sculpt *sd, Object *ob, Brush *brush, - UnifiedPaintSettings *UNUSED(ups)) + UnifiedPaintSettings *UNUSED(ups), + PaintModeSettings *UNUSED(paint_mode_settings)) { SculptSession *ss = ob->sculpt; @@ -3170,7 +3207,11 @@ static void do_brush_action_task_cb(void *__restrict userdata, } } -static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups) +static void do_brush_action(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings) { SculptSession *ss = ob->sculpt; int totnode; @@ -3209,6 +3250,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); } + if (sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob)) { + sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob); + } + /* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the * vertices and uses regular coords undo. */ /* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type @@ -3399,7 +3444,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_PAINT: - SCULPT_do_paint_brush(sd, ob, nodes, totnode); + SCULPT_do_paint_brush(paint_mode_settings, sd, ob, nodes, totnode); break; case SCULPT_TOOL_SMEAR: SCULPT_do_smear_brush(sd, ob, nodes, totnode); @@ -3704,10 +3749,18 @@ void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, } } -typedef void (*BrushActionFunc)(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups); +typedef void (*BrushActionFunc)(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings); -static void do_tiled( - Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushActionFunc action) +static void do_tiled(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, + BrushActionFunc action) { SculptSession *ss = ob->sculpt; StrokeCache *cache = ss->cache; @@ -3741,7 +3794,7 @@ static void do_tiled( /* First do the "un-tiled" position to initialize the stroke for this location. */ cache->tile_pass = 0; - action(sd, ob, brush, ups); + action(sd, ob, brush, ups, paint_mode_settings); /* Now do it for all the tiles. */ copy_v3_v3_int(cur, start); @@ -3760,7 +3813,7 @@ static void do_tiled( cache->plane_offset[dim] = cur[dim] * step[dim]; cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim]; } - action(sd, ob, brush, ups); + action(sd, ob, brush, ups, paint_mode_settings); } } } @@ -3770,6 +3823,7 @@ static void do_radial_symmetry(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, BrushActionFunc action, const char symm, const int axis, @@ -3781,7 +3835,7 @@ static void do_radial_symmetry(Sculpt *sd, const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; ss->cache->radial_symmetry_pass = i; SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); - do_tiled(sd, ob, brush, ups, action); + do_tiled(sd, ob, brush, ups, paint_mode_settings, action); } } @@ -3803,7 +3857,8 @@ static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob) static void do_symmetrical_brush_actions(Sculpt *sd, Object *ob, BrushActionFunc action, - UnifiedPaintSettings *ups) + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings) { Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; @@ -3812,7 +3867,7 @@ static void do_symmetrical_brush_actions(Sculpt *sd, float feather = calc_symmetry_feather(sd, ss->cache); - cache->bstrength = brush_strength(sd, cache, feather, ups); + cache->bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings); cache->symmetry = symm; /* `symm` is a bit combination of XYZ - @@ -3825,11 +3880,11 @@ static void do_symmetrical_brush_actions(Sculpt *sd, cache->radial_symmetry_pass = 0; SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); - do_tiled(sd, ob, brush, ups, action); + do_tiled(sd, ob, brush, ups, paint_mode_settings, action); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'X', feather); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'Y', feather); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'Z', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'X', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Y', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Z', feather); } } @@ -4609,7 +4664,8 @@ static bool sculpt_needs_connectivity_info(const Sculpt *sd, SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool) || (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) || - (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR)); + (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR) || + (brush->sculpt_tool == SCULPT_TOOL_PAINT)); } void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) @@ -5057,6 +5113,15 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); } + if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) { + ED_region_tag_redraw(region); + if (update_flags == SCULPT_UPDATE_IMAGE) { + /* Early exit when only need to update the images. We don't want to tag any geometry updates + * that would rebuilt the PBVH. */ + return; + } + } + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); /* Only current viewport matters, slower update for all viewports will @@ -5136,6 +5201,16 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up } } } + + if (update_flags & SCULPT_UPDATE_IMAGE) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = area->spacedata.first; + if (sl->spacetype != SPACE_IMAGE) { + continue; + } + ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); + } + } } if (update_flags & SCULPT_UPDATE_COORDS) { @@ -5227,6 +5302,7 @@ static void sculpt_stroke_update_step(bContext *C, Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; const Brush *brush = BKE_paint_brush(&sd->paint); + ToolSettings *tool_settings = CTX_data_tool_settings(C); SCULPT_stroke_modifiers_check(C, ob, brush); sculpt_update_cache_variants(C, sd, ob, itemptr); @@ -5246,10 +5322,10 @@ static void sculpt_stroke_update_step(bContext *C, } if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { - do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups); + do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); } - do_symmetrical_brush_actions(sd, ob, do_brush_action, ups); + do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode); sculpt_combine_proxies(sd, ob); /* Hack to fix noise texture tearing mesh. */ @@ -5280,7 +5356,12 @@ static void sculpt_stroke_update_step(bContext *C, SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); } else if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE); + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + } } else { SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); @@ -5302,6 +5383,7 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + ToolSettings *tool_settings = CTX_data_tool_settings(C); /* Finished. */ if (!ss->cache) { @@ -5335,6 +5417,11 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str if (brush->sculpt_tool == SCULPT_TOOL_MASK) { SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); } + else if (brush->sculpt_tool == SCULPT_TOOL_PAINT) { + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_IMAGE); + } + } else { SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 466432a35ec..3839c0e71e4 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -11,11 +11,13 @@ #include "DNA_key_types.h" #include "DNA_listBase.h" #include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" #include "DNA_vec_types.h" #include "BKE_paint.h" #include "BKE_pbvh.h" #include "BLI_bitmap.h" +#include "BLI_compiler_attrs.h" #include "BLI_compiler_compat.h" #include "BLI_gsqueue.h" #include "BLI_threads.h" @@ -25,12 +27,13 @@ extern "C" { #endif struct AutomaskingCache; +struct Image; +struct ImageUser; struct KeyBlock; struct Object; struct SculptUndoNode; struct bContext; - -enum ePaintSymmetryFlags; +struct PaintModeSettings; /* Updates */ @@ -43,6 +46,7 @@ typedef enum SculptUpdateType { SCULPT_UPDATE_MASK = 1 << 1, SCULPT_UPDATE_VISIBILITY = 1 << 2, SCULPT_UPDATE_COLOR = 1 << 3, + SCULPT_UPDATE_IMAGE = 1 << 4, } SculptUpdateType; typedef struct SculptCursorGeometryInfo { @@ -1626,7 +1630,29 @@ void SCULPT_multiplane_scrape_preview_draw(uint gpuattr, void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); /* Paint Brush. */ -void SCULPT_do_paint_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); +void SCULPT_do_paint_brush(struct PaintModeSettings *paint_mode_settings, + Sculpt *sd, + Object *ob, + PBVHNode **nodes, + int totnode) ATTR_NONNULL(); + +/** + * @brief Get the image canvas for painting on the given object. + * + * @return #true if an image is found. The #r_image and #r_image_user fields are filled with the + * image and image user. Returns false when the image isn't found. In the later case the r_image + * and r_image_user are set to nullptr/NULL. + */ +bool SCULPT_paint_image_canvas_get(struct PaintModeSettings *paint_mode_settings, + struct Object *ob, + struct Image **r_image, + struct ImageUser **r_image_user) ATTR_NONNULL(); +void SCULPT_do_paint_brush_image(struct PaintModeSettings *paint_mode_settings, + Sculpt *sd, + Object *ob, + PBVHNode **nodes, + int totnode) ATTR_NONNULL(); +bool SCULPT_use_image_paint_brush(struct PaintModeSettings *settings, Object *ob) ATTR_NONNULL(); /* Smear Brush. */ void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.c b/source/blender/editors/sculpt_paint/sculpt_ops.c index f84852d1d0e..9581bf2071c 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.c +++ b/source/blender/editors/sculpt_paint/sculpt_ops.c @@ -1030,14 +1030,6 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven return OPERATOR_CANCELLED; } - if (!SCULPT_has_colors(ss)) { - return OPERATOR_CANCELLED; - } - - if (SCULPT_has_loop_colors(ob)) { - BKE_pbvh_ensure_node_loops(ss->pbvh); - } - SCULPT_vertex_random_access_ensure(ss); /* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move, @@ -1049,12 +1041,17 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); SCULPT_undo_push_begin(ob, "Mask by color"); + BKE_sculpt_color_layer_create_if_needed(ob); const int active_vertex = SCULPT_active_vertex_get(ss); const float threshold = RNA_float_get(op->ptr, "threshold"); const bool invert = RNA_boolean_get(op->ptr, "invert"); const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask"); + if (SCULPT_has_loop_colors(ob)) { + BKE_pbvh_ensure_node_loops(ss->pbvh); + } + if (RNA_boolean_get(op->ptr, "contiguous")) { sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask); } @@ -1080,7 +1077,7 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot) /* api callbacks */ ot->invoke = sculpt_mask_by_color_invoke; - ot->poll = SCULPT_vertex_colors_poll; + ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER; diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.c b/source/blender/editors/sculpt_paint/sculpt_paint_color.c index e7a713efa14..7a8a6e8e484 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.c @@ -240,8 +240,14 @@ static void sample_wet_paint_reduce(const void *__restrict UNUSED(userdata), add_v4_v4(join->color, swptd->color); } -void SCULPT_do_paint_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +void SCULPT_do_paint_brush( + PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { + if (SCULPT_use_image_paint_brush(paint_mode_settings, ob)) { + SCULPT_do_paint_brush_image(paint_mode_settings, sd, ob, nodes, totnode); + return; + } + Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc new file mode 100644 index 00000000000..1fc7551f545 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc @@ -0,0 +1,430 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "DNA_image_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" + +#include "ED_paint.h" +#include "ED_uvedit.h" + +#include "BLI_math.h" +#include "BLI_math_color_blend.h" +#include "BLI_task.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" + +#include "BKE_brush.h" +#include "BKE_image_wrappers.hh" +#include "BKE_material.h" +#include "BKE_pbvh.h" +#include "BKE_pbvh_pixels.hh" + +#include "bmesh.h" + +#include "NOD_shader.h" + +#include "sculpt_intern.h" + +namespace blender::ed::sculpt_paint::paint::image { + +using namespace blender::bke::pbvh::pixels; +using namespace blender::bke::image; + +struct ImageData { + Image *image = nullptr; + ImageUser *image_user = nullptr; + + ~ImageData() = default; + + static bool init_active_image(Object *ob, + ImageData *r_image_data, + PaintModeSettings *paint_mode_settings) + { + return BKE_paint_canvas_image_get( + paint_mode_settings, ob, &r_image_data->image, &r_image_data->image_user); + } +}; + +struct TexturePaintingUserData { + Object *ob; + Brush *brush; + PBVHNode **nodes; + ImageData image_data; +}; + +/** Reading and writing to image buffer with 4 float channels. */ +class ImageBufferFloat4 { + private: + int pixel_offset; + + public: + void set_image_position(ImBuf *image_buffer, ushort2 image_pixel_position) + { + pixel_offset = int(image_pixel_position.y) * image_buffer->x + int(image_pixel_position.x); + } + + void next_pixel() + { + pixel_offset += 1; + } + + float4 read_pixel(ImBuf *image_buffer) const + { + return &image_buffer->rect_float[pixel_offset * 4]; + } + + void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const + { + copy_v4_v4(&image_buffer->rect_float[pixel_offset * 4], pixel_data); + } + + const char *get_colorspace_name(ImBuf *image_buffer) + { + return IMB_colormanagement_get_float_colorspace(image_buffer); + } +}; + +/** Reading and writing to image buffer with 4 byte channels. */ +class ImageBufferByte4 { + private: + int pixel_offset; + + public: + void set_image_position(ImBuf *image_buffer, ushort2 image_pixel_position) + { + pixel_offset = int(image_pixel_position.y) * image_buffer->x + int(image_pixel_position.x); + } + + void next_pixel() + { + pixel_offset += 1; + } + + float4 read_pixel(ImBuf *image_buffer) const + { + float4 result; + rgba_uchar_to_float(result, + static_cast<const uchar *>( + static_cast<const void *>(&(image_buffer->rect[pixel_offset])))); + return result; + } + + void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const + { + rgba_float_to_uchar( + static_cast<uchar *>(static_cast<void *>(&image_buffer->rect[pixel_offset])), pixel_data); + } + + const char *get_colorspace_name(ImBuf *image_buffer) + { + return IMB_colormanagement_get_rect_colorspace(image_buffer); + } +}; + +template<typename ImageBuffer> class PaintingKernel { + ImageBuffer image_accessor; + + SculptSession *ss; + const Brush *brush; + const int thread_id; + const MVert *mvert; + + float4 brush_color; + float brush_strength; + + SculptBrushTestFn brush_test_fn; + SculptBrushTest test; + /* Pointer to the last used image buffer to detect when buffers are switched. */ + void *last_used_image_buffer_ptr = nullptr; + const char *last_used_color_space = nullptr; + + public: + explicit PaintingKernel(SculptSession *ss, + const Brush *brush, + const int thread_id, + const MVert *mvert) + : ss(ss), brush(brush), thread_id(thread_id), mvert(mvert) + { + init_brush_strength(); + init_brush_test(); + } + + bool paint(const Triangles &triangles, const PackedPixelRow &pixel_row, ImBuf *image_buffer) + { + image_accessor.set_image_position(image_buffer, pixel_row.start_image_coordinate); + const TrianglePaintInput triangle = triangles.get_paint_input(pixel_row.triangle_index); + float3 pixel_pos = get_start_pixel_pos(triangle, pixel_row); + const float3 delta_pixel_pos = get_delta_pixel_pos(triangle, pixel_row, pixel_pos); + bool pixels_painted = false; + for (int x = 0; x < pixel_row.num_pixels; x++) { + if (!brush_test_fn(&test, pixel_pos)) { + pixel_pos += delta_pixel_pos; + image_accessor.next_pixel(); + continue; + } + + float4 color = image_accessor.read_pixel(image_buffer); + const float3 normal(0.0f, 0.0f, 0.0f); + const float3 face_normal(0.0f, 0.0f, 0.0f); + const float mask = 0.0f; + const float falloff_strength = SCULPT_brush_strength_factor( + ss, brush, pixel_pos, sqrtf(test.dist), normal, face_normal, mask, 0, thread_id); + float4 paint_color = brush_color * falloff_strength * brush_strength; + float4 buffer_color; + blend_color_mix_float(buffer_color, color, paint_color); + buffer_color *= brush->alpha; + IMB_blend_color_float(color, color, buffer_color, static_cast<IMB_BlendMode>(brush->blend)); + image_accessor.write_pixel(image_buffer, color); + pixels_painted = true; + + image_accessor.next_pixel(); + pixel_pos += delta_pixel_pos; + } + return pixels_painted; + } + + void init_brush_color(ImBuf *image_buffer) + { + const char *to_colorspace = image_accessor.get_colorspace_name(image_buffer); + if (last_used_color_space == to_colorspace) { + return; + } + copy_v3_v3(brush_color, + ss->cache->invert ? BKE_brush_secondary_color_get(ss->scene, brush) : + BKE_brush_color_get(ss->scene, brush)); + /* NOTE: Brush colors are stored in sRGB. We use math color to follow other areas that + * use brush colors. From there on we use IMB_colormanagement to convert the brush color to the + * colorspace of the texture. This isn't ideal, but would need more refactoring to make sure + * that brush colors are stored in scene linear by default. */ + srgb_to_linearrgb_v3_v3(brush_color, brush_color); + brush_color[3] = 1.0f; + + const char *from_colorspace = IMB_colormanagement_role_colorspace_name_get( + COLOR_ROLE_SCENE_LINEAR); + ColormanageProcessor *cm_processor = IMB_colormanagement_colorspace_processor_new( + from_colorspace, to_colorspace); + IMB_colormanagement_processor_apply_v4(cm_processor, brush_color); + IMB_colormanagement_processor_free(cm_processor); + last_used_color_space = to_colorspace; + } + + private: + void init_brush_strength() + { + brush_strength = ss->cache->bstrength; + } + void init_brush_test() + { + brush_test_fn = SCULPT_brush_test_init_with_falloff_shape(ss, &test, brush->falloff_shape); + } + + /** + * Extract the starting pixel position from the given encoded_pixels belonging to the triangle. + */ + float3 get_start_pixel_pos(const TrianglePaintInput &triangle, + const PackedPixelRow &encoded_pixels) const + { + return init_pixel_pos(triangle, encoded_pixels.start_barycentric_coord); + } + + /** + * Extract the delta pixel position that will be used to advance a Pixel instance to the next + * pixel. + */ + float3 get_delta_pixel_pos(const TrianglePaintInput &triangle, + const PackedPixelRow &encoded_pixels, + const float3 &start_pixel) const + { + float3 result = init_pixel_pos( + triangle, encoded_pixels.start_barycentric_coord + triangle.delta_barycentric_coord_u); + return result - start_pixel; + } + + float3 init_pixel_pos(const TrianglePaintInput &triangle, + const float2 &barycentric_weights) const + { + const int3 &vert_indices = triangle.vert_indices; + float3 result; + const float3 barycentric(barycentric_weights.x, + barycentric_weights.y, + 1.0f - barycentric_weights.x - barycentric_weights.y); + interp_v3_v3v3v3(result, + mvert[vert_indices[0]].co, + mvert[vert_indices[1]].co, + mvert[vert_indices[2]].co, + barycentric); + return result; + } +}; + +static std::vector<bool> init_triangle_brush_test(SculptSession *ss, + Triangles &triangles, + const MVert *mvert) +{ + std::vector<bool> brush_test(triangles.size()); + SculptBrushTest test; + SCULPT_brush_test_init(ss, &test); + float3 brush_min_bounds(test.location[0] - test.radius, + test.location[1] - test.radius, + test.location[2] - test.radius); + float3 brush_max_bounds(test.location[0] + test.radius, + test.location[1] + test.radius, + test.location[2] + test.radius); + for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) { + TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index); + + float3 triangle_min_bounds(mvert[triangle.vert_indices[0]].co); + float3 triangle_max_bounds(triangle_min_bounds); + for (int i = 1; i < 3; i++) { + const float3 &pos = mvert[triangle.vert_indices[i]].co; + triangle_min_bounds.x = min_ff(triangle_min_bounds.x, pos.x); + triangle_min_bounds.y = min_ff(triangle_min_bounds.y, pos.y); + triangle_min_bounds.z = min_ff(triangle_min_bounds.z, pos.z); + triangle_max_bounds.x = max_ff(triangle_max_bounds.x, pos.x); + triangle_max_bounds.y = max_ff(triangle_max_bounds.y, pos.y); + triangle_max_bounds.z = max_ff(triangle_max_bounds.z, pos.z); + } + brush_test[triangle_index] = isect_aabb_aabb_v3( + brush_min_bounds, brush_max_bounds, triangle_min_bounds, triangle_max_bounds); + } + return brush_test; +} + +static void do_paint_pixels(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata); + Object *ob = data->ob; + SculptSession *ss = ob->sculpt; + const Brush *brush = data->brush; + PBVHNode *node = data->nodes[n]; + + NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node); + const int thread_id = BLI_task_parallel_thread_id(tls); + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + + std::vector<bool> brush_test = init_triangle_brush_test(ss, node_data.triangles, mvert); + + PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, mvert); + PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, mvert); + + ImageUser image_user = *data->image_data.image_user; + bool pixels_updated = false; + for (UDIMTilePixels &tile_data : node_data.tiles) { + LISTBASE_FOREACH (ImageTile *, tile, &data->image_data.image->tiles) { + ImageTileWrapper image_tile(tile); + if (image_tile.get_tile_number() == tile_data.tile_number) { + image_user.tile = image_tile.get_tile_number(); + + ImBuf *image_buffer = BKE_image_acquire_ibuf(data->image_data.image, &image_user, nullptr); + if (image_buffer == nullptr) { + continue; + } + + if (image_buffer->rect_float != nullptr) { + kernel_float4.init_brush_color(image_buffer); + } + else { + kernel_float4.init_brush_color(image_buffer); + } + + for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) { + if (!brush_test[pixel_row.triangle_index]) { + continue; + } + bool pixels_painted = false; + if (image_buffer->rect_float != nullptr) { + pixels_painted = kernel_float4.paint(node_data.triangles, pixel_row, image_buffer); + } + else { + pixels_painted = kernel_byte4.paint(node_data.triangles, pixel_row, image_buffer); + } + + if (pixels_painted) { + tile_data.mark_dirty(pixel_row); + } + } + + BKE_image_release_ibuf(data->image_data.image, image_buffer, nullptr); + pixels_updated |= tile_data.flags.dirty; + break; + } + } + } + + node_data.flags.dirty |= pixels_updated; +} + +static void do_mark_dirty_regions(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata); + PBVHNode *node = data->nodes[n]; + BKE_pbvh_pixels_mark_image_dirty(*node, *data->image_data.image, *data->image_data.image_user); +} + +} // namespace blender::ed::sculpt_paint::paint::image + +extern "C" { + +using namespace blender::ed::sculpt_paint::paint::image; + +bool SCULPT_paint_image_canvas_get(PaintModeSettings *paint_mode_settings, + Object *ob, + Image **r_image, + ImageUser **r_image_user) +{ + BLI_assert(r_image); + BLI_assert(r_image_user); + ImageData image_data; + if (!ImageData::init_active_image(ob, &image_data, paint_mode_settings)) { + return false; + } + + *r_image = image_data.image; + *r_image_user = image_data.image_user; + return true; +} + +bool SCULPT_use_image_paint_brush(PaintModeSettings *settings, Object *ob) +{ + if (!U.experimental.use_sculpt_texture_paint) { + return false; + } + if (ob->type != OB_MESH) { + return false; + } + Image *image; + ImageUser *image_user; + return BKE_paint_canvas_image_get(settings, ob, &image, &image_user); +} + +void SCULPT_do_paint_brush_image( + PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + TexturePaintingUserData data = {nullptr}; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + + if (!ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) { + return; + } + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_paint_pixels, &settings); + + TaskParallelSettings settings_flush; + BKE_pbvh_parallel_range_settings(&settings_flush, false, totnode); + BLI_task_parallel_range(0, totnode, &data, do_mark_dirty_regions, &settings_flush); +} +} diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 692940d405c..47d35ced371 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -1882,7 +1882,7 @@ static void node_draw_extra_info_row(const bNode &node, 0, 0, extra_info_row.tooltip); - if (extra_info_row.tooltip_fn != NULL) { + if (extra_info_row.tooltip_fn != nullptr) { UI_but_func_tooltip_set(but_icon, extra_info_row.tooltip_fn, extra_info_row.tooltip_fn_arg, diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 9d83f977fe0..2d7972e2291 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -1306,6 +1306,11 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) newlink->flag = link->flag; newlink->tonode = node_map.lookup(link->tonode); newlink->tosock = socket_map.lookup(link->tosock); + + if (link->tosock->flag & SOCK_MULTI_INPUT) { + newlink->multi_input_socket_index = link->multi_input_socket_index; + } + if (link->fromnode && (link->fromnode->flag & NODE_SELECT)) { newlink->fromnode = node_map.lookup(link->fromnode); newlink->fromsock = socket_map.lookup(link->fromsock); diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index 65b4ba15369..5da64177e51 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -2017,7 +2017,7 @@ static const EnumPropertyItem prop_id_op_types[] = { {OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET, "OVERRIDE_LIBRARY_RESET", 0, - "Reset Library Override", + "Reset Library Override Single", "Reset this local override to its linked values"}, {OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY, "OVERRIDE_LIBRARY_RESET_HIERARCHY", @@ -2037,18 +2037,18 @@ static const EnumPropertyItem prop_id_op_types[] = { "Rebuild this local override from its linked reference, as well as its hierarchy of " "dependencies, enforcing that hierarchy to match the linked data (i.e. ignoring exiting " "overrides on data-blocks pointer properties)"}, + {OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE, + "OVERRIDE_LIBRARY_CLEAR_SINGLE", + 0, + "Clear Library Override Single", + "Delete this local override and relink its usages to the linked data-blocks if possible, " + "else reset it and mark it as non editable"}, {OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_HIERARCHY, "OVERRIDE_LIBRARY_CLEAR_HIERARCHY", 0, "Clear Library Override Hierarchy", "Delete this local override (including its hierarchy of override dependencies) and relink " "its usages to the linked data-blocks"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE, - "OVERRIDE_LIBRARY_CLEAR_SINGLE", - 0, - "Clear Single Library Override", - "Delete this local override if possible, else reset it and mark it as non editable, and " - "relink its usages to the linked data-blocks"}, {0, "", 0, nullptr, nullptr}, {OUTLINER_IDOP_COPY, "COPY", ICON_COPYDOWN, "Copy", ""}, {OUTLINER_IDOP_PASTE, "PASTE", ICON_PASTEDOWN, "Paste", ""}, diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 31e885d16f2..1c1694479d3 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -383,7 +383,7 @@ static void draw_seq_waveform_overlay(View2D *v2d, return; } - /* F-curve lookup is quite expensive, so do this after precondition. */ + /* F-Curve lookup is quite expensive, so do this after precondition. */ FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL); WaveVizData *tri_strip_arr = MEM_callocN(sizeof(*tri_strip_arr) * pix_strip_len * 2, diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c index 055aac041f1..4606908b91f 100644 --- a/source/blender/editors/space_view3d/view3d_iterators.c +++ b/source/blender/editors/space_view3d/view3d_iterators.c @@ -568,7 +568,7 @@ void mesh_foreachScreenFace( BM_mesh_elem_table_ensure(vc->em->bm, BM_FACE); - if (BKE_modifiers_uses_subsurf_facedots(vc->scene, vc->obedit)) { + if (me->runtime.subsurf_face_dot_tags != NULL) { BKE_mesh_foreach_mapped_subdiv_face_center( me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); } diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c index ac1e961e361..71c245cd512 100644 --- a/source/blender/editors/transform/transform_convert_action.c +++ b/source/blender/editors/transform/transform_convert_action.c @@ -337,7 +337,7 @@ void createTransActionData(bContext *C, TransInfo *t) t->frame_side = 'B'; } - /* loop 1: fully select F-curve keys and count how many BezTriples are selected */ + /* loop 1: fully select F-Curve keys and count how many BezTriples are selected */ for (ale = anim_data.first; ale; ale = ale->next) { AnimData *adt = ANIM_nla_mapping_get(&ac, ale); int adt_count = 0; diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc index c3168b58c47..ab78ef6a5aa 100644 --- a/source/blender/editors/transform/transform_snap_object.cc +++ b/source/blender/editors/transform/transform_snap_object.cc @@ -178,13 +178,20 @@ static const Mesh *mesh_for_snap(Object *ob_eval, eSnapEditType edit_mode_type, /** * Calculate the minimum and maximum coordinates of the box that encompasses this mesh. */ -static void bm_mesh_minmax(BMesh *bm, float r_min[3], float r_max[3]) +static void snap_editmesh_minmax(SnapObjectContext *sctx, + BMesh *bm, + float r_min[3], + float r_max[3]) { INIT_MINMAX(r_min, r_max); BMIter iter; BMVert *v; BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (sctx->callbacks.edit_mesh.test_vert_fn && + !sctx->callbacks.edit_mesh.test_vert_fn(v, sctx->callbacks.edit_mesh.user_data)) { + continue; + } minmax_v3v3_v3(r_min, r_max, v->co); } } @@ -312,10 +319,10 @@ static SnapObjectData *snap_object_data_mesh_get(SnapObjectContext *sctx, use_hide ? BVHTREE_FROM_LOOPTRI_NO_HIDDEN : BVHTREE_FROM_LOOPTRI, 4); - BLI_assert(sod->treedata_mesh.vert != nullptr); - BLI_assert(sod->treedata_mesh.vert_normals != nullptr); - BLI_assert(sod->treedata_mesh.loop != nullptr); - BLI_assert(sod->treedata_mesh.looptri != nullptr); + BLI_assert(sod->treedata_mesh.vert == me_eval->mvert); + BLI_assert(!me_eval->mvert || sod->treedata_mesh.vert_normals); + BLI_assert(sod->treedata_mesh.loop == me_eval->mloop); + BLI_assert(!me_eval->mpoly || sod->treedata_mesh.looptri); BLI_assert(sod->has_looptris == false); sod->has_looptris = sod->treedata_mesh.tree != nullptr; @@ -423,7 +430,7 @@ static SnapObjectData *snap_object_data_editmesh_get(SnapObjectContext *sctx, sod->type = SnapObjectData::Type::EditMesh; sod->treedata_editmesh.em = em; sod->mesh_runtime = snap_object_data_editmesh_runtime_get(ob_eval); - bm_mesh_minmax(em->bm, sod->min, sod->max); + snap_editmesh_minmax(sctx, em->bm, sod->min, sod->max); } return sod; @@ -877,9 +884,6 @@ static bool raycastEditMesh(SnapObjectContext *sctx, BVHTreeFromEditMesh *treedata = &sod->treedata_editmesh; if (treedata->tree == nullptr) { - /* Operators only update the editmesh looptris of the original mesh. */ - BLI_assert(sod->treedata_editmesh.em == - BKE_editmesh_from_object(DEG_get_original_object(ob_eval))); em = sod->treedata_editmesh.em; if (sctx->callbacks.edit_mesh.test_face_fn) { @@ -1031,14 +1035,15 @@ static void raycast_obj_fn(SnapObjectContext *sctx, bool use_hide = false; const Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide); if (me_eval == nullptr) { - /* Operators only update the editmesh looptris of the original mesh. */ - BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval)); + BMEditMesh *em = BKE_editmesh_from_object(ob_eval); + BLI_assert_msg(em == BKE_editmesh_from_object(DEG_get_original_object(ob_eval)), + "Make sure there is only one pointer for looptris"); retval = raycastEditMesh(sctx, params, dt->ray_start, dt->ray_dir, ob_eval, - em_orig, + em, obmat, ob_index, ray_depth, @@ -2701,10 +2706,11 @@ static void snap_obj_fn(SnapObjectContext *sctx, bool use_hide; const Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide); if (me_eval == nullptr) { - /* Operators only update the editmesh looptris of the original mesh. */ - BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval)); + BMEditMesh *em = BKE_editmesh_from_object(ob_eval); + BLI_assert_msg(em == BKE_editmesh_from_object(DEG_get_original_object(ob_eval)), + "Make sure there is only one pointer for looptris"); retval = snapEditMesh( - sctx, params, ob_eval, em_orig, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); + sctx, params, ob_eval, em, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); break; } if (ob_eval->dt == OB_BOUNDBOX) { diff --git a/source/blender/geometry/intern/mesh_merge_by_distance.cc b/source/blender/geometry/intern/mesh_merge_by_distance.cc index 6dc6271194b..9bb1cbb324e 100644 --- a/source/blender/geometry/intern/mesh_merge_by_distance.cc +++ b/source/blender/geometry/intern/mesh_merge_by_distance.cc @@ -104,7 +104,7 @@ struct WeldMesh { /* References all polygons and loops that will be affected. */ Vector<WeldLoop> wloop; Vector<WeldPoly> wpoly; - WeldPoly *wpoly_new; + MutableSpan<WeldPoly> wpoly_new; int wloop_len; int wpoly_len; int wpoly_new_len; @@ -806,11 +806,9 @@ static void weld_poly_loop_ctx_alloc(Span<MPoly> mpoly, wpoly.resize(wpoly_len + maybe_new_poly); } - WeldPoly *poly_new = wpoly.data() + wpoly_len; - r_weld_mesh->wloop = std::move(wloop); r_weld_mesh->wpoly = std::move(wpoly); - r_weld_mesh->wpoly_new = poly_new; + r_weld_mesh->wpoly_new = r_weld_mesh->wpoly.as_mutable_span().drop_front(wpoly_len); r_weld_mesh->wloop_len = wloop_len; r_weld_mesh->wpoly_len = wpoly_len; r_weld_mesh->wpoly_new_len = 0; @@ -830,133 +828,134 @@ static void weld_poly_split_recursive(Span<int> vert_dest_map, int *r_loop_kill) { int poly_len = r_wp->len; - if (poly_len > 3 && ctx_verts_len > 1) { - const int ctx_loops_len = r_wp->loops.len; - const int ctx_loops_ofs = r_wp->loops.ofs; - MutableSpan<WeldLoop> wloop = r_weld_mesh->wloop; - WeldPoly *wpoly_new = r_weld_mesh->wpoly_new; - - int loop_kill = 0; - - WeldLoop *poly_loops = &wloop[ctx_loops_ofs]; - WeldLoop *wla = &poly_loops[0]; - WeldLoop *wla_prev = &poly_loops[ctx_loops_len - 1]; - while (wla_prev->flag == ELEM_COLLAPSED) { - wla_prev--; - } - const int la_len = ctx_loops_len - 1; - for (int la = 0; la < la_len; la++, wla++) { - wa_continue: - if (wla->flag == ELEM_COLLAPSED) { - continue; - } - int vert_a = wla->vert; - /* Only test vertices that will be merged. */ - if (vert_dest_map[vert_a] != OUT_OF_CONTEXT) { - int lb = la + 1; - WeldLoop *wlb = wla + 1; - WeldLoop *wlb_prev = wla; - int killed_ab = 0; - ctx_verts_len = 1; - for (; lb < ctx_loops_len; lb++, wlb++) { - BLI_assert(wlb->loop_skip_to == OUT_OF_CONTEXT); - if (wlb->flag == ELEM_COLLAPSED) { - killed_ab++; - continue; - } - int vert_b = wlb->vert; - if (vert_dest_map[vert_b] != OUT_OF_CONTEXT) { - ctx_verts_len++; + if (poly_len < 3 || ctx_verts_len < 1) { + return; + } + + const int ctx_loops_len = r_wp->loops.len; + const int ctx_loops_ofs = r_wp->loops.ofs; + MutableSpan<WeldLoop> wloop = r_weld_mesh->wloop; + + int loop_kill = 0; + + WeldLoop *poly_loops = &wloop[ctx_loops_ofs]; + WeldLoop *wla = &poly_loops[0]; + WeldLoop *wla_prev = &poly_loops[ctx_loops_len - 1]; + while (wla_prev->flag == ELEM_COLLAPSED) { + wla_prev--; + } + const int la_len = ctx_loops_len - 1; + for (int la = 0; la < la_len; la++, wla++) { + wa_continue: + if (wla->flag == ELEM_COLLAPSED) { + continue; + } + int vert_a = wla->vert; + /* Only test vertices that will be merged. */ + if (vert_dest_map[vert_a] != OUT_OF_CONTEXT) { + int lb = la + 1; + WeldLoop *wlb = wla + 1; + WeldLoop *wlb_prev = wla; + int killed_ab = 0; + ctx_verts_len = 1; + for (; lb < ctx_loops_len; lb++, wlb++) { + BLI_assert(wlb->loop_skip_to == OUT_OF_CONTEXT); + if (wlb->flag == ELEM_COLLAPSED) { + killed_ab++; + continue; + } + int vert_b = wlb->vert; + if (vert_dest_map[vert_b] != OUT_OF_CONTEXT) { + ctx_verts_len++; + } + if (vert_a == vert_b) { + const int dist_a = wlb->loop_orig - wla->loop_orig - killed_ab; + const int dist_b = poly_len - dist_a; + + BLI_assert(dist_a != 0 && dist_b != 0); + if (dist_a == 1 || dist_b == 1) { + BLI_assert(dist_a != dist_b); + BLI_assert((wla->flag == ELEM_COLLAPSED) || (wlb->flag == ELEM_COLLAPSED)); } - if (vert_a == vert_b) { - const int dist_a = wlb->loop_orig - wla->loop_orig - killed_ab; - const int dist_b = poly_len - dist_a; - - BLI_assert(dist_a != 0 && dist_b != 0); - if (dist_a == 1 || dist_b == 1) { - BLI_assert(dist_a != dist_b); - BLI_assert((wla->flag == ELEM_COLLAPSED) || (wlb->flag == ELEM_COLLAPSED)); + else { + WeldLoop *wl_tmp = nullptr; + if (dist_a == 2) { + wl_tmp = wlb_prev; + BLI_assert(wla->flag != ELEM_COLLAPSED); + BLI_assert(wl_tmp->flag != ELEM_COLLAPSED); + wla->flag = ELEM_COLLAPSED; + wl_tmp->flag = ELEM_COLLAPSED; + loop_kill += 2; + poly_len -= 2; } - else { - WeldLoop *wl_tmp = nullptr; - if (dist_a == 2) { - wl_tmp = wlb_prev; - BLI_assert(wla->flag != ELEM_COLLAPSED); + if (dist_b == 2) { + if (wl_tmp != nullptr) { + r_wp->flag = ELEM_COLLAPSED; + *r_poly_kill += 1; + } + else { + wl_tmp = wla_prev; + BLI_assert(wlb->flag != ELEM_COLLAPSED); BLI_assert(wl_tmp->flag != ELEM_COLLAPSED); - wla->flag = ELEM_COLLAPSED; + wlb->flag = ELEM_COLLAPSED; wl_tmp->flag = ELEM_COLLAPSED; - loop_kill += 2; - poly_len -= 2; - } - if (dist_b == 2) { - if (wl_tmp != nullptr) { - r_wp->flag = ELEM_COLLAPSED; - *r_poly_kill += 1; - } - else { - wl_tmp = wla_prev; - BLI_assert(wlb->flag != ELEM_COLLAPSED); - BLI_assert(wl_tmp->flag != ELEM_COLLAPSED); - wlb->flag = ELEM_COLLAPSED; - wl_tmp->flag = ELEM_COLLAPSED; - } - loop_kill += 2; - poly_len -= 2; } - if (wl_tmp == nullptr) { - const int new_loops_len = lb - la; - const int new_loops_ofs = ctx_loops_ofs + la; - - WeldPoly *new_wp = &wpoly_new[r_weld_mesh->wpoly_new_len++]; - new_wp->poly_dst = OUT_OF_CONTEXT; - new_wp->poly_orig = r_wp->poly_orig; - new_wp->loops.len = new_loops_len; - new_wp->loops.ofs = new_loops_ofs; - new_wp->loop_start = wla->loop_orig; - new_wp->loop_end = wlb_prev->loop_orig; - new_wp->len = dist_a; - weld_poly_split_recursive(vert_dest_map, + loop_kill += 2; + poly_len -= 2; + } + if (wl_tmp == nullptr) { + const int new_loops_len = lb - la; + const int new_loops_ofs = ctx_loops_ofs + la; + + WeldPoly *new_wp = &r_weld_mesh->wpoly_new[r_weld_mesh->wpoly_new_len++]; + new_wp->poly_dst = OUT_OF_CONTEXT; + new_wp->poly_orig = r_wp->poly_orig; + new_wp->loops.len = new_loops_len; + new_wp->loops.ofs = new_loops_ofs; + new_wp->loop_start = wla->loop_orig; + new_wp->loop_end = wlb_prev->loop_orig; + new_wp->len = dist_a; + weld_poly_split_recursive(vert_dest_map, #ifdef USE_WELD_DEBUG - mloop, + mloop, #endif - ctx_verts_len, - new_wp, - r_weld_mesh, - r_poly_kill, - r_loop_kill); - BLI_assert(dist_b == poly_len - dist_a); - poly_len = dist_b; - if (wla_prev->loop_orig > wla->loop_orig) { - /* New start. */ - r_wp->loop_start = wlb->loop_orig; - } - else { - /* The `loop_start` doesn't change but some loops must be skipped. */ - wla_prev->loop_skip_to = wlb->loop_orig; - } - wla = wlb; - la = lb; - goto wa_continue; + ctx_verts_len, + new_wp, + r_weld_mesh, + r_poly_kill, + r_loop_kill); + BLI_assert(dist_b == poly_len - dist_a); + poly_len = dist_b; + if (wla_prev->loop_orig > wla->loop_orig) { + /* New start. */ + r_wp->loop_start = wlb->loop_orig; } - break; + else { + /* The `loop_start` doesn't change but some loops must be skipped. */ + wla_prev->loop_skip_to = wlb->loop_orig; + } + wla = wlb; + la = lb; + goto wa_continue; } - } - if (wlb->flag != ELEM_COLLAPSED) { - wlb_prev = wlb; + break; } } + if (wlb->flag != ELEM_COLLAPSED) { + wlb_prev = wlb; + } } - if (wla->flag != ELEM_COLLAPSED) { - wla_prev = wla; - } } - r_wp->len = poly_len; - *r_loop_kill += loop_kill; + if (wla->flag != ELEM_COLLAPSED) { + wla_prev = wla; + } + } + r_wp->len = poly_len; + *r_loop_kill += loop_kill; #ifdef USE_WELD_DEBUG - weld_assert_poly_no_vert_repetition(*r_wp, wloop, mloop, r_weld_mesh->loop_map); + weld_assert_poly_no_vert_repetition(*r_wp, wloop, mloop, r_weld_mesh->loop_map); #endif - } } static void weld_poly_loop_ctx_setup(Span<MLoop> mloop, @@ -971,7 +970,6 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop, { MutableSpan<WeldPoly> wpoly = r_weld_mesh->wpoly; MutableSpan<WeldLoop> wloop = r_weld_mesh->wloop; - WeldPoly *wpoly_new = r_weld_mesh->wpoly_new; int wpoly_len = r_weld_mesh->wpoly_len; int wpoly_new_len = 0; int poly_kill_len = 0; @@ -1034,7 +1032,7 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop, #ifdef USE_WELD_DEBUG weld_assert_poly_and_loop_kill_len(wpoly, - {wpoly_new, wpoly_new_len}, + r_weld_mesh->wpoly_new, wloop, mloop, loop_map, @@ -1170,7 +1168,7 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop, #ifdef USE_WELD_DEBUG weld_assert_poly_and_loop_kill_len(wpoly, - {wpoly_new, wpoly_new_len}, + r_weld_mesh->wpoly_new, wloop, mloop, loop_map, @@ -1180,7 +1178,6 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop, loop_kill_len); #endif - r_weld_mesh->wpoly_new = wpoly_new; r_weld_mesh->poly_kill_len = poly_kill_len; r_weld_mesh->loop_kill_len = loop_kill_len; } diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index d1242e540fc..460b6d32967 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -292,7 +292,7 @@ struct GPUSource { const char whitespace_chars[] = " \r\n\t"; - auto function_parse = [&](const StringRef &input, + auto function_parse = [&](const StringRef input, int64_t &cursor, StringRef &out_return_type, StringRef &out_name, @@ -330,7 +330,7 @@ struct GPUSource { return true; }; - auto keyword_parse = [&](const StringRef &str, int64_t &cursor) -> const StringRef { + auto keyword_parse = [&](const StringRef str, int64_t &cursor) -> StringRef { int64_t keyword_start = str.find_first_not_of(whitespace_chars, cursor); if (keyword_start == -1) { /* No keyword found. */ @@ -345,7 +345,7 @@ struct GPUSource { return str.substr(keyword_start, keyword_end - keyword_start); }; - auto arg_parse = [&](const StringRef &str, + auto arg_parse = [&](const StringRef str, int64_t &cursor, StringRef &out_qualifier, StringRef &out_type, @@ -416,55 +416,51 @@ struct GPUSource { break; } - auto parse_qualifier = [](StringRef &qualifier) -> GPUFunctionQual { + auto parse_qualifier = [](StringRef qualifier) -> GPUFunctionQual { if (qualifier == "out") { return FUNCTION_QUAL_OUT; } - else if (qualifier == "inout") { + if (qualifier == "inout") { return FUNCTION_QUAL_INOUT; } - else { - return FUNCTION_QUAL_IN; - } + return FUNCTION_QUAL_IN; }; - auto parse_type = [](StringRef &type) -> eGPUType { + auto parse_type = [](StringRef type) -> eGPUType { if (type == "float") { return GPU_FLOAT; } - else if (type == "vec2") { + if (type == "vec2") { return GPU_VEC2; } - else if (type == "vec3") { + if (type == "vec3") { return GPU_VEC3; } - else if (type == "vec4") { + if (type == "vec4") { return GPU_VEC4; } - else if (type == "mat3") { + if (type == "mat3") { return GPU_MAT3; } - else if (type == "mat4") { + if (type == "mat4") { return GPU_MAT4; } - else if (type == "sampler1DArray") { + if (type == "sampler1DArray") { return GPU_TEX1D_ARRAY; } - else if (type == "sampler2DArray") { + if (type == "sampler2DArray") { return GPU_TEX2D_ARRAY; } - else if (type == "sampler2D") { + if (type == "sampler2D") { return GPU_TEX2D; } - else if (type == "sampler3D") { + if (type == "sampler3D") { return GPU_TEX3D; } - else if (type == "Closure") { + if (type == "Closure") { return GPU_CLOSURE; } - else { - return GPU_NONE; - } + return GPU_NONE; }; func->paramqual[func->totparam] = parse_qualifier(arg_qualifier); diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp index 8f5d2742b0e..923a392dbde 100644 --- a/source/blender/io/collada/AnimationImporter.cpp +++ b/source/blender/io/collada/AnimationImporter.cpp @@ -1059,7 +1059,7 @@ void AnimationImporter::translate_Animations( apply_matrix_curves(ob, animcurves, root, node, transform); } else { - /* Calculate RNA-paths and array index of F-curves according to transformation and + /* Calculate RNA-paths and array index of F-Curves according to transformation and * animation class */ Assign_transform_animations(transform, &bindings[j], &animcurves, is_joint, joint_path); diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt index 02bd5b2b81f..b1add38bf01 100644 --- a/source/blender/io/common/CMakeLists.txt +++ b/source/blender/io/common/CMakeLists.txt @@ -7,6 +7,8 @@ set(INC ../../blenlib ../../depsgraph ../../makesdna + ../../../../intern/guardedalloc + ../../../../extern/fast_float ) set(INC_SYS @@ -17,9 +19,11 @@ set(SRC intern/dupli_parent_finder.cc intern/dupli_persistent_id.cc intern/object_identifier.cc + intern/string_utils.cc IO_abstract_hierarchy_iterator.h IO_dupli_persistent_id.hh + IO_string_utils.hh IO_types.h intern/dupli_parent_finder.hh ) @@ -38,6 +42,7 @@ if(WITH_GTESTS) intern/abstract_hierarchy_iterator_test.cc intern/hierarchy_context_order_test.cc intern/object_identifier_test.cc + intern/string_utils_test.cc ) set(TEST_INC ../../blenloader diff --git a/source/blender/io/common/IO_string_utils.hh b/source/blender/io/common/IO_string_utils.hh new file mode 100644 index 00000000000..25f1f01c6ed --- /dev/null +++ b/source/blender/io/common/IO_string_utils.hh @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_string_ref.hh" + +/* + * Various text parsing utilities commonly used by text-based input formats. + */ + +namespace blender::io { + +/** + * Fetches next line from an input string buffer. + * + * The returned line will not have '\n' characters at the end; + * the `buffer` is modified to contain remaining text without + * the input line. + * + * Note that backslash (\) character is treated as a line + * continuation, similar to OBJ file format or a C preprocessor. + */ +StringRef read_next_line(StringRef &buffer); + +/** + * Drop leading white-space from a StringRef. + * Note that backslash character is considered white-space. + */ +StringRef drop_whitespace(StringRef str); + +/** + * Drop leading non-white-space from a StringRef. + * Note that backslash character is considered white-space. + */ +StringRef drop_non_whitespace(StringRef str); + +/** + * Parse an integer from an input string. + * The parsed result is stored in `dst`. The function skips + * leading white-space unless `skip_space=false`. If the + * number can't be parsed (invalid syntax, out of range), + * `fallback` value is stored instead. + * + * Returns the remainder of the input string after parsing. + */ +StringRef parse_int(StringRef str, int fallback, int &dst, bool skip_space = true); + +/** + * Parse a float from an input string. + * The parsed result is stored in `dst`. The function skips + * leading white-space unless `skip_space=false`. If the + * number can't be parsed (invalid syntax, out of range), + * `fallback` value is stored instead. + * + * Returns the remainder of the input string after parsing. + */ +StringRef parse_float(StringRef str, float fallback, float &dst, bool skip_space = true); + +/** + * Parse a number of white-space separated floats from an input string. + * The parsed `count` numbers are stored in `dst`. If a + * number can't be parsed (invalid syntax, out of range), + * `fallback` value is stored instead. + * + * Returns the remainder of the input string after parsing. + */ +StringRef parse_floats(StringRef str, float fallback, float *dst, int count); + +} // namespace blender::io diff --git a/source/blender/io/common/intern/string_utils.cc b/source/blender/io/common/intern/string_utils.cc new file mode 100644 index 00000000000..01107b0866e --- /dev/null +++ b/source/blender/io/common/intern/string_utils.cc @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "IO_string_utils.hh" + +/* Note: we could use C++17 <charconv> from_chars to parse + * floats, but even if some compilers claim full support, + * their standard libraries are not quite there yet. + * LLVM/libc++ only has a float parser since LLVM 14, + * and gcc/libstdc++ since 11.1. So until at least these are + * the mininum spec, use an external library. */ +#include "fast_float.h" +#include <charconv> + +namespace blender::io { + +StringRef read_next_line(StringRef &buffer) +{ + const char *start = buffer.begin(); + const char *end = buffer.end(); + size_t len = 0; + char prev = 0; + const char *ptr = start; + while (ptr < end) { + char c = *ptr++; + if (c == '\n' && prev != '\\') { + break; + } + prev = c; + ++len; + } + + buffer = StringRef(ptr, end); + return StringRef(start, len); +} + +static bool is_whitespace(char c) +{ + return c <= ' ' || c == '\\'; +} + +StringRef drop_whitespace(StringRef str) +{ + while (!str.is_empty() && is_whitespace(str[0])) { + str = str.drop_prefix(1); + } + return str; +} + +StringRef drop_non_whitespace(StringRef str) +{ + while (!str.is_empty() && !is_whitespace(str[0])) { + str = str.drop_prefix(1); + } + return str; +} + +static StringRef drop_plus(StringRef str) +{ + if (!str.is_empty() && str[0] == '+') { + str = str.drop_prefix(1); + } + return str; +} + +StringRef parse_float(StringRef str, float fallback, float &dst, bool skip_space) +{ + if (skip_space) { + str = drop_whitespace(str); + } + str = drop_plus(str); + fast_float::from_chars_result res = fast_float::from_chars(str.begin(), str.end(), dst); + if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { + dst = fallback; + } + return StringRef(res.ptr, str.end()); +} + +StringRef parse_floats(StringRef str, float fallback, float *dst, int count) +{ + for (int i = 0; i < count; ++i) { + str = parse_float(str, fallback, dst[i]); + } + return str; +} + +StringRef parse_int(StringRef str, int fallback, int &dst, bool skip_space) +{ + if (skip_space) { + str = drop_whitespace(str); + } + str = drop_plus(str); + std::from_chars_result res = std::from_chars(str.begin(), str.end(), dst); + if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { + dst = fallback; + } + return StringRef(res.ptr, str.end()); +} + +} // namespace blender::io diff --git a/source/blender/io/common/intern/string_utils_test.cc b/source/blender/io/common/intern/string_utils_test.cc new file mode 100644 index 00000000000..a78bd7ab8a3 --- /dev/null +++ b/source/blender/io/common/intern/string_utils_test.cc @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "IO_string_utils.hh" + +#include "testing/testing.h" + +namespace blender::io { + +#define EXPECT_STRREF_EQ(str1, str2) EXPECT_STREQ(str1, std::string(str2).c_str()) + +TEST(string_utils, read_next_line) +{ + std::string str = "abc\n \n\nline with \\\ncontinuation\nCRLF ending:\r\na"; + StringRef s = str; + EXPECT_STRREF_EQ("abc", read_next_line(s)); + EXPECT_STRREF_EQ(" ", read_next_line(s)); + EXPECT_STRREF_EQ("", read_next_line(s)); + EXPECT_STRREF_EQ("line with \\\ncontinuation", read_next_line(s)); + EXPECT_STRREF_EQ("CRLF ending:\r", read_next_line(s)); + EXPECT_STRREF_EQ("a", read_next_line(s)); + EXPECT_TRUE(s.is_empty()); +} + +TEST(string_utils, drop_whitespace) +{ + /* Empty */ + EXPECT_STRREF_EQ("", drop_whitespace("")); + /* Only whitespace */ + EXPECT_STRREF_EQ("", drop_whitespace(" ")); + EXPECT_STRREF_EQ("", drop_whitespace(" ")); + EXPECT_STRREF_EQ("", drop_whitespace(" \t\n\r ")); + /* Drops leading whitespace */ + EXPECT_STRREF_EQ("a", drop_whitespace(" a")); + EXPECT_STRREF_EQ("a b", drop_whitespace(" a b")); + EXPECT_STRREF_EQ("a b ", drop_whitespace(" a b ")); + /* No leading whitespace */ + EXPECT_STRREF_EQ("c", drop_whitespace("c")); + /* Case with backslash, should be treated as whitespace */ + EXPECT_STRREF_EQ("d", drop_whitespace(" \\ d")); +} + +TEST(string_utils, parse_int_valid) +{ + std::string str = "1 -10 \t 1234 1234567890 +7 123a"; + StringRef s = str; + int val; + s = parse_int(s, 0, val); + EXPECT_EQ(1, val); + s = parse_int(s, 0, val); + EXPECT_EQ(-10, val); + s = parse_int(s, 0, val); + EXPECT_EQ(1234, val); + s = parse_int(s, 0, val); + EXPECT_EQ(1234567890, val); + s = parse_int(s, 0, val); + EXPECT_EQ(7, val); + s = parse_int(s, 0, val); + EXPECT_EQ(123, val); + EXPECT_STRREF_EQ("a", s); +} + +TEST(string_utils, parse_int_invalid) +{ + int val; + /* Invalid syntax */ + EXPECT_STRREF_EQ("--123", parse_int("--123", -1, val)); + EXPECT_EQ(val, -1); + EXPECT_STRREF_EQ("foobar", parse_int("foobar", -2, val)); + EXPECT_EQ(val, -2); + /* Out of integer range */ + EXPECT_STRREF_EQ(" a", parse_int("1234567890123 a", -3, val)); + EXPECT_EQ(val, -3); + /* Has leading white-space when we don't expect it */ + EXPECT_STRREF_EQ(" 1", parse_int(" 1", -4, val, false)); + EXPECT_EQ(val, -4); +} + +TEST(string_utils, parse_float_valid) +{ + std::string str = "1 -10 123.5 -17.125 0.1 1e6 50.0e-1"; + StringRef s = str; + float val; + s = parse_float(s, 0, val); + EXPECT_EQ(1.0f, val); + s = parse_float(s, 0, val); + EXPECT_EQ(-10.0f, val); + s = parse_float(s, 0, val); + EXPECT_EQ(123.5f, val); + s = parse_float(s, 0, val); + EXPECT_EQ(-17.125f, val); + s = parse_float(s, 0, val); + EXPECT_EQ(0.1f, val); + s = parse_float(s, 0, val); + EXPECT_EQ(1.0e6f, val); + s = parse_float(s, 0, val); + EXPECT_EQ(5.0f, val); + EXPECT_TRUE(s.is_empty()); +} + +TEST(string_utils, parse_float_invalid) +{ + float val; + /* Invalid syntax */ + EXPECT_STRREF_EQ("_0", parse_float("_0", -1.0f, val)); + EXPECT_EQ(val, -1.0f); + EXPECT_STRREF_EQ("..5", parse_float("..5", -2.0f, val)); + EXPECT_EQ(val, -2.0f); + /* Out of float range. Current float parser (fast_float) + * clamps out of range numbers to +/- infinity, so this + * one gets a +inf instead of fallback -3.0. */ + EXPECT_STRREF_EQ(" a", parse_float("9.0e500 a", -3.0f, val)); + EXPECT_EQ(val, std::numeric_limits<float>::infinity()); + /* Has leading white-space when we don't expect it */ + EXPECT_STRREF_EQ(" 1", parse_float(" 1", -4.0f, val, false)); + EXPECT_EQ(val, -4.0f); +} + +} // namespace blender::io diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index 2b5ea39617e..e2e959814fa 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -16,6 +16,24 @@ add_definitions(-DPXR_STATIC) # USD headers use deprecated TBB headers, silence warning. add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1) +# Check if USD has the imaging headers available, if they are +# add a USD_HAS_IMAGING define so code can dynamically detect this. +# Cleanup of this variable is done at the end of the file since +# test code further down uses it to add imaging tests. +FIND_FILE(USD_IMAGING_HEADERS + NAMES + capsuleAdapter.h + PATHS + ${USD_INCLUDE_DIRS} + PATH_SUFFIXES + pxr/usdImaging/usdImaging/ + NO_DEFAULT_PATH +) + +if(USD_IMAGING_HEADERS) + add_definitions(-DUSD_HAS_IMAGING) +endif() + set(INC . ../common @@ -129,7 +147,13 @@ target_link_libraries(bf_usd INTERFACE ${TBB_LIBRARIES}) if(WITH_GTESTS) set(TEST_SRC tests/usd_stage_creation_test.cc + tests/usd_tests_common.cc + tests/usd_tests_common.h ) + if(USD_IMAGING_HEADERS) + LIST(APPEND TEST_SRC tests/usd_imaging_test.cc) + endif() + set(TEST_INC ) set(TEST_LIB @@ -137,3 +161,7 @@ if(WITH_GTESTS) include(GTestTesting) blender_add_test_lib(bf_io_usd_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}") endif() + +# In cmake version 3.21 and up, we can instead use the NO_CACHE option for +# find_file so we don't need to clear it from the cache here. +unset(USD_IMAGING_HEADERS CACHE) diff --git a/source/blender/io/usd/tests/usd_imaging_test.cc b/source/blender/io/usd/tests/usd_imaging_test.cc new file mode 100644 index 00000000000..497319c59bd --- /dev/null +++ b/source/blender/io/usd/tests/usd_imaging_test.cc @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ +#include "testing/testing.h" + +#include "usd_tests_common.h" + +#include <pxr/usd/usd/stage.h> +#include <pxr/usd/usdGeom/capsule.h> +#include <pxr/usdImaging/usdImaging/capsuleAdapter.h> + +namespace blender::io::usd { + +class USDImagingTest : public testing::Test { +}; + +TEST_F(USDImagingTest, CapsuleAdapterTest) +{ + /* A simple test to exercise the UsdImagingGprimAdapter API to + * ensure the code compiles, links and returns reasonable results. + * We create a capsule shape on an in-memory stage and attempt + * to access the shape's points and topology. */ + + /* We must register USD plugin paths before creating the stage + * to avoid a crash in the USD asset resolver initialization code. */ + if (register_usd_plugins_for_tests().empty()) { + FAIL(); + return; + } + + pxr::UsdStageRefPtr stage = pxr::UsdStage::CreateInMemory(); + + if (!stage) { + FAIL() << "Couldn't create in-memory stage."; + return; + } + + pxr::UsdGeomCapsule capsule = pxr::UsdGeomCapsule::Define(stage, pxr::SdfPath("/Capsule")); + + if (!capsule) { + FAIL() << "Couldn't create UsdGeomCapsule."; + return; + } + + pxr::UsdImagingCapsuleAdapter capsule_adapter; + pxr::VtValue points_value = capsule_adapter.GetMeshPoints(capsule.GetPrim(), + pxr::UsdTimeCode::Default()); + if (!points_value.IsHolding<pxr::VtArray<pxr::GfVec3f>>()) { + FAIL() << "Mesh points value holding unexpected type."; + return; + } + + pxr::VtArray<pxr::GfVec3f> points = points_value.Get<pxr::VtArray<pxr::GfVec3f>>(); + EXPECT_FALSE(points.empty()); + + pxr::VtValue topology_value = capsule_adapter.GetMeshTopology(); + + if (!topology_value.IsHolding<pxr::HdMeshTopology>()) { + FAIL() << "Mesh topology value holding unexpected type."; + return; + } + + pxr::HdMeshTopology topology = topology_value.Get<pxr::HdMeshTopology>(); + + pxr::VtArray<int> vertex_counts = topology.GetFaceVertexCounts(); + EXPECT_FALSE(vertex_counts.empty()); + + pxr::VtArray<int> vertex_indices = topology.GetFaceVertexIndices(); + EXPECT_FALSE(vertex_indices.empty()); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/tests/usd_stage_creation_test.cc b/source/blender/io/usd/tests/usd_stage_creation_test.cc index dbe5f8cd9ce..1aa6f9691ff 100644 --- a/source/blender/io/usd/tests/usd_stage_creation_test.cc +++ b/source/blender/io/usd/tests/usd_stage_creation_test.cc @@ -2,6 +2,8 @@ * Copyright 2019 Blender Foundation. All rights reserved. */ #include "testing/testing.h" +#include "usd_tests_common.h" + #include <pxr/base/plug/registry.h> #include <pxr/usd/usd/stage.h> @@ -19,23 +21,12 @@ class USDStageCreationTest : public testing::Test { TEST_F(USDStageCreationTest, JSONFileLoadingTest) { - const std::string &release_dir = blender::tests::flags_test_release_dir(); - if (release_dir.empty()) { + std::string usd_datafiles_dir = register_usd_plugins_for_tests(); + if (usd_datafiles_dir.empty()) { FAIL(); + return; } - char usd_datafiles_dir[FILE_MAX]; - const size_t path_len = BLI_path_join( - usd_datafiles_dir, FILE_MAX, release_dir.c_str(), "datafiles", "usd", nullptr); - - /* #BLI_path_join removes trailing slashes, but the USD library requires one in order to - * recognize the path as directory. */ - BLI_assert(path_len + 1 < FILE_MAX); - usd_datafiles_dir[path_len] = '/'; - usd_datafiles_dir[path_len + 1] = '\0'; - - pxr::PlugRegistry::GetInstance().RegisterPlugins(usd_datafiles_dir); - /* Simply the ability to create a USD Stage for a specific filename means that the extension * has been recognized by the USD library, and that a USD plugin has been loaded to write such * files. Practically, this is a test to see whether the USD JSON files can be found and diff --git a/source/blender/io/usd/tests/usd_tests_common.cc b/source/blender/io/usd/tests/usd_tests_common.cc new file mode 100644 index 00000000000..9f18a289433 --- /dev/null +++ b/source/blender/io/usd/tests/usd_tests_common.cc @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "usd_tests_common.h" + +#include "testing/testing.h" + +#include <pxr/base/plug/registry.h> + +#include "BLI_path_util.h" +#include "BLI_utildefines.h" + +#include "BKE_appdir.h" + +namespace blender::io::usd { + +std::string register_usd_plugins_for_tests() +{ + static char usd_datafiles_dir[FILE_MAX] = {'\0'}; + static bool plugin_path_registered = false; + if (plugin_path_registered) { + return usd_datafiles_dir; + } + plugin_path_registered = true; + + const std::string &release_dir = blender::tests::flags_test_release_dir(); + if (release_dir.empty()) { + return ""; + } + + const size_t path_len = BLI_path_join( + usd_datafiles_dir, FILE_MAX, release_dir.c_str(), "datafiles", "usd", nullptr); + + /* #BLI_path_join removes trailing slashes, but the USD library requires one in order to + * recognize the path as directory. */ + BLI_assert(path_len + 1 < FILE_MAX); + usd_datafiles_dir[path_len] = '/'; + usd_datafiles_dir[path_len + 1] = '\0'; + + pxr::PlugRegistry::GetInstance().RegisterPlugins(usd_datafiles_dir); + + return usd_datafiles_dir; +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/tests/usd_tests_common.h b/source/blender/io/usd/tests/usd_tests_common.h new file mode 100644 index 00000000000..b298a253ddc --- /dev/null +++ b/source/blender/io/usd/tests/usd_tests_common.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ +#pragma once + +#include <string> + +namespace blender::io::usd { + +/* Calls the function to load the USD plugins from the + * USD data directory under the Blender bin directory + * that was supplied as the --test-release-dir flag to ctest. + * Thus function must be called before instantiating a USD + * stage to avoid errors. The returned string is the path to + * the USD data files directory from which the plugins were + * loaded. If the USD data files directory can't be determined, + * plugin registration is skipped and the empty string is + * returned. */ +std::string register_usd_plugins_for_tests(); + +} // namespace blender::io::usd diff --git a/source/blender/io/wavefront_obj/CMakeLists.txt b/source/blender/io/wavefront_obj/CMakeLists.txt index 9cdd96ee7be..67cec000778 100644 --- a/source/blender/io/wavefront_obj/CMakeLists.txt +++ b/source/blender/io/wavefront_obj/CMakeLists.txt @@ -4,6 +4,7 @@ set(INC . ./exporter ./importer + ../common ../../blenkernel ../../blenlib ../../bmesh @@ -35,7 +36,6 @@ set(SRC importer/obj_import_mtl.cc importer/obj_import_nurbs.cc importer/obj_importer.cc - importer/parser_string_utils.cc IO_wavefront_obj.h exporter/obj_export_file_writer.hh @@ -51,11 +51,11 @@ set(SRC importer/obj_import_nurbs.hh importer/obj_import_objects.hh importer/obj_importer.hh - importer/parser_string_utils.hh ) set(LIB bf_blenkernel + bf_io_common ) if(WITH_TBB) @@ -70,6 +70,7 @@ if(WITH_GTESTS) set(TEST_SRC tests/obj_exporter_tests.cc tests/obj_importer_tests.cc + tests/obj_mtl_parser_tests.cc tests/obj_exporter_tests.hh ) diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h index b06ccbf8702..8b71ec750c0 100644 --- a/source/blender/io/wavefront_obj/IO_wavefront_obj.h +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h @@ -84,6 +84,7 @@ struct OBJImportParams { float clamp_size; eTransformAxisForward forward_axis; eTransformAxisUp up_axis; + bool validate_meshes; }; /** diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc index f78ef334d4d..96b342252c4 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc @@ -9,6 +9,7 @@ #include "BKE_blender_version.h" +#include "BLI_enumerable_thread_specific.hh" #include "BLI_path_util.h" #include "BLI_task.hh" @@ -308,6 +309,9 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, obj_mesh_data.tot_uv_vertices()); const int tot_polygons = obj_mesh_data.tot_polygons(); + const int tot_deform_groups = obj_mesh_data.tot_deform_groups(); + threading::EnumerableThreadSpecific<Vector<float>> group_weights; + obj_parallel_chunked_output(fh, tot_polygons, [&](FormatHandler<eFileType::OBJ> &buf, int idx) { /* Polygon order for writing into the file is not necessarily the same * as order in the mesh; it will be sorted by material indices. Remap current @@ -330,9 +334,12 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, /* Write vertex group if different from previous. */ if (export_params_.export_vertex_groups) { + Vector<float> &local_weights = group_weights.local(); + local_weights.resize(tot_deform_groups); const int16_t prev_group = idx == 0 ? NEGATIVE_INIT : - obj_mesh_data.get_poly_deform_group_index(prev_i); - const int16_t group = obj_mesh_data.get_poly_deform_group_index(i); + obj_mesh_data.get_poly_deform_group_index( + prev_i, local_weights); + const int16_t group = obj_mesh_data.get_poly_deform_group_index(i, local_weights); if (group != prev_group) { buf.write<eOBJSyntaxElement::object_group>( group == NOT_FOUND ? DEFORM_GROUP_DISABLED : diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc index 8c9b04a5ac3..c2a9e0574eb 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -426,6 +426,9 @@ void OBJMesh::store_normal_coords_and_indices() Vector<int> OBJMesh::calc_poly_normal_indices(const int poly_index) const { + if (loop_to_normal_index_.is_empty()) { + return {}; + } const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; const int totloop = mpoly.totloop; Vector<int> r_poly_normal_indices(totloop); @@ -436,46 +439,47 @@ Vector<int> OBJMesh::calc_poly_normal_indices(const int poly_index) const return r_poly_normal_indices; } -int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const +int OBJMesh::tot_deform_groups() const +{ + if (!BKE_object_supports_vertex_groups(&export_object_eval_)) { + return 0; + } + return BKE_object_defgroup_count(&export_object_eval_); +} + +int16_t OBJMesh::get_poly_deform_group_index(const int poly_index, + MutableSpan<float> group_weights) const { BLI_assert(poly_index < export_mesh_eval_->totpoly); - const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; - const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart]; - const Object *obj = &export_object_eval_; - const int tot_deform_groups = BKE_object_defgroup_count(obj); - /* Indices of the vector index into deform groups of an object; values are the] - * number of vertex members in one deform group. */ - Vector<int16_t> deform_group_members(tot_deform_groups, 0); - /* Whether at least one vertex in the polygon belongs to any group. */ - bool found_group = false; - - const MDeformVert *dvert_orig = static_cast<MDeformVert *>( + BLI_assert(group_weights.size() == BKE_object_defgroup_count(&export_object_eval_)); + + const MDeformVert *dvert_layer = static_cast<MDeformVert *>( CustomData_get_layer(&export_mesh_eval_->vdata, CD_MDEFORMVERT)); - if (!dvert_orig) { + if (!dvert_layer) { return NOT_FOUND; } - const MDeformWeight *curr_weight = nullptr; - const MDeformVert *dvert = nullptr; - for (int loop_index = 0; loop_index < mpoly.totloop; loop_index++) { - dvert = &dvert_orig[(mloop + loop_index)->v]; - curr_weight = dvert->dw; - if (curr_weight) { - bDeformGroup *vertex_group = static_cast<bDeformGroup *>( - BLI_findlink(BKE_object_defgroup_list(obj), curr_weight->def_nr)); - if (vertex_group) { - deform_group_members[curr_weight->def_nr] += 1; - found_group = true; + group_weights.fill(0); + bool found_any_group = false; + const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; + const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart]; + for (int loop_i = 0; loop_i < mpoly.totloop; ++loop_i, ++mloop) { + const MDeformVert &dvert = dvert_layer[mloop->v]; + for (int weight_i = 0; weight_i < dvert.totweight; ++weight_i) { + const auto group = dvert.dw[weight_i].def_nr; + if (group < group_weights.size()) { + group_weights[group] += dvert.dw[weight_i].weight; + found_any_group = true; } } } - if (!found_group) { + if (!found_any_group) { return NOT_FOUND; } /* Index of the group with maximum vertices. */ - int16_t max_idx = std::max_element(deform_group_members.begin(), deform_group_members.end()) - - deform_group_members.begin(); + int16_t max_idx = std::max_element(group_weights.begin(), group_weights.end()) - + group_weights.begin(); return max_idx; } diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh index 7650a220810..f47ca423dbc 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -116,6 +116,7 @@ class OBJMesh : NonCopyable { int tot_uv_vertices() const; int tot_normal_indices() const; int tot_edges() const; + int tot_deform_groups() const; bool is_mirrored_transform() const { return mirrored_transform_; @@ -204,13 +205,15 @@ class OBJMesh : NonCopyable { */ Vector<int> calc_poly_normal_indices(int poly_index) const; /** - * Find the index of the vertex group with the maximum number of vertices in a polygon. - * The index indices into the #Object.defbase. + * Find the most representative vertex group of a polygon. + * + * This adds up vertex group weights, and the group with the largest + * weight sum across the polygon is the one returned. * - * If two or more groups have the same number of vertices (maximum), group name depends on the - * implementation of #std::max_element. + * group_weights is temporary storage to avoid reallocations, it must + * be the size of amount of vertex groups in the object. */ - int16_t get_poly_deform_group_index(int poly_index) const; + int16_t get_poly_deform_group_index(int poly_index, MutableSpan<float> group_weights) const; /** * Find the name of the vertex deform group at the given index. * The index indices into the #Object.defbase. diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc index 17069c18295..c247048ce13 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -72,12 +72,12 @@ float3 OBJCurve::vertex_coordinates(const int spline_index, int OBJCurve::total_spline_control_points(const int spline_index) const { const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index)); - const int r_nurbs_degree = nurb->orderu - 1; + int degree = nurb->type == CU_POLY ? 1 : nurb->orderu - 1; /* Total control points = Number of points in the curve (+ degree of the * curve if it is cyclic). */ int r_tot_control_points = nurb->pntsv * nurb->pntsu; if (nurb->flagu & CU_NURB_CYCLIC) { - r_tot_control_points += r_nurbs_degree; + r_tot_control_points += degree; } return r_tot_control_points; } @@ -85,7 +85,7 @@ int OBJCurve::total_spline_control_points(const int spline_index) const int OBJCurve::get_nurbs_degree(const int spline_index) const { const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index)); - return nurb->orderu - 1; + return nurb->type == CU_POLY ? 1 : nurb->orderu - 1; } short OBJCurve::get_nurbs_flagu(const int spline_index) const diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index 584d8ae4ec0..78b709c884a 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -68,6 +68,17 @@ static void print_exception_error(const std::system_error &ex) << std::endl; } +static bool is_curve_nurbs_compatible(const Nurb *nurb) +{ + while (nurb) { + if (nurb->type == CU_BEZIER || nurb->pntsv != 1) { + return false; + } + nurb = nurb->next; + } + return true; +} + /** * Filter supported objects from the Scene. * @@ -104,27 +115,13 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par } break; } - switch (nurb->type) { - case CU_NURBS: - if (export_params.export_curves_as_nurbs) { - /* Export in parameter form: control points. */ - r_exportable_nurbs.append( - std::make_unique<OBJCurve>(depsgraph, export_params, object)); - } - else { - /* Export in mesh form: edges and vertices. */ - r_exportable_meshes.append( - std::make_unique<OBJMesh>(depsgraph, export_params, object)); - } - break; - case CU_BEZIER: - /* Always export in mesh form: edges and vertices. */ - r_exportable_meshes.append( - std::make_unique<OBJMesh>(depsgraph, export_params, object)); - break; - default: - /* Other curve types are not supported. */ - break; + if (export_params.export_curves_as_nurbs && is_curve_nurbs_compatible(nurb)) { + /* Export in parameter form: control points. */ + r_exportable_nurbs.append(std::make_unique<OBJCurve>(depsgraph, export_params, object)); + } + else { + /* Export in mesh form: edges and vertices. */ + r_exportable_meshes.append(std::make_unique<OBJMesh>(depsgraph, export_params, object)); } break; } diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc index d3d4e1ba860..3e722f8a0dd 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -8,7 +8,7 @@ #include "BLI_string_ref.hh" #include "BLI_vector.hh" -#include "parser_string_utils.hh" +#include "IO_string_utils.hh" #include "obj_import_file_reader.hh" @@ -34,7 +34,8 @@ static Geometry *create_geometry(Geometry *const prev_geometry, Geometry *g = r_all_geometries.last().get(); g->geom_type_ = new_type; g->geometry_name_ = name.is_empty() ? "New object" : name; - r_offset.set_index_offset(global_vertices.vertices.size()); + g->vertex_start_ = global_vertices.vertices.size(); + r_offset.set_index_offset(g->vertex_start_); return g; }; @@ -66,48 +67,40 @@ static Geometry *create_geometry(Geometry *const prev_geometry, } static void geom_add_vertex(Geometry *geom, - const StringRef rest_line, + const StringRef line, GlobalVertices &r_global_vertices) { - float3 curr_vert; - Vector<StringRef> str_vert_split; - split_by_char(rest_line, ' ', str_vert_split); - copy_string_to_float(str_vert_split, FLT_MAX, {curr_vert, 3}); - r_global_vertices.vertices.append(curr_vert); - geom->vertex_indices_.append(r_global_vertices.vertices.size() - 1); + float3 vert; + parse_floats(line, FLT_MAX, vert, 3); + r_global_vertices.vertices.append(vert); + geom->vertex_count_++; } static void geom_add_vertex_normal(Geometry *geom, - const StringRef rest_line, + const StringRef line, GlobalVertices &r_global_vertices) { - float3 curr_vert_normal; - Vector<StringRef> str_vert_normal_split; - split_by_char(rest_line, ' ', str_vert_normal_split); - copy_string_to_float(str_vert_normal_split, FLT_MAX, {curr_vert_normal, 3}); - r_global_vertices.vertex_normals.append(curr_vert_normal); + float3 normal; + parse_floats(line, FLT_MAX, normal, 3); + r_global_vertices.vertex_normals.append(normal); geom->has_vertex_normals_ = true; } -static void geom_add_uv_vertex(const StringRef rest_line, GlobalVertices &r_global_vertices) +static void geom_add_uv_vertex(const StringRef line, GlobalVertices &r_global_vertices) { - float2 curr_uv_vert; - Vector<StringRef> str_uv_vert_split; - split_by_char(rest_line, ' ', str_uv_vert_split); - copy_string_to_float(str_uv_vert_split, FLT_MAX, {curr_uv_vert, 2}); - r_global_vertices.uv_vertices.append(curr_uv_vert); + float2 uv; + parse_floats(line, FLT_MAX, uv, 2); + r_global_vertices.uv_vertices.append(uv); } static void geom_add_edge(Geometry *geom, - const StringRef rest_line, + StringRef line, const VertexIndexOffset &offsets, GlobalVertices &r_global_vertices) { - int edge_v1 = -1, edge_v2 = -1; - Vector<StringRef> str_edge_split; - split_by_char(rest_line, ' ', str_edge_split); - copy_string_to_int(str_edge_split[0], -1, edge_v1); - copy_string_to_int(str_edge_split[1], -1, edge_v2); + int edge_v1, edge_v2; + line = parse_int(line, -1, edge_v1); + line = parse_int(line, -1, edge_v2); /* Always keep stored indices non-negative and zero-based. */ edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; @@ -116,78 +109,45 @@ static void geom_add_edge(Geometry *geom, } static void geom_add_polygon(Geometry *geom, - const StringRef rest_line, + StringRef line, const GlobalVertices &global_vertices, const VertexIndexOffset &offsets, - const StringRef state_material_name, - const StringRef state_object_group, - const bool state_shaded_smooth) + const int material_index, + const int group_index, + const bool shaded_smooth) { PolyElem curr_face; - curr_face.shaded_smooth = state_shaded_smooth; - if (!state_material_name.is_empty()) { - curr_face.material_name = state_material_name; - } - if (!state_object_group.is_empty()) { - curr_face.vertex_group = state_object_group; - /* Yes it repeats several times, but another if-check will not reduce steps either. */ + curr_face.shaded_smooth = shaded_smooth; + curr_face.material_index = material_index; + if (group_index >= 0) { + curr_face.vertex_group_index = group_index; geom->use_vertex_groups_ = true; } + const int orig_corners_size = geom->face_corners_.size(); + curr_face.start_index_ = orig_corners_size; + bool face_valid = true; - Vector<StringRef> str_corners_split; - split_by_char(rest_line, ' ', str_corners_split); - for (StringRef str_corner : str_corners_split) { + while (!line.is_empty() && face_valid) { PolyCorner corner; - const size_t n_slash = std::count(str_corner.begin(), str_corner.end(), '/'); bool got_uv = false, got_normal = false; - if (n_slash == 0) { - /* Case: "f v1 v2 v3". */ - copy_string_to_int(str_corner, INT32_MAX, corner.vert_index); - } - else if (n_slash == 1) { - /* Case: "f v1/vt1 v2/vt2 v3/vt3". */ - Vector<StringRef> vert_uv_split; - split_by_char(str_corner, '/', vert_uv_split); - if (vert_uv_split.size() != 1 && vert_uv_split.size() != 2) { - fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str()); - face_valid = false; + /* Parse vertex index. */ + line = parse_int(line, INT32_MAX, corner.vert_index, false); + face_valid &= corner.vert_index != INT32_MAX; + if (!line.is_empty() && line[0] == '/') { + /* Parse UV index. */ + line = line.drop_prefix(1); + if (!line.is_empty() && line[0] != '/') { + line = parse_int(line, INT32_MAX, corner.uv_vert_index, false); + got_uv = corner.uv_vert_index != INT32_MAX; } - else { - copy_string_to_int(vert_uv_split[0], INT32_MAX, corner.vert_index); - if (vert_uv_split.size() == 2) { - copy_string_to_int(vert_uv_split[1], INT32_MAX, corner.uv_vert_index); - got_uv = corner.uv_vert_index != INT32_MAX; - } + /* Parse normal index. */ + if (!line.is_empty() && line[0] == '/') { + line = line.drop_prefix(1); + line = parse_int(line, INT32_MAX, corner.vertex_normal_index, false); + got_normal = corner.uv_vert_index != INT32_MAX; } } - else if (n_slash == 2) { - /* Case: "f v1//vn1 v2//vn2 v3//vn3". */ - /* Case: "f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3". */ - Vector<StringRef> vert_uv_normal_split; - split_by_char(str_corner, '/', vert_uv_normal_split); - if (vert_uv_normal_split.size() != 2 && vert_uv_normal_split.size() != 3) { - fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str()); - face_valid = false; - } - else { - copy_string_to_int(vert_uv_normal_split[0], INT32_MAX, corner.vert_index); - if (vert_uv_normal_split.size() == 3) { - copy_string_to_int(vert_uv_normal_split[1], INT32_MAX, corner.uv_vert_index); - got_uv = corner.uv_vert_index != INT32_MAX; - copy_string_to_int(vert_uv_normal_split[2], INT32_MAX, corner.vertex_normal_index); - got_normal = corner.vertex_normal_index != INT32_MAX; - } - else { - copy_string_to_int(vert_uv_normal_split[1], INT32_MAX, corner.vertex_normal_index); - got_normal = corner.vertex_normal_index != INT32_MAX; - } - } - } - else { - fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str()); - face_valid = false; - } /* Always keep stored indices non-negative and zero-based. */ corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : -offsets.get_index_offset() - 1; @@ -221,19 +181,28 @@ static void geom_add_polygon(Geometry *geom, face_valid = false; } } - curr_face.face_corners.append(corner); + geom->face_corners_.append(corner); + curr_face.corner_count_++; + + /* Skip whitespace to get to the next face corner. */ + line = drop_whitespace(line); } if (face_valid) { geom->face_elements_.append(curr_face); - geom->total_loops_ += curr_face.face_corners.size(); + geom->total_loops_ += curr_face.corner_count_; + } + else { + /* Remove just-added corners for the invalid face. */ + geom->face_corners_.resize(orig_corners_size); + geom->has_invalid_polys_ = true; } } static Geometry *geom_set_curve_type(Geometry *geom, const StringRef rest_line, const GlobalVertices &global_vertices, - const StringRef state_object_group, + const StringRef group_name, VertexIndexOffset &r_offsets, Vector<std::unique_ptr<Geometry>> &r_all_geometries) { @@ -242,254 +211,411 @@ static Geometry *geom_set_curve_type(Geometry *geom, return geom; } geom = create_geometry( - geom, GEOM_CURVE, state_object_group, global_vertices, r_all_geometries, r_offsets); - geom->nurbs_element_.group_ = state_object_group; + geom, GEOM_CURVE, group_name, global_vertices, r_all_geometries, r_offsets); + geom->nurbs_element_.group_ = group_name; return geom; } -static void geom_set_curve_degree(Geometry *geom, const StringRef rest_line) +static void geom_set_curve_degree(Geometry *geom, const StringRef line) { - copy_string_to_int(rest_line, 3, geom->nurbs_element_.degree); + parse_int(line, 3, geom->nurbs_element_.degree); } static void geom_add_curve_vertex_indices(Geometry *geom, - const StringRef rest_line, + StringRef line, const GlobalVertices &global_vertices) { - Vector<StringRef> str_curv_split; - split_by_char(rest_line, ' ', str_curv_split); - /* Remove "0.0" and "1.0" from the strings. They are hardcoded. */ - str_curv_split.remove(0); - str_curv_split.remove(0); - geom->nurbs_element_.curv_indices.resize(str_curv_split.size()); - copy_string_to_int(str_curv_split, INT32_MAX, geom->nurbs_element_.curv_indices); - for (int &curv_index : geom->nurbs_element_.curv_indices) { + /* Curve lines always have "0.0" and "1.0", skip over them. */ + float dummy[2]; + line = parse_floats(line, 0, dummy, 2); + /* Parse indices. */ + while (!line.is_empty()) { + int index; + line = parse_int(line, INT32_MAX, index); + if (index == INT32_MAX) { + return; + } /* Always keep stored indices non-negative and zero-based. */ - curv_index += curv_index < 0 ? global_vertices.vertices.size() : -1; + index += index < 0 ? global_vertices.vertices.size() : -1; + geom->nurbs_element_.curv_indices.append(index); } } -static void geom_add_curve_parameters(Geometry *geom, const StringRef rest_line) +static void geom_add_curve_parameters(Geometry *geom, StringRef line) { - Vector<StringRef> str_parm_split; - split_by_char(rest_line, ' ', str_parm_split); - if (str_parm_split[0] != "u" && str_parm_split[0] != "v") { - std::cerr << "Surfaces are not supported:'" << str_parm_split[0] << "'" << std::endl; + line = drop_whitespace(line); + if (line.is_empty()) { + std::cerr << "Invalid OBJ curve parm line: '" << line << "'" << std::endl; + return; + } + if (line[0] != 'u') { + std::cerr << "OBJ curve surfaces are not supported: '" << line[0] << "'" << std::endl; return; } - str_parm_split.remove(0); - geom->nurbs_element_.parm.resize(str_parm_split.size()); - copy_string_to_float(str_parm_split, FLT_MAX, geom->nurbs_element_.parm); + line = line.drop_prefix(1); + + while (!line.is_empty()) { + float val; + line = parse_float(line, FLT_MAX, val); + if (val != FLT_MAX) { + geom->nurbs_element_.parm.append(val); + } + else { + std::cerr << "OBJ curve parm line has invalid number" << std::endl; + return; + } + } } -static void geom_update_object_group(const StringRef rest_line, std::string &r_state_object_group) +static void geom_update_group(const StringRef rest_line, std::string &r_group_name) { if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos || rest_line.find("default") != string::npos) { /* Set group for future elements like faces or curves to empty. */ - r_state_object_group = ""; + r_group_name = ""; return; } - r_state_object_group = rest_line; -} - -static void geom_update_polygon_material(Geometry *geom, - const StringRef rest_line, - std::string &r_state_material_name) -{ - /* Materials may repeat if faces are written without sorting. */ - geom->material_names_.add(string(rest_line)); - r_state_material_name = rest_line; + r_group_name = rest_line; } -static void geom_update_smooth_group(const StringRef rest_line, bool &r_state_shaded_smooth) +static void geom_update_smooth_group(StringRef line, bool &r_state_shaded_smooth) { + line = drop_whitespace(line); /* Some implementations use "0" and "null" too, in addition to "off". */ - if (rest_line != "0" && rest_line.find("off") == StringRef::not_found && - rest_line.find("null") == StringRef::not_found) { - int smooth = 0; - copy_string_to_int(rest_line, 0, smooth); - r_state_shaded_smooth = smooth != 0; - } - else { - /* The OBJ file explicitly set shading to off. */ + if (line == "0" || line.startswith("off") || line.startswith("null")) { r_state_shaded_smooth = false; + return; } + + int smooth = 0; + parse_int(line, 0, smooth); + r_state_shaded_smooth = smooth != 0; } -OBJParser::OBJParser(const OBJImportParams &import_params) : import_params_(import_params) +OBJParser::OBJParser(const OBJImportParams &import_params, size_t read_buffer_size = 64 * 1024) + : import_params_(import_params), read_buffer_size_(read_buffer_size) { - obj_file_.open(import_params_.filepath); - if (!obj_file_.good()) { + obj_file_ = BLI_fopen(import_params_.filepath, "rb"); + if (!obj_file_) { fprintf(stderr, "Cannot read from OBJ file:'%s'.\n", import_params_.filepath); return; } } +OBJParser::~OBJParser() +{ + if (obj_file_) { + fclose(obj_file_); + } +} + void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, GlobalVertices &r_global_vertices) { - if (!obj_file_.good()) { + if (!obj_file_) { return; } - string line; - /* Store vertex coordinates that belong to other Geometry instances. */ VertexIndexOffset offsets; - /* Non owning raw pointer to a Geometry. To be updated while creating a new Geometry. */ Geometry *curr_geom = create_geometry( nullptr, GEOM_MESH, "", r_global_vertices, r_all_geometries, offsets); - /* State-setting variables: if set, they remain the same for the remaining + /* State variables: once set, they remain the same for the remaining * elements in the object. */ bool state_shaded_smooth = false; - string state_object_group; + string state_group_name; + int state_group_index = -1; string state_material_name; + int state_material_index = -1; + + /* Read the input file in chunks. We need up to twice the possible chunk size, + * to possibly store remainder of the previous input line that got broken mid-chunk. */ + Array<char> buffer(read_buffer_size_ * 2); + + size_t buffer_offset = 0; + size_t line_number = 0; + while (true) { + /* Read a chunk of input from the file. */ + size_t bytes_read = fread(buffer.data() + buffer_offset, 1, read_buffer_size_, obj_file_); + if (bytes_read == 0 && buffer_offset == 0) { + break; /* No more data to read. */ + } - while (std::getline(obj_file_, line)) { - /* Keep reading new lines if the last character is `\`. */ - /* Another way is to make a getline wrapper and use it in the while condition. */ - read_next_line(obj_file_, line); + /* Ensure buffer ends in a newline. */ + if (bytes_read < read_buffer_size_) { + if (bytes_read == 0 || buffer[buffer_offset + bytes_read - 1] != '\n') { + buffer[buffer_offset + bytes_read] = '\n'; + bytes_read++; + } + } - StringRef line_key, rest_line; - split_line_key_rest(line, line_key, rest_line); - if (line.empty() || rest_line.is_empty()) { - continue; + size_t buffer_end = buffer_offset + bytes_read; + if (buffer_end == 0) { + break; } - switch (line_key_str_to_enum(line_key)) { - case eOBJLineKey::V: { - geom_add_vertex(curr_geom, rest_line, r_global_vertices); - break; + + /* Find last newline. */ + size_t last_nl = buffer_end; + while (last_nl > 0) { + --last_nl; + if (buffer[last_nl] == '\n') { + if (last_nl < 1 || buffer[last_nl - 1] != '\\') { + break; + } } - case eOBJLineKey::VN: { - geom_add_vertex_normal(curr_geom, rest_line, r_global_vertices); - break; + } + if (buffer[last_nl] != '\n') { + /* Whole line did not fit into our read buffer. Warn and exit. */ + fprintf(stderr, + "OBJ file contains a line #%zu that is too long (max. length %zu)\n", + line_number, + read_buffer_size_); + break; + } + ++last_nl; + + /* Parse the buffer (until last newline) that we have so far, + * line by line. */ + StringRef buffer_str{buffer.data(), (int64_t)last_nl}; + while (!buffer_str.is_empty()) { + StringRef line = read_next_line(buffer_str); + ++line_number; + if (line.is_empty()) { + continue; } - case eOBJLineKey::VT: { - geom_add_uv_vertex(rest_line, r_global_vertices); - break; + /* Most common things that start with 'v': vertices, normals, UVs. */ + if (line[0] == 'v') { + if (line.startswith("v ")) { + line = line.drop_prefix(2); + geom_add_vertex(curr_geom, line, r_global_vertices); + } + else if (line.startswith("vn ")) { + line = line.drop_prefix(3); + geom_add_vertex_normal(curr_geom, line, r_global_vertices); + } + else if (line.startswith("vt ")) { + line = line.drop_prefix(3); + geom_add_uv_vertex(line, r_global_vertices); + } } - case eOBJLineKey::F: { + /* Faces. */ + else if (line.startswith("f ")) { + line = line.drop_prefix(2); geom_add_polygon(curr_geom, - rest_line, + line, r_global_vertices, offsets, - state_material_name, - state_material_name, + state_material_index, + state_group_index, /* TODO was wrongly material name! */ state_shaded_smooth); - break; - } - case eOBJLineKey::L: { - geom_add_edge(curr_geom, rest_line, offsets, r_global_vertices); - break; - } - case eOBJLineKey::CSTYPE: { - curr_geom = geom_set_curve_type(curr_geom, - rest_line, - r_global_vertices, - state_object_group, - offsets, - r_all_geometries); - break; - } - case eOBJLineKey::DEG: { - geom_set_curve_degree(curr_geom, rest_line); - break; - } - case eOBJLineKey::CURV: { - geom_add_curve_vertex_indices(curr_geom, rest_line, r_global_vertices); - break; - } - case eOBJLineKey::PARM: { - geom_add_curve_parameters(curr_geom, rest_line); - break; - } - case eOBJLineKey::O: { + } + /* Faces. */ + else if (line.startswith("l ")) { + line = line.drop_prefix(2); + geom_add_edge(curr_geom, line, offsets, r_global_vertices); + } + /* Objects. */ + else if (line.startswith("o ")) { + line = line.drop_prefix(2); state_shaded_smooth = false; - state_object_group = ""; + state_group_name = ""; state_material_name = ""; curr_geom = create_geometry( - curr_geom, GEOM_MESH, rest_line, r_global_vertices, r_all_geometries, offsets); - break; - } - case eOBJLineKey::G: { - geom_update_object_group(rest_line, state_object_group); - break; - } - case eOBJLineKey::S: { - geom_update_smooth_group(rest_line, state_shaded_smooth); - break; - } - case eOBJLineKey::USEMTL: { - geom_update_polygon_material(curr_geom, rest_line, state_material_name); - break; - } - case eOBJLineKey::MTLLIB: { - mtl_libraries_.append(string(rest_line)); - break; - } - case eOBJLineKey::COMMENT: - break; - default: - std::cout << "Element not recognised: '" << line_key << "'" << std::endl; - break; + curr_geom, GEOM_MESH, line, r_global_vertices, r_all_geometries, offsets); + } + /* Groups. */ + else if (line.startswith("g ")) { + line = line.drop_prefix(2); + geom_update_group(line, state_group_name); + int new_index = curr_geom->group_indices_.size(); + state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index); + if (new_index == state_group_index) { + curr_geom->group_order_.append(state_group_name); + } + } + /* Smoothing groups. */ + else if (line.startswith("s ")) { + line = line.drop_prefix(2); + geom_update_smooth_group(line, state_shaded_smooth); + } + /* Materials and their libraries. */ + else if (line.startswith("usemtl ")) { + line = line.drop_prefix(7); + state_material_name = line; + int new_mat_index = curr_geom->material_indices_.size(); + state_material_index = curr_geom->material_indices_.lookup_or_add(state_material_name, + new_mat_index); + if (new_mat_index == state_material_index) { + curr_geom->material_order_.append(state_material_name); + } + } + else if (line.startswith("mtllib ")) { + line = line.drop_prefix(7); + mtl_libraries_.append(string(line)); + } + /* Comments. */ + else if (line.startswith("#")) { + /* Nothing to do. */ + } + /* Curve related things. */ + else if (line.startswith("cstype ")) { + line = line.drop_prefix(7); + curr_geom = geom_set_curve_type( + curr_geom, line, r_global_vertices, state_group_name, offsets, r_all_geometries); + } + else if (line.startswith("deg ")) { + line = line.drop_prefix(4); + geom_set_curve_degree(curr_geom, line); + } + else if (line.startswith("curv ")) { + line = line.drop_prefix(5); + geom_add_curve_vertex_indices(curr_geom, line, r_global_vertices); + } + else if (line.startswith("parm ")) { + line = line.drop_prefix(5); + geom_add_curve_parameters(curr_geom, line); + } + else if (line.startswith("end")) { + /* End of curve definition, nothing else to do. */ + } + else { + std::cout << "OBJ element not recognised: '" << line << "'" << std::endl; + } } + + /* We might have a line that was cut in the middle by the previous buffer; + * copy it over for next chunk reading. */ + size_t left_size = buffer_end - last_nl; + memmove(buffer.data(), buffer.data() + last_nl, left_size); + buffer_offset = left_size; } } -/** - * Skip all texture map options and get the filepath from a "map_" line. - */ -static StringRef skip_unsupported_options(StringRef line) +static eMTLSyntaxElement mtl_line_start_to_enum(StringRef &line) { - TextureMapOptions map_options; - StringRef last_option; - int64_t last_option_pos = 0; - - /* Find the last texture map option. */ - for (StringRef option : map_options.all_options()) { - const int64_t pos{line.find(option)}; - /* Equality (>=) takes care of finding an option in the beginning of the line. Avoid messing - * with signed-unsigned int comparison. */ - if (pos != StringRef::not_found && pos >= last_option_pos) { - last_option = option; - last_option_pos = pos; - } + if (line.startswith("map_Kd")) { + line = line.drop_prefix(6); + return eMTLSyntaxElement::map_Kd; } - - if (last_option.is_empty()) { - /* No option found, line is the filepath */ - return line; + if (line.startswith("map_Ks")) { + line = line.drop_prefix(6); + return eMTLSyntaxElement::map_Ks; + } + if (line.startswith("map_Ns")) { + line = line.drop_prefix(6); + return eMTLSyntaxElement::map_Ns; + } + if (line.startswith("map_d")) { + line = line.drop_prefix(5); + return eMTLSyntaxElement::map_d; } + if (line.startswith("refl")) { + line = line.drop_prefix(4); + return eMTLSyntaxElement::map_refl; + } + if (line.startswith("map_refl")) { + line = line.drop_prefix(8); + return eMTLSyntaxElement::map_refl; + } + if (line.startswith("map_Ke")) { + line = line.drop_prefix(6); + return eMTLSyntaxElement::map_Ke; + } + if (line.startswith("bump")) { + line = line.drop_prefix(4); + return eMTLSyntaxElement::map_Bump; + } + if (line.startswith("map_Bump") || line.startswith("map_bump")) { + line = line.drop_prefix(8); + return eMTLSyntaxElement::map_Bump; + } + return eMTLSyntaxElement::string; +} - /* Remove up to start of the last option + size of the last option + space after it. */ - line = line.drop_prefix(last_option_pos + last_option.size() + 1); - for (int i = 0; i < map_options.number_of_args(last_option); i++) { - const int64_t pos_space{line.find_first_of(' ')}; - if (pos_space != StringRef::not_found) { - BLI_assert(pos_space + 1 < line.size()); - line = line.drop_prefix(pos_space + 1); +static const std::pair<const char *, int> unsupported_texture_options[] = { + {"-blendu ", 1}, + {"-blendv ", 1}, + {"-boost ", 1}, + {"-cc ", 1}, + {"-clamp ", 1}, + {"-imfchan ", 1}, + {"-mm ", 2}, + {"-t ", 3}, + {"-texres ", 1}, +}; + +static bool parse_texture_option(StringRef &line, MTLMaterial *material, tex_map_XX &tex_map) +{ + line = drop_whitespace(line); + if (line.startswith("-o ")) { + line = line.drop_prefix(3); + line = parse_floats(line, 0.0f, tex_map.translation, 3); + return true; + } + if (line.startswith("-s ")) { + line = line.drop_prefix(3); + line = parse_floats(line, 1.0f, tex_map.scale, 3); + return true; + } + if (line.startswith("-bm ")) { + line = line.drop_prefix(4); + line = parse_float(line, 0.0f, material->map_Bump_strength); + return true; + } + if (line.startswith("-type ")) { + line = line.drop_prefix(6); + line = drop_whitespace(line); + /* Only sphere is supported. */ + tex_map.projection_type = SHD_PROJ_SPHERE; + if (!line.startswith("sphere")) { + std::cerr << "OBJ import: only sphere MTL projection type is supported: '" << line << "'" + << std::endl; + } + line = drop_non_whitespace(line); + return true; + } + /* Check for unsupported options and skip them. */ + for (const auto &opt : unsupported_texture_options) { + if (line.startswith(opt.first)) { + /* Drop the option name. */ + line = line.drop_known_prefix(opt.first); + /* Drop the arguments. */ + for (int i = 0; i < opt.second; ++i) { + line = drop_whitespace(line); + line = drop_non_whitespace(line); + } + return true; } } - return line; + return false; } -/** - * Fix incoming texture map line keys for variations due to other exporters. - */ -static string fix_bad_map_keys(StringRef map_key) +static void parse_texture_map(StringRef line, MTLMaterial *material, const char *mtl_dir_path) { - string new_map_key(map_key); - if (map_key == "refl") { - new_map_key = "map_refl"; + bool is_map = line.startswith("map_"); + bool is_refl = line.startswith("refl"); + bool is_bump = line.startswith("bump"); + if (!is_map && !is_refl && !is_bump) { + return; + } + eMTLSyntaxElement key = mtl_line_start_to_enum(line); + if (key == eMTLSyntaxElement::string || !material->texture_maps.contains(key)) { + /* No supported texture map found. */ + std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl; + return; } - if (map_key.find("bump") != StringRef::not_found) { - /* Handles both "bump" and "map_Bump" */ - new_map_key = "map_Bump"; + tex_map_XX &tex_map = material->texture_maps.lookup(key); + tex_map.mtl_dir_path = mtl_dir_path; + + /* Parse texture map options. */ + while (parse_texture_option(line, material, tex_map)) { } - return new_map_key; + + /* What remains is the image path. */ + line = line.trim(); + tex_map.image_path = line; } Span<std::string> OBJParser::mtl_libraries() const @@ -503,125 +629,73 @@ MTLParser::MTLParser(StringRef mtl_library, StringRefNull obj_filepath) BLI_split_dir_part(obj_filepath.data(), obj_file_dir, FILE_MAXDIR); BLI_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data(), NULL); BLI_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR); - mtl_file_.open(mtl_file_path_); - if (!mtl_file_.good()) { - fprintf(stderr, "Cannot read from MTL file:'%s'\n", mtl_file_path_); - return; - } } -void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_mtl_materials) +void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_materials) { - if (!mtl_file_.good()) { + size_t buffer_len; + void *buffer = BLI_file_read_text_as_mem(mtl_file_path_, 0, &buffer_len); + if (buffer == nullptr) { + fprintf(stderr, "OBJ import: cannot read from MTL file: '%s'\n", mtl_file_path_); return; } - string line; - MTLMaterial *current_mtlmaterial = nullptr; + MTLMaterial *material = nullptr; - while (std::getline(mtl_file_, line)) { - StringRef line_key, rest_line; - split_line_key_rest(line, line_key, rest_line); - if (line.empty() || rest_line.is_empty()) { + StringRef buffer_str{(const char *)buffer, (int64_t)buffer_len}; + while (!buffer_str.is_empty()) { + StringRef line = read_next_line(buffer_str); + if (line.is_empty()) { continue; } - /* Fix lower case/ incomplete texture map identifiers. */ - const string fixed_key = fix_bad_map_keys(line_key); - line_key = fixed_key; - - if (line_key == "newmtl") { - if (r_mtl_materials.remove_as(rest_line)) { - std::cerr << "Duplicate material found:'" << rest_line + if (line.startswith("newmtl ")) { + line = line.drop_prefix(7); + if (r_materials.remove_as(line)) { + std::cerr << "Duplicate material found:'" << line << "', using the last encountered Material definition." << std::endl; } - current_mtlmaterial = - r_mtl_materials.lookup_or_add(string(rest_line), std::make_unique<MTLMaterial>()).get(); - } - else if (line_key == "Ns") { - copy_string_to_float(rest_line, 324.0f, current_mtlmaterial->Ns); - } - else if (line_key == "Ka") { - Vector<StringRef> str_ka_split; - split_by_char(rest_line, ' ', str_ka_split); - copy_string_to_float(str_ka_split, 0.0f, {current_mtlmaterial->Ka, 3}); - } - else if (line_key == "Kd") { - Vector<StringRef> str_kd_split; - split_by_char(rest_line, ' ', str_kd_split); - copy_string_to_float(str_kd_split, 0.8f, {current_mtlmaterial->Kd, 3}); - } - else if (line_key == "Ks") { - Vector<StringRef> str_ks_split; - split_by_char(rest_line, ' ', str_ks_split); - copy_string_to_float(str_ks_split, 0.5f, {current_mtlmaterial->Ks, 3}); - } - else if (line_key == "Ke") { - Vector<StringRef> str_ke_split; - split_by_char(rest_line, ' ', str_ke_split); - copy_string_to_float(str_ke_split, 0.0f, {current_mtlmaterial->Ke, 3}); - } - else if (line_key == "Ni") { - copy_string_to_float(rest_line, 1.45f, current_mtlmaterial->Ni); - } - else if (line_key == "d") { - copy_string_to_float(rest_line, 1.0f, current_mtlmaterial->d); + material = r_materials.lookup_or_add(string(line), std::make_unique<MTLMaterial>()).get(); } - else if (line_key == "illum") { - copy_string_to_int(rest_line, 2, current_mtlmaterial->illum); - } - - /* Parse image textures. */ - else if (line_key.find("map_") != StringRef::not_found) { - /* TODO(@howardt): fix this. */ - eMTLSyntaxElement line_key_enum = mtl_line_key_str_to_enum(line_key); - if (line_key_enum == eMTLSyntaxElement::string || - !current_mtlmaterial->texture_maps.contains_as(line_key_enum)) { - /* No supported texture map found. */ - std::cerr << "Texture map type not supported:'" << line_key << "'" << std::endl; - continue; + else if (material != nullptr) { + if (line.startswith("Ns ")) { + line = line.drop_prefix(3); + parse_float(line, 324.0f, material->Ns); } - tex_map_XX &tex_map = current_mtlmaterial->texture_maps.lookup(line_key_enum); - Vector<StringRef> str_map_xx_split; - split_by_char(rest_line, ' ', str_map_xx_split); - - /* TODO(@ankitm): use `skip_unsupported_options` for parsing these options too? */ - const int64_t pos_o{str_map_xx_split.first_index_of_try("-o")}; - if (pos_o != -1 && pos_o + 3 < str_map_xx_split.size()) { - copy_string_to_float({str_map_xx_split[pos_o + 1], - str_map_xx_split[pos_o + 2], - str_map_xx_split[pos_o + 3]}, - 0.0f, - {tex_map.translation, 3}); - } - const int64_t pos_s{str_map_xx_split.first_index_of_try("-s")}; - if (pos_s != -1 && pos_s + 3 < str_map_xx_split.size()) { - copy_string_to_float({str_map_xx_split[pos_s + 1], - str_map_xx_split[pos_s + 2], - str_map_xx_split[pos_s + 3]}, - 1.0f, - {tex_map.scale, 3}); - } - /* Only specific to Normal Map node. */ - const int64_t pos_bm{str_map_xx_split.first_index_of_try("-bm")}; - if (pos_bm != -1 && pos_bm + 1 < str_map_xx_split.size()) { - copy_string_to_float( - str_map_xx_split[pos_bm + 1], 0.0f, current_mtlmaterial->map_Bump_strength); - } - const int64_t pos_projection{str_map_xx_split.first_index_of_try("-type")}; - if (pos_projection != -1 && pos_projection + 1 < str_map_xx_split.size()) { - /* Only Sphere is supported, so whatever the type is, set it to Sphere. */ - tex_map.projection_type = SHD_PROJ_SPHERE; - if (str_map_xx_split[pos_projection + 1] != "sphere") { - std::cerr << "Using projection type 'sphere', not:'" - << str_map_xx_split[pos_projection + 1] << "'." << std::endl; - } + else if (line.startswith("Ka ")) { + line = line.drop_prefix(3); + parse_floats(line, 0.0f, material->Ka, 3); + } + else if (line.startswith("Kd ")) { + line = line.drop_prefix(3); + parse_floats(line, 0.8f, material->Kd, 3); + } + else if (line.startswith("Ks ")) { + line = line.drop_prefix(3); + parse_floats(line, 0.5f, material->Ks, 3); + } + else if (line.startswith("Ke ")) { + line = line.drop_prefix(3); + parse_floats(line, 0.0f, material->Ke, 3); + } + else if (line.startswith("Ni ")) { + line = line.drop_prefix(3); + parse_float(line, 1.45f, material->Ni); + } + else if (line.startswith("d ")) { + line = line.drop_prefix(2); + parse_float(line, 1.0f, material->d); + } + else if (line.startswith("illum ")) { + line = line.drop_prefix(6); + parse_int(line, 2, material->illum); + } + else { + parse_texture_map(line, material, mtl_dir_path_); } - - /* Skip all unsupported options and arguments. */ - tex_map.image_path = string(skip_unsupported_options(rest_line)); - tex_map.mtl_dir_path = mtl_dir_path_; } } + + MEM_freeN(buffer); } } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh index 24d026d75e5..8093417fcda 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh @@ -18,14 +18,16 @@ namespace blender::io::obj { class OBJParser { private: const OBJImportParams &import_params_; - blender::fstream obj_file_; + FILE *obj_file_; Vector<std::string> mtl_libraries_; + size_t read_buffer_size_; public: /** * Open OBJ file at the path given in import parameters. */ - OBJParser(const OBJImportParams &import_params); + OBJParser(const OBJImportParams &import_params, size_t read_buffer_size); + ~OBJParser(); /** * Read the OBJ file line by line and create OBJ Geometry instances. Also store all the vertex @@ -39,111 +41,6 @@ class OBJParser { Span<std::string> mtl_libraries() const; }; -enum class eOBJLineKey { - V, - VN, - VT, - F, - L, - CSTYPE, - DEG, - CURV, - PARM, - O, - G, - S, - USEMTL, - MTLLIB, - COMMENT -}; - -constexpr eOBJLineKey line_key_str_to_enum(const std::string_view key_str) -{ - if (key_str == "v" || key_str == "V") { - return eOBJLineKey::V; - } - if (key_str == "vn" || key_str == "VN") { - return eOBJLineKey::VN; - } - if (key_str == "vt" || key_str == "VT") { - return eOBJLineKey::VT; - } - if (key_str == "f" || key_str == "F") { - return eOBJLineKey::F; - } - if (key_str == "l" || key_str == "L") { - return eOBJLineKey::L; - } - if (key_str == "cstype" || key_str == "CSTYPE") { - return eOBJLineKey::CSTYPE; - } - if (key_str == "deg" || key_str == "DEG") { - return eOBJLineKey::DEG; - } - if (key_str == "curv" || key_str == "CURV") { - return eOBJLineKey::CURV; - } - if (key_str == "parm" || key_str == "PARM") { - return eOBJLineKey::PARM; - } - if (key_str == "o" || key_str == "O") { - return eOBJLineKey::O; - } - if (key_str == "g" || key_str == "G") { - return eOBJLineKey::G; - } - if (key_str == "s" || key_str == "S") { - return eOBJLineKey::S; - } - if (key_str == "usemtl" || key_str == "USEMTL") { - return eOBJLineKey::USEMTL; - } - if (key_str == "mtllib" || key_str == "MTLLIB") { - return eOBJLineKey::MTLLIB; - } - if (key_str == "#") { - return eOBJLineKey::COMMENT; - } - return eOBJLineKey::COMMENT; -} - -/** - * All texture map options with number of arguments they accept. - */ -class TextureMapOptions { - private: - Map<const std::string, int> tex_map_options; - - public: - TextureMapOptions() - { - tex_map_options.add_new("-blendu", 1); - tex_map_options.add_new("-blendv", 1); - tex_map_options.add_new("-boost", 1); - tex_map_options.add_new("-mm", 2); - tex_map_options.add_new("-o", 3); - tex_map_options.add_new("-s", 3); - tex_map_options.add_new("-t", 3); - tex_map_options.add_new("-texres", 1); - tex_map_options.add_new("-clamp", 1); - tex_map_options.add_new("-bm", 1); - tex_map_options.add_new("-imfchan", 1); - } - - /** - * All valid option strings. - */ - Map<const std::string, int>::KeyIterator all_options() const - { - return tex_map_options.keys(); - } - - int number_of_args(StringRef option) const - { - return tex_map_options.lookup_as(std::string(option)); - } -}; - class MTLParser { private: char mtl_file_path_[FILE_MAX]; @@ -151,7 +48,6 @@ class MTLParser { * Directory in which the MTL file is found. */ char mtl_dir_path_[FILE_MAX]; - blender::fstream mtl_file_; public: /** @@ -162,6 +58,6 @@ class MTLParser { /** * Read MTL file(s) and add MTLMaterial instances to the given Map reference. */ - void parse_and_store(Map<std::string, std::unique_ptr<MTLMaterial>> &r_mtl_materials); + void parse_and_store(Map<std::string, std::unique_ptr<MTLMaterial>> &r_materials); }; } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc index 55b2873a3de..01a2d63927e 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc @@ -18,6 +18,7 @@ #include "BLI_math_vector.h" #include "BLI_set.hh" +#include "IO_wavefront_obj.h" #include "importer_mesh_utils.hh" #include "obj_import_mesh.hh" @@ -35,7 +36,7 @@ Object *MeshFromGeometry::create_mesh( } fixup_invalid_faces(); - const int64_t tot_verts_object{mesh_geometry_.vertex_indices_.size()}; + const int64_t tot_verts_object{mesh_geometry_.vertex_count_}; /* Total explicitly imported edges, not the ones belonging the polygons to be created. */ const int64_t tot_edges{mesh_geometry_.edges_.size()}; const int64_t tot_face_elems{mesh_geometry_.face_elements_.size()}; @@ -52,11 +53,13 @@ Object *MeshFromGeometry::create_mesh( create_normals(mesh); create_materials(bmain, materials, created_materials, obj); - bool verbose_validate = false; + if (import_params.validate_meshes || mesh_geometry_.has_invalid_polys_) { + bool verbose_validate = false; #ifdef DEBUG - verbose_validate = true; + verbose_validate = true; #endif - BKE_mesh_validate(mesh, verbose_validate, false); + BKE_mesh_validate(mesh, verbose_validate, false); + } transform_object(obj, import_params); /* FIXME: after 2.80; `mesh->flag` isn't copied by #BKE_mesh_nomain_to_mesh() */ @@ -73,9 +76,9 @@ void MeshFromGeometry::fixup_invalid_faces() for (int64_t face_idx = 0; face_idx < mesh_geometry_.face_elements_.size(); ++face_idx) { const PolyElem &curr_face = mesh_geometry_.face_elements_[face_idx]; - if (curr_face.face_corners.size() < 3) { + if (curr_face.corner_count_ < 3) { /* Skip and remove faces that have fewer than 3 corners. */ - mesh_geometry_.total_loops_ -= curr_face.face_corners.size(); + mesh_geometry_.total_loops_ -= curr_face.corner_count_; mesh_geometry_.face_elements_.remove_and_reorder(face_idx); continue; } @@ -84,12 +87,14 @@ void MeshFromGeometry::fixup_invalid_faces() * basically whether it has duplicate vertex indices. */ bool valid = true; Set<int, 8> used_verts; - for (const PolyCorner &corner : curr_face.face_corners) { - if (used_verts.contains(corner.vert_index)) { + for (int i = 0; i < curr_face.corner_count_; ++i) { + int corner_idx = curr_face.start_index_ + i; + int vertex_idx = mesh_geometry_.face_corners_[corner_idx].vert_index; + if (used_verts.contains(vertex_idx)) { valid = false; break; } - used_verts.add(corner.vert_index); + used_verts.add(vertex_idx); } if (valid) { continue; @@ -100,20 +105,22 @@ void MeshFromGeometry::fixup_invalid_faces() Vector<int, 8> face_verts; Vector<int, 8> face_uvs; Vector<int, 8> face_normals; - face_verts.reserve(curr_face.face_corners.size()); - face_uvs.reserve(curr_face.face_corners.size()); - face_normals.reserve(curr_face.face_corners.size()); - for (const PolyCorner &corner : curr_face.face_corners) { + face_verts.reserve(curr_face.corner_count_); + face_uvs.reserve(curr_face.corner_count_); + face_normals.reserve(curr_face.corner_count_); + for (int i = 0; i < curr_face.corner_count_; ++i) { + int corner_idx = curr_face.start_index_ + i; + const PolyCorner &corner = mesh_geometry_.face_corners_[corner_idx]; face_verts.append(corner.vert_index); face_normals.append(corner.vertex_normal_index); face_uvs.append(corner.uv_vert_index); } - std::string face_vertex_group = curr_face.vertex_group; - std::string face_material_name = curr_face.material_name; + int face_vertex_group = curr_face.vertex_group_index; + int face_material = curr_face.material_index; bool face_shaded_smooth = curr_face.shaded_smooth; /* Remove the invalid face. */ - mesh_geometry_.total_loops_ -= curr_face.face_corners.size(); + mesh_geometry_.total_loops_ -= curr_face.corner_count_; mesh_geometry_.face_elements_.remove_and_reorder(face_idx); Vector<Vector<int>> new_faces = fixup_invalid_polygon(global_vertices_.vertices, face_verts); @@ -124,13 +131,14 @@ void MeshFromGeometry::fixup_invalid_faces() continue; } PolyElem new_face{}; - new_face.vertex_group = face_vertex_group; - new_face.material_name = face_material_name; + new_face.vertex_group_index = face_vertex_group; + new_face.material_index = face_material; new_face.shaded_smooth = face_shaded_smooth; - new_face.face_corners.reserve(face.size()); + new_face.start_index_ = mesh_geometry_.face_corners_.size(); + new_face.corner_count_ = face.size(); for (int idx : face) { BLI_assert(idx >= 0 && idx < face_verts.size()); - new_face.face_corners.append({face_verts[idx], face_uvs[idx], face_normals[idx]}); + mesh_geometry_.face_corners_.append({face_verts[idx], face_uvs[idx], face_normals[idx]}); } mesh_geometry_.face_elements_.append(new_face); mesh_geometry_.total_loops_ += face.size(); @@ -140,13 +148,14 @@ void MeshFromGeometry::fixup_invalid_faces() void MeshFromGeometry::create_vertices(Mesh *mesh) { - const int64_t tot_verts_object{mesh_geometry_.vertex_indices_.size()}; + const int tot_verts_object{mesh_geometry_.vertex_count_}; for (int i = 0; i < tot_verts_object; ++i) { - if (mesh_geometry_.vertex_indices_[i] < global_vertices_.vertices.size()) { - copy_v3_v3(mesh->mvert[i].co, global_vertices_.vertices[mesh_geometry_.vertex_indices_[i]]); + int vi = mesh_geometry_.vertex_start_ + i; + if (vi < global_vertices_.vertices.size()) { + copy_v3_v3(mesh->mvert[i].co, global_vertices_.vertices[vi]); } else { - std::cerr << "Vertex index:" << mesh_geometry_.vertex_indices_[i] + std::cerr << "Vertex index:" << vi << " larger than total vertices:" << global_vertices_.vertices.size() << " ." << std::endl; } @@ -158,7 +167,7 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh) /* Will not be used if vertex groups are not imported. */ mesh->dvert = nullptr; float weight = 0.0f; - const int64_t total_verts = mesh_geometry_.vertex_indices_.size(); + const int64_t total_verts = mesh_geometry_.vertex_count_; if (total_verts && mesh_geometry_.use_vertex_groups_) { mesh->dvert = static_cast<MDeformVert *>( CustomData_add_layer(&mesh->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, total_verts)); @@ -168,34 +177,32 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh) UNUSED_VARS(weight); } - /* Do not remove elements from the VectorSet since order of insertion is required. - * StringRef is fine since per-face deform group name outlives the VectorSet. */ - VectorSet<StringRef> group_names; const int64_t tot_face_elems{mesh->totpoly}; int tot_loop_idx = 0; for (int poly_idx = 0; poly_idx < tot_face_elems; ++poly_idx) { const PolyElem &curr_face = mesh_geometry_.face_elements_[poly_idx]; - if (curr_face.face_corners.size() < 3) { + if (curr_face.corner_count_ < 3) { /* Don't add single vertex face, or edges. */ std::cerr << "Face with less than 3 vertices found, skipping." << std::endl; continue; } MPoly &mpoly = mesh->mpoly[poly_idx]; - mpoly.totloop = curr_face.face_corners.size(); + mpoly.totloop = curr_face.corner_count_; mpoly.loopstart = tot_loop_idx; if (curr_face.shaded_smooth) { mpoly.flag |= ME_SMOOTH; } - mpoly.mat_nr = mesh_geometry_.material_names_.index_of_try(curr_face.material_name); + mpoly.mat_nr = curr_face.material_index; /* Importing obj files without any materials would result in negative indices, which is not * supported. */ if (mpoly.mat_nr < 0) { mpoly.mat_nr = 0; } - for (const PolyCorner &curr_corner : curr_face.face_corners) { + for (int idx = 0; idx < curr_face.corner_count_; ++idx) { + const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx]; MLoop &mloop = mesh->mloop[tot_loop_idx]; tot_loop_idx++; mloop.v = curr_corner.vert_index; @@ -212,23 +219,17 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh) MEM_callocN(sizeof(MDeformWeight), "OBJ Import Deform Weight")); } /* Every vertex in a face is assigned the same deform group. */ - int64_t pos_name{group_names.index_of_try(curr_face.vertex_group)}; - if (pos_name == -1) { - group_names.add_new(curr_face.vertex_group); - pos_name = group_names.size() - 1; - } - BLI_assert(pos_name >= 0); + int group_idx = curr_face.vertex_group_index; /* Deform group number (def_nr) must behave like an index into the names' list. */ - *(def_vert.dw) = {static_cast<unsigned int>(pos_name), weight}; + *(def_vert.dw) = {static_cast<unsigned int>(group_idx), weight}; } } if (!mesh->dvert) { return; } - /* Add deform group(s) to the object's defbase. */ - for (StringRef name : group_names) { - /* Adding groups in this order assumes that def_nr is an index into the names' list. */ + /* Add deform group names. */ + for (const std::string &name : mesh_geometry_.group_order_) { BKE_object_defgroup_add_name(obj, name.data()); } } @@ -236,7 +237,7 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh) void MeshFromGeometry::create_edges(Mesh *mesh) { const int64_t tot_edges{mesh_geometry_.edges_.size()}; - const int64_t total_verts{mesh_geometry_.vertex_indices_.size()}; + const int64_t total_verts{mesh_geometry_.vertex_count_}; UNUSED_VARS_NDEBUG(total_verts); for (int i = 0; i < tot_edges; ++i) { const MEdge &src_edge = mesh_geometry_.edges_[i]; @@ -263,7 +264,8 @@ void MeshFromGeometry::create_uv_verts(Mesh *mesh) int tot_loop_idx = 0; for (const PolyElem &curr_face : mesh_geometry_.face_elements_) { - for (const PolyCorner &curr_corner : curr_face.face_corners) { + for (int idx = 0; idx < curr_face.corner_count_; ++idx) { + const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx]; if (curr_corner.uv_vert_index >= 0 && curr_corner.uv_vert_index < global_vertices_.uv_vertices.size()) { const float2 &mluv_src = global_vertices_.uv_vertices[curr_corner.uv_vert_index]; @@ -317,7 +319,7 @@ void MeshFromGeometry::create_materials( Map<std::string, Material *> &created_materials, Object *obj) { - for (const std::string &name : mesh_geometry_.material_names_) { + for (const std::string &name : mesh_geometry_.material_order_) { Material *mat = get_or_create_material(bmain, name, materials, created_materials); if (mat == nullptr) { continue; @@ -340,7 +342,8 @@ void MeshFromGeometry::create_normals(Mesh *mesh) MEM_malloc_arrayN(mesh_geometry_.total_loops_, sizeof(float[3]), __func__)); int tot_loop_idx = 0; for (const PolyElem &curr_face : mesh_geometry_.face_elements_) { - for (const PolyCorner &curr_corner : curr_face.face_corners) { + for (int idx = 0; idx < curr_face.corner_count_; ++idx) { + const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx]; int n_index = curr_corner.vertex_normal_index; float3 normal(0, 0, 0); if (n_index >= 0) { diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh index 2a838215421..7cc7ed25ad1 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh @@ -45,11 +45,8 @@ class MeshFromGeometry : NonMovable, NonCopyable { void fixup_invalid_faces(); void create_vertices(Mesh *mesh); /** - * Create polygons for the Mesh, set smooth shading flag, deform group name, - * assigned material also. - * - * It must receive all polygons to be added to the mesh. - * Remove holes from polygons before * calling this. + * Create polygons for the Mesh, set smooth shading flags, deform group names, + * Materials. */ void create_polys_loops(Object *obj, Mesh *mesh); /** diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc index 88a8c07e325..f2a8941e8a7 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc @@ -13,12 +13,13 @@ #include "DNA_material_types.h" #include "DNA_node_types.h" +#include "IO_string_utils.hh" + #include "NOD_shader.h" /* TODO: move eMTLSyntaxElement out of following file into a more neutral place */ #include "obj_export_io.hh" #include "obj_import_mtl.hh" -#include "parser_string_utils.hh" namespace blender::io::obj { @@ -96,13 +97,16 @@ static bool load_texture_image(Main *bmain, const tex_map_XX &tex_map, bNode *r_ return true; } /* Try removing quotes. */ - std::string no_quote_path{replace_all_occurences(tex_path, "\"", "")}; + std::string no_quote_path{tex_path}; + auto end_pos = std::remove(no_quote_path.begin(), no_quote_path.end(), '"'); + no_quote_path.erase(end_pos, no_quote_path.end()); if (no_quote_path != tex_path && load_texture_image_at_path(bmain, tex_map, r_node, no_quote_path)) { return true; } /* Try replacing underscores with spaces. */ - std::string no_underscore_path{replace_all_occurences(no_quote_path, "_", " ")}; + std::string no_underscore_path{no_quote_path}; + std::replace(no_underscore_path.begin(), no_underscore_path.end(), '_', ' '); if (no_underscore_path != no_quote_path && no_underscore_path != tex_path && load_texture_image_at_path(bmain, tex_map, r_node, no_underscore_path)) { return true; diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh b/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh index 4b7827b2035..74bc9f21bc4 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh @@ -90,29 +90,4 @@ class ShaderNodetreeWrap { void add_image_textures(Main *bmain, Material *mat); }; -constexpr eMTLSyntaxElement mtl_line_key_str_to_enum(const std::string_view key_str) -{ - if (key_str == "map_Kd") { - return eMTLSyntaxElement::map_Kd; - } - if (key_str == "map_Ks") { - return eMTLSyntaxElement::map_Ks; - } - if (key_str == "map_Ns") { - return eMTLSyntaxElement::map_Ns; - } - if (key_str == "map_d") { - return eMTLSyntaxElement::map_d; - } - if (key_str == "refl" || key_str == "map_refl") { - return eMTLSyntaxElement::map_refl; - } - if (key_str == "map_Ke") { - return eMTLSyntaxElement::map_Ke; - } - if (key_str == "map_Bump" || key_str == "bump") { - return eMTLSyntaxElement::map_Bump; - } - return eMTLSyntaxElement::string; -} } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh index c6ce7d3c434..b67ba46af03 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh @@ -8,6 +8,7 @@ #include "BKE_lib_id.h" +#include "BLI_map.hh" #include "BLI_math_vec_types.hh" #include "BLI_vector.hh" #include "BLI_vector_set.hh" @@ -61,10 +62,11 @@ struct PolyCorner { }; struct PolyElem { - std::string vertex_group; - std::string material_name; + int vertex_group_index = -1; + int material_index = -1; bool shaded_smooth = false; - Vector<PolyCorner> face_corners; + int start_index_ = 0; + int corner_count_ = 0; }; /** @@ -93,15 +95,20 @@ enum eGeometryType { struct Geometry { eGeometryType geom_type_ = GEOM_MESH; std::string geometry_name_; - VectorSet<std::string> material_names_; - /** - * Indices in the vector range from zero to total vertices in a geometry. - * Values range from zero to total coordinates in the global list. - */ - Vector<int> vertex_indices_; + Map<std::string, int> group_indices_; + Vector<std::string> group_order_; + Map<std::string, int> material_indices_; + Vector<std::string> material_order_; + + int vertex_start_ = 0; + int vertex_count_ = 0; /** Edges written in the file in addition to (or even without polygon) elements. */ Vector<MEdge> edges_; + + Vector<PolyCorner> face_corners_; Vector<PolyElem> face_elements_; + + bool has_invalid_polys_ = false; bool has_vertex_normals_ = false; bool use_vertex_groups_ = false; NurbsElement nurbs_element_; diff --git a/source/blender/io/wavefront_obj/importer/obj_importer.cc b/source/blender/io/wavefront_obj/importer/obj_importer.cc index 631ddcc5cf4..c21d2d9583c 100644 --- a/source/blender/io/wavefront_obj/importer/obj_importer.cc +++ b/source/blender/io/wavefront_obj/importer/obj_importer.cc @@ -42,6 +42,12 @@ static void geometry_to_blender_objects( BKE_view_layer_base_deselect_all(view_layer); LayerCollection *lc = BKE_layer_collection_get_active(view_layer); + /* Don't do collection syncs for each object, will do once after the loop. */ + BKE_layer_collection_resync_forbid(); + + /* Create all the objects. */ + Vector<Object *> objects; + objects.reserve(all_geometries.size()); for (const std::unique_ptr<Geometry> &geometry : all_geometries) { Object *obj = nullptr; if (geometry->geom_type_ == GEOM_MESH) { @@ -54,17 +60,25 @@ static void geometry_to_blender_objects( } if (obj != nullptr) { BKE_collection_object_add(bmain, lc->collection, obj); - Base *base = BKE_view_layer_base_find(view_layer, obj); - /* TODO: is setting active needed? */ - BKE_view_layer_base_select_and_set_active(view_layer, base); - - DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE); - DEG_id_tag_update_ex(bmain, - &obj->id, - ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION | - ID_RECALC_BASE_FLAGS); + objects.append(obj); } } + + /* Sync the collection after all objects are created. */ + BKE_layer_collection_resync_allow(); + BKE_main_collection_sync(bmain); + + /* After collection sync, select objects in the view layer and do DEG updates. */ + for (Object *obj : objects) { + Base *base = BKE_view_layer_base_find(view_layer, obj); + BKE_view_layer_base_select_and_set_active(view_layer, base); + + DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE); + int flags = ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION | + ID_RECALC_BASE_FLAGS; + DEG_id_tag_update_ex(bmain, &obj->id, flags); + } + DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS); DEG_relations_tag_update(bmain); } @@ -81,7 +95,8 @@ void importer_main(bContext *C, const OBJImportParams &import_params) void importer_main(Main *bmain, Scene *scene, ViewLayer *view_layer, - const OBJImportParams &import_params) + const OBJImportParams &import_params, + size_t read_buffer_size) { /* List of Geometry instances to be parsed from OBJ file. */ Vector<std::unique_ptr<Geometry>> all_geometries; @@ -91,7 +106,7 @@ void importer_main(Main *bmain, Map<std::string, std::unique_ptr<MTLMaterial>> materials; Map<std::string, Material *> created_materials; - OBJParser obj_parser{import_params}; + OBJParser obj_parser{import_params, read_buffer_size}; obj_parser.parse(all_geometries, global_vertices); for (StringRef mtl_library : obj_parser.mtl_libraries()) { diff --git a/source/blender/io/wavefront_obj/importer/obj_importer.hh b/source/blender/io/wavefront_obj/importer/obj_importer.hh index fd83117ebc6..35f401d7cb0 100644 --- a/source/blender/io/wavefront_obj/importer/obj_importer.hh +++ b/source/blender/io/wavefront_obj/importer/obj_importer.hh @@ -17,6 +17,7 @@ void importer_main(bContext *C, const OBJImportParams &import_params); void importer_main(Main *bmain, Scene *scene, ViewLayer *view_layer, - const OBJImportParams &import_params); + const OBJImportParams &import_params, + size_t read_buffer_size = 64 * 1024); } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/parser_string_utils.cc b/source/blender/io/wavefront_obj/importer/parser_string_utils.cc deleted file mode 100644 index 6671a86f5ee..00000000000 --- a/source/blender/io/wavefront_obj/importer/parser_string_utils.cc +++ /dev/null @@ -1,174 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include <fstream> -#include <iostream> -#include <sstream> - -#include "BLI_math_vec_types.hh" -#include "BLI_span.hh" -#include "BLI_string_ref.hh" -#include "BLI_vector.hh" - -#include "parser_string_utils.hh" - -/* Note: these OBJ parser helper functions are planned to get fairly large - * changes "soon", so don't read too much into current implementation... */ - -namespace blender::io::obj { -using std::string; - -void read_next_line(std::fstream &file, string &r_line) -{ - std::string new_line; - while (file.good() && !r_line.empty() && r_line.back() == '\\') { - new_line.clear(); - const bool ok = static_cast<bool>(std::getline(file, new_line)); - /* Remove the last backslash character. */ - r_line.pop_back(); - r_line.append(new_line); - if (!ok || new_line.empty()) { - return; - } - } -} - -void split_line_key_rest(const StringRef line, StringRef &r_line_key, StringRef &r_rest_line) -{ - if (line.is_empty()) { - return; - } - - const int64_t pos_split{line.find_first_of(' ')}; - if (pos_split == StringRef::not_found) { - /* Use the first character if no space is found in the line. It's usually a comment like: - * #This is a comment. */ - r_line_key = line.substr(0, 1); - } - else { - r_line_key = line.substr(0, pos_split); - } - - /* Eat the delimiter also using "+ 1". */ - r_rest_line = line.drop_prefix(r_line_key.size() + 1); - if (r_rest_line.is_empty()) { - return; - } - - /* Remove any leading spaces, trailing spaces & \r character, if any. */ - const int64_t leading_space{r_rest_line.find_first_not_of(' ')}; - if (leading_space != StringRef::not_found) { - r_rest_line = r_rest_line.drop_prefix(leading_space); - } - - /* Another way is to do a test run before the actual parsing to find the newline - * character and use it in the getline. */ - const int64_t carriage_return{r_rest_line.find_first_of('\r')}; - if (carriage_return != StringRef::not_found) { - r_rest_line = r_rest_line.substr(0, carriage_return + 1); - } - - const int64_t trailing_space{r_rest_line.find_last_not_of(' ')}; - if (trailing_space != StringRef::not_found) { - /* The position is of a character that is not ' ', so count of characters is position + 1. */ - r_rest_line = r_rest_line.substr(0, trailing_space + 1); - } -} - -void split_by_char(StringRef in_string, const char delimiter, Vector<StringRef> &r_out_list) -{ - r_out_list.clear(); - - while (!in_string.is_empty()) { - const int64_t pos_delim{in_string.find_first_of(delimiter)}; - const int64_t word_len = pos_delim == StringRef::not_found ? in_string.size() : pos_delim; - - StringRef word{in_string.data(), word_len}; - if (!word.is_empty() && !(word == " " && !(word[0] == '\0'))) { - r_out_list.append(word); - } - if (pos_delim == StringRef::not_found) { - return; - } - /* Skip the word already stored. */ - in_string = in_string.drop_prefix(word_len); - /* Skip all delimiters. */ - const int64_t pos_non_delim = in_string.find_first_not_of(delimiter); - if (pos_non_delim == StringRef::not_found) { - return; - } - in_string = in_string.drop_prefix(std::min(pos_non_delim, in_string.size())); - } -} - -void copy_string_to_float(StringRef src, const float fallback_value, float &r_dst) -{ - try { - r_dst = std::stof(string(src)); - } - catch (const std::invalid_argument &inv_arg) { - std::cerr << "Bad conversion to float:'" << inv_arg.what() << "':'" << src << "'" << std::endl; - r_dst = fallback_value; - } - catch (const std::out_of_range &out_of_range) { - std::cerr << "Out of range for float:'" << out_of_range.what() << ":'" << src << "'" - << std::endl; - r_dst = fallback_value; - } -} - -void copy_string_to_float(Span<StringRef> src, - const float fallback_value, - MutableSpan<float> r_dst) -{ - for (int i = 0; i < r_dst.size(); ++i) { - if (i < src.size()) { - copy_string_to_float(src[i], fallback_value, r_dst[i]); - } - else { - r_dst[i] = fallback_value; - } - } -} - -void copy_string_to_int(StringRef src, const int fallback_value, int &r_dst) -{ - try { - r_dst = std::stoi(string(src)); - } - catch (const std::invalid_argument &inv_arg) { - std::cerr << "Bad conversion to int:'" << inv_arg.what() << "':'" << src << "'" << std::endl; - r_dst = fallback_value; - } - catch (const std::out_of_range &out_of_range) { - std::cerr << "Out of range for int:'" << out_of_range.what() << ":'" << src << "'" - << std::endl; - r_dst = fallback_value; - } -} - -void copy_string_to_int(Span<StringRef> src, const int fallback_value, MutableSpan<int> r_dst) -{ - for (int i = 0; i < r_dst.size(); ++i) { - if (i < src.size()) { - copy_string_to_int(src[i], fallback_value, r_dst[i]); - } - else { - r_dst[i] = fallback_value; - } - } -} - -std::string replace_all_occurences(StringRef original, StringRef to_remove, StringRef to_add) -{ - std::string clean{original}; - while (true) { - const std::string::size_type pos = clean.find(to_remove); - if (pos == std::string::npos) { - break; - } - clean.replace(pos, to_add.size(), to_add); - } - return clean; -} - -} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/parser_string_utils.hh b/source/blender/io/wavefront_obj/importer/parser_string_utils.hh deleted file mode 100644 index 62cfbebccf3..00000000000 --- a/source/blender/io/wavefront_obj/importer/parser_string_utils.hh +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -namespace blender::io::obj { - -/* Note: these OBJ parser helper functions are planned to get fairly large - * changes "soon", so don't read too much into current implementation... */ - -/** - * Store multiple lines separated by an escaped newline character: `\\n`. - * Use this before doing any parse operations on the read string. - */ -void read_next_line(std::fstream &file, std::string &r_line); -/** - * Split a line string into the first word (key) and the rest of the line. - * Also remove leading & trailing spaces as well as `\r` carriage return - * character if present. - */ -void split_line_key_rest(StringRef line, StringRef &r_line_key, StringRef &r_rest_line); -/** - * Split the given string by the delimiter and fill the given vector. - * If an intermediate string is empty, or space or null character, it is not appended to the - * vector. - */ -void split_by_char(StringRef in_string, const char delimiter, Vector<StringRef> &r_out_list); -/** - * Convert the given string to float and assign it to the destination value. - * - * If the string cannot be converted to a float, the fallback value is used. - */ -void copy_string_to_float(StringRef src, const float fallback_value, float &r_dst); -/** - * Convert all members of the Span of strings to floats and assign them to the float - * array members. Usually used for values like coordinates. - * - * If a string cannot be converted to a float, the fallback value is used. - */ -void copy_string_to_float(Span<StringRef> src, - const float fallback_value, - MutableSpan<float> r_dst); -/** - * Convert the given string to int and assign it to the destination value. - * - * If the string cannot be converted to an integer, the fallback value is used. - */ -void copy_string_to_int(StringRef src, const int fallback_value, int &r_dst); -/** - * Convert the given strings to ints and fill the destination int buffer. - * - * If a string cannot be converted to an integer, the fallback value is used. - */ -void copy_string_to_int(Span<StringRef> src, const int fallback_value, MutableSpan<int> r_dst); -std::string replace_all_occurences(StringRef original, StringRef to_remove, StringRef to_add); - -} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc index 8599b38d55b..f74bfc155dd 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -48,7 +48,6 @@ class obj_exporter_test : public BlendfileLoadingBaseTest { }; const std::string all_objects_file = "io_tests/blend_scene/all_objects.blend"; -const std::string all_curve_objects_file = "io_tests/blend_scene/all_curves.blend"; TEST_F(obj_exporter_test, filter_objects_curves_as_mesh) { @@ -58,7 +57,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_mesh) return; } auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - EXPECT_EQ(objmeshes.size(), 19); + EXPECT_EQ(objmeshes.size(), 20); EXPECT_EQ(objcurves.size(), 0); } @@ -72,7 +71,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_nurbs) _export.params.export_curves_as_nurbs = true; auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; EXPECT_EQ(objmeshes.size(), 18); - EXPECT_EQ(objcurves.size(), 2); + EXPECT_EQ(objcurves.size(), 3); } TEST_F(obj_exporter_test, filter_objects_selected) @@ -111,64 +110,6 @@ TEST(obj_exporter_utils, append_positive_frame_to_filename) EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth)); } -TEST_F(obj_exporter_test, curve_nurbs_points) -{ - if (!load_file_and_depsgraph(all_curve_objects_file)) { - ADD_FAILURE(); - return; - } - - OBJExportParamsDefault _export; - _export.params.export_curves_as_nurbs = true; - auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - - for (auto &objcurve : objcurves) { - if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) { - ADD_FAILURE(); - return; - } - const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get(); - EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines()); - for (int spline_index : IndexRange(objcurve->total_splines())) { - EXPECT_EQ(objcurve->total_spline_vertices(spline_index), - nurbs_truth->total_spline_vertices(spline_index)); - EXPECT_EQ(objcurve->get_nurbs_degree(spline_index), - nurbs_truth->get_nurbs_degree(spline_index)); - EXPECT_EQ(objcurve->total_spline_control_points(spline_index), - nurbs_truth->total_spline_control_points(spline_index)); - } - } -} - -TEST_F(obj_exporter_test, curve_coordinates) -{ - if (!load_file_and_depsgraph(all_curve_objects_file)) { - ADD_FAILURE(); - return; - } - - OBJExportParamsDefault _export; - _export.params.export_curves_as_nurbs = true; - auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - - for (auto &objcurve : objcurves) { - if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) { - ADD_FAILURE(); - return; - } - const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get(); - EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines()); - for (int spline_index : IndexRange(objcurve->total_splines())) { - for (int vertex_index : IndexRange(objcurve->total_spline_vertices(spline_index))) { - EXPECT_V3_NEAR(objcurve->vertex_coordinates( - spline_index, vertex_index, _export.params.scaling_factor), - nurbs_truth->vertex_coordinates(spline_index, vertex_index), - 0.000001f); - } - } - } -} - static std::unique_ptr<OBJWriter> init_writer(const OBJExportParams ¶ms, const std::string out_filepath) { @@ -467,6 +408,19 @@ TEST_F(obj_exporter_regression_test, cube_normal_edit) _export.params); } +TEST_F(obj_exporter_regression_test, cube_vertex_groups) +{ + OBJExportParamsDefault _export; + _export.params.export_materials = false; + _export.params.export_normals = false; + _export.params.export_uv = false; + _export.params.export_vertex_groups = true; + compare_obj_export_to_golden("io_tests/blend_geometry/cube_vertex_groups.blend", + "io_tests/obj/cube_vertex_groups.obj", + "", + _export.params); +} + TEST_F(obj_exporter_regression_test, cubes_positioned) { OBJExportParamsDefault _export; @@ -504,6 +458,25 @@ TEST_F(obj_exporter_regression_test, suzanne_all_data) _export.params); } +TEST_F(obj_exporter_regression_test, all_curves) +{ + OBJExportParamsDefault _export; + _export.params.export_materials = false; + compare_obj_export_to_golden( + "io_tests/blend_scene/all_curves.blend", "io_tests/obj/all_curves.obj", "", _export.params); +} + +TEST_F(obj_exporter_regression_test, all_curves_as_nurbs) +{ + OBJExportParamsDefault _export; + _export.params.export_materials = false; + _export.params.export_curves_as_nurbs = true; + compare_obj_export_to_golden("io_tests/blend_scene/all_curves.blend", + "io_tests/obj/all_curves_as_nurbs.obj", + "", + _export.params); +} + TEST_F(obj_exporter_regression_test, all_objects) { OBJExportParamsDefault _export; diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh index 42660bbbe56..6a821e0b1bf 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh @@ -1,77 +1,11 @@ /* SPDX-License-Identifier: Apache-2.0 */ -/** - * This file contains default values for several items like - * vertex coordinates, export parameters, MTL values etc. - */ - #pragma once -#include <array> -#include <gtest/gtest.h> -#include <string> -#include <vector> - #include "IO_wavefront_obj.h" namespace blender::io::obj { -using array_float_3 = std::array<float, 3>; - -/** - * This matches #OBJCurve's member functions, except that all the numbers and names are known - * constants. Used to store expected values of NURBS curves objects. - */ -class NurbsObject { - private: - std::string nurbs_name_; - /* The indices in these vectors are spline indices. */ - std::vector<std::vector<array_float_3>> coordinates_; - std::vector<int> degrees_; - std::vector<int> control_points_; - - public: - NurbsObject(const std::string nurbs_name, - const std::vector<std::vector<array_float_3>> coordinates, - const std::vector<int> degrees, - const std::vector<int> control_points) - : nurbs_name_(nurbs_name), - coordinates_(coordinates), - degrees_(degrees), - control_points_(control_points) - { - } - - int total_splines() const - { - return coordinates_.size(); - } - - int total_spline_vertices(const int spline_index) const - { - if (spline_index >= coordinates_.size()) { - ADD_FAILURE(); - return 0; - } - return coordinates_[spline_index].size(); - } - - const float *vertex_coordinates(const int spline_index, const int vertex_index) const - { - return coordinates_[spline_index][vertex_index].data(); - } - - int get_nurbs_degree(const int spline_index) const - { - return degrees_[spline_index]; - } - - int total_spline_control_points(const int spline_index) const - { - return control_points_[spline_index]; - } -}; - struct OBJExportParamsDefault { OBJExportParams params; OBJExportParamsDefault() @@ -103,48 +37,4 @@ struct OBJExportParamsDefault { } }; -const std::vector<std::vector<array_float_3>> coordinates_NurbsCurve{ - {{6.94742, 0.000000, 0.000000}, - {7.44742, 0.000000, -1.000000}, - {9.44742, 0.000000, -1.000000}, - {9.94742, 0.000000, 0.000000}}}; -const std::vector<std::vector<array_float_3>> coordinates_NurbsCircle{ - {{11.463165, 0.000000, 1.000000}, - {10.463165, 0.000000, 1.000000}, - {10.463165, 0.000000, 0.000000}, - {10.463165, 0.000000, -1.000000}, - {11.463165, 0.000000, -1.000000}, - {12.463165, 0.000000, -1.000000}, - {12.463165, 0.000000, 0.000000}, - {12.463165, 0.000000, 1.000000}}}; -const std::vector<std::vector<array_float_3>> coordinates_NurbsPathCurve{ - {{13.690557, 0.000000, 0.000000}, - {14.690557, 0.000000, 0.000000}, - {15.690557, 0.000000, 0.000000}, - {16.690557, 0.000000, 0.000000}, - {17.690557, 0.000000, 0.000000}}, - {{14.192808, 0.000000, 0.000000}, - {14.692808, 0.000000, -1.000000}, - {16.692808, 0.000000, -1.000000}, - {17.192808, 0.000000, 0.000000}}}; - -const std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs_truth = []() { - std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs; - all_nurbs.emplace( - "NurbsCurve", - /* Name, coordinates, degrees of splines, control points of splines. */ - std::make_unique<NurbsObject>( - "NurbsCurve", coordinates_NurbsCurve, std::vector<int>{3}, std::vector<int>{4})); - all_nurbs.emplace( - "NurbsCircle", - std::make_unique<NurbsObject>( - "NurbsCircle", coordinates_NurbsCircle, std::vector<int>{3}, std::vector<int>{11})); - /* This is actually an Object containing a NurbsPath and a NurbsCurve spline. */ - all_nurbs.emplace("NurbsPathCurve", - std::make_unique<NurbsObject>("NurbsPathCurve", - coordinates_NurbsPathCurve, - std::vector<int>{3, 3}, - std::vector<int>{5, 4})); - return all_nurbs; -}(); } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc index 9dd9e7c1a37..3d34fb6f9c6 100644 --- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc @@ -60,7 +60,8 @@ class obj_importer_test : public BlendfileLoadingBaseTest { std::string obj_path = blender::tests::flags_test_asset_dir() + "/io_tests/obj/" + path; strncpy(params.filepath, obj_path.c_str(), FILE_MAX - 1); - importer_main(bfile->main, bfile->curscene, bfile->cur_view_layer, params); + const size_t read_buffer_size = 650; + importer_main(bfile->main, bfile->curscene, bfile->cur_view_layer, params, read_buffer_size); depsgraph_create(DAG_EVAL_VIEWPORT); @@ -444,6 +445,7 @@ TEST_F(obj_importer_test, import_all_objects) float3(5, 1, 1), float3(0, 0, 1), float2(0.654526f, 0.579873f)}, + {"OBNurbsCircle.001", OB_MESH, 4, 4, 0, 0, float3(2, -3, 0), float3(3, -2, 0)}, {"OBSurface", OB_MESH, 256, diff --git a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc new file mode 100644 index 00000000000..176d32a0be4 --- /dev/null +++ b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include <gtest/gtest.h> + +#include "testing/testing.h" + +#include "obj_import_file_reader.hh" + +namespace blender::io::obj { + +class obj_mtl_parser_test : public testing::Test { + public: + void check(const char *file, const MTLMaterial *expect, size_t expect_count) + { + std::string obj_dir = blender::tests::flags_test_asset_dir() + "/io_tests/obj/"; + MTLParser parser(file, obj_dir + "dummy.obj"); + Map<std::string, std::unique_ptr<MTLMaterial>> materials; + parser.parse_and_store(materials); + + for (int i = 0; i < expect_count; ++i) { + const MTLMaterial &exp = expect[i]; + if (!materials.contains(exp.name)) { + fprintf(stderr, "Material '%s' was expected in parsed result\n", exp.name.c_str()); + ADD_FAILURE(); + continue; + } + const MTLMaterial &got = *materials.lookup(exp.name); + const float tol = 0.0001f; + EXPECT_V3_NEAR(exp.Ka, got.Ka, tol); + EXPECT_V3_NEAR(exp.Kd, got.Kd, tol); + EXPECT_V3_NEAR(exp.Ks, got.Ks, tol); + EXPECT_V3_NEAR(exp.Ke, got.Ke, tol); + EXPECT_NEAR(exp.Ns, got.Ns, tol); + EXPECT_NEAR(exp.Ni, got.Ni, tol); + EXPECT_NEAR(exp.d, got.d, tol); + EXPECT_NEAR(exp.map_Bump_strength, got.map_Bump_strength, tol); + EXPECT_EQ(exp.illum, got.illum); + for (const auto &it : exp.texture_maps.items()) { + const tex_map_XX &exp_tex = it.value; + const tex_map_XX &got_tex = got.texture_maps.lookup(it.key); + EXPECT_STREQ(exp_tex.image_path.c_str(), got_tex.image_path.c_str()); + EXPECT_V3_NEAR(exp_tex.translation, got_tex.translation, tol); + EXPECT_V3_NEAR(exp_tex.scale, got_tex.scale, tol); + EXPECT_EQ(exp_tex.projection_type, got_tex.projection_type); + } + } + EXPECT_EQ(materials.size(), expect_count); + } +}; + +TEST_F(obj_mtl_parser_test, cube) +{ + MTLMaterial mat; + mat.name = "red"; + mat.Ka = {0.2f, 0.2f, 0.2f}; + mat.Kd = {1, 0, 0}; + check("cube.mtl", &mat, 1); +} + +TEST_F(obj_mtl_parser_test, all_objects) +{ + MTLMaterial mat[7]; + for (auto &m : mat) { + m.Ka = {1, 1, 1}; + m.Ks = {0.5f, 0.5f, 0.5f}; + m.Ke = {0, 0, 0}; + m.Ns = 250; + m.Ni = 1; + m.d = 1; + m.illum = 2; + } + mat[0].name = "Blue"; + mat[0].Kd = {0, 0, 1}; + mat[1].name = "BlueDark"; + mat[1].Kd = {0, 0, 0.5f}; + mat[2].name = "Green"; + mat[2].Kd = {0, 1, 0}; + mat[3].name = "GreenDark"; + mat[3].Kd = {0, 0.5f, 0}; + mat[4].name = "Material"; + mat[4].Kd = {0.8f, 0.8f, 0.8f}; + mat[5].name = "Red"; + mat[5].Kd = {1, 0, 0}; + mat[6].name = "RedDark"; + mat[6].Kd = {0.5f, 0, 0}; + check("all_objects.mtl", mat, ARRAY_SIZE(mat)); +} + +TEST_F(obj_mtl_parser_test, materials) +{ + MTLMaterial mat[5]; + mat[0].name = "no_textures_red"; + mat[0].Ka = {0.3f, 0.3f, 0.3f}; + mat[0].Kd = {0.8f, 0.3f, 0.1f}; + mat[0].Ns = 5.624998f; + + mat[1].name = "four_maps"; + mat[1].Ka = {1, 1, 1}; + mat[1].Kd = {0.8f, 0.8f, 0.8f}; + mat[1].Ks = {0.5f, 0.5f, 0.5f}; + mat[1].Ke = {0, 0, 0}; + mat[1].Ns = 1000; + mat[1].Ni = 1.45f; + mat[1].d = 1; + mat[1].illum = 2; + mat[1].map_Bump_strength = 1; + { + tex_map_XX &kd = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Kd); + kd.image_path = "texture.png"; + tex_map_XX &ns = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Ns); + ns.image_path = "sometexture_Roughness.png"; + tex_map_XX &refl = mat[1].tex_map_of_type(eMTLSyntaxElement::map_refl); + refl.image_path = "sometexture_Metallic.png"; + tex_map_XX &bump = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Bump); + bump.image_path = "sometexture_Normal.png"; + } + + mat[2].name = "Clay"; + mat[2].Ka = {1, 1, 1}; + mat[2].Kd = {0.8f, 0.682657f, 0.536371f}; + mat[2].Ks = {0.5f, 0.5f, 0.5f}; + mat[2].Ke = {0, 0, 0}; + mat[2].Ns = 440.924042f; + mat[2].Ni = 1.45f; + mat[2].d = 1; + mat[2].illum = 2; + + mat[3].name = "Hat"; + mat[3].Ka = {1, 1, 1}; + mat[3].Kd = {0.8f, 0.8f, 0.8f}; + mat[3].Ks = {0.5f, 0.5f, 0.5f}; + mat[3].Ns = 800; + mat[3].map_Bump_strength = 0.5f; + { + tex_map_XX &kd = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Kd); + kd.image_path = "someHatTexture_BaseColor.jpg"; + tex_map_XX &ns = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Ns); + ns.image_path = "someHatTexture_Roughness.jpg"; + tex_map_XX &refl = mat[3].tex_map_of_type(eMTLSyntaxElement::map_refl); + refl.image_path = "someHatTexture_Metalness.jpg"; + tex_map_XX &bump = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Bump); + bump.image_path = "someHatTexture_Normal.jpg"; + } + + mat[4].name = "Parser_Test"; + mat[4].Ka = {0.1f, 0.2f, 0.3f}; + mat[4].Kd = {0.4f, 0.5f, 0.6f}; + mat[4].Ks = {0.7f, 0.8f, 0.9f}; + mat[4].illum = 6; + mat[4].Ns = 15.5; + mat[4].Ni = 1.5; + mat[4].d = 0.5; + mat[4].map_Bump_strength = 0.1f; + { + tex_map_XX &kd = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Kd); + kd.image_path = "sometex_d.png"; + tex_map_XX &ns = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Ns); + ns.image_path = "sometex_ns.psd"; + tex_map_XX &refl = mat[4].tex_map_of_type(eMTLSyntaxElement::map_refl); + refl.image_path = "clouds.tiff"; + refl.scale = {1.5f, 2.5f, 3.5f}; + refl.translation = {4.5f, 5.5f, 6.5f}; + refl.projection_type = SHD_PROJ_SPHERE; + tex_map_XX &bump = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Bump); + bump.image_path = "somebump.tga"; + bump.scale = {3, 4, 5}; + } + + check("materials.mtl", mat, ARRAY_SIZE(mat)); +} + +} // namespace blender::io::obj diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h index 38e99896ab1..332317142c7 100644 --- a/source/blender/makesdna/DNA_material_types.h +++ b/source/blender/makesdna/DNA_material_types.h @@ -31,6 +31,8 @@ typedef struct TexPaintSlot { /** Image to be painted on. Mutual exclusive with attribute_name. */ struct Image *ima; + struct ImageUser *image_user; + /** Custom-data index for uv layer, #MAX_NAME. */ char *uvname; /** diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 0ff9ebb2337..2b4858d4106 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -138,7 +138,11 @@ typedef struct Mesh_Runtime { float (*vert_normals)[3]; float (*poly_normals)[3]; - void *_pad2; + /** + * A #BLI_bitmap containing tags for the center vertices of subdivided polygons, set by the + * subdivision surface modifier and used by drawing code instead of polygon center face dots. + */ + uint32_t *subsurf_face_dot_tags; } Mesh_Runtime; typedef struct Mesh { diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h index 3c734419bfe..2a4234bde6a 100644 --- a/source/blender/makesdna/DNA_meshdata_types.h +++ b/source/blender/makesdna/DNA_meshdata_types.h @@ -33,7 +33,6 @@ typedef struct MVert { enum { /* SELECT = (1 << 0), */ ME_HIDE = (1 << 4), - ME_VERT_FACEDOT = (1 << 5), }; /** diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 9cc4d5ed55b..d6c1040110f 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -939,6 +939,7 @@ typedef struct PaintModeSettings { /** Selected image when canvas_source=PAINT_CANVAS_SOURCE_IMAGE. */ Image *canvas_image; + ImageUser image_user; } PaintModeSettings; diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 838213dd2f3..8d527caa361 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -517,7 +517,7 @@ typedef enum eGraphEdit_Mode { typedef enum eGraphEdit_Runtime_Flag { /** Temporary flag to force channel selections to be synced with main. */ SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC = (1 << 0), - /** Temporary flag to force F-curves to recalculate colors. */ + /** Temporary flag to force F-Curves to recalculate colors. */ SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC_COLOR = (1 << 1), /** diff --git a/source/blender/makesrna/intern/rna_action.c b/source/blender/makesrna/intern/rna_action.c index 11d5298dfd7..76d2087d904 100644 --- a/source/blender/makesrna/intern/rna_action.c +++ b/source/blender/makesrna/intern/rna_action.c @@ -783,7 +783,7 @@ static void rna_def_action_fcurves(BlenderRNA *brna, PropertyRNA *cprop) /* Action.fcurves.remove(...) */ func = RNA_def_function(srna, "remove", "rna_Action_fcurve_remove"); - RNA_def_function_ui_description(func, "Remove action group"); + RNA_def_function_ui_description(func, "Remove F-Curve"); RNA_def_function_flag(func, FUNC_USE_REPORTS); parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "F-Curve to remove"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 8c86e44aebf..5ddc8e97e45 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -5256,7 +5256,7 @@ static void rna_def_userdef_edit(BlenderRNA *brna) prop = RNA_def_property(srna, "use_duplicate_fcurve", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "dupflag", USER_DUP_FCURVE); RNA_def_property_ui_text( - prop, "Duplicate F-Curve", "Causes F-curve data to be duplicated with the object"); + prop, "Duplicate F-Curve", "Causes F-Curve data to be duplicated with the object"); # endif prop = RNA_def_property(srna, "use_duplicate_action", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh index 3ed09de8fab..61d1d11d859 100644 --- a/source/blender/nodes/NOD_node_tree_ref.hh +++ b/source/blender/nodes/NOD_node_tree_ref.hh @@ -65,7 +65,6 @@ class SocketRef : NonCopyable, NonMovable { bool is_input_; int id_; int index_; - PointerRNA rna_; Vector<LinkRef *> directly_linked_links_; /* These sockets are linked directly, i.e. with a single link in between. */ @@ -101,7 +100,7 @@ class SocketRef : NonCopyable, NonMovable { const InputSocketRef &as_input() const; const OutputSocketRef &as_output() const; - PointerRNA *rna() const; + PointerRNA rna() const; StringRefNull idname() const; StringRefNull name() const; @@ -152,7 +151,6 @@ class NodeRef : NonCopyable, NonMovable { private: NodeTreeRef *tree_; bNode *bnode_; - PointerRNA rna_; int id_; Vector<InputSocketRef *> inputs_; Vector<OutputSocketRef *> outputs_; @@ -183,7 +181,7 @@ class NodeRef : NonCopyable, NonMovable { bNode *bnode() const; bNodeTree *btree() const; - PointerRNA *rna() const; + PointerRNA rna() const; StringRefNull idname() const; StringRefNull name() const; StringRefNull label() const; @@ -410,11 +408,6 @@ inline const OutputSocketRef &SocketRef::as_output() const return static_cast<const OutputSocketRef &>(*this); } -inline PointerRNA *SocketRef::rna() const -{ - return const_cast<PointerRNA *>(&rna_); -} - inline StringRefNull SocketRef::idname() const { return bsocket_->idname; @@ -571,11 +564,6 @@ inline bNodeTree *NodeRef::btree() const return tree_->btree(); } -inline PointerRNA *NodeRef::rna() const -{ - return const_cast<PointerRNA *>(&rna_); -} - inline StringRefNull NodeRef::idname() const { return bnode_->idname; 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 e7a8c61290b..903a5e7c1d7 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 @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BKE_spline.hh" +#include "BKE_curves.hh" #include "BKE_curve_to_mesh.hh" @@ -27,17 +27,18 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set, const bool fill_caps) { - const std::unique_ptr<CurveEval> curve = curves_to_curve_eval( - *geometry_set.get_curves_for_read()); + const Curves &curves = *geometry_set.get_curves_for_read(); + const Curves *profile_curves = profile_set.get_curves_for_read(); if (profile_curves == nullptr) { - Mesh *mesh = bke::curve_to_wire_mesh(*curve); + Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry)); geometry_set.replace_mesh(mesh); } else { - const std::unique_ptr<CurveEval> profile_curve = curves_to_curve_eval(*profile_curves); - Mesh *mesh = bke::curve_to_mesh_sweep(*curve, *profile_curve, fill_caps); + Mesh *mesh = bke::curve_to_mesh_sweep(bke::CurvesGeometry::wrap(curves.geometry), + bke::CurvesGeometry::wrap(profile_curves->geometry), + fill_caps); geometry_set.replace_mesh(mesh); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 1e170dd5350..1b26cfe31fe 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -759,6 +759,7 @@ static void duplicate_edges(GeometrySet &geometry_set, MEdge &new_edge = new_edges[edge_range[i_duplicate]]; new_edge.v1 = vert_range[i_duplicate * 2]; new_edge.v2 = vert_range[i_duplicate * 2] + 1; + new_edge.flag = ME_LOOSEEDGE; } } }); diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index c4befd5828c..abbfe4b823d 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -476,7 +476,10 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) /* redirect links from the extension socket */ for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { - nodeAddLink(ntree, node, newsock, link->tonode, link->tosock); + bNodeLink *newlink = nodeAddLink(ntree, node, newsock, link->tonode, link->tosock); + if (newlink->tosock->flag & SOCK_MULTI_INPUT) { + newlink->multi_input_socket_index = link->multi_input_socket_index; + } } } diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index 01a31ab852d..64a8690a869 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -21,7 +21,6 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree) node.tree_ = this; node.bnode_ = bnode; node.id_ = nodes_by_id_.append_and_get_index(&node); - RNA_pointer_create(&btree->id, &RNA_Node, bnode, &node.rna_); LISTBASE_FOREACH (bNodeSocket *, bsocket, &bnode->inputs) { InputSocketRef &socket = *allocator_.construct<InputSocketRef>().release(); @@ -30,7 +29,6 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree) socket.is_input_ = true; socket.bsocket_ = bsocket; socket.id_ = sockets_by_id_.append_and_get_index(&socket); - RNA_pointer_create(&btree->id, &RNA_NodeSocket, bsocket, &socket.rna_); } LISTBASE_FOREACH (bNodeSocket *, bsocket, &bnode->outputs) { @@ -40,7 +38,6 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree) socket.is_input_ = false; socket.bsocket_ = bsocket; socket.id_ = sockets_by_id_.append_and_get_index(&socket); - RNA_pointer_create(&btree->id, &RNA_NodeSocket, bsocket, &socket.rna_); } LISTBASE_FOREACH (bNodeLink *, blink, &bnode->internal_links) { @@ -664,4 +661,18 @@ const NodeTreeRef &get_tree_ref_from_map(NodeTreeRefMap &node_tree_refs, bNodeTr [&]() { return std::make_unique<NodeTreeRef>(&btree); }); } +PointerRNA NodeRef::rna() const +{ + PointerRNA rna; + RNA_pointer_create(&tree_->btree()->id, &RNA_Node, bnode_, &rna); + return rna; +} + +PointerRNA SocketRef::rna() const +{ + PointerRNA rna; + RNA_pointer_create(&this->tree().btree()->id, &RNA_NodeSocket, bsocket_, &rna); + return rna; +} + } // namespace blender::nodes diff --git a/source/blender/nodes/shader/node_shader_tree.cc b/source/blender/nodes/shader/node_shader_tree.cc index 42a31f39041..d917106807c 100644 --- a/source/blender/nodes/shader/node_shader_tree.cc +++ b/source/blender/nodes/shader/node_shader_tree.cc @@ -16,7 +16,7 @@ #include "DNA_workspace_types.h" #include "DNA_world_types.h" -#include "BLI_alloca.h" +#include "BLI_array.hh" #include "BLI_linklist.h" #include "BLI_listbase.h" #include "BLI_threads.h" @@ -48,6 +48,7 @@ #include "node_shader_util.hh" #include "node_util.h" +using blender::Array; using blender::Vector; static bool shader_tree_poll(const bContext *C, bNodeTreeType *UNUSED(treetype)) @@ -570,8 +571,7 @@ static bNode *ntree_shader_copy_branch(bNodeTree *ntree, iter_data.node_count = 1; nodeChainIterBackwards(ntree, start_node, ntree_branch_count_and_tag_nodes, &iter_data, 1); /* Make a full copy of the branch */ - bNode **nodes_copy = static_cast<bNode **>( - MEM_mallocN(sizeof(bNode *) * iter_data.node_count, __func__)); + Array<bNode *> nodes_copy(iter_data.node_count); LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->tmp_flag >= 0) { int id = node->tmp_flag; @@ -604,7 +604,6 @@ static bNode *ntree_shader_copy_branch(bNodeTree *ntree, } } bNode *start_node_copy = nodes_copy[start_node->tmp_flag]; - MEM_freeN(nodes_copy); return start_node_copy; } @@ -705,7 +704,7 @@ static void ntree_shader_weight_tree_invert(bNodeTree *ntree, bNode *output_node int node_count = 1; nodeChainIterBackwards(ntree, output_node, ntree_weight_tree_tag_nodes, &node_count, 0); /* Make a mirror copy of the weight tree. */ - bNode **nodes_copy = static_cast<bNode **>(MEM_mallocN(sizeof(bNode *) * node_count, __func__)); + Array<bNode *> nodes_copy(node_count); LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->tmp_flag >= 0) { int id = node->tmp_flag; @@ -909,8 +908,6 @@ static void ntree_shader_weight_tree_invert(bNodeTree *ntree, bNode *output_node ntree, thickness_link->fromnode, thickness_link->fromsock, output_node, thickness_output); } BKE_ntree_update_main_tree(G.main, ntree, nullptr); - - MEM_freeN(nodes_copy); } static bool closure_node_filter(const bNode *node) diff --git a/source/blender/sequencer/SEQ_animation.h b/source/blender/sequencer/SEQ_animation.h index f2c66393b65..b8f74e5a510 100644 --- a/source/blender/sequencer/SEQ_animation.h +++ b/source/blender/sequencer/SEQ_animation.h @@ -20,15 +20,15 @@ void SEQ_free_animdata(struct Scene *scene, struct Sequence *seq); void SEQ_offset_animdata(struct Scene *scene, struct Sequence *seq, int ofs); struct GSet *SEQ_fcurves_by_strip_get(const struct Sequence *seq, struct ListBase *fcurve_base); /** - * Move all `F-curves` from `scene` to `list`. + * Move all `F-Curves` from `scene` to `list`. */ void SEQ_animation_backup_original(struct Scene *scene, struct ListBase *list); /** - * Move all `F-curves` from `list` to `scene`. + * Move all `F-Curves` from `list` to `scene`. */ void SEQ_animation_restore_original(struct Scene *scene, struct ListBase *list); /** - * Duplicate `F-curves` used by `seq` from `list` to `scene`. + * Duplicate `F-Curves` used by `seq` from `list` to `scene`. */ void SEQ_animation_duplicate(struct Scene *scene, struct Sequence *seq, struct ListBase *list); diff --git a/source/blender/sequencer/intern/strip_edit.c b/source/blender/sequencer/intern/strip_edit.c index d678518e3b0..7aa81f5ae8a 100644 --- a/source/blender/sequencer/intern/strip_edit.c +++ b/source/blender/sequencer/intern/strip_edit.c @@ -461,7 +461,7 @@ Sequence *SEQ_edit_strip_split(Main *bmain, return NULL; } - /* Store `F-curves`, so original ones aren't renamed. */ + /* Store `F-Curves`, so original ones aren't renamed. */ ListBase fcurves_original_backup = {NULL, NULL}; SEQ_animation_backup_original(scene, &fcurves_original_backup); diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c index 3c8474b1b6c..4b506564260 100644 --- a/source/blender/windowmanager/intern/wm_draw.c +++ b/source/blender/windowmanager/intern/wm_draw.c @@ -24,6 +24,7 @@ #include "BLI_utildefines.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_image.h" #include "BKE_main.h" #include "BKE_scene.h" @@ -459,13 +460,24 @@ static void wm_draw_region_buffer_create(ARegion *region, bool stereo, bool use_ } } -static void wm_draw_region_bind(ARegion *region, int view) +static bool wm_draw_region_bind(bContext *C, ARegion *region, int view) { if (!region->draw_buffer) { - return; + return true; } if (region->draw_buffer->viewport) { + if (G.is_rendering && C != NULL) { + Scene *scene = CTX_data_scene(C); + RenderEngineType *render_engine_type = RE_engines_find(scene->r.engine); + if (RE_engine_is_opengl(render_engine_type)) { + /* Do not try to acquire the viewport as this would be locking at the moment. + * But tag the viewport to update after the rendering finishes. */ + GPU_viewport_tag_update(region->draw_buffer->viewport); + return false; + } + } + GPU_viewport_bind(region->draw_buffer->viewport, view, ®ion->winrct); } else { @@ -478,6 +490,7 @@ static void wm_draw_region_bind(ARegion *region, int view) } region->draw_buffer->bound_view = view; + return true; } static void wm_draw_region_unbind(ARegion *region) @@ -700,9 +713,10 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) wm_draw_region_stereo_set(bmain, area, region, sview); } - wm_draw_region_bind(region, view); - ED_region_do_draw(C, region); - wm_draw_region_unbind(region); + if (wm_draw_region_bind(C, region, view)) { + ED_region_do_draw(C, region); + wm_draw_region_unbind(region); + } } if (use_viewport) { GPUViewport *viewport = region->draw_buffer->viewport; @@ -711,9 +725,10 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) } else { wm_draw_region_buffer_create(region, false, use_viewport); - wm_draw_region_bind(region, 0); - ED_region_do_draw(C, region); - wm_draw_region_unbind(region); + if (wm_draw_region_bind(C, region, 0)) { + ED_region_do_draw(C, region); + wm_draw_region_unbind(region); + } } GPU_debug_group_end(); @@ -744,10 +759,11 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) } wm_draw_region_buffer_create(region, false, false); - wm_draw_region_bind(region, 0); - GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f); - ED_region_do_draw(C, region); - wm_draw_region_unbind(region); + if (wm_draw_region_bind(C, region, 0)) { + GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f); + ED_region_do_draw(C, region); + wm_draw_region_unbind(region); + } GPU_debug_group_end(); @@ -1102,10 +1118,11 @@ void wm_draw_region_test(bContext *C, ScrArea *area, ARegion *region) /* Function for redraw timer benchmark. */ bool use_viewport = WM_region_use_viewport(area, region); wm_draw_region_buffer_create(region, false, use_viewport); - wm_draw_region_bind(region, 0); - ED_region_do_draw(C, region); - wm_draw_region_unbind(region); - region->do_draw = false; + if (wm_draw_region_bind(C, region, 0)) { + ED_region_do_draw(C, region); + wm_draw_region_unbind(region); + region->do_draw = false; + } } void WM_redraw_windows(bContext *C) @@ -1141,7 +1158,7 @@ void WM_draw_region_viewport_ensure(ARegion *region, short space_type) void WM_draw_region_viewport_bind(ARegion *region) { - wm_draw_region_bind(region, 0); + wm_draw_region_bind(NULL, region, 0); } void WM_draw_region_viewport_unbind(ARegion *region) diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 89bb6906a22..382a37e09e5 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -1541,7 +1541,15 @@ void wm_ghost_init(bContext *C) } g_system = GHOST_CreateSystem(); - GHOST_SystemInitDebug(g_system, G.debug & G_DEBUG_GHOST); + + GHOST_Debug debug = {0}; + if (G.debug & G_DEBUG_GHOST) { + debug.flags |= GHOST_kDebugDefault; + } + if (G.debug & G_DEBUG_WINTAB) { + debug.flags |= GHOST_kDebugWintab; + } + GHOST_SystemInitDebug(g_system, debug); if (C != NULL) { GHOST_AddEventConsumer(g_system, consumer); diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c index 05b7f1bcb85..b3f5d24ee8c 100644 --- a/source/creator/creator_args.c +++ b/source/creator/creator_args.c @@ -576,6 +576,7 @@ static int arg_handle_print_help(int UNUSED(argc), const char **UNUSED(argv), vo BLI_args_print_arg_doc(ba, "--debug-depsgraph-pretty"); BLI_args_print_arg_doc(ba, "--debug-depsgraph-uuid"); BLI_args_print_arg_doc(ba, "--debug-ghost"); + BLI_args_print_arg_doc(ba, "--debug-wintab"); BLI_args_print_arg_doc(ba, "--debug-gpu"); BLI_args_print_arg_doc(ba, "--debug-gpu-force-workarounds"); BLI_args_print_arg_doc(ba, "--debug-wm"); @@ -943,6 +944,12 @@ static const char arg_handle_debug_mode_generic_set_doc_wm[] = "\n\t" "Enable debug messages for the window manager, shows all operators in search, shows " "keymap errors."; +static const char arg_handle_debug_mode_generic_set_doc_ghost[] = + "\n\t" + "Enable debug messages for Ghost (Linux only)."; +static const char arg_handle_debug_mode_generic_set_doc_wintab[] = + "\n\t" + "Enable debug messages for Wintab."; # ifdef WITH_XR_OPENXR static const char arg_handle_debug_mode_generic_set_doc_xr[] = "\n\t" @@ -2130,8 +2137,13 @@ void main_args_setup(bContext *C, bArgs *ba) BLI_args_add(ba, NULL, "--debug-ghost", - CB_EX(arg_handle_debug_mode_generic_set, handlers), + CB_EX(arg_handle_debug_mode_generic_set, ghost), (void *)G_DEBUG_GHOST); + BLI_args_add(ba, + NULL, + "--debug-wintab", + CB_EX(arg_handle_debug_mode_generic_set, wintab), + (void *)G_DEBUG_WINTAB); BLI_args_add(ba, NULL, "--debug-all", CB(arg_handle_debug_mode_all), NULL); BLI_args_add(ba, NULL, "--debug-io", CB(arg_handle_debug_mode_io), NULL); diff --git a/source/tools b/source/tools -Subproject 1e658ca996f11e5ff3398d89bd81f5b719304a5 +Subproject 4c1e01e3e309282beb1af3b1eddb2c7f9a666b5 diff --git a/tests/performance/api/config.py b/tests/performance/api/config.py index 03d699cfdfb..6d095065123 100644 --- a/tests/performance/api/config.py +++ b/tests/performance/api/config.py @@ -68,11 +68,14 @@ class TestQueue: def rows(self, use_revision_columns: bool) -> List: # Generate rows of entries for printing and running. - entries = sorted(self.entries, key=lambda entry: - (entry.revision, - entry.device_id, - entry.category, - entry.test)) + entries = sorted( + self.entries, + key=lambda entry: ( + entry.revision, + entry.device_id, + entry.category, + entry.test, + )) if not use_revision_columns: # One entry per row. diff --git a/tests/performance/api/environment.py b/tests/performance/api/environment.py index 61a7c5dff7b..1e2e4a84e81 100644 --- a/tests/performance/api/environment.py +++ b/tests/performance/api/environment.py @@ -32,7 +32,7 @@ class TestEnvironment: self._init_default_blender_executable() self.set_default_blender_executable() - def get_machine(self, need_gpus: bool=True) -> None: + def get_machine(self, need_gpus: bool = True) -> None: if not self.machine or (need_gpus and not self.machine.has_gpus): self.machine = TestMachine(self, need_gpus) @@ -61,7 +61,8 @@ class TestEnvironment: if not self.blender_dir.exists(): print(f'Init git worktree in {self.blender_dir}') - self.call([self.git_executable, 'worktree', 'add', '--detach', self.blender_dir, 'HEAD'], self.blender_git_dir) + self.call([self.git_executable, 'worktree', 'add', '--detach', + self.blender_dir, 'HEAD'], self.blender_git_dir) else: print(f'Exists {self.blender_dir}') @@ -165,7 +166,7 @@ class TestEnvironment: def unset_log_file(self) -> None: self.log_file = None - def call(self, args: List[str], cwd: pathlib.Path, silent: bool=False, environment: Dict={}) -> 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. @@ -220,7 +221,7 @@ class TestEnvironment: def run_in_blender(self, function: Callable[[Dict], Dict], args: Dict, - blender_args: List=[], + blender_args: List = [], foreground=False) -> Dict: # Run function in a Blender instance. Arguments and return values are # passed as a Python object that must be serializable with pickle. @@ -274,7 +275,7 @@ class TestEnvironment: return names - def get_configs(self, name: str=None, names_only: bool=False) -> List: + def get_configs(self, name: str = None, names_only: bool = False) -> List: # Get list of configurations in the benchmarks directory. configs = [] diff --git a/tests/performance/api/test.py b/tests/performance/api/test.py index 72e72463cd1..d3ec092f78d 100644 --- a/tests/performance/api/test.py +++ b/tests/performance/api/test.py @@ -32,7 +32,7 @@ class Test: class TestCollection: - def __init__(self, env, names_filter: List=['*'], categories_filter: List=['*']): + def __init__(self, env, names_filter: List = ['*'], categories_filter: List = ['*']): import importlib import pkgutil import tests diff --git a/tests/python/bevel_operator.py b/tests/python/bevel_operator.py index 64cf034bb85..d6d7ade944f 100644 --- a/tests/python/bevel_operator.py +++ b/tests/python/bevel_operator.py @@ -19,265 +19,265 @@ def main(): # 0 SpecMeshTest('Cube_test_1', 'Cube_test', 'Cube_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.2}, 'EDGE', {10})]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2}, 'EDGE', {10})]), SpecMeshTest('Cube_test_2', 'Cube_test', 'Cube_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'offset_type': 'WIDTH'}, 'EDGE', {10, 7}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'offset_type': 'WIDTH'}, 'EDGE', {10, 7}, )]), SpecMeshTest('Cube_test_3', 'Cube_test', 'Cube_result_3', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'offset_type': 'DEPTH'}, 'EDGE', {8, 10, 7}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'offset_type': 'DEPTH'}, 'EDGE', {8, 10, 7}, )]), SpecMeshTest('Cube_test_4', 'Cube_test', 'Cube_result_4', - [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 2}, 'EDGE', {10}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 2}, 'EDGE', {10}, )]), SpecMeshTest('Cube_test_5', 'Cube_test', 'Cube_result_5', - [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 3}, 'EDGE', {10, 7}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 3}, 'EDGE', {10, 7}, )]), # 5 SpecMeshTest('Cube_test_6', 'Cube_test', 'Cube_result_6', - [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 4}, 'EDGE', {8, 10, 7}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 4}, 'EDGE', {8, 10, 7}, )]), SpecMeshTest('Cube_test_7', 'Cube_test', 'Cube_result_7', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 5, 'profile': 0.2}, 'EDGE', {0, 10, 4, 7}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 5, 'profile': 0.2}, 'EDGE', {0, 10, 4, 7}, )]), SpecMeshTest('Cube_test_8', 'Cube_test', 'Cube_result_8', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 5, 'profile': 0.25}, 'EDGE', {8, 10, 7}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 5, 'profile': 0.25}, 'EDGE', {8, 10, 7}, )]), SpecMeshTest('Cube_test_9', 'Cube_test', 'Cube_result_9', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 6, 'profile': 0.9}, 'EDGE', {8, 10, 7}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 6, 'profile': 0.9}, 'EDGE', {8, 10, 7}, )]), SpecMeshTest('Cube_test_10', 'Cube_test', 'Cube_result_10', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 4, 'profile': 1.0}, 'EDGE', {10, 7}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 4, 'profile': 1.0}, 'EDGE', {10, 7}, )]), # 10 SpecMeshTest('Cube_test_11', 'Cube_test', 'Cube_result_11', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 5, 'profile': 1.0}, 'EDGE', {8, 10, 7}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 5, 'profile': 1.0}, 'EDGE', {8, 10, 7}, )]), SpecMeshTest("test 12", 'Cube_test', 'Cube_result_12', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 8}, 'EDGE', - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 8}, 'EDGE', + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]), SpecMeshTest('Pyramid4_test_1', 'Pyr4_test', 'Pyr4_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {5}, )]), SpecMeshTest('Pyramid4_test_2', 'Pyr4_test', 'Pyr4_result_2', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 5}, )]), SpecMeshTest('Pyramid4_test_3', 'Pyr4_test', 'Pyr4_result_3', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3, 5}, )]), # 15 SpecMeshTest('Pyramid4_test_4', 'Pyr4_test', 'Pyr4_result_4', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {1, 2, 3, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {1, 2, 3, 5}, )]), SpecMeshTest('Pyramid4_test_5', 'Pyr4_test', 'Pyr4_result_5', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 2, 3, 5}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 2, 3, 5}, )]), SpecMeshTest('Pyramid4_test_6', 'Pyr4_test', 'Pyr4_result_6', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {2, 3}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {2, 3}, )]), SpecMeshTest('Pyramid4_test_7', 'Pyr4_test', 'Pyr4_result_7', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 4, 'profile': 0.15}, 'EDGE', {1, 2, 3, 5}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 4, 'profile': 0.15}, 'EDGE', {1, 2, 3, 5}, )]), SpecMeshTest('Pyramid4_test_8', 'Pyr4_test', 'Pyr4_result_8', - [OperatorSpecEditMode('bevel', - {'offset': 0.75, 'segments': 4, 'affect': 'VERTICES'}, 'VERT', {1}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.75, 'segments': 4, 'affect': 'VERTICES'}, 'VERT', {1}, )]), # 20 SpecMeshTest('Pyramid4_test_9', 'Pyr4_test', 'Pyr4_result_9', - [OperatorSpecEditMode('bevel', - {'offset': 0.75, 'segments': 3, 'affect': 'VERTICES', 'profile': 0.25}, 'VERT', - {1}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.75, 'segments': 3, 'affect': 'VERTICES', 'profile': 0.25}, 'VERT', + {1}, )]), SpecMeshTest('Pyramid6_test_1', 'Pyr6_test', 'Pyr6_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3}, )]), SpecMeshTest('Pyramid6_test_2', 'Pyr6_test', 'Pyr6_result_2', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {8, 2, 3}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {8, 2, 3}, )]), SpecMeshTest('Pyramid6_test_3', 'Pyr6_test', 'Pyr6_result_3', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 4, 'profile': 0.8}, 'EDGE', - {0, 2, 3, 4, 6, 7, 9, 10, 11}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 4, 'profile': 0.8}, 'EDGE', + {0, 2, 3, 4, 6, 7, 9, 10, 11}, )]), SpecMeshTest('Sept_test_1', 'Sept_test', 'Sept_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.1}, 'EDGE', {8, 9, 3, 11}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.1}, 'EDGE', {8, 9, 3, 11}, )]), # 25 SpecMeshTest('Sept_test_2', 'Sept_test', 'Sept_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.1, 'offset_type': 'WIDTH'}, 'EDGE', {8, 9, 11}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.1, 'offset_type': 'WIDTH'}, 'EDGE', {8, 9, 11}, )]), SpecMeshTest('Saddle_test_1', 'Saddle_test', 'Saddle_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.3, 'segments': 5}, 'EDGE', {2, 8, 9, 12, 13, 14}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.3, 'segments': 5}, 'EDGE', {2, 8, 9, 12, 13, 14}, )]), SpecMeshTest('Saddle_test_2', 'Saddle_test', 'Saddle_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.6, 'segments': 6, 'affect': 'VERTICES'}, 'VERT', {4}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.6, 'segments': 6, 'affect': 'VERTICES'}, 'VERT', {4}, )]), SpecMeshTest('Bent_test', 'Bent_test', 'Bent_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, - 'EDGE', - {2, 5, 8, 11, 14, 18, 21, 24, 27, 30, 34, 37, 40, 43, 46, 50, 53, 56, 59, 62, - 112, 113, 114, 115}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, + 'EDGE', + {2, 5, 8, 11, 14, 18, 21, 24, 27, 30, 34, 37, 40, 43, 46, 50, 53, 56, 59, 62, + 112, 113, 114, 115}, )]), SpecMeshTest('Bentlines_test_1', 'Bentlines_test', 'Bentlines_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 8, 9, 10, 11}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 8, 9, 10, 11}, )]), # 30 SpecMeshTest('Flaretop_test_1', 'Flaretop_test', 'Flaretop_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 2}, 'EDGE', {26, 12, 20}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 2}, 'EDGE', {26, 12, 20}, )]), SpecMeshTest('Flaretop_test_2', 'Flaretop_test', 'Flaretop_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 2, 'profile': 1.0}, 'EDGE', {26, 12, 20}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 2, 'profile': 1.0}, 'EDGE', {26, 12, 20}, )]), SpecMeshTest('Flaretop_test_3', 'Flaretop_test', 'Flaretop_result_3', - [OperatorSpecEditMode('bevel', - {'offset': 0.4, 'segments': 4}, 'FACE', {1, 6, 7, 8, 9, 10, 11, 12}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.4, 'segments': 4}, 'FACE', {1, 6, 7, 8, 9, 10, 11, 12}, )]), SpecMeshTest('BentL_test', 'BentL_test', 'BentL_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {4, 8, 10, 18, 24}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {4, 8, 10, 18, 24}, )]), SpecMeshTest('Wires_test_1', 'Wires_test', 'Wires_test_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.3}, 'EDGE', {0, 1, 2, 10}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.3}, 'EDGE', {0, 1, 2, 10}, )]), # 35 SpecMeshTest('Wires_test_2', 'Wires_test', 'Wires_test_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.3, 'affect': 'VERTICES'}, 'VERT', - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.3, 'affect': 'VERTICES'}, 'VERT', + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, )]), SpecMeshTest('tri_test_1', 'tri', 'tri_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri_test_2', 'tri', 'tri_result_2', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri_test_3', 'tri', 'tri_result_3', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri_test_4', 'tri', 'tri_result_4', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]), # 40 SpecMeshTest('tri_test_5', 'tri', 'tri_result_5', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]), SpecMeshTest('tri_test_6', 'tri', 'tri_result_6', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]), SpecMeshTest('tri_test_7', 'tri', 'tri_result_7', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2, 'affect': 'VERTICES'}, 'VERT', {3}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2, 'affect': 'VERTICES'}, 'VERT', {3}, )]), SpecMeshTest('tri_test_8', 'tri', 'tri_result_8', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3, 'affect': 'VERTICES'}, 'VERT', {3}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3, 'affect': 'VERTICES'}, 'VERT', {3}, )]), SpecMeshTest('tri_test_9', 'tri', 'tri_result_9', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {1}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {1}, )]), # 45 SpecMeshTest('tri1gap_test_2', 'tri1gap', 'tri1gap_result_2', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri1gap_test_3', 'tri1gap', 'tri1gap_result_3', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri1gap_test_1', 'tri1gap', 'tri1gap_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri1gap_test_4', 'tri1gap', 'tri1gap_result_4', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]), SpecMeshTest('tri1gap_test_5', 'tri1gap', 'tri1gap_result_5', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]), # 50 SpecMeshTest('tri1gap_test_6', 'tri1gap', 'tri1gap_result_6', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]), SpecMeshTest('tri1gap_test_7', 'tri1gap', 'tri1gap_result_7', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 5}, )]), SpecMeshTest('tri1gap_test_8', 'tri1gap', 'tri1gap_result_8', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 5}, )]), SpecMeshTest('tri1gap_test_9', 'tri1gap', 'tri1gap_result_9', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 5}, )]), SpecMeshTest('tri1gap_test_10', 'tri1gap', 'tri1gap_result_10', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]), # 55 SpecMeshTest('tri2gaps_test_1', 'tri2gaps', 'tri2gaps_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri2gaps_test_2', 'tri2gaps', 'tri2gaps_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri2gaps_test_3', 'tri2gaps', 'tri2gaps_result_3', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri2gaps_test_4', 'tri2gaps', 'tri2gaps_result_4', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]), SpecMeshTest('tri2gaps_test_5', 'tri2gaps', 'tri2gaps_result_5', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]), # 60 SpecMeshTest('tri2gaps_test_6', 'tri2gaps', 'tri2gaps_result_6', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]), SpecMeshTest('tri3gaps_test_1', 'tri3gaps', 'tri3gaps_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri3gaps_test_2', 'tri3gaps', 'tri3gaps_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('tri3gaps_test_3', 'tri3gaps', 'tri3gaps_result_3', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]), SpecMeshTest('cube3_test_1', 'cube3', 'cube3_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.2}, 'EDGE', {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2}, 'EDGE', {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]), # 65 SpecMeshTest('cube3_test_2', 'cube3', 'cube3_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2}, 'EDGE', - {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2}, 'EDGE', + {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]), SpecMeshTest('cube3_test_3', 'cube3', 'cube3_result_3', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {32, 35}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {32, 35}, )]), SpecMeshTest('cube3_test_4', 'cube3', 'cube3_result_4', - [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {24, 35}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {24, 35}, )]), SpecMeshTest('cube3_test_5', 'cube3', 'cube3_result_5', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {24, 32, 35}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {24, 32, 35}, )]), SpecMeshTest('cube3_test_6', 'cube3', 'cube3_result_6', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {24, 32, 35}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {24, 32, 35}, )]), # 70 SpecMeshTest('Tray', 'Tray', 'Tray_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.01, 'segments': 2}, 'EDGE', {0, 1, 6, 7, 12, 14, 16, 17}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.01, 'segments': 2}, 'EDGE', {0, 1, 6, 7, 12, 14, 16, 17}, )]), SpecMeshTest("test 73", 'Bumptop', 'Bumptop_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.1, 'segments': 4}, 'EDGE', - {33, 4, 38, 8, 41, 10, 42, 12, 14, 17, 24, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.1, 'segments': 4}, 'EDGE', + {33, 4, 38, 8, 41, 10, 42, 12, 14, 17, 24, 31}, )]), SpecMeshTest('Multisegment_test_1', 'Multisegment_test', 'Multisegment_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.2}, 'EDGE', {16, 14, 15}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2}, 'EDGE', {16, 14, 15}, )]), SpecMeshTest('Window_test', 'Window_test', 'Window_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.05, 'segments': 2}, 'EDGE', {19, 20, 23, 15}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.05, 'segments': 2}, 'EDGE', {19, 20, 23, 15}, )]), # 75 SpecMeshTest("test 77", 'Cube_hn_test', 'Cube_hn_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'harden_normals': True}, 'EDGE', {8}, )]), + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'harden_normals': True}, 'EDGE', {8}, )]), SpecMeshTest('Blocksteps_test_1', 'Blocksteps_test', 'Blocksteps_result_1', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'miter_outer': 'PATCH'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'miter_outer': 'PATCH'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps_test_2', 'Blocksteps_test', 'Blocksteps_result_2', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps_test_3', 'Blocksteps_test', 'Blocksteps_result_3', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3, 'miter_outer': 'PATCH'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3, 'miter_outer': 'PATCH'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps_test_4', 'Blocksteps_test', 'Blocksteps_result_4', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'miter_outer': 'ARC'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'miter_outer': 'ARC'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]), # 80 SpecMeshTest('Blocksteps_test_5', 'Blocksteps_test', 'Blocksteps_result_5', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps_test_6', 'Blocksteps_test', 'Blocksteps_result_6', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps_test_7', 'Blocksteps_test', 'Blocksteps_result_7', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest("Blocksteps_test_8", 'Blocksteps_test', 'Blocksteps_result_8', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}, - 'EDGE', {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}, + 'EDGE', {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps2_test', 'Blocksteps2_test', 'Blocksteps2_result_9', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), # 85 SpecMeshTest('Blocksteps3_test', 'Blocksteps3_test', 'Blocksteps3_result_10', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps4_test_1', 'Blocksteps4_test', 'Blocksteps4_result_11', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Blocksteps4_test_2', 'Blocksteps4_test', 'Blocksteps4_result_12', - [OperatorSpecEditMode('bevel', - {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE', - {4, 7, 39, 27, 30, 31}, )]), + [OperatorSpecEditMode('bevel', + {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE', + {4, 7, 39, 27, 30, 31}, )]), SpecMeshTest('Spike_test', 'Spike_test', 'Spike_result_1', - [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 7})]) + [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 7})]) ] operator_test = RunTest(tests) diff --git a/tests/python/bl_animation_fcurves.py b/tests/python/bl_animation_fcurves.py index b93c201dc08..449f17ebfec 100644 --- a/tests/python/bl_animation_fcurves.py +++ b/tests/python/bl_animation_fcurves.py @@ -72,7 +72,7 @@ class EulerFilterTest(AbstractAnimationTest, unittest.TestCase): self.activate_object('Three-Channel-Jump') fcu_rot = self.active_object_rotation_channels() - ## Check some pre-filter values to make sure the file is as we expect. + # # Check some pre-filter values to make sure the file is as we expect. # Keyframes before the "jump". These shouldn't be touched by the filter. self.assertEqualAngle(-87.5742, fcu_rot[0], 22) self.assertEqualAngle(69.1701, fcu_rot[1], 22) @@ -99,7 +99,7 @@ class EulerFilterTest(AbstractAnimationTest, unittest.TestCase): self.activate_object('One-Channel-Jumps') fcu_rot = self.active_object_rotation_channels() - ## Check some pre-filter values to make sure the file is as we expect. + # # Check some pre-filter values to make sure the file is as we expect. # Keyframes before the "jump". These shouldn't be touched by the filter. self.assertEqualAngle(360, fcu_rot[0], 15) self.assertEqualAngle(396, fcu_rot[1], 21) # X and Y are keyed on different frames. diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py index 9a6aa9c9e10..120afba4911 100644 --- a/tests/python/bl_blendfile_liblink.py +++ b/tests/python/bl_blendfile_liblink.py @@ -204,7 +204,12 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=False, do_reuse_local_id=False) - print(bpy.data.materials[:], bpy.data.materials[0].library, bpy.data.materials[0].users, bpy.data.materials[0].use_fake_user) + print( + bpy.data.materials[:], + bpy.data.materials[0].library, + bpy.data.materials[0].users, + bpy.data.materials[0].use_fake_user, + ) assert(len(bpy.data.materials) == 1) assert(bpy.data.materials[0].library is not None) @@ -400,7 +405,6 @@ class TestBlendLibLibraryReload(TestBlendLibLinkHelper): assert(orig_data == reload_data) - class TestBlendLibLibraryRelocate(TestBlendLibLinkHelper): def __init__(self, args): diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py index 6d9530e771e..1acc1e4d862 100644 --- a/tests/python/bl_blendfile_library_overrides.py +++ b/tests/python/bl_blendfile_library_overrides.py @@ -59,7 +59,7 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase): self.assertIsNone(local_id.data.override_library) assert(len(local_id.override_library.properties) == 0) - ##### Generate an override property & operation automatically by editing the local override data. + # #### Generate an override property & operation automatically by editing the local override data. local_id.location.y = 1.0 local_id.override_library.operations_update() assert(len(local_id.override_library.properties) == 1) @@ -71,12 +71,12 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase): # Setting location.y overrode all elements in the location array. -1 is a wildcard. assert(override_operation.subitem_local_index == -1) - ##### Reset the override to its linked reference data. + # #### Reset the override to its linked reference data. local_id.override_library.reset() assert(len(local_id.override_library.properties) == 0) assert(local_id.location == local_id.override_library.reference.location) - ##### Generate an override property & operation manually using the API. + # #### Generate an override property & operation manually using the API. override_property = local_id.override_library.properties.add(rna_path="location") override_property.operations.add(operation='REPLACE') @@ -95,11 +95,11 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase): assert(len(local_id.override_library.properties) == 0) - ##### Delete the override. + # #### Delete the override. local_id_name = local_id.name assert(bpy.data.objects.get((local_id_name, None), None) == local_id) local_id.override_library.destroy() - assert(bpy.data.objects.get((local_id_name, None), None) == None) + assert(bpy.data.objects.get((local_id_name, None), None) is None) def test_link_permissive(self): """ diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py index 1743893dc8a..b87eed0c0df 100644 --- a/tests/python/bl_keymap_validate.py +++ b/tests/python/bl_keymap_validate.py @@ -72,6 +72,7 @@ ALLOW_DUPLICATES = { # ----------------------------------------------------------------------------- # Generic Utilities + @contextlib.contextmanager def temp_fn_argument_extractor( mod: types.ModuleType, @@ -200,7 +201,7 @@ def keyconfig_config_as_filename_component(values: Sequence[Tuple[str, Any]]) -> return "(" + quote( ".".join([ "-".join((str(key), str(val))) - for key, val in values + for key, val in values ]), # Needed so forward slashes aren't included in the resulting name. safe="", diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py index ac1bd618570..ddb5be03594 100644 --- a/tests/python/bl_pyapi_idprop.py +++ b/tests/python/bl_pyapi_idprop.py @@ -12,6 +12,7 @@ try: except ImportError: np = None + class TestHelper: @property @@ -179,7 +180,6 @@ class TestIdPropertyGroupView(TestHelper, unittest.TestCase): self.assertEqual(len(group), len(text)) self.assertEqual(list(iter(group)), text) - def test_contains(self): # Check `idprop.types.IDPropertyGroupView{Keys/Values/Items}.__contains__` text = ["A", "B", "C"] diff --git a/tests/python/bl_rigging_symmetrize.py b/tests/python/bl_rigging_symmetrize.py index dcd0ab65ced..3a5a0777d6c 100644 --- a/tests/python/bl_rigging_symmetrize.py +++ b/tests/python/bl_rigging_symmetrize.py @@ -98,7 +98,7 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone): # Make sure that the constraint exists self.assertTrue(const_name in bone.constraints, "Bone %s is expected to contain constraint %s, but it does not." % ( - bone.name, const_name)) + bone.name, const_name)) constraint = bone.constraints[const_name] const_variables = constraint.bl_rna.properties.keys() @@ -119,7 +119,7 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone): if isinstance(value, str): self.assertEqual(value, exp_value, "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % ( - bone.name, const_name, var)) + bone.name, const_name, var)) elif hasattr(value, "name"): # Some constraints targets the armature itself, so the armature name should missmatch. if value.name == input_arm.name and exp_value.name == expected_arm.name: @@ -127,16 +127,16 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone): self.assertEqual(value.name, exp_value.name, "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % ( - bone.name, const_name, var)) + bone.name, const_name, var)) elif isinstance(value, bool): self.assertEqual(value, exp_value, - "Missmatching constraint boolean in pose.bones[%s].constraints[%s].%s" % ( - bone.name, const_name, var)) + "Missmatching constraint boolean in pose.bones[%s].constraints[%s].%s" % ( + bone.name, const_name, var)) else: self.assertAlmostEqual(value, exp_value, "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % ( - bone.name, const_name, var)) + bone.name, const_name, var)) class AbstractAnimationTest: diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index fc2fc819c8d..1ba9b4f1edf 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -10,6 +10,7 @@ import bpy args = None + class AbstractUSDTest(unittest.TestCase): @classmethod def setUpClass(cls): @@ -22,6 +23,7 @@ class AbstractUSDTest(unittest.TestCase): # Make sure we always start with a known-empty file. bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend")) + class USDImportTest(AbstractUSDTest): def test_import_prim_hierarchy(self): @@ -42,6 +44,7 @@ class USDImportTest(AbstractUSDTest): self.assertEqual(objects['World'], objects['Empty'].parent) self.assertEqual(objects['Empty'], objects['Plane_002'].parent) + def main(): global args import argparse diff --git a/tests/python/boolean_operator.py b/tests/python/boolean_operator.py index afad48fad36..fed0b2bddfd 100644 --- a/tests/python/boolean_operator.py +++ b/tests/python/boolean_operator.py @@ -20,31 +20,31 @@ def main(): tests = [ SpecMeshTest('Cubecube_intersect_union', 'Cubecube', 'Cubecube_result_1', - [OperatorSpecEditMode('intersect_boolean', - {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), + [OperatorSpecEditMode('intersect_boolean', + {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), SpecMeshTest('Cubecube_intersect_intersect', 'Cubecube', 'Cubecube_result_2', - [OperatorSpecEditMode('intersect_boolean', {'operation': 'INTERSECT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), + [OperatorSpecEditMode('intersect_boolean', {'operation': 'INTERSECT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), SpecMeshTest('Cubecube_intersect_difference', 'Cubecube', 'Cubecube_result_3', - [OperatorSpecEditMode('intersect_boolean', {'operation': 'DIFFERENCE', 'solver': 'FAST'}, 'FACE', - {0, 1, 2, 3, 4, 5}, )]), + [OperatorSpecEditMode('intersect_boolean', {'operation': 'DIFFERENCE', 'solver': 'FAST'}, 'FACE', + {0, 1, 2, 3, 4, 5}, )]), SpecMeshTest('Cubecube_intersect_cut', 'Cubecube', 'Cubecube_result_4', [OperatorSpecEditMode('intersect', - {'separate_mode': 'CUT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), + {'separate_mode': 'CUT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), SpecMeshTest('Cubecube_intersect_all', 'Cubecube', 'Cubecube_result_5', - [OperatorSpecEditMode('intersect', - {'separate_mode': 'ALL', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), + [OperatorSpecEditMode('intersect', + {'separate_mode': 'ALL', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), SpecMeshTest('Cubecube_intersect_none', 'Cubecube', 'Cubecube_result_6', - [OperatorSpecEditMode('intersect', - {'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), + [OperatorSpecEditMode('intersect', + {'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), SpecMeshTest('Cubecube_intersect_select_none', 'Cubecube', - 'Cubecube_result_7', - [OperatorSpecEditMode('intersect', - {'mode': 'SELECT', 'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE', - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]), + 'Cubecube_result_7', + [OperatorSpecEditMode('intersect', + {'mode': 'SELECT', 'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE', + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]), SpecMeshTest('Cubecone_intersect_union', 'Cubecone', 'Cubecone_result_1', - [OperatorSpecEditMode('intersect_boolean', - {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {6, 7, 8, 9, 10}, )]), + [OperatorSpecEditMode('intersect_boolean', + {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {6, 7, 8, 9, 10}, )]), SpecMeshTest('Cubecones_intersect_union', 'Cubecones', 'Cubecones_result_1', - [OperatorSpecEditMode('intersect_boolean', {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), + [OperatorSpecEditMode('intersect_boolean', {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), ] diff --git a/tests/python/curve_to_mesh.py b/tests/python/curve_to_mesh.py index d37e7ef23a8..1d0c87b273a 100644 --- a/tests/python/curve_to_mesh.py +++ b/tests/python/curve_to_mesh.py @@ -14,69 +14,69 @@ from modules.mesh_test import SpecMeshTest, OperatorSpecObjectMode, RunTest def main(): tests = [ SpecMeshTest('2D Non Cyclic', 'test2DNonCyclic', 'expected2DNonCyclic', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('2D NURBS With Tail', 'test2DNURBSWithTail', 'expected2DNURBSWithTail', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('2D Shape With Hole', 'test2DShapeWithHole', 'expected2DShapeWithHole', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('2D Simple Lower Res', 'test2DSimpleLowerRes', 'expected2DSimpleLowerRes', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('2D Simple Low Res', 'test2DSimpleLowRes', 'expected2DSimpleLowRes', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('2D Square', 'test2DSquare', 'expected2DSquare', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('2D Extrude', 'test2DExtrude', 'expected2DExtrude', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Back', 'testBevelBack', 'expectedBevelBack', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Back Low Res', 'testBevelBackLowRes', 'expectedBevelBackLowRes', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Extrude Back', 'testBevelExtrudeBack', 'expectedBevelExtrudeBack', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Extrude Front', 'testBevelExtrudeFront', 'expectedBevelExtrudeFront', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Extrude Full', 'testBevelExtrudeFull', 'expectedBevelExtrudeFull', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Extrude Half', 'testBevelExtrudeHalf', 'expectedBevelExtrudeHalf', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Front', 'testBevelFront', 'expectedBevelFront', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Front Low Res', 'testBevelFrontLowRes', 'expectedBevelFrontLowRes', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Full', 'testBevelFull', 'expectedBevelFull', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Full Low Res', 'testBevelFullLowRes', 'expectedBevelFullLowRes', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Half', 'testBevelHalf', 'expectedBevelHalf', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Bevel Half Low Res', 'testBevelHalfLowRes', 'expectedBevelHalfLowRes', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps None', 'testCapsNone', 'expectedCapsNone', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Object Bevel', 'testCapsObjectBevel', 'expectedCapsObjectBevel', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Profile Bevel', 'testCapsProfileBevel', 'expectedCapsProfileBevel', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Profile Bevel Half', 'testCapsProfileBevelHalf', 'expectedCapsProfileBevelHalf', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Profile Bevel Quarter', 'testCapsProfileBevelQuarter', 'expectedCapsProfileBevelQuarter', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Round Bevel', 'testCapsRoundBevel', 'expectedCapsRoundBevel', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Round Bevel Extrude', 'testCapsRoundBevelExtrude', 'expectedCapsRoundBevelExtrude', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Round Bevel Half', 'testCapsRoundBevelHalf', 'expectedCapsRoundBevelHalf', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Caps Round Bevel Quarter', 'testCapsRoundBevelQuarter', 'expectedCapsRoundBevelQuarter', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Extrude Back', 'testExtrudeBack', 'expectedExtrudeBack', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Extrude Front', 'testExtrudeFront', 'expectedExtrudeFront', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Extrude Full', 'testExtrudeFull', 'expectedExtrudeFull', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), SpecMeshTest('Extrude Half', 'testExtrudeHalf', 'expectedExtrudeHalf', - [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), + [OperatorSpecObjectMode('convert', {'target': 'MESH'})]), ] operator_test = RunTest(tests) diff --git a/tests/python/deform_modifiers.py b/tests/python/deform_modifiers.py index cf0d1924e8f..40cd9d4839c 100644 --- a/tests/python/deform_modifiers.py +++ b/tests/python/deform_modifiers.py @@ -20,70 +20,70 @@ tests = [ # Actual deformation occurs by animating imitating user input. SpecMeshTest("SurfaceDeform", "testObjMonkeySurfaceDeform", "expObjMonkeySurfaceDeform", - [DeformModifierSpec(10, [ - ModifierSpec('surface_deform', 'SURFACE_DEFORM', {'target': bpy.data.objects["Cube"]})], - OperatorSpecObjectMode('surfacedeform_bind', {'modifier': 'surface_deform'}))]), + [DeformModifierSpec(10, [ + ModifierSpec('surface_deform', 'SURFACE_DEFORM', {'target': bpy.data.objects["Cube"]})], + OperatorSpecObjectMode('surfacedeform_bind', {'modifier': 'surface_deform'}))]), # Mesh Deform Test, finally can bind to the Target object. # Actual deformation occurs by animating imitating user input. SpecMeshTest("MeshDeform", "testObjMonkeyMeshDeform", "expObjMonkeyMeshDeform", - [DeformModifierSpec(10, [ModifierSpec('mesh_deform', 'MESH_DEFORM', - {'object': bpy.data.objects["MeshCube"], 'precision': 2})], - OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform'}))]), + [DeformModifierSpec(10, [ModifierSpec('mesh_deform', 'MESH_DEFORM', + {'object': bpy.data.objects["MeshCube"], 'precision': 2})], + OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform'}))]), # Surface Deform Test, finally can bind to the Target object. # Actual deformation occurs by animating imitating user input. SpecMeshTest("Hook", "testObjHookPlane", "expObjHookPlane", - [DeformModifierSpec(10, [ModifierSpec('hook', 'HOOK', - {'object': bpy.data.objects["Empty"], 'falloff_radius': 1, - 'vertex_group': 'Group'})])]), + [DeformModifierSpec(10, [ModifierSpec('hook', 'HOOK', + {'object': bpy.data.objects["Empty"], 'falloff_radius': 1, + 'vertex_group': 'Group'})])]), # Laplacian Deform Test, first a hook is attached. SpecMeshTest("Laplace", "testObjCubeLaplacian", "expObjCubeLaplacian", - [DeformModifierSpec(10, - [ModifierSpec('hook2', 'HOOK', {'object': bpy.data.objects["Empty.001"], - 'vertex_group': 'hook_vg'}), - ModifierSpec('laplace', 'LAPLACIANDEFORM', {'vertex_group': 'laplace_vg'})], - OperatorSpecObjectMode('laplaciandeform_bind', {'modifier': 'laplace'}))]), + [DeformModifierSpec(10, + [ModifierSpec('hook2', 'HOOK', {'object': bpy.data.objects["Empty.001"], + 'vertex_group': 'hook_vg'}), + ModifierSpec('laplace', 'LAPLACIANDEFORM', {'vertex_group': 'laplace_vg'})], + OperatorSpecObjectMode('laplaciandeform_bind', {'modifier': 'laplace'}))]), SpecMeshTest("WarpPlane", "testObjPlaneWarp", "expObjPlaneWarp", - [DeformModifierSpec(10, [ModifierSpec('warp', 'WARP', - {'object_from': bpy.data.objects["From"], - 'object_to': bpy.data.objects["To"], - })])]), + [DeformModifierSpec(10, [ModifierSpec('warp', 'WARP', + {'object_from': bpy.data.objects["From"], + 'object_to': bpy.data.objects["To"], + })])]), ############################################# # Curves Deform Modifiers ############################################# SpecMeshTest("CurveArmature", "testObjBezierCurveArmature", "expObjBezierCurveArmature", - [DeformModifierSpec(10, [ModifierSpec('curve_armature', 'ARMATURE', - {'object': bpy.data.objects['testArmatureHelper'], - 'use_vertex_groups': False, 'use_bone_envelopes': True})])]), + [DeformModifierSpec(10, [ModifierSpec('curve_armature', 'ARMATURE', + {'object': bpy.data.objects['testArmatureHelper'], + 'use_vertex_groups': False, 'use_bone_envelopes': True})])]), SpecMeshTest("CurveLattice", "testObjBezierCurveLattice", "expObjBezierCurveLattice", - [DeformModifierSpec(10, [ModifierSpec('curve_lattice', 'LATTICE', - {'object': bpy.data.objects['testLatticeCurve']})])]), + [DeformModifierSpec(10, [ModifierSpec('curve_lattice', 'LATTICE', + {'object': bpy.data.objects['testLatticeCurve']})])]), # HOOK for Curves can't be tested with current framework, as it requires going to Edit Mode to select vertices, # here is no equivalent of a vertex group in Curves. # Dummy test for Hook, can also be called corner case SpecMeshTest("CurveHook", "testObjBezierCurveHook", "expObjBezierCurveHook", - [DeformModifierSpec(10, - [ModifierSpec('curve_Hook', 'HOOK', {'object': bpy.data.objects['EmptyCurve']})])]), + [DeformModifierSpec(10, + [ModifierSpec('curve_Hook', 'HOOK', {'object': bpy.data.objects['EmptyCurve']})])]), SpecMeshTest("MeshDeformCurve", "testObjCurveMeshDeform", "expObjCurveMeshDeform", - [DeformModifierSpec(10, [ - ModifierSpec('mesh_deform_curve', 'MESH_DEFORM', {'object': bpy.data.objects["Cylinder"], - 'precision': 2})], - OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform_curve'}))]), + [DeformModifierSpec(10, [ + ModifierSpec('mesh_deform_curve', 'MESH_DEFORM', {'object': bpy.data.objects["Cylinder"], + 'precision': 2})], + OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform_curve'}))]), SpecMeshTest("WarpCurve", "testObjBezierCurveWarp", "expObjBezierCurveWarp", - [DeformModifierSpec(10, [ModifierSpec('warp_curve', 'WARP', - {'object_from': bpy.data.objects["From_curve"], - 'object_to': bpy.data.objects["To_curve"]})])]), + [DeformModifierSpec(10, [ModifierSpec('warp_curve', 'WARP', + {'object_from': bpy.data.objects["From_curve"], + 'object_to': bpy.data.objects["To_curve"]})])]), ] diff --git a/tests/python/modifiers.py b/tests/python/modifiers.py index 2433ecd29f9..8f8b5c6498c 100644 --- a/tests/python/modifiers.py +++ b/tests/python/modifiers.py @@ -73,147 +73,147 @@ def main(): # 0 # SpecMeshTest("testCube", "expectedCube", get_generate_modifiers_list("testCube")), SpecMeshTest("CubeRandom", "testCubeRandom", "expectedCubeRandom", - get_generate_modifiers_list("testCubeRandom", randomize=True)), + get_generate_modifiers_list("testCubeRandom", randomize=True)), SpecMeshTest("CubeMaskFirst", "testCubeMaskFirst", "expectedCubeMaskFirst", mask_first_list), SpecMeshTest("CollapseDecimate", "testCollapseDecimate", "expectedCollapseDecimate", - [ModifierSpec('decimate', 'DECIMATE', - {'decimate_type': 'COLLAPSE', 'ratio': 0.25, 'use_collapse_triangulate': True})]), + [ModifierSpec('decimate', 'DECIMATE', + {'decimate_type': 'COLLAPSE', 'ratio': 0.25, 'use_collapse_triangulate': True})]), SpecMeshTest("PlanarDecimate", "testPlanarDecimate", "expectedPlanarDecimate", - [ModifierSpec('decimate', 'DECIMATE', - {'decimate_type': 'DISSOLVE', 'angle_limit': math.radians(30)})]), + [ModifierSpec('decimate', 'DECIMATE', + {'decimate_type': 'DISSOLVE', 'angle_limit': math.radians(30)})]), SpecMeshTest("UnsubdivideDecimate", "testUnsubdivideDecimate", "expectedUnsubdivideDecimate", - [ModifierSpec('decimate', 'DECIMATE', {'decimate_type': 'UNSUBDIV', 'iterations': 2})]), + [ModifierSpec('decimate', 'DECIMATE', {'decimate_type': 'UNSUBDIV', 'iterations': 2})]), # 5 SpecMeshTest("RadialBisectMirror", "testRadialBisectMirror", "expectedRadialBisectMirror", - [ModifierSpec('mirror1', 'MIRROR', {'use_bisect_axis': (True, False, False)}), - ModifierSpec('mirror2', 'MIRROR', {'use_bisect_axis': (True, False, False), - 'mirror_object': bpy.data.objects[ + [ModifierSpec('mirror1', 'MIRROR', {'use_bisect_axis': (True, False, False)}), + ModifierSpec('mirror2', 'MIRROR', {'use_bisect_axis': (True, False, False), + 'mirror_object': bpy.data.objects[ "testRadialBisectMirrorHelper"]}), - ModifierSpec('mirror3', 'MIRROR', - {'use_axis': (False, True, False), 'use_bisect_axis': (False, True, False), - 'use_bisect_flip_axis': (False, True, False), - 'mirror_object': bpy.data.objects["testRadialBisectMirrorHelper"]})]), + ModifierSpec('mirror3', 'MIRROR', + {'use_axis': (False, True, False), 'use_bisect_axis': (False, True, False), + 'use_bisect_flip_axis': (False, True, False), + 'mirror_object': bpy.data.objects["testRadialBisectMirrorHelper"]})]), SpecMeshTest("T58411Mirror", "regressT58411Mirror", "expectedT58411Mirror", - [ModifierSpec('mirror', 'MIRROR', {}), - ModifierSpec('bevel', 'BEVEL', {'segments': 2, 'limit_method': 'WEIGHT'}), - ModifierSpec('subd', 'SUBSURF', {'levels': 1})]), + [ModifierSpec('mirror', 'MIRROR', {}), + ModifierSpec('bevel', 'BEVEL', {'segments': 2, 'limit_method': 'WEIGHT'}), + ModifierSpec('subd', 'SUBSURF', {'levels': 1})]), SpecMeshTest("BasicScrew", "testBasicScrew", "expectedBasicScrew", - [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testBasicScrewHelper"]}), - ModifierSpec("screw", 'SCREW', - {'angle': math.radians(400), 'steps': 20, 'iterations': 2, 'screw_offset': 2, - 'use_normal_calculate': True})]), + [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testBasicScrewHelper"]}), + ModifierSpec("screw", 'SCREW', + {'angle': math.radians(400), 'steps': 20, 'iterations': 2, 'screw_offset': 2, + 'use_normal_calculate': True})]), SpecMeshTest("ObjectScrew", "testObjectScrew", "expectedObjectScrew", - [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testObjectScrewHelper2"]}), - ModifierSpec("screw", 'SCREW', - {"angle": math.radians(600), 'steps': 32, 'iterations': 1, - 'use_object_screw_offset': True, - 'use_normal_calculate': True, 'object': bpy.data.objects["testObjectScrewHelper1"]})]), + [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testObjectScrewHelper2"]}), + ModifierSpec("screw", 'SCREW', + {"angle": math.radians(600), 'steps': 32, 'iterations': 1, + 'use_object_screw_offset': True, + 'use_normal_calculate': True, 'object': bpy.data.objects["testObjectScrewHelper1"]})]), # 9 SpecMeshTest("MergedScrewWeld", "testMergedScrewWeld", "expectedMergedScrewWeld", - [ModifierSpec("screw", 'SCREW', - {'angle': math.radians(360), 'steps': 12, 'iterations': 1, 'screw_offset': 1, - 'use_normal_calculate': True, 'use_merge_vertices': True}), - ModifierSpec("weld", 'WELD', {"merge_threshold": 0.001})]), + [ModifierSpec("screw", 'SCREW', + {'angle': math.radians(360), 'steps': 12, 'iterations': 1, 'screw_offset': 1, + 'use_normal_calculate': True, 'use_merge_vertices': True}), + ModifierSpec("weld", 'WELD', {"merge_threshold": 0.001})]), SpecMeshTest("T72380Weld", "regressT72380Weld", "expectedT72380Weld", - [ModifierSpec('vedit', 'VERTEX_WEIGHT_EDIT', - {'vertex_group': 'Group', 'use_remove': True, 'remove_threshold': 1}), - ModifierSpec("weld", 'WELD', {"merge_threshold": 0.2, "vertex_group": "Group"})]), + [ModifierSpec('vedit', 'VERTEX_WEIGHT_EDIT', + {'vertex_group': 'Group', 'use_remove': True, 'remove_threshold': 1}), + ModifierSpec("weld", 'WELD', {"merge_threshold": 0.2, "vertex_group": "Group"})]), SpecMeshTest("T72792Weld", "regressT72792Weld", "expectedT72792Weld", - [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 2}), - ModifierSpec("weld", 'WELD', {"merge_threshold": 0.1, "vertex_group": "Group"})]), + [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 2}), + ModifierSpec("weld", 'WELD', {"merge_threshold": 0.1, "vertex_group": "Group"})]), ############################################ # One 'Generate' modifier on primitive meshes ############################################# # 12 SpecMeshTest("CubeArray", "testCubeArray", "expectedCubeArray", - [ModifierSpec('array', 'ARRAY', {})]), + [ModifierSpec('array', 'ARRAY', {})]), SpecMeshTest("CapArray", "testCapArray", "expectedCapArray", - [ModifierSpec('array', 'ARRAY', - {'fit_type': 'FIT_LENGTH', 'fit_length': 2.0, - 'start_cap': bpy.data.objects["testCapStart"], - 'end_cap': bpy.data.objects["testCapEnd"]})]), + [ModifierSpec('array', 'ARRAY', + {'fit_type': 'FIT_LENGTH', 'fit_length': 2.0, + 'start_cap': bpy.data.objects["testCapStart"], + 'end_cap': bpy.data.objects["testCapEnd"]})]), SpecMeshTest("CurveArray", "testCurveArray", "expectedCurveArray", - [ModifierSpec('array', 'ARRAY', - {'fit_type': 'FIT_CURVE', 'curve': bpy.data.objects["testCurveArrayHelper"], - 'use_relative_offset': False, 'use_constant_offset': True, - 'constant_offset_displace': (0.5, 0, 0)})]), + [ModifierSpec('array', 'ARRAY', + {'fit_type': 'FIT_CURVE', 'curve': bpy.data.objects["testCurveArrayHelper"], + 'use_relative_offset': False, 'use_constant_offset': True, + 'constant_offset_displace': (0.5, 0, 0)})]), SpecMeshTest("RadialArray", "testRadialArray", "expectedRadialArray", - [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 3, 'use_merge_vertices': True, - 'use_merge_vertices_cap': True, 'use_relative_offset': False, - 'use_object_offset': True, - 'offset_object': bpy.data.objects["testRadialArrayHelper"]})]), + [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 3, 'use_merge_vertices': True, + 'use_merge_vertices_cap': True, 'use_relative_offset': False, + 'use_object_offset': True, + 'offset_object': bpy.data.objects["testRadialArrayHelper"]})]), SpecMeshTest("CylinderBuild", "testCylinderBuild", "expectedCylinderBuild", - [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]), + [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]), # 17 SpecMeshTest("ConeDecimate", "testConeDecimate", "expectedConeDecimate", - [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]), + [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]), SpecMeshTest("CubeEdgeSplit", "testCubeEdgeSplit", "expectedCubeEdgeSplit", - [ModifierSpec('edge split', 'EDGE_SPLIT', {})]), + [ModifierSpec('edge split', 'EDGE_SPLIT', {})]), SpecMeshTest("SphereMirror", "testSphereMirror", "expectedSphereMirror", - [ModifierSpec('mirror', 'MIRROR', {})]), + [ModifierSpec('mirror', 'MIRROR', {})]), SpecMeshTest("LocalMirror", "testLocalMirror", "expectedLocalMirror", - [ModifierSpec('mirror', 'MIRROR', {'use_clip': True})]), + [ModifierSpec('mirror', 'MIRROR', {'use_clip': True})]), SpecMeshTest("ObjectOffsetMirror", "testObjectOffsetMirror", "expectedObjectOffsetMirror", - [ModifierSpec('mirror', 'MIRROR', - {'mirror_object': bpy.data.objects["testObjectOffsetMirrorHelper"]})]), + [ModifierSpec('mirror', 'MIRROR', + {'mirror_object': bpy.data.objects["testObjectOffsetMirrorHelper"]})]), SpecMeshTest("CylinderMask", "testCylinderMask", "expectedCylinderMask", - [ModifierSpec('mask', 'MASK', {'vertex_group': "mask_vertex_group"})]), + [ModifierSpec('mask', 'MASK', {'vertex_group': "mask_vertex_group"})]), SpecMeshTest("ConeMultiRes", "testConeMultiRes", "expectedConeMultiRes", - [ModifierSpec('multires', 'MULTIRES', {})]), + [ModifierSpec('multires', 'MULTIRES', {})]), # 24 SpecMeshTest("CubeScrew", "testCubeScrew", "expectedCubeScrew", - [ModifierSpec('screw', 'SCREW', {})]), + [ModifierSpec('screw', 'SCREW', {})]), SpecMeshTest("CubeSolidify", "testCubeSolidify", "expectedCubeSolidify", - [ModifierSpec('solidify', 'SOLIDIFY', {})]), + [ModifierSpec('solidify', 'SOLIDIFY', {})]), SpecMeshTest("ComplexSolidify", "testComplexSolidify", "expectedComplexSolidify", - [ModifierSpec('solidify', 'SOLIDIFY', {'solidify_mode': 'NON_MANIFOLD', 'thickness': 0.05, 'offset': 0, - 'nonmanifold_thickness_mode': 'CONSTRAINTS'})]), + [ModifierSpec('solidify', 'SOLIDIFY', {'solidify_mode': 'NON_MANIFOLD', 'thickness': 0.05, 'offset': 0, + 'nonmanifold_thickness_mode': 'CONSTRAINTS'})]), SpecMeshTest("T63063Solidify", "regressT63063Solidify", "expectedT63063Solidify", - [ModifierSpec('solid', 'SOLIDIFY', {'thickness': 0.1, 'offset': 0.7})]), + [ModifierSpec('solid', 'SOLIDIFY', {'thickness': 0.1, 'offset': 0.7})]), SpecMeshTest("T61979Solidify", "regressT61979Solidify", "expectedT61979Solidify", - [ModifierSpec('solid', 'SOLIDIFY', - {'thickness': -0.25, 'use_even_offset': True, 'use_quality_normals': True})]), + [ModifierSpec('solid', 'SOLIDIFY', + {'thickness': -0.25, 'use_even_offset': True, 'use_quality_normals': True})]), SpecMeshTest("MonkeySubsurf", "testMonkeySubsurf", "expectedMonkeySubsurf", - [ModifierSpec('subsurf', 'SUBSURF', {})]), + [ModifierSpec('subsurf', 'SUBSURF', {})]), SpecMeshTest("CatmullClarkSubdivisionSurface", "testCatmullClarkSubdivisionSurface", - "expectedCatmullClarkSubdivisionSurface", - [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]), + "expectedCatmullClarkSubdivisionSurface", + [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]), SpecMeshTest("SimpleSubdivisionSurface", "testSimpleSubdivisionSurface", "expectedSimpleSubdivisionSurface", - [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2, 'subdivision_type': 'SIMPLE'})]), + [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2, 'subdivision_type': 'SIMPLE'})]), SpecMeshTest("Crease2dSubdivisionSurface", "testCrease2dSubdivisionSurface", "expectedCrease2dSubdivisionSurface", - [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]), + [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]), SpecMeshTest("Crease3dSubdivisionSurface", "testCrease3dSubdivisionSurface", "expectedCrease3dSubdivisionSurface", - [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]), + [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]), # 34 SpecMeshTest("SphereTriangulate", "testSphereTriangulate", "expectedSphereTriangulate", - [ModifierSpec('triangulate', 'TRIANGULATE', {})]), + [ModifierSpec('triangulate', 'TRIANGULATE', {})]), SpecMeshTest("MonkeyWireframe", "testMonkeyWireframe", "expectedMonkeyWireframe", - [ModifierSpec('wireframe', 'WIREFRAME', {})]), + [ModifierSpec('wireframe', 'WIREFRAME', {})]), # Duplicate the object, test object and expected object have same world coordinates. SpecMeshTest("Skin", "testObjPlaneSkin", "expObjPlaneSkin", - [ModifierSpec('skin', 'SKIN', {})]), + [ModifierSpec('skin', 'SKIN', {})]), SpecMeshTest("MergedWeld", "testMergedWeld", "expectedMergedWeld", - [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.021})]), + [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.021})]), SpecMeshTest("MergedAllWeld", "testMergedAllWeld", "expectedMergedAllWeld", - [ModifierSpec("weld", 'WELD', {"merge_threshold": 1.8})]), + [ModifierSpec("weld", 'WELD', {"merge_threshold": 1.8})]), SpecMeshTest("MergedNoneWeld", "testMergedNoneWeld", "expectedMergedNoneWeld", - [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.019})]), + [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.019})]), ############################################# @@ -221,14 +221,14 @@ def main(): ############################################# # 39 SpecMeshTest("MonkeyArmature", "testMonkeyArmature", "expectedMonkeyArmature", - [ModifierSpec('armature', 'ARMATURE', - {'object': bpy.data.objects['testArmature'], 'use_vertex_groups': True})]), + [ModifierSpec('armature', 'ARMATURE', + {'object': bpy.data.objects['testArmature'], 'use_vertex_groups': True})]), SpecMeshTest("TorusCast", "testTorusCast", "expectedTorusCast", - [ModifierSpec('cast', 'CAST', {'factor': 2.64})]), + [ModifierSpec('cast', 'CAST', {'factor': 2.64})]), SpecMeshTest("CubeCurve", "testCubeCurve", "expectedCubeCurve", - [ModifierSpec('curve', 'CURVE', {'object': bpy.data.objects['testBezierCurve']})]), + [ModifierSpec('curve', 'CURVE', {'object': bpy.data.objects['testBezierCurve']})]), SpecMeshTest("MonkeyDisplace", "testMonkeyDisplace", "expectedMonkeyDisplace", - [ModifierSpec('displace', "DISPLACE", {})]), + [ModifierSpec('displace', "DISPLACE", {})]), # Hook modifier requires moving the hook object to get a mesh change # so can't test it with the current framework @@ -239,91 +239,91 @@ def main(): # 43 # ModifierSpec('laplacian_deform', 'LAPLACIANDEFORM', {}) Laplacian requires a more complex mesh SpecMeshTest("CubeLattice", "testCubeLattice", "expectedCubeLattice", - [ModifierSpec('lattice', 'LATTICE', {'object': bpy.data.objects["testLattice"]})]), + [ModifierSpec('lattice', 'LATTICE', {'object': bpy.data.objects["testLattice"]})]), SpecMeshTest("PlaneShrinkWrap", "testPlaneShrinkWrap", "expectedPlaneShrinkWrap", - [ModifierSpec('shrinkwrap', 'SHRINKWRAP', - {'target': bpy.data.objects["testCubeWrap"], 'offset': 0.5})]), + [ModifierSpec('shrinkwrap', 'SHRINKWRAP', + {'target': bpy.data.objects["testCubeWrap"], 'offset': 0.5})]), SpecMeshTest("CylinderSimpleDeform", "testCylinderSimpleDeform", "expectedCylinderSimpleDeform", - [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(180), 'deform_axis': 'Z'})]), + [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(180), 'deform_axis': 'Z'})]), SpecMeshTest("PlaneSmooth", "testPlaneSmooth", "expectedPlaneSmooth", - [ModifierSpec('smooth', 'SMOOTH', {'iterations': 11})]), + [ModifierSpec('smooth', 'SMOOTH', {'iterations': 11})]), # Smooth corrective requires a complex mesh. SpecMeshTest("BalloonLaplacianSmooth", "testBalloonLaplacianSmooth", "expectedBalloonLaplacianSmooth", - [ModifierSpec('laplaciansmooth', 'LAPLACIANSMOOTH', {'lambda_factor': 12, 'lambda_border': 12})]), + [ModifierSpec('laplaciansmooth', 'LAPLACIANSMOOTH', {'lambda_factor': 12, 'lambda_border': 12})]), # Gets updated often SpecMeshTest("WavePlane", "testObjPlaneWave", "expObjPlaneWave", - [ModifierSpec('wave', 'WAVE', {})]), + [ModifierSpec('wave', 'WAVE', {})]), ############################################# # CURVES Generate Modifiers ############################################# # Caution: Make sure test object has no modifier in "added" state, the test may fail. SpecMeshTest("BezCurveArray", "testObjBezierCurveArray", "expObjBezierCurveArray", - [ModifierSpec('array', 'ARRAY', {})]), + [ModifierSpec('array', 'ARRAY', {})]), SpecMeshTest("CurveBevel", "testObjBezierCurveBevel", "expObjBezierCurveBevel", - [ModifierSpec('bevel', 'BEVEL', {'limit_method': 'NONE'})]), + [ModifierSpec('bevel', 'BEVEL', {'limit_method': 'NONE'})]), SpecMeshTest("CurveBuild", "testObjBezierCurveBuild", "expObjBezierCurveBuild", - [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]), + [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]), SpecMeshTest("CurveDecimate", "testObjBezierCurveDecimate", "expObjBezierCurveDecimate", - [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]), + [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]), SpecMeshTest("CurveEdgeSplit", "testObjBezierCurveEdgeSplit", "expObjBezierCurveEdgeSplit", - [ModifierSpec('edgeSplit', 'EDGE_SPLIT', {})]), + [ModifierSpec('edgeSplit', 'EDGE_SPLIT', {})]), SpecMeshTest("CurveMirror", "testObjBezierCurveMirror", "expObjBezierCurveMirror", - [ModifierSpec('mirror', 'MIRROR', {'use_axis': (True, True, False)})]), + [ModifierSpec('mirror', 'MIRROR', {'use_axis': (True, True, False)})]), SpecMeshTest("CurveScrew", "testObjBezierCurveScrew", "expObjBezierCurveScrew", - [ModifierSpec('screw', 'SCREW', {})]), + [ModifierSpec('screw', 'SCREW', {})]), SpecMeshTest("CurveSolidify", "testObjBezierCurveSolidify", "expObjBezierCurveSolidify", - [ModifierSpec('solidify', 'SOLIDIFY', {'thickness': 1})]), + [ModifierSpec('solidify', 'SOLIDIFY', {'thickness': 1})]), SpecMeshTest("CurveSubSurf", "testObjBezierCurveSubSurf", "expObjBezierCurveSubSurf", - [ModifierSpec('subSurf', 'SUBSURF', {})]), + [ModifierSpec('subSurf', 'SUBSURF', {})]), SpecMeshTest("CurveTriangulate", "testObjBezierCurveTriangulate", "expObjBezierCurveTriangulate", - [ModifierSpec('triangulate', 'TRIANGULATE', {})]), + [ModifierSpec('triangulate', 'TRIANGULATE', {})]), # Test 60 # Caution Weld: if the distance is increased beyond a limit, the object disappears SpecMeshTest("CurveWeld", "testObjBezierCurveWeld", "expObjBezierCurveWeld", - [ModifierSpec('weld', 'WELD', {})]), + [ModifierSpec('weld', 'WELD', {})]), SpecMeshTest("CurveWeld2", "testObjBezierCurveWeld2", "expObjBezierCurveWeld2", - [ModifierSpec('weld', 'WELD', {})]), + [ModifierSpec('weld', 'WELD', {})]), ############################################# # Curves Deform Modifiers ############################################# # Test 62 SpecMeshTest("CurveCast", "testObjBezierCurveCast", "expObjBezierCurveCast", - [ModifierSpec('Cast', 'CAST', {'cast_type': 'CYLINDER', 'factor': 10})]), + [ModifierSpec('Cast', 'CAST', {'cast_type': 'CYLINDER', 'factor': 10})]), SpecMeshTest("CurveShrinkWrap", "testObjBezierCurveShrinkWrap", "expObjBezierCurveShrinkWrap", - [ModifierSpec('ShrinkWrap', 'SHRINKWRAP', - {'target': bpy.data.objects['testShrinkWrapHelperSuzanne']})]), + [ModifierSpec('ShrinkWrap', 'SHRINKWRAP', + {'target': bpy.data.objects['testShrinkWrapHelperSuzanne']})]), SpecMeshTest("CurveSimpleDeform", "testObjBezierCurveSimpleDeform", "expObjBezierCurveSimpleDeform", - [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(90)})]), + [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(90)})]), SpecMeshTest("CurveSmooth", "testObjBezierCurveSmooth", "expObjBezierCurveSmooth", - [ModifierSpec('smooth', 'SMOOTH', {'factor': 10})]), + [ModifierSpec('smooth', 'SMOOTH', {'factor': 10})]), SpecMeshTest("CurveWave", "testObjBezierCurveWave", "expObjBezierCurveWave", - [ModifierSpec('curve_wave', 'WAVE', {'time_offset': -1.5})]), + [ModifierSpec('curve_wave', 'WAVE', {'time_offset': -1.5})]), SpecMeshTest("CurveCurve", "testObjBezierCurveCurve", "expObjBezierCurveCurve", - [ModifierSpec('curve_Curve', 'CURVE', {'object': bpy.data.objects['NurbsCurve']})]), + [ModifierSpec('curve_Curve', 'CURVE', {'object': bpy.data.objects['NurbsCurve']})]), ] diff --git a/tests/python/modules/mesh_test.py b/tests/python/modules/mesh_test.py index 730c0d61a26..873ab779d65 100644 --- a/tests/python/modules/mesh_test.py +++ b/tests/python/modules/mesh_test.py @@ -87,6 +87,7 @@ class OperatorSpecEditMode: """ Holds one operator and its parameters. """ + def __init__( self, operator_name: str, @@ -206,7 +207,7 @@ class MeshTest(ABC): self.expected_object = self.evaluated_object self.expected_object.name = self.exp_object_name x, y, z = self.test_object.location - self.expected_object.location = (x, y+10, z) + self.expected_object.location = (x, y + 10, z) bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) def create_evaluated_object(self): @@ -261,7 +262,6 @@ class MeshTest(ABC): if not inside_loop_flag: success = False - if success: self.print_passed_test_result(result) # Clean up. @@ -320,7 +320,7 @@ class MeshTest(ABC): bm = bmesh.from_edit_mesh(mesh) - #bpy.ops.object.mode_set(mode='OBJECT') + # bpy.ops.object.mode_set(mode='OBJECT') bpy.context.tool_settings.mesh_select_mode = (select_mode == 'VERT', select_mode == 'EDGE', diff --git a/tests/python/modules/test_utils.py b/tests/python/modules/test_utils.py index 18b4eb78f4b..d5cd743cde9 100755 --- a/tests/python/modules/test_utils.py +++ b/tests/python/modules/test_utils.py @@ -43,7 +43,7 @@ class AbstractBlenderRunnerTest(unittest.TestCase): blender: pathlib.Path = None testdir: pathlib.Path = None - def run_blender(self, filepath: str, python_script: str, timeout: int=300) -> str: + def run_blender(self, filepath: str, python_script: str, timeout: int = 300) -> str: """Runs Blender by opening a blendfile and executing a script. Returns Blender's stdout + stderr combined into one string. diff --git a/tests/python/operators.py b/tests/python/operators.py index 645ce31390d..6ccc96dba5d 100644 --- a/tests/python/operators.py +++ b/tests/python/operators.py @@ -22,104 +22,154 @@ MONKEY_LOOP_EDGE = {131, 278, 299, 305, 307, 334, 337, 359, 384, 396, 399, 412, def main(): tests = [ # bisect - SpecMeshTest("CubeBisect", "testCubeBisect", "expectedCubeBisect", - [OperatorSpecEditMode("bisect", - {"plane_co": (0, 0, 0), "plane_no": (0, 1, 1), "clear_inner": True, - "use_fill": True}, 'FACE', {0, 1, 2, 3, 4, 5}, )]), + SpecMeshTest( + "CubeBisect", "testCubeBisect", "expectedCubeBisect", + [OperatorSpecEditMode("bisect", + {"plane_co": (0, 0, 0), "plane_no": (0, 1, 1), "clear_inner": True, + "use_fill": True}, 'FACE', {0, 1, 2, 3, 4, 5}, )], + ), # blend from shape - SpecMeshTest("CubeBlendFromShape", "testCubeBlendFromShape", "expectedCubeBlendFromShape", - [OperatorSpecEditMode("blend_from_shape", {"shape": "Key 1"}, 'FACE', {0, 1, 2, 3, 4, 5})]), + SpecMeshTest( + "CubeBlendFromShape", "testCubeBlendFromShape", "expectedCubeBlendFromShape", + [OperatorSpecEditMode("blend_from_shape", {"shape": "Key 1"}, 'FACE', {0, 1, 2, 3, 4, 5})], + ), # bridge edge loops - SpecMeshTest("CubeBridgeEdgeLoop", "testCubeBrigeEdgeLoop", "expectedCubeBridgeEdgeLoop", - [OperatorSpecEditMode("bridge_edge_loops", {}, "FACE", {0, 1})]), + SpecMeshTest( + "CubeBridgeEdgeLoop", "testCubeBrigeEdgeLoop", "expectedCubeBridgeEdgeLoop", + [OperatorSpecEditMode("bridge_edge_loops", {}, "FACE", {0, 1})], + ), # decimate - SpecMeshTest("MonkeyDecimate", "testMonkeyDecimate", "expectedMonkeyDecimate", - [OperatorSpecEditMode("decimate", - {"ratio": 0.1}, "FACE", {i for i in range(500)})]), + SpecMeshTest( + "MonkeyDecimate", "testMonkeyDecimate", "expectedMonkeyDecimate", + [OperatorSpecEditMode("decimate", + {"ratio": 0.1}, "FACE", {i for i in range(500)})], + ), # delete - SpecMeshTest("CubeDeleteVertices", "testCubeDeleteVertices", "expectedCubeDeleteVertices", - [OperatorSpecEditMode("delete", {}, "VERT", {3})]), - SpecMeshTest("CubeDeleteFaces", "testCubeDeleteFaces", "expectedCubeDeleteFaces", - [OperatorSpecEditMode("delete", {}, "FACE", {0})]), - SpecMeshTest("CubeDeleteEdges", "testCubeDeleteEdges", "expectedCubeDeleteEdges", - [OperatorSpecEditMode("delete", {}, "EDGE", {0, 1, 2, 3})]), + SpecMeshTest( + "CubeDeleteVertices", "testCubeDeleteVertices", "expectedCubeDeleteVertices", + [OperatorSpecEditMode("delete", {}, "VERT", {3})], + ), + SpecMeshTest( + "CubeDeleteFaces", "testCubeDeleteFaces", "expectedCubeDeleteFaces", + [OperatorSpecEditMode("delete", {}, "FACE", {0})], + ), + SpecMeshTest( + "CubeDeleteEdges", "testCubeDeleteEdges", "expectedCubeDeleteEdges", + [OperatorSpecEditMode("delete", {}, "EDGE", {0, 1, 2, 3})], + ), # delete edge loop - SpecMeshTest("MonkeyDeleteEdgeLoopVertices", "testMokneyDeleteEdgeLoopVertices", - "expectedMonkeyDeleteEdgeLoopVertices", - [OperatorSpecEditMode("delete_edgeloop", {}, "VERT", MONKEY_LOOP_VERT)]), + SpecMeshTest( + "MonkeyDeleteEdgeLoopVertices", "testMokneyDeleteEdgeLoopVertices", + "expectedMonkeyDeleteEdgeLoopVertices", + [OperatorSpecEditMode("delete_edgeloop", {}, "VERT", MONKEY_LOOP_VERT)], + ), - SpecMeshTest("MonkeyDeleteEdgeLoopEdges", "testMokneyDeleteEdgeLoopEdges", - "expectedMonkeyDeleteEdgeLoopEdges", - [OperatorSpecEditMode("delete_edgeloop", {}, "EDGE", MONKEY_LOOP_EDGE)]), + SpecMeshTest( + "MonkeyDeleteEdgeLoopEdges", "testMokneyDeleteEdgeLoopEdges", + "expectedMonkeyDeleteEdgeLoopEdges", + [OperatorSpecEditMode("delete_edgeloop", {}, "EDGE", MONKEY_LOOP_EDGE)], + ), # delete loose - SpecMeshTest("CubeDeleteLooseVertices", "testCubeDeleteLooseVertices", - "expectedCubeDeleteLooseVertices", - [OperatorSpecEditMode("delete_loose", {"use_verts": True, "use_edges": False, "use_faces": False}, - "VERT", - {i for i in range(12)})]), - SpecMeshTest("CubeDeleteLooseEdges", "testCubeDeleteLooseEdges", - "expectedCubeDeleteLooseEdges", - [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": True, "use_faces": False}, - "EDGE", - {i for i in range(14)})]), - SpecMeshTest("CubeDeleteLooseFaces", "testCubeDeleteLooseFaces", - "expectedCubeDeleteLooseFaces", - [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": False, "use_faces": True}, - "FACE", - {i for i in range(7)})]), + SpecMeshTest( + "CubeDeleteLooseVertices", "testCubeDeleteLooseVertices", + "expectedCubeDeleteLooseVertices", + [OperatorSpecEditMode("delete_loose", {"use_verts": True, "use_edges": False, "use_faces": False}, + "VERT", + {i for i in range(12)})], + ), + SpecMeshTest( + "CubeDeleteLooseEdges", "testCubeDeleteLooseEdges", + "expectedCubeDeleteLooseEdges", + [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": True, "use_faces": False}, + "EDGE", + {i for i in range(14)})], + ), + SpecMeshTest( + "CubeDeleteLooseFaces", "testCubeDeleteLooseFaces", + "expectedCubeDeleteLooseFaces", + [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": False, "use_faces": True}, + "FACE", + {i for i in range(7)})], + ), # dissolve degenerate - SpecMeshTest("CubeDissolveDegenerate", "testCubeDissolveDegenerate", - "expectedCubeDissolveDegenerate", - [OperatorSpecEditMode("dissolve_degenerate", {}, "VERT", {i for i in range(8)})]), + SpecMeshTest( + "CubeDissolveDegenerate", "testCubeDissolveDegenerate", + "expectedCubeDissolveDegenerate", + [OperatorSpecEditMode("dissolve_degenerate", {}, "VERT", {i for i in range(8)})], + ), # dissolve edges - SpecMeshTest("CylinderDissolveEdges", "testCylinderDissolveEdges", "expectedCylinderDissolveEdges", - [OperatorSpecEditMode("dissolve_edges", {}, "EDGE", {0, 5, 6, 9})]), + SpecMeshTest( + "CylinderDissolveEdges", "testCylinderDissolveEdges", "expectedCylinderDissolveEdges", + [OperatorSpecEditMode("dissolve_edges", {}, "EDGE", {0, 5, 6, 9})], + ), # dissolve faces - SpecMeshTest("CubeDissolveFaces", "testCubeDissolveFaces", "expectedCubeDissolveFaces", - [OperatorSpecEditMode("dissolve_faces", {}, "VERT", {5, 34, 47, 49, 83, 91, 95})]), + SpecMeshTest( + "CubeDissolveFaces", "testCubeDissolveFaces", "expectedCubeDissolveFaces", + [OperatorSpecEditMode("dissolve_faces", {}, "VERT", {5, 34, 47, 49, 83, 91, 95})], + ), # dissolve verts - SpecMeshTest("CubeDissolveVerts", "testCubeDissolveVerts", "expectedCubeDissolveVerts", - [OperatorSpecEditMode("dissolve_verts", {}, "VERT", {16, 20, 22, 23, 25})]), + SpecMeshTest( + "CubeDissolveVerts", "testCubeDissolveVerts", "expectedCubeDissolveVerts", + [OperatorSpecEditMode("dissolve_verts", {}, "VERT", {16, 20, 22, 23, 25})], + ), # duplicate - SpecMeshTest("ConeDuplicateVertices", "testConeDuplicateVertices", - "expectedConeDuplicateVertices", - [OperatorSpecEditMode("duplicate", {}, "VERT", {i for i in range(33)} - {23})]), + SpecMeshTest( + "ConeDuplicateVertices", "testConeDuplicateVertices", + "expectedConeDuplicateVertices", + [OperatorSpecEditMode("duplicate", {}, "VERT", {i for i in range(33)} - {23})], + ), - SpecMeshTest("ConeDuplicateOneVertex", "testConeDuplicateOneVertex", "expectedConeDuplicateOneVertex", - [OperatorSpecEditMode("duplicate", {}, "VERT", {23})]), - SpecMeshTest("ConeDuplicateFaces", "testConeDuplicateFaces", "expectedConeDuplicateFaces", - [OperatorSpecEditMode("duplicate", {}, "FACE", {6, 9})]), - SpecMeshTest("ConeDuplicateEdges", "testConeDuplicateEdges", "expectedConeDuplicateEdges", - [OperatorSpecEditMode("duplicate", {}, "EDGE", {i for i in range(64)})]), + SpecMeshTest( + "ConeDuplicateOneVertex", "testConeDuplicateOneVertex", "expectedConeDuplicateOneVertex", + [OperatorSpecEditMode("duplicate", {}, "VERT", {23})], + ), + SpecMeshTest( + "ConeDuplicateFaces", "testConeDuplicateFaces", "expectedConeDuplicateFaces", + [OperatorSpecEditMode("duplicate", {}, "FACE", {6, 9})], + ), + SpecMeshTest( + "ConeDuplicateEdges", "testConeDuplicateEdges", "expectedConeDuplicateEdges", + [OperatorSpecEditMode("duplicate", {}, "EDGE", {i for i in range(64)})], + ), # edge collapse - SpecMeshTest("CylinderEdgeCollapse", "testCylinderEdgeCollapse", "expectedCylinderEdgeCollapse", - [OperatorSpecEditMode("edge_collapse", {}, "EDGE", {1, 9, 4})]), + SpecMeshTest( + "CylinderEdgeCollapse", "testCylinderEdgeCollapse", "expectedCylinderEdgeCollapse", + [OperatorSpecEditMode("edge_collapse", {}, "EDGE", {1, 9, 4})], + ), # edge face add - SpecMeshTest("CubeEdgeFaceAddFace", "testCubeEdgeFaceAddFace", "expectedCubeEdgeFaceAddFace", - [OperatorSpecEditMode("edge_face_add", {}, "VERT", {1, 3, 4, 5, 7})]), - SpecMeshTest("CubeEdgeFaceAddEdge", "testCubeEdgeFaceAddEdge", "expectedCubeEdgeFaceAddEdge", - [OperatorSpecEditMode("edge_face_add", {}, "VERT", {4, 5})]), + SpecMeshTest( + "CubeEdgeFaceAddFace", "testCubeEdgeFaceAddFace", "expectedCubeEdgeFaceAddFace", + [OperatorSpecEditMode("edge_face_add", {}, "VERT", {1, 3, 4, 5, 7})], + ), + SpecMeshTest( + "CubeEdgeFaceAddEdge", "testCubeEdgeFaceAddEdge", "expectedCubeEdgeFaceAddEdge", + [OperatorSpecEditMode("edge_face_add", {}, "VERT", {4, 5})], + ), # edge rotate - SpecMeshTest("CubeEdgeRotate", "testCubeEdgeRotate", "expectedCubeEdgeRotate", - [OperatorSpecEditMode("edge_rotate", {}, "EDGE", {1})]), + SpecMeshTest( + "CubeEdgeRotate", "testCubeEdgeRotate", "expectedCubeEdgeRotate", + [OperatorSpecEditMode("edge_rotate", {}, "EDGE", {1})], + ), # edge split - SpecMeshTest("CubeEdgeSplit", "testCubeEdgeSplit", "expectedCubeEdgeSplit", - [OperatorSpecEditMode("edge_split", {}, "EDGE", {2, 5, 8, 11, 14, 17, 20, 23})]), + SpecMeshTest( + "CubeEdgeSplit", "testCubeEdgeSplit", "expectedCubeEdgeSplit", + [OperatorSpecEditMode("edge_split", {}, "EDGE", {2, 5, 8, 11, 14, 17, 20, 23})], + ), # edge ring select - Cannot be tested. Need user input. # SpecMeshTest("CubeEdgeRingSelect", "testCubeEdgeRingSelect", "expectedCubeEdgeRingSelect", @@ -130,113 +180,173 @@ def main(): # [OperatorSpecEditMode("edgering_select", {}, "VERT", {})]), # edges select sharp - SpecMeshTest("CubeEdgesSelectSharp", "testCubeEdgeSelectSharp", "expectedCubeEdgeSelectSharp", - [OperatorSpecEditMode("edges_select_sharp", {}, "EDGE", {20})]), - SpecMeshTest("SphereEdgesSelectSharp", "testSphereEdgesSelectSharp", "expectedSphereEdgeSelectSharp", - [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.25}, "EDGE", {288})]), - SpecMeshTest("HoledSphereEdgesSelectSharp", "testHoledSphereEdgesSelectSharp", "expectedHoledSphereEdgeSelectSharp", - [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.18}, "VERT", {})]), - SpecMeshTest("EmptyMeshEdgesSelectSharp", "testEmptyMeshEdgeSelectSharp", "expectedEmptyMeshEdgeSelectSharp", - [OperatorSpecEditMode("edges_select_sharp", {}, "VERT", {})]), + SpecMeshTest( + "CubeEdgesSelectSharp", "testCubeEdgeSelectSharp", "expectedCubeEdgeSelectSharp", + [OperatorSpecEditMode("edges_select_sharp", {}, "EDGE", {20})], + ), + SpecMeshTest( + "SphereEdgesSelectSharp", "testSphereEdgesSelectSharp", "expectedSphereEdgeSelectSharp", + [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.25}, "EDGE", {288})], + ), + SpecMeshTest( + "HoledSphereEdgesSelectSharp", "testHoledSphereEdgesSelectSharp", "expectedHoledSphereEdgeSelectSharp", + [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.18}, "VERT", {})], + ), + SpecMeshTest( + "EmptyMeshEdgesSelectSharp", "testEmptyMeshEdgeSelectSharp", "expectedEmptyMeshEdgeSelectSharp", + [OperatorSpecEditMode("edges_select_sharp", {}, "VERT", {})], + ), # face make planar - SpecMeshTest("MonkeyFaceMakePlanar", "testMonkeyFaceMakePlanar", - "expectedMonkeyFaceMakePlanar", - [OperatorSpecEditMode("face_make_planar", {}, "FACE", {i for i in range(500)})]), + SpecMeshTest( + "MonkeyFaceMakePlanar", "testMonkeyFaceMakePlanar", + "expectedMonkeyFaceMakePlanar", + [OperatorSpecEditMode("face_make_planar", {}, "FACE", {i for i in range(500)})], + ), # face split by edges - SpecMeshTest("PlaneFaceSplitByEdges", "testPlaneFaceSplitByEdges", - "expectedPlaneFaceSplitByEdges", - [OperatorSpecEditMode("face_split_by_edges", {}, "VERT", {i for i in range(6)})]), + SpecMeshTest( + "PlaneFaceSplitByEdges", "testPlaneFaceSplitByEdges", + "expectedPlaneFaceSplitByEdges", + [OperatorSpecEditMode("face_split_by_edges", {}, "VERT", {i for i in range(6)})], + ), # faces select linked flat - SpecMeshTest("CubeFacesSelectLinkedFlat", "testCubeFaceSelectLinkedFlat", "expectedCubeFaceSelectLinkedFlat", - [OperatorSpecEditMode("faces_select_linked_flat", {}, "FACE", {7})]), - SpecMeshTest("PlaneFacesSelectLinkedFlat", "testPlaneFaceSelectLinkedFlat", "expectedPlaneFaceSelectLinkedFlat", - [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {1})]), - SpecMeshTest("EmptyMeshFacesSelectLinkedFlat", "testEmptyMeshFaceSelectLinkedFlat", - "expectedEmptyMeshFaceSelectLinkedFlat", - [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {})]), + SpecMeshTest( + "CubeFacesSelectLinkedFlat", "testCubeFaceSelectLinkedFlat", "expectedCubeFaceSelectLinkedFlat", + [OperatorSpecEditMode("faces_select_linked_flat", {}, "FACE", {7})], + ), + SpecMeshTest( + "PlaneFacesSelectLinkedFlat", "testPlaneFaceSelectLinkedFlat", "expectedPlaneFaceSelectLinkedFlat", + [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {1})], + ), + SpecMeshTest( + "EmptyMeshFacesSelectLinkedFlat", "testEmptyMeshFaceSelectLinkedFlat", + "expectedEmptyMeshFaceSelectLinkedFlat", + [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {})], + ), # fill - SpecMeshTest("IcosphereFill", "testIcosphereFill", "expectedIcosphereFill", - [OperatorSpecEditMode("fill", {}, "EDGE", {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})]), - SpecMeshTest("IcosphereFillUseBeautyFalse", - "testIcosphereFillUseBeautyFalse", "expectedIcosphereFillUseBeautyFalse", - [OperatorSpecEditMode("fill", {"use_beauty": False}, "EDGE", - {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})]), + SpecMeshTest( + "IcosphereFill", "testIcosphereFill", "expectedIcosphereFill", + [OperatorSpecEditMode("fill", {}, "EDGE", {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})], + ), + SpecMeshTest( + "IcosphereFillUseBeautyFalse", + "testIcosphereFillUseBeautyFalse", "expectedIcosphereFillUseBeautyFalse", + [OperatorSpecEditMode("fill", {"use_beauty": False}, "EDGE", + {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})], + ), # fill grid - SpecMeshTest("PlaneFillGrid", "testPlaneFillGrid", - "expectedPlaneFillGrid", - [OperatorSpecEditMode("fill_grid", {}, "EDGE", {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})]), - SpecMeshTest("PlaneFillGridSimpleBlending", - "testPlaneFillGridSimpleBlending", - "expectedPlaneFillGridSimpleBlending", - [OperatorSpecEditMode("fill_grid", {"use_interp_simple": True}, "EDGE", - {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})]), + SpecMeshTest( + "PlaneFillGrid", "testPlaneFillGrid", + "expectedPlaneFillGrid", + [OperatorSpecEditMode("fill_grid", {}, "EDGE", {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})], + ), + SpecMeshTest( + "PlaneFillGridSimpleBlending", + "testPlaneFillGridSimpleBlending", + "expectedPlaneFillGridSimpleBlending", + [OperatorSpecEditMode("fill_grid", {"use_interp_simple": True}, "EDGE", + {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})], + ), # fill holes - SpecMeshTest("SphereFillHoles", "testSphereFillHoles", "expectedSphereFillHoles", - [OperatorSpecEditMode("fill_holes", {"sides": 9}, "VERT", {i for i in range(481)})]), + SpecMeshTest( + "SphereFillHoles", "testSphereFillHoles", "expectedSphereFillHoles", + [OperatorSpecEditMode("fill_holes", {"sides": 9}, "VERT", {i for i in range(481)})], + ), # face shade smooth (not a real test) - SpecMeshTest("CubeShadeSmooth", "testCubeShadeSmooth", "expectedCubeShadeSmooth", - [OperatorSpecEditMode("faces_shade_smooth", {}, "VERT", {i for i in range(8)})]), + SpecMeshTest( + "CubeShadeSmooth", "testCubeShadeSmooth", "expectedCubeShadeSmooth", + [OperatorSpecEditMode("faces_shade_smooth", {}, "VERT", {i for i in range(8)})], + ), # faces shade flat (not a real test) - SpecMeshTest("CubeShadeFlat", "testCubeShadeFlat", "expectedCubeShadeFlat", - [OperatorSpecEditMode("faces_shade_flat", {}, "FACE", {i for i in range(6)})]), + SpecMeshTest( + "CubeShadeFlat", "testCubeShadeFlat", "expectedCubeShadeFlat", + [OperatorSpecEditMode("faces_shade_flat", {}, "FACE", {i for i in range(6)})], + ), # hide - SpecMeshTest("HideFace", "testCubeHideFace", "expectedCubeHideFace", - [OperatorSpecEditMode("hide", {}, "FACE", {3})]), - SpecMeshTest("HideEdge", "testCubeHideEdge", "expectedCubeHideEdge", - [OperatorSpecEditMode("hide", {}, "EDGE", {1})]), - SpecMeshTest("HideVertex", "testCubeHideVertex", "expectedCubeHideVertex", - [OperatorSpecEditMode("hide", {}, "VERT", {0})]), + SpecMeshTest( + "HideFace", "testCubeHideFace", "expectedCubeHideFace", + [OperatorSpecEditMode("hide", {}, "FACE", {3})], + ), + SpecMeshTest( + "HideEdge", "testCubeHideEdge", "expectedCubeHideEdge", + [OperatorSpecEditMode("hide", {}, "EDGE", {1})], + ), + SpecMeshTest( + "HideVertex", "testCubeHideVertex", "expectedCubeHideVertex", + [OperatorSpecEditMode("hide", {}, "VERT", {0})], + ), # inset faces - SpecMeshTest("CubeInset", - "testCubeInset", "expectedCubeInset", [OperatorSpecEditMode("inset", {"thickness": 0.2}, "VERT", - {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, - 52, - 59, 61, 62, 65, 83, 91, 95})]), - SpecMeshTest("CubeInsetEvenOffsetFalse", - "testCubeInsetEvenOffsetFalse", "expectedCubeInsetEvenOffsetFalse", - [OperatorSpecEditMode("inset", {"thickness": 0.2, "use_even_offset": False}, "VERT", - {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, 62, 65, 83, 91, 95})]), - SpecMeshTest("CubeInsetDepth", - "testCubeInsetDepth", - "expectedCubeInsetDepth", [OperatorSpecEditMode("inset", {"thickness": 0.2, "depth": 0.2}, "VERT", - {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, - 62, - 65, 83, 91, 95})]), - SpecMeshTest("GridInsetRelativeOffset", "testGridInsetRelativeOffset", - "expectedGridInsetRelativeOffset", - [OperatorSpecEditMode("inset", {"thickness": 0.4, - "use_relative_offset": True}, "FACE", - {35, 36, 37, 45, 46, 47, 55, 56, 57})]), + SpecMeshTest( + "CubeInset", + "testCubeInset", "expectedCubeInset", [OperatorSpecEditMode("inset", {"thickness": 0.2}, "VERT", + {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, + 52, + 59, 61, 62, 65, 83, 91, 95})], + ), + SpecMeshTest( + "CubeInsetEvenOffsetFalse", + "testCubeInsetEvenOffsetFalse", "expectedCubeInsetEvenOffsetFalse", + [OperatorSpecEditMode("inset", {"thickness": 0.2, "use_even_offset": False}, "VERT", + {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, 62, 65, 83, 91, 95})], + ), + SpecMeshTest( + "CubeInsetDepth", + "testCubeInsetDepth", + "expectedCubeInsetDepth", [OperatorSpecEditMode("inset", {"thickness": 0.2, "depth": 0.2}, "VERT", + {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, + 62, + 65, 83, 91, 95})], + ), + SpecMeshTest( + "GridInsetRelativeOffset", "testGridInsetRelativeOffset", + "expectedGridInsetRelativeOffset", + [OperatorSpecEditMode("inset", {"thickness": 0.4, + "use_relative_offset": True}, "FACE", + {35, 36, 37, 45, 46, 47, 55, 56, 57})], + ), # loop multi select - SpecMeshTest("MokeyLoopMultiSelect", "testMonkeyLoopMultiSelect", "expectedMonkeyLoopMultiSelect", - [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {355, 359, 73, 301, 302})]), - SpecMeshTest("HoledGridLoopMultiSelect", "testGridLoopMultiSelect", "expectedGridLoopMultiSelect", - [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {257, 169, 202, 207, 274, 278, 63})]), - SpecMeshTest("EmptyMeshLoopMultiSelect", "testEmptyMeshLoopMultiSelect", "expectedEmptyMeshLoopMultiSelect", - [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {})]), + SpecMeshTest( + "MokeyLoopMultiSelect", "testMonkeyLoopMultiSelect", "expectedMonkeyLoopMultiSelect", + [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {355, 359, 73, 301, 302})], + ), + SpecMeshTest( + "HoledGridLoopMultiSelect", "testGridLoopMultiSelect", "expectedGridLoopMultiSelect", + [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {257, 169, 202, 207, 274, 278, 63})], + ), + SpecMeshTest( + "EmptyMeshLoopMultiSelect", "testEmptyMeshLoopMultiSelect", "expectedEmptyMeshLoopMultiSelect", + [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {})], + ), # mark seam - SpecMeshTest("CubeMarkSeam", "testCubeMarkSeam", "expectedCubeMarkSeam", - [OperatorSpecEditMode("mark_seam", {}, "EDGE", {1})]), + SpecMeshTest( + "CubeMarkSeam", "testCubeMarkSeam", "expectedCubeMarkSeam", + [OperatorSpecEditMode("mark_seam", {}, "EDGE", {1})], + ), # select all - SpecMeshTest("CircleSelectAll", "testCircleSelectAll", "expectedCircleSelectAll", - [OperatorSpecEditMode("select_all", {}, "VERT", {1})]), - SpecMeshTest("IsolatedVertsSelectAll", "testIsolatedVertsSelectAll", "expectedIsolatedVertsSelectAll", - [OperatorSpecEditMode("select_all", {}, "VERT", {})]), - SpecMeshTest("EmptyMeshSelectAll", "testEmptyMeshSelectAll", "expectedEmptyMeshSelectAll", - [OperatorSpecEditMode("select_all", {}, "VERT", {})]), + SpecMeshTest( + "CircleSelectAll", "testCircleSelectAll", "expectedCircleSelectAll", + [OperatorSpecEditMode("select_all", {}, "VERT", {1})], + ), + SpecMeshTest( + "IsolatedVertsSelectAll", "testIsolatedVertsSelectAll", "expectedIsolatedVertsSelectAll", + [OperatorSpecEditMode("select_all", {}, "VERT", {})], + ), + SpecMeshTest( + "EmptyMeshSelectAll", "testEmptyMeshSelectAll", "expectedEmptyMeshSelectAll", + [OperatorSpecEditMode("select_all", {}, "VERT", {})], + ), # select axis - Cannot be tested. Needs active vert selection # SpecMeshTest("MonkeySelectAxisX", "testMonkeySelectAxisX", "expectedMonkeySelectAxisX", @@ -249,120 +359,183 @@ def main(): # OperatorSpecEditMode("select_axis", {"axis": "Z", "sign": "NEG"}, "FACE", {})]), # select faces by sides - SpecMeshTest("CubeSelectFacesBySide", "testCubeSelectFacesBySide", "expectedCubeSelectFacesBySide", - [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]), - SpecMeshTest("CubeSelectFacesBySideGreater", "testCubeSelectFacesBySideGreater", "expectedCubeSelectFacesBySideGreater", - [OperatorSpecEditMode("select_face_by_sides", {"number": 4, "type": "GREATER", "extend": True}, "FACE", {})]), - SpecMeshTest("CubeSelectFacesBySideLess", "testCubeSelectFacesBySideLess", "expectedCubeSelectFacesBySideLess", - [OperatorSpecEditMode("select_face_by_sides", {"number": 4, "type": "GREATER", "extend": True}, "FACE", {})]), + SpecMeshTest( + "CubeSelectFacesBySide", "testCubeSelectFacesBySide", "expectedCubeSelectFacesBySide", + [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})], + ), + SpecMeshTest( + "CubeSelectFacesBySideGreater", "testCubeSelectFacesBySideGreater", "expectedCubeSelectFacesBySideGreater", + [OperatorSpecEditMode("select_face_by_sides", {"number": 4, + "type": "GREATER", "extend": True}, "FACE", {})], + ), + SpecMeshTest( + "CubeSelectFacesBySideLess", "testCubeSelectFacesBySideLess", "expectedCubeSelectFacesBySideLess", + [OperatorSpecEditMode("select_face_by_sides", {"number": 4, + "type": "GREATER", "extend": True}, "FACE", {})], + ), # select interior faces - SpecMeshTest("CubeSelectInteriorFaces", "testCubeSelectInteriorFaces", "expectedCubeSelectInteriorFaces", - [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]), - SpecMeshTest("HoledCubeSelectInteriorFaces", "testHoledCubeSelectInteriorFaces", "expectedHoledCubeSelectInteriorFaces", - [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]), - SpecMeshTest("EmptyMeshSelectInteriorFaces", "testEmptyMeshSelectInteriorFaces", "expectedEmptyMeshSelectInteriorFaces", - [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]), + SpecMeshTest( + "CubeSelectInteriorFaces", "testCubeSelectInteriorFaces", "expectedCubeSelectInteriorFaces", + [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})], + ), + SpecMeshTest( + "HoledCubeSelectInteriorFaces", "testHoledCubeSelectInteriorFaces", "expectedHoledCubeSelectInteriorFaces", + [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})], + ), + SpecMeshTest( + "EmptyMeshSelectInteriorFaces", "testEmptyMeshSelectInteriorFaces", "expectedEmptyMeshSelectInteriorFaces", + [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})], + ), # select less - SpecMeshTest("MonkeySelectLess", "testMonkeySelectLess", "expectedMonkeySelectLess", - [OperatorSpecEditMode("select_less", {}, "VERT", {2, 8, 24, 34, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 68, - 69, 70, 71, 74, 75, 78, 80, 81, 82, 83, 90, 91, 93, 95, 97, 99, - 101, 109, 111, 115, 117, 119, 121, 123, 125, 127, 129, 130, 131, - 132, 133, 134, 135, 136, 138, 141, 143, 145, 147, 149, 151, 153, - 155, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, - 175, 176, 177, 178, 181, 182, 184, 185, 186, 187, 188, 189, 190, - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, - 206, 207, 208, 210, 216, 217, 218, 219, 220, 221, 222, 229, 230, - 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, - 257, 259, 263, 267, 269, 271, 275, 277, 289, 291, 293, 295, 309, - 310, 311, 312, 316, 317, 318, 319, 320, 323, 325, 327, 329, 331, - 341, 347, 349, 350, 351, 354, 356, 359, 361, 363, 365, 367, 369, - 375, 379, 381, 382, 385, 386, 387, 388, 389, 390, 391, 392, 393, - 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, - 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, - 420, 421, 423, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, - 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, - 448, 449, 450, 451, 452, 454, 455, 456, 457, 458, 459, 460, 461, - 462, 463, 464, 471, 473, 474, 475, 476, 477, 478, 479, 480, 481, - 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 495, - 496, 497, 498, 499, 502, 505})]), - SpecMeshTest("HoledCubeSelectLess", "testHoledCubeSelectLess", "expectedHoledCubeSelectLess", - [OperatorSpecEditMode("select_face_by_sides", {}, "FACE", {})]), - SpecMeshTest("EmptyMeshSelectLess", "testEmptyMeshSelectLess", "expectedEmptyMeshSelectLess", - [OperatorSpecEditMode("select_face_by_sides", {}, "VERT", {})]), + SpecMeshTest( + "MonkeySelectLess", "testMonkeySelectLess", "expectedMonkeySelectLess", + [OperatorSpecEditMode("select_less", {}, "VERT", { + 2, 8, 24, 34, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 68, + 69, 70, 71, 74, 75, 78, 80, 81, 82, 83, 90, 91, 93, 95, 97, 99, + 101, 109, 111, 115, 117, 119, 121, 123, 125, 127, 129, 130, 131, + 132, 133, 134, 135, 136, 138, 141, 143, 145, 147, 149, 151, 153, + 155, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, + 175, 176, 177, 178, 181, 182, 184, 185, 186, 187, 188, 189, 190, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 206, 207, 208, 210, 216, 217, 218, 219, 220, 221, 222, 229, 230, + 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, + 257, 259, 263, 267, 269, 271, 275, 277, 289, 291, 293, 295, 309, + 310, 311, 312, 316, 317, 318, 319, 320, 323, 325, 327, 329, 331, + 341, 347, 349, 350, 351, 354, 356, 359, 361, 363, 365, 367, 369, + 375, 379, 381, 382, 385, 386, 387, 388, 389, 390, 391, 392, 393, + 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, + 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, + 420, 421, 423, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, + 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, + 448, 449, 450, 451, 452, 454, 455, 456, 457, 458, 459, 460, 461, + 462, 463, 464, 471, 473, 474, 475, 476, 477, 478, 479, 480, 481, + 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 495, + 496, 497, 498, 499, 502, 505, + })], + ), + SpecMeshTest( + "HoledCubeSelectLess", "testHoledCubeSelectLess", "expectedHoledCubeSelectLess", + [OperatorSpecEditMode("select_face_by_sides", {}, "FACE", {})], + ), + SpecMeshTest( + "EmptyMeshSelectLess", "testEmptyMeshSelectLess", "expectedEmptyMeshSelectLess", + [OperatorSpecEditMode("select_face_by_sides", {}, "VERT", {})], + ), # select linked - SpecMeshTest("PlanesSelectLinked", "testPlanesSelectLinked", "expectedPlanesSelectedLinked", - [OperatorSpecEditMode("select_linked", {}, "VERT", {7})]), - SpecMeshTest("CubesSelectLinked", "testCubesSelectLinked", "expectedCubesSelectLinked", - [OperatorSpecEditMode("select_linked", {}, "VERT", {11})]), - SpecMeshTest("EmptyMeshSelectLinked", "testEmptyMeshSelectLinked", "expectedEmptyMeshSelectLinked", - [OperatorSpecEditMode("select_linked", {}, "VERT", {})]), + SpecMeshTest( + "PlanesSelectLinked", "testPlanesSelectLinked", "expectedPlanesSelectedLinked", + [OperatorSpecEditMode("select_linked", {}, "VERT", {7})], + ), + SpecMeshTest( + "CubesSelectLinked", "testCubesSelectLinked", "expectedCubesSelectLinked", + [OperatorSpecEditMode("select_linked", {}, "VERT", {11})], + ), + SpecMeshTest( + "EmptyMeshSelectLinked", "testEmptyMeshSelectLinked", "expectedEmptyMeshSelectLinked", + [OperatorSpecEditMode("select_linked", {}, "VERT", {})], + ), # select nth (checkered deselect) - SpecMeshTest("CircleSelect2nd", "testCircleSelect2nd", "expectedCircleSelect2nd", - [OperatorSpecEditMode("select_nth", {}, "VERT", {i for i in range(32)})]), + SpecMeshTest( + "CircleSelect2nd", "testCircleSelect2nd", "expectedCircleSelect2nd", + [OperatorSpecEditMode("select_nth", {}, "VERT", {i for i in range(32)})], + ), # Subdivide edgering - Not currently functional, operator returns inconsistently - #SpecMeshTest("SubdivideEdgeringSurface", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringSurface", + # SpecMeshTest("SubdivideEdgeringSurface", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringSurface", # [OperatorSpecEditMode("subdivide_edgering", {"number_cuts": 5, "interpolation": 'SURFACE', "profile_shape_factor": 0.1}, "EDGE", {0, (i for i in range(96) if (i % 3))})]), - #SpecMeshTest("SubdivideEdgeringPath", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringPath", + # SpecMeshTest("SubdivideEdgeringPath", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringPath", # [OperatorSpecEditMode("subdivide_edgering", {"number_cuts": 5, "interpolation": 'PATH', "profile_shape_factor": 0.1}, "EDGE", {0, (i for i in range(96) if (i % 3))})]), - #SpecMeshTest("SubdivideEdgeringLinear", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringLinear", + # SpecMeshTest("SubdivideEdgeringLinear", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringLinear", # [OperatorSpecEditMode("subdivide_edgering", {"number_cuts": 5, "interpolation": 'LINEAR', "profile_shape_factor": 0.1}, "EDGE", {0, (i for i in range(96) if (i % 3))})]), # Symmetry Snap - SpecMeshTest("SymmetrySnap", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnap", - [OperatorSpecEditMode("symmetry_snap", {"direction": 'POSITIVE_X', "threshold": 1, "factor": 0.75, - "use_center": False}, "VERT", {i for i in range(5)})]), - SpecMeshTest("SymmetrySnapCenter", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnapCenter", - [OperatorSpecEditMode("symmetry_snap", {"direction": 'NEGATIVE_X', "threshold": 1, "factor": 0.75, - "use_center": True}, "VERT", {i for i in range(5)})]), + SpecMeshTest( + "SymmetrySnap", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnap", + [OperatorSpecEditMode("symmetry_snap", {"direction": 'POSITIVE_X', "threshold": 1, "factor": 0.75, + "use_center": False}, "VERT", {i for i in range(5)})], + ), + SpecMeshTest( + "SymmetrySnapCenter", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnapCenter", + [OperatorSpecEditMode("symmetry_snap", {"direction": 'NEGATIVE_X', "threshold": 1, "factor": 0.75, + "use_center": True}, "VERT", {i for i in range(5)})], + ), # Tris to Quads - SpecMeshTest("TrisToQuads", "testPlanesTrisToQuad", "expectedPlanesTrisToQuad", - [OperatorSpecEditMode("tris_convert_to_quads", {"face_threshold":0.174533, "shape_threshold":0.174533, - "uvs":True, "vcols":True, "seam":True, "sharp":True, "materials":True}, "VERT", {i for i in range(32)})]), + SpecMeshTest( + "TrisToQuads", "testPlanesTrisToQuad", "expectedPlanesTrisToQuad", + [OperatorSpecEditMode("tris_convert_to_quads", {"face_threshold": 0.174533, "shape_threshold": 0.174533, + "uvs": True, "vcols": True, "seam": True, "sharp": True, "materials": True}, "VERT", {i for i in range(32)})], + ), # unsubdivide # normal case - SpecMeshTest("CubeFaceUnsubdivide", "testCubeUnsubdivide", "expectedCubeUnsubdivide", - [OperatorSpecEditMode("unsubdivide", {}, "FACE", {i for i in range(6)})]), + SpecMeshTest( + "CubeFaceUnsubdivide", "testCubeUnsubdivide", "expectedCubeUnsubdivide", + [OperatorSpecEditMode("unsubdivide", {}, "FACE", {i for i in range(6)})], + ), # UV Manipulation - SpecMeshTest("UVRotate", "testCubeUV", "expectedCubeUVRotate", - [OperatorSpecEditMode("uvs_rotate", {}, "FACE", {2})]), - SpecMeshTest("UVRotateCCW", "testCubeUV", "expectedCubeUVRotateCCW", - [OperatorSpecEditMode("uvs_rotate", {"use_ccw": True}, "FACE", {2})]), - SpecMeshTest("UVReverse", "testCubeUV", "expectedCubeUVReverse", - [OperatorSpecEditMode("uvs_reverse", {}, "FACE", {2})]), - SpecMeshTest("UVAdd", "testCubeUV", "expectedCubeUVAdd", - [OperatorSpecEditMode("uv_texture_add", {}, "FACE", {})]), - SpecMeshTest("UVRemove", "testCubeUV", "expectedCubeUVRemove", - [OperatorSpecEditMode("uv_texture_remove", {}, "FACE", {})]), + SpecMeshTest( + "UVRotate", "testCubeUV", "expectedCubeUVRotate", + [OperatorSpecEditMode("uvs_rotate", {}, "FACE", {2})], + ), + SpecMeshTest( + "UVRotateCCW", "testCubeUV", "expectedCubeUVRotateCCW", + [OperatorSpecEditMode("uvs_rotate", {"use_ccw": True}, "FACE", {2})], + ), + SpecMeshTest( + "UVReverse", "testCubeUV", "expectedCubeUVReverse", + [OperatorSpecEditMode("uvs_reverse", {}, "FACE", {2})], + ), + SpecMeshTest( + "UVAdd", "testCubeUV", "expectedCubeUVAdd", + [OperatorSpecEditMode("uv_texture_add", {}, "FACE", {})], + ), + SpecMeshTest( + "UVRemove", "testCubeUV", "expectedCubeUVRemove", + [OperatorSpecEditMode("uv_texture_remove", {}, "FACE", {})], + ), # Vert Connect Concave - SpecMeshTest("VertexConnectConcave", "testPlaneVertConnectConcave", "expectedPlaneVertConnectConcave", - [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})]), - SpecMeshTest("VertexConnectConcaveConvexPentagon", "testPentagonVertConnectConcave", "expectedPentagonVertConnectConcave", - [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})]), - SpecMeshTest("VertexConnectConcaveQuad", "testPlaneVertConnectConcaveQuad", "expectedPlaneVertConnectConcaveQuad", - [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})]), + SpecMeshTest( + "VertexConnectConcave", "testPlaneVertConnectConcave", "expectedPlaneVertConnectConcave", + [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})], + ), + SpecMeshTest( + "VertexConnectConcaveConvexPentagon", "testPentagonVertConnectConcave", "expectedPentagonVertConnectConcave", + [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})], + ), + SpecMeshTest( + "VertexConnectConcaveQuad", "testPlaneVertConnectConcaveQuad", "expectedPlaneVertConnectConcaveQuad", + [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})], + ), # Vert Connect Nonplanar - SpecMeshTest("VertexConnectNonplanar", "testPlaneVertConnectNonplanar", "expectedPlaneVertConnectNonplanar", - [OperatorSpecEditMode("vert_connect_nonplanar", {"angle_limit": 0.17453292}, "VERT", {i for i in range(9)})]), - SpecMeshTest("VertexConnectNonplanarNgon", "testPlaneVertConnectNonplanarNgon", "expectedPlaneVertConnectNonplanarNgon", - [OperatorSpecEditMode("vert_connect_nonplanar", {"angle_limit": 0.218166}, "VERT", {i for i in range(6)})]), + SpecMeshTest( + "VertexConnectNonplanar", "testPlaneVertConnectNonplanar", "expectedPlaneVertConnectNonplanar", + [OperatorSpecEditMode("vert_connect_nonplanar", { + "angle_limit": 0.17453292}, "VERT", {i for i in range(9)})], + ), + SpecMeshTest( + "VertexConnectNonplanarNgon", "testPlaneVertConnectNonplanarNgon", "expectedPlaneVertConnectNonplanarNgon", + [OperatorSpecEditMode("vert_connect_nonplanar", {"angle_limit": 0.218166}, "VERT", {i for i in range(6)})], + ), # T87259 - test cases - SpecMeshTest("CubeEdgeUnsubdivide", "testCubeEdgeUnsubdivide", "expectedCubeEdgeUnsubdivide", - [OperatorSpecEditMode("unsubdivide", {}, "EDGE", {i for i in range(6)})]), - SpecMeshTest("UVSphereUnsubdivide", "testUVSphereUnsubdivide", "expectedUVSphereUnsubdivide", - [OperatorSpecEditMode("unsubdivide", {'iterations': 9}, "FACE", {i for i in range(512)})]), + SpecMeshTest( + "CubeEdgeUnsubdivide", "testCubeEdgeUnsubdivide", "expectedCubeEdgeUnsubdivide", + [OperatorSpecEditMode("unsubdivide", {}, "EDGE", {i for i in range(6)})], + ), + SpecMeshTest( + "UVSphereUnsubdivide", "testUVSphereUnsubdivide", "expectedUVSphereUnsubdivide", + [OperatorSpecEditMode("unsubdivide", {'iterations': 9}, "FACE", {i for i in range(512)})], + ), # vert connect path # Tip: It works only if there is an already existing face or more than 2 vertices. @@ -395,23 +568,32 @@ def main(): # Laplacian Smooth SpecMeshTest( "LaplacianSmoothDefault", "testSphereLaplacianSmoothDefault", "expectedSphereLaplacianSmoothDefault", - [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False}, "VERT", {i for i in range(482)})], + [OperatorSpecEditMode("vertices_smooth_laplacian", { + "preserve_volume": False}, "VERT", {i for i in range(482)})], ), SpecMeshTest( "LaplacianSmoothHighValues", "testSphereLaplacianSmoothHigh", "expectedSphereLaplacianSmoothHigh", - [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False, "repeat": 100, "lambda_factor": 10.0}, "VERT", {i for i in range(482)})], + [OperatorSpecEditMode("vertices_smooth_laplacian", + {"preserve_volume": False, + "repeat": 100, + "lambda_factor": 10.0}, + "VERT", + {i for i in range(482)})], ), SpecMeshTest( "LaplacianSmoothBorder", "testCubeLaplacianSmoothBorder", "expectedCubeLaplacianSmoothBorder", - [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False, "lambda_border": 1.0}, "VERT", {i for i in range(25)})], + [OperatorSpecEditMode("vertices_smooth_laplacian", { + "preserve_volume": False, "lambda_border": 1.0}, "VERT", {i for i in range(25)})], ), SpecMeshTest( "LaplacianSmoothHighBorder", "testCubeLaplacianSmoothHighBorder", "expectedCubeLaplacianSmoothHighBorder", - [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False, "lambda_border": 100.0}, "VERT", {i for i in range(25)})], + [OperatorSpecEditMode("vertices_smooth_laplacian", { + "preserve_volume": False, "lambda_border": 100.0}, "VERT", {i for i in range(25)})], ), SpecMeshTest( "LaplacianSmoothPreserveVolume", "testSphereLaplacianSmoothPreserveVol", "expectedSphereLaplacianSmoothPreserveVol", - [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": True}, "VERT", {i for i in range(482)})], + [OperatorSpecEditMode("vertices_smooth_laplacian", { + "preserve_volume": True}, "VERT", {i for i in range(482)})], ), diff --git a/tests/python/physics_cloth.py b/tests/python/physics_cloth.py index c507a85c087..e453b4dd68b 100644 --- a/tests/python/physics_cloth.py +++ b/tests/python/physics_cloth.py @@ -15,7 +15,7 @@ def main(): test = [ SpecMeshTest("ClothSimple", "testClothPlane", "expectedClothPlane", - [ModifierSpec('Cloth', 'CLOTH', {'settings': {'quality': 5}}, 15)], threshold=1e-3), + [ModifierSpec('Cloth', 'CLOTH', {'settings': {'quality': 5}}, 15)], threshold=1e-3), # Not reproducible # SpecMeshTest("ClothPressure", "testObjClothPressure", "expObjClothPressure", @@ -27,7 +27,7 @@ def main(): # [ModifierSpec('Cloth', 'CLOTH', {'collision_settings': {'use_self_collision': True}}, 67)]), SpecMeshTest("ClothSpring", "testTorusClothSpring", "expTorusClothSpring", - [ModifierSpec('Cloth2', 'CLOTH', {'settings': {'use_internal_springs': True}}, 10)], threshold=1e-3), + [ModifierSpec('Cloth2', 'CLOTH', {'settings': {'use_internal_springs': True}}, 10)], threshold=1e-3), ] cloth_test = RunTest(test) diff --git a/tests/python/physics_dynamic_paint.py b/tests/python/physics_dynamic_paint.py index f5fabb23adc..57b96ccffba 100644 --- a/tests/python/physics_dynamic_paint.py +++ b/tests/python/physics_dynamic_paint.py @@ -15,10 +15,10 @@ def main(): test = [ SpecMeshTest("DynamicPaintSimple", "testObjDynamicPaintPlane", "expObjDynamicPaintPlane", - [ModifierSpec('dynamic_paint', 'DYNAMIC_PAINT', - {'ui_type': 'CANVAS', - 'canvas_settings': {'canvas_surfaces': {'surface_type': 'WAVE', 'frame_end': 15}}}, - 15)]), + [ModifierSpec('dynamic_paint', 'DYNAMIC_PAINT', + {'ui_type': 'CANVAS', + 'canvas_settings': {'canvas_surfaces': {'surface_type': 'WAVE', 'frame_end': 15}}}, + 15)]), ] dynamic_paint_test = RunTest(test) diff --git a/tests/python/physics_ocean.py b/tests/python/physics_ocean.py index 7a02fc9de4b..20d563f782b 100644 --- a/tests/python/physics_ocean.py +++ b/tests/python/physics_ocean.py @@ -15,7 +15,7 @@ def main(): test = [ # World coordinates of test and expected object should be same. SpecMeshTest("PlaneOcean", "testObjPlaneOcean", "expObjPlaneOcean", - [ModifierSpec('Ocean', 'OCEAN', {})]), + [ModifierSpec('Ocean', 'OCEAN', {})]), ] ocean_test = RunTest(test) diff --git a/tests/python/physics_particle_instance.py b/tests/python/physics_particle_instance.py index d31ce68c7b2..353c0d868c8 100644 --- a/tests/python/physics_particle_instance.py +++ b/tests/python/physics_particle_instance.py @@ -15,8 +15,8 @@ def main(): test = [ SpecMeshTest("ParticleInstanceSimple", "testParticleInstance", "expectedParticleInstance", - [ModifierSpec('ParticleInstance', 'PARTICLE_INSTANCE', {'object': bpy.data.objects['Cube']})], - threshold=1e-3), + [ModifierSpec('ParticleInstance', 'PARTICLE_INSTANCE', {'object': bpy.data.objects['Cube']})], + threshold=1e-3), ] particle_instance_test = RunTest(test) diff --git a/tests/python/physics_particle_system.py b/tests/python/physics_particle_system.py index d304d11a894..f558df0aeb7 100644 --- a/tests/python/physics_particle_system.py +++ b/tests/python/physics_particle_system.py @@ -13,8 +13,8 @@ from modules.mesh_test import RunTest, ParticleSystemSpec, SpecMeshTest def main(): test = [ SpecMeshTest("ParticleSystemTest", "testParticleSystem", "expParticleSystem", - [ParticleSystemSpec('Particles', 'PARTICLE_SYSTEM', {'render_type': "OBJECT", - 'instance_object': bpy.data.objects['Cube']}, 20)], threshold=1e-3), + [ParticleSystemSpec('Particles', 'PARTICLE_SYSTEM', {'render_type': "OBJECT", + 'instance_object': bpy.data.objects['Cube']}, 20)], threshold=1e-3), ] particle_test = RunTest(test) diff --git a/tests/python/physics_softbody.py b/tests/python/physics_softbody.py index b167529ccca..00d2a637cf7 100644 --- a/tests/python/physics_softbody.py +++ b/tests/python/physics_softbody.py @@ -15,9 +15,9 @@ def main(): test = [ SpecMeshTest("SoftBodySimple", "testSoftBody", "expectedSoftBody", - [ModifierSpec('Softbody', 'SOFT_BODY', - {'settings': {'use_goal': False, 'bend': 8, 'pull': 0.8, 'push': 0.8}}, - 45)]), + [ModifierSpec('Softbody', 'SOFT_BODY', + {'settings': {'use_goal': False, 'bend': 8, 'pull': 0.8, 'push': 0.8}}, + 45)]), ] soft_body_test = RunTest(test) |