Merge branch 'master' into sculpt-dev

This commit is contained in:
Pablo Dobarro 2021-04-15 20:14:30 +02:00
commit 8189f2c806
181 changed files with 3355 additions and 1521 deletions

View File

@ -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) \

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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) + " %:"

View File

@ -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) + " %:"

View File

@ -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()

View File

@ -923,6 +923,10 @@ function(get_blender_version)
math(EXPR _out_version_major "${_out_version} / 100")
math(EXPR _out_version_minor "${_out_version} % 100")
# Zero pad the minor version so `_out_version_minor` is always two characters.
# This is needed if the minor version is a single digit.
string(REGEX REPLACE "^([0-9])$" "0\\1" _out_version_minor "${_out_version_minor}")
# output vars
set(BLENDER_VERSION "${_out_version_major}.${_out_version_minor}" PARENT_SCOPE)
set(BLENDER_VERSION_MAJOR "${_out_version_major}" PARENT_SCOPE)

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -498,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;

View File

@ -569,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)
@ -580,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());

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
////////////////////////////////////////////////////////////////////////

View File

@ -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;

@ -1 +1 @@
Subproject commit 2cef4877edc40875978c4e95322bb5193f5815bf
Subproject commit f7b706dd6434db2d752f47c4b8c3148b2990fd73

Binary file not shown.

@ -1 +1 @@
Subproject commit bcd08a9506d33bdd7358201031b04d041ef22d94
Subproject commit 81815ea92c2071a08566dc66d4a871b6e2f5c868

@ -1 +1 @@
Subproject commit f948f658ba33eb670a65e0bba058d43138abea7e
Subproject commit 8970953d4a8a4ea3bf77c66370c817ed0cf1308a

View File

@ -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), ("flash_object", True)]}),
# Brush strokes
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("mode", 'NORMAL')]}),
@ -4595,8 +4596,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)],)]}),

View File

@ -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:

View File

@ -306,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,
@ -314,4 +370,5 @@ classes = (
NODE_OT_add_search,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_tree_path_parent,
NODE_OT_active_preview_toggle,
)

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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,

View File

@ -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")),

View File

@ -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()
@ -3040,9 +3040,16 @@ class VIEW3D_MT_sculpt(Menu):
layout.separator()
props = layout.operator("object.transfer_mode", text="Transfer Sculpt Mode")
props.use_eyedropper = True
props.flash_object = False
layout.separator()
layout.operator("sculpt.reset_brushes")
class VIEW3D_MT_mask(Menu):
bl_label = "Mask"
@ -3094,19 +3101,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()
@ -3160,6 +3163,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()

View File

@ -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 17
#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

View File

@ -40,7 +40,6 @@ struct MDeformVert;
struct Main;
struct Nurb;
struct Object;
struct Path;
struct TextBox;
struct rctf;

View File

@ -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);

View File

@ -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,

View File

@ -70,7 +70,9 @@ 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_set_preview_geometry_set(struct Object *ob, struct GeometrySet *geometry_set);
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,

View File

@ -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,21 +213,24 @@ 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->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 */
const float splineLen = BKE_anim_path_get_length(cache);
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 (BKE_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 (BKE_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];

View File

@ -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"
@ -210,23 +210,25 @@ class ConvertedReadAttribute final : public ReadAttribute {
const CPPType &from_type_;
const CPPType &to_type_;
ReadAttributePtr base_attribute_;
const nodes::DataTypeConversions &conversions_;
void (*convert_)(const void *src, void *dst);
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())
base_attribute_(std::move(base_attribute))
{
const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions();
convert_ = conversions.get_conversion_functions(base_attribute_->cpp_type(), to_type)
->convert_single_to_uninitialized;
}
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);
convert_(buffer, r_value);
}
};
@ -989,7 +991,7 @@ blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_rea
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);
conversions.convert_to_uninitialized(*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>(

View File

@ -132,7 +132,7 @@ static void blender_version_init(void)
BLI_snprintf(blender_version_string,
ARRAY_SIZE(blender_version_string),
"%d.%02d.%d%s",
"%d.%01d.%d%s",
BLENDER_VERSION / 100,
BLENDER_VERSION % 100,
BLENDER_VERSION_PATCH,

View File

@ -414,6 +414,7 @@ void BKE_curve_init(Curve *cu, const short curve_type)
cu->tb[0].w = cu->tb[0].h = 0.0;
}
else if (cu->type == OB_SURF) {
cu->flag |= CU_3D;
cu->resolv = 4;
}
cu->bevel_profile = NULL;

View File

@ -814,14 +814,16 @@ static void set_loop_uv(MLoopUV &uv, const float2 &co)
static Color4f get_loop_color(const MLoopCol &col)
{
Color4f value;
rgba_uchar_to_float(value, &col.r);
return value;
Color4f srgb_color;
rgba_uchar_to_float(srgb_color, &col.r);
Color4f linear_color;
srgb_to_linearrgb_v4(linear_color, srgb_color);
return linear_color;
}
static void set_loop_color(MLoopCol &col, const Color4f &value)
static void set_loop_color(MLoopCol &col, const Color4f &linear_color)
{
rgba_float_to_uchar(&col.r, value);
linearrgb_to_srgb_uchar4(&col.r, linear_color);
}
static float get_crease(const MEdge &edge)

View File

@ -2007,7 +2007,8 @@ bNode *nodeAddStaticNode(const struct bContext *C, bNodeTree *ntree, int type)
/* do an extra poll here, because some int types are used
* for multiple node types, this helps find the desired type
*/
if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree))) {
const char *disabled_hint;
if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree, &disabled_hint))) {
idname = ntype->idname;
break;
}
@ -4407,15 +4408,17 @@ static void node_type_base_defaults(bNodeType *ntype)
}
/* allow this node for any tree type */
static bool node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(ntree))
static bool node_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *UNUSED(ntree),
const char **UNUSED(disabled_hint))
{
return true;
}
/* use the basic poll function */
static bool node_poll_instance_default(bNode *node, bNodeTree *ntree)
static bool node_poll_instance_default(bNode *node, bNodeTree *ntree, const char **disabled_hint)
{
return node->typeinfo->poll(node->typeinfo, ntree);
return node->typeinfo->poll(node->typeinfo, ntree, disabled_hint);
}
/* NOLINTNEXTLINE: readability-function-size */
@ -4634,7 +4637,9 @@ void node_type_internal_links(bNodeType *ntype,
/* callbacks for undefined types */
static bool node_undefined_poll(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(nodetree))
static bool node_undefined_poll(bNodeType *UNUSED(ntype),
bNodeTree *UNUSED(nodetree),
const char **UNUSED(r_disabled_hint))
{
/* this type can not be added deliberately, it's just a placeholder */
return false;

View File

@ -163,6 +163,6 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
const CustomDataType data_type)
{
NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node);
node_ui_storage.attribute_hints.add_as(attribute_name,
AvailableAttributeInfo{domain, data_type});
node_ui_storage.attribute_hints.add_as(
AvailableAttributeInfo{attribute_name, domain, data_type});
}

View File

@ -1761,9 +1761,9 @@ void BKE_object_free_derived_caches(Object *ob)
BKE_geometry_set_free(ob->runtime.geometry_set_eval);
ob->runtime.geometry_set_eval = NULL;
}
if (ob->runtime.geometry_set_preview != NULL) {
BKE_geometry_set_free(ob->runtime.geometry_set_preview);
ob->runtime.geometry_set_preview = NULL;
if (ob->runtime.geometry_set_previews != NULL) {
BLI_ghash_free(ob->runtime.geometry_set_previews, NULL, (GHashValFreeFP)BKE_geometry_set_free);
ob->runtime.geometry_set_previews = NULL;
}
}
@ -1816,14 +1816,20 @@ void BKE_object_free_caches(Object *object)
}
/* Can be called from multiple threads. */
void BKE_object_set_preview_geometry_set(Object *ob, struct GeometrySet *geometry_set)
void BKE_object_preview_geometry_set_add(Object *ob,
const uint64_t key,
struct GeometrySet *geometry_set)
{
static ThreadMutex mutex = BLI_MUTEX_INITIALIZER;
BLI_mutex_lock(&mutex);
if (ob->runtime.geometry_set_preview != NULL) {
BKE_geometry_set_free(ob->runtime.geometry_set_preview);
if (ob->runtime.geometry_set_previews == NULL) {
ob->runtime.geometry_set_previews = BLI_ghash_int_new(__func__);
}
ob->runtime.geometry_set_preview = geometry_set;
BLI_ghash_reinsert(ob->runtime.geometry_set_previews,
POINTER_FROM_UINT(key),
geometry_set,
NULL,
(GHashValFreeFP)BKE_geometry_set_free);
BLI_mutex_unlock(&mutex);
}

View File

@ -30,7 +30,8 @@
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "BLI_alloca.h"
#include "BLI_array.hh"
#include "BLI_float3.hh"
#include "BLI_float4x4.hh"
#include "BLI_math.h"
#include "BLI_rand.h"
@ -68,6 +69,8 @@
#include "BLI_hash.h"
#include "BLI_strict_flags.h"
using blender::Array;
using blender::float3;
using blender::float4x4;
using blender::Span;
@ -912,40 +915,37 @@ struct FaceDupliData_EditMesh {
const float (*vert_coords)[3];
};
static void get_dupliface_transform_from_coords(const float coords[][3],
const int coords_len,
static void get_dupliface_transform_from_coords(Span<float3> coords,
const bool use_scale,
const float scale_fac,
float r_mat[4][4])
{
float loc[3], quat[4], scale, size[3];
/* Location. */
{
const float w = 1.0f / (float)coords_len;
zero_v3(loc);
for (int i = 0; i < coords_len; i++) {
madd_v3_v3fl(loc, coords[i], w);
}
float3 location(0);
for (const float3 &coord : coords) {
location += coord;
}
location *= 1.0f / (float)coords.size();
/* Rotation. */
{
float f_no[3];
cross_poly_v3(f_no, coords, (uint)coords_len);
normalize_v3(f_no);
tri_to_quat_ex(quat, coords[0], coords[1], coords[2], f_no);
}
float quat[4];
float3 f_no;
cross_poly_v3(f_no, (const float(*)[3])coords.data(), (uint)coords.size());
f_no.normalize();
tri_to_quat_ex(quat, coords[0], coords[1], coords[2], f_no);
/* Scale. */
float scale;
if (use_scale) {
const float area = area_poly_v3(coords, (uint)coords_len);
const float area = area_poly_v3((const float(*)[3])coords.data(), (uint)coords.size());
scale = sqrtf(area) * scale_fac;
}
else {
scale = 1.0f;
}
size[0] = size[1] = size[2] = scale;
loc_quat_size_to_mat4(r_mat, loc, quat, size);
loc_quat_size_to_mat4(r_mat, location, quat, float3(scale));
}
static DupliObject *face_dupli(const DupliContext *ctx,
@ -954,14 +954,13 @@ static DupliObject *face_dupli(const DupliContext *ctx,
const int index,
const bool use_scale,
const float scale_fac,
const float (*coords)[3],
const int coords_len)
Span<float3> coords)
{
float obmat[4][4];
float space_mat[4][4];
/* `obmat` is transform to face. */
get_dupliface_transform_from_coords(coords, coords_len, use_scale, scale_fac, obmat);
get_dupliface_transform_from_coords(coords, use_scale, scale_fac, obmat);
/* Make offset relative to inst_ob using relative child transform. */
mul_mat3_m4_v3(child_imat, obmat[3]);
@ -989,7 +988,6 @@ static DupliObject *face_dupli(const DupliContext *ctx,
return dob;
}
/** Wrap #face_dupli, needed since we can't #alloca in a loop. */
static DupliObject *face_dupli_from_mesh(const DupliContext *ctx,
Object *inst_ob,
const float child_imat[4][4],
@ -1003,17 +1001,16 @@ static DupliObject *face_dupli_from_mesh(const DupliContext *ctx,
const MVert *mvert)
{
const int coords_len = mpoly->totloop;
float(*coords)[3] = (float(*)[3])BLI_array_alloca(coords, (size_t)coords_len);
Array<float3, 64> coords(coords_len);
const MLoop *ml = mloopstart;
for (int i = 0; i < coords_len; i++, ml++) {
copy_v3_v3(coords[i], mvert[ml->v].co);
coords[i] = float3(mvert[ml->v].co);
}
return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords, coords_len);
return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords);
}
/** Wrap #face_dupli, needed since we can't #alloca in a loop. */
static DupliObject *face_dupli_from_editmesh(const DupliContext *ctx,
Object *inst_ob,
const float child_imat[4][4],
@ -1026,7 +1023,7 @@ static DupliObject *face_dupli_from_editmesh(const DupliContext *ctx,
const float (*vert_coords)[3])
{
const int coords_len = f->len;
float(*coords)[3] = (float(*)[3])BLI_array_alloca(coords, (size_t)coords_len);
Array<float3, 64> coords(coords_len);
BMLoop *l_first, *l_iter;
int i = 0;
@ -1042,7 +1039,7 @@ static DupliObject *face_dupli_from_editmesh(const DupliContext *ctx,
} while ((l_iter = l_iter->next) != l_first);
}
return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords, coords_len);
return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords);
}
static void make_child_duplis_faces_from_mesh(const DupliContext *ctx,

View File

@ -227,7 +227,12 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
case SPACE_SPREADSHEET: {
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
BKE_LIB_FOREACHID_PROCESS_ID(data, sspreadsheet->pinned_id, IDWALK_CB_NOP);
LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) {
if (context->type == SPREADSHEET_CONTEXT_OBJECT) {
BKE_LIB_FOREACHID_PROCESS(
data, ((SpreadsheetContextObject *)context)->object, IDWALK_CB_NOP);
}
}
break;
}
default:
@ -1357,6 +1362,27 @@ static void write_area(BlendWriter *writer, ScrArea *area)
BLO_write_struct(writer, SpreadsheetColumnID, column->id);
BLO_write_string(writer, column->id->name);
}
LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) {
switch (context->type) {
case SPREADSHEET_CONTEXT_OBJECT: {
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context;
BLO_write_struct(writer, SpreadsheetContextObject, object_context);
break;
}
case SPREADSHEET_CONTEXT_MODIFIER: {
SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context;
BLO_write_struct(writer, SpreadsheetContextModifier, modifier_context);
BLO_write_string(writer, modifier_context->modifier_name);
break;
}
case SPREADSHEET_CONTEXT_NODE: {
SpreadsheetContextNode *node_context = (SpreadsheetContextNode *)context;
BLO_write_struct(writer, SpreadsheetContextNode, node_context);
BLO_write_string(writer, node_context->node_name);
break;
}
}
}
}
}
}
@ -1715,6 +1741,25 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
BLO_read_data_address(reader, &column->id);
BLO_read_data_address(reader, &column->id->name);
}
BLO_read_list(reader, &sspreadsheet->context_path);
LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) {
switch (context->type) {
case SPREADSHEET_CONTEXT_NODE: {
SpreadsheetContextNode *node_context = (SpreadsheetContextNode *)context;
BLO_read_data_address(reader, &node_context->node_name);
break;
}
case SPREADSHEET_CONTEXT_MODIFIER: {
SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context;
BLO_read_data_address(reader, &modifier_context->modifier_name);
break;
}
case SPREADSHEET_CONTEXT_OBJECT: {
break;
}
}
}
}
}
@ -1931,7 +1976,12 @@ void BKE_screen_area_blend_read_lib(BlendLibReader *reader, ID *parent_id, ScrAr
}
case SPACE_SPREADSHEET: {
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
BLO_read_id_address(reader, parent_id->lib, &sspreadsheet->pinned_id);
LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) {
if (context->type == SPREADSHEET_CONTEXT_OBJECT) {
BLO_read_id_address(
reader, parent_id->lib, &((SpreadsheetContextObject *)context)->object);
}
}
break;
}
default:

View File

@ -73,6 +73,12 @@ template<typename Key, typename Value> class MultiValueMap {
vector.append(std::forward<ForwardValue>(value));
}
void add_non_duplicates(const Key &key, const Value &value)
{
Vector<Value> &vector = map_.lookup_or_add_default_as(key);
vector.append_non_duplicates(value);
}
/**
* Add all given values to the key.
*/

View File

@ -3010,8 +3010,13 @@ static void lib_link_workspace_layout_restore(struct IDNameLib_Map *id_map,
else if (sl->spacetype == SPACE_SPREADSHEET) {
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
sspreadsheet->pinned_id = restore_pointer_by_name(
id_map, sspreadsheet->pinned_id, USER_IGNORE);
LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) {
if (context->type == SPREADSHEET_CONTEXT_OBJECT) {
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context;
object_context->object = restore_pointer_by_name(
id_map, (ID *)object_context->object, USER_IGNORE);
}
}
}
}
}

View File

@ -2010,30 +2010,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - "versioning_userdef.c", #blo_do_versions_userdef
* - "versioning_userdef.c", #do_versions_theme
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
LISTBASE_FOREACH (Brush *, br, &bmain->brushes) {
BKE_brush_default_input_curves_set(br);
}
if (!DNA_struct_elem_find(fd->filesdna, "Sculpt", "float", "smooth_strength_factor")) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
Sculpt *sd = scene->toolsettings->sculpt;
if (sd) {
sd->smooth_strength_factor = 1.0f;
}
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 293, 18)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
version_node_socket_name(ntree, GEO_NODE_VOLUME_TO_MESH, "Grid", "Density");
@ -2067,5 +2044,44 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
}
/* Consolidate node and final evaluation modes. */
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
if (sl->spacetype == SPACE_SPREADSHEET) {
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
if (sspreadsheet->object_eval_state == 2) {
sspreadsheet->object_eval_state = SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED;
}
}
}
}
}
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - "versioning_userdef.c", #blo_do_versions_userdef
* - "versioning_userdef.c", #do_versions_theme
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
LISTBASE_FOREACH (Brush *, br, &bmain->brushes) {
BKE_brush_default_input_curves_set(br);
}
if (!DNA_struct_elem_find(fd->filesdna, "Sculpt", "float", "smooth_strength_factor")) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
Sculpt *sd = scene->toolsettings->sculpt;
if (sd) {
sd->smooth_strength_factor = 1.0f;
}
}
}
}
}

View File

@ -1805,7 +1805,8 @@ BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm,
BMVert *v_kill,
const bool do_del,
const bool check_edge_exists,
const bool kill_degenerate_faces)
const bool kill_degenerate_faces,
const bool kill_duplicate_faces)
{
BMEdge *e_old;
BMVert *v_old, *v_target;
@ -1840,6 +1841,9 @@ BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm,
BLI_SMALLSTACK_DECLARE(faces_degenerate, BMFace *);
BMLoop *l_kill_next;
/* Candidates for being duplicate. */
BLI_SMALLSTACK_DECLARE(faces_duplicate_candidate, BMFace *);
#ifndef NDEBUG
/* For verification later, count valence of 'v_old' and 'v_target' */
valence1 = bmesh_disk_count(v_old);
@ -1877,9 +1881,14 @@ BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm,
/* fix len attribute of face */
l_kill->f->len--;
if (kill_degenerate_faces) {
if (l_kill->f->len < 3) {
BLI_SMALLSTACK_PUSH(faces_degenerate, l_kill->f);
if (kill_degenerate_faces && (l_kill->f->len < 3)) {
BLI_SMALLSTACK_PUSH(faces_degenerate, l_kill->f);
}
else {
/* The duplicate test isn't reliable at this point as `e_splice` might be set,
* so the duplicate test needs to run once the edge has been spliced. */
if (kill_duplicate_faces) {
BLI_SMALLSTACK_PUSH(faces_duplicate_candidate, l_kill->f);
}
}
l_kill_next = l_kill->radial_next;
@ -1940,6 +1949,15 @@ BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm,
}
}
if (kill_duplicate_faces) {
BMFace *f_kill;
while ((f_kill = BLI_SMALLSTACK_POP(faces_duplicate_candidate))) {
if (BM_face_find_double(f_kill)) {
BM_face_kill(bm, f_kill);
}
}
}
BM_CHECK_ELEMENT(v_old);
BM_CHECK_ELEMENT(v_target);
BM_CHECK_ELEMENT(e_old);

View File

@ -115,7 +115,8 @@ BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm,
BMVert *v_kill,
const bool do_del,
const bool check_edge_exists,
const bool kill_degenerate_faces);
const bool kill_degenerate_faces,
const bool kill_duplicate_faces);
BMVert *bmesh_kernel_join_vert_kill_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,

View File

@ -72,7 +72,7 @@ bool BM_vert_dissolve(BMesh *bm, BMVert *v)
}
if (!v->e->l) {
if (len == 2) {
return (BM_vert_collapse_edge(bm, v->e, v, true, true) != NULL);
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != NULL);
}
/* used to kill the vertex here, but it may be connected to faces.
* so better do nothing */
@ -82,7 +82,7 @@ bool BM_vert_dissolve(BMesh *bm, BMVert *v)
}
if (len == 2 && BM_vert_face_count_is_equal(v, 1)) {
/* boundary vertex on a face */
return (BM_vert_collapse_edge(bm, v->e, v, true, true) != NULL);
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != NULL);
}
return BM_disk_dissolve(bm, v);
}
@ -133,7 +133,7 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v)
if (UNLIKELY(!BM_faces_join_pair(bm, e->l, e->l->radial_next, true))) {
return false;
}
if (UNLIKELY(!BM_vert_collapse_faces(bm, v->e, v, 1.0, true, false, true))) {
if (UNLIKELY(!BM_vert_collapse_faces(bm, v->e, v, 1.0, true, false, true, true))) {
return false;
}
#endif
@ -141,7 +141,7 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v)
}
if (keepedge == NULL && len == 2) {
/* collapse the vertex */
e = BM_vert_collapse_faces(bm, v->e, v, 1.0, true, true, true);
e = BM_vert_collapse_faces(bm, v->e, v, 1.0, true, true, true, true);
if (!e) {
return false;
@ -184,7 +184,8 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v)
/* collapse the vertex */
/* note, the baseedge can be a boundary of manifold, use this as join_faces arg */
e = BM_vert_collapse_faces(bm, baseedge, v, 1.0, true, !BM_edge_is_boundary(baseedge), true);
e = BM_vert_collapse_faces(
bm, baseedge, v, 1.0, true, !BM_edge_is_boundary(baseedge), true, true);
if (!e) {
return false;
@ -432,7 +433,8 @@ BMEdge *BM_vert_collapse_faces(BMesh *bm,
float fac,
const bool do_del,
const bool join_faces,
const bool kill_degenerate_faces)
const bool kill_degenerate_faces,
const bool kill_duplicate_faces)
{
BMEdge *e_new = NULL;
BMVert *tv = BM_edge_other_vert(e_kill, v_kill);
@ -503,7 +505,8 @@ BMEdge *BM_vert_collapse_faces(BMesh *bm,
/* same as BM_vert_collapse_edge() however we already
* have vars to perform this operation so don't call. */
e_new = bmesh_kernel_join_edge_kill_vert(
bm, e_kill, v_kill, do_del, true, kill_degenerate_faces);
bm, e_kill, v_kill, do_del, true, kill_degenerate_faces, kill_duplicate_faces);
/* e_new = BM_edge_exists(tv, tv2); */ /* same as return above */
}
@ -517,8 +520,12 @@ BMEdge *BM_vert_collapse_faces(BMesh *bm,
*
* \return The New Edge
*/
BMEdge *BM_vert_collapse_edge(
BMesh *bm, BMEdge *e_kill, BMVert *v_kill, const bool do_del, const bool kill_degenerate_faces)
BMEdge *BM_vert_collapse_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
const bool do_del,
const bool kill_degenerate_faces,
const bool kill_duplicate_faces)
{
/* nice example implementation but we want loops to have their customdata
* accounted for */
@ -546,7 +553,8 @@ BMEdge *BM_vert_collapse_edge(
#else
/* with these args faces are never joined, same as above
* but account for loop customdata */
return BM_vert_collapse_faces(bm, e_kill, v_kill, 1.0f, do_del, false, kill_degenerate_faces);
return BM_vert_collapse_faces(
bm, e_kill, v_kill, 1.0f, do_del, false, kill_degenerate_faces, kill_duplicate_faces);
#endif
}

View File

@ -51,12 +51,14 @@ BMEdge *BM_vert_collapse_faces(BMesh *bm,
float fac,
const bool do_del,
const bool join_faces,
const bool kill_degenerate_faces);
const bool kill_degenerate_faces,
const bool kill_duplicate_faces);
BMEdge *BM_vert_collapse_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
const bool do_del,
const bool kill_degenerate_faces);
const bool kill_degenerate_faces,
const bool kill_duplicate_faces);
BMVert *BM_edge_collapse(BMesh *bm,
BMEdge *e_kill,

View File

@ -242,7 +242,7 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op)
BM_ITER_MESH_MUTABLE (v, v_next, &viter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, VERT_MARK)) {
if (BM_vert_is_edge_pair(v)) {
BM_vert_collapse_edge(bm, v->e, v, true, true);
BM_vert_collapse_edge(bm, v->e, v, true, true, true);
}
}
}
@ -327,6 +327,10 @@ void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op)
/* join faces */
f_new = BM_faces_join_pair(bm, l_a, l_b, false);
if (f_new && BM_face_find_double(f_new)) {
BM_face_kill(bm, f_new);
f_new = NULL;
}
if (f_new) {
/* maintain active face */
@ -355,7 +359,7 @@ void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op)
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, VERT_MARK)) {
if (BM_vert_is_edge_pair(v)) {
BM_vert_collapse_edge(bm, v->e, v, true, true);
BM_vert_collapse_edge(bm, v->e, v, true, true, true);
}
}
}
@ -441,10 +445,16 @@ void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op)
/* join faces */
f_new = BM_faces_join_pair(bm, l_a, l_b, false);
if (f_new && BM_face_find_double(f_new)) {
BM_face_kill(bm, f_new);
f_new = NULL;
}
/* maintain active face */
if (act_face && bm->act_face == NULL) {
bm->act_face = f_new;
if (f_new) {
/* maintain active face */
if (act_face && bm->act_face == NULL) {
bm->act_face = f_new;
}
}
}
}
@ -462,7 +472,7 @@ void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op)
/* final cleanup */
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
if (BM_vert_is_edge_pair(v)) {
BM_vert_collapse_edge(bm, v->e, v, false, true);
BM_vert_collapse_edge(bm, v->e, v, false, true, true);
}
}

View File

@ -263,7 +263,7 @@ void bmo_offset_edgeloops_exec(BMesh *bm, BMOperator *op)
}
while ((v = STACK_POP(varr))) {
bmesh_kernel_join_edge_kill_vert(bm, v->e, v, true, false, false);
bmesh_kernel_join_edge_kill_vert(bm, v->e, v, true, false, false, true);
}
}
}

View File

@ -439,7 +439,7 @@ void BM_mesh_decimate_dissolve_ex(BMesh *bm,
for (i = 0; i < vinput_len; i++) {
BMVert *v = vinput_arr[i];
if (LIKELY(v != NULL) && BM_vert_is_edge_pair(v)) {
BM_vert_collapse_edge(bm, v->e, v, true, true); /* join edges */
BM_vert_collapse_edge(bm, v->e, v, true, true, true); /* join edges */
}
}
}
@ -482,7 +482,7 @@ void BM_mesh_decimate_dissolve_ex(BMesh *bm,
BM_vert_is_edge_pair(v)
#endif
) {
e_new = BM_vert_collapse_edge(bm, v->e, v, true, true); /* join edges */
e_new = BM_vert_collapse_edge(bm, v->e, v, true, true, true); /* join edges */
if (e_new) {

View File

@ -110,7 +110,7 @@ static bool bm_vert_dissolve_fan(BMesh *bm, BMVert *v)
if (tot_edge == 2) {
/* check for 2 wire verts only */
if (tot_edge_wire == 2) {
return (BM_vert_collapse_edge(bm, v->e, v, true, true) != NULL);
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != NULL);
}
}
else if (tot_edge == 4) {

View File

@ -1622,7 +1622,7 @@ bool BM_mesh_intersect(BMesh *bm,
}
if (ok) {
BM_vert_collapse_edge(bm, v->e, v, true, false);
BM_vert_collapse_edge(bm, v->e, v, true, false, false);
}
}
}

View File

@ -59,11 +59,11 @@ set(SRC
intern/COM_CompositorContext.h
intern/COM_Converter.cc
intern/COM_Converter.h
intern/COM_Enums.cc
intern/COM_Debug.cc
intern/COM_Debug.h
intern/COM_Device.cc
intern/COM_Device.h
intern/COM_Enums.cc
intern/COM_ExecutionGroup.cc
intern/COM_ExecutionGroup.h
intern/COM_ExecutionSystem.cc
@ -279,6 +279,8 @@ set(SRC
nodes/COM_VectorBlurNode.h
operations/COM_VectorBlurOperation.cc
operations/COM_VectorBlurOperation.h
nodes/COM_AntiAliasingNode.cc
nodes/COM_AntiAliasingNode.h
nodes/COM_BlurNode.cc
nodes/COM_BlurNode.h
nodes/COM_BokehBlurNode.cc
@ -295,8 +297,6 @@ set(SRC
nodes/COM_FilterNode.h
nodes/COM_InpaintNode.cc
nodes/COM_InpaintNode.h
nodes/COM_AntiAliasingNode.cc
nodes/COM_AntiAliasingNode.h
operations/COM_BlurBaseOperation.cc
operations/COM_BlurBaseOperation.h
@ -322,10 +322,10 @@ set(SRC
operations/COM_MovieClipAttributeOperation.h
operations/COM_MovieDistortionOperation.cc
operations/COM_MovieDistortionOperation.h
operations/COM_VariableSizeBokehBlurOperation.cc
operations/COM_VariableSizeBokehBlurOperation.h
operations/COM_SMAAOperation.cc
operations/COM_SMAAOperation.h
operations/COM_VariableSizeBokehBlurOperation.cc
operations/COM_VariableSizeBokehBlurOperation.h
# Matte nodes
nodes/COM_BoxMaskNode.cc

View File

@ -62,15 +62,29 @@ void NodeOperation::addOutputSocket(DataType datatype)
void NodeOperation::determineResolution(unsigned int resolution[2],
unsigned int preferredResolution[2])
{
if (m_resolutionInputSocketIndex < m_inputs.size()) {
unsigned int used_resolution_index = 0;
if (m_resolutionInputSocketIndex == RESOLUTION_INPUT_ANY) {
for (NodeOperationInput &input : m_inputs) {
unsigned int any_resolution[2] = {0, 0};
input.determineResolution(any_resolution, preferredResolution);
if (any_resolution[0] * any_resolution[1] > 0) {
resolution[0] = any_resolution[0];
resolution[1] = any_resolution[1];
break;
}
used_resolution_index += 1;
}
}
else if (m_resolutionInputSocketIndex < m_inputs.size()) {
NodeOperationInput &input = m_inputs[m_resolutionInputSocketIndex];
input.determineResolution(resolution, preferredResolution);
used_resolution_index = m_resolutionInputSocketIndex;
}
unsigned int temp2[2] = {resolution[0], resolution[1]};
unsigned int temp[2];
for (unsigned int index = 0; index < m_inputs.size(); index++) {
if (index == this->m_resolutionInputSocketIndex) {
if (index == used_resolution_index) {
continue;
}
NodeOperationInput &input = m_inputs[index];
@ -206,7 +220,9 @@ void NodeOperationOutput::determineResolution(unsigned int resolution[2],
}
else {
operation.determineResolution(resolution, preferredResolution);
operation.setResolution(resolution);
if (resolution[0] > 0 && resolution[1] > 0) {
operation.setResolution(resolution);
}
}
}

View File

@ -43,6 +43,13 @@ class WriteBufferOperation;
class NodeOperation;
typedef NodeOperation SocketReader;
/**
* RESOLUTION_INPUT_ANY is a wildcard when any resolution of an input can be used.
* This solves the issue that the FileInputNode in a group node cannot find the
* correct resolution.
*/
static constexpr unsigned int RESOLUTION_INPUT_ANY = 999999;
/**
* \brief Resize modes of inputsockets
* How are the input and working resolutions matched

View File

@ -63,7 +63,7 @@ void GlareNode::convertToOperations(NodeConverter &converter,
thresholdOperation->setGlareSettings(glare);
SetValueOperation *mixvalueoperation = new SetValueOperation();
mixvalueoperation->setValue(0.5f + glare->mix * 0.5f);
mixvalueoperation->setValue(glare->mix);
MixGlareOperation *mixoperation = new MixGlareOperation();
mixoperation->setResolutionInputSocketIndex(1);

View File

@ -18,7 +18,6 @@
#include "COM_OutputFileNode.h"
#include "COM_ExecutionSystem.h"
#include "COM_OutputFileMultiViewOperation.h"
#include "COM_OutputFileOperation.h"
#include "BKE_scene.h"
@ -32,6 +31,31 @@ OutputFileNode::OutputFileNode(bNode *editorNode) : Node(editorNode)
/* pass */
}
void OutputFileNode::add_input_sockets(OutputOpenExrMultiLayerOperation &operation) const
{
for (NodeInput *input : inputs) {
NodeImageMultiFileSocket *sockdata =
(NodeImageMultiFileSocket *)input->getbNodeSocket()->storage;
/* note: layer becomes an empty placeholder if the input is not linked */
operation.add_layer(sockdata->layer, input->getDataType(), input->isLinked());
}
}
void OutputFileNode::map_input_sockets(NodeConverter &converter,
OutputOpenExrMultiLayerOperation &operation) const
{
bool previewAdded = false;
int index = 0;
for (NodeInput *input : inputs) {
converter.mapInputSocket(input, operation.getInputSocket(index++));
if (!previewAdded) {
converter.addNodeInputPreview(input);
previewAdded = true;
}
}
}
void OutputFileNode::convertToOperations(NodeConverter &converter,
const CompositorContext &context) const
{
@ -71,22 +95,11 @@ void OutputFileNode::convertToOperations(NodeConverter &converter,
}
converter.addOperation(outputOperation);
bool previewAdded = false;
int index = 0;
for (NodeInput *input : inputs) {
NodeImageMultiFileSocket *sockdata =
(NodeImageMultiFileSocket *)input->getbNodeSocket()->storage;
/* note: layer becomes an empty placeholder if the input is not linked */
outputOperation->add_layer(sockdata->layer, input->getDataType(), input->isLinked());
converter.mapInputSocket(input, outputOperation->getInputSocket(index++));
if (!previewAdded) {
converter.addNodeInputPreview(input);
previewAdded = true;
}
}
/* First add all inputs. Inputs are stored in a Vector and can be moved to a different
* memory address during this time.*/
add_input_sockets(*outputOperation);
/* After adding the sockets the memory addresses will stick. */
map_input_sockets(converter, *outputOperation);
}
else { /* single layer format */
bool previewAdded = false;

View File

@ -19,6 +19,9 @@
#pragma once
#include "COM_Node.h"
#include "COM_OutputFileMultiViewOperation.h"
#include "DNA_node_types.h"
namespace blender::compositor {
@ -32,6 +35,11 @@ class OutputFileNode : public Node {
OutputFileNode(bNode *editorNode);
void convertToOperations(NodeConverter &converter,
const CompositorContext &context) const override;
private:
void add_input_sockets(OutputOpenExrMultiLayerOperation &operation) const;
void map_input_sockets(NodeConverter &converter,
OutputOpenExrMultiLayerOperation &operation) const;
};
} // namespace blender::compositor

View File

@ -101,6 +101,11 @@ void DenoiseOperation::generateDenoise(float *data,
if (BLI_cpu_support_sse41())
# endif
{
/* Since it's memory intensive, it's better to run only one instance of OIDN at a time.
* OpenImageDenoise is multithreaded internally and should use all available cores nonetheless.
*/
BLI_mutex_lock(&oidn_lock);
oidn::DeviceRef device = oidn::newDevice();
device.commit();
@ -145,10 +150,6 @@ void DenoiseOperation::generateDenoise(float *data,
}
filter.commit();
/* Since it's memory intensive, it's better to run only one instance of OIDN at a time.
* OpenImageDenoise is multithreaded internally and should use all available cores nonetheless.
*/
BLI_mutex_lock(&oidn_lock);
filter.execute();
BLI_mutex_unlock(&oidn_lock);

View File

@ -462,27 +462,26 @@ void MixGlareOperation::executePixelSampled(float output[4],
float inputColor1[4];
float inputColor2[4];
float inputValue[4];
float value;
float value, input_weight, glare_weight;
this->m_inputValueOperation->readSampled(inputValue, x, y, sampler);
this->m_inputColor1Operation->readSampled(inputColor1, x, y, sampler);
this->m_inputColor2Operation->readSampled(inputColor2, x, y, sampler);
value = inputValue[0];
float mf = 2.0f - 2.0f * fabsf(value - 0.5f);
if (inputColor1[0] < 0.0f) {
inputColor1[0] = 0.0f;
/* Linear interpolation between 3 cases:
* value=-1:output=input value=0:output=input+glare value=1:output=glare
*/
if (value < 0.0f) {
input_weight = 1.0f;
glare_weight = 1.0f + value;
}
if (inputColor1[1] < 0.0f) {
inputColor1[1] = 0.0f;
else {
input_weight = 1.0f - value;
glare_weight = 1.0f;
}
if (inputColor1[2] < 0.0f) {
inputColor1[2] = 0.0f;
}
output[0] = mf * MAX2(inputColor1[0] + value * (inputColor2[0] - inputColor1[0]), 0.0f);
output[1] = mf * MAX2(inputColor1[1] + value * (inputColor2[1] - inputColor1[1]), 0.0f);
output[2] = mf * MAX2(inputColor1[2] + value * (inputColor2[2] - inputColor1[2]), 0.0f);
output[0] = input_weight * MAX2(inputColor1[0], 0.0f) + glare_weight * inputColor2[0];
output[1] = input_weight * MAX2(inputColor1[1], 0.0f) + glare_weight * inputColor2[1];
output[2] = input_weight * MAX2(inputColor1[2], 0.0f) + glare_weight * inputColor2[2];
output[3] = inputColor1[3];
clampIfNeeded(output);

View File

@ -323,6 +323,7 @@ OutputOpenExrMultiLayerOperation::OutputOpenExrMultiLayerOperation(const Scene *
this->m_exr_codec = exr_codec;
this->m_exr_half_float = exr_half_float;
this->m_viewName = viewName;
this->setResolutionInputSocketIndex(RESOLUTION_INPUT_ANY);
}
void OutputOpenExrMultiLayerOperation::add_layer(const char *name,

View File

@ -105,10 +105,12 @@ static void gpencil_shade_color(float color[3])
}
/* Apply all overrides from the solid viewport mode to the GPencil material. */
static MaterialGPencilStyle *gpencil_viewport_material_overrides(GPENCIL_PrivateData *pd,
Object *ob,
int color_type,
MaterialGPencilStyle *gp_style)
static MaterialGPencilStyle *gpencil_viewport_material_overrides(
GPENCIL_PrivateData *pd,
Object *ob,
int color_type,
MaterialGPencilStyle *gp_style,
const eV3DShadingLightingMode lighting_mode)
{
static MaterialGPencilStyle gp_style_tmp;
@ -148,7 +150,9 @@ static MaterialGPencilStyle *gpencil_viewport_material_overrides(GPENCIL_Private
copy_v3_v3(gp_style->fill_rgba, pd->v3d_single_color);
gp_style->fill_rgba[3] = 1.0f;
copy_v4_v4(gp_style->stroke_rgba, gp_style->fill_rgba);
gpencil_shade_color(gp_style->stroke_rgba);
if (lighting_mode != V3D_LIGHTING_FLAT) {
gpencil_shade_color(gp_style->fill_rgba);
}
break;
case V3D_SHADING_OBJECT_COLOR:
gp_style = &gp_style_tmp;
@ -156,7 +160,9 @@ static MaterialGPencilStyle *gpencil_viewport_material_overrides(GPENCIL_Private
gp_style->fill_style = GP_MATERIAL_FILL_STYLE_SOLID;
copy_v4_v4(gp_style->fill_rgba, ob->color);
copy_v4_v4(gp_style->stroke_rgba, ob->color);
gpencil_shade_color(gp_style->stroke_rgba);
if (lighting_mode != V3D_LIGHTING_FLAT) {
gpencil_shade_color(gp_style->fill_rgba);
}
break;
case V3D_SHADING_VERTEX_COLOR:
gp_style = &gp_style_tmp;
@ -198,6 +204,8 @@ GPENCIL_MaterialPool *gpencil_material_pool_create(GPENCIL_PrivateData *pd, Obje
int color_type = (pd->v3d_color_type != -1 && GPENCIL_VERTEX_MODE(gpd)) ?
V3D_SHADING_VERTEX_COLOR :
pd->v3d_color_type;
const eV3DShadingLightingMode lighting_mode = (pd->v3d != NULL) ? pd->v3d->shading.light :
V3D_LIGHTING_STUDIO;
GPENCIL_MaterialPool *pool = matpool;
for (int i = 0; i < mat_len; i++) {
@ -245,7 +253,7 @@ GPENCIL_MaterialPool *gpencil_material_pool_create(GPENCIL_PrivateData *pd, Obje
mat_data->flag |= GP_FILL_HOLDOUT;
}
gp_style = gpencil_viewport_material_overrides(pd, ob, color_type, gp_style);
gp_style = gpencil_viewport_material_overrides(pd, ob, color_type, gp_style, lighting_mode);
/* Dots or Squares rotation. */
mat_data->alignment_rot_cos = cosf(gp_style->alignment_rotation);

View File

@ -2141,7 +2141,7 @@ static int armature_select_mirror_exec(bContext *C, wmOperator *op)
void ARMATURE_OT_select_mirror(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Flip Active/Selected Bone";
ot->name = "Select Mirror";
ot->idname = "ARMATURE_OT_select_mirror";
ot->description = "Mirror the bone selection";

View File

@ -1284,7 +1284,7 @@ static int pose_select_mirror_exec(bContext *C, wmOperator *op)
void POSE_OT_select_mirror(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Flip Active/Selected Bone";
ot->name = "Select Mirror";
ot->idname = "POSE_OT_select_mirror";
ot->description = "Mirror the bone selection";

View File

@ -3981,6 +3981,11 @@ static int gpencil_count_subdivision_cuts(bGPDstroke *gps)
}
}
if ((gps->flag & GP_STROKE_CYCLIC) && (gps->points[0].flag & GP_SPOINT_SELECT) &&
(gps->points[gps->totpoints - 1].flag & GP_SPOINT_SELECT)) {
totnewpoints++;
}
return totnewpoints;
}
@ -4079,6 +4084,47 @@ static void gpencil_stroke_subdivide(bGPDstroke *gps, const int cuts)
}
}
}
/* Subdivide between last and first point. */
if (gps->flag & GP_STROKE_CYCLIC) {
bGPDspoint *pt = &temp_points[oldtotpoints - 1];
bGPDspoint *next = &temp_points[0];
if ((pt->flag & GP_SPOINT_SELECT) && (next->flag & GP_SPOINT_SELECT)) {
bGPDspoint *pt_final = &gps->points[i2];
if (gps->dvert != NULL) {
dvert_final = &gps->dvert[i2];
}
/* Interpolate all values */
interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f);
pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f);
pt_final->strength = interpf(pt->strength, next->strength, 0.5f);
CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f);
interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f);
pt_final->time = interpf(pt->time, next->time, 0.5f);
pt_final->flag |= GP_SPOINT_SELECT;
/* interpolate weights */
if (gps->dvert != NULL) {
dvert = &temp_dverts[oldtotpoints - 1];
dvert_next = &temp_dverts[0];
dvert_final = &gps->dvert[i2];
dvert_final->totweight = dvert->totweight;
dvert_final->dw = MEM_dupallocN(dvert->dw);
/* interpolate weight values */
for (int d = 0; d < dvert->totweight; d++) {
MDeformWeight *dw_a = &dvert->dw[d];
if (dvert_next->totweight > d) {
MDeformWeight *dw_b = &dvert_next->dw[d];
MDeformWeight *dw_final = &dvert_final->dw[d];
dw_final->weight = interpf(dw_a->weight, dw_b->weight, 0.5f);
}
}
}
}
}
/* free temp memory */
MEM_SAFE_FREE(temp_points);
MEM_SAFE_FREE(temp_dverts);

View File

@ -505,7 +505,7 @@ void GPENCIL_OT_select_alternate(wmOperatorType *ot)
/* properties */
RNA_def_boolean(ot->srna,
"unselect_ends",
true,
false,
"Unselect Ends",
"Do not select the first and last point of the stroke");
}

View File

@ -107,8 +107,9 @@ bool user_string_to_number(bContext *C,
const char *str,
const struct UnitSettings *unit,
int type,
const char *error_prefix,
double *r_value);
double *r_value,
const bool use_single_line_error,
char **r_error);
/** \} */

View File

@ -91,8 +91,7 @@ void ED_preview_shader_job(const struct bContext *C,
int sizex,
int sizey,
int method);
void ED_preview_icon_render(struct Main *bmain,
struct Depsgraph *depsgraph,
void ED_preview_icon_render(const struct bContext *C,
struct Scene *scene,
struct ID *id,
unsigned int *rect,

View 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.
*/
#pragma once
struct SpreadsheetContext;
struct SpaceSpreadsheet;
struct SpaceNode;
struct ID;
struct bNode;
#ifdef __cplusplus
extern "C" {
#endif
struct SpreadsheetContext *ED_spreadsheet_context_new(int type);
void ED_spreadsheet_context_free(struct SpreadsheetContext *context);
void ED_spreadsheet_context_path_clear(struct SpaceSpreadsheet *sspreadsheet);
void ED_spreadsheet_context_path_update_tag(struct SpaceSpreadsheet *sspreadsheet);
uint64_t ED_spreadsheet_context_path_hash(struct SpaceSpreadsheet *sspreadsheet);
struct ID *ED_spreadsheet_get_current_id(struct SpaceSpreadsheet *sspreadsheet);
void ED_spreadsheet_set_geometry_node_context(struct SpaceSpreadsheet *sspreadsheet,
struct SpaceNode *snode,
struct bNode *node);
#ifdef __cplusplus
}
#endif

View File

@ -1600,6 +1600,7 @@ void UI_but_func_search_set(uiBut *but,
uiButSearchCreateFn search_create_fn,
uiButSearchUpdateFn search_update_fn,
void *arg,
const bool free_arg,
uiButSearchArgFreeFn search_arg_free_fn,
uiButHandleFunc search_exec_fn,
void *active);

View File

@ -52,6 +52,7 @@
#include "BKE_context.h"
#include "BKE_idprop.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
#include "BKE_unit.h"
@ -2912,7 +2913,14 @@ char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size)
static bool ui_number_from_string_units(
bContext *C, const char *str, const int unit_type, const UnitSettings *unit, double *r_value)
{
return user_string_to_number(C, str, unit, unit_type, UI_NUMBER_EVAL_ERROR_PREFIX, r_value);
char *error = NULL;
const bool ok = user_string_to_number(C, str, unit, unit_type, r_value, true, &error);
if (error) {
ReportList *reports = CTX_wm_reports(C);
BKE_reportf(reports, RPT_ERROR, "%s: %s", UI_NUMBER_EVAL_ERROR_PREFIX, error);
MEM_freeN(error);
}
return ok;
}
static bool ui_number_from_string_units_with_but(bContext *C,
@ -2929,7 +2937,11 @@ static bool ui_number_from_string(bContext *C, const char *str, double *r_value)
{
bool ok;
#ifdef WITH_PYTHON
ok = BPY_run_string_as_number(C, NULL, str, UI_NUMBER_EVAL_ERROR_PREFIX, r_value);
struct BPy_RunErrInfo err_info = {
.reports = CTX_wm_reports(C),
.report_prefix = UI_NUMBER_EVAL_ERROR_PREFIX,
};
ok = BPY_run_string_as_number(C, NULL, str, &err_info, r_value);
#else
UNUSED_VARS(C);
*r_value = atof(str);
@ -6590,6 +6602,8 @@ uiBut *uiDefSearchBut(uiBlock *block,
* \param search_create_fn: Function to create the menu.
* \param search_update_fn: Function to refresh search content after the search text has changed.
* \param arg: user value.
* \param free_arg: Set to true if the argument is newly allocated memory for every redraw and
* should be freed when the button is destroyed.
* \param search_arg_free_fn: When non-null, use this function to free \a arg.
* \param search_exec_fn: Function that executes the action, gets \a arg as the first argument.
* The second argument as the active item-pointer
@ -6600,6 +6614,7 @@ void UI_but_func_search_set(uiBut *but,
uiButSearchCreateFn search_create_fn,
uiButSearchUpdateFn search_update_fn,
void *arg,
const bool free_arg,
uiButSearchArgFreeFn search_arg_free_fn,
uiButHandleFunc search_exec_fn,
void *active)
@ -6635,11 +6650,17 @@ void UI_but_func_search_set(uiBut *but,
}
#endif
/* Handling will pass the active item as arg2 later, so keep it NULL here. */
UI_but_func_set(but, search_exec_fn, search_but->arg, NULL);
if (free_arg) {
UI_but_funcN_set(but, search_exec_fn, search_but->arg, NULL);
}
else {
UI_but_func_set(but, search_exec_fn, search_but->arg, NULL);
}
}
/* search buttons show red-alert if item doesn't exist, not for menus */
if (0 == (but->block->flag & UI_BLOCK_LOOP)) {
/* search buttons show red-alert if item doesn't exist, not for menus. Don't do this for
* buttons where any result is valid anyway, since any string will be valid anyway. */
if (0 == (but->block->flag & UI_BLOCK_LOOP) && !search_but->results_are_suggestions) {
/* skip empty buttons, not all buttons need input, we only show invalid */
if (but->drawstr[0]) {
ui_but_search_refresh(search_but);
@ -6779,6 +6800,7 @@ uiBut *uiDefSearchButO_ptr(uiBlock *block,
ui_searchbox_create_generic,
operator_enum_search_update_fn,
but,
false,
NULL,
operator_enum_search_exec_fn,
NULL);

View File

@ -399,7 +399,7 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um)
"'%s').label",
idname);
char *expr_result = NULL;
if (BPY_run_string_as_string(C, expr_imports, expr, __func__, &expr_result)) {
if (BPY_run_string_as_string(C, expr_imports, expr, NULL, &expr_result)) {
STRNCPY(drawstr, expr_result);
MEM_freeN(expr_result);
}

View File

@ -1428,13 +1428,7 @@ static void icon_set_image(const bContext *C,
scene = CTX_data_scene(C);
}
/* Immediate version */
ED_preview_icon_render(CTX_data_main(C),
CTX_data_ensure_evaluated_depsgraph(C),
scene,
id,
prv_img->rect[size],
prv_img->w[size],
prv_img->h[size]);
ED_preview_icon_render(C, scene, id, prv_img->rect[size], prv_img->w[size], prv_img->h[size]);
}
}

View File

@ -2718,6 +2718,7 @@ uiBut *ui_but_add_search(
ui_searchbox_create_generic,
ui_rna_collection_search_update_fn,
coll_search,
false,
ui_rna_collection_search_arg_free_fn,
NULL,
NULL);

View File

@ -428,7 +428,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
if (has_valid_context == false) {
expr_result = BLI_strdup(has_valid_context_error);
}
else if (BPY_run_string_as_string(C, expr_imports, expr, __func__, &expr_result)) {
else if (BPY_run_string_as_string(C, expr_imports, expr, NULL, &expr_result)) {
if (STREQ(expr_result, "")) {
MEM_freeN(expr_result);
expr_result = NULL;
@ -485,7 +485,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
if (has_valid_context == false) {
expr_result = BLI_strdup(has_valid_context_error);
}
else if (BPY_run_string_as_string(C, expr_imports, expr, __func__, &expr_result)) {
else if (BPY_run_string_as_string(C, expr_imports, expr, NULL, &expr_result)) {
if (STREQ(expr_result, ".")) {
MEM_freeN(expr_result);
expr_result = NULL;
@ -589,7 +589,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
if (has_valid_context == false) {
shortcut = BLI_strdup(has_valid_context_error);
}
else if (BPY_run_string_as_intptr(C, expr_imports, expr, __func__, &expr_result)) {
else if (BPY_run_string_as_intptr(C, expr_imports, expr, NULL, &expr_result)) {
if (expr_result != 0) {
wmKeyMap *keymap = (wmKeyMap *)expr_result;
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
@ -654,7 +654,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
/* pass */
}
else if (BPY_run_string_as_string_and_size(
C, expr_imports, expr, __func__, &expr_result, &expr_result_len)) {
C, expr_imports, expr, NULL, &expr_result, &expr_result_len)) {
/* pass. */
}
}
@ -731,7 +731,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
if (has_valid_context == false) {
/* pass */
}
else if (BPY_run_string_as_intptr(C, expr_imports, expr, __func__, &expr_result)) {
else if (BPY_run_string_as_intptr(C, expr_imports, expr, NULL, &expr_result)) {
if (expr_result != 0) {
{
uiTooltipField *field = text_field_add(data,

View File

@ -1148,6 +1148,7 @@ void UI_but_func_menu_search(uiBut *but)
ui_searchbox_create_menu,
menu_search_update_fn,
data,
false,
menu_search_arg_free_fn,
menu_search_exec_fn,
NULL);

View File

@ -121,6 +121,7 @@ void UI_but_func_operator_search(uiBut *but)
operator_search_update_fn,
NULL,
false,
NULL,
operator_search_exec_fn,
NULL);
}

View File

@ -309,6 +309,7 @@ static uiBlock *template_common_search_menu(const bContext *C,
ui_searchbox_create_generic,
search_update_fn,
search_arg,
false,
NULL,
search_exec_fn,
active_item);

View File

@ -1225,12 +1225,6 @@ static bool draw_widgetbase_batch_skip_draw_cache(void)
return true;
}
/* There are also reports that some AMD and Mesa driver configuration suffer from the
* same issue, T78803. */
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE)) {
return true;
}
return false;
}

View File

@ -27,7 +27,6 @@
struct ARegion;
struct bContext;
struct View3D;
struct wmOperator;
struct wmOperatorType;
void WM_OT_gpencil_import_svg(struct wmOperatorType *ot);

View File

@ -90,7 +90,7 @@ void OBJECT_OT_forcefield_toggle(struct wmOperatorType *ot);
void OBJECT_OT_move_to_collection(struct wmOperatorType *ot);
void OBJECT_OT_link_to_collection(struct wmOperatorType *ot);
void OBJECT_OT_switch_object(struct wmOperatorType *ot);
void OBJECT_OT_transfer_mode(struct wmOperatorType *ot);
/* object_select.c */
void OBJECT_OT_select_all(struct wmOperatorType *ot);

View File

@ -40,6 +40,7 @@
#include "GPU_immediate_util.h"
#include "GPU_matrix.h"
#include "GPU_state.h"
#include "BLT_translation.h"
#include "BKE_context.h"
#include "BKE_gpencil_modifier.h"
@ -52,6 +53,7 @@
#include "BKE_paint.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
#include "WM_api.h"
#include "WM_types.h"
@ -416,7 +418,7 @@ bool ED_object_mode_generic_has_data(struct Depsgraph *depsgraph, struct Object
/** \} */
/* -------------------------------------------------------------------- */
/** \name Switch Object
/** \name Transfer Mode
*
* Enters the same mode of the current active object in another object,
* leaving the mode of the current object.
@ -434,7 +436,7 @@ typedef struct SwitchObjectCustomData {
float alpha;
} SwitchObjectCustomData;
static void switch_object_draw(const bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
static void transfer_mode_draw(const bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
{
SwitchObjectCustomData *cd = arg;
@ -478,73 +480,53 @@ static void switch_object_draw(const bContext *UNUSED(C), ARegion *UNUSED(ar), v
GPU_line_smooth(false);
}
static int object_switch_object_modal(bContext *C, wmOperator *op, const wmEvent *event)
static bool object_transfer_mode_poll(bContext *C)
{
ARegion *region = CTX_wm_region(C);
SwitchObjectCustomData *cd = op->customdata;
if (cd->start_time == 0.0) {
cd->start_time = PIL_check_seconds_timer();
}
const double time = PIL_check_seconds_timer();
const double delta_time = time - cd->start_time;
float alpha = 1.0f - (6.0 * delta_time);
if (alpha > 0.01f) {
cd->alpha = alpha;
ED_region_tag_redraw(region);
return OPERATOR_RUNNING_MODAL;
}
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), cd->timer);
ED_region_draw_cb_exit(region->type, cd->draw_handle);
MEM_freeN(op->customdata);
ED_region_tag_redraw(region);
return OPERATOR_FINISHED;
}
static bool object_switch_object_poll(bContext *C)
{
if (!U.experimental.use_switch_object_operator) {
return false;
}
if (!CTX_wm_region_view3d(C)) {
return false;
}
const Object *ob = CTX_data_active_object(C);
return ob && (ob->mode & (OB_MODE_EDIT | OB_MODE_SCULPT));
return ob && (ob->mode & (OB_MODE_SCULPT));
}
static int object_switch_object_invoke(bContext *C, wmOperator *op, const wmEvent *event)
/* Update the viewport rotation origin to the mouse cursor. */
static void object_transfer_mode_reposition_view_pivot(bContext *C, const int mval[2])
{
ARegion *region = CTX_wm_region(C);
Scene *scene = CTX_data_scene(C);
float global_loc[3];
if (!ED_view3d_autodist_simple(region, mval, global_loc, 0, NULL)) {
return;
}
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
copy_v3_v3(ups->average_stroke_accum, global_loc);
ups->average_stroke_counter = 1;
ups->last_stroke_valid = true;
}
static bool object_transfer_mode_to_base(bContext *C, wmOperator *op, Base *base_dst)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Base *base_dst = ED_view3d_give_base_under_cursor(C, event->mval);
if (base_dst == NULL) {
return OPERATOR_CANCELLED;
return false;
}
Object *ob_dst = base_dst->object;
Object *ob_src = CTX_data_active_object(C);
if (ob_dst == ob_src) {
return OPERATOR_CANCELLED;
return false;
}
const eObjectMode last_mode = (eObjectMode)ob_src->mode;
if (!ED_object_mode_compat_test(ob_dst, last_mode)) {
return OPERATOR_CANCELLED;
return false;
}
int retval = OPERATOR_CANCELLED;
bool switched = false;
bool mode_transfered = false;
ED_undo_group_begin(C);
@ -560,43 +542,64 @@ static int object_switch_object_invoke(bContext *C, wmOperator *op, const wmEven
ob_dst_orig = DEG_get_original_object(ob_dst);
ED_object_mode_set_ex(C, last_mode, true, op->reports);
/* Update the viewport rotation origin to the mouse cursor. */
if (last_mode & OB_MODE_ALL_PAINT) {
float global_loc[3];
if (ED_view3d_autodist_simple(region, event->mval, global_loc, 0, NULL)) {
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
copy_v3_v3(ups->average_stroke_accum, global_loc);
ups->average_stroke_counter = 1;
ups->last_stroke_valid = true;
}
}
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
WM_toolsystem_update_from_context_view3d(C);
retval = OPERATOR_FINISHED;
switched = true;
mode_transfered = true;
}
ED_undo_group_end(C);
return mode_transfered;
}
const bool flash_object = true;
static bool object_transfer_mode_flash_animation_update(bContext *C, wmOperator *op) {
SwitchObjectCustomData *cd = op->customdata;
if (!flash_object) {
if (switched) {
return OPERATOR_FINISHED;
}
else {
return OPERATOR_CANCELLED;
}
if (cd == NULL) {
return true;
}
ARegion *region = CTX_wm_region(C);
if (cd->start_time == 0.0) {
cd->start_time = PIL_check_seconds_timer();
}
const double time = PIL_check_seconds_timer();
const double delta_time = time - cd->start_time;
float alpha = 1.0f - (6.0 * delta_time);
if (alpha > 0.01f) {
cd->alpha = alpha;
ED_region_tag_redraw(region);
return false;
}
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), cd->timer);
ED_region_draw_cb_exit(region->type, cd->draw_handle);
MEM_freeN(op->customdata);
ED_region_tag_redraw(region);
return true;
}
static void object_transfer_mode_flash_animation_begin(bContext *C, wmOperator *op, Base *base_dst) {
if (!base_dst) {
return;
}
if (!base_dst->object) {
return;
}
ARegion *region = CTX_wm_region(C);
SwitchObjectCustomData *cd = MEM_callocN(sizeof(SwitchObjectCustomData),
"Switch Object Custom Data");
cd->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.05f);
cd->draw_handle = ED_region_draw_cb_activate(
region->type, switch_object_draw, cd, REGION_DRAW_POST_VIEW);
cd->target_object = ob_dst;
region->type, transfer_mode_draw, cd, REGION_DRAW_POST_VIEW);
cd->target_object = base_dst->object;
cd->start_time = 0.0;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
@ -607,31 +610,123 @@ static int object_switch_object_invoke(bContext *C, wmOperator *op, const wmEven
cd->tottris = BKE_mesh_runtime_looptri_len(mesh);
cd->mesh = mesh;
cd->alpha = 1.0f;
op->customdata = cd;
}
static int object_transfer_mode_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
const bool use_eyedropper = RNA_boolean_get(op->ptr, "use_eyedropper");
const bool flash_object = RNA_boolean_get(op->ptr, "flash_object");
if (use_eyedropper) {
switch (event->type) {
case LEFTMOUSE:
if (event->val == KM_PRESS) {
WM_cursor_modal_restore(CTX_wm_window(C));
ED_workspace_status_text(C, NULL);
/* This ensures that the click was done in an viewport region. */
bScreen *screen = CTX_wm_screen(C);
ARegion *region = BKE_screen_find_main_region_at_xy(
screen, SPACE_VIEW3D, event->x, event->y);
if (!region) {
return OPERATOR_CANCELLED;
}
const int mval[2] = {event->x - region->winrct.xmin, event->y - region->winrct.ymin};
Base *base_dst = ED_view3d_give_base_under_cursor(C, mval);
const bool mode_transfered = object_transfer_mode_to_base(C, op, base_dst);
if (!mode_transfered) {
return OPERATOR_CANCELLED;
}
if (!flash_object) {
return OPERATOR_FINISHED;
}
object_transfer_mode_flash_animation_begin(C, op, base_dst);
}
break;
case RIGHTMOUSE: {
WM_cursor_modal_restore(CTX_wm_window(C));
ED_workspace_status_text(C, NULL);
return OPERATOR_CANCELLED;
}
}
if (!flash_object) {
return OPERATOR_RUNNING_MODAL;
}
}
if (!object_transfer_mode_flash_animation_update(C, op)) {
return OPERATOR_RUNNING_MODAL;
}
return OPERATOR_FINISHED;
}
static int object_transfer_mode_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const bool use_eyedropper = RNA_boolean_get(op->ptr, "use_eyedropper");
const bool flash_object = RNA_boolean_get(op->ptr, "flash_object");
if (use_eyedropper) {
ED_workspace_status_text(C, TIP_("Click in the viewport to select an object"));
WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_EYEDROPPER);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
Object *ob_src = CTX_data_active_object(C);
const eObjectMode src_mode = (eObjectMode)ob_src->mode;
Base *base_dst = ED_view3d_give_base_under_cursor(C, event->mval);
const bool mode_transfered = object_transfer_mode_to_base(C, op, base_dst);
if (!mode_transfered) {
return OPERATOR_CANCELLED;
}
if (src_mode & OB_MODE_ALL_PAINT) {
object_transfer_mode_reposition_view_pivot(C, event->mval);
}
if (!flash_object) {
return OPERATOR_FINISHED;
}
object_transfer_mode_flash_animation_begin(C, op, base_dst);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
void OBJECT_OT_switch_object(wmOperatorType *ot)
void OBJECT_OT_transfer_mode(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Switch Object";
ot->idname = "OBJECT_OT_switch_object";
ot->name = "Transfer Mode";
ot->idname = "OBJECT_OT_transfer_mode";
ot->description =
"Switches the active object and assigns the same mode to a new one under the mouse cursor, "
"leaving the active mode in the current one";
/* api callbacks */
ot->invoke = object_switch_object_invoke;
ot->modal = object_switch_object_modal;
ot->poll = object_switch_object_poll;
ot->invoke = object_transfer_mode_invoke;
ot->modal = object_transfer_mode_modal;
ot->poll = object_transfer_mode_poll;
/* Undo push is handled by the operator. */
ot->flag = OPTYPE_REGISTER;
RNA_def_boolean(
ot->srna, "flash_object", true, "Flash Object", "Flash object when entering the new mode");
RNA_def_boolean(ot->srna,
"use_eyedropper",
false,
"Use Eyedropper",
"Pick the object to switch to using an eyedropper");
}
/** \} */

View File

@ -65,7 +65,7 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_paths_range_update);
WM_operatortype_append(OBJECT_OT_forcefield_toggle);
WM_operatortype_append(OBJECT_OT_switch_object);
WM_operatortype_append(OBJECT_OT_transfer_mode);
WM_operatortype_append(OBJECT_OT_parent_set);
WM_operatortype_append(OBJECT_OT_parent_no_inverse_set);

View File

@ -1608,7 +1608,7 @@ static void icon_preview_free(void *customdata)
}
void ED_preview_icon_render(
Main *bmain, Depsgraph *depsgraph, Scene *scene, ID *id, uint *rect, int sizex, int sizey)
const bContext *C, Scene *scene, ID *id, uint *rect, int sizex, int sizey)
{
IconPreview ip = {NULL};
short stop = false, update = false;
@ -1616,9 +1616,9 @@ void ED_preview_icon_render(
ED_preview_ensure_dbase();
ip.bmain = bmain;
ip.bmain = CTX_data_main(C);
ip.scene = scene;
ip.depsgraph = depsgraph;
ip.depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ip.owner = BKE_previewimg_id_ensure(id);
ip.id = id;
/* Control isn't given back to the caller until the preview is done. So we don't need to copy

View File

@ -83,7 +83,6 @@ static void draw_render_info(
const bContext *C, Scene *scene, Image *ima, ARegion *region, float zoomx, float zoomy)
{
Render *re = RE_GetSceneRender(scene);
RenderData *rd = RE_engine_get_render_data(re);
Scene *stats_scene = ED_render_job_get_scene(C);
if (stats_scene == NULL) {
stats_scene = CTX_data_scene(C);
@ -112,6 +111,7 @@ static void draw_render_info(
GPU_matrix_translate_2f(x, y);
GPU_matrix_scale_2f(zoomx, zoomy);
RenderData *rd = RE_engine_get_render_data(re);
if (rd->mode & R_BORDER) {
/* TODO: round or floor instead of casting to int */
GPU_matrix_translate_2f((int)(-rd->border.xmin * rd->xsch * rd->size * 0.01f),

View File

@ -339,7 +339,25 @@ static bNodeTree *node_add_group_get_and_poll_group_node_tree(Main *bmain,
if (!node_group) {
return NULL;
}
if ((node_group->type != ntree->type) || !nodeGroupPoll(ntree, node_group)) {
const char *disabled_hint = NULL;
if ((node_group->type != ntree->type) || !nodeGroupPoll(ntree, node_group, &disabled_hint)) {
if (disabled_hint) {
BKE_reportf(op->reports,
RPT_ERROR,
"Can not add node group '%s' to '%s':\n %s",
node_group->id.name + 2,
ntree->id.name + 2,
disabled_hint);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Can not add node group '%s' to '%s'",
node_group->id.name + 2,
ntree->id.name + 2);
}
return NULL;
}

View File

@ -157,6 +157,11 @@ static void draw_socket_list(const bContext *C,
RNA_pointer_create((ID *)ntree, &RNA_NodeSocketInterface, socket, &socket_ptr);
uiItemR(layout, &socket_ptr, "name", 0, NULL, ICON_NONE);
/* Display descriptions only for Geometry Nodes, since it's only used in the modifier panel. */
if (ntree->type == NTREE_GEOMETRY) {
uiItemR(layout, &socket_ptr, "description", 0, NULL, ICON_NONE);
}
if (socket->typeinfo->interface_draw) {
socket->typeinfo->interface_draw((bContext *)C, layout, &socket_ptr);
}

View File

@ -1422,7 +1422,7 @@ static void node_draw_basis(const bContext *C,
0,
0,
0,
"Show this node's geometry output in the spreadsheet in Node mode");
"Show this node's geometry output in the spreadsheet");
UI_but_func_set(but, node_toggle_button_cb, node, (void *)"NODE_OT_active_preview_toggle");
UI_block_emboss_set(node->block, UI_EMBOSS);
}

View File

@ -1694,55 +1694,6 @@ void NODE_OT_hide_socket_toggle(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static void disable_active_preview_on_all_nodes(bNodeTree *ntree)
{
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
node->flag &= ~NODE_ACTIVE_PREVIEW;
}
}
static int node_active_preview_toggle_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceNode *snode = CTX_wm_space_node(C);
Main *bmain = CTX_data_main(C);
bNodeTree *ntree = snode->edittree;
disable_active_preview_on_all_nodes(ntree);
bNode *active_node = nodeGetActive(ntree);
active_node->flag |= NODE_ACTIVE_PREVIEW;
/* Tag for update, so that dependent objects are reevaluated. This is necessary when a
* spreadsheet editor displays data from a node. */
LISTBASE_FOREACH (wmWindow *, window, &((wmWindowManager *)bmain->wm.first)->windows) {
bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area->spacetype == SPACE_SPREADSHEET) {
SpaceSpreadsheet *sspreadsheet = area->spacedata.first;
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_NODE) {
DEG_id_tag_update(&ntree->id, ID_RECALC_COPY_ON_WRITE);
ED_area_tag_redraw(area);
}
}
}
}
return OPERATOR_FINISHED;
}
void NODE_OT_active_preview_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Toggle Active Preview";
ot->description = "Toggle active preview state of node";
ot->idname = "NODE_OT_active_preview_toggle";
/* callbacks */
ot->exec = node_active_preview_toggle_exec;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ****************** Mute operator *********************** */
static int node_mute_exec(bContext *C, wmOperator *UNUSED(op))
@ -2241,13 +2192,25 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
/* make sure all clipboard nodes would be valid in the target tree */
bool all_nodes_valid = true;
LISTBASE_FOREACH (bNode *, node, clipboard_nodes_lb) {
if (!node->typeinfo->poll_instance || !node->typeinfo->poll_instance(node, ntree)) {
const char *disabled_hint = NULL;
if (!node->typeinfo->poll_instance ||
!node->typeinfo->poll_instance(node, ntree, &disabled_hint)) {
all_nodes_valid = false;
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s",
node->name,
ntree->id.name + 2);
if (disabled_hint) {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s:\n %s",
node->name,
ntree->id.name + 2,
disabled_hint);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s",
node->name,
ntree->id.name + 2);
}
}
}
if (!all_nodes_valid) {

View File

@ -30,6 +30,11 @@
#include "BKE_node_ui_storage.hh"
#include "BKE_object.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
#include "BLT_translation.h"
#include "UI_interface.h"
#include "UI_resources.h"
@ -37,44 +42,77 @@
using blender::IndexRange;
using blender::Map;
using blender::MultiValueMap;
using blender::Set;
using blender::StringRef;
struct AttributeSearchData {
const bNodeTree &node_tree;
const bNode &node;
uiBut *search_button;
/* Used to keep track of a button pointer over multiple redraws. Since the UI code
* may reallocate the button, without this we might end up with a dangling pointer. */
uiButStore *button_store;
uiBlock *button_store_block;
bNodeSocket &socket;
};
/* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */
BLI_STATIC_ASSERT(std::is_trivially_destructible_v<AttributeSearchData>, "");
static StringRef attribute_data_type_string(const CustomDataType type)
{
const char *name = nullptr;
RNA_enum_name_from_value(rna_enum_attribute_type_items, type, &name);
return StringRef(IFACE_(name));
}
static StringRef attribute_domain_string(const AttributeDomain domain)
{
const char *name = nullptr;
RNA_enum_name_from_value(rna_enum_attribute_domain_items, domain, &name);
return StringRef(IFACE_(name));
}
/* Unicode arrow. */
#define MENU_SEP "\xe2\x96\xb6"
static bool attribute_search_item_add(uiSearchItems *items, const AvailableAttributeInfo &item)
{
const StringRef data_type_name = attribute_data_type_string(item.data_type);
const StringRef domain_name = attribute_domain_string(item.domain);
std::string search_item_text = domain_name + " " + MENU_SEP + item.name + UI_SEP_CHAR +
data_type_name;
return UI_search_item_add(
items, search_item_text.c_str(), (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0);
}
static void attribute_search_update_fn(
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
{
AttributeSearchData *data = static_cast<AttributeSearchData *>(arg);
NodeTreeUIStorage *tree_ui_storage = data->node_tree.ui_storage;
if (tree_ui_storage == nullptr) {
return;
}
const NodeUIStorage *ui_storage = BKE_node_tree_ui_storage_get_from_context(
C, data->node_tree, data->node);
if (ui_storage == nullptr) {
return;
}
const MultiValueMap<std::string, AvailableAttributeInfo> &attribute_hints =
ui_storage->attribute_hints;
const Set<AvailableAttributeInfo> &attribute_hints = ui_storage->attribute_hints;
if (str[0] != '\0' && attribute_hints.lookup_as(StringRef(str)).is_empty()) {
/* Any string may be valid, so add the current search string with the hints. */
UI_search_item_add(items, str, (void *)str, ICON_ADD, 0, 0);
/* Any string may be valid, so add the current search string along with the hints. */
if (str[0] != '\0') {
/* Note that the attribute domain and data type are dummies, since
* #AvailableAttributeInfo equality is only based on the string. */
if (!attribute_hints.contains(AvailableAttributeInfo{str, ATTR_DOMAIN_AUTO, CD_PROP_BOOL})) {
tree_ui_storage->dummy_info_for_search.name = std::string(str);
UI_search_item_add(items, str, &tree_ui_storage->dummy_info_for_search, ICON_ADD, 0, 0);
}
}
if (str[0] == '\0' && !is_first) {
/* Allow clearing the text field when the string is empty, but not on the first pass,
* or opening an attribute field for the first time would show this search item. */
UI_search_item_add(items, str, (void *)str, ICON_X, 0, 0);
tree_ui_storage->dummy_info_for_search.name = std::string(str);
UI_search_item_add(items, str, &tree_ui_storage->dummy_info_for_search, ICON_X, 0, 0);
}
/* Don't filter when the menu is first opened, but still run the search
@ -82,16 +120,16 @@ static void attribute_search_update_fn(
const char *string = is_first ? "" : str;
StringSearch *search = BLI_string_search_new();
for (const std::string &attribute_name : attribute_hints.keys()) {
BLI_string_search_add(search, attribute_name.c_str(), (void *)&attribute_name);
for (const AvailableAttributeInfo &item : attribute_hints) {
BLI_string_search_add(search, item.name.c_str(), (void *)&item);
}
std::string **filtered_items;
AvailableAttributeInfo **filtered_items;
const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
for (const int i : IndexRange(filtered_amount)) {
std::string *item = filtered_items[i];
if (!UI_search_item_add(items, item->c_str(), item, ICON_NONE, 0, 0)) {
const AvailableAttributeInfo *item = filtered_items[i];
if (!attribute_search_item_add(items, *item)) {
break;
}
}
@ -100,12 +138,14 @@ static void attribute_search_update_fn(
BLI_string_search_free(search);
}
static void attribute_search_free_fn(void *arg)
static void attribute_search_exec_fn(bContext *UNUSED(C), void *data_v, void *item_v)
{
AttributeSearchData *data = static_cast<AttributeSearchData *>(arg);
AttributeSearchData *data = static_cast<AttributeSearchData *>(data_v);
AvailableAttributeInfo *item = static_cast<AvailableAttributeInfo *>(item_v);
UI_butstore_free(data->button_store_block, data->button_store);
delete data;
bNodeSocket &socket = data->socket;
bNodeSocketValueString *value = static_cast<bNodeSocketValueString *>(socket.default_value);
BLI_strncpy(value->value, item->name.c_str(), MAX_NAME);
}
void node_geometry_add_attribute_search_button(const bNodeTree *node_tree,
@ -132,22 +172,17 @@ void node_geometry_add_attribute_search_button(const bNodeTree *node_tree,
0.0f,
"");
AttributeSearchData *data = new AttributeSearchData{
*node_tree,
*node,
but,
UI_butstore_create(block),
block,
};
UI_butstore_register(data->button_store, &data->search_button);
AttributeSearchData *data = OBJECT_GUARDED_NEW(
AttributeSearchData, {*node_tree, *node, *static_cast<bNodeSocket *>(socket_ptr->data)});
UI_but_func_search_set_results_are_suggestions(but, true);
UI_but_func_search_set_sep_string(but, MENU_SEP);
UI_but_func_search_set(but,
nullptr,
attribute_search_update_fn,
static_cast<void *>(data),
attribute_search_free_fn,
true,
nullptr,
attribute_search_exec_fn,
nullptr);
}

Some files were not shown because too many files have changed in this diff Show More