Compare commits
348 Commits
spreadshee
...
temp-attri
Author | SHA1 | Date | |
---|---|---|---|
3447b31a31 | |||
6e4b10d1b2 | |||
81b76f8daf | |||
29d11cad54 | |||
ed04957d76 | |||
89bf629589 | |||
7834fcc67d | |||
2125ee4305 | |||
b2c6eb8640 | |||
27cfc1ea11 | |||
1703831f62 | |||
6e8cb545b3 | |||
2efd3509ee | |||
db0b1cab1f | |||
8c0f7d1772 | |||
435e9393e9 | |||
5d2ec2bc08 | |||
8c5c55b8c9 | |||
5e82e77112 | |||
b760481f8d | |||
72fec0f7c5 | |||
7ccd19fc12 | |||
ebe04bd3ca | |||
985ccba77c | |||
795f024558 | |||
0566ebdebe | |||
2d1d6fbb23 | |||
d1fbf1599f | |||
b42454be8b | |||
68cbf0a2be | |||
cedd8b8c56 | |||
c13b3dd168 | |||
d7caae56c4 | |||
2dc5961874 | |||
b8b7b47a00 | |||
458cbcbfea | |||
6f761c7110 | |||
a62c1d7700 | |||
8032bd98d8 | |||
f99f884444 | |||
feedf28549 | |||
4402c2324b | |||
e1acefd45e | |||
46a13482cb | |||
33440760a6 | |||
8245805ce3 | |||
33f218fa3f | |||
eb06ccc324 | |||
c75b2019e1 | |||
![]() |
799f532f46 | ||
112fb77157 | |||
1d96493c2f | |||
969cc22a89 | |||
eae39a4973 | |||
6dffdb02fa | |||
3b4b231be5 | |||
0b903755a9 | |||
9175911ffa | |||
0af28f007f | |||
950d8360f8 | |||
1825e67dea | |||
c8dd684b65 | |||
6879868814 | |||
063c9938f1 | |||
9a69653b42 | |||
06888a8970 | |||
76eae59648 | |||
01448ee7ce | |||
8d30a7a1cf | |||
4bce9c5283 | |||
1544bcfc37 | |||
4e1507bd3b | |||
1fc446a908 | |||
aca9a1bac3 | |||
ebcf49fe1a | |||
2d5a715f44 | |||
3516ee5a14 | |||
ddbfae7a0d | |||
1a010450bc | |||
eff2b89446 | |||
686452fb1b | |||
e4990a5658 | |||
fa2c00ae91 | |||
d0d85742fc | |||
5cf6f570c6 | |||
4dca44086f | |||
d9224f64a1 | |||
3608891282 | |||
e524a6ecf7 | |||
fc37b265c8 | |||
b2a0f69275 | |||
7eda4cde71 | |||
463b38b0e0 | |||
cf6d10ef46 | |||
0f81dafe6c | |||
537460b86d | |||
0bf630493f | |||
c7a8bcfa37 | |||
fa4b2d25cb | |||
bfc0f483c6 | |||
7bbead1e87 | |||
7aa38de085 | |||
0edfa5350e | |||
278b19745b | |||
afd8e4bce7 | |||
be34d14575 | |||
68c4ba3482 | |||
1266df87c8 | |||
bb9c83b9ff | |||
ea7eff1cc8 | |||
4bef49e32b | |||
ca37d8485c | |||
![]() |
58818cba40 | ||
a2e4d81849 | |||
382b06c80c | |||
45eafd6562 | |||
073ef4d265 | |||
919558854d | |||
43b08d0578 | |||
cb4646a6df | |||
dc8a43c875 | |||
0567f2b0bb | |||
43baf38758 | |||
62bff15377 | |||
ec241eb0d0 | |||
fa8d566c3b | |||
018fa1fce3 | |||
32d3b07b03 | |||
09eb04c0a8 | |||
5425388e60 | |||
ced26bacb7 | |||
a4877f9e54 | |||
27005f58c5 | |||
bd8fd78b40 | |||
5b2353b230 | |||
51991ffd38 | |||
6e39da7948 | |||
d169314f9b | |||
61c3d7aa2c | |||
ecc2db8a3a | |||
27e13a608f | |||
5c067189e3 | |||
4cf3010714 | |||
05dbbd83f0 | |||
2c3a9caffe | |||
5ec39fc2e4 | |||
bbea79ce5e | |||
3810bcc160 | |||
0bac768223 | |||
adb21faa83 | |||
2cc3a89cf6 | |||
788a28251b | |||
ac90c8a774 | |||
4dd32f94aa | |||
![]() |
7c18fb062d | ||
71eaf872c2 | |||
![]() |
d705335c2b | ||
b9207fb43d | |||
9ca55b10b8 | |||
![]() |
a425b2b25c | ||
6776b74d0a | |||
937b843944 | |||
74cbe6d52c | |||
e45a57656c | |||
d63b72e9f9 | |||
170293475c | |||
86915d04ee | |||
1a81693d38 | |||
4baf3964ae | |||
d851fc3ad6 | |||
5936ef986a | |||
b8479a70c9 | |||
1f2a801044 | |||
7cbd66d42f | |||
93d8c7516a | |||
961b6a6f7e | |||
8c2c49ff9f | |||
1534da457e | |||
2f367db2cc | |||
![]() |
5b08cbae51 | ||
c50d55dd34 | |||
30ee57be60 | |||
721ff6ad12 | |||
e9c548c3c3 | |||
9b15c552cc | |||
7b9d94e073 | |||
f5060bc901 | |||
8d9fd0427d | |||
1e8a808910 | |||
ec20b21d04 | |||
1c357a3c5f | |||
2b9e6943cd | |||
d2f55be7bb | |||
ce259ca382 | |||
e45389c1a1 | |||
2bd9f9d976 | |||
cbd1932619 | |||
![]() |
15253d18b7 | ||
c037a02096 | |||
e96f0d2e2b | |||
![]() |
95e010a22e | ||
53f277a2e6 | |||
71cb0bdc43 | |||
75642b4cfd | |||
175c1382da | |||
1a4d0fa72d | |||
59f49072d0 | |||
2a14ab998a | |||
3a05135e12 | |||
b58c1f6b1c | |||
b60c168e43 | |||
cf8773b525 | |||
09d7d88cc4 | |||
d3cb1d845b | |||
1241e91707 | |||
f9c0d7261a | |||
1198b187b5 | |||
947ba0d27b | |||
0e3bc2e321 | |||
ed5507de8a | |||
3bd892a74c | |||
c9e054c5de | |||
f337310b43 | |||
0515ff70ec | |||
a272eb38f4 | |||
e3a06420b2 | |||
a631a9eb1f | |||
7ab8a3838c | |||
87bfa2b207 | |||
e4a2fc57d5 | |||
![]() |
7cef01b090 | ||
27dcccb20f | |||
3515036319 | |||
bae66609b4 | |||
![]() |
b08394d57a | ||
75491fe100 | |||
22574f741c | |||
2110239547 | |||
3f91591c1d | |||
71da3f31d4 | |||
3d7e3d5ad0 | |||
500045a0d3 | |||
57fe650b76 | |||
0e2a1ef132 | |||
223093ed17 | |||
1ec9ac2016 | |||
fd414b4906 | |||
b51562ed76 | |||
8777a44ccb | |||
a19aa090d2 | |||
18dca6911b | |||
dbb4d51971 | |||
![]() |
3d6798962c | ||
08ae545de4 | |||
2e3b0e8a99 | |||
c6ff722a1f | |||
5e77ff79cc | |||
5c4d24e1fd | |||
cf2baa585c | |||
f031791185 | |||
89b3c9da48 | |||
945b1143df | |||
d244067d12 | |||
76cc8e8436 | |||
ed2639c722 | |||
5e7331f12b | |||
0c0e9390d1 | |||
3249ab70ef | |||
4d43683899 | |||
e92a7800b5 | |||
c1b1ed4d5b | |||
14d74fb341 | |||
b4d6fe1f87 | |||
89e353354f | |||
92dfc8f267 | |||
e1ae5bd45f | |||
8b68a75872 | |||
de6f19ce22 | |||
19dfb6ea1f | |||
0ea66039dd | |||
b7b1b2325c | |||
79ba4fde15 | |||
1a8db9ec04 | |||
22ba85b510 | |||
7c04ef210e | |||
554d921124 | |||
![]() |
965425bcbc | ||
02a7289fe3 | |||
15670ebb95 | |||
2fbee4598c | |||
fd0a0096dd | |||
e0a1a2f49d | |||
![]() |
93114180d7 | ||
b61b34a5d2 | |||
b93c445809 | |||
![]() |
490801ba1d | ||
24d71acd86 | |||
9676a1f61e | |||
19ff2479cf | |||
663a82b10d | |||
e71408d0d7 | |||
![]() |
7334c481c0 | ||
c6ddc2abd3 | |||
9c20228128 | |||
3f3b4745b6 | |||
074b0b6da0 | |||
cf0e7af07e | |||
44c76e4ce3 | |||
50782df425 | |||
3fa580866e | |||
edd2f51b4e | |||
8681504f06 | |||
62f8d9e478 | |||
321eef6a0c | |||
46d75052eb | |||
e0a07700bf | |||
fcc3227efd | |||
0e8fa1d44b | |||
bd9c479475 | |||
8bd0bde012 | |||
8aff86a0c7 | |||
36427a8d03 | |||
b6ab1107c2 | |||
5a491adc17 | |||
210f7f0f8e | |||
a0f705f18c | |||
fa9b05149c | |||
e7a0a75919 | |||
548d16d4a5 | |||
![]() |
3fcac26362 | ||
6578f9d1e9 | |||
a0e1080428 | |||
77f6857740 | |||
d91fec1a85 | |||
c5d9968392 | |||
3ca1cf2b51 | |||
e8573a59f6 | |||
76cdcc2bca | |||
5da5fb31db | |||
2dc16223de | |||
6ec463a4b7 | |||
952c4e41bb | |||
75b675ceab | |||
b5c2c3aba8 | |||
![]() |
f674976edd | ||
7a757662bc | |||
6be964e267 | |||
2a2a4c8a27 |
@@ -35,7 +35,6 @@ Checks: >
|
||||
-modernize-use-auto,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-use-equals-default,
|
||||
-modernize-use-nodiscard,
|
||||
-modernize-loop-convert,
|
||||
-modernize-pass-by-value,
|
||||
|
13
GNUmakefile
13
GNUmakefile
@@ -128,6 +128,9 @@ Utilities
|
||||
* source_archive:
|
||||
Create a compressed archive of the source code.
|
||||
|
||||
* source_archive_complete:
|
||||
Create a compressed archive of the source code and all the libraries of dependencies.
|
||||
|
||||
* update:
|
||||
Updates git and all submodules and svn.
|
||||
|
||||
@@ -477,6 +480,9 @@ check_smatch: .FORCE
|
||||
cd "$(BUILD_DIR)" ; \
|
||||
$(PYTHON) "$(BLENDER_DIR)/build_files/cmake/cmake_static_check_smatch.py"
|
||||
|
||||
check_mypy: .FORCE
|
||||
$(PYTHON) "$(BLENDER_DIR)/source/tools/check_source/check_mypy.py"
|
||||
|
||||
check_spelling_py: .FORCE
|
||||
cd "$(BUILD_DIR)" ; \
|
||||
PYTHONIOENCODING=utf_8 $(PYTHON) \
|
||||
@@ -511,6 +517,13 @@ check_descriptions: .FORCE
|
||||
source_archive: .FORCE
|
||||
python3 ./build_files/utils/make_source_archive.py
|
||||
|
||||
source_archive_complete: .FORCE
|
||||
cmake -S "$(BLENDER_DIR)/build_files/build_environment" -B"$(BUILD_DIR)/source_archive" \
|
||||
-DCMAKE_BUILD_TYPE_INIT:STRING=$(BUILD_TYPE) -DPACKAGE_USE_UPSTREAM_SOURCES=OFF
|
||||
# This assumes CMake is still using a default `PACKAGE_DIR` variable:
|
||||
python3 ./build_files/utils/make_source_archive.py --include-packages "$(BUILD_DIR)/source_archive/packages"
|
||||
|
||||
|
||||
INKSCAPE_BIN?="inkscape"
|
||||
icons: .FORCE
|
||||
BLENDER_BIN=$(BLENDER_BIN) INKSCAPE_BIN=$(INKSCAPE_BIN) \
|
||||
|
@@ -12,7 +12,7 @@ function(download_source dep)
|
||||
if(NOT EXISTS ${TARGET_FILE})
|
||||
message("Checking source : ${dep} - source not found downloading from ${TARGET_URI}")
|
||||
file(DOWNLOAD ${TARGET_URI} ${TARGET_FILE}
|
||||
TIMEOUT 60 # seconds
|
||||
TIMEOUT 1800 # seconds
|
||||
EXPECTED_HASH ${TARGET_HASH_TYPE}=${TARGET_HASH}
|
||||
TLS_VERIFY ON
|
||||
SHOW_PROGRESS
|
||||
|
@@ -68,7 +68,6 @@ set(OPENIMAGEIO_EXTRA_ARGS
|
||||
-DBOOST_LIBRARYDIR=${LIBDIR}/boost/lib/
|
||||
-DBoost_NO_SYSTEM_PATHS=ON
|
||||
-DBoost_NO_BOOST_CMAKE=ON
|
||||
-OIIO_BUILD_CPP11=ON
|
||||
-DUSE_LIBSQUISH=OFF
|
||||
-DUSE_QT5=OFF
|
||||
-DUSE_NUKE=OFF
|
||||
|
@@ -37,14 +37,8 @@ else(BUILD_MODE STREQUAL "Debug")
|
||||
endif()
|
||||
|
||||
set(DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/downloads" CACHE STRING "Path for downloaded files")
|
||||
# look in blenders source folder for packages directory, if that exists
|
||||
# it will our package folder, otherwise it will be in the build folder
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/../../packages")
|
||||
set(PACKAGE_DIR_DEFAULT "${CMAKE_SOURCE_DIR}/../../packages")
|
||||
else()
|
||||
set(PACKAGE_DIR_DEFAULT "${CMAKE_CURRENT_BINARY_DIR}/packages")
|
||||
endif()
|
||||
set(PACKAGE_DIR ${PACKAGE_DIR_DEFAULT} CACHE STRING "Path for downloaded source files")
|
||||
# This path must be hard-coded like this, so that the GNUmakefile knows where it is and can pass it to make_source_archive.py:
|
||||
set(PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/packages")
|
||||
option(PACKAGE_USE_UPSTREAM_SOURCES "Use soures upstream to download the package sources, when OFF the blender mirror will be used" ON)
|
||||
|
||||
file(TO_CMAKE_PATH ${DOWNLOAD_DIR} DOWNLOAD_DIR)
|
||||
|
@@ -63,3 +63,19 @@ diff -Naur org/CMakeLists.txt external_osl/CMakeLists.txt
|
||||
|
||||
set (OSL_NO_DEFAULT_TEXTURESYSTEM OFF CACHE BOOL "Do not use create a raw OIIO::TextureSystem")
|
||||
if (OSL_NO_DEFAULT_TEXTURESYSTEM)
|
||||
diff --git a/src/liboslexec/llvm_util.cpp b/src/liboslexec/llvm_util.cpp
|
||||
index 445f6400..3d468de2 100644
|
||||
--- a/src/liboslexec/llvm_util.cpp
|
||||
+++ b/src/liboslexec/llvm_util.cpp
|
||||
@@ -3430,8 +3430,9 @@ LLVM_Util::call_function (llvm::Value *func, cspan<llvm::Value *> args)
|
||||
#endif
|
||||
//llvm_gen_debug_printf (std::string("start ") + std::string(name));
|
||||
#if OSL_LLVM_VERSION >= 110
|
||||
- OSL_DASSERT(llvm::isa<llvm::Function>(func));
|
||||
- llvm::Value *r = builder().CreateCall(llvm::cast<llvm::Function>(func), llvm::ArrayRef<llvm::Value *>(args.data(), args.size()));
|
||||
+ llvm::Value* r = builder().CreateCall(
|
||||
+ llvm::cast<llvm::FunctionType>(func->getType()->getPointerElementType()), func,
|
||||
+ llvm::ArrayRef<llvm::Value*>(args.data(), args.size()));
|
||||
#else
|
||||
llvm::Value *r = builder().CreateCall (func, llvm::ArrayRef<llvm::Value *>(args.data(), args.size()));
|
||||
#endif
|
||||
|
@@ -85,8 +85,8 @@ class VersionInfo:
|
||||
version_number = int(self._parse_header_file(blender_h, 'BLENDER_VERSION'))
|
||||
version_number_patch = int(self._parse_header_file(blender_h, 'BLENDER_VERSION_PATCH'))
|
||||
version_numbers = (version_number // 100, version_number % 100, version_number_patch)
|
||||
self.short_version = "%d.%02d" % (version_numbers[0], version_numbers[1])
|
||||
self.version = "%d.%02d.%d" % version_numbers
|
||||
self.short_version = "%d.%d" % (version_numbers[0], version_numbers[1])
|
||||
self.version = "%d.%d.%d" % version_numbers
|
||||
self.version_cycle = self._parse_header_file(blender_h, 'BLENDER_VERSION_CYCLE')
|
||||
self.hash = self._parse_header_file(buildinfo_h, 'BUILD_HASH')[1:-1]
|
||||
|
||||
|
@@ -75,7 +75,7 @@ FIND_PATH(OSL_SHADER_DIR
|
||||
/usr/share/OSL/
|
||||
/usr/include/OSL/
|
||||
PATH_SUFFIXES
|
||||
shaders
|
||||
share/OSL/shaders
|
||||
)
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set OSL_FOUND to TRUE if
|
||||
|
@@ -28,6 +28,14 @@ if sys.version_info.major < 3:
|
||||
sys.version.partition(" ")[0])
|
||||
sys.exit(1)
|
||||
|
||||
import os
|
||||
from os.path import (
|
||||
dirname,
|
||||
join,
|
||||
normpath,
|
||||
splitext,
|
||||
)
|
||||
|
||||
from cmake_consistency_check_config import (
|
||||
IGNORE_SOURCE,
|
||||
IGNORE_SOURCE_MISSING,
|
||||
@@ -37,32 +45,35 @@ from cmake_consistency_check_config import (
|
||||
BUILD_DIR,
|
||||
)
|
||||
|
||||
|
||||
import os
|
||||
from os.path import (
|
||||
dirname,
|
||||
join,
|
||||
normpath,
|
||||
splitext,
|
||||
from typing import (
|
||||
Callable,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
|
||||
global_h = set()
|
||||
global_c = set()
|
||||
global_refs = {}
|
||||
global_refs: Dict[str, List[Tuple[str, int]]] = {}
|
||||
|
||||
# Flatten `IGNORE_SOURCE_MISSING` to avoid nested looping.
|
||||
IGNORE_SOURCE_MISSING = [
|
||||
IGNORE_SOURCE_MISSING_FLAT = [
|
||||
(k, ignore_path) for k, ig_list in IGNORE_SOURCE_MISSING
|
||||
for ignore_path in ig_list
|
||||
]
|
||||
|
||||
# Ignore cmake file, path pairs.
|
||||
global_ignore_source_missing = {}
|
||||
for k, v in IGNORE_SOURCE_MISSING:
|
||||
global_ignore_source_missing: Dict[str, List[str]] = {}
|
||||
for k, v in IGNORE_SOURCE_MISSING_FLAT:
|
||||
global_ignore_source_missing.setdefault(k, []).append(v)
|
||||
del IGNORE_SOURCE_MISSING_FLAT
|
||||
|
||||
|
||||
def replace_line(f, i, text, keep_indent=True):
|
||||
def replace_line(f: str, i: int, text: str, keep_indent: bool = True) -> None:
|
||||
file_handle = open(f, 'r')
|
||||
data = file_handle.readlines()
|
||||
file_handle.close()
|
||||
@@ -77,7 +88,10 @@ def replace_line(f, i, text, keep_indent=True):
|
||||
file_handle.close()
|
||||
|
||||
|
||||
def source_list(path, filename_check=None):
|
||||
def source_list(
|
||||
path: str,
|
||||
filename_check: Optional[Callable[[str], bool]] = None,
|
||||
) -> Generator[str, None, None]:
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
# skip '.git'
|
||||
dirnames[:] = [d for d in dirnames if not d.startswith(".")]
|
||||
@@ -88,37 +102,37 @@ def source_list(path, filename_check=None):
|
||||
|
||||
|
||||
# extension checking
|
||||
def is_cmake(filename):
|
||||
def is_cmake(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext == ".cmake") or (filename == "CMakeLists.txt")
|
||||
|
||||
|
||||
def is_c_header(filename):
|
||||
def is_c_header(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext in {".h", ".hpp", ".hxx", ".hh"})
|
||||
|
||||
|
||||
def is_c(filename):
|
||||
def is_c(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl"})
|
||||
|
||||
|
||||
def is_c_any(filename):
|
||||
def is_c_any(filename: str) -> bool:
|
||||
return is_c(filename) or is_c_header(filename)
|
||||
|
||||
|
||||
def cmake_get_src(f):
|
||||
def cmake_get_src(f: str) -> None:
|
||||
|
||||
sources_h = []
|
||||
sources_c = []
|
||||
|
||||
filen = open(f, "r", encoding="utf8")
|
||||
it = iter(filen)
|
||||
it: Optional[Iterator[str]] = iter(filen)
|
||||
found = False
|
||||
i = 0
|
||||
# print(f)
|
||||
|
||||
def is_definition(l, f, i, name):
|
||||
def is_definition(l: str, f: str, i: int, name: str) -> bool:
|
||||
if l.startswith("unset("):
|
||||
return False
|
||||
|
||||
@@ -131,6 +145,7 @@ def cmake_get_src(f):
|
||||
if l.endswith(")"):
|
||||
raise Exception("strict formatting not kept 'list(APPEND %s...)' on 1 line %s:%d" % (name, f, i))
|
||||
return True
|
||||
return False
|
||||
|
||||
while it is not None:
|
||||
context_name = ""
|
||||
@@ -269,7 +284,7 @@ def cmake_get_src(f):
|
||||
filen.close()
|
||||
|
||||
|
||||
def is_ignore_source(f, ignore_used):
|
||||
def is_ignore_source(f: str, ignore_used: List[bool]) -> bool:
|
||||
for index, ignore_path in enumerate(IGNORE_SOURCE):
|
||||
if ignore_path in f:
|
||||
ignore_used[index] = True
|
||||
@@ -277,7 +292,7 @@ def is_ignore_source(f, ignore_used):
|
||||
return False
|
||||
|
||||
|
||||
def is_ignore_cmake(f, ignore_used):
|
||||
def is_ignore_cmake(f: str, ignore_used: List[bool]) -> bool:
|
||||
for index, ignore_path in enumerate(IGNORE_CMAKE):
|
||||
if ignore_path in f:
|
||||
ignore_used[index] = True
|
||||
@@ -285,7 +300,7 @@ def is_ignore_cmake(f, ignore_used):
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
|
||||
print("Scanning:", SOURCE_DIR)
|
||||
|
||||
@@ -359,7 +374,7 @@ def main():
|
||||
if "extern" not in f:
|
||||
i = 1
|
||||
try:
|
||||
for l in open(f, "r", encoding="utf8"):
|
||||
for _ in open(f, "r", encoding="utf8"):
|
||||
i += 1
|
||||
except UnicodeDecodeError:
|
||||
print("Non utf8: %s:%d" % (f, i))
|
||||
|
@@ -25,6 +25,14 @@ import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
List,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
|
||||
USE_QUIET = (os.environ.get("QUIET", None) is not None)
|
||||
|
||||
CHECKER_IGNORE_PREFIX = [
|
||||
@@ -43,7 +51,7 @@ CHECKER_ARGS = [
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
source_info = project_source_info.build_info(ignore_prefix_list=CHECKER_IGNORE_PREFIX)
|
||||
|
||||
check_commands = []
|
||||
@@ -52,18 +60,19 @@ def main():
|
||||
# ~if "source/blender" not in c:
|
||||
# ~ continue
|
||||
|
||||
cmd = ([CHECKER_BIN] +
|
||||
CHECKER_ARGS +
|
||||
[c] +
|
||||
[("-I%s" % i) for i in inc_dirs] +
|
||||
[("-D%s" % d) for d in defs]
|
||||
)
|
||||
cmd = (
|
||||
[CHECKER_BIN] +
|
||||
CHECKER_ARGS +
|
||||
[c] +
|
||||
[("-I%s" % i) for i in inc_dirs] +
|
||||
[("-D%s" % d) for d in defs]
|
||||
)
|
||||
|
||||
check_commands.append((c, cmd))
|
||||
|
||||
process_functions = []
|
||||
|
||||
def my_process(i, c, cmd):
|
||||
def my_process(i: int, c: str, cmd: str) -> subprocess.Popen[Any]:
|
||||
if not USE_QUIET:
|
||||
percent = 100.0 * (i / (len(check_commands) - 1))
|
||||
percent_str = "[" + ("%.2f]" % percent).rjust(7) + " %:"
|
||||
|
@@ -25,6 +25,12 @@ import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
List,
|
||||
)
|
||||
|
||||
|
||||
USE_QUIET = (os.environ.get("QUIET", None) is not None)
|
||||
|
||||
CHECKER_IGNORE_PREFIX = [
|
||||
@@ -47,25 +53,26 @@ if USE_QUIET:
|
||||
CHECKER_ARGS.append("--quiet")
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
source_info = project_source_info.build_info(ignore_prefix_list=CHECKER_IGNORE_PREFIX)
|
||||
source_defines = project_source_info.build_defines_as_args()
|
||||
|
||||
check_commands = []
|
||||
for c, inc_dirs, defs in source_info:
|
||||
cmd = ([CHECKER_BIN] +
|
||||
CHECKER_ARGS +
|
||||
[c] +
|
||||
[("-I%s" % i) for i in inc_dirs] +
|
||||
[("-D%s" % d) for d in defs] +
|
||||
source_defines
|
||||
)
|
||||
cmd = (
|
||||
[CHECKER_BIN] +
|
||||
CHECKER_ARGS +
|
||||
[c] +
|
||||
[("-I%s" % i) for i in inc_dirs] +
|
||||
[("-D%s" % d) for d in defs] +
|
||||
source_defines
|
||||
)
|
||||
|
||||
check_commands.append((c, cmd))
|
||||
|
||||
process_functions = []
|
||||
|
||||
def my_process(i, c, cmd):
|
||||
def my_process(i: int, c: str, cmd: List[str]) -> subprocess.Popen[Any]:
|
||||
if not USE_QUIET:
|
||||
percent = 100.0 * (i / len(check_commands))
|
||||
percent_str = "[" + ("%.2f]" % percent).rjust(7) + " %:"
|
||||
|
@@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
|
||||
def print_help(targets):
|
||||
print("CMake quicky wrapper, no valid targets given.")
|
||||
print(" * targets can contain a subset of the full target name.")
|
||||
print(" * arguments with a '-' prefix are passed onto make.")
|
||||
print(" * this must run from the cmake build dir")
|
||||
print(" * alias this with a short command for speedy access, in bash:")
|
||||
print(" alias mk='../blender/build_files/cmake/example_scripts/make_quicky.py'")
|
||||
print("")
|
||||
print(" eg: make_quicky.py -j3 extern python")
|
||||
print(" ...will execute")
|
||||
print(" make -j3 extern_binreloc extern_glew bf_python bf_python_ext blender/fast")
|
||||
print("")
|
||||
print("Target List:")
|
||||
for t in targets:
|
||||
print(" %s" % t)
|
||||
print("...exiting")
|
||||
|
||||
|
||||
def main():
|
||||
targets = set()
|
||||
|
||||
# collect targets
|
||||
makefile = open("Makefile", "r")
|
||||
for line in makefile:
|
||||
line = line.rstrip()
|
||||
if not line or line[0] in ". \t@$#":
|
||||
continue
|
||||
|
||||
line = line.split("#", 1)[0]
|
||||
if ":" not in line:
|
||||
continue
|
||||
|
||||
line = line.split(":", 1)[0]
|
||||
|
||||
if "/" in line: # cmake terget options, dont need these
|
||||
continue
|
||||
|
||||
targets.add(line)
|
||||
makefile.close()
|
||||
|
||||
# remove cmake targets
|
||||
bad = set([
|
||||
"help",
|
||||
"clean",
|
||||
"all",
|
||||
"preinstall",
|
||||
"install",
|
||||
"default_target",
|
||||
"edit_cache",
|
||||
"cmake_force",
|
||||
"rebuild_cache",
|
||||
"depend",
|
||||
"cmake_check_build_system",
|
||||
])
|
||||
|
||||
targets -= set(bad)
|
||||
|
||||
# parse args
|
||||
targets = list(targets)
|
||||
targets.sort()
|
||||
|
||||
import sys
|
||||
if len(sys.argv) == 1:
|
||||
print_help(targets)
|
||||
return
|
||||
|
||||
targets_new = []
|
||||
args = []
|
||||
for arg in sys.argv[1:]:
|
||||
if arg[0] in "/-":
|
||||
args.append(arg)
|
||||
else:
|
||||
found = False
|
||||
for t in targets:
|
||||
if arg in t and t not in targets_new:
|
||||
targets_new.append(t)
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
print("Error '%s' not found in...")
|
||||
for t in targets:
|
||||
print(" %s" % t)
|
||||
print("...aborting.")
|
||||
return
|
||||
|
||||
# execute
|
||||
cmd = ["make"] + args + targets_new + ["blender/fast"]
|
||||
print("cmake building with targets: %s" % " ".join(targets_new))
|
||||
print("executing: %s" % " ".join(cmd))
|
||||
|
||||
import subprocess
|
||||
subprocess.call(cmd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@@ -44,6 +44,15 @@ __all__ = (
|
||||
"init",
|
||||
)
|
||||
|
||||
from typing import (
|
||||
Callable,
|
||||
Generator,
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
|
||||
import sys
|
||||
if sys.version_info.major < 3:
|
||||
@@ -70,10 +79,11 @@ SOURCE_DIR = abspath(SOURCE_DIR)
|
||||
SIMPLE_PROJECTFILE = False
|
||||
|
||||
# must initialize from 'init'
|
||||
CMAKE_DIR = None
|
||||
CMAKE_DIR = ""
|
||||
PROJECT_DIR = ""
|
||||
|
||||
|
||||
def init(cmake_path):
|
||||
def init(cmake_path: str) -> bool:
|
||||
global CMAKE_DIR, PROJECT_DIR
|
||||
|
||||
# get cmake path
|
||||
@@ -91,7 +101,10 @@ def init(cmake_path):
|
||||
return True
|
||||
|
||||
|
||||
def source_list(path, filename_check=None):
|
||||
def source_list(
|
||||
path: str,
|
||||
filename_check: Optional[Callable[[str], bool]] = None,
|
||||
) -> Generator[str, None, None]:
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
# skip '.git'
|
||||
dirnames[:] = [d for d in dirnames if not d.startswith(".")]
|
||||
@@ -103,53 +116,57 @@ def source_list(path, filename_check=None):
|
||||
|
||||
|
||||
# extension checking
|
||||
def is_cmake(filename):
|
||||
def is_cmake(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext == ".cmake") or (filename.endswith("CMakeLists.txt"))
|
||||
|
||||
|
||||
def is_c_header(filename):
|
||||
def is_c_header(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext in {".h", ".hpp", ".hxx", ".hh"})
|
||||
|
||||
|
||||
def is_py(filename):
|
||||
def is_py(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext == ".py")
|
||||
|
||||
|
||||
def is_glsl(filename):
|
||||
def is_glsl(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext == ".glsl")
|
||||
|
||||
|
||||
def is_c(filename):
|
||||
def is_c(filename: str) -> bool:
|
||||
ext = splitext(filename)[1]
|
||||
return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl", ".osl"})
|
||||
|
||||
|
||||
def is_c_any(filename):
|
||||
def is_c_any(filename: str) -> bool:
|
||||
return is_c(filename) or is_c_header(filename)
|
||||
|
||||
|
||||
def is_svn_file(filename):
|
||||
def is_svn_file(filename: str) -> bool:
|
||||
dn, fn = os.path.split(filename)
|
||||
filename_svn = join(dn, ".svn", "text-base", "%s.svn-base" % fn)
|
||||
return exists(filename_svn)
|
||||
|
||||
|
||||
def is_project_file(filename):
|
||||
def is_project_file(filename: str) -> bool:
|
||||
return (is_c_any(filename) or is_cmake(filename) or is_glsl(filename)) # and is_svn_file(filename)
|
||||
|
||||
|
||||
def cmake_advanced_info():
|
||||
def cmake_advanced_info() -> Union[Tuple[List[str], List[Tuple[str, str]]], Tuple[None, None]]:
|
||||
""" Extract includes and defines from cmake.
|
||||
"""
|
||||
|
||||
make_exe = cmake_cache_var("CMAKE_MAKE_PROGRAM")
|
||||
if make_exe is None:
|
||||
print("Make command not found in: %r not found" % project_path)
|
||||
return None, None
|
||||
|
||||
make_exe_basename = os.path.basename(make_exe)
|
||||
|
||||
def create_eclipse_project():
|
||||
def create_eclipse_project() -> str:
|
||||
print("CMAKE_DIR %r" % CMAKE_DIR)
|
||||
if sys.platform == "win32":
|
||||
raise Exception("Error: win32 is not supported")
|
||||
@@ -219,7 +236,7 @@ def cmake_advanced_info():
|
||||
return includes, defines
|
||||
|
||||
|
||||
def cmake_cache_var(var):
|
||||
def cmake_cache_var(var: str) -> Optional[str]:
|
||||
with open(os.path.join(CMAKE_DIR, "CMakeCache.txt"), encoding='utf-8') as cache_file:
|
||||
lines = [
|
||||
l_strip for l in cache_file
|
||||
@@ -233,12 +250,12 @@ def cmake_cache_var(var):
|
||||
return None
|
||||
|
||||
|
||||
def cmake_compiler_defines():
|
||||
def cmake_compiler_defines() -> Optional[List[str]]:
|
||||
compiler = cmake_cache_var("CMAKE_C_COMPILER") # could do CXX too
|
||||
|
||||
if compiler is None:
|
||||
print("Couldn't find the compiler, os defines will be omitted...")
|
||||
return
|
||||
return None
|
||||
|
||||
import tempfile
|
||||
temp_c = tempfile.mkstemp(suffix=".c")[1]
|
||||
@@ -255,5 +272,5 @@ def cmake_compiler_defines():
|
||||
return lines
|
||||
|
||||
|
||||
def project_name_get():
|
||||
def project_name_get() -> Optional[str]:
|
||||
return cmake_cache_var("CMAKE_PROJECT_NAME")
|
||||
|
@@ -34,30 +34,45 @@ if sys.version_info.major < 3:
|
||||
import os
|
||||
from os.path import join, dirname, normpath, abspath
|
||||
|
||||
import subprocess
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Generator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
|
||||
SOURCE_DIR = join(dirname(__file__), "..", "..")
|
||||
SOURCE_DIR = normpath(SOURCE_DIR)
|
||||
SOURCE_DIR = abspath(SOURCE_DIR)
|
||||
|
||||
|
||||
def is_c_header(filename):
|
||||
def is_c_header(filename: str) -> bool:
|
||||
ext = os.path.splitext(filename)[1]
|
||||
return (ext in {".h", ".hpp", ".hxx", ".hh"})
|
||||
|
||||
|
||||
def is_c(filename):
|
||||
def is_c(filename: str) -> bool:
|
||||
ext = os.path.splitext(filename)[1]
|
||||
return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl", ".osl"})
|
||||
|
||||
|
||||
def is_c_any(filename):
|
||||
return os.path.s_c(filename) or is_c_header(filename)
|
||||
def is_c_any(filename: str) -> bool:
|
||||
return is_c(filename) or is_c_header(filename)
|
||||
|
||||
|
||||
# copied from project_info.py
|
||||
CMAKE_DIR = "."
|
||||
|
||||
|
||||
def cmake_cache_var_iter():
|
||||
def cmake_cache_var_iter() -> Generator[Tuple[str, str, str], None, None]:
|
||||
import re
|
||||
re_cache = re.compile(r'([A-Za-z0-9_\-]+)?:?([A-Za-z0-9_\-]+)?=(.*)$')
|
||||
with open(join(CMAKE_DIR, "CMakeCache.txt"), 'r', encoding='utf-8') as cache_file:
|
||||
@@ -68,14 +83,22 @@ def cmake_cache_var_iter():
|
||||
yield (var, type_ or "", val)
|
||||
|
||||
|
||||
def cmake_cache_var(var):
|
||||
def cmake_cache_var(var: str) -> Optional[str]:
|
||||
for var_iter, type_iter, value_iter in cmake_cache_var_iter():
|
||||
if var == var_iter:
|
||||
return value_iter
|
||||
return None
|
||||
|
||||
|
||||
def do_ignore(filepath, ignore_prefix_list):
|
||||
def cmake_cache_var_or_exit(var: str) -> str:
|
||||
value = cmake_cache_var(var)
|
||||
if value is None:
|
||||
print("Unable to find %r exiting!" % value)
|
||||
sys.exit(1)
|
||||
return value
|
||||
|
||||
|
||||
def do_ignore(filepath: str, ignore_prefix_list: Optional[Sequence[str]]) -> bool:
|
||||
if ignore_prefix_list is None:
|
||||
return False
|
||||
|
||||
@@ -83,12 +106,13 @@ def do_ignore(filepath, ignore_prefix_list):
|
||||
return any([relpath.startswith(prefix) for prefix in ignore_prefix_list])
|
||||
|
||||
|
||||
def makefile_log():
|
||||
def makefile_log() -> List[str]:
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
# support both make and ninja
|
||||
make_exe = cmake_cache_var("CMAKE_MAKE_PROGRAM")
|
||||
make_exe = cmake_cache_var_or_exit("CMAKE_MAKE_PROGRAM")
|
||||
|
||||
make_exe_basename = os.path.basename(make_exe)
|
||||
|
||||
if make_exe_basename.startswith(("make", "gmake")):
|
||||
@@ -102,26 +126,37 @@ def makefile_log():
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
|
||||
if process is None:
|
||||
print("Can't execute process")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
while process.poll():
|
||||
time.sleep(1)
|
||||
|
||||
out = process.stdout.read()
|
||||
process.stdout.close()
|
||||
# We know this is always true based on the input arguments to `Popen`.
|
||||
stdout: IO[bytes] = process.stdout # type: ignore
|
||||
|
||||
out = stdout.read()
|
||||
stdout.close()
|
||||
print("done!", len(out), "bytes")
|
||||
return out.decode("utf-8", errors="ignore").split("\n")
|
||||
return cast(List[str], out.decode("utf-8", errors="ignore").split("\n"))
|
||||
|
||||
|
||||
def build_info(use_c=True, use_cxx=True, ignore_prefix_list=None):
|
||||
|
||||
def build_info(
|
||||
use_c: bool = True,
|
||||
use_cxx: bool = True,
|
||||
ignore_prefix_list: Optional[List[str]] = None,
|
||||
) -> List[Tuple[str, List[str], List[str]]]:
|
||||
makelog = makefile_log()
|
||||
|
||||
source = []
|
||||
|
||||
compilers = []
|
||||
if use_c:
|
||||
compilers.append(cmake_cache_var("CMAKE_C_COMPILER"))
|
||||
compilers.append(cmake_cache_var_or_exit("CMAKE_C_COMPILER"))
|
||||
if use_cxx:
|
||||
compilers.append(cmake_cache_var("CMAKE_CXX_COMPILER"))
|
||||
compilers.append(cmake_cache_var_or_exit("CMAKE_CXX_COMPILER"))
|
||||
|
||||
print("compilers:", " ".join(compilers))
|
||||
|
||||
@@ -131,7 +166,7 @@ def build_info(use_c=True, use_cxx=True, ignore_prefix_list=None):
|
||||
|
||||
for line in makelog:
|
||||
|
||||
args = line.split()
|
||||
args: Union[str, List[str]] = line.split()
|
||||
|
||||
if not any([(c in args) for c in compilers]):
|
||||
continue
|
||||
@@ -176,29 +211,40 @@ def build_info(use_c=True, use_cxx=True, ignore_prefix_list=None):
|
||||
return source
|
||||
|
||||
|
||||
def build_defines_as_source():
|
||||
def build_defines_as_source() -> str:
|
||||
"""
|
||||
Returns a string formatted as an include:
|
||||
'#defines A=B\n#define....'
|
||||
"""
|
||||
import subprocess
|
||||
# works for both gcc and clang
|
||||
cmd = (cmake_cache_var("CMAKE_C_COMPILER"), "-dM", "-E", "-")
|
||||
return subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.DEVNULL,
|
||||
).stdout.read().strip().decode('ascii')
|
||||
cmd = (cmake_cache_var_or_exit("CMAKE_C_COMPILER"), "-dM", "-E", "-")
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
# We know this is always true based on the input arguments to `Popen`.
|
||||
stdout: IO[bytes] = process.stdout # type: ignore
|
||||
|
||||
return cast(str, stdout.read().strip().decode('ascii'))
|
||||
|
||||
|
||||
def build_defines_as_args():
|
||||
return [("-D" + "=".join(l.split(maxsplit=2)[1:]))
|
||||
for l in build_defines_as_source().split("\n")
|
||||
if l.startswith('#define')]
|
||||
def build_defines_as_args() -> List[str]:
|
||||
return [
|
||||
("-D" + "=".join(l.split(maxsplit=2)[1:]))
|
||||
for l in build_defines_as_source().split("\n")
|
||||
if l.startswith('#define')
|
||||
]
|
||||
|
||||
|
||||
# could be moved elsewhere!, this just happens to be used by scripts that also
|
||||
# use this module.
|
||||
def queue_processes(process_funcs, job_total=-1):
|
||||
def queue_processes(
|
||||
process_funcs: Sequence[Tuple[Callable[..., subprocess.Popen[Any]], Tuple[Any, ...]]],
|
||||
job_total: int =-1,
|
||||
) -> None:
|
||||
""" Takes a list of function arg pairs, each function must return a process
|
||||
"""
|
||||
|
||||
@@ -217,7 +263,7 @@ def queue_processes(process_funcs, job_total=-1):
|
||||
else:
|
||||
import time
|
||||
|
||||
processes = []
|
||||
processes: List[subprocess.Popen[Any]] = []
|
||||
for func, args in process_funcs:
|
||||
# wait until a thread is free
|
||||
while 1:
|
||||
@@ -234,7 +280,7 @@ def queue_processes(process_funcs, job_total=-1):
|
||||
processes.append(func(*args))
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
if not os.path.exists(join(CMAKE_DIR, "CMakeCache.txt")):
|
||||
print("This script must run from the cmake build dir")
|
||||
return
|
||||
|
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Iterable, TextIO
|
||||
from typing import Iterable, TextIO, Optional, Any, Union
|
||||
|
||||
# This script can run from any location,
|
||||
# output is created in the $CWD
|
||||
@@ -18,21 +19,43 @@ SKIP_NAMES = {
|
||||
".gitignore",
|
||||
".gitmodules",
|
||||
".arcconfig",
|
||||
".svn",
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
output_dir = Path(".").absolute()
|
||||
blender_srcdir = Path(__file__).absolute().parent.parent.parent
|
||||
|
||||
cli_parser = argparse.ArgumentParser(
|
||||
description=f"Create a tarball of the Blender sources, optionally including sources of dependencies.",
|
||||
epilog="This script is intended to be run by `make source_archive_complete`.",
|
||||
)
|
||||
cli_parser.add_argument(
|
||||
"-p",
|
||||
"--include-packages",
|
||||
type=Path,
|
||||
default=None,
|
||||
metavar="PACKAGE_PATH",
|
||||
help="Include all source files from the given package directory as well.",
|
||||
)
|
||||
|
||||
cli_args = cli_parser.parse_args()
|
||||
|
||||
print(f"Source dir: {blender_srcdir}")
|
||||
|
||||
version = parse_blender_version(blender_srcdir)
|
||||
manifest = output_dir / f"blender-{version}-manifest.txt"
|
||||
tarball = output_dir / f"blender-{version}.tar.xz"
|
||||
curdir = blender_srcdir.parent
|
||||
os.chdir(curdir)
|
||||
blender_srcdir = blender_srcdir.relative_to(curdir)
|
||||
|
||||
os.chdir(blender_srcdir)
|
||||
create_manifest(version, manifest)
|
||||
create_tarball(version, tarball, manifest)
|
||||
print(f"Output dir: {curdir}")
|
||||
|
||||
version = parse_blender_version(blender_srcdir)
|
||||
tarball = tarball_path(curdir, version, cli_args)
|
||||
manifest = manifest_path(tarball)
|
||||
packages_dir = packages_path(curdir, cli_args)
|
||||
|
||||
create_manifest(version, manifest, blender_srcdir, packages_dir)
|
||||
create_tarball(version, tarball, manifest, blender_srcdir, packages_dir)
|
||||
create_checksum_file(tarball)
|
||||
cleanup(manifest)
|
||||
print("Done!")
|
||||
@@ -84,43 +107,109 @@ def parse_blender_version(blender_srcdir: Path) -> BlenderVersion:
|
||||
)
|
||||
|
||||
|
||||
def tarball_path(output_dir: Path, version: BlenderVersion, cli_args: Any) -> Path:
|
||||
extra = ""
|
||||
if cli_args.include_packages:
|
||||
extra = "-with-libraries"
|
||||
|
||||
return output_dir / f"blender{extra}-{version}.tar.xz"
|
||||
|
||||
|
||||
def manifest_path(tarball: Path) -> Path:
|
||||
"""Return the manifest path for the given tarball path.
|
||||
|
||||
>>> from pathlib import Path
|
||||
>>> tarball = Path("/home/sybren/workspace/blender-git/blender-test.tar.gz")
|
||||
>>> manifest_path(tarball).as_posix()
|
||||
'/home/sybren/workspace/blender-git/blender-test-manifest.txt'
|
||||
"""
|
||||
# ".tar.gz" is seen as two suffixes.
|
||||
without_suffix = tarball.with_suffix("").with_suffix("")
|
||||
name = without_suffix.name
|
||||
return without_suffix.with_name(f"{name}-manifest.txt")
|
||||
|
||||
|
||||
def packages_path(current_directory: Path, cli_args: Any) -> Optional[Path]:
|
||||
if not cli_args.include_packages:
|
||||
return None
|
||||
|
||||
abspath = cli_args.include_packages.absolute()
|
||||
|
||||
# os.path.relpath() can return paths like "../../packages", where
|
||||
# Path.relative_to() will not go up directories (so its return value never
|
||||
# has "../" in there).
|
||||
relpath = os.path.relpath(abspath, current_directory)
|
||||
|
||||
return Path(relpath)
|
||||
|
||||
|
||||
### Manifest creation
|
||||
|
||||
|
||||
def create_manifest(version: BlenderVersion, outpath: Path) -> None:
|
||||
def create_manifest(
|
||||
version: BlenderVersion,
|
||||
outpath: Path,
|
||||
blender_srcdir: Path,
|
||||
packages_dir: Optional[Path],
|
||||
) -> None:
|
||||
print(f'Building manifest of files: "{outpath}"...', end="", flush=True)
|
||||
with outpath.open("w", encoding="utf-8") as outfile:
|
||||
main_files_to_manifest(outfile)
|
||||
submodules_to_manifest(version, outfile)
|
||||
main_files_to_manifest(blender_srcdir, outfile)
|
||||
submodules_to_manifest(blender_srcdir, version, outfile)
|
||||
|
||||
if packages_dir:
|
||||
packages_to_manifest(outfile, packages_dir)
|
||||
print("OK")
|
||||
|
||||
|
||||
def main_files_to_manifest(outfile: TextIO) -> None:
|
||||
for path in git_ls_files():
|
||||
def main_files_to_manifest(blender_srcdir: Path, outfile: TextIO) -> None:
|
||||
assert not blender_srcdir.is_absolute()
|
||||
for path in git_ls_files(blender_srcdir):
|
||||
print(path, file=outfile)
|
||||
|
||||
|
||||
def submodules_to_manifest(version: BlenderVersion, outfile: TextIO) -> None:
|
||||
def submodules_to_manifest(
|
||||
blender_srcdir: Path, version: BlenderVersion, outfile: TextIO
|
||||
) -> None:
|
||||
skip_addon_contrib = version.is_release
|
||||
assert not blender_srcdir.is_absolute()
|
||||
|
||||
for line in git_command("submodule"):
|
||||
for line in git_command("-C", blender_srcdir, "submodule"):
|
||||
submodule = line.split()[1]
|
||||
|
||||
# Don't use native slashes as GIT for MS-Windows outputs forward slashes.
|
||||
if skip_addon_contrib and submodule == "release/scripts/addons_contrib":
|
||||
continue
|
||||
|
||||
for path in git_ls_files(Path(submodule)):
|
||||
for path in git_ls_files(blender_srcdir / submodule):
|
||||
print(path, file=outfile)
|
||||
|
||||
|
||||
def create_tarball(version: BlenderVersion, tarball: Path, manifest: Path) -> None:
|
||||
def packages_to_manifest(outfile: TextIO, packages_dir: Path) -> None:
|
||||
for path in packages_dir.glob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if path.name in SKIP_NAMES:
|
||||
continue
|
||||
print(path, file=outfile)
|
||||
|
||||
|
||||
### Higher-level functions
|
||||
|
||||
|
||||
def create_tarball(
|
||||
version: BlenderVersion, tarball: Path, manifest: Path, blender_srcdir: Path, packages_dir: Optional[Path]
|
||||
) -> None:
|
||||
print(f'Creating archive: "{tarball}" ...', end="", flush=True)
|
||||
command = ["tar"]
|
||||
|
||||
# Requires GNU `tar`, since `--transform` is used.
|
||||
command = [
|
||||
"tar",
|
||||
if packages_dir:
|
||||
command += ["--transform", f"s,{packages_dir}/,packages/,g"]
|
||||
|
||||
command += [
|
||||
"--transform",
|
||||
f"s,^,blender-{version}/,g",
|
||||
f"s,^{blender_srcdir.name}/,blender-{version}/,g",
|
||||
"--use-compress-program=xz -9",
|
||||
"--create",
|
||||
f"--file={tarball}",
|
||||
@@ -130,7 +219,8 @@ def create_tarball(version: BlenderVersion, tarball: Path, manifest: Path) -> No
|
||||
"--owner=0",
|
||||
"--group=0",
|
||||
]
|
||||
subprocess.run(command, check=True, timeout=300)
|
||||
|
||||
subprocess.run(command, check=True, timeout=3600)
|
||||
print("OK")
|
||||
|
||||
|
||||
@@ -174,7 +264,7 @@ def git_ls_files(directory: Path = Path(".")) -> Iterable[Path]:
|
||||
yield path
|
||||
|
||||
|
||||
def git_command(*cli_args) -> Iterable[str]:
|
||||
def git_command(*cli_args: Union[bytes, str, Path] ) -> Iterable[str]:
|
||||
"""Generator, yields lines of output from a Git command."""
|
||||
command = ("git", *cli_args)
|
||||
|
||||
|
@@ -38,7 +38,7 @@ PROJECT_NAME = Blender
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "V2.93"
|
||||
PROJECT_NUMBER = "V3.0"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
@@ -22,7 +22,7 @@
|
||||
This script generates the blender.1 man page, embedding the help text
|
||||
from the Blender executable itself. Invoke it as follows:
|
||||
|
||||
blender.1.py <path-to-blender> <output-filename>
|
||||
blender.1.py --blender <path-to-blender> --output <output-filename>
|
||||
|
||||
where <path-to-blender> is the path to the Blender executable,
|
||||
and <output-filename> is where to write the generated man page.
|
||||
@@ -30,108 +30,147 @@ and <output-filename> is where to write the generated man page.
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import time
|
||||
|
||||
from typing import (
|
||||
Dict,
|
||||
TextIO,
|
||||
)
|
||||
|
||||
def man_format(data):
|
||||
|
||||
def man_format(data: str) -> str:
|
||||
data = data.replace("-", "\\-")
|
||||
data = data.replace("\t", " ")
|
||||
return data
|
||||
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
import getopt
|
||||
raise getopt.GetoptError("Usage: %s <path-to-blender> <output-filename>" % sys.argv[0])
|
||||
def blender_extract_info(blender_bin: str) -> Dict[str, str]:
|
||||
|
||||
blender_bin = sys.argv[1]
|
||||
outfilename = sys.argv[2]
|
||||
blender_env = {
|
||||
"ASAN_OPTIONS": "exitcode=0:" + os.environ.get("ASAN_OPTIONS", ""),
|
||||
}
|
||||
|
||||
cmd = [blender_bin, "--help"]
|
||||
print(" executing:", " ".join(cmd))
|
||||
ASAN_OPTIONS = "exitcode=0:" + os.environ.get("ASAN_OPTIONS", "")
|
||||
blender_help = subprocess.run(
|
||||
cmd, env={"ASAN_OPTIONS": ASAN_OPTIONS}, check=True, stdout=subprocess.PIPE).stdout.decode(encoding="utf-8")
|
||||
blender_version = subprocess.run(
|
||||
[blender_bin, "--version"], env={"ASAN_OPTIONS": ASAN_OPTIONS}, check=True, stdout=subprocess.PIPE).stdout.decode(encoding="utf-8").strip()
|
||||
blender_version, blender_date = (blender_version.split("build") + [None, None])[0:2]
|
||||
blender_version = blender_version.rstrip().partition(" ")[2] # remove 'Blender' prefix.
|
||||
if blender_date is None:
|
||||
# Happens when built without WITH_BUILD_INFO e.g.
|
||||
date_string = time.strftime("%B %d, %Y", time.gmtime(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))))
|
||||
else:
|
||||
blender_date = blender_date.strip().partition(" ")[2] # remove 'date:' prefix
|
||||
date_string = time.strftime("%B %d, %Y", time.strptime(blender_date, "%Y-%m-%d"))
|
||||
blender_help = subprocess.run(
|
||||
[blender_bin, "--help"],
|
||||
env=blender_env,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout.decode(encoding="utf-8")
|
||||
|
||||
outfile = open(outfilename, "w")
|
||||
fw = outfile.write
|
||||
blender_version_ouput = subprocess.run(
|
||||
[blender_bin, "--version"],
|
||||
env=blender_env,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout.decode(encoding="utf-8")
|
||||
|
||||
fw('.TH "BLENDER" "1" "%s" "Blender %s"\n' % (date_string, blender_version.replace(".", "\\&.")))
|
||||
# Extract information from the version string.
|
||||
# Note that some internal modules may print errors (e.g. color management),
|
||||
# check for each lines prefix to ensure these aren't included.
|
||||
blender_version = ""
|
||||
blender_date = ""
|
||||
for l in blender_version_ouput.split("\n"):
|
||||
if l.startswith("Blender "):
|
||||
# Remove 'Blender' prefix.
|
||||
blender_version = l.split(" ", 1)[1].strip()
|
||||
elif l.lstrip().startswith("build date:"):
|
||||
# Remove 'build date:' prefix.
|
||||
blender_date = l.split(":", 1)[1].strip()
|
||||
if blender_version and blender_date:
|
||||
break
|
||||
|
||||
fw('''
|
||||
if not blender_date:
|
||||
# Happens when built without WITH_BUILD_INFO e.g.
|
||||
date_string = time.strftime("%B %d, %Y", time.gmtime(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))))
|
||||
else:
|
||||
date_string = time.strftime("%B %d, %Y", time.strptime(blender_date, "%Y-%m-%d"))
|
||||
|
||||
return {
|
||||
"help": blender_help,
|
||||
"version": blender_version,
|
||||
"date": date_string,
|
||||
}
|
||||
|
||||
|
||||
def man_page_from_blender_help(fh: TextIO, blender_bin: str) -> None:
|
||||
blender_info = blender_extract_info(blender_bin)
|
||||
|
||||
# Header Content.
|
||||
fh.write(
|
||||
'.TH "BLENDER" "1" "%s" "Blender %s"\n' %
|
||||
(blender_info["date"], blender_info["version"].replace(".", "\\&."))
|
||||
)
|
||||
|
||||
fh.write(r'''
|
||||
.SH NAME
|
||||
blender \- a full-featured 3D application''')
|
||||
|
||||
fw('''
|
||||
fh.write(r'''
|
||||
.SH SYNOPSIS
|
||||
.B blender [args ...] [file] [args ...]''')
|
||||
|
||||
fw('''
|
||||
fh.write(r'''
|
||||
.br
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
.B blender
|
||||
is a full-featured 3D application. It supports the entirety of the 3D pipeline - modeling, rigging, animation, simulation, rendering, compositing, motion tracking, and video editing.
|
||||
is a full-featured 3D application. It supports the entirety of the 3D pipeline - '''
|
||||
'''modeling, rigging, animation, simulation, rendering, compositing, motion tracking, and video editing.
|
||||
|
||||
Use Blender to create 3D images and animations, films and commercials, content for games, architectural and industrial visualizatons, and scientific visualizations.
|
||||
Use Blender to create 3D images and animations, films and commercials, content for games, '''
|
||||
r'''architectural and industrial visualizatons, and scientific visualizations.
|
||||
|
||||
https://www.blender.org''')
|
||||
|
||||
fw('''
|
||||
fh.write(r'''
|
||||
.SH OPTIONS''')
|
||||
|
||||
fw("\n\n")
|
||||
fh.write("\n\n")
|
||||
|
||||
lines = [line.rstrip() for line in blender_help.split("\n")]
|
||||
# Body Content.
|
||||
|
||||
while lines:
|
||||
l = lines.pop(0)
|
||||
if l.startswith("Environment Variables:"):
|
||||
fw('.SH "ENVIRONMENT VARIABLES"\n')
|
||||
elif l.endswith(":"): # one line
|
||||
fw('.SS "%s"\n\n' % l)
|
||||
elif l.startswith("-") or l.startswith("/"): # can be multi line
|
||||
lines = [line.rstrip() for line in blender_info["help"].split("\n")]
|
||||
|
||||
fw('.TP\n')
|
||||
fw('.B %s\n' % man_format(l))
|
||||
while lines:
|
||||
l = lines.pop(0)
|
||||
if l.startswith("Environment Variables:"):
|
||||
fh.write('.SH "ENVIRONMENT VARIABLES"\n')
|
||||
elif l.endswith(":"): # One line.
|
||||
fh.write('.SS "%s"\n\n' % l)
|
||||
elif l.startswith("-") or l.startswith("/"): # Can be multi line.
|
||||
fh.write('.TP\n')
|
||||
fh.write('.B %s\n' % man_format(l))
|
||||
|
||||
while lines:
|
||||
# line with no
|
||||
if lines[0].strip() and len(lines[0].lstrip()) == len(lines[0]): # no white space
|
||||
break
|
||||
while lines:
|
||||
# line with no
|
||||
if lines[0].strip() and len(lines[0].lstrip()) == len(lines[0]): # No white space.
|
||||
break
|
||||
|
||||
if not l: # second blank line
|
||||
fw('.IP\n')
|
||||
else:
|
||||
fw('.br\n')
|
||||
if not l: # Second blank line.
|
||||
fh.write('.IP\n')
|
||||
else:
|
||||
fh.write('.br\n')
|
||||
|
||||
l = lines.pop(0)
|
||||
l = l[1:] # remove first whitespace (tab)
|
||||
l = lines.pop(0)
|
||||
if l:
|
||||
assert(l.startswith('\t'))
|
||||
l = l[1:] # Remove first white-space (tab).
|
||||
|
||||
fw('%s\n' % man_format(l))
|
||||
fh.write('%s\n' % man_format(l))
|
||||
|
||||
else:
|
||||
if not l.strip():
|
||||
fw('.br\n')
|
||||
else:
|
||||
fw('%s\n' % man_format(l))
|
||||
if not l.strip():
|
||||
fh.write('.br\n')
|
||||
else:
|
||||
fh.write('%s\n' % man_format(l))
|
||||
|
||||
# footer
|
||||
# Footer Content.
|
||||
|
||||
fw('''
|
||||
fh.write(r'''
|
||||
.br
|
||||
.SH SEE ALSO
|
||||
.B luxrender(1)
|
||||
@@ -143,5 +182,33 @@ This manpage was written for a Debian GNU/Linux system by Daniel Mester
|
||||
<cyril.brulebois@enst-bretagne.fr> and Dan Eicher <dan@trollwerks.org>.
|
||||
''')
|
||||
|
||||
outfile.close()
|
||||
print("written:", outfilename)
|
||||
|
||||
def create_argparse() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
required=True,
|
||||
help="The man page to write to."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--blender",
|
||||
required=True,
|
||||
help="Path to the blender binary."
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = create_argparse()
|
||||
args = parser.parse_args()
|
||||
|
||||
blender_bin = args.blender
|
||||
output_filename = args.output
|
||||
|
||||
with open(output_filename, "w", encoding="utf-8") as fh:
|
||||
man_page_from_blender_help(fh, blender_bin)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Sphinx==3.5.1
|
||||
sphinx_rtd_theme==0.5.1
|
||||
Sphinx==3.5.3
|
||||
sphinx_rtd_theme==0.5.2
|
||||
|
@@ -50,7 +50,8 @@ you should be able to find the poll function with no knowledge of C.
|
||||
|
||||
Blender does have the functionality for poll functions to describe why they fail,
|
||||
but its currently not used much, if you're interested to help improve the API
|
||||
feel free to add calls to ``CTX_wm_operator_poll_msg_set`` where its not obvious why poll fails, e.g:
|
||||
feel free to add calls to :class:`bpy.types.Operator.poll_message_set` (``CTX_wm_operator_poll_msg_set`` in C)
|
||||
where its not obvious why poll fails, e.g:
|
||||
|
||||
>>> bpy.ops.gpencil.draw()
|
||||
RuntimeError: Operator bpy.ops.gpencil.draw.poll() Failed to find Grease Pencil data to draw into
|
||||
|
@@ -41,6 +41,9 @@ class AddPresetIntegrator(AddPresetBase, Operator):
|
||||
"cycles.caustics_reflective",
|
||||
"cycles.caustics_refractive",
|
||||
"cycles.blur_glossy"
|
||||
"cycles.use_fast_gi"
|
||||
"cycles.ao_bounces"
|
||||
"cycles.ao_bounces_render"
|
||||
]
|
||||
|
||||
preset_subdir = "cycles/integrator"
|
||||
|
@@ -801,17 +801,22 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
|
||||
items=enum_texture_limit
|
||||
)
|
||||
|
||||
use_fast_gi: BoolProperty(
|
||||
name="Fast GI Approximation",
|
||||
description="Approximate diffuse indirect light with background tinted ambient occlusion. This provides fast alternative to full global illumination, for interactive viewport rendering or final renders with reduced quality",
|
||||
default=False,
|
||||
)
|
||||
ao_bounces: IntProperty(
|
||||
name="AO Bounces",
|
||||
default=0,
|
||||
description="Approximate indirect light with background tinted ambient occlusion at the specified bounce, 0 disables this feature",
|
||||
default=1,
|
||||
description="After this number of light bounces, use approximate global illumination. 0 disables this feature",
|
||||
min=0, max=1024,
|
||||
)
|
||||
|
||||
ao_bounces_render: IntProperty(
|
||||
name="AO Bounces Render",
|
||||
default=0,
|
||||
description="Approximate indirect light with background tinted ambient occlusion at the specified bounce, 0 disables this feature",
|
||||
default=1,
|
||||
description="After this number of light bounces, use approximate global illumination. 0 disables this feature",
|
||||
min=0, max=1024,
|
||||
)
|
||||
|
||||
|
@@ -526,6 +526,35 @@ class CYCLES_RENDER_PT_light_paths_caustics(CyclesButtonsPanel, Panel):
|
||||
col.prop(cscene, "caustics_refractive", text="Refractive")
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_light_paths_fast_gi(CyclesButtonsPanel, Panel):
|
||||
bl_label = "Fast GI Approximation"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "CYCLES_RENDER_PT_light_paths"
|
||||
|
||||
def draw_header(self, context):
|
||||
scene = context.scene
|
||||
cscene = scene.cycles
|
||||
|
||||
self.layout.prop(cscene, "use_fast_gi", text="")
|
||||
|
||||
def draw(self, context):
|
||||
scene = context.scene
|
||||
cscene = scene.cycles
|
||||
world = scene.world
|
||||
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(cscene, "ao_bounces", text="Viewport Bounces")
|
||||
col.prop(cscene, "ao_bounces_render", text="Render Bounces")
|
||||
|
||||
if world:
|
||||
light = world.light_settings
|
||||
layout.prop(light, "distance", text="AO Distance")
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_motion_blur(CyclesButtonsPanel, Panel):
|
||||
bl_label = "Motion Blur"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
@@ -746,7 +775,7 @@ class CYCLES_RENDER_PT_performance_final_render(CyclesButtonsPanel, Panel):
|
||||
col = layout.column()
|
||||
|
||||
col.prop(rd, "use_save_buffers")
|
||||
col.prop(rd, "use_persistent_data", text="Persistent Images")
|
||||
col.prop(rd, "use_persistent_data", text="Persistent Data")
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_performance_viewport(CyclesButtonsPanel, Panel):
|
||||
@@ -2041,7 +2070,6 @@ class CYCLES_RENDER_PT_simplify_viewport(CyclesButtonsPanel, Panel):
|
||||
col.prop(rd, "simplify_subdivision", text="Max Subdivision")
|
||||
col.prop(rd, "simplify_child_particles", text="Child Particles")
|
||||
col.prop(cscene, "texture_limit", text="Texture Limit")
|
||||
col.prop(cscene, "ao_bounces", text="AO Bounces")
|
||||
col.prop(rd, "simplify_volumes", text="Volume Resolution")
|
||||
|
||||
|
||||
@@ -2067,7 +2095,6 @@ class CYCLES_RENDER_PT_simplify_render(CyclesButtonsPanel, Panel):
|
||||
col.prop(rd, "simplify_subdivision_render", text="Max Subdivision")
|
||||
col.prop(rd, "simplify_child_particles_render", text="Child Particles")
|
||||
col.prop(cscene, "texture_limit_render", text="Texture Limit")
|
||||
col.prop(cscene, "ao_bounces_render", text="AO Bounces")
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_simplify_culling(CyclesButtonsPanel, Panel):
|
||||
@@ -2245,6 +2272,7 @@ classes = (
|
||||
CYCLES_RENDER_PT_light_paths_max_bounces,
|
||||
CYCLES_RENDER_PT_light_paths_clamping,
|
||||
CYCLES_RENDER_PT_light_paths_caustics,
|
||||
CYCLES_RENDER_PT_light_paths_fast_gi,
|
||||
CYCLES_RENDER_PT_volumes,
|
||||
CYCLES_RENDER_PT_subdivision,
|
||||
CYCLES_RENDER_PT_hair,
|
||||
|
@@ -217,6 +217,18 @@ def do_versions(self):
|
||||
baov.name = caov.get("name", "AOV")
|
||||
baov.type = "COLOR" if caov.get("type", 1) == 1 else "VALUE"
|
||||
|
||||
if version <= (2, 93, 16):
|
||||
cscene = scene.cycles
|
||||
ao_bounces = cscene.get("ao_bounces", 0)
|
||||
ao_bounces_render = cscene.get("ao_bounces_render", 0)
|
||||
if scene.render.use_simplify and (ao_bounces or ao_bounces_render):
|
||||
cscene.use_fast_gi = True
|
||||
cscene.ao_bounces = ao_bounces
|
||||
cscene.ao_bounces_render = ao_bounces_render
|
||||
else:
|
||||
cscene.ao_bounces = 1
|
||||
cscene.ao_bounces_render = 1
|
||||
|
||||
# Lamps
|
||||
for light in bpy.data.lights:
|
||||
if light.library not in libraries:
|
||||
|
@@ -143,12 +143,6 @@ void BlenderSession::create_session()
|
||||
|
||||
session->scene = scene;
|
||||
|
||||
/* There is no single depsgraph to use for the entire render.
|
||||
* So we need to handle this differently.
|
||||
*
|
||||
* We could loop over the final render result render layers in pipeline and keep Cycles unaware
|
||||
* of multiple layers, or perhaps move syncing further down in the pipeline.
|
||||
*/
|
||||
/* create sync */
|
||||
sync = new BlenderSync(b_engine, b_data, b_scene, scene, !background, session->progress);
|
||||
BL::Object b_camera_override(b_engine.camera_override());
|
||||
@@ -213,7 +207,7 @@ void BlenderSession::reset_session(BL::BlendData &b_data, BL::Depsgraph &b_depsg
|
||||
SceneParams scene_params = BlenderSync::get_scene_params(b_scene, background);
|
||||
|
||||
if (scene->params.modified(scene_params) || session->params.modified(session_params) ||
|
||||
!scene_params.persistent_data) {
|
||||
!this->b_render.use_persistent_data()) {
|
||||
/* if scene or session parameters changed, it's easier to simply re-create
|
||||
* them rather than trying to distinguish which settings need to be updated
|
||||
*/
|
||||
@@ -225,7 +219,6 @@ void BlenderSession::reset_session(BL::BlendData &b_data, BL::Depsgraph &b_depsg
|
||||
}
|
||||
|
||||
session->progress.reset();
|
||||
scene->reset();
|
||||
|
||||
session->tile_manager.set_tile_order(session_params.tile_order);
|
||||
|
||||
@@ -234,12 +227,15 @@ void BlenderSession::reset_session(BL::BlendData &b_data, BL::Depsgraph &b_depsg
|
||||
*/
|
||||
session->stats.mem_peak = session->stats.mem_used;
|
||||
|
||||
/* There is no single depsgraph to use for the entire render.
|
||||
* See note on create_session().
|
||||
*/
|
||||
/* sync object should be re-created */
|
||||
delete sync;
|
||||
sync = new BlenderSync(b_engine, b_data, b_scene, scene, !background, session->progress);
|
||||
if (is_new_session) {
|
||||
/* Sync object should be re-created for new scene. */
|
||||
delete sync;
|
||||
sync = new BlenderSync(b_engine, b_data, b_scene, scene, !background, session->progress);
|
||||
}
|
||||
else {
|
||||
/* Sync recalculations to do just the required updates. */
|
||||
sync->sync_recalc(b_depsgraph, b_v3d);
|
||||
}
|
||||
|
||||
BL::SpaceView3D b_null_space_view3d(PointerRNA_NULL);
|
||||
BL::RegionView3D b_null_region_view3d(PointerRNA_NULL);
|
||||
@@ -502,7 +498,7 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
|
||||
|
||||
/* Compute render passes and film settings. */
|
||||
vector<Pass> passes = sync->sync_render_passes(
|
||||
b_rlay, b_view_layer, session_params.adaptive_sampling, session_params.denoising);
|
||||
b_scene, b_rlay, b_view_layer, session_params.adaptive_sampling, session_params.denoising);
|
||||
|
||||
/* Set buffer params, using film settings from sync_render_passes. */
|
||||
buffer_params.passes = passes;
|
||||
@@ -598,18 +594,6 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
|
||||
/* clear callback */
|
||||
session->write_render_tile_cb = function_null;
|
||||
session->update_render_tile_cb = function_null;
|
||||
|
||||
/* TODO: find a way to clear this data for persistent data render */
|
||||
#if 0
|
||||
/* free all memory used (host and device), so we wouldn't leave render
|
||||
* engine with extra memory allocated
|
||||
*/
|
||||
|
||||
session->device_free();
|
||||
|
||||
delete sync;
|
||||
sync = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int bake_pass_filter_get(const int pass_filter)
|
||||
|
@@ -211,9 +211,11 @@ void BlenderSync::sync_recalc(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d
|
||||
}
|
||||
}
|
||||
|
||||
BlenderViewportParameters new_viewport_parameters(b_v3d);
|
||||
if (viewport_parameters.modified(new_viewport_parameters)) {
|
||||
world_recalc = true;
|
||||
if (b_v3d) {
|
||||
BlenderViewportParameters new_viewport_parameters(b_v3d);
|
||||
if (viewport_parameters.modified(new_viewport_parameters)) {
|
||||
world_recalc = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +360,7 @@ void BlenderSync::sync_integrator()
|
||||
|
||||
integrator->set_adaptive_min_samples(adaptive_min_samples);
|
||||
|
||||
if (b_scene.render().use_simplify()) {
|
||||
if (get_boolean(cscene, "use_fast_gi")) {
|
||||
if (preview) {
|
||||
integrator->set_ao_bounces(get_int(cscene, "ao_bounces"));
|
||||
}
|
||||
@@ -567,7 +569,8 @@ int BlenderSync::get_denoising_pass(BL::RenderPass &b_pass)
|
||||
return -1;
|
||||
}
|
||||
|
||||
vector<Pass> BlenderSync::sync_render_passes(BL::RenderLayer &b_rlay,
|
||||
vector<Pass> BlenderSync::sync_render_passes(BL::Scene &b_scene,
|
||||
BL::RenderLayer &b_rlay,
|
||||
BL::ViewLayer &b_view_layer,
|
||||
bool adaptive_sampling,
|
||||
const DenoiseParams &denoising)
|
||||
@@ -578,7 +581,7 @@ vector<Pass> BlenderSync::sync_render_passes(BL::RenderLayer &b_rlay,
|
||||
for (BL::RenderPass &b_pass : b_rlay.passes) {
|
||||
PassType pass_type = get_pass_type(b_pass);
|
||||
|
||||
if (pass_type == PASS_MOTION && scene->integrator->get_motion_blur())
|
||||
if (pass_type == PASS_MOTION && b_scene.render().use_motion_blur())
|
||||
continue;
|
||||
if (pass_type != PASS_NONE)
|
||||
Pass::add(pass_type, passes, b_pass.name().c_str());
|
||||
@@ -757,7 +760,6 @@ void BlenderSync::free_data_after_sync(BL::Depsgraph &b_depsgraph)
|
||||
|
||||
SceneParams BlenderSync::get_scene_params(BL::Scene &b_scene, bool background)
|
||||
{
|
||||
BL::RenderSettings r = b_scene.render();
|
||||
SceneParams params;
|
||||
PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
|
||||
const bool shadingsystem = RNA_boolean_get(&cscene, "shading_system");
|
||||
@@ -781,11 +783,6 @@ SceneParams BlenderSync::get_scene_params(BL::Scene &b_scene, bool background)
|
||||
params.hair_shape = (CurveShapeType)get_enum(
|
||||
csscene, "shape", CURVE_NUM_SHAPE_TYPES, CURVE_THICK);
|
||||
|
||||
if (background && params.shadingsystem != SHADINGSYSTEM_OSL)
|
||||
params.persistent_data = r.use_persistent_data();
|
||||
else
|
||||
params.persistent_data = false;
|
||||
|
||||
int texture_limit;
|
||||
if (background) {
|
||||
texture_limit = RNA_enum_get(&cscene, "texture_limit_render");
|
||||
|
@@ -74,7 +74,8 @@ class BlenderSync {
|
||||
int height,
|
||||
void **python_thread_state);
|
||||
void sync_view_layer(BL::SpaceView3D &b_v3d, BL::ViewLayer &b_view_layer);
|
||||
vector<Pass> sync_render_passes(BL::RenderLayer &b_render_layer,
|
||||
vector<Pass> sync_render_passes(BL::Scene &b_scene,
|
||||
BL::RenderLayer &b_render_layer,
|
||||
BL::ViewLayer &b_view_layer,
|
||||
bool adaptive_sampling,
|
||||
const DenoiseParams &denoising);
|
||||
|
@@ -26,7 +26,7 @@
|
||||
#ifdef WITH_OPENVDB
|
||||
# include <openvdb/openvdb.h>
|
||||
openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const struct Volume *volume,
|
||||
struct VolumeGrid *grid);
|
||||
const struct VolumeGrid *grid);
|
||||
#endif
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
@@ -227,7 +227,7 @@ class BlenderVolumeLoader : public VDBImageLoader {
|
||||
const bool unload = !b_volume_grid.is_loaded();
|
||||
|
||||
::Volume *volume = (::Volume *)b_volume.ptr.data;
|
||||
VolumeGrid *volume_grid = (VolumeGrid *)b_volume_grid.ptr.data;
|
||||
const VolumeGrid *volume_grid = (VolumeGrid *)b_volume_grid.ptr.data;
|
||||
grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
|
||||
|
||||
if (unload) {
|
||||
|
@@ -362,7 +362,7 @@ class OptiXDevice : public CUDADevice {
|
||||
}
|
||||
}
|
||||
|
||||
OptixModuleCompileOptions module_options;
|
||||
OptixModuleCompileOptions module_options = {};
|
||||
module_options.maxRegisterCount = 0; // Do not set an explicit register limit
|
||||
# ifdef WITH_CYCLES_DEBUG
|
||||
module_options.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_0;
|
||||
@@ -377,7 +377,7 @@ class OptiXDevice : public CUDADevice {
|
||||
module_options.numBoundValues = 0;
|
||||
# endif
|
||||
|
||||
OptixPipelineCompileOptions pipeline_options;
|
||||
OptixPipelineCompileOptions pipeline_options = {};
|
||||
// Default to no motion blur and two-level graph, since it is the fastest option
|
||||
pipeline_options.usesMotionBlur = false;
|
||||
pipeline_options.traversableGraphFlags =
|
||||
@@ -477,7 +477,7 @@ class OptiXDevice : public CUDADevice {
|
||||
|
||||
# if OPTIX_ABI_VERSION >= 36
|
||||
if (DebugFlags().optix.curves_api && requested_features.use_hair_thick) {
|
||||
OptixBuiltinISOptions builtin_options;
|
||||
OptixBuiltinISOptions builtin_options = {};
|
||||
builtin_options.builtinISModuleType = OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE;
|
||||
builtin_options.usesMotionBlur = false;
|
||||
|
||||
@@ -571,7 +571,7 @@ class OptiXDevice : public CUDADevice {
|
||||
stack_size[PG_HITS_MOTION].cssIS + stack_size[PG_HITS_MOTION].cssAH);
|
||||
# endif
|
||||
|
||||
OptixPipelineLinkOptions link_options;
|
||||
OptixPipelineLinkOptions link_options = {};
|
||||
link_options.maxTraceDepth = 1;
|
||||
# ifdef WITH_CYCLES_DEBUG
|
||||
link_options.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL;
|
||||
@@ -953,7 +953,7 @@ class OptiXDevice : public CUDADevice {
|
||||
}
|
||||
|
||||
// Create OptiX denoiser handle on demand when it is first used
|
||||
OptixDenoiserOptions denoiser_options;
|
||||
OptixDenoiserOptions denoiser_options = {};
|
||||
assert(task.denoising.input_passes >= 1 && task.denoising.input_passes <= 3);
|
||||
denoiser_options.inputKind = static_cast<OptixDenoiserInputKind>(
|
||||
OPTIX_DENOISER_INPUT_RGB + (task.denoising.input_passes - 1));
|
||||
@@ -1157,7 +1157,7 @@ class OptiXDevice : public CUDADevice {
|
||||
|
||||
// Compute memory usage
|
||||
OptixAccelBufferSizes sizes = {};
|
||||
OptixAccelBuildOptions options;
|
||||
OptixAccelBuildOptions options = {};
|
||||
options.operation = operation;
|
||||
if (background) {
|
||||
// Prefer best performance and lowest memory consumption in background
|
||||
@@ -1195,7 +1195,7 @@ class OptiXDevice : public CUDADevice {
|
||||
}
|
||||
|
||||
// Finally build the acceleration structure
|
||||
OptixAccelEmitDesc compacted_size_prop;
|
||||
OptixAccelEmitDesc compacted_size_prop = {};
|
||||
compacted_size_prop.type = OPTIX_PROPERTY_TYPE_COMPACTED_SIZE;
|
||||
// A tiny space was allocated for this property at the end of the temporary buffer above
|
||||
// Make sure this pointer is 8-byte aligned
|
||||
|
@@ -93,6 +93,7 @@ set(SRC_BVH_HEADERS
|
||||
bvh/bvh_local.h
|
||||
bvh/bvh_traversal.h
|
||||
bvh/bvh_types.h
|
||||
bvh/bvh_util.h
|
||||
bvh/bvh_volume.h
|
||||
bvh/bvh_volume_all.h
|
||||
bvh/bvh_embree.h
|
||||
|
@@ -29,9 +29,10 @@
|
||||
# include "kernel/bvh/bvh_embree.h"
|
||||
#endif
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
#include "kernel/bvh/bvh_types.h"
|
||||
#include "kernel/bvh/bvh_util.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
#ifndef __KERNEL_OPTIX__
|
||||
|
||||
@@ -533,97 +534,4 @@ ccl_device_intersect uint scene_intersect_volume_all(KernelGlobals *kg,
|
||||
}
|
||||
#endif /* __VOLUME_RECORD_ALL__ */
|
||||
|
||||
/* Ray offset to avoid self intersection.
|
||||
*
|
||||
* This function should be used to compute a modified ray start position for
|
||||
* rays leaving from a surface. */
|
||||
|
||||
ccl_device_inline float3 ray_offset(float3 P, float3 Ng)
|
||||
{
|
||||
#ifdef __INTERSECTION_REFINE__
|
||||
const float epsilon_f = 1e-5f;
|
||||
/* ideally this should match epsilon_f, but instancing and motion blur
|
||||
* precision makes it problematic */
|
||||
const float epsilon_test = 1.0f;
|
||||
const int epsilon_i = 32;
|
||||
|
||||
float3 res;
|
||||
|
||||
/* x component */
|
||||
if (fabsf(P.x) < epsilon_test) {
|
||||
res.x = P.x + Ng.x * epsilon_f;
|
||||
}
|
||||
else {
|
||||
uint ix = __float_as_uint(P.x);
|
||||
ix += ((ix ^ __float_as_uint(Ng.x)) >> 31) ? -epsilon_i : epsilon_i;
|
||||
res.x = __uint_as_float(ix);
|
||||
}
|
||||
|
||||
/* y component */
|
||||
if (fabsf(P.y) < epsilon_test) {
|
||||
res.y = P.y + Ng.y * epsilon_f;
|
||||
}
|
||||
else {
|
||||
uint iy = __float_as_uint(P.y);
|
||||
iy += ((iy ^ __float_as_uint(Ng.y)) >> 31) ? -epsilon_i : epsilon_i;
|
||||
res.y = __uint_as_float(iy);
|
||||
}
|
||||
|
||||
/* z component */
|
||||
if (fabsf(P.z) < epsilon_test) {
|
||||
res.z = P.z + Ng.z * epsilon_f;
|
||||
}
|
||||
else {
|
||||
uint iz = __float_as_uint(P.z);
|
||||
iz += ((iz ^ __float_as_uint(Ng.z)) >> 31) ? -epsilon_i : epsilon_i;
|
||||
res.z = __uint_as_float(iz);
|
||||
}
|
||||
|
||||
return res;
|
||||
#else
|
||||
const float epsilon_f = 1e-4f;
|
||||
return P + epsilon_f * Ng;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__VOLUME_RECORD_ALL__) || (defined(__SHADOW_RECORD_ALL__) && defined(__KERNEL_CPU__))
|
||||
/* ToDo: Move to another file? */
|
||||
ccl_device int intersections_compare(const void *a, const void *b)
|
||||
{
|
||||
const Intersection *isect_a = (const Intersection *)a;
|
||||
const Intersection *isect_b = (const Intersection *)b;
|
||||
|
||||
if (isect_a->t < isect_b->t)
|
||||
return -1;
|
||||
else if (isect_a->t > isect_b->t)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__SHADOW_RECORD_ALL__)
|
||||
ccl_device_inline void sort_intersections(Intersection *hits, uint num_hits)
|
||||
{
|
||||
# ifdef __KERNEL_GPU__
|
||||
/* Use bubble sort which has more friendly memory pattern on GPU. */
|
||||
bool swapped;
|
||||
do {
|
||||
swapped = false;
|
||||
for (int j = 0; j < num_hits - 1; ++j) {
|
||||
if (hits[j].t > hits[j + 1].t) {
|
||||
struct Intersection tmp = hits[j];
|
||||
hits[j] = hits[j + 1];
|
||||
hits[j + 1] = tmp;
|
||||
swapped = true;
|
||||
}
|
||||
}
|
||||
--num_hits;
|
||||
} while (swapped);
|
||||
# else
|
||||
qsort(hits, num_hits, sizeof(Intersection), intersections_compare);
|
||||
# endif
|
||||
}
|
||||
#endif /* __SHADOW_RECORD_ALL__ | __VOLUME_RECORD_ALL__ */
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@@ -180,25 +180,10 @@ ccl_device_inline
|
||||
|
||||
/* todo: optimize so primitive visibility flag indicates if
|
||||
* the primitive has a transparent shadow shader? */
|
||||
int prim = kernel_tex_fetch(__prim_index, isect_array->prim);
|
||||
int shader = 0;
|
||||
|
||||
#ifdef __HAIR__
|
||||
if (kernel_tex_fetch(__prim_type, isect_array->prim) & PRIMITIVE_ALL_TRIANGLE)
|
||||
#endif
|
||||
{
|
||||
shader = kernel_tex_fetch(__tri_shader, prim);
|
||||
}
|
||||
#ifdef __HAIR__
|
||||
else {
|
||||
float4 str = kernel_tex_fetch(__curves, prim);
|
||||
shader = __float_as_int(str.z);
|
||||
}
|
||||
#endif
|
||||
int flag = kernel_tex_fetch(__shaders, (shader & SHADER_MASK)).flags;
|
||||
const int flags = intersection_get_shader_flags(kg, isect_array);
|
||||
|
||||
/* if no transparent shadows, all light is blocked */
|
||||
if (!(flag & SD_HAS_TRANSPARENT_SHADOW)) {
|
||||
if (!(flags & SD_HAS_TRANSPARENT_SHADOW)) {
|
||||
return true;
|
||||
}
|
||||
/* if maximum number of hits reached, block all light */
|
||||
|
162
intern/cycles/kernel/bvh/bvh_util.h
Normal file
162
intern/cycles/kernel/bvh/bvh_util.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Ray offset to avoid self intersection.
|
||||
*
|
||||
* This function should be used to compute a modified ray start position for
|
||||
* rays leaving from a surface. */
|
||||
|
||||
ccl_device_inline float3 ray_offset(float3 P, float3 Ng)
|
||||
{
|
||||
#ifdef __INTERSECTION_REFINE__
|
||||
const float epsilon_f = 1e-5f;
|
||||
/* ideally this should match epsilon_f, but instancing and motion blur
|
||||
* precision makes it problematic */
|
||||
const float epsilon_test = 1.0f;
|
||||
const int epsilon_i = 32;
|
||||
|
||||
float3 res;
|
||||
|
||||
/* x component */
|
||||
if (fabsf(P.x) < epsilon_test) {
|
||||
res.x = P.x + Ng.x * epsilon_f;
|
||||
}
|
||||
else {
|
||||
uint ix = __float_as_uint(P.x);
|
||||
ix += ((ix ^ __float_as_uint(Ng.x)) >> 31) ? -epsilon_i : epsilon_i;
|
||||
res.x = __uint_as_float(ix);
|
||||
}
|
||||
|
||||
/* y component */
|
||||
if (fabsf(P.y) < epsilon_test) {
|
||||
res.y = P.y + Ng.y * epsilon_f;
|
||||
}
|
||||
else {
|
||||
uint iy = __float_as_uint(P.y);
|
||||
iy += ((iy ^ __float_as_uint(Ng.y)) >> 31) ? -epsilon_i : epsilon_i;
|
||||
res.y = __uint_as_float(iy);
|
||||
}
|
||||
|
||||
/* z component */
|
||||
if (fabsf(P.z) < epsilon_test) {
|
||||
res.z = P.z + Ng.z * epsilon_f;
|
||||
}
|
||||
else {
|
||||
uint iz = __float_as_uint(P.z);
|
||||
iz += ((iz ^ __float_as_uint(Ng.z)) >> 31) ? -epsilon_i : epsilon_i;
|
||||
res.z = __uint_as_float(iz);
|
||||
}
|
||||
|
||||
return res;
|
||||
#else
|
||||
const float epsilon_f = 1e-4f;
|
||||
return P + epsilon_f * Ng;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__VOLUME_RECORD_ALL__) || (defined(__SHADOW_RECORD_ALL__) && defined(__KERNEL_CPU__))
|
||||
/* ToDo: Move to another file? */
|
||||
ccl_device int intersections_compare(const void *a, const void *b)
|
||||
{
|
||||
const Intersection *isect_a = (const Intersection *)a;
|
||||
const Intersection *isect_b = (const Intersection *)b;
|
||||
|
||||
if (isect_a->t < isect_b->t)
|
||||
return -1;
|
||||
else if (isect_a->t > isect_b->t)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__SHADOW_RECORD_ALL__)
|
||||
ccl_device_inline void sort_intersections(Intersection *hits, uint num_hits)
|
||||
{
|
||||
kernel_assert(num_hits > 0);
|
||||
|
||||
# ifdef __KERNEL_GPU__
|
||||
/* Use bubble sort which has more friendly memory pattern on GPU. */
|
||||
bool swapped;
|
||||
do {
|
||||
swapped = false;
|
||||
for (int j = 0; j < num_hits - 1; ++j) {
|
||||
if (hits[j].t > hits[j + 1].t) {
|
||||
struct Intersection tmp = hits[j];
|
||||
hits[j] = hits[j + 1];
|
||||
hits[j + 1] = tmp;
|
||||
swapped = true;
|
||||
}
|
||||
}
|
||||
--num_hits;
|
||||
} while (swapped);
|
||||
# else
|
||||
qsort(hits, num_hits, sizeof(Intersection), intersections_compare);
|
||||
# endif
|
||||
}
|
||||
#endif /* __SHADOW_RECORD_ALL__ | __VOLUME_RECORD_ALL__ */
|
||||
|
||||
/* Utility to quickly get a shader flags from an intersection. */
|
||||
|
||||
ccl_device_forceinline int intersection_get_shader_flags(KernelGlobals *ccl_restrict kg,
|
||||
const Intersection *isect)
|
||||
{
|
||||
const int prim = kernel_tex_fetch(__prim_index, isect->prim);
|
||||
int shader = 0;
|
||||
|
||||
#ifdef __HAIR__
|
||||
if (kernel_tex_fetch(__prim_type, isect->prim) & PRIMITIVE_ALL_TRIANGLE)
|
||||
#endif
|
||||
{
|
||||
shader = kernel_tex_fetch(__tri_shader, prim);
|
||||
}
|
||||
#ifdef __HAIR__
|
||||
else {
|
||||
float4 str = kernel_tex_fetch(__curves, prim);
|
||||
shader = __float_as_int(str.z);
|
||||
}
|
||||
#endif
|
||||
|
||||
return kernel_tex_fetch(__shaders, (shader & SHADER_MASK)).flags;
|
||||
}
|
||||
|
||||
ccl_device_forceinline int intersection_get_shader(KernelGlobals *ccl_restrict kg,
|
||||
const Intersection *isect)
|
||||
{
|
||||
const int prim = kernel_tex_fetch(__prim_index, isect->prim);
|
||||
int shader = 0;
|
||||
|
||||
#ifdef __HAIR__
|
||||
if (kernel_tex_fetch(__prim_type, isect->prim) & PRIMITIVE_ALL_TRIANGLE)
|
||||
#endif
|
||||
{
|
||||
shader = kernel_tex_fetch(__tri_shader, prim);
|
||||
}
|
||||
#ifdef __HAIR__
|
||||
else {
|
||||
float4 str = kernel_tex_fetch(__curves, prim);
|
||||
shader = __float_as_int(str.z);
|
||||
}
|
||||
#endif
|
||||
|
||||
return shader & SHADER_MASK;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -135,6 +135,8 @@ ccl_device_inline bool lamp_light_sample(
|
||||
ls->pdf = invarea;
|
||||
}
|
||||
else {
|
||||
inplane = ls->P;
|
||||
|
||||
float3 sample_axisu = axisu;
|
||||
float3 sample_axisv = axisv;
|
||||
|
||||
@@ -145,7 +147,6 @@ ccl_device_inline bool lamp_light_sample(
|
||||
}
|
||||
}
|
||||
|
||||
inplane = ls->P;
|
||||
ls->pdf = rect_light_sample(P, &ls->P, sample_axisu, sample_axisv, randu, randv, true);
|
||||
inplane = ls->P - inplane;
|
||||
}
|
||||
|
@@ -65,7 +65,6 @@ ccl_device_forceinline bool kernel_path_scene_intersect(KernelGlobals *kg,
|
||||
uint visibility = path_state_ray_visibility(kg, state);
|
||||
|
||||
if (path_state_ao_bounce(kg, state)) {
|
||||
visibility = PATH_RAY_SHADOW;
|
||||
ray->t = kernel_data.background.ao_distance;
|
||||
}
|
||||
|
||||
@@ -416,7 +415,13 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg,
|
||||
break;
|
||||
}
|
||||
else if (path_state_ao_bounce(kg, state)) {
|
||||
break;
|
||||
if (intersection_get_shader_flags(kg, &isect) &
|
||||
(SD_HAS_TRANSPARENT_SHADOW | SD_HAS_EMISSION)) {
|
||||
state->flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup shader data. */
|
||||
@@ -554,7 +559,13 @@ ccl_device_forceinline void kernel_path_integrate(KernelGlobals *kg,
|
||||
break;
|
||||
}
|
||||
else if (path_state_ao_bounce(kg, state)) {
|
||||
break;
|
||||
if (intersection_get_shader_flags(kg, &isect) &
|
||||
(SD_HAS_TRANSPARENT_SHADOW | SD_HAS_EMISSION)) {
|
||||
state->flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup shader data. */
|
||||
|
@@ -895,6 +895,8 @@ enum ShaderDataFlag {
|
||||
SD_HAS_CONSTANT_EMISSION = (1 << 27),
|
||||
/* Needs to access attributes for volume rendering */
|
||||
SD_NEED_VOLUME_ATTRIBUTES = (1 << 28),
|
||||
/* Shader has emission */
|
||||
SD_HAS_EMISSION = (1 << 29),
|
||||
|
||||
SD_SHADER_FLAGS = (SD_USE_MIS | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME | SD_HAS_ONLY_VOLUME |
|
||||
SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP | SD_VOLUME_EQUIANGULAR |
|
||||
|
@@ -1364,7 +1364,6 @@ void GeometryManager::device_update_bvh(Device *device,
|
||||
}
|
||||
|
||||
dscene->data.bvh.root = pack.root_index;
|
||||
dscene->data.bvh.bvh_layout = bparams.bvh_layout;
|
||||
dscene->data.bvh.use_bvh_steps = (scene->params.num_bvh_time_steps != 0);
|
||||
dscene->data.bvh.curve_subdivisions = scene->params.curve_subdivisions();
|
||||
/* The scene handle is set in 'CPUDevice::const_copy_to' and 'OptiXDevice::const_copy_to' */
|
||||
@@ -1984,6 +1983,11 @@ void GeometryManager::device_update(Device *device,
|
||||
}
|
||||
}
|
||||
|
||||
/* Always set BVH layout again after displacement where it was set to none,
|
||||
* to avoid ray-tracing at that stage. */
|
||||
dscene->data.bvh.bvh_layout = BVHParams::best_bvh_layout(scene->params.bvh_layout,
|
||||
device->get_bvh_layout_mask());
|
||||
|
||||
{
|
||||
scoped_callback_timer timer([scene](double time) {
|
||||
if (scene->update_stats) {
|
||||
|
@@ -179,7 +179,7 @@ void Scene::free_memory(bool final)
|
||||
|
||||
bake_manager->device_free(device, &dscene);
|
||||
|
||||
if (!params.persistent_data || final)
|
||||
if (final)
|
||||
image_manager->device_free(device);
|
||||
else
|
||||
image_manager->device_free_builtin(device);
|
||||
|
@@ -178,7 +178,6 @@ class SceneParams {
|
||||
int num_bvh_time_steps;
|
||||
int hair_subdivisions;
|
||||
CurveShapeType hair_shape;
|
||||
bool persistent_data;
|
||||
int texture_limit;
|
||||
|
||||
bool background;
|
||||
@@ -193,7 +192,6 @@ class SceneParams {
|
||||
num_bvh_time_steps = 0;
|
||||
hair_subdivisions = 3;
|
||||
hair_shape = CURVE_RIBBON;
|
||||
persistent_data = false;
|
||||
texture_limit = 0;
|
||||
background = true;
|
||||
}
|
||||
@@ -206,7 +204,7 @@ class SceneParams {
|
||||
use_bvh_unaligned_nodes == params.use_bvh_unaligned_nodes &&
|
||||
num_bvh_time_steps == params.num_bvh_time_steps &&
|
||||
hair_subdivisions == params.hair_subdivisions && hair_shape == params.hair_shape &&
|
||||
persistent_data == params.persistent_data && texture_limit == params.texture_limit);
|
||||
texture_limit == params.texture_limit);
|
||||
}
|
||||
|
||||
int curve_subdivisions()
|
||||
|
@@ -528,6 +528,8 @@ void ShaderManager::device_update_common(Device *device,
|
||||
|
||||
if (shader->get_use_mis())
|
||||
flag |= SD_USE_MIS;
|
||||
if (shader->has_surface_emission)
|
||||
flag |= SD_HAS_EMISSION;
|
||||
if (shader->has_surface_transparent && shader->get_use_transparent_shadow())
|
||||
flag |= SD_HAS_TRANSPARENT_SHADOW;
|
||||
if (shader->has_volume) {
|
||||
|
@@ -788,7 +788,7 @@ ccl_device_inline float compare_floats(float a, float b, float abs_diff, int ulp
|
||||
}
|
||||
|
||||
/* Calculate the angle between the two vectors a and b.
|
||||
* The usual approach acos(dot(a, b)) has severe precision issues for small angles,
|
||||
* The usual approach `acos(dot(a, b))` has severe precision issues for small angles,
|
||||
* which are avoided by this method.
|
||||
* Based on "Mangled Angles" from https://people.eecs.berkeley.edu/~wkahan/Mindless.pdf
|
||||
*/
|
||||
|
@@ -1442,12 +1442,23 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
*/
|
||||
break;
|
||||
case WM_SYSCOMMAND:
|
||||
/* The WM_SYSCHAR message is sent to the window when system commands such as
|
||||
/* The WM_SYSCOMMAND message is sent to the window when system commands such as
|
||||
* maximize, minimize or close the window are triggered. Also it is sent when ALT
|
||||
* button is press for menu. To prevent this we must return preventing DefWindowProc.
|
||||
*
|
||||
* Note that the four low-order bits of the wParam parameter are used internally by the
|
||||
* OS. To obtain the correct result when testing the value of wParam, an application
|
||||
* must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator.
|
||||
*/
|
||||
if (wParam == SC_KEYMENU) {
|
||||
eventHandled = true;
|
||||
switch (wParam & 0xFFF0) {
|
||||
case SC_KEYMENU:
|
||||
eventHandled = true;
|
||||
break;
|
||||
case SC_RESTORE:
|
||||
::ShowWindow(hwnd, SW_RESTORE);
|
||||
window->setState(window->getState());
|
||||
eventHandled = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -1131,7 +1131,8 @@ GHOST_TSuccess GHOST_WindowCocoa::hasCursorShape(GHOST_TStandardCursor shape)
|
||||
return success;
|
||||
}
|
||||
|
||||
/** Reverse the bits in a GHOST_TUns8
|
||||
/* Reverse the bits in a GHOST_TUns8 */
|
||||
#if 0
|
||||
static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch)
|
||||
{
|
||||
ch= ((ch >> 1) & 0x55) | ((ch << 1) & 0xAA);
|
||||
@@ -1139,7 +1140,7 @@ static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch)
|
||||
ch= ((ch >> 4) & 0x0F) | ((ch << 4) & 0xF0);
|
||||
return ch;
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
|
||||
/** Reverse the bits in a GHOST_TUns16 */
|
||||
static GHOST_TUns16 uns16ReverseBits(GHOST_TUns16 shrt)
|
||||
|
@@ -521,7 +521,7 @@ GHOST_TSuccess GHOST_WindowWin32::setState(GHOST_TWindowState state)
|
||||
|
||||
switch (state) {
|
||||
case GHOST_kWindowStateMinimized:
|
||||
wp.showCmd = SW_SHOWMINIMIZED;
|
||||
wp.showCmd = SW_MINIMIZE;
|
||||
break;
|
||||
case GHOST_kWindowStateMaximized:
|
||||
wp.showCmd = SW_SHOWMAXIMIZED;
|
||||
|
@@ -555,7 +555,7 @@ class GHOST_WindowWin32 : public GHOST_Window {
|
||||
|
||||
/* Wintab API */
|
||||
struct {
|
||||
/** WinTab DLL handle. */
|
||||
/** `WinTab.dll` handle. */
|
||||
HMODULE handle = NULL;
|
||||
|
||||
/** API functions */
|
||||
@@ -574,7 +574,7 @@ class GHOST_WindowWin32 : public GHOST_Window {
|
||||
|
||||
GHOST_TWindowState m_normal_state;
|
||||
|
||||
/** user32 dll handle*/
|
||||
/** `user32.dll` handle */
|
||||
HMODULE m_user32;
|
||||
GHOST_WIN32_GetPointerInfoHistory m_fpGetPointerInfoHistory;
|
||||
GHOST_WIN32_GetPointerPenInfoHistory m_fpGetPointerPenInfoHistory;
|
||||
|
@@ -57,9 +57,11 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Returns the length of the allocated memory segment pointed at
|
||||
/**
|
||||
* Returns the length of the allocated memory segment pointed at
|
||||
* by vmemh. If the pointer was not previously allocated by this
|
||||
* module, the result is undefined.*/
|
||||
* module, the result is undefined.
|
||||
*/
|
||||
extern size_t (*MEM_allocN_len)(const void *vmemh) ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@@ -103,7 +105,8 @@ extern void *(*MEM_recallocN_id)(void *vmemh,
|
||||
/**
|
||||
* Allocate a block of memory of size len, with tag name str. The
|
||||
* memory is cleared. The name must be static, because only a
|
||||
* pointer to it is stored ! */
|
||||
* pointer to it is stored!
|
||||
*/
|
||||
extern void *(*MEM_callocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
|
||||
|
||||
@@ -143,12 +146,15 @@ extern void *(*MEM_mallocN_aligned)(size_t len,
|
||||
const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
|
||||
|
||||
/** Print a list of the names and sizes of all allocated memory
|
||||
* blocks. as a python dict for easy investigation */
|
||||
/**
|
||||
* Print a list of the names and sizes of all allocated memory
|
||||
* blocks. as a python dict for easy investigation.
|
||||
*/
|
||||
extern void (*MEM_printmemlist_pydict)(void);
|
||||
|
||||
/** Print a list of the names and sizes of all allocated memory
|
||||
* blocks. */
|
||||
/**
|
||||
* Print a list of the names and sizes of all allocated memory blocks.
|
||||
*/
|
||||
extern void (*MEM_printmemlist)(void);
|
||||
|
||||
/** calls the function on all allocated memory blocks. */
|
||||
@@ -163,7 +169,8 @@ extern void (*MEM_set_error_callback)(void (*func)(const char *));
|
||||
/**
|
||||
* Are the start/end block markers still correct ?
|
||||
*
|
||||
* \retval true for correct memory, false for corrupted memory. */
|
||||
* \retval true for correct memory, false for corrupted memory.
|
||||
*/
|
||||
extern bool (*MEM_consistency_check)(void);
|
||||
|
||||
/** Attempt to enforce OSX (or other OS's) to have malloc and stack nonzero */
|
||||
@@ -209,8 +216,10 @@ extern size_t (*MEM_get_peak_memory)(void) ATTR_WARN_UNUSED_RESULT;
|
||||
extern const char *(*MEM_name_ptr)(void *vmemh);
|
||||
#endif
|
||||
|
||||
/** This should be called as early as possible in the program. When it has been called, information
|
||||
* about memory leaks will be printed on exit. */
|
||||
/**
|
||||
* This should be called as early as possible in the program. When it has been called, information
|
||||
* about memory leaks will be printed on exit.
|
||||
*/
|
||||
void MEM_init_memleak_detection(void);
|
||||
|
||||
/**
|
||||
@@ -219,9 +228,11 @@ void MEM_init_memleak_detection(void);
|
||||
*/
|
||||
void MEM_use_memleak_detection(bool enabled);
|
||||
|
||||
/** When this has been called and memory leaks have been detected, the process will have an exit
|
||||
/**
|
||||
* When this has been called and memory leaks have been detected, the process will have an exit
|
||||
* code that indicates failure. This can be used for when checking for memory leaks with automated
|
||||
* tests. */
|
||||
* tests.
|
||||
*/
|
||||
void MEM_enable_fail_on_memleak(void);
|
||||
|
||||
/* Switch allocator to fast mode, with less tracking.
|
||||
|
@@ -561,6 +561,7 @@ static OCIO_GPUDisplayShader &getGPUDisplayShader(
|
||||
GpuShaderDescRcPtr shaderdesc_to_scene_linear = GpuShaderDesc::CreateShaderDesc();
|
||||
shaderdesc_to_scene_linear->setLanguage(GPU_LANGUAGE_GLSL_1_3);
|
||||
shaderdesc_to_scene_linear->setFunctionName("OCIO_to_scene_linear");
|
||||
shaderdesc_to_scene_linear->setResourcePrefix("to_scene");
|
||||
(*(ConstProcessorRcPtr *)processor_to_scene_linear)
|
||||
->getDefaultGPUProcessor()
|
||||
->extractGpuShaderInfo(shaderdesc_to_scene_linear);
|
||||
@@ -569,6 +570,7 @@ static OCIO_GPUDisplayShader &getGPUDisplayShader(
|
||||
GpuShaderDescRcPtr shaderdesc_to_display = GpuShaderDesc::CreateShaderDesc();
|
||||
shaderdesc_to_display->setLanguage(GPU_LANGUAGE_GLSL_1_3);
|
||||
shaderdesc_to_display->setFunctionName("OCIO_to_display");
|
||||
shaderdesc_to_scene_linear->setResourcePrefix("to_display");
|
||||
(*(ConstProcessorRcPtr *)processor_to_display)
|
||||
->getDefaultGPUProcessor()
|
||||
->extractGpuShaderInfo(shaderdesc_to_display);
|
||||
|
@@ -35,7 +35,7 @@ Development
|
||||
License
|
||||
-------
|
||||
|
||||
Blender as a whole is licensed under the GNU Public License, Version 3.
|
||||
Blender as a whole is licensed under the GNU General Public License, Version 3.
|
||||
Individual files may have a different, but compatible license.
|
||||
|
||||
See `blender.org/about/license <https://www.blender.org/about/license>`__ for details.
|
||||
|
Submodule release/datafiles/locale updated: 2cef4877ed...f7b706dd64
Binary file not shown.
Submodule release/scripts/addons updated: 63492d3d03...81815ea92c
Submodule release/scripts/addons_contrib updated: f948f658ba...8970953d4a
14
release/scripts/presets/cycles/integrator/Default.py
Normal file
14
release/scripts/presets/cycles/integrator/Default.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import bpy
|
||||
cycles = bpy.context.scene.cycles
|
||||
|
||||
cycles.max_bounces = 12
|
||||
cycles.caustics_reflective = True
|
||||
cycles.caustics_refractive = True
|
||||
cycles.diffuse_bounces = 4
|
||||
cycles.glossy_bounces = 4
|
||||
cycles.transmission_bounces = 12
|
||||
cycles.volume_bounces = 0
|
||||
cycles.transparent_max_bounces = 8
|
||||
cycles.use_fast_gi = False
|
||||
cycles.ao_bounces = 1
|
||||
cycles.ao_bounces_render = 1
|
@@ -9,3 +9,6 @@ cycles.glossy_bounces = 1
|
||||
cycles.transmission_bounces = 2
|
||||
cycles.volume_bounces = 0
|
||||
cycles.transparent_max_bounces = 8
|
||||
cycles.use_fast_gi = False
|
||||
cycles.ao_bounces = 1
|
||||
cycles.ao_bounces_render = 1
|
||||
|
@@ -0,0 +1,14 @@
|
||||
import bpy
|
||||
cycles = bpy.context.scene.cycles
|
||||
|
||||
cycles.max_bounces = 8
|
||||
cycles.caustics_reflective = False
|
||||
cycles.caustics_refractive = False
|
||||
cycles.diffuse_bounces = 1
|
||||
cycles.glossy_bounces = 4
|
||||
cycles.transmission_bounces = 8
|
||||
cycles.volume_bounces = 2
|
||||
cycles.transparent_max_bounces = 8
|
||||
cycles.use_fast_gi = True
|
||||
cycles.ao_bounces = 2
|
||||
cycles.ao_bounces_render = 2
|
@@ -1,11 +1,14 @@
|
||||
import bpy
|
||||
cycles = bpy.context.scene.cycles
|
||||
|
||||
cycles.max_bounces = 128
|
||||
cycles.max_bounces = 32
|
||||
cycles.caustics_reflective = True
|
||||
cycles.caustics_refractive = True
|
||||
cycles.diffuse_bounces = 128
|
||||
cycles.glossy_bounces = 128
|
||||
cycles.transmission_bounces = 128
|
||||
cycles.volume_bounces = 128
|
||||
cycles.transparent_max_bounces = 128
|
||||
cycles.diffuse_bounces = 32
|
||||
cycles.glossy_bounces = 32
|
||||
cycles.transmission_bounces = 32
|
||||
cycles.volume_bounces = 32
|
||||
cycles.transparent_max_bounces = 32
|
||||
cycles.use_fast_gi = False
|
||||
cycles.ao_bounces = 1
|
||||
cycles.ao_bounces_render = 1
|
||||
|
@@ -7,5 +7,8 @@ cycles.caustics_refractive = False
|
||||
cycles.diffuse_bounces = 1
|
||||
cycles.glossy_bounces = 4
|
||||
cycles.transmission_bounces = 8
|
||||
cycles.volume_bounces = 2
|
||||
cycles.volume_bounces = 0
|
||||
cycles.transparent_max_bounces = 8
|
||||
cycles.use_fast_gi = False
|
||||
cycles.ao_bounces = 1
|
||||
cycles.ao_bounces_render = 1
|
||||
|
@@ -4469,8 +4469,9 @@ def km_sculpt(params):
|
||||
)
|
||||
|
||||
items.extend([
|
||||
# Switch Object (release to avoid conflict with grease pencil drawing).
|
||||
("object.switch_object", {"type": 'D', "value": 'RELEASE'}, None),
|
||||
# Transfer Sculpt Mode (release to avoid conflict with grease pencil drawing).
|
||||
("object.transfer_mode", {"type": 'D', "value": 'RELEASE'},
|
||||
{"properties": [("use_eyedropper", False)]}),
|
||||
# Brush strokes
|
||||
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("mode", 'NORMAL')]}),
|
||||
@@ -4594,8 +4595,6 @@ def km_mesh(params):
|
||||
)
|
||||
|
||||
items.extend([
|
||||
# Switch Object (release to avoid conflict with grease pencil drawing).
|
||||
("object.switch_object", {"type": 'D', "value": 'RELEASE'}, None),
|
||||
# Tools.
|
||||
("mesh.loopcut_slide", {"type": 'R', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("TRANSFORM_OT_edge_slide", [("release_confirm", False)],)]}),
|
||||
|
@@ -18,7 +18,6 @@
|
||||
|
||||
# <pep8 compliant>
|
||||
import bpy
|
||||
import os
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty
|
||||
from mathutils import (
|
||||
@@ -356,6 +355,7 @@ class CLIP_OT_delete_proxy(Operator):
|
||||
|
||||
@staticmethod
|
||||
def _rmproxy(abspath):
|
||||
import os
|
||||
import shutil
|
||||
|
||||
if not os.path.exists(abspath):
|
||||
@@ -367,6 +367,7 @@ class CLIP_OT_delete_proxy(Operator):
|
||||
os.remove(abspath)
|
||||
|
||||
def execute(self, context):
|
||||
import os
|
||||
sc = context.space_data
|
||||
clip = sc.clip
|
||||
if clip.use_proxy_custom_directory:
|
||||
|
@@ -20,7 +20,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
import nodeitems_utils
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
PropertyGroup,
|
||||
@@ -195,6 +194,8 @@ class NODE_OT_add_search(NodeAddOperator, Operator):
|
||||
|
||||
# Create an enum list from node items
|
||||
def node_enum_items(self, context):
|
||||
import nodeitems_utils
|
||||
|
||||
enum_items = NODE_OT_add_search._enum_item_hack
|
||||
enum_items.clear()
|
||||
|
||||
@@ -210,6 +211,8 @@ class NODE_OT_add_search(NodeAddOperator, Operator):
|
||||
|
||||
# Look up the item based on index
|
||||
def find_node_item(self, context):
|
||||
import nodeitems_utils
|
||||
|
||||
node_item = int(self.node_item)
|
||||
for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
|
||||
if index == node_item:
|
||||
@@ -303,6 +306,62 @@ class NODE_OT_tree_path_parent(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class NODE_OT_active_preview_toggle(Operator):
|
||||
'''Toggle active preview state of node'''
|
||||
bl_idname = "node.active_preview_toggle"
|
||||
bl_label = "Toggle Active Preview"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
space = context.space_data
|
||||
if space.type != 'NODE_EDITOR':
|
||||
return False
|
||||
if space.edit_tree is None:
|
||||
return False
|
||||
if space.edit_tree.nodes.active is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
node_editor = context.space_data
|
||||
ntree = node_editor.edit_tree
|
||||
active_node = ntree.nodes.active
|
||||
|
||||
if active_node.active_preview:
|
||||
self.disable_preview(context, ntree, active_node)
|
||||
else:
|
||||
self.enable_preview(context, node_editor, ntree, active_node)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def enable_preview(self, context, node_editor, ntree, active_node):
|
||||
spreadsheets = self.find_unpinned_spreadsheets(context)
|
||||
|
||||
for spreadsheet in spreadsheets:
|
||||
spreadsheet.set_geometry_node_context(node_editor, active_node)
|
||||
|
||||
for node in ntree.nodes:
|
||||
node.active_preview = False
|
||||
active_node.active_preview = True
|
||||
|
||||
def disable_preview(self, context, ntree, active_node):
|
||||
spreadsheets = self.find_unpinned_spreadsheets(context)
|
||||
for spreadsheet in spreadsheets:
|
||||
spreadsheet.context_path.clear()
|
||||
|
||||
active_node.active_preview = False
|
||||
|
||||
def find_unpinned_spreadsheets(self, context):
|
||||
spreadsheets = []
|
||||
for window in context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
space = area.spaces.active
|
||||
if space.type == 'SPREADSHEET' and not space.is_pinned:
|
||||
spreadsheets.append(space)
|
||||
return spreadsheets
|
||||
|
||||
|
||||
classes = (
|
||||
NodeSetting,
|
||||
|
||||
@@ -311,4 +370,5 @@ classes = (
|
||||
NODE_OT_add_search,
|
||||
NODE_OT_collapse_hide_unused_toggle,
|
||||
NODE_OT_tree_path_parent,
|
||||
NODE_OT_active_preview_toggle,
|
||||
)
|
||||
|
@@ -22,7 +22,6 @@
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import os
|
||||
|
||||
from bpy.app.translations import pgettext_tip as tip_
|
||||
|
||||
@@ -62,6 +61,7 @@ class PlayRenderedAnim(Operator):
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
import os
|
||||
import subprocess
|
||||
from shlex import quote
|
||||
|
||||
|
@@ -34,13 +34,45 @@ class SPREADSHEET_OT_toggle_pin(Operator):
|
||||
def execute(self, context):
|
||||
space = context.space_data
|
||||
|
||||
if space.pinned_id:
|
||||
space.pinned_id = None
|
||||
if space.is_pinned:
|
||||
self.unpin(context)
|
||||
else:
|
||||
space.pinned_id = context.active_object
|
||||
|
||||
self.pin(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
def pin(self, context):
|
||||
space = context.space_data
|
||||
space.is_pinned = True
|
||||
|
||||
def unpin(self, context):
|
||||
space = context.space_data
|
||||
space.is_pinned = False
|
||||
|
||||
space.context_path.clear()
|
||||
|
||||
# Try to find a node with an active preview in any open editor.
|
||||
if space.object_eval_state == 'EVALUATED':
|
||||
node_editors = self.find_geometry_node_editors(context)
|
||||
for node_editor in node_editors:
|
||||
ntree = node_editor.edit_tree
|
||||
for node in ntree.nodes:
|
||||
if node.active_preview:
|
||||
space.set_geometry_node_context(node_editor, node)
|
||||
return
|
||||
|
||||
def find_geometry_node_editors(self, context):
|
||||
editors = []
|
||||
for window in context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
space = area.spaces.active
|
||||
if space.type != 'NODE_EDITOR':
|
||||
continue
|
||||
if space.edit_tree is None:
|
||||
continue
|
||||
if space.edit_tree.type == 'GEOMETRY':
|
||||
editors.append(space)
|
||||
return editors
|
||||
|
||||
|
||||
classes = (
|
||||
SPREADSHEET_OT_toggle_pin,
|
||||
|
@@ -99,6 +99,15 @@ class PREFERENCES_OT_copy_prev(Operator):
|
||||
version = bpy.app.version
|
||||
version_new = ((version[0] * 100) + version[1])
|
||||
version_old = ((version[0] * 100) + version[1]) - 1
|
||||
|
||||
# Special case, remove when the version is > 3.0.
|
||||
if version_new == 300:
|
||||
version_new = 294
|
||||
version_old = 293
|
||||
else:
|
||||
print("TODO: remove exception!")
|
||||
# End special case.
|
||||
|
||||
# Ensure we only try to copy files from a point release.
|
||||
# The check below ensures the second numbers match.
|
||||
while (version_new % 100) // 10 == (version_old % 100) // 10:
|
||||
|
@@ -71,10 +71,10 @@ class POINTCLOUD_MT_add_attribute(Menu):
|
||||
layout = self.layout
|
||||
pointcloud = context.pointcloud
|
||||
|
||||
self.add_standard_attribute(layout, pointcloud, 'Radius', 'FLOAT', 'POINT')
|
||||
self.add_standard_attribute(layout, pointcloud, 'Color', 'FLOAT_COLOR', 'POINT')
|
||||
self.add_standard_attribute(layout, pointcloud, 'Particle ID', 'INT', 'POINT')
|
||||
self.add_standard_attribute(layout, pointcloud, 'Velocity', 'FLOAT_VECTOR', 'POINT')
|
||||
self.add_standard_attribute(layout, pointcloud, 'radius', 'FLOAT', 'POINT')
|
||||
self.add_standard_attribute(layout, pointcloud, 'color', 'FLOAT_COLOR', 'POINT')
|
||||
self.add_standard_attribute(layout, pointcloud, 'id', 'INT', 'POINT')
|
||||
self.add_standard_attribute(layout, pointcloud, 'velocity', 'FLOAT_VECTOR', 'POINT')
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
@@ -147,8 +147,7 @@ class GreasePencilDisplayPanel:
|
||||
|
||||
if self.is_popover:
|
||||
row = layout.row(align=True)
|
||||
row.prop(settings, "show_brush", text="")
|
||||
row.label(text="Display Cursor")
|
||||
row.prop(settings, "show_brush", text="Display Cursor")
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.active = settings.show_brush
|
||||
@@ -822,6 +821,12 @@ class GreasePencilLayerMasksPanel:
|
||||
col2.menu("GPENCIL_MT_layer_mask_menu", icon='ADD', text="")
|
||||
col2.operator("gpencil.layer_mask_remove", icon='REMOVE', text="")
|
||||
|
||||
col2.separator()
|
||||
|
||||
sub = col2.column(align=True)
|
||||
sub.operator("gpencil.layer_mask_move", icon='TRIA_UP', text="").type = 'UP'
|
||||
sub.operator("gpencil.layer_mask_move", icon='TRIA_DOWN', text="").type = 'DOWN'
|
||||
|
||||
|
||||
class GreasePencilLayerRelationsPanel:
|
||||
|
||||
|
@@ -38,13 +38,12 @@ class PhysicButtonsPanel:
|
||||
def physics_add(layout, md, name, type, typeicon, toggles):
|
||||
row = layout.row(align=True)
|
||||
if md:
|
||||
row.context_pointer_set("modifier", md)
|
||||
row.operator(
|
||||
"object.modifier_remove",
|
||||
text=name,
|
||||
text_ctxt=i18n_contexts.default,
|
||||
icon='X',
|
||||
)
|
||||
).modifier = md.name
|
||||
if toggles:
|
||||
row.prop(md, "show_viewport", text="")
|
||||
row.prop(md, "show_render", text="")
|
||||
|
@@ -246,11 +246,12 @@ class IMAGE_MT_image(Menu):
|
||||
layout.separator()
|
||||
layout.operator("image.pack", text="Pack")
|
||||
|
||||
if ima:
|
||||
if ima and context.area.ui_type == 'IMAGE_EDITOR':
|
||||
layout.separator()
|
||||
layout.operator("palette.extract_from_image", text="Extract Palette")
|
||||
layout.operator("gpencil.image_to_grease_pencil", text="Generate Grease Pencil")
|
||||
|
||||
|
||||
class IMAGE_MT_image_flip(Menu):
|
||||
bl_label = "Flip"
|
||||
|
||||
|
@@ -18,7 +18,6 @@
|
||||
|
||||
# <pep8 compliant>
|
||||
import bpy
|
||||
import nodeitems_utils
|
||||
from bpy.types import Header, Menu, Panel
|
||||
from bpy.app.translations import pgettext_iface as iface_
|
||||
from bpy.app.translations import contexts as i18n_contexts
|
||||
@@ -225,6 +224,8 @@ class NODE_MT_add(bpy.types.Menu):
|
||||
bl_translation_context = i18n_contexts.operator_default
|
||||
|
||||
def draw(self, context):
|
||||
import nodeitems_utils
|
||||
|
||||
layout = self.layout
|
||||
|
||||
layout.operator_context = 'INVOKE_DEFAULT'
|
||||
|
@@ -51,13 +51,13 @@ class OUTLINER_HT_header(Header):
|
||||
row.prop(space, "use_sync_select", icon='UV_SYNC_SELECT', text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
if display_mode in {'SCENES', 'VIEW_LAYER'}:
|
||||
if display_mode in {'SCENES', 'VIEW_LAYER', 'LIBRARY_OVERRIDES'}:
|
||||
row.popover(
|
||||
panel="OUTLINER_PT_filter",
|
||||
text="",
|
||||
icon='FILTER',
|
||||
)
|
||||
elif display_mode in {'LIBRARIES', 'ORPHAN_DATA'}:
|
||||
if display_mode in {'LIBRARIES', 'LIBRARY_OVERRIDES', 'ORPHAN_DATA'}:
|
||||
row.prop(space, "use_filter_id_type", text="", icon='FILTER')
|
||||
sub = row.row(align=True)
|
||||
sub.active = space.use_filter_id_type
|
||||
@@ -341,19 +341,26 @@ class OUTLINER_PT_filter(Panel):
|
||||
col = layout.column(align=True)
|
||||
col.prop(space, "use_sort_alpha")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(space, "use_sync_select", text="Sync Selection")
|
||||
if display_mode not in {'LIBRARY_OVERRIDES'}:
|
||||
row = layout.row(align=True)
|
||||
row.prop(space, "use_sync_select", text="Sync Selection")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(space, "show_mode_column", text="Show Mode Column")
|
||||
layout.separator()
|
||||
row = layout.row(align=True)
|
||||
row.prop(space, "show_mode_column", text="Show Mode Column")
|
||||
layout.separator()
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Search")
|
||||
col.prop(space, "use_filter_complete", text="Exact Match")
|
||||
col.prop(space, "use_filter_case_sensitive", text="Case Sensitive")
|
||||
|
||||
if display_mode != 'VIEW_LAYER':
|
||||
if display_mode in {'LIBRARY_OVERRIDES'} and bpy.data.libraries:
|
||||
col.separator()
|
||||
row = col.row()
|
||||
row.label(icon='LIBRARY_DATA_OVERRIDE')
|
||||
row.prop(space, "use_filter_lib_override_system", text="System Overrides")
|
||||
|
||||
if display_mode not in {'VIEW_LAYER'}:
|
||||
return
|
||||
|
||||
layout.separator()
|
||||
@@ -405,10 +412,6 @@ class OUTLINER_PT_filter(Panel):
|
||||
row = sub.row()
|
||||
row.label(icon='EMPTY_DATA')
|
||||
row.prop(space, "use_filter_object_empty", text="Empties")
|
||||
row = sub.row()
|
||||
if bpy.data.libraries:
|
||||
row.label(icon='LIBRARY_DATA_OVERRIDE')
|
||||
row.prop(space, "use_filter_lib_override", text="Library Overrides")
|
||||
|
||||
if (
|
||||
bpy.data.curves or
|
||||
@@ -425,6 +428,16 @@ class OUTLINER_PT_filter(Panel):
|
||||
row.label(icon='BLANK1')
|
||||
row.prop(space, "use_filter_object_others", text="Others")
|
||||
|
||||
if bpy.data.libraries:
|
||||
col.separator()
|
||||
row = col.row()
|
||||
row.label(icon='LIBRARY_DATA_OVERRIDE')
|
||||
row.prop(space, "use_filter_lib_override", text="Library Overrides")
|
||||
row = col.row()
|
||||
row.label(icon='LIBRARY_DATA_OVERRIDE')
|
||||
row.prop(space, "use_filter_lib_override_system", text="System Overrides")
|
||||
|
||||
|
||||
|
||||
classes = (
|
||||
OUTLINER_HT_header,
|
||||
|
@@ -28,8 +28,17 @@ class SPREADSHEET_HT_header(bpy.types.Header):
|
||||
|
||||
layout.template_header()
|
||||
|
||||
pinned_id = space.pinned_id
|
||||
used_id = pinned_id if pinned_id else context.active_object
|
||||
if len(space.context_path) == 0:
|
||||
self.draw_without_context_path(layout)
|
||||
return
|
||||
root_context = space.context_path[0]
|
||||
if root_context.type != 'OBJECT':
|
||||
self.draw_without_context_path(layout)
|
||||
return
|
||||
obj = root_context.object
|
||||
if obj is None:
|
||||
self.draw_without_context_path(layout)
|
||||
return
|
||||
|
||||
layout.prop(space, "object_eval_state", text="")
|
||||
if space.object_eval_state != 'ORIGINAL':
|
||||
@@ -37,16 +46,61 @@ class SPREADSHEET_HT_header(bpy.types.Header):
|
||||
if space.geometry_component_type != 'INSTANCES':
|
||||
layout.prop(space, "attribute_domain", text="")
|
||||
|
||||
if used_id:
|
||||
layout.label(text=used_id.name, icon='OBJECT_DATA')
|
||||
context_path = space.context_path
|
||||
if space.object_eval_state == 'ORIGINAL':
|
||||
# Only show first context.
|
||||
context_path = context_path[:1]
|
||||
if space.display_context_path_collapsed:
|
||||
self.draw_collapsed_context_path(context, layout, context_path)
|
||||
else:
|
||||
self.draw_full_context_path(context, layout, context_path)
|
||||
|
||||
layout.operator("spreadsheet.toggle_pin", text="", icon='PINNED' if pinned_id else 'UNPINNED', emboss=False)
|
||||
pin_icon = 'PINNED' if space.is_pinned else 'UNPINNED'
|
||||
layout.operator("spreadsheet.toggle_pin", text="", icon=pin_icon, emboss=False)
|
||||
|
||||
layout.separator_spacer()
|
||||
|
||||
if isinstance(used_id, bpy.types.Object) and used_id.mode == 'EDIT':
|
||||
if isinstance(obj, bpy.types.Object) and obj.mode == 'EDIT':
|
||||
layout.prop(space, "show_only_selected", text="Selected Only")
|
||||
|
||||
def draw_without_context_path(self, layout):
|
||||
layout.label(text="No active context")
|
||||
|
||||
def draw_full_context_path(self, context, layout, context_path):
|
||||
space = context.space_data
|
||||
row = layout.row()
|
||||
for ctx in context_path[:-1]:
|
||||
subrow = row.row(align=True)
|
||||
self.draw_spreadsheet_context(subrow, ctx)
|
||||
self.draw_spreadsheet_context_path_icon(subrow, space)
|
||||
|
||||
self.draw_spreadsheet_context(row, context_path[-1])
|
||||
|
||||
def draw_collapsed_context_path(self, context, layout, context_path):
|
||||
space = context.space_data
|
||||
row = layout.row(align=True)
|
||||
self.draw_spreadsheet_context(row, context_path[0])
|
||||
if len(context_path) == 1:
|
||||
return
|
||||
self.draw_spreadsheet_context_path_icon(row, space)
|
||||
if len(context_path) > 2:
|
||||
self.draw_spreadsheet_context_path_icon(row, space, icon='DOT')
|
||||
self.draw_spreadsheet_context_path_icon(row, space)
|
||||
self.draw_spreadsheet_context(row, context_path[-1])
|
||||
|
||||
def draw_spreadsheet_context(self, layout, ctx):
|
||||
if ctx.type == 'OBJECT':
|
||||
if ctx.object is None:
|
||||
layout.label(text="<no object>", icon='OBJECT_DATA')
|
||||
else:
|
||||
layout.label(text=ctx.object.name, icon='OBJECT_DATA')
|
||||
elif ctx.type == 'MODIFIER':
|
||||
layout.label(text=ctx.modifier_name, icon='MODIFIER')
|
||||
elif ctx.type == 'NODE':
|
||||
layout.label(text=ctx.node_name, icon='NODE')
|
||||
|
||||
def draw_spreadsheet_context_path_icon(self, layout, space, icon='RIGHTARROW_THIN'):
|
||||
layout.prop(space, "display_context_path_collapsed", icon_only=True, emboss=False, icon=icon)
|
||||
|
||||
classes = (
|
||||
SPREADSHEET_HT_header,
|
||||
|
@@ -2241,7 +2241,6 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
|
||||
self._draw_items(
|
||||
context, (
|
||||
({"property": "use_sculpt_vertex_colors"}, "T71947"),
|
||||
({"property": "use_switch_object_operator"}, "T80402"),
|
||||
({"property": "use_sculpt_tools_tilt"}, "T82877"),
|
||||
({"property": "use_asset_browser"}, ("project/profile/124/", "Milestone 1")),
|
||||
({"property": "use_override_templates"}, ("T73318", "Milestone 4")),
|
||||
|
@@ -161,9 +161,9 @@ class VIEW3D_HT_tool_header(Header):
|
||||
elif mode_string in {'EDIT_MESH', 'PAINT_WEIGHT', 'SCULPT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}:
|
||||
# Mesh Modes, Use Mesh Symmetry
|
||||
row, sub = row_for_mirror()
|
||||
sub.prop(context.object.data, "use_mirror_x", text="X", toggle=True)
|
||||
sub.prop(context.object.data, "use_mirror_y", text="Y", toggle=True)
|
||||
sub.prop(context.object.data, "use_mirror_z", text="Z", toggle=True)
|
||||
sub.prop(context.object, "use_mesh_mirror_x", text="X", toggle=True)
|
||||
sub.prop(context.object, "use_mesh_mirror_y", text="Y", toggle=True)
|
||||
sub.prop(context.object, "use_mesh_mirror_z", text="Z", toggle=True)
|
||||
if mode_string == 'EDIT_MESH':
|
||||
tool_settings = context.tool_settings
|
||||
layout.prop(tool_settings, "use_mesh_automerge", text="")
|
||||
@@ -1366,7 +1366,7 @@ class VIEW3D_MT_select_object(Menu):
|
||||
|
||||
layout.operator_menu_enum("object.select_by_type", "type", text="Select All by Type")
|
||||
layout.operator("object.select_camera", text="Select Active Camera")
|
||||
layout.operator("object.select_mirror", text="Mirror Selection")
|
||||
layout.operator("object.select_mirror")
|
||||
layout.operator("object.select_random", text="Select Random")
|
||||
|
||||
layout.separator()
|
||||
@@ -1424,7 +1424,7 @@ class VIEW3D_MT_select_pose(Menu):
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("pose.select_mirror", text="Flip Active")
|
||||
layout.operator("pose.select_mirror")
|
||||
|
||||
layout.separator()
|
||||
|
||||
@@ -1597,7 +1597,7 @@ class VIEW3D_MT_select_edit_mesh(Menu):
|
||||
layout.separator()
|
||||
|
||||
layout.operator("mesh.select_axis", text="Side of Active")
|
||||
layout.operator("mesh.select_mirror", text="Mirror Selection")
|
||||
layout.operator("mesh.select_mirror")
|
||||
|
||||
|
||||
class VIEW3D_MT_select_edit_curve(Menu):
|
||||
@@ -1784,7 +1784,7 @@ class VIEW3D_MT_select_edit_armature(Menu):
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("armature.select_mirror", text="Mirror").extend = False
|
||||
layout.operator("armature.select_mirror")
|
||||
|
||||
layout.separator()
|
||||
|
||||
@@ -3038,6 +3038,11 @@ class VIEW3D_MT_sculpt(Menu):
|
||||
|
||||
layout.operator("sculpt.optimize")
|
||||
|
||||
layout.separator()
|
||||
|
||||
props = layout.operator("object.transfer_mode", text="Transfer Sculpt Mode")
|
||||
props.use_eyedropper = True
|
||||
|
||||
|
||||
class VIEW3D_MT_mask(Menu):
|
||||
bl_label = "Mask"
|
||||
@@ -3090,19 +3095,15 @@ class VIEW3D_MT_mask(Menu):
|
||||
|
||||
layout.separator()
|
||||
|
||||
props = layout.operator("sculpt.mask_expand", text="Expand Mask by Topology")
|
||||
props.use_normals = False
|
||||
props.keep_previous_mask = False
|
||||
props = layout.operator("sculpt.expand", text="Expand Mask by Topology")
|
||||
props.target = 'MASK'
|
||||
props.falloff_type = 'GEODESIC'
|
||||
props.invert = True
|
||||
props.smooth_iterations = 2
|
||||
props.create_face_set = False
|
||||
|
||||
props = layout.operator("sculpt.mask_expand", text="Expand Mask by Curvature")
|
||||
props.use_normals = True
|
||||
props.keep_previous_mask = True
|
||||
props = layout.operator("sculpt.expand", text="Expand Mask by Normals")
|
||||
props.target = 'MASK'
|
||||
props.falloff_type = 'NORMALS'
|
||||
props.invert = False
|
||||
props.smooth_iterations = 0
|
||||
props.create_face_set = False
|
||||
|
||||
layout.separator()
|
||||
|
||||
@@ -3156,6 +3157,20 @@ class VIEW3D_MT_face_sets(Menu):
|
||||
|
||||
layout.separator()
|
||||
|
||||
props = layout.operator("sculpt.expand", text="Expand Face Set by Topology")
|
||||
props.target = 'FACE_SETS'
|
||||
props.falloff_type = 'GEODESIC'
|
||||
props.invert = False
|
||||
props.use_modify_active = False
|
||||
|
||||
props = layout.operator("sculpt.expand", text="Expand Active Face Set")
|
||||
props.target = 'FACE_SETS'
|
||||
props.falloff_type = 'BOUNDARY_FACE_SET'
|
||||
props.invert = False
|
||||
props.use_modify_active = True
|
||||
|
||||
layout.separator()
|
||||
|
||||
op = layout.operator("mesh.face_set_extract", text='Extract Face Set')
|
||||
|
||||
layout.separator()
|
||||
|
@@ -123,13 +123,15 @@ class View3DPanel:
|
||||
# **************** standard tool clusters ******************
|
||||
|
||||
# Used by vertex & weight paint
|
||||
def draw_vpaint_symmetry(layout, vpaint, mesh):
|
||||
def draw_vpaint_symmetry(layout, vpaint, obj):
|
||||
col = layout.column()
|
||||
row = col.row(heading="Mirror", align=True)
|
||||
row.prop(mesh, "use_mirror_x", text="X", toggle=True)
|
||||
row.prop(mesh, "use_mirror_y", text="Y", toggle=True)
|
||||
row.prop(mesh, "use_mirror_z", text="Z", toggle=True)
|
||||
row.prop(obj, "use_mesh_mirror_x", text="X", toggle=True)
|
||||
row.prop(obj, "use_mesh_mirror_y", text="Y", toggle=True)
|
||||
row.prop(obj, "use_mesh_mirror_z", text="Z", toggle=True)
|
||||
|
||||
col = layout.column()
|
||||
col.active = not obj.data.use_mirror_vertex_groups
|
||||
col.prop(vpaint, "radial_symmetry", text="Radial")
|
||||
|
||||
|
||||
@@ -977,12 +979,12 @@ class VIEW3D_PT_tools_weightpaint_symmetry(Panel, View3DPaintPanel):
|
||||
wpaint = tool_settings.weight_paint
|
||||
mesh = context.object.data
|
||||
|
||||
draw_vpaint_symmetry(layout, wpaint, mesh)
|
||||
layout.prop(mesh, 'use_mirror_vertex_groups')
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(mesh, 'use_mirror_vertex_group_x', text="Vertex Group X")
|
||||
row = col.row()
|
||||
row.active = mesh.use_mirror_vertex_group_x
|
||||
draw_vpaint_symmetry(layout, wpaint, context.object)
|
||||
|
||||
row = layout.row()
|
||||
row.active = mesh.use_mirror_vertex_groups
|
||||
row.prop(mesh, "use_mirror_topology")
|
||||
|
||||
|
||||
@@ -1057,7 +1059,7 @@ class VIEW3D_PT_tools_vertexpaint_symmetry(Panel, View3DPaintPanel):
|
||||
tool_settings = context.tool_settings
|
||||
vpaint = tool_settings.vertex_paint
|
||||
|
||||
draw_vpaint_symmetry(layout, vpaint, context.object.data)
|
||||
draw_vpaint_symmetry(layout, vpaint, context.object)
|
||||
|
||||
|
||||
class VIEW3D_PT_tools_vertexpaint_symmetry_for_topbar(Panel):
|
||||
|
@@ -498,6 +498,7 @@ geometry_node_categories = [
|
||||
NodeItem("GeometryNodeAttributeSeparateXYZ"),
|
||||
NodeItem("GeometryNodeAttributeRemove"),
|
||||
NodeItem("GeometryNodeAttributeMapRange"),
|
||||
NodeItem("GeometryNodeAttributeTransfer"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_COLOR", "Color", items=[
|
||||
NodeItem("ShaderNodeValToRGB"),
|
||||
@@ -505,6 +506,7 @@ geometry_node_categories = [
|
||||
NodeItem("ShaderNodeCombineRGB"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[
|
||||
NodeItem("GeometryNodeBoundBox"),
|
||||
NodeItem("GeometryNodeTransform"),
|
||||
NodeItem("GeometryNodeJoinGeometry"),
|
||||
]),
|
||||
@@ -549,6 +551,7 @@ geometry_node_categories = [
|
||||
NodeItem("ShaderNodeMath"),
|
||||
NodeItem("FunctionNodeBooleanMath"),
|
||||
NodeItem("FunctionNodeFloatCompare"),
|
||||
NodeItem("GeometryNodeSwitch"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_VECTOR", "Vector", items=[
|
||||
NodeItem("ShaderNodeSeparateXYZ"),
|
||||
|
@@ -26,22 +26,28 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ListBase;
|
||||
struct CurveCache;
|
||||
struct Object;
|
||||
struct Path;
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* Curve Paths */
|
||||
|
||||
void free_path(struct Path *path);
|
||||
void calc_curvepath(struct Object *ob, struct ListBase *nurbs);
|
||||
bool where_on_path(const struct Object *ob,
|
||||
float ctime,
|
||||
float r_vec[4],
|
||||
float r_dir[3],
|
||||
float r_quat[4],
|
||||
float *r_radius,
|
||||
float *r_weight);
|
||||
int BKE_anim_path_get_array_size(const struct CurveCache *curve_cache);
|
||||
float BKE_anim_path_get_length(const struct CurveCache *curve_cache);
|
||||
|
||||
/* This function populates the 'ob->runtime.curve_cache->anim_path_accum_length' data.
|
||||
* You should never have to call this manually as it should already have been called by
|
||||
* 'BKE_displist_make_curveTypes'. Do not call this manually unless you know what you are doing.
|
||||
*/
|
||||
void BKE_anim_path_calc_data(struct Object *ob);
|
||||
|
||||
bool BKE_where_on_path(const struct Object *ob,
|
||||
float ctime,
|
||||
float r_vec[4],
|
||||
float r_dir[3],
|
||||
float r_quat[4],
|
||||
float *r_radius,
|
||||
float *r_weight);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "FN_cpp_type.hh"
|
||||
#include "FN_generic_span.hh"
|
||||
#include "FN_generic_virtual_array.hh"
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
|
||||
@@ -30,6 +31,10 @@
|
||||
namespace blender::bke {
|
||||
|
||||
using fn::CPPType;
|
||||
using fn::GVArray;
|
||||
using fn::GVArrayPtr;
|
||||
using fn::GVMutableArray;
|
||||
using fn::GVMutableArrayPtr;
|
||||
|
||||
const CPPType *custom_data_type_to_cpp_type(const CustomDataType type);
|
||||
CustomDataType cpp_type_to_custom_data_type(const CPPType &type);
|
||||
@@ -37,112 +42,92 @@ CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_
|
||||
AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains);
|
||||
|
||||
/**
|
||||
* This class offers an indirection for reading an attribute.
|
||||
* This is useful for the following reasons:
|
||||
* - Blender does not store all attributes the same way.
|
||||
* The simplest case are custom data layers with primitive types.
|
||||
* A bit more complex are mesh attributes like the position of vertices,
|
||||
* which are embedded into the MVert struct.
|
||||
* Even more complex to access are vertex weights.
|
||||
* - Sometimes attributes are stored on one domain, but we want to access
|
||||
* the attribute on a different domain. Therefore, we have to interpolate
|
||||
* between the domains.
|
||||
* Used when looking up a "plain attribute" based on a name for reading from it.
|
||||
*/
|
||||
class ReadAttribute {
|
||||
protected:
|
||||
const AttributeDomain domain_;
|
||||
const CPPType &cpp_type_;
|
||||
const CustomDataType custom_data_type_;
|
||||
const int64_t size_;
|
||||
struct ReadAttributeLookup {
|
||||
/* The virtual array that is used to read from this attribute. */
|
||||
GVArrayPtr varray;
|
||||
/* Domain the attribute lives on in the geometry. */
|
||||
AttributeDomain domain;
|
||||
|
||||
/* Protects the span below, so that no two threads initialize it at the same time. */
|
||||
mutable std::mutex span_mutex_;
|
||||
/* When it is not null, it points to the attribute array or a temporary array that contains all
|
||||
* the attribute values. */
|
||||
mutable void *array_buffer_ = nullptr;
|
||||
/* Is true when the buffer above is owned by the attribute accessor. */
|
||||
mutable bool array_is_temporary_ = false;
|
||||
|
||||
public:
|
||||
ReadAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size)
|
||||
: domain_(domain),
|
||||
cpp_type_(cpp_type),
|
||||
custom_data_type_(cpp_type_to_custom_data_type(cpp_type)),
|
||||
size_(size)
|
||||
/* Convenience function to check if the attribute has been found. */
|
||||
operator bool() const
|
||||
{
|
||||
return this->varray.get() != nullptr;
|
||||
}
|
||||
|
||||
virtual ~ReadAttribute();
|
||||
|
||||
AttributeDomain domain() const
|
||||
{
|
||||
return domain_;
|
||||
}
|
||||
|
||||
const CPPType &cpp_type() const
|
||||
{
|
||||
return cpp_type_;
|
||||
}
|
||||
|
||||
CustomDataType custom_data_type() const
|
||||
{
|
||||
return custom_data_type_;
|
||||
}
|
||||
|
||||
int64_t size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
void get(const int64_t index, void *r_value) const
|
||||
{
|
||||
BLI_assert(index < size_);
|
||||
this->get_internal(index, r_value);
|
||||
}
|
||||
|
||||
/* Get a span that contains all attribute values. */
|
||||
fn::GSpan get_span() const;
|
||||
|
||||
template<typename T> Span<T> get_span() const
|
||||
{
|
||||
return this->get_span().typed<T>();
|
||||
}
|
||||
|
||||
protected:
|
||||
/* r_value is expected to be uninitialized. */
|
||||
virtual void get_internal(const int64_t index, void *r_value) const = 0;
|
||||
|
||||
virtual void initialize_span() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* This exists for similar reasons as the ReadAttribute class, except that
|
||||
* it does not deal with interpolation between domains.
|
||||
* Used when looking up a "plain attribute" based on a name for reading from it and writing to it.
|
||||
*/
|
||||
class WriteAttribute {
|
||||
protected:
|
||||
const AttributeDomain domain_;
|
||||
const CPPType &cpp_type_;
|
||||
const CustomDataType custom_data_type_;
|
||||
const int64_t size_;
|
||||
struct WriteAttributeLookup {
|
||||
/* The virtual array that is used to read from and write to the attribute. */
|
||||
GVMutableArrayPtr varray;
|
||||
/* Domain the attributes lives on in the geometry. */
|
||||
AttributeDomain domain;
|
||||
|
||||
/* When not null, this points either to the attribute array or to a temporary array. */
|
||||
void *array_buffer_ = nullptr;
|
||||
/* True, when the buffer points to a temporary array. */
|
||||
bool array_is_temporary_ = false;
|
||||
/* This helps to protect against forgetting to apply changes done to the array. */
|
||||
bool array_should_be_applied_ = false;
|
||||
/* Convenience function to check if the attribute has been found. */
|
||||
operator bool() const
|
||||
{
|
||||
return this->varray.get() != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An output attribute allows writing to an attribute (and optionally reading as well). It adds
|
||||
* some convenience features on top of `GVMutableArray` that are very commonly used.
|
||||
*
|
||||
* Supported convenience features:
|
||||
* - Implicit type conversion when writing to builtin attributes.
|
||||
* - Supports simple access to a span containing the attribute values (that avoids the use of
|
||||
* VMutableArray_Span in many cases).
|
||||
* - An output attribute can live side by side with an existing attribute with a different domain
|
||||
* or data type. The old attribute will only be overwritten when the #save function is called.
|
||||
*/
|
||||
class OutputAttribute {
|
||||
public:
|
||||
using SaveFn = std::function<void(OutputAttribute &)>;
|
||||
|
||||
private:
|
||||
GVMutableArrayPtr varray_;
|
||||
AttributeDomain domain_;
|
||||
SaveFn save_;
|
||||
std::optional<fn::GVMutableArray_GSpan> optional_span_varray_;
|
||||
bool ignore_old_values_ = false;
|
||||
|
||||
public:
|
||||
WriteAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size)
|
||||
: domain_(domain),
|
||||
cpp_type_(cpp_type),
|
||||
custom_data_type_(cpp_type_to_custom_data_type(cpp_type)),
|
||||
size_(size)
|
||||
OutputAttribute() = default;
|
||||
|
||||
OutputAttribute(GVMutableArrayPtr varray,
|
||||
AttributeDomain domain,
|
||||
SaveFn save,
|
||||
const bool ignore_old_values)
|
||||
: varray_(std::move(varray)),
|
||||
domain_(domain),
|
||||
save_(std::move(save)),
|
||||
ignore_old_values_(ignore_old_values)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~WriteAttribute();
|
||||
operator bool() const
|
||||
{
|
||||
return varray_.get() != nullptr;
|
||||
}
|
||||
|
||||
GVMutableArray &operator*()
|
||||
{
|
||||
return *varray_;
|
||||
}
|
||||
|
||||
GVMutableArray *operator->()
|
||||
{
|
||||
return varray_.get();
|
||||
}
|
||||
|
||||
GVMutableArray &varray()
|
||||
{
|
||||
return *varray_;
|
||||
}
|
||||
|
||||
AttributeDomain domain() const
|
||||
{
|
||||
@@ -151,168 +136,94 @@ class WriteAttribute {
|
||||
|
||||
const CPPType &cpp_type() const
|
||||
{
|
||||
return cpp_type_;
|
||||
return varray_->type();
|
||||
}
|
||||
|
||||
CustomDataType custom_data_type() const
|
||||
{
|
||||
return custom_data_type_;
|
||||
return cpp_type_to_custom_data_type(this->cpp_type());
|
||||
}
|
||||
|
||||
int64_t size() const
|
||||
fn::GMutableSpan as_span()
|
||||
{
|
||||
return size_;
|
||||
if (!optional_span_varray_.has_value()) {
|
||||
const bool materialize_old_values = !ignore_old_values_;
|
||||
optional_span_varray_.emplace(*varray_, materialize_old_values);
|
||||
}
|
||||
fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_;
|
||||
return span_varray;
|
||||
}
|
||||
|
||||
void get(const int64_t index, void *r_value) const
|
||||
template<typename T> MutableSpan<T> as_span()
|
||||
{
|
||||
BLI_assert(index < size_);
|
||||
this->get_internal(index, r_value);
|
||||
return this->as_span().typed<T>();
|
||||
}
|
||||
|
||||
void set(const int64_t index, const void *value)
|
||||
{
|
||||
BLI_assert(index < size_);
|
||||
this->set_internal(index, value);
|
||||
}
|
||||
|
||||
/* Get a span that new attribute values can be written into. When all values have been changed,
|
||||
* #apply_span has to be called. */
|
||||
fn::GMutableSpan get_span();
|
||||
/* The span returned by this method might not contain the current attribute values. */
|
||||
fn::GMutableSpan get_span_for_write_only();
|
||||
/* Write the changes to the span into the actual attribute, if they aren't already. */
|
||||
void apply_span();
|
||||
|
||||
template<typename T> MutableSpan<T> get_span()
|
||||
{
|
||||
return this->get_span().typed<T>();
|
||||
}
|
||||
|
||||
template<typename T> MutableSpan<T> get_span_for_write_only()
|
||||
{
|
||||
return this->get_span_for_write_only().typed<T>();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void get_internal(const int64_t index, void *r_value) const = 0;
|
||||
virtual void set_internal(const int64_t index, const void *value) = 0;
|
||||
|
||||
virtual void initialize_span(const bool write_only);
|
||||
virtual void apply_span_if_necessary();
|
||||
void save();
|
||||
};
|
||||
|
||||
using ReadAttributePtr = std::unique_ptr<ReadAttribute>;
|
||||
using WriteAttributePtr = std::unique_ptr<WriteAttribute>;
|
||||
|
||||
/* This provides type safe access to an attribute.
|
||||
* The underlying ReadAttribute is owned optionally. */
|
||||
template<typename T> class TypedReadAttribute {
|
||||
/**
|
||||
* Same as OutputAttribute, but should be used when the data type is known at compile time.
|
||||
*/
|
||||
template<typename T> class OutputAttribute_Typed {
|
||||
private:
|
||||
std::unique_ptr<const ReadAttribute> owned_attribute_;
|
||||
const ReadAttribute *attribute_;
|
||||
OutputAttribute attribute_;
|
||||
std::optional<fn::GVMutableArray_Typed<T>> optional_varray_;
|
||||
VMutableArray<T> *varray_ = nullptr;
|
||||
|
||||
public:
|
||||
TypedReadAttribute(ReadAttributePtr attribute) : TypedReadAttribute(*attribute)
|
||||
OutputAttribute_Typed(OutputAttribute attribute) : attribute_(std::move(attribute))
|
||||
{
|
||||
owned_attribute_ = std::move(attribute);
|
||||
BLI_assert(owned_attribute_);
|
||||
if (attribute_) {
|
||||
optional_varray_.emplace(attribute_.varray());
|
||||
varray_ = &**optional_varray_;
|
||||
}
|
||||
}
|
||||
|
||||
TypedReadAttribute(const ReadAttribute &attribute) : attribute_(&attribute)
|
||||
operator bool() const
|
||||
{
|
||||
BLI_assert(attribute_->cpp_type().is<T>());
|
||||
return varray_ != nullptr;
|
||||
}
|
||||
|
||||
int64_t size() const
|
||||
VMutableArray<T> &operator*()
|
||||
{
|
||||
return attribute_->size();
|
||||
return *varray_;
|
||||
}
|
||||
|
||||
T operator[](const int64_t index) const
|
||||
VMutableArray<T> *operator->()
|
||||
{
|
||||
BLI_assert(index < attribute_->size());
|
||||
T value;
|
||||
value.~T();
|
||||
attribute_->get(index, &value);
|
||||
return value;
|
||||
return varray_;
|
||||
}
|
||||
|
||||
/* Get a span to that contains all attribute values for faster and more convenient access. */
|
||||
Span<T> get_span() const
|
||||
VMutableArray<T> &varray()
|
||||
{
|
||||
return attribute_->get_span().template typed<T>();
|
||||
return *varray_;
|
||||
}
|
||||
|
||||
AttributeDomain domain() const
|
||||
{
|
||||
return attribute_.domain();
|
||||
}
|
||||
|
||||
const CPPType &cpp_type() const
|
||||
{
|
||||
return CPPType::get<T>();
|
||||
}
|
||||
|
||||
CustomDataType custom_data_type() const
|
||||
{
|
||||
return cpp_type_to_custom_data_type(this->cpp_type());
|
||||
}
|
||||
|
||||
MutableSpan<T> as_span()
|
||||
{
|
||||
return attribute_.as_span<T>();
|
||||
}
|
||||
|
||||
void save()
|
||||
{
|
||||
attribute_.save();
|
||||
}
|
||||
};
|
||||
|
||||
/* This provides type safe access to an attribute.
|
||||
* The underlying WriteAttribute is owned optionally. */
|
||||
template<typename T> class TypedWriteAttribute {
|
||||
private:
|
||||
std::unique_ptr<WriteAttribute> owned_attribute_;
|
||||
WriteAttribute *attribute_;
|
||||
|
||||
public:
|
||||
TypedWriteAttribute(WriteAttributePtr attribute) : TypedWriteAttribute(*attribute)
|
||||
{
|
||||
owned_attribute_ = std::move(attribute);
|
||||
BLI_assert(owned_attribute_);
|
||||
}
|
||||
|
||||
TypedWriteAttribute(WriteAttribute &attribute) : attribute_(&attribute)
|
||||
{
|
||||
BLI_assert(attribute_->cpp_type().is<T>());
|
||||
}
|
||||
|
||||
int64_t size() const
|
||||
{
|
||||
return attribute_->size();
|
||||
}
|
||||
|
||||
T operator[](const int64_t index) const
|
||||
{
|
||||
BLI_assert(index < attribute_->size());
|
||||
T value;
|
||||
value.~T();
|
||||
attribute_->get(index, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void set(const int64_t index, const T &value)
|
||||
{
|
||||
attribute_->set(index, &value);
|
||||
}
|
||||
|
||||
/* Get a span that new values can be written into. Once all values have been updated #apply_span
|
||||
* has to be called. */
|
||||
MutableSpan<T> get_span()
|
||||
{
|
||||
return attribute_->get_span().typed<T>();
|
||||
}
|
||||
/* The span returned by this method might not contain the current attribute values. */
|
||||
MutableSpan<T> get_span_for_write_only()
|
||||
{
|
||||
return attribute_->get_span_for_write_only().typed<T>();
|
||||
}
|
||||
|
||||
/* Write back all changes to the actual attribute, if necessary. */
|
||||
void apply_span()
|
||||
{
|
||||
attribute_->apply_span();
|
||||
}
|
||||
};
|
||||
|
||||
using BooleanReadAttribute = TypedReadAttribute<bool>;
|
||||
using FloatReadAttribute = TypedReadAttribute<float>;
|
||||
using Float2ReadAttribute = TypedReadAttribute<float2>;
|
||||
using Float3ReadAttribute = TypedReadAttribute<float3>;
|
||||
using Int32ReadAttribute = TypedReadAttribute<int>;
|
||||
using Color4fReadAttribute = TypedReadAttribute<Color4f>;
|
||||
using BooleanWriteAttribute = TypedWriteAttribute<bool>;
|
||||
using FloatWriteAttribute = TypedWriteAttribute<float>;
|
||||
using Float2WriteAttribute = TypedWriteAttribute<float2>;
|
||||
using Float3WriteAttribute = TypedWriteAttribute<float3>;
|
||||
using Int32WriteAttribute = TypedWriteAttribute<int>;
|
||||
using Color4fWriteAttribute = TypedWriteAttribute<Color4f>;
|
||||
|
||||
} // namespace blender::bke
|
||||
|
@@ -21,8 +21,12 @@
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
|
||||
#include "FN_cpp_type.hh"
|
||||
|
||||
namespace blender::attribute_math {
|
||||
|
||||
using fn::CPPType;
|
||||
|
||||
/**
|
||||
* Utility function that simplifies calling a templated function based on a custom data type.
|
||||
*/
|
||||
@@ -54,6 +58,31 @@ void convert_to_static_type(const CustomDataType data_type, const Func &func)
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Func> void convert_to_static_type(const fn::CPPType &cpp_type, const Func &func)
|
||||
{
|
||||
if (cpp_type.is<float>()) {
|
||||
func(float());
|
||||
}
|
||||
else if (cpp_type.is<float2>()) {
|
||||
func(float2());
|
||||
}
|
||||
else if (cpp_type.is<float3>()) {
|
||||
func(float3());
|
||||
}
|
||||
else if (cpp_type.is<int>()) {
|
||||
func(int());
|
||||
}
|
||||
else if (cpp_type.is<bool>()) {
|
||||
func(bool());
|
||||
}
|
||||
else if (cpp_type.is<Color4f>()) {
|
||||
func(Color4f());
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Mix three values of the same type.
|
||||
*
|
||||
|
@@ -31,7 +31,7 @@ extern "C" {
|
||||
*/
|
||||
|
||||
/* Blender major and minor version. */
|
||||
#define BLENDER_VERSION 293
|
||||
#define BLENDER_VERSION 300
|
||||
/* Blender patch version for bugfix releases. */
|
||||
#define BLENDER_VERSION_PATCH 0
|
||||
/** Blender release cycle stage: alpha/beta/rc/release. */
|
||||
@@ -39,7 +39,7 @@ extern "C" {
|
||||
|
||||
/* Blender file format version. */
|
||||
#define BLENDER_FILE_VERSION BLENDER_VERSION
|
||||
#define BLENDER_FILE_SUBVERSION 15
|
||||
#define BLENDER_FILE_SUBVERSION 0
|
||||
|
||||
/* Minimum Blender version that supports reading file written with the current
|
||||
* version. Older Blender versions will test this and show a warning if the file
|
||||
|
@@ -88,7 +88,7 @@ struct Collection *BKE_collection_object_find(struct Main *bmain,
|
||||
struct Scene *scene,
|
||||
struct Collection *collection,
|
||||
struct Object *ob);
|
||||
bool BKE_collection_is_empty(struct Collection *collection);
|
||||
bool BKE_collection_is_empty(const struct Collection *collection);
|
||||
|
||||
bool BKE_collection_object_add(struct Main *bmain,
|
||||
struct Collection *collection,
|
||||
@@ -227,6 +227,8 @@ void BKE_scene_objects_iterator_begin(struct BLI_Iterator *iter, void *data_in);
|
||||
void BKE_scene_objects_iterator_next(struct BLI_Iterator *iter);
|
||||
void BKE_scene_objects_iterator_end(struct BLI_Iterator *iter);
|
||||
|
||||
struct GSet *BKE_scene_objects_as_gset(struct Scene *scene, struct GSet *objects_gset);
|
||||
|
||||
#define FOREACH_SCENE_COLLECTION_BEGIN(scene, _instance) \
|
||||
ITER_BEGIN (BKE_scene_collections_iterator_begin, \
|
||||
BKE_scene_collections_iterator_next, \
|
||||
|
@@ -206,8 +206,25 @@ void CTX_wm_area_set(bContext *C, struct ScrArea *area);
|
||||
void CTX_wm_region_set(bContext *C, struct ARegion *region);
|
||||
void CTX_wm_menu_set(bContext *C, struct ARegion *menu);
|
||||
void CTX_wm_gizmo_group_set(bContext *C, struct wmGizmoGroup *gzgroup);
|
||||
const char *CTX_wm_operator_poll_msg_get(struct bContext *C);
|
||||
|
||||
/**
|
||||
* Values to create the message that describes the reason poll failed.
|
||||
*
|
||||
* \note This must be called in the same context as the poll function that created it.
|
||||
*/
|
||||
struct bContextPollMsgDyn_Params {
|
||||
/** The result is allocated . */
|
||||
char *(*get_fn)(bContext *C, void *user_data);
|
||||
/** Optionally free the user-data. */
|
||||
void (*free_fn)(bContext *C, void *user_data);
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
const char *CTX_wm_operator_poll_msg_get(struct bContext *C, bool *r_free);
|
||||
void CTX_wm_operator_poll_msg_set(struct bContext *C, const char *msg);
|
||||
void CTX_wm_operator_poll_msg_set_dynamic(bContext *C,
|
||||
const struct bContextPollMsgDyn_Params *params);
|
||||
void CTX_wm_operator_poll_msg_clear(struct bContext *C);
|
||||
|
||||
/* Data Context
|
||||
*
|
||||
|
@@ -40,7 +40,6 @@ struct MDeformVert;
|
||||
struct Main;
|
||||
struct Nurb;
|
||||
struct Object;
|
||||
struct Path;
|
||||
struct TextBox;
|
||||
struct rctf;
|
||||
|
||||
@@ -50,7 +49,13 @@ typedef struct CurveCache {
|
||||
ListBase disp;
|
||||
ListBase bev;
|
||||
ListBase deformed_nurbs;
|
||||
struct Path *path;
|
||||
/* This array contains the accumulative length of the curve segments.
|
||||
* So you can see this as a "total distance traveled" along the curve.
|
||||
* The first entry is the length between point 0 and 1 while the last is the
|
||||
* total length of the curve.
|
||||
*
|
||||
* Used by #BKE_where_on_path. */
|
||||
const float *anim_path_accum_length;
|
||||
} CurveCache;
|
||||
|
||||
/* Definitions needed for shape keys */
|
||||
@@ -69,16 +74,16 @@ typedef struct CVKeyIndex {
|
||||
#define SEGMENTSU(nu) (((nu)->flagu & CU_NURB_CYCLIC) ? (nu)->pntsu : (nu)->pntsu - 1)
|
||||
#define SEGMENTSV(nu) (((nu)->flagv & CU_NURB_CYCLIC) ? (nu)->pntsv : (nu)->pntsv - 1)
|
||||
|
||||
#define CU_DO_TILT(cu, nu) ((((nu)->flag & CU_2D) && ((cu)->flag & CU_3D) == 0) ? 0 : 1)
|
||||
#define CU_DO_RADIUS(cu, nu) \
|
||||
((CU_DO_TILT(cu, nu) || ((cu)->flag & CU_PATH_RADIUS) || (cu)->bevobj || (cu)->ext1 != 0.0f || \
|
||||
((((cu)->flag & (CU_PATH_RADIUS | CU_3D)) || (cu)->bevobj || (cu)->ext1 != 0.0f || \
|
||||
(cu)->ext2 != 0.0f) ? \
|
||||
1 : \
|
||||
0)
|
||||
|
||||
#define CU_IS_2D(cu) (((cu)->flag & CU_3D) == 0)
|
||||
|
||||
/* not 3d and not unfilled */
|
||||
#define CU_DO_2DFILL(cu) \
|
||||
((((cu)->flag & CU_3D) == 0) && (((cu)->flag & (CU_FRONT | CU_BACK)) != 0))
|
||||
#define CU_DO_2DFILL(cu) (CU_IS_2D(cu) && (((cu)->flag & (CU_FRONT | CU_BACK)) != 0))
|
||||
|
||||
/* ** Curve ** */
|
||||
void BKE_curve_editfont_free(struct Curve *cu);
|
||||
@@ -86,7 +91,7 @@ void BKE_curve_init(struct Curve *cu, const short curve_type);
|
||||
struct Curve *BKE_curve_add(struct Main *bmain, const char *name, int type);
|
||||
short BKE_curve_type_get(const struct Curve *cu);
|
||||
void BKE_curve_type_test(struct Object *ob);
|
||||
void BKE_curve_curve_dimension_update(struct Curve *cu);
|
||||
void BKE_curve_dimension_update(struct Curve *cu);
|
||||
|
||||
struct BoundBox *BKE_curve_boundbox_get(struct Object *ob);
|
||||
|
||||
@@ -186,7 +191,7 @@ void BKE_nurb_free(struct Nurb *nu);
|
||||
struct Nurb *BKE_nurb_duplicate(const struct Nurb *nu);
|
||||
struct Nurb *BKE_nurb_copy(struct Nurb *src, int pntsu, int pntsv);
|
||||
|
||||
void BKE_nurb_test_2d(struct Nurb *nu);
|
||||
void BKE_nurb_project_2d(struct Nurb *nu);
|
||||
void BKE_nurb_minmax(const struct Nurb *nu, bool use_radius, float min[3], float max[3]);
|
||||
float BKE_nurb_calc_length(const struct Nurb *nu, int resolution);
|
||||
|
||||
|
@@ -55,11 +55,6 @@ typedef struct InstancedData {
|
||||
} data;
|
||||
} InstancedData;
|
||||
|
||||
int BKE_geometry_set_instances(const struct GeometrySet *geometry_set,
|
||||
float (**r_transforms)[4][4],
|
||||
const int **r_almost_unique_ids,
|
||||
struct InstancedData **r_instanced_data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -55,60 +55,6 @@ class ComponentAttributeProviders;
|
||||
|
||||
class GeometryComponent;
|
||||
|
||||
/**
|
||||
* An #OutputAttributePtr wraps a #WriteAttributePtr that might not be stored in its final
|
||||
* destination yet. Therefore, once the attribute has been filled with data, the #save method has
|
||||
* to be called, to store the attribute where it belongs (possibly by replacing an existing
|
||||
* attribute with the same name).
|
||||
*
|
||||
* This is useful for example in the Attribute Color Ramp node, when the same attribute name is
|
||||
* used as input and output. Typically the input is a float attribute, and the output is a color.
|
||||
* Those two attributes cannot exist at the same time, due to a name collision. To handle this
|
||||
* situation well, first the output colors have to be computed before the input floats are deleted.
|
||||
* Therefore, the outputs have to be written to a temporary buffer that replaces the existing
|
||||
* attribute once all computations are done.
|
||||
*/
|
||||
class OutputAttributePtr {
|
||||
private:
|
||||
blender::bke::WriteAttributePtr attribute_;
|
||||
|
||||
public:
|
||||
OutputAttributePtr() = default;
|
||||
OutputAttributePtr(blender::bke::WriteAttributePtr attribute);
|
||||
OutputAttributePtr(GeometryComponent &component,
|
||||
AttributeDomain domain,
|
||||
std::string name,
|
||||
CustomDataType data_type);
|
||||
|
||||
~OutputAttributePtr();
|
||||
|
||||
/* Returns false, when this wrapper is empty. */
|
||||
operator bool() const
|
||||
{
|
||||
return static_cast<bool>(attribute_);
|
||||
}
|
||||
|
||||
/* Get a reference to the underlying #WriteAttribute. */
|
||||
blender::bke::WriteAttribute &get()
|
||||
{
|
||||
BLI_assert(attribute_);
|
||||
return *attribute_;
|
||||
}
|
||||
|
||||
blender::bke::WriteAttribute &operator*()
|
||||
{
|
||||
return *attribute_;
|
||||
}
|
||||
|
||||
blender::bke::WriteAttribute *operator->()
|
||||
{
|
||||
return attribute_.get();
|
||||
}
|
||||
|
||||
void save();
|
||||
void apply_span_and_save();
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains information about an attribute in a geometry component.
|
||||
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
|
||||
@@ -135,12 +81,18 @@ class GeometryComponent {
|
||||
|
||||
public:
|
||||
GeometryComponent(GeometryComponentType type);
|
||||
virtual ~GeometryComponent();
|
||||
virtual ~GeometryComponent() = default;
|
||||
static GeometryComponent *create(GeometryComponentType component_type);
|
||||
|
||||
/* The returned component should be of the same type as the type this is called on. */
|
||||
virtual GeometryComponent *copy() const = 0;
|
||||
|
||||
/* Direct data is everything except for instances of objects/collections.
|
||||
* If this returns true, the geometry set can be cached and is still valid after e.g. modifier
|
||||
* evaluation ends. Instances can only be valid as long as the data they instance is valid. */
|
||||
virtual bool owns_direct_data() const = 0;
|
||||
virtual void ensure_owns_direct_data() = 0;
|
||||
|
||||
void user_add() const;
|
||||
void user_remove() const;
|
||||
bool is_mutable() const;
|
||||
@@ -155,21 +107,25 @@ class GeometryComponent {
|
||||
/* Can only be used with supported domain types. */
|
||||
virtual int attribute_domain_size(const AttributeDomain domain) const;
|
||||
|
||||
bool attribute_is_builtin(const blender::StringRef attribute_name) const;
|
||||
|
||||
/* Get read-only access to the highest priority attribute with the given name.
|
||||
* Returns null if the attribute does not exist. */
|
||||
blender::bke::ReadAttributePtr attribute_try_get_for_read(
|
||||
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name) const;
|
||||
|
||||
/* Get read and write access to the highest priority attribute with the given name.
|
||||
* Returns null if the attribute does not exist. */
|
||||
blender::bke::WriteAttributePtr attribute_try_get_for_write(
|
||||
blender::bke::WriteAttributeLookup attribute_try_get_for_write(
|
||||
const blender::StringRef attribute_name);
|
||||
|
||||
/* Get a read-only attribute for the domain based on the given attribute. This can be used to
|
||||
* interpolate from one domain to another.
|
||||
* Returns null if the interpolation is not implemented. */
|
||||
virtual blender::bke::ReadAttributePtr attribute_try_adapt_domain(
|
||||
blender::bke::ReadAttributePtr attribute, const AttributeDomain new_domain) const;
|
||||
virtual std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain(
|
||||
std::unique_ptr<blender::fn::GVArray> varray,
|
||||
const AttributeDomain from_domain,
|
||||
const AttributeDomain to_domain) const;
|
||||
|
||||
/* Returns true when the attribute has been deleted. */
|
||||
bool attribute_try_delete(const blender::StringRef attribute_name);
|
||||
@@ -179,80 +135,97 @@ class GeometryComponent {
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type);
|
||||
|
||||
/* Try to create the builtin attribute with the given name. No data type or domain has to be
|
||||
* provided, because those are fixed for builtin attributes. */
|
||||
bool attribute_try_create_builtin(const blender::StringRef attribute_name);
|
||||
|
||||
blender::Set<std::string> attribute_names() const;
|
||||
void attribute_foreach(const AttributeForeachCallback callback) const;
|
||||
bool attribute_foreach(const AttributeForeachCallback callback) const;
|
||||
|
||||
virtual bool is_empty() const;
|
||||
|
||||
/* Get a read-only attribute for the given domain and data type.
|
||||
* Returns null when it does not exist. */
|
||||
blender::bke::ReadAttributePtr attribute_try_get_for_read(
|
||||
/* Get a virtual array to read the data of an attribute on the given domain and data type.
|
||||
* Returns null when the attribute does not exist or cannot be converted to the requested domain
|
||||
* and data type. */
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type) const;
|
||||
|
||||
/* Get a read-only attribute interpolated to the input domain, leaving the data type unchanged.
|
||||
* Returns null when the attribute does not exist. */
|
||||
blender::bke::ReadAttributePtr attribute_try_get_for_read(
|
||||
/* Get a virtual array to read the data of an attribute on the given domain. The data type is
|
||||
* left unchanged. Returns null when the attribute does not exist or cannot be adapted to the
|
||||
* requested domain. */
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const AttributeDomain domain) const;
|
||||
|
||||
/* Get a read-only attribute for the given domain and data type.
|
||||
* Returns a constant attribute based on the default value if the attribute does not exist.
|
||||
* Never returns null. */
|
||||
blender::bke::ReadAttributePtr attribute_get_for_read(const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value) const;
|
||||
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const CustomDataType data_type) const;
|
||||
|
||||
/* Get a typed read-only attribute for the given domain and type. */
|
||||
template<typename T>
|
||||
blender::bke::TypedReadAttribute<T> attribute_get_for_read(
|
||||
/* Get a virtual array to read the data of an attribute. If that is not possible, the returned
|
||||
* virtual array will contain a default value. This never returns null. */
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_get_for_read(
|
||||
const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const T &default_value) const
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
return this->attribute_get_for_read(attribute_name, domain, type, &default_value);
|
||||
}
|
||||
const CustomDataType data_type,
|
||||
const void *default_value = nullptr) const;
|
||||
|
||||
/* Get a read-only dummy attribute that always returns the same value. */
|
||||
blender::bke::ReadAttributePtr attribute_get_constant_for_read(const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *value) const;
|
||||
|
||||
/* Create a read-only dummy attribute that always returns the same value.
|
||||
* The given value is converted to the correct type if necessary. */
|
||||
blender::bke::ReadAttributePtr attribute_get_constant_for_read_converted(
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType in_data_type,
|
||||
const CustomDataType out_data_type,
|
||||
const void *value) const;
|
||||
|
||||
/* Get a read-only dummy attribute that always returns the same value. */
|
||||
/* Should be used instead of the method above when the requested data type is known at compile
|
||||
* time for better type safety. */
|
||||
template<typename T>
|
||||
blender::bke::TypedReadAttribute<T> attribute_get_constant_for_read(const AttributeDomain domain,
|
||||
const T &value) const
|
||||
blender::fn::GVArray_Typed<T> attribute_get_for_read(const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const T &default_value) const
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
return this->attribute_get_constant_for_read(domain, type, &value);
|
||||
std::unique_ptr varray = this->attribute_get_for_read(
|
||||
attribute_name, domain, type, &default_value);
|
||||
return blender::fn::GVArray_Typed<T>(std::move(varray));
|
||||
}
|
||||
|
||||
/**
|
||||
* If an attribute with the given params exist, it is returned.
|
||||
* If no attribute with the given name exists, create it and
|
||||
* fill it with the default value if it is provided.
|
||||
* If an attribute with the given name but different domain or type exists, a temporary attribute
|
||||
* is created that has to be saved after the output has been computed. This avoids deleting
|
||||
* another attribute, before a computation is finished.
|
||||
* Returns an "output attribute", which is essentially a mutable virtual array with some commonly
|
||||
* used convince features. The returned output attribute might be empty if requested attribute
|
||||
* cannot exist on the geometry.
|
||||
*
|
||||
* This might return no attribute when the attribute cannot exist on the component.
|
||||
* The included convenience features are:
|
||||
* - Implicit type conversion when writing to builtin attributes.
|
||||
* - If the attribute name exists already, but has a different type/domain, a temporary attribute
|
||||
* is created that will overwrite the existing attribute in the end.
|
||||
*/
|
||||
OutputAttributePtr attribute_try_get_for_output(const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value = nullptr);
|
||||
blender::bke::OutputAttribute attribute_try_get_for_output(
|
||||
const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value = nullptr);
|
||||
|
||||
/* Same as attribute_try_get_for_output, but should be used when the original values in the
|
||||
* attributes are not read, i.e. the attribute is used only for output. Since values are not read
|
||||
* from this attribute, no default value is necessary. */
|
||||
blender::bke::OutputAttribute attribute_try_get_for_output_only(
|
||||
const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type);
|
||||
|
||||
/* Statically typed method corresponding to the equally named generic one. */
|
||||
template<typename T>
|
||||
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output(
|
||||
const blender::StringRef attribute_name, const AttributeDomain domain, const T default_value)
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
return this->attribute_try_get_for_output(attribute_name, domain, data_type, &default_value);
|
||||
}
|
||||
|
||||
/* Statically typed method corresponding to the equally named generic one. */
|
||||
template<typename T>
|
||||
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only(
|
||||
const blender::StringRef attribute_name, const AttributeDomain domain)
|
||||
{
|
||||
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
|
||||
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
return this->attribute_try_get_for_output_only(attribute_name, domain, data_type);
|
||||
}
|
||||
|
||||
private:
|
||||
virtual const blender::bke::ComponentAttributeProviders *get_attribute_providers() const;
|
||||
@@ -315,6 +288,8 @@ struct GeometrySet {
|
||||
|
||||
void clear();
|
||||
|
||||
void ensure_owns_direct_data();
|
||||
|
||||
/* Utility methods for creation. */
|
||||
static GeometrySet create_with_mesh(
|
||||
Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
|
||||
@@ -369,11 +344,16 @@ class MeshComponent : public GeometryComponent {
|
||||
Mesh *get_for_write();
|
||||
|
||||
int attribute_domain_size(const AttributeDomain domain) const final;
|
||||
blender::bke::ReadAttributePtr attribute_try_adapt_domain(
|
||||
blender::bke::ReadAttributePtr attribute, const AttributeDomain new_domain) const final;
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain(
|
||||
std::unique_ptr<blender::fn::GVArray> varray,
|
||||
const AttributeDomain from_domain,
|
||||
const AttributeDomain to_domain) const final;
|
||||
|
||||
bool is_empty() const final;
|
||||
|
||||
bool owns_direct_data() const override;
|
||||
void ensure_owns_direct_data() override;
|
||||
|
||||
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_MESH;
|
||||
|
||||
private:
|
||||
@@ -404,6 +384,9 @@ class PointCloudComponent : public GeometryComponent {
|
||||
|
||||
bool is_empty() const final;
|
||||
|
||||
bool owns_direct_data() const override;
|
||||
void ensure_owns_direct_data() override;
|
||||
|
||||
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_POINT_CLOUD;
|
||||
|
||||
private:
|
||||
@@ -444,6 +427,9 @@ class InstancesComponent : public GeometryComponent {
|
||||
|
||||
bool is_empty() const final;
|
||||
|
||||
bool owns_direct_data() const override;
|
||||
void ensure_owns_direct_data() override;
|
||||
|
||||
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_INSTANCES;
|
||||
};
|
||||
|
||||
@@ -466,5 +452,8 @@ class VolumeComponent : public GeometryComponent {
|
||||
const Volume *get_for_read() const;
|
||||
Volume *get_for_write();
|
||||
|
||||
bool owns_direct_data() const override;
|
||||
void ensure_owns_direct_data() override;
|
||||
|
||||
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME;
|
||||
};
|
||||
|
@@ -39,6 +39,10 @@ struct GeometryInstanceGroup {
|
||||
Vector<float4x4> transforms;
|
||||
};
|
||||
|
||||
void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set,
|
||||
const AttributeForeachCallback callback,
|
||||
const int limit);
|
||||
|
||||
void geometry_set_gather_instances(const GeometrySet &geometry_set,
|
||||
Vector<GeometryInstanceGroup> &r_instance_groups);
|
||||
|
||||
@@ -55,9 +59,9 @@ struct AttributeKind {
|
||||
* will contain the highest complexity data type and the highest priority domain among every
|
||||
* attribute with the given name on all of the input components.
|
||||
*/
|
||||
void gather_attribute_info(Map<std::string, AttributeKind> &attributes,
|
||||
Span<GeometryComponentType> component_types,
|
||||
Span<bke::GeometryInstanceGroup> set_groups,
|
||||
const Set<std::string> &ignored_attributes);
|
||||
void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups,
|
||||
Span<GeometryComponentType> component_types,
|
||||
const Set<std::string> &ignored_attributes,
|
||||
Map<std::string, AttributeKind> &r_attributes);
|
||||
|
||||
} // namespace blender::bke
|
||||
|
@@ -116,6 +116,8 @@ enum {
|
||||
LIB_ID_COPY_NO_ANIMDATA = 1 << 19,
|
||||
/** Mesh: Reference CD data layers instead of doing real copy - USE WITH CAUTION! */
|
||||
LIB_ID_COPY_CD_REFERENCE = 1 << 20,
|
||||
/** Do not copy id->override_library, used by ID datablock override routines. */
|
||||
LIB_ID_COPY_NO_LIB_OVERRIDE = 1 << 21,
|
||||
|
||||
/* *** XXX Hackish/not-so-nice specific behaviors needed for some corner cases. *** */
|
||||
/* *** Ideally we should not have those, but we need them for now... *** */
|
||||
@@ -136,7 +138,8 @@ enum {
|
||||
LIB_ID_CREATE_LOCALIZE = LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT |
|
||||
LIB_ID_CREATE_NO_DEG_TAG,
|
||||
/** Generate a local copy, outside of bmain, to work on (used by COW e.g.). */
|
||||
LIB_ID_COPY_LOCALIZE = LIB_ID_CREATE_LOCALIZE | LIB_ID_COPY_NO_PREVIEW | LIB_ID_COPY_CACHES,
|
||||
LIB_ID_COPY_LOCALIZE = LIB_ID_CREATE_LOCALIZE | LIB_ID_COPY_NO_PREVIEW | LIB_ID_COPY_CACHES |
|
||||
LIB_ID_COPY_NO_LIB_OVERRIDE,
|
||||
};
|
||||
|
||||
void BKE_libblock_copy_ex(struct Main *bmain,
|
||||
|
@@ -84,7 +84,8 @@ bool BKE_lib_override_library_resync(struct Main *bmain,
|
||||
struct ViewLayer *view_layer,
|
||||
struct ID *id_root,
|
||||
struct Collection *override_resync_residual_storage,
|
||||
const bool do_hierarchy_enforce);
|
||||
const bool do_hierarchy_enforce,
|
||||
const bool do_post_process);
|
||||
void BKE_lib_override_library_main_resync(struct Main *bmain,
|
||||
struct Scene *scene,
|
||||
struct ViewLayer *view_layer);
|
||||
|
43
source/blender/blenkernel/BKE_mesh_boolean_convert.hh
Normal file
43
source/blender/blenkernel/BKE_mesh_boolean_convert.hh
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* The Original Code is Copyright (C) 2019 Blender Foundation.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_float4x4.hh"
|
||||
#include "BLI_mesh_boolean.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace blender::meshintersect {
|
||||
|
||||
Mesh *direct_mesh_boolean(blender::Span<const Mesh *> meshes,
|
||||
blender::Span<const float4x4 *> obmats,
|
||||
const float4x4 &target_transform,
|
||||
blender::Span<blender::Array<short>> material_remaps,
|
||||
const bool use_self,
|
||||
const bool hole_tolerant,
|
||||
const int boolean_mode);
|
||||
|
||||
} // namespace blender::meshintersect
|
55
source/blender/blenkernel/BKE_mesh_sample.hh
Normal file
55
source/blender/blenkernel/BKE_mesh_sample.hh
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "FN_generic_virtual_array.hh"
|
||||
|
||||
#include "BLI_float3.hh"
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace blender::bke::mesh_surface_sample {
|
||||
|
||||
using fn::CPPType;
|
||||
using fn::GMutableSpan;
|
||||
using fn::GSpan;
|
||||
using fn::GVArray;
|
||||
|
||||
void sample_point_attribute(const Mesh &mesh,
|
||||
Span<int> looptri_indices,
|
||||
Span<float3> bary_coords,
|
||||
const GVArray &data_in,
|
||||
GMutableSpan data_out);
|
||||
|
||||
void sample_corner_attribute(const Mesh &mesh,
|
||||
Span<int> looptri_indices,
|
||||
Span<float3> bary_coords,
|
||||
const GVArray &data_in,
|
||||
GMutableSpan data_out);
|
||||
|
||||
void sample_face_attribute(const Mesh &mesh,
|
||||
Span<int> looptri_indices,
|
||||
const GVArray &data_in,
|
||||
GMutableSpan data_out);
|
||||
|
||||
} // namespace blender::bke::mesh_surface_sample
|
@@ -289,10 +289,22 @@ typedef struct bNodeType {
|
||||
void (*freefunc_api)(struct PointerRNA *ptr);
|
||||
void (*copyfunc_api)(struct PointerRNA *ptr, const struct bNode *src_node);
|
||||
|
||||
/* can this node type be added to a node tree */
|
||||
bool (*poll)(struct bNodeType *ntype, struct bNodeTree *nodetree);
|
||||
/* can this node be added to a node tree */
|
||||
bool (*poll_instance)(struct bNode *node, struct bNodeTree *nodetree);
|
||||
/**
|
||||
* Can this node type be added to a node tree?
|
||||
* \param r_disabled_hint: Optional hint to display in the UI when the poll fails.
|
||||
* The callback can set this to a static string without having to
|
||||
* null-check it (or without setting it to null if it's not used).
|
||||
* The caller must pass a valid `const char **` and null-initialize it
|
||||
* when it's not just a dummy, that is, if it actually wants to access
|
||||
* the returned disabled-hint (null-check needed!).
|
||||
*/
|
||||
bool (*poll)(struct bNodeType *ntype, struct bNodeTree *nodetree, const char **r_disabled_hint);
|
||||
/** Can this node be added to a node tree?
|
||||
* \param r_disabled_hint: See `poll()`.
|
||||
*/
|
||||
bool (*poll_instance)(struct bNode *node,
|
||||
struct bNodeTree *nodetree,
|
||||
const char **r_disabled_hint);
|
||||
|
||||
/* optional handling of link insertion */
|
||||
void (*insert_link)(struct bNodeTree *ntree, struct bNode *node, struct bNodeLink *link);
|
||||
@@ -804,7 +816,9 @@ void BKE_node_preview_set_pixel(
|
||||
void nodeLabel(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen);
|
||||
const char *nodeSocketLabel(const struct bNodeSocket *sock);
|
||||
|
||||
int nodeGroupPoll(struct bNodeTree *nodetree, struct bNodeTree *grouptree);
|
||||
bool nodeGroupPoll(struct bNodeTree *nodetree,
|
||||
struct bNodeTree *grouptree,
|
||||
const char **r_disabled_hint);
|
||||
|
||||
/* Init a new node type struct with default values and callbacks */
|
||||
void node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass, short flag);
|
||||
@@ -1067,7 +1081,6 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree,
|
||||
|
||||
struct bNodeTreeExec *ntreeShaderBeginExecTree(struct bNodeTree *ntree);
|
||||
void ntreeShaderEndExecTree(struct bNodeTreeExec *exec);
|
||||
bool ntreeShaderExecTree(struct bNodeTree *ntree, int thread);
|
||||
struct bNode *ntreeShaderOutputNode(struct bNodeTree *ntree, int target);
|
||||
|
||||
void ntreeGPUMaterialNodes(struct bNodeTree *localtree,
|
||||
@@ -1399,6 +1412,9 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
|
||||
#define GEO_NODE_MESH_PRIMITIVE_GRID 1039
|
||||
#define GEO_NODE_ATTRIBUTE_MAP_RANGE 1040
|
||||
#define GEO_NODE_ATTRIBUTE_CLAMP 1041
|
||||
#define GEO_NODE_BOUNDING_BOX 1042
|
||||
#define GEO_NODE_SWITCH 1043
|
||||
#define GEO_NODE_ATTRIBUTE_TRANSFER 1044
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@@ -20,7 +20,6 @@
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_session_uuid.h"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
@@ -80,30 +79,37 @@ struct NodeWarning {
|
||||
};
|
||||
|
||||
struct AvailableAttributeInfo {
|
||||
std::string name;
|
||||
AttributeDomain domain;
|
||||
CustomDataType data_type;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
uint64_t domain_hash = (uint64_t)domain;
|
||||
uint64_t data_type_hash = (uint64_t)data_type;
|
||||
return (domain_hash * 33) ^ (data_type_hash * 89);
|
||||
return blender::get_default_hash(name);
|
||||
}
|
||||
|
||||
friend bool operator==(const AvailableAttributeInfo &a, const AvailableAttributeInfo &b)
|
||||
{
|
||||
return a.domain == b.domain && a.data_type == b.data_type;
|
||||
return a.name == b.name;
|
||||
}
|
||||
};
|
||||
|
||||
struct NodeUIStorage {
|
||||
blender::Vector<NodeWarning> warnings;
|
||||
blender::MultiValueMap<std::string, AvailableAttributeInfo> attribute_hints;
|
||||
blender::Set<AvailableAttributeInfo> attribute_hints;
|
||||
};
|
||||
|
||||
struct NodeTreeUIStorage {
|
||||
blender::Map<NodeTreeEvaluationContext, blender::Map<std::string, NodeUIStorage>> context_map;
|
||||
std::mutex context_map_mutex;
|
||||
|
||||
/**
|
||||
* Attribute search uses this to store the fake info for the string typed into a node, in order
|
||||
* to pass the info to the execute callback that sets node socket values. This is mutable since
|
||||
* we can count on only one attribute search being open at a time, and there is no real data
|
||||
* stored here.
|
||||
*/
|
||||
mutable AvailableAttributeInfo dummy_info_for_search;
|
||||
};
|
||||
|
||||
const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C,
|
||||
|
@@ -34,6 +34,7 @@ struct Base;
|
||||
struct BoundBox;
|
||||
struct Curve;
|
||||
struct Depsgraph;
|
||||
struct GeometrySet;
|
||||
struct GpencilModifierData;
|
||||
struct HookGpencilModifierData;
|
||||
struct HookModifierData;
|
||||
@@ -69,6 +70,10 @@ void BKE_object_free_curve_cache(struct Object *ob);
|
||||
void BKE_object_free_derived_caches(struct Object *ob);
|
||||
void BKE_object_free_caches(struct Object *object);
|
||||
|
||||
void BKE_object_preview_geometry_set_add(struct Object *ob,
|
||||
const uint64_t key,
|
||||
struct GeometrySet *geometry_set);
|
||||
|
||||
void BKE_object_modifier_hook_reset(struct Object *ob, struct HookModifierData *hmd);
|
||||
void BKE_object_modifier_gpencil_hook_reset(struct Object *ob,
|
||||
struct HookGpencilModifierData *hmd);
|
||||
|
@@ -140,6 +140,8 @@ struct TransformOrientationSlot *BKE_scene_orientation_slot_get(struct Scene *sc
|
||||
void BKE_scene_orientation_slot_set_index(struct TransformOrientationSlot *orient_slot,
|
||||
int orientation);
|
||||
int BKE_scene_orientation_slot_get_index(const struct TransformOrientationSlot *orient_slot);
|
||||
int BKE_scene_orientation_get_index(struct Scene *scene, int slot_index);
|
||||
int BKE_scene_orientation_get_index_from_flag(struct Scene *scene, int flag);
|
||||
|
||||
/* ** Scene evaluation ** */
|
||||
|
||||
@@ -150,6 +152,7 @@ void BKE_scene_graph_update_tagged(struct Depsgraph *depsgraph, struct Main *bma
|
||||
void BKE_scene_graph_evaluated_ensure(struct Depsgraph *depsgraph, struct Main *bmain);
|
||||
|
||||
void BKE_scene_graph_update_for_newframe(struct Depsgraph *depsgraph);
|
||||
void BKE_scene_graph_update_for_newframe_ex(struct Depsgraph *depsgraph, const bool clear_recalc);
|
||||
|
||||
void BKE_scene_view_layer_graph_evaluated_ensure(struct Main *bmain,
|
||||
struct Scene *scene,
|
||||
|
@@ -78,16 +78,17 @@ extern void (*BKE_volume_batch_cache_free_cb)(struct Volume *volume);
|
||||
|
||||
typedef struct VolumeGrid VolumeGrid;
|
||||
|
||||
bool BKE_volume_load(struct Volume *volume, struct Main *bmain);
|
||||
bool BKE_volume_load(const struct Volume *volume, const struct Main *bmain);
|
||||
void BKE_volume_unload(struct Volume *volume);
|
||||
bool BKE_volume_is_loaded(const struct Volume *volume);
|
||||
|
||||
int BKE_volume_num_grids(const struct Volume *volume);
|
||||
const char *BKE_volume_grids_error_msg(const struct Volume *volume);
|
||||
const char *BKE_volume_grids_frame_filepath(const struct Volume *volume);
|
||||
VolumeGrid *BKE_volume_grid_get(const struct Volume *volume, int grid_index);
|
||||
VolumeGrid *BKE_volume_grid_active_get(const struct Volume *volume);
|
||||
VolumeGrid *BKE_volume_grid_find(const struct Volume *volume, const char *name);
|
||||
const VolumeGrid *BKE_volume_grid_get_for_read(const struct Volume *volume, int grid_index);
|
||||
VolumeGrid *BKE_volume_grid_get_for_write(struct Volume *volume, int grid_index);
|
||||
const VolumeGrid *BKE_volume_grid_active_get_for_read(const struct Volume *volume);
|
||||
const VolumeGrid *BKE_volume_grid_find_for_read(const struct Volume *volume, const char *name);
|
||||
|
||||
/* Grid
|
||||
*
|
||||
@@ -109,8 +110,8 @@ typedef enum VolumeGridType {
|
||||
VOLUME_GRID_POINTS,
|
||||
} VolumeGridType;
|
||||
|
||||
bool BKE_volume_grid_load(const struct Volume *volume, struct VolumeGrid *grid);
|
||||
void BKE_volume_grid_unload(const struct Volume *volume, struct VolumeGrid *grid);
|
||||
bool BKE_volume_grid_load(const struct Volume *volume, const struct VolumeGrid *grid);
|
||||
void BKE_volume_grid_unload(const struct Volume *volume, const struct VolumeGrid *grid);
|
||||
bool BKE_volume_grid_is_loaded(const struct VolumeGrid *grid);
|
||||
|
||||
/* Metadata */
|
||||
@@ -119,9 +120,6 @@ VolumeGridType BKE_volume_grid_type(const struct VolumeGrid *grid);
|
||||
int BKE_volume_grid_channels(const struct VolumeGrid *grid);
|
||||
void BKE_volume_grid_transform_matrix(const struct VolumeGrid *grid, float mat[4][4]);
|
||||
|
||||
/* Bounds */
|
||||
bool BKE_volume_grid_bounds(const struct VolumeGrid *grid, float min[3], float max[3]);
|
||||
|
||||
/* Volume Editing
|
||||
*
|
||||
* These are intended for modifiers to use on evaluated datablocks.
|
||||
@@ -145,8 +143,8 @@ int BKE_volume_simplify_level(const struct Depsgraph *depsgraph);
|
||||
float BKE_volume_simplify_factor(const struct Depsgraph *depsgraph);
|
||||
|
||||
/* File Save */
|
||||
bool BKE_volume_save(struct Volume *volume,
|
||||
struct Main *bmain,
|
||||
bool BKE_volume_save(const struct Volume *volume,
|
||||
const struct Main *bmain,
|
||||
struct ReportList *reports,
|
||||
const char *filepath);
|
||||
|
||||
@@ -159,13 +157,26 @@ bool BKE_volume_save(struct Volume *volume,
|
||||
* Access to OpenVDB grid for C++. These will automatically load grids from
|
||||
* file or copy shared grids to make them writeable. */
|
||||
|
||||
#if defined(__cplusplus) && defined(WITH_OPENVDB)
|
||||
# include <openvdb/openvdb.h>
|
||||
# include <openvdb/points/PointDataGrid.h>
|
||||
#ifdef __cplusplus
|
||||
# include "BLI_float3.hh"
|
||||
# include "BLI_float4x4.hh"
|
||||
|
||||
bool BKE_volume_min_max(const Volume *volume, blender::float3 &r_min, blender::float3 &r_max);
|
||||
|
||||
# ifdef WITH_OPENVDB
|
||||
# include <openvdb/openvdb.h>
|
||||
# include <openvdb/points/PointDataGrid.h>
|
||||
|
||||
bool BKE_volume_grid_bounds(openvdb::GridBase::ConstPtr grid,
|
||||
blender::float3 &r_min,
|
||||
blender::float3 &r_max);
|
||||
|
||||
openvdb::GridBase::ConstPtr BKE_volume_grid_shallow_transform(openvdb::GridBase::ConstPtr grid,
|
||||
const blender::float4x4 &transform);
|
||||
|
||||
openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_metadata(const struct VolumeGrid *grid);
|
||||
openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const struct Volume *volume,
|
||||
struct VolumeGrid *grid);
|
||||
const struct VolumeGrid *grid);
|
||||
openvdb::GridBase::Ptr BKE_volume_grid_openvdb_for_write(const struct Volume *volume,
|
||||
struct VolumeGrid *grid,
|
||||
const bool clear);
|
||||
@@ -212,4 +223,5 @@ openvdb::GridBase::Ptr BKE_volume_grid_create_with_changed_resolution(
|
||||
const openvdb::GridBase &old_grid,
|
||||
const float resolution_factor);
|
||||
|
||||
# endif
|
||||
#endif
|
||||
|
@@ -43,7 +43,7 @@ typedef struct DenseFloatVolumeGrid {
|
||||
} DenseFloatVolumeGrid;
|
||||
|
||||
bool BKE_volume_grid_dense_floats(const struct Volume *volume,
|
||||
struct VolumeGrid *volume_grid,
|
||||
const struct VolumeGrid *volume_grid,
|
||||
DenseFloatVolumeGrid *r_dense_grid);
|
||||
void BKE_volume_dense_float_grid_clear(DenseFloatVolumeGrid *dense_grid);
|
||||
|
||||
@@ -53,7 +53,7 @@ typedef void (*BKE_volume_wireframe_cb)(
|
||||
void *userdata, float (*verts)[3], int (*edges)[2], int totvert, int totedge);
|
||||
|
||||
void BKE_volume_grid_wireframe(const struct Volume *volume,
|
||||
struct VolumeGrid *volume_grid,
|
||||
const struct VolumeGrid *volume_grid,
|
||||
BKE_volume_wireframe_cb cb,
|
||||
void *cb_userdata);
|
||||
|
||||
@@ -63,7 +63,7 @@ typedef void (*BKE_volume_selection_surface_cb)(
|
||||
void *userdata, float (*verts)[3], int (*tris)[3], int totvert, int tottris);
|
||||
|
||||
void BKE_volume_grid_selection_surface(const struct Volume *volume,
|
||||
struct VolumeGrid *volume_grid,
|
||||
const struct VolumeGrid *volume_grid,
|
||||
BKE_volume_selection_surface_cb cb,
|
||||
void *cb_userdata);
|
||||
|
||||
|
@@ -190,6 +190,7 @@ set(SRC
|
||||
intern/mesh_remap.c
|
||||
intern/mesh_remesh_voxel.c
|
||||
intern/mesh_runtime.c
|
||||
intern/mesh_sample.cc
|
||||
intern/mesh_tangent.c
|
||||
intern/mesh_validate.c
|
||||
intern/mesh_validate.cc
|
||||
@@ -212,7 +213,7 @@ set(SRC
|
||||
intern/node_ui_storage.cc
|
||||
intern/object.c
|
||||
intern/object_deform.c
|
||||
intern/object_dupli.c
|
||||
intern/object_dupli.cc
|
||||
intern/object_facemap.c
|
||||
intern/object_update.c
|
||||
intern/ocean.c
|
||||
@@ -371,7 +372,7 @@ set(SRC
|
||||
BKE_mball.h
|
||||
BKE_mball_tessellate.h
|
||||
BKE_mesh.h
|
||||
BKE_mesh_boolean_convert.h
|
||||
BKE_mesh_boolean_convert.hh
|
||||
BKE_mesh_fair.h
|
||||
BKE_mesh_iterators.h
|
||||
BKE_mesh_mapping.h
|
||||
@@ -379,6 +380,7 @@ set(SRC
|
||||
BKE_mesh_remap.h
|
||||
BKE_mesh_remesh_voxel.h
|
||||
BKE_mesh_runtime.h
|
||||
BKE_mesh_sample.hh
|
||||
BKE_mesh_tangent.h
|
||||
BKE_mesh_types.h
|
||||
BKE_mesh_wrapper.h
|
||||
|
@@ -287,8 +287,10 @@ bool BKE_animdata_id_is_animated(const struct ID *id)
|
||||
!BLI_listbase_is_empty(&adt->overrides);
|
||||
}
|
||||
|
||||
/** Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of
|
||||
* `IDTypeInfo` structure). */
|
||||
/**
|
||||
* Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of
|
||||
* `IDTypeInfo` structure).
|
||||
*/
|
||||
void BKE_animdata_foreach_id(AnimData *adt, LibraryForeachIDData *data)
|
||||
{
|
||||
LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
|
||||
|
@@ -42,157 +42,192 @@ static CLG_LogRef LOG = {"bke.anim"};
|
||||
/* ******************************************************************** */
|
||||
/* Curve Paths - for curve deforms and/or curve following */
|
||||
|
||||
/**
|
||||
* Free curve path data
|
||||
*
|
||||
* \note Frees the path itself!
|
||||
* \note This is increasingly inaccurate with non-uniform #BevPoint subdivisions T24633.
|
||||
*/
|
||||
void free_path(Path *path)
|
||||
static int get_bevlist_seg_array_size(const BevList *bl)
|
||||
{
|
||||
if (path->data) {
|
||||
MEM_freeN(path->data);
|
||||
if (bl->poly >= 0) {
|
||||
/* Cyclic curve. */
|
||||
return bl->nr;
|
||||
}
|
||||
MEM_freeN(path);
|
||||
|
||||
return bl->nr - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a curve-deform path for a curve
|
||||
* - Only called from displist.c -> #do_makeDispListCurveTypes
|
||||
*/
|
||||
void calc_curvepath(Object *ob, ListBase *nurbs)
|
||||
int BKE_anim_path_get_array_size(const CurveCache *curve_cache)
|
||||
{
|
||||
BevList *bl;
|
||||
BevPoint *bevp, *bevpn, *bevpfirst, *bevplast;
|
||||
PathPoint *pp;
|
||||
Nurb *nu;
|
||||
Path *path;
|
||||
float *fp, *dist, *maxdist, xyz[3];
|
||||
float fac, d = 0, fac1, fac2;
|
||||
int a, tot, cycl = 0;
|
||||
BLI_assert(curve_cache != NULL);
|
||||
|
||||
/* in a path vertices are with equal differences: path->len = number of verts */
|
||||
/* NOW WITH BEVELCURVE!!! */
|
||||
BevList *bl = curve_cache->bev.first;
|
||||
|
||||
BLI_assert(bl != NULL && bl->nr > 1);
|
||||
|
||||
return get_bevlist_seg_array_size(bl);
|
||||
}
|
||||
|
||||
float BKE_anim_path_get_length(const CurveCache *curve_cache)
|
||||
{
|
||||
const int seg_size = BKE_anim_path_get_array_size(curve_cache);
|
||||
return curve_cache->anim_path_accum_length[seg_size - 1];
|
||||
}
|
||||
|
||||
void BKE_anim_path_calc_data(Object *ob)
|
||||
{
|
||||
if (ob == NULL || ob->type != OB_CURVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ob->runtime.curve_cache->path) {
|
||||
free_path(ob->runtime.curve_cache->path);
|
||||
if (ob->runtime.curve_cache == NULL) {
|
||||
CLOG_WARN(&LOG, "No curve cache!");
|
||||
return;
|
||||
}
|
||||
ob->runtime.curve_cache->path = NULL;
|
||||
|
||||
/* weak! can only use first curve */
|
||||
bl = ob->runtime.curve_cache->bev.first;
|
||||
/* We only use the first curve. */
|
||||
BevList *bl = ob->runtime.curve_cache->bev.first;
|
||||
if (bl == NULL || !bl->nr) {
|
||||
CLOG_WARN(&LOG, "No bev list data!");
|
||||
return;
|
||||
}
|
||||
|
||||
nu = nurbs->first;
|
||||
|
||||
ob->runtime.curve_cache->path = path = MEM_callocN(sizeof(Path), "calc_curvepath");
|
||||
|
||||
/* if POLY: last vertice != first vertice */
|
||||
cycl = (bl->poly != -1);
|
||||
|
||||
tot = cycl ? bl->nr : bl->nr - 1;
|
||||
|
||||
path->len = tot + 1;
|
||||
/* Exception: vector handle paths and polygon paths should be subdivided
|
||||
* at least a factor resolution. */
|
||||
if (path->len < nu->resolu * SEGMENTSU(nu)) {
|
||||
path->len = nu->resolu * SEGMENTSU(nu);
|
||||
/* Free old data. */
|
||||
if (ob->runtime.curve_cache->anim_path_accum_length) {
|
||||
MEM_freeN((void *)ob->runtime.curve_cache->anim_path_accum_length);
|
||||
}
|
||||
|
||||
dist = (float *)MEM_mallocN(sizeof(float) * (tot + 1), "calcpathdist");
|
||||
/* We assume that we have at least two points.
|
||||
* If there is less than two points in the curve,
|
||||
* no BevList should have been generated.
|
||||
*/
|
||||
BLI_assert(bl->nr > 1);
|
||||
|
||||
/* all lengths in *dist */
|
||||
bevp = bevpfirst = bl->bevpoints;
|
||||
fp = dist;
|
||||
*fp = 0.0f;
|
||||
for (a = 0; a < tot; a++) {
|
||||
fp++;
|
||||
if (cycl && a == tot - 1) {
|
||||
sub_v3_v3v3(xyz, bevpfirst->vec, bevp->vec);
|
||||
}
|
||||
else {
|
||||
sub_v3_v3v3(xyz, (bevp + 1)->vec, bevp->vec);
|
||||
}
|
||||
const int seg_size = get_bevlist_seg_array_size(bl);
|
||||
float *len_data = (float *)MEM_mallocN(sizeof(float) * seg_size, "calcpathdist");
|
||||
ob->runtime.curve_cache->anim_path_accum_length = len_data;
|
||||
|
||||
*fp = *(fp - 1) + len_v3(xyz);
|
||||
bevp++;
|
||||
BevPoint *bp_arr = bl->bevpoints;
|
||||
float prev_len = 0.0f;
|
||||
for (int i = 0; i < bl->nr - 1; i++) {
|
||||
prev_len += len_v3v3(bp_arr[i].vec, bp_arr[i + 1].vec);
|
||||
len_data[i] = prev_len;
|
||||
}
|
||||
|
||||
path->totdist = *fp;
|
||||
|
||||
/* the path verts in path->data */
|
||||
/* now also with TILT value */
|
||||
pp = path->data = (PathPoint *)MEM_callocN(sizeof(PathPoint) * path->len, "pathdata");
|
||||
|
||||
bevp = bevpfirst;
|
||||
bevpn = bevp + 1;
|
||||
bevplast = bevpfirst + (bl->nr - 1);
|
||||
if (UNLIKELY(bevpn > bevplast)) {
|
||||
bevpn = cycl ? bevpfirst : bevplast;
|
||||
if (bl->poly >= 0) {
|
||||
/* Cyclic curve. */
|
||||
len_data[seg_size - 1] = prev_len + len_v3v3(bp_arr[0].vec, bp_arr[bl->nr - 1].vec);
|
||||
}
|
||||
fp = dist + 1;
|
||||
maxdist = dist + tot;
|
||||
fac = 1.0f / ((float)path->len - 1.0f);
|
||||
fac = fac * path->totdist;
|
||||
|
||||
for (a = 0; a < path->len; a++) {
|
||||
|
||||
d = ((float)a) * fac;
|
||||
|
||||
/* we're looking for location (distance) 'd' in the array */
|
||||
if (LIKELY(tot > 0)) {
|
||||
while ((fp < maxdist) && (d >= *fp)) {
|
||||
fp++;
|
||||
if (bevp < bevplast) {
|
||||
bevp++;
|
||||
}
|
||||
bevpn = bevp + 1;
|
||||
if (UNLIKELY(bevpn > bevplast)) {
|
||||
bevpn = cycl ? bevpfirst : bevplast;
|
||||
}
|
||||
}
|
||||
|
||||
fac1 = (*(fp)-d) / (*(fp) - *(fp - 1));
|
||||
fac2 = 1.0f - fac1;
|
||||
}
|
||||
else {
|
||||
fac1 = 1.0f;
|
||||
fac2 = 0.0f;
|
||||
}
|
||||
|
||||
interp_v3_v3v3(pp->vec, bevp->vec, bevpn->vec, fac2);
|
||||
pp->vec[3] = fac1 * bevp->tilt + fac2 * bevpn->tilt;
|
||||
pp->radius = fac1 * bevp->radius + fac2 * bevpn->radius;
|
||||
pp->weight = fac1 * bevp->weight + fac2 * bevpn->weight;
|
||||
interp_qt_qtqt(pp->quat, bevp->quat, bevpn->quat, fac2);
|
||||
normalize_qt(pp->quat);
|
||||
|
||||
pp++;
|
||||
}
|
||||
|
||||
MEM_freeN(dist);
|
||||
}
|
||||
|
||||
static int interval_test(const int min, const int max, int p1, const int cycl)
|
||||
static void get_curve_points_from_idx(const int idx,
|
||||
const BevList *bl,
|
||||
const bool is_cyclic,
|
||||
BevPoint const **r_p0,
|
||||
BevPoint const **r_p1,
|
||||
BevPoint const **r_p2,
|
||||
BevPoint const **r_p3)
|
||||
{
|
||||
if (cycl) {
|
||||
p1 = mod_i(p1 - min, (max - min + 1)) + min;
|
||||
}
|
||||
else {
|
||||
if (p1 < min) {
|
||||
p1 = min;
|
||||
BLI_assert(idx >= 0);
|
||||
BLI_assert(idx < bl->nr - 1 || (is_cyclic && idx < bl->nr));
|
||||
BLI_assert(bl->nr > 1);
|
||||
|
||||
const BevPoint *bp_arr = bl->bevpoints;
|
||||
|
||||
/* First segment. */
|
||||
if (idx == 0) {
|
||||
*r_p1 = &bp_arr[0];
|
||||
if (is_cyclic) {
|
||||
*r_p0 = &bp_arr[bl->nr - 1];
|
||||
}
|
||||
else if (p1 > max) {
|
||||
p1 = max;
|
||||
else {
|
||||
*r_p0 = *r_p1;
|
||||
}
|
||||
|
||||
*r_p2 = &bp_arr[1];
|
||||
|
||||
if (bl->nr > 2) {
|
||||
*r_p3 = &bp_arr[2];
|
||||
}
|
||||
else {
|
||||
*r_p3 = *r_p2;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Last segment (or next to last in a cyclic curve). */
|
||||
if (idx == bl->nr - 2) {
|
||||
/* The case when the bl->nr == 2 falls in to the "first segment" check above.
|
||||
* So here we can assume that bl->nr > 2.
|
||||
*/
|
||||
*r_p0 = &bp_arr[idx - 1];
|
||||
*r_p1 = &bp_arr[idx];
|
||||
*r_p2 = &bp_arr[idx + 1];
|
||||
|
||||
if (is_cyclic) {
|
||||
*r_p3 = &bp_arr[0];
|
||||
}
|
||||
else {
|
||||
*r_p3 = *r_p2;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx == bl->nr - 1) {
|
||||
/* Last segment in a cyclic curve. This should only trigger if the curve is cyclic
|
||||
* as it gets an extra segment between the end and the start point. */
|
||||
*r_p0 = &bp_arr[idx - 1];
|
||||
*r_p1 = &bp_arr[idx];
|
||||
*r_p2 = &bp_arr[0];
|
||||
*r_p3 = &bp_arr[1];
|
||||
return;
|
||||
}
|
||||
|
||||
/* To get here the curve has to have four curve points or more and idx can't
|
||||
* be the first or the last segment.
|
||||
* So we can assume that we can get four points without any special checks.
|
||||
*/
|
||||
*r_p0 = &bp_arr[idx - 1];
|
||||
*r_p1 = &bp_arr[idx];
|
||||
*r_p2 = &bp_arr[idx + 1];
|
||||
*r_p3 = &bp_arr[idx + 2];
|
||||
}
|
||||
|
||||
static bool binary_search_anim_path(const float *accum_len_arr,
|
||||
const int seg_size,
|
||||
const float goal_len,
|
||||
int *r_idx,
|
||||
float *r_frac)
|
||||
{
|
||||
float left_len, right_len;
|
||||
int cur_idx = 0, cur_base = 0;
|
||||
int cur_step = seg_size - 1;
|
||||
|
||||
while (true) {
|
||||
cur_idx = cur_base + cur_step / 2;
|
||||
left_len = accum_len_arr[cur_idx];
|
||||
right_len = accum_len_arr[cur_idx + 1];
|
||||
|
||||
if (left_len <= goal_len && right_len > goal_len) {
|
||||
*r_idx = cur_idx + 1;
|
||||
*r_frac = (goal_len - left_len) / (right_len - left_len);
|
||||
return true;
|
||||
}
|
||||
if (cur_idx == 0) {
|
||||
/* We ended up at the first segment. The point must be in here. */
|
||||
*r_idx = 0;
|
||||
*r_frac = goal_len / accum_len_arr[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (UNLIKELY(cur_step == 0)) {
|
||||
/* This should never happen unless there is something horribly wrong. */
|
||||
CLOG_ERROR(&LOG, "Couldn't find any valid point on the animation path!");
|
||||
BLI_assert(!"Couldn't find any valid point on the animation path!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (left_len < goal_len) {
|
||||
/* Go to the right. */
|
||||
cur_base = cur_idx + 1;
|
||||
cur_step--;
|
||||
} /* Else, go to the left. */
|
||||
|
||||
cur_step /= 2;
|
||||
}
|
||||
return p1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,66 +238,70 @@ static int interval_test(const int min, const int max, int p1, const int cycl)
|
||||
*
|
||||
* \return success.
|
||||
*/
|
||||
bool where_on_path(const Object *ob,
|
||||
float ctime,
|
||||
float r_vec[4],
|
||||
float r_dir[3],
|
||||
float r_quat[4],
|
||||
float *r_radius,
|
||||
float *r_weight)
|
||||
bool BKE_where_on_path(const Object *ob,
|
||||
float ctime,
|
||||
float r_vec[4],
|
||||
float r_dir[3],
|
||||
float r_quat[4],
|
||||
float *r_radius,
|
||||
float *r_weight)
|
||||
{
|
||||
Curve *cu;
|
||||
const Nurb *nu;
|
||||
const BevList *bl;
|
||||
const Path *path;
|
||||
const PathPoint *pp, *p0, *p1, *p2, *p3;
|
||||
float fac;
|
||||
float data[4];
|
||||
int cycl = 0, s0, s1, s2, s3;
|
||||
const ListBase *nurbs;
|
||||
|
||||
if (ob == NULL || ob->type != OB_CURVE) {
|
||||
return false;
|
||||
}
|
||||
cu = ob->data;
|
||||
if (ob->runtime.curve_cache == NULL || ob->runtime.curve_cache->path == NULL ||
|
||||
ob->runtime.curve_cache->path->data == NULL) {
|
||||
CLOG_WARN(&LOG, "no path!");
|
||||
Curve *cu = ob->data;
|
||||
if (ob->runtime.curve_cache == NULL) {
|
||||
CLOG_WARN(&LOG, "No curve cache!");
|
||||
return false;
|
||||
}
|
||||
path = ob->runtime.curve_cache->path;
|
||||
pp = path->data;
|
||||
|
||||
/* test for cyclic */
|
||||
bl = ob->runtime.curve_cache->bev.first;
|
||||
if (!bl) {
|
||||
/* We only use the first curve. */
|
||||
BevList *bl = ob->runtime.curve_cache->bev.first;
|
||||
if (bl == NULL || !bl->nr) {
|
||||
CLOG_WARN(&LOG, "No bev list data!");
|
||||
return false;
|
||||
}
|
||||
if (!bl->nr) {
|
||||
return false;
|
||||
}
|
||||
if (bl->poly > -1) {
|
||||
cycl = 1;
|
||||
|
||||
/* Test for cyclic curve. */
|
||||
const bool is_cyclic = bl->poly >= 0;
|
||||
|
||||
if (is_cyclic) {
|
||||
/* Wrap the time into a 0.0 - 1.0 range. */
|
||||
if (ctime < 0.0f || ctime > 1.0f) {
|
||||
ctime -= floorf(ctime);
|
||||
}
|
||||
}
|
||||
|
||||
/* values below zero for non-cyclic curves give strange results */
|
||||
BLI_assert(cycl || ctime >= 0.0f);
|
||||
/* The curve points for this ctime value. */
|
||||
const BevPoint *p0, *p1, *p2, *p3;
|
||||
|
||||
ctime *= (path->len - 1);
|
||||
float frac;
|
||||
const int seg_size = get_bevlist_seg_array_size(bl);
|
||||
const float *accum_len_arr = ob->runtime.curve_cache->anim_path_accum_length;
|
||||
const float goal_len = ctime * accum_len_arr[seg_size - 1];
|
||||
|
||||
s1 = (int)floor(ctime);
|
||||
fac = (float)(s1 + 1) - ctime;
|
||||
/* Are we simply trying to get the start/end point? */
|
||||
if (ctime <= 0.0f || ctime >= 1.0f) {
|
||||
const float clamp_time = clamp_f(ctime, 0.0f, 1.0f);
|
||||
const int idx = clamp_time * (seg_size - 1);
|
||||
get_curve_points_from_idx(idx, bl, is_cyclic, &p0, &p1, &p2, &p3);
|
||||
|
||||
/* path->len is corrected for cyclic */
|
||||
s0 = interval_test(0, path->len - 1 - cycl, s1 - 1, cycl);
|
||||
s1 = interval_test(0, path->len - 1 - cycl, s1, cycl);
|
||||
s2 = interval_test(0, path->len - 1 - cycl, s1 + 1, cycl);
|
||||
s3 = interval_test(0, path->len - 1 - cycl, s1 + 2, cycl);
|
||||
if (idx == 0) {
|
||||
frac = goal_len / accum_len_arr[0];
|
||||
}
|
||||
else {
|
||||
frac = (goal_len - accum_len_arr[idx - 1]) / (accum_len_arr[idx] - accum_len_arr[idx - 1]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Do binary search to get the correct segment. */
|
||||
int idx;
|
||||
const bool found_idx = binary_search_anim_path(accum_len_arr, seg_size, goal_len, &idx, &frac);
|
||||
|
||||
p0 = pp + s0;
|
||||
p1 = pp + s1;
|
||||
p2 = pp + s2;
|
||||
p3 = pp + s3;
|
||||
if (UNLIKELY(!found_idx)) {
|
||||
return false;
|
||||
}
|
||||
get_curve_points_from_idx(idx, bl, is_cyclic, &p0, &p1, &p2, &p3);
|
||||
}
|
||||
|
||||
/* NOTE: commented out for follow constraint
|
||||
*
|
||||
@@ -272,65 +311,68 @@ bool where_on_path(const Object *ob,
|
||||
*/
|
||||
// if (cu->flag & CU_FOLLOW) {
|
||||
|
||||
key_curve_tangent_weights(1.0f - fac, data, KEY_BSPLINE);
|
||||
float w[4];
|
||||
|
||||
interp_v3_v3v3v3v3(r_dir, p0->vec, p1->vec, p2->vec, p3->vec, data);
|
||||
key_curve_tangent_weights(frac, w, KEY_BSPLINE);
|
||||
|
||||
interp_v3_v3v3v3v3(r_dir, p0->vec, p1->vec, p2->vec, p3->vec, w);
|
||||
|
||||
/* Make compatible with #vec_to_quat. */
|
||||
negate_v3(r_dir);
|
||||
//}
|
||||
|
||||
nurbs = BKE_curve_editNurbs_get(cu);
|
||||
const ListBase *nurbs = BKE_curve_editNurbs_get(cu);
|
||||
if (!nurbs) {
|
||||
nurbs = &cu->nurb;
|
||||
}
|
||||
nu = nurbs->first;
|
||||
const Nurb *nu = nurbs->first;
|
||||
|
||||
/* make sure that first and last frame are included in the vectors here */
|
||||
if (nu->type == CU_POLY) {
|
||||
key_curve_position_weights(1.0f - fac, data, KEY_LINEAR);
|
||||
if (ELEM(nu->type, CU_POLY, CU_BEZIER, CU_NURBS)) {
|
||||
key_curve_position_weights(frac, w, KEY_LINEAR);
|
||||
}
|
||||
else if (nu->type == CU_BEZIER) {
|
||||
key_curve_position_weights(1.0f - fac, data, KEY_LINEAR);
|
||||
}
|
||||
else if (s0 == s1 || p2 == p3) {
|
||||
key_curve_position_weights(1.0f - fac, data, KEY_CARDINAL);
|
||||
else if (p2 == p3) {
|
||||
key_curve_position_weights(frac, w, KEY_CARDINAL);
|
||||
}
|
||||
else {
|
||||
key_curve_position_weights(1.0f - fac, data, KEY_BSPLINE);
|
||||
key_curve_position_weights(frac, w, KEY_BSPLINE);
|
||||
}
|
||||
|
||||
r_vec[0] = /* X */
|
||||
data[0] * p0->vec[0] + data[1] * p1->vec[0] + data[2] * p2->vec[0] + data[3] * p3->vec[0];
|
||||
w[0] * p0->vec[0] + w[1] * p1->vec[0] + w[2] * p2->vec[0] + w[3] * p3->vec[0];
|
||||
r_vec[1] = /* Y */
|
||||
data[0] * p0->vec[1] + data[1] * p1->vec[1] + data[2] * p2->vec[1] + data[3] * p3->vec[1];
|
||||
w[0] * p0->vec[1] + w[1] * p1->vec[1] + w[2] * p2->vec[1] + w[3] * p3->vec[1];
|
||||
r_vec[2] = /* Z */
|
||||
data[0] * p0->vec[2] + data[1] * p1->vec[2] + data[2] * p2->vec[2] + data[3] * p3->vec[2];
|
||||
r_vec[3] = /* Tilt, should not be needed since we have quat still used */
|
||||
data[0] * p0->vec[3] + data[1] * p1->vec[3] + data[2] * p2->vec[3] + data[3] * p3->vec[3];
|
||||
w[0] * p0->vec[2] + w[1] * p1->vec[2] + w[2] * p2->vec[2] + w[3] * p3->vec[2];
|
||||
|
||||
/* Clamp weights to 0-1 as we don't want to extrapolate other values than position. */
|
||||
clamp_v4(w, 0.0f, 1.0f);
|
||||
|
||||
/* Tilt, should not be needed since we have quat still used. */
|
||||
r_vec[3] = w[0] * p0->tilt + w[1] * p1->tilt + w[2] * p2->tilt + w[3] * p3->tilt;
|
||||
|
||||
if (r_quat) {
|
||||
float totfac, q1[4], q2[4];
|
||||
|
||||
totfac = data[0] + data[3];
|
||||
totfac = w[0] + w[3];
|
||||
if (totfac > FLT_EPSILON) {
|
||||
interp_qt_qtqt(q1, p0->quat, p3->quat, data[3] / totfac);
|
||||
interp_qt_qtqt(q1, p0->quat, p3->quat, w[3] / totfac);
|
||||
}
|
||||
else {
|
||||
copy_qt_qt(q1, p1->quat);
|
||||
}
|
||||
|
||||
totfac = data[1] + data[2];
|
||||
totfac = w[1] + w[2];
|
||||
if (totfac > FLT_EPSILON) {
|
||||
interp_qt_qtqt(q2, p1->quat, p2->quat, data[2] / totfac);
|
||||
interp_qt_qtqt(q2, p1->quat, p2->quat, w[2] / totfac);
|
||||
}
|
||||
else {
|
||||
copy_qt_qt(q2, p3->quat);
|
||||
}
|
||||
|
||||
totfac = data[0] + data[1] + data[2] + data[3];
|
||||
totfac = w[0] + w[1] + w[2] + w[3];
|
||||
if (totfac > FLT_EPSILON) {
|
||||
interp_qt_qtqt(r_quat, q1, q2, (data[1] + data[2]) / totfac);
|
||||
interp_qt_qtqt(r_quat, q1, q2, (w[1] + w[2]) / totfac);
|
||||
}
|
||||
else {
|
||||
copy_qt_qt(r_quat, q2);
|
||||
@@ -338,13 +380,11 @@ bool where_on_path(const Object *ob,
|
||||
}
|
||||
|
||||
if (r_radius) {
|
||||
*r_radius = data[0] * p0->radius + data[1] * p1->radius + data[2] * p2->radius +
|
||||
data[3] * p3->radius;
|
||||
*r_radius = w[0] * p0->radius + w[1] * p1->radius + w[2] * p2->radius + w[3] * p3->radius;
|
||||
}
|
||||
|
||||
if (r_weight) {
|
||||
*r_weight = data[0] * p0->weight + data[1] * p1->weight + data[2] * p2->weight +
|
||||
data[3] * p3->weight;
|
||||
*r_weight = w[0] * p0->weight + w[1] * p1->weight + w[2] * p2->weight + w[3] * p3->weight;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -1605,8 +1605,9 @@ static bool nla_combine_get_inverted_strip_value(const int mix_mode,
|
||||
}
|
||||
}
|
||||
|
||||
/** Accumulate quaternion channels for Combine mode according to influence.
|
||||
* \returns blended_value = lower_values @ strip_values^infl
|
||||
/**
|
||||
* Accumulate quaternion channels for Combine mode according to influence.
|
||||
* \returns `blended_value = lower_values @ strip_values^infl`
|
||||
*/
|
||||
static void nla_combine_quaternion(const float lower_values[4],
|
||||
const float strip_values[4],
|
||||
@@ -2094,8 +2095,10 @@ static void animsys_evaluate_nla_domain(PointerRNA *ptr, NlaEvalData *channels,
|
||||
|
||||
/* ---------------------- */
|
||||
|
||||
/** Tweaked strip is evaluated differently from other strips. Adjacent strips are ignored
|
||||
* and includes a workaround for when user is not editing in place. */
|
||||
/**
|
||||
* Tweaked strip is evaluated differently from other strips. Adjacent strips are ignored
|
||||
* and includes a workaround for when user is not editing in place.
|
||||
*/
|
||||
static void animsys_create_tweak_strip(const AnimData *adt,
|
||||
const bool keyframing_to_strip,
|
||||
NlaStrip *r_tweak_strip)
|
||||
@@ -2210,8 +2213,10 @@ static bool is_nlatrack_evaluatable(const AnimData *adt, const NlaTrack *nlt)
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Check for special case of non-pushed action being evaluated with no NLA influence (off and no
|
||||
* strips evaluated) nor NLA interference (ensure NLA not soloing). */
|
||||
/**
|
||||
* Check for special case of non-pushed action being evaluated with no NLA influence (off and no
|
||||
* strips evaluated) nor NLA interference (ensure NLA not soloing).
|
||||
*/
|
||||
static bool is_action_track_evaluated_without_nla(const AnimData *adt,
|
||||
const bool any_strip_evaluated)
|
||||
{
|
||||
@@ -2491,7 +2496,8 @@ void nlasnapshot_ensure_channels(NlaEvalData *eval_data, NlaEvalSnapshot *snapsh
|
||||
}
|
||||
}
|
||||
|
||||
/** Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according
|
||||
/**
|
||||
* Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according
|
||||
* to the given \a upper_blendmode and \a upper_influence.
|
||||
*
|
||||
* For \a upper_snapshot, blending limited to values in the \a blend_domain. For Replace blendmode,
|
||||
|
@@ -137,7 +137,7 @@ static char *blender_version_decimal(const int version)
|
||||
{
|
||||
static char version_str[5];
|
||||
BLI_assert(version < 1000);
|
||||
BLI_snprintf(version_str, sizeof(version_str), "%d.%02d", version / 100, version % 100);
|
||||
BLI_snprintf(version_str, sizeof(version_str), "%d.%d", version / 100, version % 100);
|
||||
return version_str;
|
||||
}
|
||||
|
||||
|
@@ -63,40 +63,40 @@ typedef struct tSplineIK_Tree {
|
||||
|
||||
bPoseChannel *root; /* bone that is the root node of the chain */
|
||||
|
||||
bConstraint *con; /* constraint for this chain */
|
||||
bSplineIKConstraint *ikData; /* constraint settings for this chain */
|
||||
bConstraint *con; /* constraint for this chain */
|
||||
bSplineIKConstraint *ik_data; /* constraint settings for this chain */
|
||||
} tSplineIK_Tree;
|
||||
|
||||
/* ----------- */
|
||||
|
||||
/* Tag the bones in the chain formed by the given bone for IK */
|
||||
/* Tag the bones in the chain formed by the given bone for IK. */
|
||||
static void splineik_init_tree_from_pchan(Scene *UNUSED(scene),
|
||||
Object *UNUSED(ob),
|
||||
bPoseChannel *pchan_tip)
|
||||
{
|
||||
bPoseChannel *pchan, *pchanRoot = NULL;
|
||||
bPoseChannel *pchanChain[255];
|
||||
bPoseChannel *pchan, *pchan_root = NULL;
|
||||
bPoseChannel *pchan_chain[255];
|
||||
bConstraint *con = NULL;
|
||||
bSplineIKConstraint *ikData = NULL;
|
||||
float boneLengths[255];
|
||||
float totLength = 0.0f;
|
||||
bSplineIKConstraint *ik_data = NULL;
|
||||
float bone_lengths[255];
|
||||
float totlength = 0.0f;
|
||||
int segcount = 0;
|
||||
|
||||
/* find the SplineIK constraint */
|
||||
/* Find the SplineIK constraint. */
|
||||
for (con = pchan_tip->constraints.first; con; con = con->next) {
|
||||
if (con->type == CONSTRAINT_TYPE_SPLINEIK) {
|
||||
ikData = con->data;
|
||||
ik_data = con->data;
|
||||
|
||||
/* target can only be curve */
|
||||
if ((ikData->tar == NULL) || (ikData->tar->type != OB_CURVE)) {
|
||||
/* Target can only be a curve. */
|
||||
if ((ik_data->tar == NULL) || (ik_data->tar->type != OB_CURVE)) {
|
||||
continue;
|
||||
}
|
||||
/* skip if disabled */
|
||||
/* Skip if disabled. */
|
||||
if ((con->enforce == 0.0f) || (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* otherwise, constraint is ok... */
|
||||
/* Otherwise, constraint is ok... */
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -104,102 +104,102 @@ static void splineik_init_tree_from_pchan(Scene *UNUSED(scene),
|
||||
return;
|
||||
}
|
||||
|
||||
/* find the root bone and the chain of bones from the root to the tip
|
||||
/* Find the root bone and the chain of bones from the root to the tip.
|
||||
* NOTE: this assumes that the bones are connected, but that may not be true... */
|
||||
for (pchan = pchan_tip; pchan && (segcount < ikData->chainlen);
|
||||
for (pchan = pchan_tip; pchan && (segcount < ik_data->chainlen);
|
||||
pchan = pchan->parent, segcount++) {
|
||||
/* store this segment in the chain */
|
||||
pchanChain[segcount] = pchan;
|
||||
/* Store this segment in the chain. */
|
||||
pchan_chain[segcount] = pchan;
|
||||
|
||||
/* if performing rebinding, calculate the length of the bone */
|
||||
boneLengths[segcount] = pchan->bone->length;
|
||||
totLength += boneLengths[segcount];
|
||||
/* If performing rebinding, calculate the length of the bone. */
|
||||
bone_lengths[segcount] = pchan->bone->length;
|
||||
totlength += bone_lengths[segcount];
|
||||
}
|
||||
|
||||
if (segcount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
pchanRoot = pchanChain[segcount - 1];
|
||||
pchan_root = pchan_chain[segcount - 1];
|
||||
|
||||
/* perform binding step if required */
|
||||
if ((ikData->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) {
|
||||
/* Perform binding step if required. */
|
||||
if ((ik_data->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) {
|
||||
float segmentLen = (1.0f / (float)segcount);
|
||||
|
||||
/* setup new empty array for the points list */
|
||||
if (ikData->points) {
|
||||
MEM_freeN(ikData->points);
|
||||
/* Setup new empty array for the points list. */
|
||||
if (ik_data->points) {
|
||||
MEM_freeN(ik_data->points);
|
||||
}
|
||||
ikData->numpoints = ikData->chainlen + 1;
|
||||
ikData->points = MEM_mallocN(sizeof(float) * ikData->numpoints, "Spline IK Binding");
|
||||
ik_data->numpoints = ik_data->chainlen + 1;
|
||||
ik_data->points = MEM_mallocN(sizeof(float) * ik_data->numpoints, "Spline IK Binding");
|
||||
|
||||
/* bind 'tip' of chain (i.e. first joint = tip of bone with the Spline IK Constraint) */
|
||||
ikData->points[0] = 1.0f;
|
||||
/* Bind 'tip' of chain (i.e. first joint = tip of bone with the Spline IK Constraint). */
|
||||
ik_data->points[0] = 1.0f;
|
||||
|
||||
/* perform binding of the joints to parametric positions along the curve based
|
||||
* proportion of the total length that each bone occupies
|
||||
/* Perform binding of the joints to parametric positions along the curve based
|
||||
* proportion of the total length that each bone occupies.
|
||||
*/
|
||||
for (int i = 0; i < segcount; i++) {
|
||||
/* 'head' joints, traveling towards the root of the chain
|
||||
* - 2 methods; the one chosen depends on whether we've got usable lengths
|
||||
/* 'head' joints, traveling towards the root of the chain.
|
||||
* - 2 methods; the one chosen depends on whether we've got usable lengths.
|
||||
*/
|
||||
if ((ikData->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totLength == 0.0f)) {
|
||||
/* 1) equi-spaced joints */
|
||||
ikData->points[i + 1] = ikData->points[i] - segmentLen;
|
||||
if ((ik_data->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totlength == 0.0f)) {
|
||||
/* 1) Equi-spaced joints. */
|
||||
ik_data->points[i + 1] = ik_data->points[i] - segmentLen;
|
||||
}
|
||||
else {
|
||||
/* 2) to find this point on the curve, we take a step from the previous joint
|
||||
* a distance given by the proportion that this bone takes
|
||||
/* 2) To find this point on the curve, we take a step from the previous joint
|
||||
* a distance given by the proportion that this bone takes.
|
||||
*/
|
||||
ikData->points[i + 1] = ikData->points[i] - (boneLengths[i] / totLength);
|
||||
ik_data->points[i + 1] = ik_data->points[i] - (bone_lengths[i] / totlength);
|
||||
}
|
||||
}
|
||||
|
||||
/* spline has now been bound */
|
||||
ikData->flag |= CONSTRAINT_SPLINEIK_BOUND;
|
||||
/* Spline has now been bound. */
|
||||
ik_data->flag |= CONSTRAINT_SPLINEIK_BOUND;
|
||||
}
|
||||
|
||||
/* disallow negative values (happens with float precision) */
|
||||
CLAMP_MIN(ikData->points[segcount], 0.0f);
|
||||
/* Disallow negative values (happens with float precision). */
|
||||
CLAMP_MIN(ik_data->points[segcount], 0.0f);
|
||||
|
||||
/* make a new Spline-IK chain, and store it in the IK chains */
|
||||
/* Make a new Spline-IK chain, and store it in the IK chains. */
|
||||
/* TODO: we should check if there is already an IK chain on this,
|
||||
* since that would take precedence... */
|
||||
{
|
||||
/* make new tree */
|
||||
/* Make a new tree. */
|
||||
tSplineIK_Tree *tree = MEM_callocN(sizeof(tSplineIK_Tree), "SplineIK Tree");
|
||||
tree->type = CONSTRAINT_TYPE_SPLINEIK;
|
||||
|
||||
tree->chainlen = segcount;
|
||||
tree->totlength = totLength;
|
||||
tree->totlength = totlength;
|
||||
|
||||
/* copy over the array of links to bones in the chain (from tip to root) */
|
||||
/* Copy over the array of links to bones in the chain (from tip to root). */
|
||||
tree->chain = MEM_mallocN(sizeof(bPoseChannel *) * segcount, "SplineIK Chain");
|
||||
memcpy(tree->chain, pchanChain, sizeof(bPoseChannel *) * segcount);
|
||||
memcpy(tree->chain, pchan_chain, sizeof(bPoseChannel *) * segcount);
|
||||
|
||||
/* store reference to joint position array */
|
||||
tree->points = ikData->points;
|
||||
/* Store reference to joint position array. */
|
||||
tree->points = ik_data->points;
|
||||
|
||||
/* store references to different parts of the chain */
|
||||
tree->root = pchanRoot;
|
||||
/* Store references to different parts of the chain. */
|
||||
tree->root = pchan_root;
|
||||
tree->con = con;
|
||||
tree->ikData = ikData;
|
||||
tree->ik_data = ik_data;
|
||||
|
||||
/* AND! link the tree to the root */
|
||||
BLI_addtail(&pchanRoot->siktree, tree);
|
||||
/* AND! Link the tree to the root. */
|
||||
BLI_addtail(&pchan_root->siktree, tree);
|
||||
}
|
||||
|
||||
/* mark root channel having an IK tree */
|
||||
pchanRoot->flag |= POSE_IKSPLINE;
|
||||
/* Mark root channel having an IK tree. */
|
||||
pchan_root->flag |= POSE_IKSPLINE;
|
||||
}
|
||||
|
||||
/* Tag which bones are members of Spline IK chains */
|
||||
/* Tag which bones are members of Spline IK chains. */
|
||||
static void splineik_init_tree(Scene *scene, Object *ob, float UNUSED(ctime))
|
||||
{
|
||||
bPoseChannel *pchan;
|
||||
|
||||
/* find the tips of Spline IK chains,
|
||||
* which are simply the bones which have been tagged as such */
|
||||
/* Find the tips of Spline IK chains,
|
||||
* which are simply the bones which have been tagged as such. */
|
||||
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
|
||||
if (pchan->constflag & PCHAN_HAS_SPLINEIK) {
|
||||
splineik_init_tree_from_pchan(scene, ob, pchan);
|
||||
@@ -213,23 +213,26 @@ typedef struct tSplineIk_EvalState {
|
||||
float curve_position; /* Current position along the curve. */
|
||||
float curve_scale; /* Global scale to apply to curve positions. */
|
||||
float locrot_offset[4][4]; /* Bone rotation and location offset inherited from parent. */
|
||||
float prev_tail_loc[3]; /* Tail location of the previous bone. */
|
||||
float prev_tail_radius; /* Tail curve radius of the previous bone. */
|
||||
int prev_tail_seg_idx; /* Curve segment the previous tail bone belongs to. */
|
||||
} tSplineIk_EvalState;
|
||||
|
||||
/* Prepare data to evaluate spline IK. */
|
||||
static bool splineik_evaluate_init(tSplineIK_Tree *tree, tSplineIk_EvalState *state)
|
||||
{
|
||||
bSplineIKConstraint *ikData = tree->ikData;
|
||||
bSplineIKConstraint *ik_data = tree->ik_data;
|
||||
|
||||
/* Make sure that the constraint targets are ok, to avoid crashes
|
||||
* in case of a depsgraph bug or dependency cycle.
|
||||
*/
|
||||
if (ikData->tar == NULL) {
|
||||
if (ik_data->tar == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CurveCache *cache = ikData->tar->runtime.curve_cache;
|
||||
CurveCache *cache = ik_data->tar->runtime.curve_cache;
|
||||
|
||||
if (ELEM(NULL, cache, cache->path, cache->path->data)) {
|
||||
if (ELEM(NULL, cache, cache->anim_path_accum_length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -237,97 +240,248 @@ static bool splineik_evaluate_init(tSplineIK_Tree *tree, tSplineIk_EvalState *st
|
||||
state->curve_position = 0.0f;
|
||||
state->curve_scale = 1.0f;
|
||||
unit_m4(state->locrot_offset);
|
||||
zero_v3(state->prev_tail_loc);
|
||||
state->prev_tail_radius = 1.0f;
|
||||
state->prev_tail_seg_idx = 0;
|
||||
|
||||
/* Apply corrections for sensitivity to scaling. */
|
||||
if ((ikData->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) && (tree->totlength != 0.0f)) {
|
||||
/* get the current length of the curve */
|
||||
/* NOTE: this is assumed to be correct even after the curve was resized */
|
||||
float splineLen = cache->path->totdist;
|
||||
if ((ik_data->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) && (tree->totlength != 0.0f)) {
|
||||
/* Get the current length of the curve. */
|
||||
/* NOTE: This is assumed to be correct even after the curve was resized. */
|
||||
const float spline_len = BKE_anim_path_get_length(cache);
|
||||
|
||||
/* calculate the scale factor to multiply all the path values by so that the
|
||||
* bone chain retains its current length, such that
|
||||
/* Calculate the scale factor to multiply all the path values by so that the
|
||||
* bone chain retains its current length, such that:
|
||||
* maxScale * splineLen = totLength
|
||||
*/
|
||||
state->curve_scale = tree->totlength / splineLen;
|
||||
state->curve_scale = tree->totlength / spline_len;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void apply_curve_transform(
|
||||
bSplineIKConstraint *ik_data, Object *ob, float radius, float r_vec[3], float *r_radius)
|
||||
{
|
||||
/* Apply the curve's object-mode transforms to the position
|
||||
* unless the option to allow curve to be positioned elsewhere is activated (i.e. no root).
|
||||
*/
|
||||
if ((ik_data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) {
|
||||
mul_m4_v3(ik_data->tar->obmat, r_vec);
|
||||
}
|
||||
|
||||
/* Convert the position to pose-space. */
|
||||
mul_m4_v3(ob->imat, r_vec);
|
||||
|
||||
/* Set the new radius (it should be the average value). */
|
||||
*r_radius = (radius + *r_radius) / 2;
|
||||
}
|
||||
|
||||
/* This function positions the tail of the bone so that it preserves the length of it.
|
||||
* The length of the bone can be seen as a sphere radius.
|
||||
*/
|
||||
static int position_tail_on_spline(bSplineIKConstraint *ik_data,
|
||||
const float head_pos[3],
|
||||
const float sphere_radius,
|
||||
const int prev_seg_idx,
|
||||
float r_tail_pos[3],
|
||||
float *r_new_curve_pos,
|
||||
float *r_radius)
|
||||
{
|
||||
/* This is using the tessellated curve data.
|
||||
* So we are working with piece-wise linear curve segments.
|
||||
* The same method is use in #BKE_where_on_path to get curve location data. */
|
||||
const CurveCache *cache = ik_data->tar->runtime.curve_cache;
|
||||
const BevList *bl = cache->bev.first;
|
||||
BevPoint *bp = bl->bevpoints;
|
||||
const float spline_len = BKE_anim_path_get_length(cache);
|
||||
const float *seg_accum_len = cache->anim_path_accum_length;
|
||||
|
||||
int max_seg_idx = BKE_anim_path_get_array_size(cache) - 1;
|
||||
|
||||
/* Convert our initial intersection point guess to a point index.
|
||||
* If the curve was a straight line, then pointEnd would be the correct location.
|
||||
* So make it our first initial guess.
|
||||
*/
|
||||
const float guessed_len = *r_new_curve_pos * spline_len;
|
||||
|
||||
BLI_assert(prev_seg_idx >= 0);
|
||||
|
||||
int cur_seg_idx = prev_seg_idx;
|
||||
while (cur_seg_idx < max_seg_idx && guessed_len > seg_accum_len[cur_seg_idx]) {
|
||||
cur_seg_idx++;
|
||||
}
|
||||
|
||||
int bp_idx = cur_seg_idx + 1;
|
||||
|
||||
bp = bp + bp_idx;
|
||||
bool is_cyclic = bl->poly >= 0;
|
||||
BevPoint *prev_bp = bp - 1;
|
||||
|
||||
/* Go to the next tessellated curve point until we cross to outside of the sphere. */
|
||||
while (len_v3v3(head_pos, bp->vec) < sphere_radius) {
|
||||
if (bp_idx > max_seg_idx) {
|
||||
/* We are outside the defined curve. We will now extrapolate the intersection point. */
|
||||
break;
|
||||
}
|
||||
prev_bp = bp;
|
||||
if (is_cyclic && bp_idx == max_seg_idx) {
|
||||
/* Wrap around to the start point.
|
||||
* Don't set the bp_idx to zero here as we use it to get the segment index later.
|
||||
*/
|
||||
bp = bl->bevpoints;
|
||||
}
|
||||
else {
|
||||
bp++;
|
||||
}
|
||||
bp_idx++;
|
||||
}
|
||||
|
||||
float isect_1[3], isect_2[3];
|
||||
|
||||
/* Calculate the intersection point. */
|
||||
isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2);
|
||||
|
||||
/* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the
|
||||
* intersection point we want. And it will always intersect as we go from inside to outside
|
||||
* of the sphere.
|
||||
*/
|
||||
copy_v3_v3(r_tail_pos, isect_1);
|
||||
|
||||
cur_seg_idx = bp_idx - 2;
|
||||
float prev_seg_len = 0;
|
||||
|
||||
if (cur_seg_idx < 0) {
|
||||
cur_seg_idx = 0;
|
||||
prev_seg_len = 0;
|
||||
}
|
||||
else {
|
||||
prev_seg_len = seg_accum_len[cur_seg_idx];
|
||||
}
|
||||
|
||||
/* Convert the point back into the 0-1 interpolation range. */
|
||||
const float isect_seg_len = len_v3v3(prev_bp->vec, isect_1);
|
||||
const float frac = isect_seg_len / len_v3v3(prev_bp->vec, bp->vec);
|
||||
*r_new_curve_pos = (prev_seg_len + isect_seg_len) / spline_len;
|
||||
|
||||
if (*r_new_curve_pos > 1.0f) {
|
||||
*r_radius = bp->radius;
|
||||
}
|
||||
else {
|
||||
*r_radius = (1.0f - frac) * prev_bp->radius + frac * bp->radius;
|
||||
}
|
||||
|
||||
return cur_seg_idx;
|
||||
}
|
||||
|
||||
/* Evaluate spline IK for a given bone. */
|
||||
static void splineik_evaluate_bone(
|
||||
tSplineIK_Tree *tree, Object *ob, bPoseChannel *pchan, int index, tSplineIk_EvalState *state)
|
||||
{
|
||||
bSplineIKConstraint *ikData = tree->ikData;
|
||||
float origHead[3], origTail[3], poseHead[3], poseTail[3], basePoseMat[3][3], poseMat[3][3];
|
||||
float splineVec[3], scaleFac, radius = 1.0f;
|
||||
float tailBlendFac = 0.0f;
|
||||
bSplineIKConstraint *ik_data = tree->ik_data;
|
||||
|
||||
mul_v3_m4v3(poseHead, state->locrot_offset, pchan->pose_head);
|
||||
mul_v3_m4v3(poseTail, state->locrot_offset, pchan->pose_tail);
|
||||
if (pchan->bone->length == 0.0f) {
|
||||
/* Only move the bone position with zero length bones. */
|
||||
float bone_pos[4], dir[3], rad;
|
||||
BKE_where_on_path(ik_data->tar, state->curve_position, bone_pos, dir, NULL, &rad, NULL);
|
||||
|
||||
copy_v3_v3(origHead, poseHead);
|
||||
apply_curve_transform(ik_data, ob, rad, bone_pos, &rad);
|
||||
|
||||
/* first, adjust the point positions on the curve */
|
||||
float curveLen = tree->points[index] - tree->points[index + 1];
|
||||
float pointStart = state->curve_position;
|
||||
float poseScale = len_v3v3(poseHead, poseTail) / pchan->bone->length;
|
||||
float baseScale = 1.0f;
|
||||
|
||||
if (ikData->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) {
|
||||
/* Carry over the bone Y scale to the curve range. */
|
||||
baseScale = poseScale;
|
||||
copy_v3_v3(pchan->pose_mat[3], bone_pos);
|
||||
copy_v3_v3(pchan->pose_head, bone_pos);
|
||||
copy_v3_v3(pchan->pose_tail, bone_pos);
|
||||
pchan->flag |= POSE_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
float pointEnd = pointStart + curveLen * baseScale * state->curve_scale;
|
||||
float orig_head[3], orig_tail[3], pose_head[3], pose_tail[3];
|
||||
float base_pose_mat[3][3], pose_mat[3][3];
|
||||
float spline_vec[3], scale_fac, radius = 1.0f;
|
||||
float tail_blend_fac = 0.0f;
|
||||
|
||||
state->curve_position = pointEnd;
|
||||
mul_v3_m4v3(pose_head, state->locrot_offset, pchan->pose_head);
|
||||
mul_v3_m4v3(pose_tail, state->locrot_offset, pchan->pose_tail);
|
||||
|
||||
/* step 1: determine the positions for the endpoints of the bone */
|
||||
if (pointStart < 1.0f) {
|
||||
copy_v3_v3(orig_head, pose_head);
|
||||
|
||||
/* First, adjust the point positions on the curve. */
|
||||
float curveLen = tree->points[index] - tree->points[index + 1];
|
||||
float bone_len = len_v3v3(pose_head, pose_tail);
|
||||
float point_start = state->curve_position;
|
||||
float pose_scale = bone_len / pchan->bone->length;
|
||||
float base_scale = 1.0f;
|
||||
|
||||
if (ik_data->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) {
|
||||
/* Carry over the bone Y scale to the curve range. */
|
||||
base_scale = pose_scale;
|
||||
}
|
||||
|
||||
float point_end = point_start + curveLen * base_scale * state->curve_scale;
|
||||
|
||||
state->curve_position = point_end;
|
||||
|
||||
/* Step 1: determine the positions for the endpoints of the bone. */
|
||||
if (point_start < 1.0f) {
|
||||
float vec[4], dir[3], rad;
|
||||
radius = 0.0f;
|
||||
|
||||
/* determine if the bone should still be affected by SplineIK */
|
||||
if (pointEnd >= 1.0f) {
|
||||
/* blending factor depends on the amount of the bone still left on the chain */
|
||||
tailBlendFac = (1.0f - pointStart) / (pointEnd - pointStart);
|
||||
/* Calculate head position. */
|
||||
if (point_start == 0.0f) {
|
||||
/* Start of the path. We have no previous tail position to copy. */
|
||||
BKE_where_on_path(ik_data->tar, point_start, vec, dir, NULL, &rad, NULL);
|
||||
}
|
||||
else {
|
||||
tailBlendFac = 1.0f;
|
||||
copy_v3_v3(vec, state->prev_tail_loc);
|
||||
rad = state->prev_tail_radius;
|
||||
}
|
||||
|
||||
/* tail endpoint */
|
||||
if (where_on_path(ikData->tar, pointEnd, vec, dir, NULL, &rad, NULL)) {
|
||||
/* apply curve's object-mode transforms to the position
|
||||
* unless the option to allow curve to be positioned elsewhere is activated (i.e. no root)
|
||||
*/
|
||||
if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) {
|
||||
mul_m4_v3(ikData->tar->obmat, vec);
|
||||
radius = rad;
|
||||
copy_v3_v3(pose_head, vec);
|
||||
apply_curve_transform(ik_data, ob, rad, pose_head, &radius);
|
||||
|
||||
/* Calculate tail position. */
|
||||
if (ik_data->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) {
|
||||
float sphere_radius;
|
||||
|
||||
if (ik_data->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) {
|
||||
sphere_radius = bone_len;
|
||||
}
|
||||
else {
|
||||
/* Don't take bone scale into account. */
|
||||
sphere_radius = pchan->bone->length;
|
||||
}
|
||||
|
||||
/* convert the position to pose-space, then store it */
|
||||
mul_m4_v3(ob->imat, vec);
|
||||
copy_v3_v3(poseTail, vec);
|
||||
/* Calculate the tail position with sphere curve intersection. */
|
||||
state->prev_tail_seg_idx = position_tail_on_spline(
|
||||
ik_data, vec, sphere_radius, state->prev_tail_seg_idx, pose_tail, &point_end, &rad);
|
||||
|
||||
/* set the new radius */
|
||||
radius = rad;
|
||||
state->prev_tail_radius = rad;
|
||||
copy_v3_v3(state->prev_tail_loc, pose_tail);
|
||||
|
||||
apply_curve_transform(ik_data, ob, rad, pose_tail, &radius);
|
||||
state->curve_position = point_end;
|
||||
}
|
||||
else {
|
||||
/* Scale to fit curve end position. */
|
||||
if (BKE_where_on_path(ik_data->tar, point_end, vec, dir, NULL, &rad, NULL)) {
|
||||
state->prev_tail_radius = rad;
|
||||
copy_v3_v3(state->prev_tail_loc, vec);
|
||||
copy_v3_v3(pose_tail, vec);
|
||||
apply_curve_transform(ik_data, ob, rad, pose_tail, &radius);
|
||||
}
|
||||
}
|
||||
|
||||
/* head endpoint */
|
||||
if (where_on_path(ikData->tar, pointStart, vec, dir, NULL, &rad, NULL)) {
|
||||
/* apply curve's object-mode transforms to the position
|
||||
* unless the option to allow curve to be positioned elsewhere is activated (i.e. no root)
|
||||
*/
|
||||
if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) {
|
||||
mul_m4_v3(ikData->tar->obmat, vec);
|
||||
}
|
||||
|
||||
/* store the position, and convert it to pose space */
|
||||
mul_m4_v3(ob->imat, vec);
|
||||
copy_v3_v3(poseHead, vec);
|
||||
|
||||
/* set the new radius (it should be the average value) */
|
||||
radius = (radius + rad) / 2;
|
||||
/* Determine if the bone should still be affected by SplineIK.
|
||||
* This makes it so that the bone slowly becomes poseable again the further it rolls off the
|
||||
* curve. When the whole bone has rolled off the curve, the IK constraint will not influence it
|
||||
* anymore.
|
||||
*/
|
||||
if (point_end >= 1.0f) {
|
||||
/* Blending factor depends on the amount of the bone still left on the chain. */
|
||||
tail_blend_fac = (1.0f - point_start) / (point_end - point_start);
|
||||
}
|
||||
else {
|
||||
tail_blend_fac = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,11 +489,8 @@ static void splineik_evaluate_bone(
|
||||
* - splineVec: the vector direction that the spline applies on the bone.
|
||||
* - scaleFac: the factor that the bone length is scaled by to get the desired amount.
|
||||
*/
|
||||
sub_v3_v3v3(splineVec, poseTail, poseHead);
|
||||
scaleFac = len_v3(splineVec) / pchan->bone->length;
|
||||
|
||||
/* Extrapolate the full length of the bone as it rolls off the end of the curve. */
|
||||
scaleFac = (tailBlendFac < 1e-5f) ? baseScale : scaleFac / tailBlendFac;
|
||||
sub_v3_v3v3(spline_vec, pose_tail, pose_head);
|
||||
scale_fac = len_v3(spline_vec) / pchan->bone->length;
|
||||
|
||||
/* Step 3: compute the shortest rotation needed
|
||||
* to map from the bone rotation to the current axis.
|
||||
@@ -350,83 +501,83 @@ static void splineik_evaluate_bone(
|
||||
float dmat[3][3], rmat[3][3];
|
||||
float raxis[3], rangle;
|
||||
|
||||
/* compute the raw rotation matrix from the bone's current matrix by extracting only the
|
||||
* orientation-relevant axes, and normalizing them
|
||||
/* Compute the raw rotation matrix from the bone's current matrix by extracting only the
|
||||
* orientation-relevant axes, and normalizing them.
|
||||
*/
|
||||
mul_m3_m4m4(basePoseMat, state->locrot_offset, pchan->pose_mat);
|
||||
normalize_m3_m3(rmat, basePoseMat);
|
||||
mul_m3_m4m4(base_pose_mat, state->locrot_offset, pchan->pose_mat);
|
||||
normalize_m3_m3(rmat, base_pose_mat);
|
||||
|
||||
/* Also, normalize the orientation imposed by the bone,
|
||||
* now that we've extracted the scale factor. */
|
||||
normalize_v3(splineVec);
|
||||
normalize_v3(spline_vec);
|
||||
|
||||
/* calculate smallest axis-angle rotation necessary for getting from the
|
||||
* current orientation of the bone, to the spline-imposed direction
|
||||
/* Calculate smallest axis-angle rotation necessary for getting from the
|
||||
* current orientation of the bone, to the spline-imposed direction.
|
||||
*/
|
||||
cross_v3_v3v3(raxis, rmat[1], splineVec);
|
||||
cross_v3_v3v3(raxis, rmat[1], spline_vec);
|
||||
|
||||
rangle = dot_v3v3(rmat[1], splineVec);
|
||||
rangle = dot_v3v3(rmat[1], spline_vec);
|
||||
CLAMP(rangle, -1.0f, 1.0f);
|
||||
rangle = acosf(rangle);
|
||||
|
||||
/* multiply the magnitude of the angle by the influence of the constraint to
|
||||
* control the influence of the SplineIK effect
|
||||
/* Multiply the magnitude of the angle by the influence of the constraint to
|
||||
* control the influence of the SplineIK effect.
|
||||
*/
|
||||
rangle *= tree->con->enforce * tailBlendFac;
|
||||
rangle *= tree->con->enforce * tail_blend_fac;
|
||||
|
||||
/* construct rotation matrix from the axis-angle rotation found above
|
||||
* - this call takes care to make sure that the axis provided is a unit vector first
|
||||
/* Construct rotation matrix from the axis-angle rotation found above.
|
||||
* - This call takes care to make sure that the axis provided is a unit vector first.
|
||||
*/
|
||||
axis_angle_to_mat3(dmat, raxis, rangle);
|
||||
|
||||
/* Combine these rotations so that the y-axis of the bone is now aligned as the
|
||||
* spline dictates, while still maintaining roll control from the existing bone animation. */
|
||||
mul_m3_m3m3(poseMat, dmat, rmat);
|
||||
mul_m3_m3m3(pose_mat, dmat, rmat);
|
||||
|
||||
/* attempt to reduce shearing, though I doubt this'll really help too much now... */
|
||||
normalize_m3(poseMat);
|
||||
/* Attempt to reduce shearing, though I doubt this'll really help too much now... */
|
||||
normalize_m3(pose_mat);
|
||||
|
||||
mul_m3_m3m3(basePoseMat, dmat, basePoseMat);
|
||||
mul_m3_m3m3(base_pose_mat, dmat, base_pose_mat);
|
||||
|
||||
/* apply rotation to the accumulated parent transform */
|
||||
/* Apply rotation to the accumulated parent transform. */
|
||||
mul_m4_m3m4(state->locrot_offset, dmat, state->locrot_offset);
|
||||
}
|
||||
|
||||
/* step 4: set the scaling factors for the axes */
|
||||
/* Step 4: Set the scaling factors for the axes. */
|
||||
|
||||
/* Always multiply the y-axis by the scaling factor to get the correct length. */
|
||||
mul_v3_fl(poseMat[1], scaleFac);
|
||||
mul_v3_fl(pose_mat[1], scale_fac);
|
||||
|
||||
/* After that, apply x/z scaling modes. */
|
||||
if (ikData->xzScaleMode != CONSTRAINT_SPLINEIK_XZS_NONE) {
|
||||
if (ik_data->xzScaleMode != CONSTRAINT_SPLINEIK_XZS_NONE) {
|
||||
/* First, apply the original scale if enabled. */
|
||||
if (ikData->xzScaleMode == CONSTRAINT_SPLINEIK_XZS_ORIGINAL ||
|
||||
(ikData->flag & CONSTRAINT_SPLINEIK_USE_ORIGINAL_SCALE) != 0) {
|
||||
if (ik_data->xzScaleMode == CONSTRAINT_SPLINEIK_XZS_ORIGINAL ||
|
||||
(ik_data->flag & CONSTRAINT_SPLINEIK_USE_ORIGINAL_SCALE) != 0) {
|
||||
float scale;
|
||||
|
||||
/* x-axis scale */
|
||||
/* X-axis scale. */
|
||||
scale = len_v3(pchan->pose_mat[0]);
|
||||
mul_v3_fl(poseMat[0], scale);
|
||||
/* z-axis scale */
|
||||
mul_v3_fl(pose_mat[0], scale);
|
||||
/* Z-axis scale. */
|
||||
scale = len_v3(pchan->pose_mat[2]);
|
||||
mul_v3_fl(poseMat[2], scale);
|
||||
mul_v3_fl(pose_mat[2], scale);
|
||||
|
||||
/* Adjust the scale factor used for volume preservation
|
||||
* to consider the pre-IK scaling as the initial volume. */
|
||||
scaleFac /= poseScale;
|
||||
scale_fac /= pose_scale;
|
||||
}
|
||||
|
||||
/* Apply volume preservation. */
|
||||
switch (ikData->xzScaleMode) {
|
||||
switch (ik_data->xzScaleMode) {
|
||||
case CONSTRAINT_SPLINEIK_XZS_INVERSE: {
|
||||
/* old 'volume preservation' method using the inverse scale */
|
||||
/* Old 'volume preservation' method using the inverse scale. */
|
||||
float scale;
|
||||
|
||||
/* calculate volume preservation factor which is
|
||||
* basically the inverse of the y-scaling factor
|
||||
/* Calculate volume preservation factor which is
|
||||
* basically the inverse of the y-scaling factor.
|
||||
*/
|
||||
if (fabsf(scaleFac) != 0.0f) {
|
||||
scale = 1.0f / fabsf(scaleFac);
|
||||
if (fabsf(scale_fac) != 0.0f) {
|
||||
scale = 1.0f / fabsf(scale_fac);
|
||||
|
||||
/* We need to clamp this within sensible values. */
|
||||
/* NOTE: these should be fine for now, but should get sanitized in future. */
|
||||
@@ -436,56 +587,56 @@ static void splineik_evaluate_bone(
|
||||
scale = 1.0f;
|
||||
}
|
||||
|
||||
/* apply the scaling */
|
||||
mul_v3_fl(poseMat[0], scale);
|
||||
mul_v3_fl(poseMat[2], scale);
|
||||
/* Apply the scaling. */
|
||||
mul_v3_fl(pose_mat[0], scale);
|
||||
mul_v3_fl(pose_mat[2], scale);
|
||||
break;
|
||||
}
|
||||
case CONSTRAINT_SPLINEIK_XZS_VOLUMETRIC: {
|
||||
/* improved volume preservation based on the Stretch To constraint */
|
||||
/* Improved volume preservation based on the Stretch To constraint. */
|
||||
float final_scale;
|
||||
|
||||
/* as the basis for volume preservation, we use the inverse scale factor... */
|
||||
if (fabsf(scaleFac) != 0.0f) {
|
||||
/* NOTE: The method here is taken wholesale from the Stretch To constraint */
|
||||
float bulge = powf(1.0f / fabsf(scaleFac), ikData->bulge);
|
||||
/* As the basis for volume preservation, we use the inverse scale factor... */
|
||||
if (fabsf(scale_fac) != 0.0f) {
|
||||
/* NOTE: The method here is taken wholesale from the Stretch To constraint. */
|
||||
float bulge = powf(1.0f / fabsf(scale_fac), ik_data->bulge);
|
||||
|
||||
if (bulge > 1.0f) {
|
||||
if (ikData->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MAX) {
|
||||
float bulge_max = max_ff(ikData->bulge_max, 1.0f);
|
||||
if (ik_data->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MAX) {
|
||||
float bulge_max = max_ff(ik_data->bulge_max, 1.0f);
|
||||
float hard = min_ff(bulge, bulge_max);
|
||||
|
||||
float range = bulge_max - 1.0f;
|
||||
float scale = (range > 0.0f) ? 1.0f / range : 0.0f;
|
||||
float soft = 1.0f + range * atanf((bulge - 1.0f) * scale) / (float)M_PI_2;
|
||||
|
||||
bulge = interpf(soft, hard, ikData->bulge_smooth);
|
||||
bulge = interpf(soft, hard, ik_data->bulge_smooth);
|
||||
}
|
||||
}
|
||||
if (bulge < 1.0f) {
|
||||
if (ikData->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MIN) {
|
||||
float bulge_min = CLAMPIS(ikData->bulge_min, 0.0f, 1.0f);
|
||||
if (ik_data->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MIN) {
|
||||
float bulge_min = CLAMPIS(ik_data->bulge_min, 0.0f, 1.0f);
|
||||
float hard = max_ff(bulge, bulge_min);
|
||||
|
||||
float range = 1.0f - bulge_min;
|
||||
float scale = (range > 0.0f) ? 1.0f / range : 0.0f;
|
||||
float soft = 1.0f - range * atanf((1.0f - bulge) * scale) / (float)M_PI_2;
|
||||
|
||||
bulge = interpf(soft, hard, ikData->bulge_smooth);
|
||||
bulge = interpf(soft, hard, ik_data->bulge_smooth);
|
||||
}
|
||||
}
|
||||
|
||||
/* compute scale factor for xz axes from this value */
|
||||
/* Compute scale factor for xz axes from this value. */
|
||||
final_scale = sqrtf(bulge);
|
||||
}
|
||||
else {
|
||||
/* no scaling, so scale factor is simple */
|
||||
/* No scaling, so scale factor is simple. */
|
||||
final_scale = 1.0f;
|
||||
}
|
||||
|
||||
/* Apply the scaling (assuming normalized scale). */
|
||||
mul_v3_fl(poseMat[0], final_scale);
|
||||
mul_v3_fl(poseMat[2], final_scale);
|
||||
mul_v3_fl(pose_mat[0], final_scale);
|
||||
mul_v3_fl(pose_mat[2], final_scale);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -494,49 +645,49 @@ static void splineik_evaluate_bone(
|
||||
/* Finally, multiply the x and z scaling by the radius of the curve too,
|
||||
* to allow automatic scales to get tweaked still.
|
||||
*/
|
||||
if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_CURVERAD) == 0) {
|
||||
mul_v3_fl(poseMat[0], radius);
|
||||
mul_v3_fl(poseMat[2], radius);
|
||||
if ((ik_data->flag & CONSTRAINT_SPLINEIK_NO_CURVERAD) == 0) {
|
||||
mul_v3_fl(pose_mat[0], radius);
|
||||
mul_v3_fl(pose_mat[2], radius);
|
||||
}
|
||||
|
||||
/* Blend the scaling of the matrix according to the influence. */
|
||||
sub_m3_m3m3(poseMat, poseMat, basePoseMat);
|
||||
madd_m3_m3m3fl(poseMat, basePoseMat, poseMat, tree->con->enforce * tailBlendFac);
|
||||
sub_m3_m3m3(pose_mat, pose_mat, base_pose_mat);
|
||||
madd_m3_m3m3fl(pose_mat, base_pose_mat, pose_mat, tree->con->enforce * tail_blend_fac);
|
||||
|
||||
/* step 5: set the location of the bone in the matrix */
|
||||
if (ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) {
|
||||
/* when the 'no-root' option is affected, the chain can retain
|
||||
* the shape but be moved elsewhere
|
||||
/* Step 5: Set the location of the bone in the matrix. */
|
||||
if (ik_data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) {
|
||||
/* When the 'no-root' option is affected, the chain can retain
|
||||
* the shape but be moved elsewhere.
|
||||
*/
|
||||
copy_v3_v3(poseHead, origHead);
|
||||
copy_v3_v3(pose_head, orig_head);
|
||||
}
|
||||
else if (tree->con->enforce < 1.0f) {
|
||||
/* when the influence is too low
|
||||
* - blend the positions for the 'root' bone
|
||||
* - stick to the parent for any other
|
||||
/* When the influence is too low:
|
||||
* - Blend the positions for the 'root' bone.
|
||||
* - Stick to the parent for any other.
|
||||
*/
|
||||
if (index < tree->chainlen - 1) {
|
||||
copy_v3_v3(poseHead, origHead);
|
||||
copy_v3_v3(pose_head, orig_head);
|
||||
}
|
||||
else {
|
||||
interp_v3_v3v3(poseHead, origHead, poseHead, tree->con->enforce);
|
||||
interp_v3_v3v3(pose_head, orig_head, pose_head, tree->con->enforce);
|
||||
}
|
||||
}
|
||||
|
||||
/* finally, store the new transform */
|
||||
copy_m4_m3(pchan->pose_mat, poseMat);
|
||||
copy_v3_v3(pchan->pose_mat[3], poseHead);
|
||||
copy_v3_v3(pchan->pose_head, poseHead);
|
||||
/* Finally, store the new transform. */
|
||||
copy_m4_m3(pchan->pose_mat, pose_mat);
|
||||
copy_v3_v3(pchan->pose_mat[3], pose_head);
|
||||
copy_v3_v3(pchan->pose_head, pose_head);
|
||||
|
||||
mul_v3_mat3_m4v3(origTail, state->locrot_offset, pchan->pose_tail);
|
||||
mul_v3_mat3_m4v3(orig_tail, state->locrot_offset, pchan->pose_tail);
|
||||
|
||||
/* recalculate tail, as it's now outdated after the head gets adjusted above! */
|
||||
/* Recalculate tail, as it's now outdated after the head gets adjusted above! */
|
||||
BKE_pose_where_is_bone_tail(pchan);
|
||||
|
||||
/* update the offset in the accumulated parent transform */
|
||||
sub_v3_v3v3(state->locrot_offset[3], pchan->pose_tail, origTail);
|
||||
/* Update the offset in the accumulated parent transform. */
|
||||
sub_v3_v3v3(state->locrot_offset[3], pchan->pose_tail, orig_tail);
|
||||
|
||||
/* done! */
|
||||
/* Done! */
|
||||
pchan->flag |= POSE_DONE;
|
||||
}
|
||||
|
||||
@@ -559,8 +710,8 @@ static void splineik_execute_tree(
|
||||
|
||||
if (splineik_evaluate_init(tree, &state)) {
|
||||
/* Walk over each bone in the chain, calculating the effects of spline IK
|
||||
* - the chain is traversed in the opposite order to storage order (i.e. parent to children)
|
||||
* so that dependencies are correct
|
||||
* - the chain is traversed in the opposite order to storage order
|
||||
* (i.e. parent to children) so that dependencies are correct
|
||||
*/
|
||||
for (int i = tree->chainlen - 1; i >= 0; i--) {
|
||||
bPoseChannel *pchan = tree->chain[i];
|
||||
|
@@ -34,7 +34,7 @@
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "NOD_node_tree_multi_function.hh"
|
||||
#include "NOD_type_conversions.hh"
|
||||
|
||||
#include "attribute_access_intern.hh"
|
||||
|
||||
@@ -44,194 +44,10 @@ using blender::float3;
|
||||
using blender::Set;
|
||||
using blender::StringRef;
|
||||
using blender::StringRefNull;
|
||||
using blender::bke::ReadAttributePtr;
|
||||
using blender::bke::WriteAttributePtr;
|
||||
using blender::fn::GMutableSpan;
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Attribute Accessor implementations
|
||||
* \{ */
|
||||
|
||||
ReadAttribute::~ReadAttribute()
|
||||
{
|
||||
if (array_is_temporary_ && array_buffer_ != nullptr) {
|
||||
cpp_type_.destruct_n(array_buffer_, size_);
|
||||
MEM_freeN(array_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
fn::GSpan ReadAttribute::get_span() const
|
||||
{
|
||||
if (size_ == 0) {
|
||||
return fn::GSpan(cpp_type_);
|
||||
}
|
||||
if (array_buffer_ == nullptr) {
|
||||
std::lock_guard lock{span_mutex_};
|
||||
if (array_buffer_ == nullptr) {
|
||||
this->initialize_span();
|
||||
}
|
||||
}
|
||||
return fn::GSpan(cpp_type_, array_buffer_, size_);
|
||||
}
|
||||
|
||||
void ReadAttribute::initialize_span() const
|
||||
{
|
||||
const int element_size = cpp_type_.size();
|
||||
array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__);
|
||||
array_is_temporary_ = true;
|
||||
for (const int i : IndexRange(size_)) {
|
||||
this->get_internal(i, POINTER_OFFSET(array_buffer_, i * element_size));
|
||||
}
|
||||
}
|
||||
|
||||
WriteAttribute::~WriteAttribute()
|
||||
{
|
||||
if (array_should_be_applied_) {
|
||||
CLOG_ERROR(&LOG, "Forgot to call apply_span.");
|
||||
}
|
||||
if (array_is_temporary_ && array_buffer_ != nullptr) {
|
||||
cpp_type_.destruct_n(array_buffer_, size_);
|
||||
MEM_freeN(array_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mutable span that can be modified. When all modifications to the attribute are done,
|
||||
* #apply_span should be called. */
|
||||
fn::GMutableSpan WriteAttribute::get_span()
|
||||
{
|
||||
if (size_ == 0) {
|
||||
return fn::GMutableSpan(cpp_type_);
|
||||
}
|
||||
if (array_buffer_ == nullptr) {
|
||||
this->initialize_span(false);
|
||||
}
|
||||
array_should_be_applied_ = true;
|
||||
return fn::GMutableSpan(cpp_type_, array_buffer_, size_);
|
||||
}
|
||||
|
||||
fn::GMutableSpan WriteAttribute::get_span_for_write_only()
|
||||
{
|
||||
if (size_ == 0) {
|
||||
return fn::GMutableSpan(cpp_type_);
|
||||
}
|
||||
if (array_buffer_ == nullptr) {
|
||||
this->initialize_span(true);
|
||||
}
|
||||
array_should_be_applied_ = true;
|
||||
return fn::GMutableSpan(cpp_type_, array_buffer_, size_);
|
||||
}
|
||||
|
||||
void WriteAttribute::initialize_span(const bool write_only)
|
||||
{
|
||||
const int element_size = cpp_type_.size();
|
||||
array_buffer_ = MEM_mallocN_aligned(element_size * size_, cpp_type_.alignment(), __func__);
|
||||
array_is_temporary_ = true;
|
||||
if (write_only) {
|
||||
/* This does nothing for trivial types, but is necessary for general correctness. */
|
||||
cpp_type_.construct_default_n(array_buffer_, size_);
|
||||
}
|
||||
else {
|
||||
for (const int i : IndexRange(size_)) {
|
||||
this->get(i, POINTER_OFFSET(array_buffer_, i * element_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteAttribute::apply_span()
|
||||
{
|
||||
this->apply_span_if_necessary();
|
||||
array_should_be_applied_ = false;
|
||||
}
|
||||
|
||||
void WriteAttribute::apply_span_if_necessary()
|
||||
{
|
||||
/* Only works when the span has been initialized beforehand. */
|
||||
BLI_assert(array_buffer_ != nullptr);
|
||||
|
||||
const int element_size = cpp_type_.size();
|
||||
for (const int i : IndexRange(size_)) {
|
||||
this->set_internal(i, POINTER_OFFSET(array_buffer_, i * element_size));
|
||||
}
|
||||
}
|
||||
|
||||
/* This is used by the #OutputAttributePtr class. */
|
||||
class TemporaryWriteAttribute final : public WriteAttribute {
|
||||
public:
|
||||
GMutableSpan data;
|
||||
GeometryComponent &component;
|
||||
std::string final_name;
|
||||
|
||||
TemporaryWriteAttribute(AttributeDomain domain,
|
||||
GMutableSpan data,
|
||||
GeometryComponent &component,
|
||||
std::string final_name)
|
||||
: WriteAttribute(domain, data.type(), data.size()),
|
||||
data(data),
|
||||
component(component),
|
||||
final_name(std::move(final_name))
|
||||
{
|
||||
}
|
||||
|
||||
~TemporaryWriteAttribute() override
|
||||
{
|
||||
if (data.data() != nullptr) {
|
||||
cpp_type_.destruct_n(data.data(), data.size());
|
||||
MEM_freeN(data.data());
|
||||
}
|
||||
}
|
||||
|
||||
void get_internal(const int64_t index, void *r_value) const override
|
||||
{
|
||||
data.type().copy_to_uninitialized(data[index], r_value);
|
||||
}
|
||||
|
||||
void set_internal(const int64_t index, const void *value) override
|
||||
{
|
||||
data.type().copy_to_initialized(value, data[index]);
|
||||
}
|
||||
|
||||
void initialize_span(const bool UNUSED(write_only)) override
|
||||
{
|
||||
array_buffer_ = data.data();
|
||||
array_is_temporary_ = false;
|
||||
}
|
||||
|
||||
void apply_span_if_necessary() override
|
||||
{
|
||||
/* Do nothing, because the span contains the attribute itself already. */
|
||||
}
|
||||
};
|
||||
|
||||
class ConvertedReadAttribute final : public ReadAttribute {
|
||||
private:
|
||||
const CPPType &from_type_;
|
||||
const CPPType &to_type_;
|
||||
ReadAttributePtr base_attribute_;
|
||||
const nodes::DataTypeConversions &conversions_;
|
||||
|
||||
public:
|
||||
ConvertedReadAttribute(ReadAttributePtr base_attribute, const CPPType &to_type)
|
||||
: ReadAttribute(base_attribute->domain(), to_type, base_attribute->size()),
|
||||
from_type_(base_attribute->cpp_type()),
|
||||
to_type_(to_type),
|
||||
base_attribute_(std::move(base_attribute)),
|
||||
conversions_(nodes::get_implicit_type_conversions())
|
||||
{
|
||||
}
|
||||
|
||||
void get_internal(const int64_t index, void *r_value) const override
|
||||
{
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer);
|
||||
base_attribute_->get(index, buffer);
|
||||
conversions_.convert(from_type_, to_type_, buffer, r_value);
|
||||
}
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type)
|
||||
{
|
||||
switch (type) {
|
||||
@@ -366,7 +182,17 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains)
|
||||
return highest_priority_domain;
|
||||
}
|
||||
|
||||
ReadAttributePtr BuiltinCustomDataLayerProvider::try_get_for_read(
|
||||
void OutputAttribute::save()
|
||||
{
|
||||
if (optional_span_varray_.has_value()) {
|
||||
optional_span_varray_->save();
|
||||
}
|
||||
if (save_) {
|
||||
save_(*this);
|
||||
}
|
||||
}
|
||||
|
||||
GVArrayPtr BuiltinCustomDataLayerProvider::try_get_for_read(
|
||||
const GeometryComponent &component) const
|
||||
{
|
||||
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
|
||||
@@ -382,7 +208,7 @@ ReadAttributePtr BuiltinCustomDataLayerProvider::try_get_for_read(
|
||||
return as_read_attribute_(data, domain_size);
|
||||
}
|
||||
|
||||
WriteAttributePtr BuiltinCustomDataLayerProvider::try_get_for_write(
|
||||
GVMutableArrayPtr BuiltinCustomDataLayerProvider::try_get_for_write(
|
||||
GeometryComponent &component) const
|
||||
{
|
||||
if (writable_ != Writable) {
|
||||
@@ -461,7 +287,7 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component)
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
ReadAttributePtr CustomDataAttributeProvider::try_get_for_read(
|
||||
ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
|
||||
const GeometryComponent &component, const StringRef attribute_name) const
|
||||
{
|
||||
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
|
||||
@@ -494,7 +320,7 @@ ReadAttributePtr CustomDataAttributeProvider::try_get_for_read(
|
||||
return {};
|
||||
}
|
||||
|
||||
WriteAttributePtr CustomDataAttributeProvider::try_get_for_write(
|
||||
WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
|
||||
GeometryComponent &component, const StringRef attribute_name) const
|
||||
{
|
||||
CustomData *custom_data = custom_data_access_.get_custom_data(component);
|
||||
@@ -593,7 +419,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com
|
||||
return true;
|
||||
}
|
||||
|
||||
ReadAttributePtr NamedLegacyCustomDataProvider::try_get_for_read(
|
||||
ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
|
||||
const GeometryComponent &component, const StringRef attribute_name) const
|
||||
{
|
||||
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
|
||||
@@ -604,14 +430,14 @@ ReadAttributePtr NamedLegacyCustomDataProvider::try_get_for_read(
|
||||
if (layer.type == stored_type_) {
|
||||
if (layer.name == attribute_name) {
|
||||
const int domain_size = component.attribute_domain_size(domain_);
|
||||
return as_read_attribute_(layer.data, domain_size);
|
||||
return {as_read_attribute_(layer.data, domain_size), domain_};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
WriteAttributePtr NamedLegacyCustomDataProvider::try_get_for_write(
|
||||
WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
|
||||
GeometryComponent &component, const StringRef attribute_name) const
|
||||
{
|
||||
CustomData *custom_data = custom_data_access_.get_custom_data(component);
|
||||
@@ -628,7 +454,7 @@ WriteAttributePtr NamedLegacyCustomDataProvider::try_get_for_write(
|
||||
if (data_old != data_new) {
|
||||
custom_data_access_.update_custom_data_pointers(component);
|
||||
}
|
||||
return as_write_attribute_(layer.data, domain_size);
|
||||
return {as_write_attribute_(layer.data, domain_size), domain_};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -706,7 +532,17 @@ int GeometryComponent::attribute_domain_size(const AttributeDomain UNUSED(domain
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReadAttributePtr GeometryComponent::attribute_try_get_for_read(
|
||||
bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_name) const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
if (providers == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return providers->builtin_attribute_providers().contains_as(attribute_name);
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
|
||||
const StringRef attribute_name) const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
@@ -717,11 +553,11 @@ ReadAttributePtr GeometryComponent::attribute_try_get_for_read(
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return builtin_provider->try_get_for_read(*this);
|
||||
return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
|
||||
}
|
||||
for (const DynamicAttributesProvider *dynamic_provider :
|
||||
providers->dynamic_attribute_providers()) {
|
||||
ReadAttributePtr attribute = dynamic_provider->try_get_for_read(*this, attribute_name);
|
||||
ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name);
|
||||
if (attribute) {
|
||||
return attribute;
|
||||
}
|
||||
@@ -729,16 +565,19 @@ ReadAttributePtr GeometryComponent::attribute_try_get_for_read(
|
||||
return {};
|
||||
}
|
||||
|
||||
ReadAttributePtr GeometryComponent::attribute_try_adapt_domain(
|
||||
ReadAttributePtr attribute, const AttributeDomain new_domain) const
|
||||
std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_domain(
|
||||
std::unique_ptr<blender::fn::GVArray> varray,
|
||||
const AttributeDomain from_domain,
|
||||
const AttributeDomain to_domain) const
|
||||
{
|
||||
if (attribute && attribute->domain() == new_domain) {
|
||||
return attribute;
|
||||
if (from_domain == to_domain) {
|
||||
return varray;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
WriteAttributePtr GeometryComponent::attribute_try_get_for_write(const StringRef attribute_name)
|
||||
blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write(
|
||||
const StringRef attribute_name)
|
||||
{
|
||||
using namespace blender::bke;
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
@@ -748,11 +587,11 @@ WriteAttributePtr GeometryComponent::attribute_try_get_for_write(const StringRef
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
|
||||
if (builtin_provider != nullptr) {
|
||||
return builtin_provider->try_get_for_write(*this);
|
||||
return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
|
||||
}
|
||||
for (const DynamicAttributesProvider *dynamic_provider :
|
||||
providers->dynamic_attribute_providers()) {
|
||||
WriteAttributePtr attribute = dynamic_provider->try_get_for_write(*this, attribute_name);
|
||||
WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name);
|
||||
if (attribute) {
|
||||
return attribute;
|
||||
}
|
||||
@@ -812,6 +651,24 @@ bool GeometryComponent::attribute_try_create(const StringRef attribute_name,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef attribute_name)
|
||||
{
|
||||
using namespace blender::bke;
|
||||
if (attribute_name.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
if (providers == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const BuiltinAttributeProvider *builtin_provider =
|
||||
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
|
||||
if (builtin_provider == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return builtin_provider->try_create(*this);
|
||||
}
|
||||
|
||||
Set<std::string> GeometryComponent::attribute_names() const
|
||||
{
|
||||
Set<std::string> attributes;
|
||||
@@ -822,12 +679,16 @@ Set<std::string> GeometryComponent::attribute_names() const
|
||||
return attributes;
|
||||
}
|
||||
|
||||
void GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const
|
||||
/**
|
||||
* \return False if the callback explicitly returned false at any point, otherwise true,
|
||||
* meaning the callback made it all the way through.
|
||||
*/
|
||||
bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
const ComponentAttributeProviders *providers = this->get_attribute_providers();
|
||||
if (providers == nullptr) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Keep track handled attribute names to make sure that we do not return the same name twice. */
|
||||
@@ -838,7 +699,7 @@ void GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
|
||||
if (provider->exists(*this)) {
|
||||
AttributeMetaData meta_data{provider->domain(), provider->data_type()};
|
||||
if (!callback(provider->name(), meta_data)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
handled_attribute_names.add_new(provider->name());
|
||||
}
|
||||
@@ -852,271 +713,260 @@ void GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
|
||||
return true;
|
||||
});
|
||||
if (!continue_loop) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const
|
||||
{
|
||||
ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
if (attribute) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static ReadAttributePtr try_adapt_data_type(ReadAttributePtr attribute,
|
||||
const blender::fn::CPPType &to_type)
|
||||
static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type(
|
||||
std::unique_ptr<blender::fn::GVArray> varray, const blender::fn::CPPType &to_type)
|
||||
{
|
||||
const blender::fn::CPPType &from_type = attribute->cpp_type();
|
||||
if (from_type == to_type) {
|
||||
return attribute;
|
||||
}
|
||||
|
||||
const blender::nodes::DataTypeConversions &conversions =
|
||||
blender::nodes::get_implicit_type_conversions();
|
||||
if (!conversions.is_convertible(from_type, to_type)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::make_unique<blender::bke::ConvertedReadAttribute>(std::move(attribute), to_type);
|
||||
return conversions.try_convert(std::move(varray), to_type);
|
||||
}
|
||||
|
||||
ReadAttributePtr GeometryComponent::attribute_try_get_for_read(
|
||||
std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read(
|
||||
const StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type) const
|
||||
{
|
||||
ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
if (!attribute) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (domain != ATTR_DOMAIN_AUTO && attribute->domain() != domain) {
|
||||
attribute = this->attribute_try_adapt_domain(std::move(attribute), domain);
|
||||
if (!attribute) {
|
||||
std::unique_ptr<blender::fn::GVArray> varray = std::move(attribute.varray);
|
||||
if (domain != ATTR_DOMAIN_AUTO && attribute.domain != domain) {
|
||||
varray = this->attribute_try_adapt_domain(std::move(varray), attribute.domain, domain);
|
||||
if (!varray) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
if (attribute->cpp_type() != *cpp_type) {
|
||||
attribute = try_adapt_data_type(std::move(attribute), *cpp_type);
|
||||
if (!attribute) {
|
||||
if (varray->type() != *cpp_type) {
|
||||
varray = try_adapt_data_type(std::move(varray), *cpp_type);
|
||||
if (!varray) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return attribute;
|
||||
return varray;
|
||||
}
|
||||
|
||||
ReadAttributePtr GeometryComponent::attribute_try_get_for_read(const StringRef attribute_name,
|
||||
const AttributeDomain domain) const
|
||||
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read(
|
||||
const StringRef attribute_name, const AttributeDomain domain) const
|
||||
{
|
||||
if (!this->attribute_domain_supported(domain)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
if (!attribute) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (attribute->domain() != domain) {
|
||||
attribute = this->attribute_try_adapt_domain(std::move(attribute), domain);
|
||||
if (attribute.domain != domain) {
|
||||
return this->attribute_try_adapt_domain(std::move(attribute.varray), attribute.domain, domain);
|
||||
}
|
||||
|
||||
return std::move(attribute.varray);
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const CustomDataType data_type) const
|
||||
{
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
if (!attribute) {
|
||||
return {};
|
||||
}
|
||||
const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
BLI_assert(type != nullptr);
|
||||
if (attribute.varray->type() == *type) {
|
||||
return attribute;
|
||||
}
|
||||
const blender::nodes::DataTypeConversions &conversions =
|
||||
blender::nodes::get_implicit_type_conversions();
|
||||
return {conversions.try_convert(std::move(attribute.varray), *type), attribute.domain};
|
||||
}
|
||||
|
||||
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read(
|
||||
const StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value) const
|
||||
{
|
||||
std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read(
|
||||
attribute_name, domain, data_type);
|
||||
if (varray) {
|
||||
return varray;
|
||||
}
|
||||
const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
if (default_value == nullptr) {
|
||||
default_value = type->default_value();
|
||||
}
|
||||
const int domain_size = this->attribute_domain_size(domain);
|
||||
return std::make_unique<blender::fn::GVArray_For_SingleValue>(*type, domain_size, default_value);
|
||||
}
|
||||
|
||||
class GVMutableAttribute_For_OutputAttribute
|
||||
: public blender::fn::GVMutableArray_For_GMutableSpan {
|
||||
public:
|
||||
GeometryComponent *component;
|
||||
std::string final_name;
|
||||
|
||||
GVMutableAttribute_For_OutputAttribute(GMutableSpan data,
|
||||
GeometryComponent &component,
|
||||
std::string final_name)
|
||||
: blender::fn::GVMutableArray_For_GMutableSpan(data),
|
||||
component(&component),
|
||||
final_name(std::move(final_name))
|
||||
{
|
||||
}
|
||||
|
||||
~GVMutableAttribute_For_OutputAttribute() override
|
||||
{
|
||||
type_->destruct_n(data_, size_);
|
||||
MEM_freeN(data_);
|
||||
}
|
||||
};
|
||||
|
||||
static void save_output_attribute(blender::bke::OutputAttribute &output_attribute)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::fn;
|
||||
using namespace blender::bke;
|
||||
|
||||
GVMutableAttribute_For_OutputAttribute &varray =
|
||||
dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray());
|
||||
|
||||
GeometryComponent &component = *varray.component;
|
||||
const StringRefNull name = varray.final_name;
|
||||
const AttributeDomain domain = output_attribute.domain();
|
||||
const CustomDataType data_type = output_attribute.custom_data_type();
|
||||
const CPPType &cpp_type = output_attribute.cpp_type();
|
||||
|
||||
component.attribute_try_delete(name);
|
||||
if (!component.attribute_try_create(varray.final_name, domain, data_type)) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Could not create the '%s' attribute with type '%s'.",
|
||||
name.c_str(),
|
||||
cpp_type.name().c_str());
|
||||
return;
|
||||
}
|
||||
WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name);
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer);
|
||||
for (const int i : IndexRange(varray.size())) {
|
||||
varray.get(i, buffer);
|
||||
write_attribute.varray->set_by_relocate(i, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static blender::bke::OutputAttribute create_output_attribute(
|
||||
GeometryComponent &component,
|
||||
const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const bool ignore_old_values,
|
||||
const void *default_value)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::fn;
|
||||
using namespace blender::bke;
|
||||
|
||||
if (attribute_name.is_empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const CPPType *cpp_type = custom_data_type_to_cpp_type(data_type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions();
|
||||
|
||||
if (component.attribute_is_builtin(attribute_name)) {
|
||||
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
|
||||
if (!attribute) {
|
||||
component.attribute_try_create_builtin(attribute_name);
|
||||
attribute = component.attribute_try_get_for_write(attribute_name);
|
||||
if (!attribute) {
|
||||
/* Builtin attribute does not exist and can't be created. */
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (attribute.domain != domain) {
|
||||
/* Builtin attribute is on different domain. */
|
||||
return {};
|
||||
}
|
||||
GVMutableArrayPtr varray = std::move(attribute.varray);
|
||||
if (varray->type() == *cpp_type) {
|
||||
/* Builtin attribute matches exactly. */
|
||||
return OutputAttribute(std::move(varray), domain, {}, ignore_old_values);
|
||||
}
|
||||
/* Builtin attribute is on the same domain but has a different data type. */
|
||||
varray = conversions.try_convert(std::move(varray), *cpp_type);
|
||||
return OutputAttribute(std::move(varray), domain, {}, ignore_old_values);
|
||||
}
|
||||
|
||||
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
|
||||
if (!attribute) {
|
||||
component.attribute_try_create(attribute_name, domain, data_type);
|
||||
attribute = component.attribute_try_get_for_write(attribute_name);
|
||||
if (!attribute) {
|
||||
/* Can't create the attribute. */
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
ReadAttributePtr GeometryComponent::attribute_get_for_read(const StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value) const
|
||||
{
|
||||
ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name, domain, data_type);
|
||||
if (attribute) {
|
||||
return attribute;
|
||||
if (attribute.domain == domain && attribute.varray->type() == *cpp_type) {
|
||||
/* Existing generic attribute matches exactly. */
|
||||
return OutputAttribute(std::move(attribute.varray), domain, {}, ignore_old_values);
|
||||
}
|
||||
return this->attribute_get_constant_for_read(domain, data_type, default_value);
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read(
|
||||
const AttributeDomain domain, const CustomDataType data_type, const void *value) const
|
||||
{
|
||||
BLI_assert(this->attribute_domain_supported(domain));
|
||||
const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
if (value == nullptr) {
|
||||
value = cpp_type->default_value();
|
||||
}
|
||||
const int domain_size = this->attribute_domain_size(domain);
|
||||
return std::make_unique<blender::bke::ConstantReadAttribute>(
|
||||
domain, domain_size, *cpp_type, value);
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read_converted(
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType in_data_type,
|
||||
const CustomDataType out_data_type,
|
||||
const void *value) const
|
||||
{
|
||||
BLI_assert(this->attribute_domain_supported(domain));
|
||||
if (value == nullptr || in_data_type == out_data_type) {
|
||||
return this->attribute_get_constant_for_read(domain, out_data_type, value);
|
||||
}
|
||||
|
||||
const blender::fn::CPPType *in_cpp_type = blender::bke::custom_data_type_to_cpp_type(
|
||||
in_data_type);
|
||||
const blender::fn::CPPType *out_cpp_type = blender::bke::custom_data_type_to_cpp_type(
|
||||
out_data_type);
|
||||
BLI_assert(in_cpp_type != nullptr);
|
||||
BLI_assert(out_cpp_type != nullptr);
|
||||
|
||||
const blender::nodes::DataTypeConversions &conversions =
|
||||
blender::nodes::get_implicit_type_conversions();
|
||||
BLI_assert(conversions.is_convertible(*in_cpp_type, *out_cpp_type));
|
||||
|
||||
void *out_value = alloca(out_cpp_type->size());
|
||||
conversions.convert(*in_cpp_type, *out_cpp_type, value, out_value);
|
||||
|
||||
const int domain_size = this->attribute_domain_size(domain);
|
||||
blender::bke::ReadAttributePtr attribute = std::make_unique<blender::bke::ConstantReadAttribute>(
|
||||
domain, domain_size, *out_cpp_type, out_value);
|
||||
|
||||
out_cpp_type->destruct(out_value);
|
||||
return attribute;
|
||||
}
|
||||
|
||||
OutputAttributePtr GeometryComponent::attribute_try_get_for_output(const StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value)
|
||||
{
|
||||
const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
|
||||
WriteAttributePtr attribute = this->attribute_try_get_for_write(attribute_name);
|
||||
|
||||
/* If the attribute doesn't exist, make a new one with the correct type. */
|
||||
if (!attribute) {
|
||||
this->attribute_try_create(attribute_name, domain, data_type);
|
||||
attribute = this->attribute_try_get_for_write(attribute_name);
|
||||
if (attribute && default_value != nullptr) {
|
||||
void *data = attribute->get_span_for_write_only().data();
|
||||
cpp_type->fill_initialized(default_value, data, attribute->size());
|
||||
attribute->apply_span();
|
||||
}
|
||||
return OutputAttributePtr(std::move(attribute));
|
||||
}
|
||||
|
||||
/* If an existing attribute has a matching domain and type, just use that. */
|
||||
if (attribute->domain() == domain && attribute->cpp_type() == *cpp_type) {
|
||||
return OutputAttributePtr(std::move(attribute));
|
||||
}
|
||||
|
||||
/* Otherwise create a temporary buffer to use before saving the new attribute. */
|
||||
return OutputAttributePtr(*this, domain, attribute_name, data_type);
|
||||
}
|
||||
|
||||
/* Construct from an attribute that already exists in the geometry component. */
|
||||
OutputAttributePtr::OutputAttributePtr(WriteAttributePtr attribute)
|
||||
: attribute_(std::move(attribute))
|
||||
{
|
||||
}
|
||||
|
||||
/* Construct a temporary attribute that has to replace an existing one later on. */
|
||||
OutputAttributePtr::OutputAttributePtr(GeometryComponent &component,
|
||||
AttributeDomain domain,
|
||||
std::string final_name,
|
||||
CustomDataType data_type)
|
||||
{
|
||||
const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
BLI_assert(cpp_type != nullptr);
|
||||
|
||||
const int domain_size = component.attribute_domain_size(domain);
|
||||
void *buffer = MEM_malloc_arrayN(domain_size, cpp_type->size(), __func__);
|
||||
GMutableSpan new_span{*cpp_type, buffer, domain_size};
|
||||
|
||||
/* Copy converted values from conflicting attribute, in case the value is read.
|
||||
* TODO: An optimization could be to not do this, when the caller says that the attribute will
|
||||
* only be written. */
|
||||
ReadAttributePtr src_attribute = component.attribute_get_for_read(
|
||||
final_name, domain, data_type, nullptr);
|
||||
for (const int i : blender::IndexRange(domain_size)) {
|
||||
src_attribute->get(i, new_span[i]);
|
||||
/* Allocate a new array that lives next to the existing attribute. It will overwrite the existing
|
||||
* attribute after processing is done. */
|
||||
void *data = MEM_mallocN_aligned(
|
||||
cpp_type->size() * domain_size, cpp_type->alignment(), __func__);
|
||||
if (ignore_old_values) {
|
||||
/* This does nothing for trivially constructible types, but is necessary for correctness. */
|
||||
cpp_type->construct_default_n(data, domain);
|
||||
}
|
||||
else {
|
||||
/* Fill the temporary array with values from the existing attribute. */
|
||||
GVArrayPtr old_varray = component.attribute_get_for_read(
|
||||
attribute_name, domain, data_type, default_value);
|
||||
old_varray->materialize_to_uninitialized(IndexRange(domain_size), data);
|
||||
}
|
||||
GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>(
|
||||
GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name);
|
||||
|
||||
attribute_ = std::make_unique<blender::bke::TemporaryWriteAttribute>(
|
||||
domain, new_span, component, std::move(final_name));
|
||||
return OutputAttribute(std::move(varray), domain, save_output_attribute, true);
|
||||
}
|
||||
|
||||
/* Store the computed attribute. If it was stored from the beginning already, nothing is done. This
|
||||
* might delete another attribute with the same name. */
|
||||
void OutputAttributePtr::save()
|
||||
blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output(
|
||||
const StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type,
|
||||
const void *default_value)
|
||||
{
|
||||
if (!attribute_) {
|
||||
CLOG_WARN(&LOG, "Trying to save an attribute that does not exist anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
blender::bke::TemporaryWriteAttribute *attribute =
|
||||
dynamic_cast<blender::bke::TemporaryWriteAttribute *>(attribute_.get());
|
||||
|
||||
if (attribute == nullptr) {
|
||||
/* The attribute is saved already. */
|
||||
attribute_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
StringRefNull name = attribute->final_name;
|
||||
const blender::fn::CPPType &cpp_type = attribute->cpp_type();
|
||||
|
||||
/* Delete an existing attribute with the same name if necessary. */
|
||||
attribute->component.attribute_try_delete(name);
|
||||
|
||||
if (!attribute->component.attribute_try_create(
|
||||
name, attribute_->domain(), attribute_->custom_data_type())) {
|
||||
/* Cannot create the target attribute for some reason. */
|
||||
CLOG_WARN(&LOG,
|
||||
"Creating the '%s' attribute with type '%s' failed.",
|
||||
name.c_str(),
|
||||
cpp_type.name().c_str());
|
||||
attribute_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
WriteAttributePtr new_attribute = attribute->component.attribute_try_get_for_write(name);
|
||||
|
||||
GMutableSpan temp_span = attribute->data;
|
||||
GMutableSpan new_span = new_attribute->get_span_for_write_only();
|
||||
BLI_assert(temp_span.size() == new_span.size());
|
||||
|
||||
/* Currently we copy over the attribute. In the future we want to reuse the buffer. */
|
||||
cpp_type.move_to_initialized_n(temp_span.data(), new_span.data(), new_span.size());
|
||||
new_attribute->apply_span();
|
||||
|
||||
attribute_.reset();
|
||||
return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value);
|
||||
}
|
||||
|
||||
OutputAttributePtr::~OutputAttributePtr()
|
||||
blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
|
||||
const blender::StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
const CustomDataType data_type)
|
||||
{
|
||||
if (attribute_) {
|
||||
CLOG_ERROR(&LOG, "Forgot to call #save or #apply_span_and_save.");
|
||||
}
|
||||
return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr);
|
||||
}
|
||||
|
||||
/* Utility function to call #apply_span and #save in the right order. */
|
||||
void OutputAttributePtr::apply_span_and_save()
|
||||
{
|
||||
BLI_assert(attribute_);
|
||||
attribute_->apply_span();
|
||||
this->save();
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user