Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClément Foucault <foucault.clem@gmail.com>2022-01-28 01:04:17 +0300
committerClément Foucault <foucault.clem@gmail.com>2022-01-28 01:04:17 +0300
commitb6ac95145cf616a36e58c846e7fccbf7ccc69faa (patch)
tree916ec4c6900dbe1709002dcb41c2ceebdf983b67
parent79c90bd49fafd050883161798a45427d60ffd0ed (diff)
parentda848b7440ab8878b350f764dfbf70729e43021d (diff)
Merge branch 'master' into temp-gpu-image-enginetemp-gpu-image-engine
-rw-r--r--build_files/build_environment/cmake/versions.cmake4
-rwxr-xr-xbuild_files/build_environment/install_deps.sh2
-rw-r--r--build_files/cmake/platform/platform_apple.cmake13
-rw-r--r--build_files/cmake/platform/platform_win32.cmake2
-rw-r--r--source/blender/blenkernel/BKE_camera.h2
-rw-r--r--source/blender/blenkernel/BKE_customdata.h6
-rw-r--r--source/blender/blenkernel/intern/camera.c2
-rw-r--r--source/blender/blenkernel/intern/curve_to_mesh_convert.cc4
-rw-r--r--source/blender/blenkernel/intern/customdata.cc7
-rw-r--r--source/blender/blenkernel/intern/mesh_mirror.c4
-rw-r--r--source/blender/blenlib/BLI_float4x4.hh14
-rw-r--r--source/blender/blenlib/BLI_path_util.h5
-rw-r--r--source/blender/blenlib/intern/path_util.c18
-rw-r--r--source/blender/draw/engines/workbench/workbench_shader.cc2
-rw-r--r--source/blender/draw/intern/DRW_gpu_wrapper.hh56
-rw-r--r--source/blender/draw/intern/DRW_render.h8
-rw-r--r--source/blender/draw/intern/draw_manager_data.c4
-rw-r--r--source/blender/draw/intern/draw_manager_exec.c4
-rw-r--r--source/blender/draw/intern/draw_manager_shader.c4
-rw-r--r--source/blender/editors/interface/interface_context_menu.c14
-rw-r--r--source/blender/editors/interface/interface_ops.c6
-rw-r--r--source/blender/editors/io/io_usd.c55
-rw-r--r--source/blender/editors/object/object_data_transfer.c15
-rw-r--r--source/blender/editors/object/object_edit.c10
-rw-r--r--source/blender/editors/space_outliner/outliner_select.cc21
-rw-r--r--source/blender/gpu/GPU_texture.h2
-rw-r--r--source/blender/gpu/intern/gpu_shader_dependency.cc20
-rw-r--r--source/blender/gpu/intern/gpu_viewport.c11
-rw-r--r--source/blender/gpu/opengl/gl_framebuffer.cc7
-rw-r--r--source/blender/io/usd/CMakeLists.txt2
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.cc39
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.h2
-rw-r--r--source/blender/io/usd/intern/usd_writer_material.cc767
-rw-r--r--source/blender/io/usd/intern/usd_writer_material.h55
-rw-r--r--source/blender/io/usd/intern/usd_writer_mesh.cc4
-rw-r--r--source/blender/io/usd/usd.h4
-rw-r--r--source/blender/makesrna/intern/rna_ID.c23
-rw-r--r--source/blender/modifiers/intern/MOD_mirror.c3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc72
-rw-r--r--source/blender/render/RE_pipeline.h10
-rw-r--r--source/blender/render/intern/initrender.c8
-rw-r--r--source/blender/render/intern/texture_margin.cc23
-rw-r--r--source/blender/windowmanager/intern/wm_event_system.c114
-rw-r--r--source/creator/CMakeLists.txt6
44 files changed, 1254 insertions, 200 deletions
diff --git a/build_files/build_environment/cmake/versions.cmake b/build_files/build_environment/cmake/versions.cmake
index 83b8c2c5e8b..c5f9aecabbf 100644
--- a/build_files/build_environment/cmake/versions.cmake
+++ b/build_files/build_environment/cmake/versions.cmake
@@ -221,7 +221,9 @@ set(URLLIB3_VERSION 1.26.8)
set(CERTIFI_VERSION 2021.10.8)
set(REQUESTS_VERSION 2.27.1)
set(CYTHON_VERSION 0.29.26)
-set(ZSTANDARD_VERSION 0.17.0 )
+# The version of the zstd library used to build the Python package should match ZSTD_VERSION defined below.
+# At this time of writing, 0.17.0 was already released, but built against zstd 1.5.1, while we use 1.5.0.
+set(ZSTANDARD_VERSION 0.16.0)
set(NUMPY_VERSION 1.22.0)
set(NUMPY_SHORT_VERSION 1.22)
diff --git a/build_files/build_environment/install_deps.sh b/build_files/build_environment/install_deps.sh
index 5edc7250608..03a75468400 100755
--- a/build_files/build_environment/install_deps.sh
+++ b/build_files/build_environment/install_deps.sh
@@ -414,7 +414,7 @@ PYTHON_REQUESTS_VERSION_MIN="2.0"
PYTHON_REQUESTS_VERSION_MEX="3.0"
PYTHON_REQUESTS_NAME="requests"
-PYTHON_ZSTANDARD_VERSION="0.17.0"
+PYTHON_ZSTANDARD_VERSION="0.16.0"
PYTHON_ZSTANDARD_VERSION_MIN="0.15.2"
PYTHON_ZSTANDARD_VERSION_MEX="0.20.0"
PYTHON_ZSTANDARD_NAME="zstandard"
diff --git a/build_files/cmake/platform/platform_apple.cmake b/build_files/cmake/platform/platform_apple.cmake
index 8acea1be841..379370a989a 100644
--- a/build_files/cmake/platform/platform_apple.cmake
+++ b/build_files/cmake/platform/platform_apple.cmake
@@ -128,25 +128,20 @@ if(WITH_CODEC_SNDFILE)
endif()
if(WITH_PYTHON)
- # we use precompiled libraries for py 3.9 and up by default
- set(PYTHON_VERSION 3.9)
+ # Use precompiled libraries by default.
+ set(PYTHON_VERSION 3.10)
if(NOT WITH_PYTHON_MODULE AND NOT WITH_PYTHON_FRAMEWORK)
- # normally cached but not since we include them with blender
+ # Normally cached but not since we include them with blender.
set(PYTHON_INCLUDE_DIR "${LIBDIR}/python/include/python${PYTHON_VERSION}")
set(PYTHON_EXECUTABLE "${LIBDIR}/python/bin/python${PYTHON_VERSION}")
set(PYTHON_LIBRARY ${LIBDIR}/python/lib/libpython${PYTHON_VERSION}.a)
set(PYTHON_LIBPATH "${LIBDIR}/python/lib/python${PYTHON_VERSION}")
- # set(PYTHON_LINKFLAGS "-u _PyMac_Error") # won't build with this enabled
else()
- # module must be compiled against Python framework
+ # Module must be compiled against Python framework.
set(_py_framework "/Library/Frameworks/Python.framework/Versions/${PYTHON_VERSION}")
-
set(PYTHON_INCLUDE_DIR "${_py_framework}/include/python${PYTHON_VERSION}")
set(PYTHON_EXECUTABLE "${_py_framework}/bin/python${PYTHON_VERSION}")
set(PYTHON_LIBPATH "${_py_framework}/lib/python${PYTHON_VERSION}")
- # set(PYTHON_LIBRARY python${PYTHON_VERSION})
- # set(PYTHON_LINKFLAGS "-u _PyMac_Error -framework Python") # won't build with this enabled
-
unset(_py_framework)
endif()
diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake
index 9f565e44d8f..220b67bc1e3 100644
--- a/build_files/cmake/platform/platform_win32.cmake
+++ b/build_files/cmake/platform/platform_win32.cmake
@@ -463,7 +463,7 @@ if(WITH_JACK)
endif()
if(WITH_PYTHON)
- set(PYTHON_VERSION 3.9) # CACHE STRING)
+ set(PYTHON_VERSION 3.10) # CACHE STRING)
string(REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${PYTHON_VERSION})
set(PYTHON_LIBRARY ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/libs/python${_PYTHON_VERSION_NO_DOTS}.lib)
diff --git a/source/blender/blenkernel/BKE_camera.h b/source/blender/blenkernel/BKE_camera.h
index ee78621c11f..550ce4eb601 100644
--- a/source/blender/blenkernel/BKE_camera.h
+++ b/source/blender/blenkernel/BKE_camera.h
@@ -46,7 +46,7 @@ void *BKE_camera_add(struct Main *bmain, const char *name);
/**
* Get the camera's DOF value, takes the DOF object into account.
*/
-float BKE_camera_object_dof_distance(struct Object *ob);
+float BKE_camera_object_dof_distance(const struct Object *ob);
int BKE_camera_sensor_fit(int sensor_fit, float sizex, float sizey);
float BKE_camera_sensor_size(int sensor_fit, float sensor_x, float sensor_y);
diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h
index 00eae2e8e6e..76389b0c66f 100644
--- a/source/blender/blenkernel/BKE_customdata.h
+++ b/source/blender/blenkernel/BKE_customdata.h
@@ -437,6 +437,12 @@ int CustomData_get_clone_layer(const struct CustomData *data, int type);
int CustomData_get_stencil_layer(const struct CustomData *data, int type);
/**
+ * Returns name of the active layer of the given type or NULL
+ * if no such active layer is defined.
+ */
+const char *CustomData_get_active_layer_name(const struct CustomData *data, int type);
+
+/**
* Copies the data from source to the data element at index in the first layer of type
* no effect if there is no layer of type.
*/
diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c
index 7940936b64a..d9c637eb177 100644
--- a/source/blender/blenkernel/intern/camera.c
+++ b/source/blender/blenkernel/intern/camera.c
@@ -218,7 +218,7 @@ void *BKE_camera_add(Main *bmain, const char *name)
return cam;
}
-float BKE_camera_object_dof_distance(Object *ob)
+float BKE_camera_object_dof_distance(const Object *ob)
{
Camera *cam = (Camera *)ob->data;
if (ob->type != OB_CAMERA) {
diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
index 073d9d18a04..833b2fe99ec 100644
--- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
+++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
@@ -66,8 +66,8 @@ static void vert_extrude_to_mesh_data(const Spline &spline,
if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) {
MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1];
- edge.v1 = vert_offset;
- edge.v2 = vert_offset + eval_size - 1;
+ edge.v1 = vert_offset + eval_size - 1;
+ edge.v2 = vert_offset;
edge.flag = ME_LOOSEEDGE;
}
diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc
index 5e3beab9b72..02b50027ef9 100644
--- a/source/blender/blenkernel/intern/customdata.cc
+++ b/source/blender/blenkernel/intern/customdata.cc
@@ -2427,6 +2427,13 @@ int CustomData_get_stencil_layer(const CustomData *data, int type)
return (layer_index != -1) ? data->layers[layer_index].active_mask : -1;
}
+const char *CustomData_get_active_layer_name(const struct CustomData *data, const int type)
+{
+ /* Get the layer index of the active layer of this type. */
+ const int layer_index = CustomData_get_active_layer_index(data, type);
+ return layer_index < 0 ? NULL : data->layers[layer_index].name;
+}
+
void CustomData_set_layer_active(CustomData *data, int type, int n)
{
for (int i = 0; i < data->totlayer; i++) {
diff --git a/source/blender/blenkernel/intern/mesh_mirror.c b/source/blender/blenkernel/intern/mesh_mirror.c
index abc0b518d92..ec3655ac491 100644
--- a/source/blender/blenkernel/intern/mesh_mirror.c
+++ b/source/blender/blenkernel/intern/mesh_mirror.c
@@ -420,7 +420,7 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd,
/* calculate custom normals into loop_normals, then mirror first half into second half */
BKE_mesh_normals_loop_split(result->mvert,
- BKE_mesh_vertex_normals_ensure(mesh),
+ BKE_mesh_vertex_normals_ensure(result),
result->totvert,
result->medge,
result->totedge,
@@ -428,7 +428,7 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd,
loop_normals,
totloop,
result->mpoly,
- BKE_mesh_poly_normals_ensure(mesh),
+ BKE_mesh_poly_normals_ensure(result),
totpoly,
true,
mesh->smoothresh,
diff --git a/source/blender/blenlib/BLI_float4x4.hh b/source/blender/blenlib/BLI_float4x4.hh
index 81c969d02d0..f451f58f6cf 100644
--- a/source/blender/blenlib/BLI_float4x4.hh
+++ b/source/blender/blenlib/BLI_float4x4.hh
@@ -107,6 +107,20 @@ struct float4x4 {
return &values[0][0];
}
+ float *operator[](const int64_t index)
+ {
+ BLI_assert(index >= 0);
+ BLI_assert(index < 4);
+ return &values[index][0];
+ }
+
+ const float *operator[](const int64_t index) const
+ {
+ BLI_assert(index >= 0);
+ BLI_assert(index < 4);
+ return &values[index][0];
+ }
+
using c_style_float4x4 = float[4][4];
c_style_float4x4 &ptr()
{
diff --git a/source/blender/blenlib/BLI_path_util.h b/source/blender/blenlib/BLI_path_util.h
index 658cc0c3825..a2bb8ca47c5 100644
--- a/source/blender/blenlib/BLI_path_util.h
+++ b/source/blender/blenlib/BLI_path_util.h
@@ -381,6 +381,11 @@ void BLI_path_normalize_unc(char *path_16, int maxlen);
#endif
/**
+ * Returns true if the given paths are equal.
+ */
+bool BLI_paths_equal(const char *p1, const char *p2);
+
+/**
* Appends a suffix to the string, fitting it before the extension
*
* string = Foo.png, suffix = 123, separator = _
diff --git a/source/blender/blenlib/intern/path_util.c b/source/blender/blenlib/intern/path_util.c
index 64bde1193a6..250415c11f9 100644
--- a/source/blender/blenlib/intern/path_util.c
+++ b/source/blender/blenlib/intern/path_util.c
@@ -1829,3 +1829,21 @@ void BLI_path_slash_native(char *path)
BLI_str_replace_char(path + BLI_path_unc_prefix_len(path), ALTSEP, SEP);
#endif
}
+
+bool BLI_paths_equal(const char *p1, const char *p2)
+{
+ /* Normalize the paths so we can compare them. */
+ char norm_p1[FILE_MAX];
+ char norm_p2[FILE_MAX];
+
+ BLI_strncpy(norm_p1, p1, sizeof(norm_p1));
+ BLI_strncpy(norm_p2, p2, sizeof(norm_p2));
+
+ BLI_path_slash_native(norm_p1);
+ BLI_path_slash_native(norm_p2);
+
+ BLI_path_normalize(NULL, norm_p1);
+ BLI_path_normalize(NULL, norm_p2);
+
+ return BLI_path_cmp(norm_p1, norm_p2) == 0;
+}
diff --git a/source/blender/draw/engines/workbench/workbench_shader.cc b/source/blender/draw/engines/workbench/workbench_shader.cc
index 011a3fd3b13..bbc0bc02b03 100644
--- a/source/blender/draw/engines/workbench/workbench_shader.cc
+++ b/source/blender/draw/engines/workbench/workbench_shader.cc
@@ -64,7 +64,7 @@ static struct {
struct GPUShader *volume_sh[2][2][3][2];
-} e_data = {{{{NULL}}}};
+} e_data = {{{{nullptr}}}};
/* -------------------------------------------------------------------- */
/** \name Conversions
diff --git a/source/blender/draw/intern/DRW_gpu_wrapper.hh b/source/blender/draw/intern/DRW_gpu_wrapper.hh
index 7a9bdb377fe..af262272577 100644
--- a/source/blender/draw/intern/DRW_gpu_wrapper.hh
+++ b/source/blender/draw/intern/DRW_gpu_wrapper.hh
@@ -72,10 +72,7 @@
#include "draw_texture_pool.h"
-#include "BLI_float4.hh"
-#include "BLI_int2.hh"
-#include "BLI_int3.hh"
-#include "BLI_int4.hh"
+#include "BLI_math_vec_types.hh"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
#include "BLI_utility_mixins.hh"
@@ -105,7 +102,8 @@ class DataBuffer {
T *data_ = nullptr;
int64_t len_ = len;
- BLI_STATIC_ASSERT((sizeof(T) % 16) == 0, "Type need to be aligned to size of float4.");
+ BLI_STATIC_ASSERT(((sizeof(T) * len) % 16) == 0,
+ "Buffer size need to be aligned to size of float4.");
public:
/**
@@ -295,7 +293,7 @@ class UniformArrayBuffer : public detail::UniformCommon<T, len, false> {
UniformArrayBuffer()
{
/* TODO(fclem) We should map memory instead. */
- this->data_ = MEM_mallocN_aligned(this->name_);
+ this->data_ = (T *)MEM_mallocN_aligned(len * sizeof(T), 16, this->name_);
}
};
@@ -484,7 +482,7 @@ class Texture : NonCopyable {
* Ensure the texture has the correct properties. Recreating it if needed.
* Return true if a texture has been created.
*/
- bool ensure_2d(eGPUTextureFormat format, const int2 &extent, float *data = nullptr, int mips = 1)
+ bool ensure_2d(eGPUTextureFormat format, int2 extent, float *data = nullptr, int mips = 1)
{
return ensure_impl(UNPACK2(extent), 0, mips, format, data, false, false);
}
@@ -493,11 +491,8 @@ class Texture : NonCopyable {
* Ensure the texture has the correct properties. Recreating it if needed.
* Return true if a texture has been created.
*/
- bool ensure_2d_array(eGPUTextureFormat format,
- const int2 &extent,
- int layers,
- float *data = nullptr,
- int mips = 1)
+ bool ensure_2d_array(
+ eGPUTextureFormat format, int2 extent, int layers, float *data = nullptr, int mips = 1)
{
return ensure_impl(UNPACK2(extent), layers, mips, format, data, true, false);
}
@@ -506,7 +501,7 @@ class Texture : NonCopyable {
* Ensure the texture has the correct properties. Recreating it if needed.
* Return true if a texture has been created.
*/
- bool ensure_3d(eGPUTextureFormat format, const int3 &extent, float *data = nullptr, int mips = 1)
+ bool ensure_3d(eGPUTextureFormat format, int3 extent, float *data = nullptr, int mips = 1)
{
return ensure_impl(UNPACK3(extent), mips, format, data, false, false);
}
@@ -599,14 +594,6 @@ class Texture : NonCopyable {
/**
* Clear the entirety of the texture using one pixel worth of data.
*/
- void clear(uchar4 values)
- {
- GPU_texture_clear(tx_, GPU_DATA_UBYTE, &values[0]);
- }
-
- /**
- * Clear the entirety of the texture using one pixel worth of data.
- */
void clear(int4 values)
{
GPU_texture_clear(tx_, GPU_DATA_INT, &values[0]);
@@ -634,6 +621,15 @@ class Texture : NonCopyable {
GPU_TEXTURE_FREE_SAFE(tx_);
}
+ /**
+ * Swap the content of the two textures.
+ */
+ static void swap(Texture &a, Texture &b)
+ {
+ SWAP(GPUTexture *, a.tx_, b.tx_);
+ SWAP(const char *, a.name_, b.name_);
+ }
+
private:
bool ensure_impl(int w,
int h = 0,
@@ -713,7 +709,7 @@ class TextureFromPool : public Texture, NonMovable {
TextureFromPool(const char *name = "gpu::Texture") : Texture(name){};
/* Always use `release()` after rendering. */
- void acquire(int w, int h, eGPUTextureFormat format, void *owner_)
+ void acquire(int2 extent, eGPUTextureFormat format, void *owner_)
{
if (this->tx_ == nullptr) {
if (tx_tmp_saved_ != nullptr) {
@@ -721,7 +717,7 @@ class TextureFromPool : public Texture, NonMovable {
return;
}
DrawEngineType *owner = (DrawEngineType *)owner_;
- this->tx_ = DRW_texture_pool_query_2d(w, h, format, owner);
+ this->tx_ = DRW_texture_pool_query_2d(UNPACK2(extent), format, owner);
}
}
@@ -750,11 +746,6 @@ class TextureFromPool : public Texture, NonMovable {
bool ensure_cube_array(int, int, int, eGPUTextureFormat, float *) = delete;
void filter_mode(bool) = delete;
void free() = delete;
- /**
- * Forbid the use of DRW_shgroup_uniform_texture.
- * Use DRW_shgroup_uniform_texture_ref instead.
- */
- operator GPUTexture *() const = delete;
};
/** \} */
@@ -805,6 +796,15 @@ class Framebuffer : NonCopyable {
{
return fb_;
}
+
+ /**
+ * Swap the content of the two framebuffer.
+ */
+ static void swap(Framebuffer &a, Framebuffer &b)
+ {
+ SWAP(GPUFrameBuffer *, a.fb_, b.fb_);
+ SWAP(const char *, a.name_, b.name_);
+ }
};
/** \} */
diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h
index b16caf49209..8c56d21746d 100644
--- a/source/blender/draw/intern/DRW_render.h
+++ b/source/blender/draw/intern/DRW_render.h
@@ -293,7 +293,9 @@ DRWShaderLibrary *DRW_shader_library_create(void);
/**
* \warning Each library must be added after all its dependencies.
*/
-void DRW_shader_library_add_file(DRWShaderLibrary *lib, char *lib_code, const char *lib_name);
+void DRW_shader_library_add_file(DRWShaderLibrary *lib,
+ const char *lib_code,
+ const char *lib_name);
#define DRW_SHADER_LIB_ADD(lib, lib_name) \
DRW_shader_library_add_file(lib, datatoc_##lib_name##_glsl, STRINGIFY(lib_name) ".glsl")
@@ -696,7 +698,7 @@ const DRWView *DRW_view_default_get(void);
/**
* MUST only be called once per render and only in render mode. Sets default view.
*/
-void DRW_view_default_set(DRWView *view);
+void DRW_view_default_set(const DRWView *view);
/**
* \warning Only use in render AND only if you are going to set view_default again.
*/
@@ -704,7 +706,7 @@ void DRW_view_reset(void);
/**
* Set active view for rendering.
*/
-void DRW_view_set_active(DRWView *view);
+void DRW_view_set_active(const DRWView *view);
const DRWView *DRW_view_get_active(void);
/**
diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c
index ab570667a77..a4d5d6f3c31 100644
--- a/source/blender/draw/intern/draw_manager_data.c
+++ b/source/blender/draw/intern/draw_manager_data.c
@@ -1904,10 +1904,10 @@ void DRW_view_reset(void)
DST.view_previous = NULL;
}
-void DRW_view_default_set(DRWView *view)
+void DRW_view_default_set(const DRWView *view)
{
BLI_assert(DST.view_default == NULL);
- DST.view_default = view;
+ DST.view_default = (DRWView *)view;
}
void DRW_view_clip_planes_set(DRWView *view, float (*planes)[4], int plane_len)
diff --git a/source/blender/draw/intern/draw_manager_exec.c b/source/blender/draw/intern/draw_manager_exec.c
index 8dd24c01337..2095a8483d6 100644
--- a/source/blender/draw/intern/draw_manager_exec.c
+++ b/source/blender/draw/intern/draw_manager_exec.c
@@ -354,9 +354,9 @@ static bool draw_call_is_culled(const DRWResourceHandle *handle, DRWView *view)
return (culling->mask & view->culling_mask) != 0;
}
-void DRW_view_set_active(DRWView *view)
+void DRW_view_set_active(const DRWView *view)
{
- DST.view_active = (view) ? view : DST.view_default;
+ DST.view_active = (view != NULL) ? ((DRWView *)view) : DST.view_default;
}
const DRWView *DRW_view_get_active(void)
diff --git a/source/blender/draw/intern/draw_manager_shader.c b/source/blender/draw/intern/draw_manager_shader.c
index 84440a8effe..f8e64041a92 100644
--- a/source/blender/draw/intern/draw_manager_shader.c
+++ b/source/blender/draw/intern/draw_manager_shader.c
@@ -567,7 +567,7 @@ void DRW_shader_free(GPUShader *shader)
#define MAX_LIB_DEPS 8
struct DRWShaderLibrary {
- char *libs[MAX_LIB];
+ const char *libs[MAX_LIB];
char libs_name[MAX_LIB][MAX_LIB_NAME];
uint32_t libs_deps[MAX_LIB];
};
@@ -629,7 +629,7 @@ static uint32_t drw_shader_dependencies_get(const DRWShaderLibrary *lib, const c
return deps;
}
-void DRW_shader_library_add_file(DRWShaderLibrary *lib, char *lib_code, const char *lib_name)
+void DRW_shader_library_add_file(DRWShaderLibrary *lib, const char *lib_code, const char *lib_name)
{
int index = -1;
for (int i = 0; i < MAX_LIB; i++) {
diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c
index 190b2d12ed9..dd5ce118d5f 100644
--- a/source/blender/editors/interface/interface_context_menu.c
+++ b/source/blender/editors/interface/interface_context_menu.c
@@ -809,12 +809,18 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev
else {
if (is_array_component) {
ot = WM_operatortype_find("UI_OT_override_type_set_button", false);
- uiItemFullO_ptr(
- layout, ot, "Define Overrides", ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr);
+ uiItemFullO_ptr(layout,
+ ot,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Define Overrides"),
+ ICON_NONE,
+ NULL,
+ WM_OP_INVOKE_DEFAULT,
+ 0,
+ &op_ptr);
RNA_boolean_set(&op_ptr, "all", true);
uiItemFullO_ptr(layout,
ot,
- "Define Single Override",
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Define Single Override"),
ICON_NONE,
NULL,
WM_OP_INVOKE_DEFAULT,
@@ -825,7 +831,7 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev
else {
uiItemFullO(layout,
"UI_OT_override_type_set_button",
- "Define Override",
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Define Override"),
ICON_NONE,
NULL,
WM_OP_INVOKE_DEFAULT,
diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c
index f7424066ad8..33c6e382f50 100644
--- a/source/blender/editors/interface/interface_ops.c
+++ b/source/blender/editors/interface/interface_ops.c
@@ -597,6 +597,9 @@ static int override_type_set_button_exec(bContext *C, wmOperator *op)
opop->operation = operation;
}
+ /* Outliner e.g. has to be aware of this change. */
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
+
return operator_button_property_finish(C, &ptr, prop);
}
@@ -714,6 +717,9 @@ static int override_remove_button_exec(bContext *C, wmOperator *op)
}
}
+ /* Outliner e.g. has to be aware of this change. */
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
+
return operator_button_property_finish(C, &ptr, prop);
}
diff --git a/source/blender/editors/io/io_usd.c b/source/blender/editors/io/io_usd.c
index 4e2ccea36ab..49d60ede277 100644
--- a/source/blender/editors/io/io_usd.c
+++ b/source/blender/editors/io/io_usd.c
@@ -131,6 +131,11 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool use_instancing = RNA_boolean_get(op->ptr, "use_instancing");
const bool evaluation_mode = RNA_enum_get(op->ptr, "evaluation_mode");
+ const bool generate_preview_surface = RNA_boolean_get(op->ptr, "generate_preview_surface");
+ const bool export_textures = RNA_boolean_get(op->ptr, "export_textures");
+ const bool overwrite_textures = RNA_boolean_get(op->ptr, "overwrite_textures");
+ const bool relative_texture_paths = RNA_boolean_get(op->ptr, "relative_texture_paths");
+
struct USDExportParams params = {
export_animation,
export_hair,
@@ -141,6 +146,10 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
visible_objects_only,
use_instancing,
evaluation_mode,
+ generate_preview_surface,
+ export_textures,
+ overwrite_textures,
+ relative_texture_paths,
};
bool ok = USD_export(C, filename, &params, as_background_job);
@@ -173,6 +182,26 @@ static void wm_usd_export_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(col, ptr, "evaluation_mode", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
+ col = uiLayoutColumnWithHeading(box, true, IFACE_("Materials"));
+ uiItemR(col, ptr, "generate_preview_surface", 0, NULL, ICON_NONE);
+ const bool export_mtl = RNA_boolean_get(ptr, "export_materials");
+ uiLayoutSetActive(col, export_mtl);
+
+ uiLayout *row = uiLayoutRow(col, true);
+ uiItemR(row, ptr, "export_textures", 0, NULL, ICON_NONE);
+ const bool preview = RNA_boolean_get(ptr, "generate_preview_surface");
+ uiLayoutSetActive(row, export_mtl && preview);
+
+ row = uiLayoutRow(col, true);
+ uiItemR(row, ptr, "overwrite_textures", 0, NULL, ICON_NONE);
+ const bool export_tex = RNA_boolean_get(ptr, "export_textures");
+ uiLayoutSetActive(row, export_mtl && preview && export_tex);
+
+ row = uiLayoutRow(col, true);
+ uiItemR(row, ptr, "relative_texture_paths", 0, NULL, ICON_NONE);
+ uiLayoutSetActive(row, export_mtl && preview);
+
+ box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Experimental"), ICON_NONE);
uiItemR(box, ptr, "use_instancing", 0, NULL, ICON_NONE);
}
@@ -249,6 +278,32 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
"Use Settings for",
"Determines visibility of objects, modifier settings, and other areas where there "
"are different settings for viewport and rendering");
+
+ RNA_def_boolean(ot->srna,
+ "generate_preview_surface",
+ true,
+ "To USD Preview Surface",
+ "Generate an approximate USD Preview Surface shader "
+ "representation of a Principled BSDF node network");
+
+ RNA_def_boolean(ot->srna,
+ "export_textures",
+ true,
+ "Export Textures",
+ "If exporting materials, export textures referenced by material nodes "
+ "to a 'textures' directory in the same directory as the USD file");
+
+ RNA_def_boolean(ot->srna,
+ "overwrite_textures",
+ false,
+ "Overwrite Textures",
+ "Allow overwriting existing texture files when exporting textures");
+
+ RNA_def_boolean(ot->srna,
+ "relative_texture_paths",
+ true,
+ "Relative Texture Paths",
+ "Make texture asset paths relative to the USD file");
}
/* ====== USD Import ====== */
diff --git a/source/blender/editors/object/object_data_transfer.c b/source/blender/editors/object/object_data_transfer.c
index 49149a5152f..595822de1e7 100644
--- a/source/blender/editors/object/object_data_transfer.c
+++ b/source/blender/editors/object/object_data_transfer.c
@@ -603,6 +603,20 @@ static bool data_transfer_poll_property(const bContext *UNUSED(C),
return true;
}
+static char *data_transfer_get_description(bContext *UNUSED(C),
+ wmOperatorType *UNUSED(ot),
+ PointerRNA *ptr)
+{
+ const bool reverse_transfer = RNA_boolean_get(ptr, "use_reverse_transfer");
+
+ if (reverse_transfer) {
+ return BLI_strdup(
+ "Transfer data layer(s) (weights, edge sharp, etc.) from selected meshes to active one");
+ }
+
+ return NULL;
+}
+
void OBJECT_OT_data_transfer(wmOperatorType *ot)
{
PropertyRNA *prop;
@@ -619,6 +633,7 @@ void OBJECT_OT_data_transfer(wmOperatorType *ot)
ot->invoke = WM_menu_invoke;
ot->exec = data_transfer_exec;
ot->check = data_transfer_check;
+ ot->get_description = data_transfer_get_description;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 38d0a044cb4..a38a5165c8c 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -1976,8 +1976,14 @@ static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *
RNA_int_set(&menu->ptr, "collection_index", menu->index);
RNA_boolean_set(&menu->ptr, "is_new", true);
- uiItemFullO_ptr(
- layout, menu->ot, "New Collection", ICON_ADD, menu->ptr.data, WM_OP_INVOKE_DEFAULT, 0, NULL);
+ uiItemFullO_ptr(layout,
+ menu->ot,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "New Collection"),
+ ICON_ADD,
+ menu->ptr.data,
+ WM_OP_INVOKE_DEFAULT,
+ 0,
+ NULL);
uiItemS(layout);
diff --git a/source/blender/editors/space_outliner/outliner_select.cc b/source/blender/editors/space_outliner/outliner_select.cc
index 3ff8b9e973f..ebb4e529b04 100644
--- a/source/blender/editors/space_outliner/outliner_select.cc
+++ b/source/blender/editors/space_outliner/outliner_select.cc
@@ -1611,8 +1611,7 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
if (!(te = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]))) {
if (deselect_all) {
- outliner_flag_set(&space_outliner->tree, TSE_SELECTED, false);
- changed = true;
+ changed |= outliner_flag_set(&space_outliner->tree, TSE_SELECTED, false);
}
}
/* Don't allow toggle on scene collection */
@@ -1660,17 +1659,19 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
changed = true;
}
- if (changed) {
- if (rebuild_tree) {
- ED_region_tag_redraw(region);
- }
- else {
- ED_region_tag_redraw_no_rebuild(region);
- }
+ if (!changed) {
+ return OPERATOR_CANCELLED;
+ }
- ED_outliner_select_sync_from_outliner(C, space_outliner);
+ if (rebuild_tree) {
+ ED_region_tag_redraw(region);
+ }
+ else {
+ ED_region_tag_redraw_no_rebuild(region);
}
+ ED_outliner_select_sync_from_outliner(C, space_outliner);
+
return OPERATOR_FINISHED;
}
diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h
index d65115541fa..6fae4a13918 100644
--- a/source/blender/gpu/GPU_texture.h
+++ b/source/blender/gpu/GPU_texture.h
@@ -272,7 +272,7 @@ void *GPU_texture_read(GPUTexture *tex, eGPUDataFormat data_format, int miplvl);
* Fills the whole texture with the same data for all pixels.
* \warning Only work for 2D texture for now.
* \warning Only clears the mip 0 of the texture.
- * \param data_format: data format of the pixel data.
+ * \param data_format: data format of the pixel data. \note The format is float for unorm textures.
* \param data: 1 pixel worth of data to fill the texture with.
*/
void GPU_texture_clear(GPUTexture *tex, eGPUDataFormat data_format, const void *data);
diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc
index 2333590810f..e8c9a1e6e52 100644
--- a/source/blender/gpu/intern/gpu_shader_dependency.cc
+++ b/source/blender/gpu/intern/gpu_shader_dependency.cc
@@ -292,17 +292,18 @@ struct GPUSource {
source = processed_source.c_str();
};
- void init_dependencies(const GPUSourceDictionnary &dict)
+ /* Return 1 one error. */
+ int init_dependencies(const GPUSourceDictionnary &dict)
{
if (dependencies_init) {
- return;
+ return 0;
}
dependencies_init = true;
int64_t pos = 0;
while (true) {
pos = source.find("pragma BLENDER_REQUIRE(", pos);
if (pos == -1) {
- return;
+ return 0;
}
int64_t start = source.find('(', pos) + 1;
int64_t end = source.find(')', pos);
@@ -310,7 +311,7 @@ struct GPUSource {
/* TODO Use clog. */
std::cout << "Error: " << filename << " : Malformed BLENDER_REQUIRE: Missing \")\"."
<< std::endl;
- return;
+ return 1;
}
StringRef dependency_name = source.substr(start, end - start);
GPUSource *dependency_source = dict.lookup_default(dependency_name, nullptr);
@@ -318,10 +319,13 @@ struct GPUSource {
/* TODO Use clog. */
std::cout << "Error: " << filename << " : Dependency not found \"" << dependency_name
<< "\"." << std::endl;
- return;
+ return 1;
}
/* Recursive. */
- dependency_source->init_dependencies(dict);
+ int result = dependency_source->init_dependencies(dict);
+ if (result != 0) {
+ return 1;
+ }
for (auto *dep : dependency_source->dependencies) {
dependencies.append_non_duplicates(dep);
@@ -358,9 +362,11 @@ void gpu_shader_dependency_init()
#include "glsl_gpu_source_list.h"
#undef SHADER_SOURCE
+ int errors = 0;
for (auto *value : g_sources->values()) {
- value->init_dependencies(*g_sources);
+ errors += value->init_dependencies(*g_sources);
}
+ BLI_assert_msg(errors == 0, "Dependency errors detected: Aborting");
}
void gpu_shader_dependency_exit()
diff --git a/source/blender/gpu/intern/gpu_viewport.c b/source/blender/gpu/intern/gpu_viewport.c
index 6d8ff8e7088..7a20278e5aa 100644
--- a/source/blender/gpu/intern/gpu_viewport.c
+++ b/source/blender/gpu/intern/gpu_viewport.c
@@ -137,25 +137,24 @@ struct DRWData **GPU_viewport_data_get(GPUViewport *viewport)
static void gpu_viewport_textures_create(GPUViewport *viewport)
{
int *size = viewport->size;
- float empty_pixel_fl[4] = {0.0f, 0.0f, 0.0f, 0.0f};
- uchar empty_pixel_u[4] = {0, 0, 0, 0};
+ float empty_pixel[4] = {0.0f, 0.0f, 0.0f, 0.0f};
if (viewport->color_render_tx[0] == NULL) {
viewport->color_render_tx[0] = GPU_texture_create_2d(
"dtxl_color", UNPACK2(size), 1, GPU_RGBA16F, NULL);
- GPU_texture_clear(viewport->color_render_tx[0], GPU_DATA_FLOAT, empty_pixel_fl);
+ GPU_texture_clear(viewport->color_render_tx[0], GPU_DATA_FLOAT, empty_pixel);
viewport->color_overlay_tx[0] = GPU_texture_create_2d(
"dtxl_color_overlay", UNPACK2(size), 1, GPU_SRGB8_A8, NULL);
- GPU_texture_clear(viewport->color_overlay_tx[0], GPU_DATA_UBYTE, empty_pixel_u);
+ GPU_texture_clear(viewport->color_overlay_tx[0], GPU_DATA_FLOAT, empty_pixel);
}
if ((viewport->flag & GPU_VIEWPORT_STEREO) != 0 && viewport->color_render_tx[1] == NULL) {
viewport->color_render_tx[1] = GPU_texture_create_2d(
"dtxl_color_stereo", UNPACK2(size), 1, GPU_RGBA16F, NULL);
- GPU_texture_clear(viewport->color_render_tx[1], GPU_DATA_FLOAT, empty_pixel_fl);
+ GPU_texture_clear(viewport->color_render_tx[1], GPU_DATA_FLOAT, empty_pixel);
viewport->color_overlay_tx[1] = GPU_texture_create_2d(
"dtxl_color_overlay_stereo", UNPACK2(size), 1, GPU_SRGB8_A8, NULL);
- GPU_texture_clear(viewport->color_overlay_tx[1], GPU_DATA_UBYTE, empty_pixel_u);
+ GPU_texture_clear(viewport->color_overlay_tx[1], GPU_DATA_FLOAT, empty_pixel);
}
/* Can be shared with GPUOffscreen. */
diff --git a/source/blender/gpu/opengl/gl_framebuffer.cc b/source/blender/gpu/opengl/gl_framebuffer.cc
index 13f03195437..106a12bfefd 100644
--- a/source/blender/gpu/opengl/gl_framebuffer.cc
+++ b/source/blender/gpu/opengl/gl_framebuffer.cc
@@ -429,8 +429,15 @@ void GLFrameBuffer::read(eGPUFrameBufferBits plane,
switch (plane) {
case GPU_DEPTH_BIT:
format = GL_DEPTH_COMPONENT;
+ BLI_assert_msg(
+ this->attachments_[GPU_FB_DEPTH_ATTACHMENT].tex != nullptr ||
+ this->attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex != nullptr,
+ "GPUFramebuffer: Error: Trying to read depth without a depth buffer attached.");
break;
case GPU_COLOR_BIT:
+ BLI_assert_msg(
+ mode != GL_NONE,
+ "GPUFramebuffer: Error: Trying to read a color slot without valid attachment.");
format = channel_len_to_gl(channel_len);
/* TODO: needed for selection buffers to work properly, this should be handled better. */
if (format == GL_RED && type == GL_UNSIGNED_INT) {
diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt
index 12015bf1698..980f33fffa1 100644
--- a/source/blender/io/usd/CMakeLists.txt
+++ b/source/blender/io/usd/CMakeLists.txt
@@ -64,6 +64,7 @@ set(SRC
intern/usd_writer_camera.cc
intern/usd_writer_hair.cc
intern/usd_writer_light.cc
+ intern/usd_writer_material.cc
intern/usd_writer_mesh.cc
intern/usd_writer_metaball.cc
intern/usd_writer_transform.cc
@@ -89,6 +90,7 @@ set(SRC
intern/usd_writer_camera.h
intern/usd_writer_hair.h
intern/usd_writer_light.h
+ intern/usd_writer_material.h
intern/usd_writer_mesh.h
intern/usd_writer_metaball.h
intern/usd_writer_transform.h
diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc
index 2b5326eb4c1..a358c563c88 100644
--- a/source/blender/io/usd/intern/usd_writer_abstract.cc
+++ b/source/blender/io/usd/intern/usd_writer_abstract.cc
@@ -18,11 +18,15 @@
*/
#include "usd_writer_abstract.h"
#include "usd_hierarchy_iterator.h"
+#include "usd_writer_material.h"
#include <pxr/base/tf/stringUtils.h>
+#include "BKE_customdata.h"
#include "BLI_assert.h"
+#include "DNA_mesh_types.h"
+
/* TfToken objects are not cheap to construct, so we do it once. */
namespace usdtokens {
/* Materials */
@@ -34,6 +38,19 @@ static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
} // namespace usdtokens
+static std::string get_mesh_active_uvlayer_name(const Object *ob)
+{
+ if (!ob || ob->type != OB_MESH || !ob->data) {
+ return "";
+ }
+
+ const Mesh *me = static_cast<Mesh *>(ob->data);
+
+ const char *name = CustomData_get_active_layer_name(&me->ldata, CD_MLOOPUV);
+
+ return name ? name : "";
+}
+
namespace blender::io::usd {
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
@@ -78,7 +95,8 @@ const pxr::SdfPath &USDAbstractWriter::usd_path() const
return usd_export_context_.usd_path;
}
-pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material)
+pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
+ Material *material)
{
static pxr::SdfPath material_library_path("/_materials");
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
@@ -92,17 +110,14 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material)
}
usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
- /* Construct the shader. */
- pxr::SdfPath shader_path = usd_path.AppendChild(usdtokens::preview_shader);
- pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(stage, shader_path);
- shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
- shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
- .Set(pxr::GfVec3f(material->r, material->g, material->b));
- shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
- shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
-
- /* Connect the shader and the material together. */
- usd_material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
+ if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
+ std::string active_uv = get_mesh_active_uvlayer_name(context.object);
+ create_usd_preview_surface_material(
+ this->usd_export_context_, material, usd_material, active_uv);
+ }
+ else {
+ create_usd_viewport_material(this->usd_export_context_, material, usd_material);
+ }
return usd_material;
}
diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h
index dd81dd47c83..c67aa824263 100644
--- a/source/blender/io/usd/intern/usd_writer_abstract.h
+++ b/source/blender/io/usd/intern/usd_writer_abstract.h
@@ -69,7 +69,7 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
virtual void do_write(HierarchyContext &context) = 0;
pxr::UsdTimeCode get_export_time_code() const;
- pxr::UsdShadeMaterial ensure_usd_material(Material *material);
+ pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material);
void write_visibility(const HierarchyContext &context,
const pxr::UsdTimeCode timecode,
diff --git a/source/blender/io/usd/intern/usd_writer_material.cc b/source/blender/io/usd/intern/usd_writer_material.cc
new file mode 100644
index 00000000000..34fd884f51a
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_material.cc
@@ -0,0 +1,767 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "usd_writer_material.h"
+
+#include "usd.h"
+#include "usd_exporter_context.h"
+
+#include "BKE_image.h"
+#include "BKE_main.h"
+#include "BKE_node.h"
+
+#include "BLI_fileops.h"
+#include "BLI_linklist.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "DNA_material_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "WM_api.h"
+
+#include <pxr/base/tf/stringUtils.h>
+#include <pxr/pxr.h>
+#include <pxr/usd/usdGeom/scope.h>
+
+#include <iostream>
+
+/* TfToken objects are not cheap to construct, so we do it once. */
+namespace usdtokens {
+// Materials
+static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal);
+static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal);
+static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal);
+static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
+static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
+static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
+static const pxr::TfToken uv_texture("UsdUVTexture", pxr::TfToken::Immortal);
+static const pxr::TfToken primvar_float2("UsdPrimvarReader_float2", pxr::TfToken::Immortal);
+static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
+static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
+static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal);
+static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
+static const pxr::TfToken perspective("perspective", pxr::TfToken::Immortal);
+static const pxr::TfToken orthographic("orthographic", pxr::TfToken::Immortal);
+static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal);
+static const pxr::TfToken r("r", pxr::TfToken::Immortal);
+static const pxr::TfToken g("g", pxr::TfToken::Immortal);
+static const pxr::TfToken b("b", pxr::TfToken::Immortal);
+static const pxr::TfToken st("st", pxr::TfToken::Immortal);
+static const pxr::TfToken result("result", pxr::TfToken::Immortal);
+static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
+static const pxr::TfToken out("out", pxr::TfToken::Immortal);
+static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
+static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
+static const pxr::TfToken file("file", pxr::TfToken::Immortal);
+static const pxr::TfToken preview("preview", pxr::TfToken::Immortal);
+static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
+static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
+static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
+static const pxr::TfToken Shader("Shader", pxr::TfToken::Immortal);
+} // namespace usdtokens
+
+/* Cycles specific tokens. */
+namespace cyclestokens {
+static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal);
+} // namespace cyclestokens
+
+namespace blender::io::usd {
+
+/* Preview surface input specification. */
+struct InputSpec {
+ pxr::TfToken input_name;
+ pxr::SdfValueTypeName input_type;
+ pxr::TfToken source_name;
+ /* Whether a default value should be set
+ * if the node socket has not input. Usually
+ * false for the Normal input. */
+ bool set_default_value;
+};
+
+/* Map Blender socket names to USD Preview Surface InputSpec structs. */
+typedef std::map<std::string, InputSpec> InputSpecMap;
+
+/* Static function forward declarations. */
+static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
+ pxr::UsdShadeMaterial &material,
+ const char *name,
+ int type);
+static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
+ pxr::UsdShadeMaterial &material,
+ bNode *node);
+static void create_uvmap_shader(const USDExporterContext &usd_export_context,
+ bNode *tex_node,
+ pxr::UsdShadeMaterial &usd_material,
+ pxr::UsdShadeShader &usd_tex_shader,
+ const pxr::TfToken &default_uv);
+static void export_texture(bNode *node,
+ const pxr::UsdStageRefPtr stage,
+ const bool allow_overwrite = false);
+static bNode *find_bsdf_node(Material *material);
+static void get_absolute_path(Image *ima, char *r_path);
+static std::string get_tex_image_asset_path(bNode *node,
+ const pxr::UsdStageRefPtr stage,
+ const USDExportParams &export_params);
+static InputSpecMap &preview_surface_input_map();
+static bNode *traverse_channel(bNodeSocket *input, short target_type);
+
+template<typename T1, typename T2>
+void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value);
+
+void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
+ Material *material,
+ pxr::UsdShadeMaterial &usd_material,
+ const std::string &default_uv)
+{
+ if (!material) {
+ return;
+ }
+
+ /* Define a 'preview' scope beneath the material which will contain the preview shaders. */
+ pxr::UsdGeomScope::Define(usd_export_context.stage,
+ usd_material.GetPath().AppendChild(usdtokens::preview));
+
+ /* Default map when creating UV primvar reader shaders. */
+ pxr::TfToken default_uv_sampler = default_uv.empty() ? cyclestokens::UVMap :
+ pxr::TfToken(default_uv);
+
+ /* We only handle the first instance of either principled or
+ * diffuse bsdf nodes in the material's node tree, because
+ * USD Preview Surface has no concept of layering materials. */
+ bNode *node = find_bsdf_node(material);
+ if (!node) {
+ return;
+ }
+
+ pxr::UsdShadeShader preview_surface = create_usd_preview_shader(
+ usd_export_context, usd_material, node);
+
+ const InputSpecMap &input_map = preview_surface_input_map();
+
+ /* Set the preview surface inputs. */
+ LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
+
+ /* Check if this socket is mapped to a USD preview shader input. */
+ const InputSpecMap::const_iterator it = input_map.find(sock->name);
+
+ if (it == input_map.end()) {
+ continue;
+ }
+
+ pxr::UsdShadeShader created_shader;
+
+ bNode *input_node = traverse_channel(sock, SH_NODE_TEX_IMAGE);
+
+ const InputSpec &input_spec = it->second;
+
+ if (input_node) {
+ /* Create connection. */
+ created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
+
+ preview_surface.CreateInput(input_spec.input_name, input_spec.input_type)
+ .ConnectToSource(created_shader, input_spec.source_name);
+ }
+ else if (input_spec.set_default_value) {
+ /* Set hardcoded value. */
+ switch (sock->type) {
+ case SOCK_FLOAT: {
+ create_input<bNodeSocketValueFloat, float>(
+ preview_surface, input_spec, sock->default_value);
+ } break;
+ case SOCK_VECTOR: {
+ create_input<bNodeSocketValueVector, pxr::GfVec3f>(
+ preview_surface, input_spec, sock->default_value);
+ } break;
+ case SOCK_RGBA: {
+ create_input<bNodeSocketValueRGBA, pxr::GfVec3f>(
+ preview_surface, input_spec, sock->default_value);
+ } break;
+ default:
+ break;
+ }
+ }
+
+ /* If any input texture node has been found, export the texture, if necessary,
+ * and look for a connected uv node. */
+ if (!(created_shader && input_node && input_node->type == SH_NODE_TEX_IMAGE)) {
+ continue;
+ }
+
+ if (usd_export_context.export_params.export_textures) {
+ export_texture(input_node,
+ usd_export_context.stage,
+ usd_export_context.export_params.overwrite_textures);
+ }
+
+ create_uvmap_shader(
+ usd_export_context, input_node, usd_material, created_shader, default_uv_sampler);
+ }
+}
+
+void create_usd_viewport_material(const USDExporterContext &usd_export_context,
+ Material *material,
+ pxr::UsdShadeMaterial &usd_material)
+{
+ /* Construct the shader. */
+ pxr::SdfPath shader_path = usd_material.GetPath().AppendChild(usdtokens::preview_shader);
+ pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
+
+ shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
+ shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
+ .Set(pxr::GfVec3f(material->r, material->g, material->b));
+ shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
+ shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
+
+ /* Connect the shader and the material together. */
+ usd_material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
+}
+
+/* Return USD Preview Surface input map singleton. */
+static InputSpecMap &preview_surface_input_map()
+{
+ static InputSpecMap input_map = {
+ {"Base Color",
+ {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}},
+ {"Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}},
+ {"Roughness", {usdtokens::roughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
+ {"Metallic", {usdtokens::metallic, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
+ {"Specular", {usdtokens::specular, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
+ {"Alpha", {usdtokens::opacity, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
+ {"IOR", {usdtokens::ior, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
+ /* Note that for the Normal input set_default_value is false. */
+ {"Normal", {usdtokens::normal, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, false}},
+ {"Clearcoat", {usdtokens::clearcoat, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
+ {"Clearcoat Roughness",
+ {usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
+ };
+
+ return input_map;
+}
+
+/* Create an input on the given shader with name and type
+ * provided by the InputSpec and assign the given value to the
+ * input. Parameters T1 and T2 indicate the Blender and USD
+ * value types, respectively. */
+template<typename T1, typename T2>
+void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value)
+{
+ const T1 *cast_value = static_cast<const T1 *>(value);
+ shader.CreateInput(spec.input_name, spec.input_type).Set(T2(cast_value->value));
+}
+
+/* Find the UVMAP node input to the given texture image node and convert it
+ * to a USD primvar reader shader. If no UVMAP node is found, create a primvar
+ * reader for the given default uv set. The primvar reader will be attached to
+ * the 'st' input of the given USD texture shader. */
+static void create_uvmap_shader(const USDExporterContext &usd_export_context,
+ bNode *tex_node,
+ pxr::UsdShadeMaterial &usd_material,
+ pxr::UsdShadeShader &usd_tex_shader,
+ const pxr::TfToken &default_uv)
+{
+ bool found_uv_node = false;
+
+ /* Find UV input to the texture node. */
+ LISTBASE_FOREACH (bNodeSocket *, tex_node_sock, &tex_node->inputs) {
+
+ if (!tex_node_sock->link || !STREQ(tex_node_sock->name, "Vector")) {
+ continue;
+ }
+
+ bNode *uv_node = traverse_channel(tex_node_sock, SH_NODE_UVMAP);
+ if (uv_node == NULL) {
+ continue;
+ }
+
+ pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
+ usd_export_context, usd_material, uv_node);
+
+ if (!uv_shader.GetPrim().IsValid()) {
+ continue;
+ }
+
+ found_uv_node = true;
+
+ if (NodeShaderUVMap *shader_uv_map = static_cast<NodeShaderUVMap *>(uv_node->storage)) {
+ /* We need to make valid here because actual uv primvar has been. */
+ std::string uv_set = pxr::TfMakeValidIdentifier(shader_uv_map->uv_map);
+
+ uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token)
+ .Set(pxr::TfToken(uv_set));
+ usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
+ .ConnectToSource(uv_shader, usdtokens::result);
+ }
+ else {
+ uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(default_uv);
+ usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
+ .ConnectToSource(uv_shader, usdtokens::result);
+ }
+ }
+
+ if (!found_uv_node) {
+ /* No UVMAP node was linked to the texture node. However, we generate
+ * a primvar reader node that specifies the UV set to sample, as some
+ * DCCs require this. */
+
+ pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
+ usd_export_context, usd_material, "uvmap", SH_NODE_TEX_COORD);
+
+ if (uv_shader.GetPrim().IsValid()) {
+ uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(default_uv);
+ usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
+ .ConnectToSource(uv_shader, usdtokens::result);
+ }
+ }
+}
+
+/* Generate a file name for an in-memory image that doesn't have a
+ * filepath already defined. */
+static std::string get_in_memory_texture_filename(Image *ima)
+{
+ bool is_dirty = BKE_image_is_dirty(ima);
+ bool is_generated = ima->source == IMA_SRC_GENERATED;
+ bool is_packed = BKE_image_has_packedfile(ima);
+ if (!(is_generated || is_dirty || is_packed)) {
+ return "";
+ }
+
+ /* Determine the correct file extension from the image format. */
+ ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
+ if (!imbuf) {
+ return "";
+ }
+
+ ImageFormatData imageFormat;
+ BKE_imbuf_to_image_format(&imageFormat, imbuf);
+
+ char file_name[FILE_MAX];
+ /* Use the image name for the file name. */
+ strcpy(file_name, ima->id.name + 2);
+
+ BKE_image_path_ensure_ext_from_imformat(file_name, &imageFormat);
+
+ return file_name;
+}
+
+static void export_in_memory_texture(Image *ima,
+ const std::string &export_dir,
+ const bool allow_overwrite)
+{
+ char image_abs_path[FILE_MAX];
+
+ char file_name[FILE_MAX];
+ if (strlen(ima->filepath) > 0) {
+ get_absolute_path(ima, image_abs_path);
+ BLI_split_file_part(image_abs_path, file_name, FILE_MAX);
+ }
+ else {
+ /* Use the image name for the file name. */
+ strcpy(file_name, ima->id.name + 2);
+ }
+
+ ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
+ if (!imbuf) {
+ return;
+ }
+
+ ImageFormatData imageFormat;
+ BKE_imbuf_to_image_format(&imageFormat, imbuf);
+
+ /* This image in its current state only exists in Blender memory.
+ * So we have to export it. The export will keep the image state intact,
+ * so the exported file will not be associated with the image. */
+
+ BKE_image_path_ensure_ext_from_imformat(file_name, &imageFormat);
+
+ char export_path[FILE_MAX];
+ BLI_path_join(export_path, FILE_MAX, export_dir.c_str(), file_name, NULL);
+
+ if (!allow_overwrite && BLI_exists(export_path)) {
+ return;
+ }
+
+ if (BLI_paths_equal(export_path, image_abs_path) && BLI_exists(image_abs_path)) {
+ /* As a precaution, don't overwrite the original path. */
+ return;
+ }
+
+ std::cout << "Exporting in-memory texture to " << export_path << std::endl;
+
+ if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) {
+ WM_reportf(RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path);
+ }
+}
+
+/* Get the absolute filepath of the given image. Assumes
+ * r_path result array is of length FILE_MAX. */
+static void get_absolute_path(Image *ima, char *r_path)
+{
+ /* Make absolute source path. */
+ BLI_strncpy(r_path, ima->filepath, FILE_MAX);
+ BLI_path_abs(r_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
+ BLI_path_normalize(nullptr, r_path);
+}
+
+static pxr::TfToken get_node_tex_image_color_space(bNode *node)
+{
+ if (!node->id) {
+ return pxr::TfToken();
+ }
+
+ Image *ima = reinterpret_cast<Image *>(node->id);
+
+ if (strcmp(ima->colorspace_settings.name, "Raw") == 0) {
+ return usdtokens::raw;
+ }
+ if (strcmp(ima->colorspace_settings.name, "Non-Color") == 0) {
+ return usdtokens::raw;
+ }
+ if (strcmp(ima->colorspace_settings.name, "sRGB") == 0) {
+ return usdtokens::sRGB;
+ }
+
+ return pxr::TfToken();
+}
+
+/* Search the upstream nodes connected to the given socket and return the first occurrance
+ * of the node of the given type. Return null if no node of this type was found. */
+static bNode *traverse_channel(bNodeSocket *input, const short target_type)
+{
+ if (!input->link) {
+ return nullptr;
+ }
+
+ bNode *linked_node = input->link->fromnode;
+ if (linked_node->type == target_type) {
+ /* Return match. */
+ return linked_node;
+ }
+
+ /* Recursively traverse the linked node's sockets. */
+ LISTBASE_FOREACH (bNodeSocket *, sock, &linked_node->inputs) {
+ if (bNode *found_node = traverse_channel(sock, target_type)) {
+ return found_node;
+ }
+ }
+
+ return nullptr;
+}
+
+/* Returns the first occurence of a principled bsdf or a diffuse bsdf node found in the given
+ * material's node tree. Returns null if no instance of either type was found.*/
+static bNode *find_bsdf_node(Material *material)
+{
+ LISTBASE_FOREACH (bNode *, node, &material->nodetree->nodes) {
+ if (node->type == SH_NODE_BSDF_PRINCIPLED || node->type == SH_NODE_BSDF_DIFFUSE) {
+ return node;
+ }
+ }
+
+ return nullptr;
+}
+
+/* Creates a USD Preview Surface shader based on the given cycles node name and type. */
+static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
+ pxr::UsdShadeMaterial &material,
+ const char *name,
+ const int type)
+{
+ pxr::SdfPath shader_path = material.GetPath()
+ .AppendChild(usdtokens::preview)
+ .AppendChild(pxr::TfToken(pxr::TfMakeValidIdentifier(name)));
+ pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
+
+ switch (type) {
+ case SH_NODE_TEX_IMAGE: {
+ shader.CreateIdAttr(pxr::VtValue(usdtokens::uv_texture));
+ break;
+ }
+ case SH_NODE_TEX_COORD:
+ case SH_NODE_UVMAP: {
+ shader.CreateIdAttr(pxr::VtValue(usdtokens::primvar_float2));
+ break;
+ }
+ case SH_NODE_BSDF_DIFFUSE:
+ case SH_NODE_BSDF_PRINCIPLED: {
+ shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
+ material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return shader;
+}
+
+/* Creates a USD Preview Surface shader based on the given cycles shading node. */
+static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
+ pxr::UsdShadeMaterial &material,
+ bNode *node)
+{
+ pxr::UsdShadeShader shader = create_usd_preview_shader(
+ usd_export_context, material, node->name, node->type);
+
+ if (node->type != SH_NODE_TEX_IMAGE) {
+ return shader;
+ }
+
+ /* For texture image nodes we set the image path and color space. */
+ std::string imagePath = get_tex_image_asset_path(
+ node, usd_export_context.stage, usd_export_context.export_params);
+ if (!imagePath.empty()) {
+ shader.CreateInput(usdtokens::file, pxr::SdfValueTypeNames->Asset)
+ .Set(pxr::SdfAssetPath(imagePath));
+ }
+
+ pxr::TfToken colorSpace = get_node_tex_image_color_space(node);
+ if (!colorSpace.IsEmpty()) {
+ shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token).Set(colorSpace);
+ }
+
+ return shader;
+}
+
+static std::string get_tex_image_asset_path(Image *ima)
+{
+ char filepath[FILE_MAX];
+ get_absolute_path(ima, filepath);
+
+ return std::string(filepath);
+}
+
+/* Gets an asset path for the given texture image node. The resulting path
+ * may be absolute, relative to the USD file, or in a 'textures' directory
+ * in the same directory as the USD file, depending on the export parameters.
+ * The filename is typically the image filepath but might also be automatically
+ * generated based on the image name for in-memory textures when exporting textures.
+ * This function may return an empty string if the image does not have a filepath
+ * assigned and no asset path could be determined. */
+static std::string get_tex_image_asset_path(bNode *node,
+ const pxr::UsdStageRefPtr stage,
+ const USDExportParams &export_params)
+{
+ Image *ima = reinterpret_cast<Image *>(node->id);
+ if (!ima) {
+ return "";
+ }
+
+ std::string path;
+
+ if (strlen(ima->filepath) > 0) {
+ /* Get absolute path. */
+ path = get_tex_image_asset_path(ima);
+ }
+ else if (export_params.export_textures) {
+ /* Image has no filepath, but since we are exporting textures,
+ * check if this is an in-memory texture for which we can
+ * generate a file name. */
+ path = get_in_memory_texture_filename(ima);
+ }
+
+ if (path.empty()) {
+ return path;
+ }
+
+ if (export_params.export_textures) {
+ /* The texture is exported to a 'textures' directory next to the
+ * USD root layer. */
+
+ char exp_path[FILE_MAX];
+ char file_path[FILE_MAX];
+ BLI_split_file_part(path.c_str(), file_path, FILE_MAX);
+
+ if (export_params.relative_texture_paths) {
+ BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path, NULL);
+ }
+ else {
+ /* Create absolute path in the textures directory. */
+ pxr::SdfLayerHandle layer = stage->GetRootLayer();
+ std::string stage_path = layer->GetRealPath();
+ if (stage_path.empty()) {
+ return path;
+ }
+
+ char dir_path[FILE_MAX];
+ BLI_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
+ BLI_path_join(exp_path, FILE_MAX, dir_path, "textures", file_path, NULL);
+ }
+ return exp_path;
+ }
+
+ if (export_params.relative_texture_paths) {
+ /* Get the path relative to the USD. */
+ pxr::SdfLayerHandle layer = stage->GetRootLayer();
+ std::string stage_path = layer->GetRealPath();
+ if (stage_path.empty()) {
+ return path;
+ }
+
+ char rel_path[FILE_MAX];
+ strcpy(rel_path, path.c_str());
+
+ BLI_path_rel(rel_path, stage_path.c_str());
+
+ /* BLI_path_rel adds '//' as a prefix to the path, if
+ * generating the relative path was successful. */
+ if (rel_path[0] != '/' || rel_path[1] != '/') {
+ /* No relative path generated. */
+ return path;
+ }
+
+ return rel_path + 2;
+ }
+
+ return path;
+}
+
+/* If the given image is tiled, copy the image tiles to the given
+ * destination directory. */
+static void copy_tiled_textures(Image *ima,
+ const std::string &dest_dir,
+ const bool allow_overwrite)
+{
+ char src_path[FILE_MAX];
+ get_absolute_path(ima, src_path);
+
+ eUDIM_TILE_FORMAT tile_format;
+ char *udim_pattern = BKE_image_get_tile_strformat(src_path, &tile_format);
+
+ /* Only <UDIM> tile formats are supported by USD right now. */
+ if (tile_format != UDIM_TILE_FORMAT_UDIM) {
+ std::cout << "WARNING: unsupported tile format for `" << src_path << "`" << std::endl;
+ MEM_SAFE_FREE(udim_pattern);
+ return;
+ }
+
+ /* Copy all tiles. */
+ LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+ char src_tile_path[FILE_MAX];
+ BKE_image_set_filepath_from_tile_number(
+ src_tile_path, udim_pattern, tile_format, tile->tile_number);
+
+ char dest_filename[FILE_MAXFILE];
+ BLI_split_file_part(src_tile_path, dest_filename, sizeof(dest_filename));
+
+ char dest_tile_path[FILE_MAX];
+ BLI_path_join(dest_tile_path, FILE_MAX, dest_dir.c_str(), dest_filename, nullptr);
+
+ if (!allow_overwrite && BLI_exists(dest_tile_path)) {
+ continue;
+ }
+
+ if (BLI_paths_equal(src_tile_path, dest_tile_path)) {
+ /* Source and destination paths are the same, don't copy. */
+ continue;
+ }
+
+ std::cout << "Copying texture tile from " << src_tile_path << " to " << dest_tile_path
+ << std::endl;
+
+ /* Copy the file. */
+ if (BLI_copy(src_tile_path, dest_tile_path) != 0) {
+ WM_reportf(RPT_WARNING,
+ "USD export: couldn't copy texture tile from %s to %s",
+ src_tile_path,
+ dest_tile_path);
+ }
+ }
+ MEM_SAFE_FREE(udim_pattern);
+}
+
+/* Copy the given image to the destination directory. */
+static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite)
+{
+ char source_path[FILE_MAX];
+ get_absolute_path(ima, source_path);
+
+ char file_name[FILE_MAX];
+ BLI_split_file_part(source_path, file_name, FILE_MAX);
+
+ char dest_path[FILE_MAX];
+ BLI_path_join(dest_path, FILE_MAX, dest_dir.c_str(), file_name, NULL);
+
+ if (!allow_overwrite && BLI_exists(dest_path)) {
+ return;
+ }
+
+ if (BLI_paths_equal(source_path, dest_path)) {
+ /* Source and destination paths are the same, don't copy. */
+ return;
+ }
+
+ std::cout << "Copying texture from " << source_path << " to " << dest_path << std::endl;
+
+ /* Copy the file. */
+ if (BLI_copy(source_path, dest_path) != 0) {
+ WM_reportf(
+ RPT_WARNING, "USD export: couldn't copy texture from %s to %s", source_path, dest_path);
+ }
+}
+
+/* Export the given texture node's image to a 'textures' directory
+ * next to given stage's root layer USD.
+ * Based on ImagesExporter::export_UV_Image() */
+static void export_texture(bNode *node,
+ const pxr::UsdStageRefPtr stage,
+ const bool allow_overwrite)
+{
+ if (node->type != SH_NODE_TEX_IMAGE && node->type != SH_NODE_TEX_ENVIRONMENT) {
+ return;
+ }
+
+ Image *ima = reinterpret_cast<Image *>(node->id);
+ if (!ima) {
+ return;
+ }
+
+ pxr::SdfLayerHandle layer = stage->GetRootLayer();
+ std::string stage_path = layer->GetRealPath();
+ if (stage_path.empty()) {
+ return;
+ }
+
+ char usd_dir_path[FILE_MAX];
+ BLI_split_dir_part(stage_path.c_str(), usd_dir_path, FILE_MAX);
+
+ char tex_dir_path[FILE_MAX];
+ BLI_path_join(tex_dir_path, FILE_MAX, usd_dir_path, "textures", SEP_STR, NULL);
+
+ BLI_dir_create_recursive(tex_dir_path);
+
+ const bool is_dirty = BKE_image_is_dirty(ima);
+ const bool is_generated = ima->source == IMA_SRC_GENERATED;
+ const bool is_packed = BKE_image_has_packedfile(ima);
+
+ std::string dest_dir(tex_dir_path);
+
+ if (is_generated || is_dirty || is_packed) {
+ export_in_memory_texture(ima, dest_dir, allow_overwrite);
+ }
+ else if (ima->source == IMA_SRC_TILED) {
+ copy_tiled_textures(ima, dest_dir, allow_overwrite);
+ }
+ else {
+ copy_single_file(ima, dest_dir, allow_overwrite);
+ }
+}
+
+} // namespace blender::io::usd
diff --git a/source/blender/io/usd/intern/usd_writer_material.h b/source/blender/io/usd/intern/usd_writer_material.h
new file mode 100644
index 00000000000..b2c732963dc
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_material.h
@@ -0,0 +1,55 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#pragma once
+
+#include <pxr/pxr.h>
+#include <pxr/usd/usd/prim.h>
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdShade/material.h>
+
+#include <string>
+
+struct bNode;
+struct bNodeTree;
+struct Material;
+struct USDExportParams;
+
+namespace blender::io::usd {
+
+struct USDExporterContext;
+
+/* Entry point to create an approximate USD Preview Surface network from a Cycles node graph.
+ * Due to the limited nodes in the USD Preview Surface specification, only the following nodes
+ * are supported:
+ * - UVMap
+ * - Texture Coordinate
+ * - Image Texture
+ * - Principled BSDF
+ * More may be added in the future.
+ *
+ * The 'default_uv' paramter is used as the default UV set name sampled by the primvar
+ * reader shaders generated for image texture nodes that don't have an attached UVMAp node. */
+void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
+ Material *material,
+ pxr::UsdShadeMaterial &usd_material,
+ const std::string &default_uv = "");
+
+/* Entry point to create USD Shade Material network from Blender viewport display settings. */
+void create_usd_viewport_material(const USDExporterContext &usd_export_context,
+ Material *material,
+ pxr::UsdShadeMaterial &usd_material);
+
+} // namespace blender::io::usd
diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc
index b061a2ff795..1d3a5f5280d 100644
--- a/source/blender/io/usd/intern/usd_writer_mesh.cc
+++ b/source/blender/io/usd/intern/usd_writer_mesh.cc
@@ -361,7 +361,7 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
continue;
}
- pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
+ pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
material_binding_api.Bind(usd_material);
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
@@ -395,7 +395,7 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
continue;
}
- pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
+ pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h
index 16bd826ecdd..505f917ede5 100644
--- a/source/blender/io/usd/usd.h
+++ b/source/blender/io/usd/usd.h
@@ -41,6 +41,10 @@ struct USDExportParams {
bool visible_objects_only;
bool use_instancing;
enum eEvaluationMode evaluation_mode;
+ bool generate_preview_surface;
+ bool export_textures;
+ bool overwrite_textures;
+ bool relative_texture_paths;
};
struct USDImportParams {
diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c
index 264e89b2c2d..1ae036a254e 100644
--- a/source/blender/makesrna/intern/rna_ID.c
+++ b/source/blender/makesrna/intern/rna_ID.c
@@ -704,6 +704,7 @@ static ID *rna_ID_override_create(ID *id, Main *bmain, bool remap_local_usages)
}
WM_main_add_notifier(NC_ID | NA_ADDED, NULL);
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
return local_id;
}
@@ -721,6 +722,7 @@ static ID *rna_ID_override_hierarchy_create(
BKE_lib_override_library_create(bmain, scene, view_layer, id, id_reference, &id_root_override);
WM_main_add_notifier(NC_ID | NA_ADDED, NULL);
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
return id_root_override;
}
@@ -741,6 +743,8 @@ static void rna_ID_override_template_create(ID *id, ReportList *reports)
return;
}
BKE_lib_override_library_template_create(id);
+
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
}
static void rna_ID_override_library_operations_update(ID *id,
@@ -754,6 +758,8 @@ static void rna_ID_override_library_operations_update(ID *id,
}
BKE_lib_override_library_operations_create(bmain, id);
+
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
}
static void rna_ID_override_library_reset(ID *id,
@@ -773,6 +779,8 @@ static void rna_ID_override_library_reset(ID *id,
else {
BKE_lib_override_library_id_reset(bmain, id);
}
+
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
}
static void rna_ID_override_library_destroy(ID *id,
@@ -793,6 +801,8 @@ static void rna_ID_override_library_destroy(ID *id,
BKE_libblock_remap(bmain, id, id->override_library->reference, ID_REMAP_SKIP_INDIRECT_USAGE);
BKE_id_delete(bmain, id);
}
+
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
}
static IDOverrideLibraryProperty *rna_ID_override_library_properties_add(
@@ -806,6 +816,7 @@ static IDOverrideLibraryProperty *rna_ID_override_library_properties_add(
BKE_report(reports, RPT_DEBUG, "No new override property created, property already exists");
}
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
return result;
}
@@ -819,6 +830,8 @@ static void rna_ID_override_library_properties_remove(IDOverrideLibrary *overrid
}
BKE_lib_override_library_property_delete(override_library, override_property);
+
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
}
static IDOverrideLibraryPropertyOperation *rna_ID_override_library_property_operations_add(
@@ -845,6 +858,8 @@ static IDOverrideLibraryPropertyOperation *rna_ID_override_library_property_oper
if (!created) {
BKE_report(reports, RPT_DEBUG, "No new override operation created, operation already exists");
}
+
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
return result;
}
@@ -859,6 +874,8 @@ static void rna_ID_override_library_property_operations_remove(
}
BKE_lib_override_library_property_operation_delete(override_property, override_operation);
+
+ WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
}
static void rna_ID_update_tag(ID *id, Main *bmain, ReportList *reports, int flag)
@@ -1747,6 +1764,7 @@ static void rna_def_ID_override_library_property(BlenderRNA *brna)
"IDOverrideLibraryPropertyOperation",
"Operations",
"List of overriding operations for a property");
+ RNA_def_property_update(prop, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
rna_def_ID_override_library_property_operations(brna, prop);
rna_def_ID_override_library_property_operation(brna);
@@ -1799,8 +1817,9 @@ static void rna_def_ID_override_library(BlenderRNA *brna)
RNA_def_struct_ui_text(
srna, "ID Library Override", "Struct gathering all data needed by overridden linked IDs");
- RNA_def_pointer(
+ prop = RNA_def_pointer(
srna, "reference", "ID", "Reference ID", "Linked ID used as reference by this override");
+ RNA_def_property_update(prop, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
prop = RNA_def_boolean(srna,
"is_in_hierarchy",
@@ -1808,6 +1827,7 @@ static void rna_def_ID_override_library(BlenderRNA *brna)
"Is In Hierarchy",
"Whether this library override is defined as part of a library "
"hierarchy, or as a single, isolated and autonomous override");
+ RNA_def_property_update(prop, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY);
prop = RNA_def_collection(srna,
@@ -1815,6 +1835,7 @@ static void rna_def_ID_override_library(BlenderRNA *brna)
"IDOverrideLibraryProperty",
"Properties",
"List of overridden properties");
+ RNA_def_property_update(prop, NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
rna_def_ID_override_library_properties(brna, prop);
/* Update function. */
diff --git a/source/blender/modifiers/intern/MOD_mirror.c b/source/blender/modifiers/intern/MOD_mirror.c
index bbac6589577..1bdc97f0a8b 100644
--- a/source/blender/modifiers/intern/MOD_mirror.c
+++ b/source/blender/modifiers/intern/MOD_mirror.c
@@ -120,9 +120,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
result = mirrorModifier__doMirror(mmd, ctx->object, mesh);
- if (result != mesh) {
- BKE_mesh_normals_tag_dirty(result);
- }
return result;
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
index 5a8d9ab470d..e6b14bc69e5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
@@ -386,11 +386,11 @@ static bool component_is_available(const GeometrySet &geometry,
/**
* \note Multi-threading for this function is provided by the field evaluator. Since the #call
- * function could be called many times, calculate the data from the target geometry once and store
+ * function could be called many times, calculate the data from the source geometry once and store
* it for later.
*/
class NearestInterpolatedTransferFunction : public fn::MultiFunction {
- GeometrySet target_;
+ GeometrySet source_;
GField src_field_;
/**
@@ -403,18 +403,18 @@ class NearestInterpolatedTransferFunction : public fn::MultiFunction {
fn::MFSignature signature_;
- std::optional<GeometryComponentFieldContext> target_context_;
- std::unique_ptr<FieldEvaluator> target_evaluator_;
- const GVArray *target_data_;
+ std::optional<GeometryComponentFieldContext> source_context_;
+ std::unique_ptr<FieldEvaluator> source_evaluator_;
+ const GVArray *source_data_;
public:
NearestInterpolatedTransferFunction(GeometrySet geometry, GField src_field)
- : target_(std::move(geometry)), src_field_(std::move(src_field))
+ : source_(std::move(geometry)), src_field_(std::move(src_field))
{
- target_.ensure_owns_direct_data();
+ source_.ensure_owns_direct_data();
signature_ = this->create_signature();
this->set_signature(&signature_);
- this->evaluate_target_field();
+ this->evaluate_source_field();
}
fn::MFSignature create_signature()
@@ -430,7 +430,7 @@ class NearestInterpolatedTransferFunction : public fn::MultiFunction {
const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position");
GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Attribute");
- const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>();
+ const MeshComponent &mesh_component = *source_.get_component_for_read<MeshComponent>();
BLI_assert(mesh_component.has_mesh());
const Mesh &mesh = *mesh_component.get_for_read();
BLI_assert(mesh.totpoly > 0);
@@ -441,29 +441,29 @@ class NearestInterpolatedTransferFunction : public fn::MultiFunction {
get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, {}, sampled_positions);
MeshAttributeInterpolator interp(&mesh, mask, sampled_positions, looptri_indices);
- interp.sample_data(*target_data_, domain_, eAttributeMapMode::INTERPOLATED, dst);
+ interp.sample_data(*source_data_, domain_, eAttributeMapMode::INTERPOLATED, dst);
}
private:
- void evaluate_target_field()
+ void evaluate_source_field()
{
- const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>();
- target_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_});
+ const MeshComponent &mesh_component = *source_.get_component_for_read<MeshComponent>();
+ source_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_});
const int domain_size = mesh_component.attribute_domain_size(domain_);
- target_evaluator_ = std::make_unique<FieldEvaluator>(*target_context_, domain_size);
- target_evaluator_->add(src_field_);
- target_evaluator_->evaluate();
- target_data_ = &target_evaluator_->get_evaluated(0);
+ source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, domain_size);
+ source_evaluator_->add(src_field_);
+ source_evaluator_->evaluate();
+ source_data_ = &source_evaluator_->get_evaluated(0);
}
};
/**
* \note Multi-threading for this function is provided by the field evaluator. Since the #call
- * function could be called many times, calculate the data from the target geometry once and store
+ * function could be called many times, calculate the data from the source geometry once and store
* it for later.
*/
class NearestTransferFunction : public fn::MultiFunction {
- GeometrySet target_;
+ GeometrySet source_;
GField src_field_;
AttributeDomain domain_;
@@ -472,7 +472,7 @@ class NearestTransferFunction : public fn::MultiFunction {
bool use_mesh_;
bool use_points_;
- /* Store data from the target as a virtual array, since we may only access a few indices. */
+ /* Store data from the source as a virtual array, since we may only access a few indices. */
std::optional<GeometryComponentFieldContext> mesh_context_;
std::unique_ptr<FieldEvaluator> mesh_evaluator_;
const GVArray *mesh_data_;
@@ -483,16 +483,16 @@ class NearestTransferFunction : public fn::MultiFunction {
public:
NearestTransferFunction(GeometrySet geometry, GField src_field, AttributeDomain domain)
- : target_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain)
+ : source_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain)
{
- target_.ensure_owns_direct_data();
+ source_.ensure_owns_direct_data();
signature_ = this->create_signature();
this->set_signature(&signature_);
- this->use_mesh_ = component_is_available(target_, GEO_COMPONENT_TYPE_MESH, domain_);
- this->use_points_ = component_is_available(target_, GEO_COMPONENT_TYPE_POINT_CLOUD, domain_);
+ this->use_mesh_ = component_is_available(source_, GEO_COMPONENT_TYPE_MESH, domain_);
+ this->use_points_ = component_is_available(source_, GEO_COMPONENT_TYPE_POINT_CLOUD, domain_);
- this->evaluate_target_field();
+ this->evaluate_source_field();
}
fn::MFSignature create_signature()
@@ -513,8 +513,8 @@ class NearestTransferFunction : public fn::MultiFunction {
return;
}
- const Mesh *mesh = use_mesh_ ? target_.get_mesh_for_read() : nullptr;
- const PointCloud *pointcloud = use_points_ ? target_.get_pointcloud_for_read() : nullptr;
+ const Mesh *mesh = use_mesh_ ? source_.get_mesh_for_read() : nullptr;
+ const PointCloud *pointcloud = use_points_ ? source_.get_pointcloud_for_read() : nullptr;
const int tot_samples = mask.min_array_size();
@@ -590,10 +590,10 @@ class NearestTransferFunction : public fn::MultiFunction {
}
private:
- void evaluate_target_field()
+ void evaluate_source_field()
{
if (use_mesh_) {
- const MeshComponent &mesh = *target_.get_component_for_read<MeshComponent>();
+ const MeshComponent &mesh = *source_.get_component_for_read<MeshComponent>();
const int domain_size = mesh.attribute_domain_size(domain_);
mesh_context_.emplace(GeometryComponentFieldContext(mesh, domain_));
mesh_evaluator_ = std::make_unique<FieldEvaluator>(*mesh_context_, domain_size);
@@ -603,7 +603,7 @@ class NearestTransferFunction : public fn::MultiFunction {
}
if (use_points_) {
- const PointCloudComponent &points = *target_.get_component_for_read<PointCloudComponent>();
+ const PointCloudComponent &points = *source_.get_component_for_read<PointCloudComponent>();
const int domain_size = points.attribute_domain_size(domain_);
point_context_.emplace(GeometryComponentFieldContext(points, domain_));
point_evaluator_ = std::make_unique<FieldEvaluator>(*point_context_, domain_size);
@@ -614,7 +614,7 @@ class NearestTransferFunction : public fn::MultiFunction {
}
};
-static const GeometryComponent *find_target_component(const GeometrySet &geometry,
+static const GeometryComponent *find_source_component(const GeometrySet &geometry,
const AttributeDomain domain)
{
/* Choose the other component based on a consistent order, rather than some more complicated
@@ -634,7 +634,7 @@ static const GeometryComponent *find_target_component(const GeometrySet &geometr
/**
* The index-based transfer theoretically does not need realized data when there is only one
- * instance geometry set in the target. A future optimization could be removing that limitation
+ * instance geometry set in the source. A future optimization could be removing that limitation
* internally.
*/
class IndexTransferFunction : public fn::MultiFunction {
@@ -670,7 +670,7 @@ class IndexTransferFunction : public fn::MultiFunction {
void evaluate_field()
{
- const GeometryComponent *component = find_target_component(src_geometry_, domain_);
+ const GeometryComponent *component = find_source_component(src_geometry_, domain_);
if (component == nullptr) {
return;
}
@@ -772,7 +772,7 @@ static void node_geo_exec(GeoNodeExecParams params)
if (mesh == nullptr) {
if (!geometry.is_empty()) {
params.error_message_add(NodeWarningType::Error,
- TIP_("The target geometry must contain a mesh"));
+ TIP_("The source geometry must contain a mesh"));
}
return return_default();
}
@@ -780,7 +780,7 @@ static void node_geo_exec(GeoNodeExecParams params)
/* Don't add a warning for empty meshes. */
if (mesh->totvert != 0) {
params.error_message_add(NodeWarningType::Error,
- TIP_("The target mesh must have faces"));
+ TIP_("The source mesh must have faces"));
}
return return_default();
}
@@ -794,7 +794,7 @@ static void node_geo_exec(GeoNodeExecParams params)
case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: {
if (geometry.has_curve() && !geometry.has_mesh() && !geometry.has_pointcloud()) {
params.error_message_add(NodeWarningType::Error,
- TIP_("The target geometry must contain a mesh or a point cloud"));
+ TIP_("The source geometry must contain a mesh or a point cloud"));
return return_default();
}
auto fn = std::make_unique<NearestTransferFunction>(
diff --git a/source/blender/render/RE_pipeline.h b/source/blender/render/RE_pipeline.h
index 072fc7363b2..c1392b24023 100644
--- a/source/blender/render/RE_pipeline.h
+++ b/source/blender/render/RE_pipeline.h
@@ -310,7 +310,7 @@ void RE_SetOverrideCamera(struct Render *re, struct Object *cam_ob);
*
* \note call this after #RE_InitState().
*/
-void RE_SetCamera(struct Render *re, struct Object *cam_ob);
+void RE_SetCamera(struct Render *re, const struct Object *cam_ob);
/**
* Get current view and window transform.
@@ -454,12 +454,14 @@ struct RenderPass *RE_pass_find_by_type(volatile struct RenderLayer *rl,
#define RE_BAKE_DISPLACEMENT 1
#define RE_BAKE_AO 2
-void RE_GetCameraWindow(struct Render *re, struct Object *camera, float mat[4][4]);
+void RE_GetCameraWindow(struct Render *re, const struct Object *camera, float mat[4][4]);
/**
* Must be called after #RE_GetCameraWindow(), does not change `re->winmat`.
*/
-void RE_GetCameraWindowWithOverscan(struct Render *re, float overscan, float r_winmat[4][4]);
-void RE_GetCameraModelMatrix(struct Render *re, struct Object *camera, float r_modelmat[4][4]);
+void RE_GetCameraWindowWithOverscan(const struct Render *re, float overscan, float r_winmat[4][4]);
+void RE_GetCameraModelMatrix(const struct Render *re,
+ const struct Object *camera,
+ float r_modelmat[4][4]);
struct Scene *RE_GetScene(struct Render *re);
void RE_SetScene(struct Render *re, struct Scene *sce);
diff --git a/source/blender/render/intern/initrender.c b/source/blender/render/intern/initrender.c
index f696b81cffb..3bdf60c13a2 100644
--- a/source/blender/render/intern/initrender.c
+++ b/source/blender/render/intern/initrender.c
@@ -174,7 +174,7 @@ void RE_SetOverrideCamera(Render *re, Object *cam_ob)
re->camera_override = cam_ob;
}
-void RE_SetCamera(Render *re, Object *cam_ob)
+void RE_SetCamera(Render *re, const Object *cam_ob)
{
CameraParams params;
@@ -194,13 +194,13 @@ void RE_SetCamera(Render *re, Object *cam_ob)
re->viewplane = params.viewplane;
}
-void RE_GetCameraWindow(struct Render *re, struct Object *camera, float r_winmat[4][4])
+void RE_GetCameraWindow(struct Render *re, const struct Object *camera, float r_winmat[4][4])
{
RE_SetCamera(re, camera);
copy_m4_m4(r_winmat, re->winmat);
}
-void RE_GetCameraWindowWithOverscan(struct Render *re, float overscan, float r_winmat[4][4])
+void RE_GetCameraWindowWithOverscan(const struct Render *re, float overscan, float r_winmat[4][4])
{
CameraParams params;
params.is_ortho = re->winmat[3][3] != 0.0f;
@@ -218,7 +218,7 @@ void RE_GetCameraWindowWithOverscan(struct Render *re, float overscan, float r_w
copy_m4_m4(r_winmat, params.winmat);
}
-void RE_GetCameraModelMatrix(Render *re, struct Object *camera, float r_modelmat[4][4])
+void RE_GetCameraModelMatrix(const Render *re, const struct Object *camera, float r_modelmat[4][4])
{
BKE_camera_multiview_model_matrix(&re->r, camera, re->viewname, r_modelmat);
}
diff --git a/source/blender/render/intern/texture_margin.cc b/source/blender/render/intern/texture_margin.cc
index 0e47c2cec8a..4fbd20eeaf1 100644
--- a/source/blender/render/intern/texture_margin.cc
+++ b/source/blender/render/intern/texture_margin.cc
@@ -43,7 +43,7 @@
#include "RE_texture_margin.h"
#include <algorithm>
-#include <math.h>
+#include <cmath>
#include <valarray>
namespace blender::render::texturemargin {
@@ -128,7 +128,8 @@ class TextureMarginMap {
&zspan_, this, &(v1[0]), &(v2[0]), &(v3[0]), TextureMarginMap::zscan_store_pixel);
}
- static void zscan_store_pixel(void *map, int x, int y, float, float)
+ static void zscan_store_pixel(
+ void *map, int x, int y, [[maybe_unused]] float u, [[maybe_unused]] float v)
{
/* NOTE: Not thread safe, see comment above.
*
@@ -441,17 +442,17 @@ static void generate_margin(ImBuf *ibuf,
int tottri;
MLoopTri const *looptri;
- MLoopTri *looptri_mem = NULL;
+ MLoopTri *looptri_mem = nullptr;
if (me) {
- BLI_assert(dm == NULL);
+ BLI_assert(dm == nullptr);
totpoly = me->totpoly;
totloop = me->totloop;
totedge = me->totedge;
mpoly = me->mpoly;
mloop = me->mloop;
- if ((uv_layer == NULL) || (uv_layer[0] == '\0')) {
+ if ((uv_layer == nullptr) || (uv_layer[0] == '\0')) {
mloopuv = static_cast<MLoopUV const *>(CustomData_get_layer(&me->ldata, CD_MLOOPUV));
}
else {
@@ -467,9 +468,9 @@ static void generate_margin(ImBuf *ibuf,
looptri = looptri_mem;
}
else {
- BLI_assert(dm != NULL);
- BLI_assert(me == NULL);
- BLI_assert(mloopuv == NULL);
+ BLI_assert(dm != nullptr);
+ BLI_assert(me == nullptr);
+ BLI_assert(mloopuv == nullptr);
totpoly = dm->getNumPolys(dm);
totedge = dm->getNumEdges(dm);
totloop = dm->getNumLoops(dm);
@@ -510,7 +511,7 @@ static void generate_margin(ImBuf *ibuf,
}
BLI_assert(lt->poly < 0x80000000); // NOTE: we need the top bit for the dijkstra distance map
- map.rasterize_tri(vec[0], vec[1], vec[2], lt->poly, draw_new_mask ? mask : NULL);
+ map.rasterize_tri(vec[0], vec[1], vec[2], lt->poly, draw_new_mask ? mask : nullptr);
}
char *tmpmask = (char *)MEM_dupallocN(mask);
@@ -543,7 +544,7 @@ static void generate_margin(ImBuf *ibuf,
void RE_generate_texturemargin_adjacentfaces(
ImBuf *ibuf, char *mask, const int margin, const Mesh *me, char const *uv_layer)
{
- blender::render::texturemargin::generate_margin(ibuf, mask, margin, me, NULL, uv_layer);
+ blender::render::texturemargin::generate_margin(ibuf, mask, margin, me, nullptr, uv_layer);
}
void RE_generate_texturemargin_adjacentfaces_dm(ImBuf *ibuf,
@@ -551,5 +552,5 @@ void RE_generate_texturemargin_adjacentfaces_dm(ImBuf *ibuf,
const int margin,
DerivedMesh *dm)
{
- blender::render::texturemargin::generate_margin(ibuf, mask, margin, NULL, dm, NULL);
+ blender::render::texturemargin::generate_margin(ibuf, mask, margin, nullptr, dm, nullptr);
}
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 3e30c06ade2..9d2c97e151f 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -645,6 +645,20 @@ static int wm_event_always_pass(const wmEvent *event)
return ISTIMER(event->type) || (event->type == WINDEACTIVATE);
}
+/**
+ * Debug only sanity check for the return value of event handlers. Checks that "always pass" events
+ * don't cause non-passing handler return values, and thus actually pass.
+ *
+ * Can't be executed if the handler just loaded a file (typically identified by `CTX_wm_window(C)`
+ * returning `NULL`), because the event will have been freed then.
+ */
+BLI_INLINE void wm_event_handler_return_value_check(const wmEvent *event, const int action)
+{
+ BLI_assert_msg(!wm_event_always_pass(event) || (action != WM_HANDLER_BREAK),
+ "Return value for events that should always pass should never be BREAK.");
+ UNUSED_VARS_NDEBUG(event, action);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
@@ -2965,9 +2979,9 @@ static int wm_handlers_do_intern(bContext *C, wmWindow *win, wmEvent *event, Lis
wmWindowManager *wm = CTX_wm_manager(C);
int action = WM_HANDLER_CONTINUE;
- int always_pass;
if (handlers == NULL) {
+ wm_event_handler_return_value_check(event, action);
return action;
}
@@ -2988,7 +3002,7 @@ static int wm_handlers_do_intern(bContext *C, wmWindow *win, wmEvent *event, Lis
}
else if (handler_base->poll == NULL || handler_base->poll(CTX_wm_region(C), event)) {
/* In advance to avoid access to freed event on window close. */
- always_pass = wm_event_always_pass(event);
+ const int always_pass = wm_event_always_pass(event);
/* Modal+blocking handler_base. */
if (handler_base->flag & WM_HANDLER_BLOCKING) {
@@ -3130,6 +3144,10 @@ static int wm_handlers_do_intern(bContext *C, wmWindow *win, wmEvent *event, Lis
wm_cursor_arrow_move(CTX_wm_window(C), event);
}
+ /* Do some extra sanity checking before returning the action. */
+ if (CTX_wm_window(C) != NULL) {
+ wm_event_handler_return_value_check(event, action);
+ }
return action;
}
@@ -3257,6 +3275,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
}
}
+ wm_event_handler_return_value_check(event, action);
return action;
}
@@ -3279,14 +3298,6 @@ static bool wm_event_inside_rect(const wmEvent *event, const rcti *rect)
return false;
}
-static bool wm_event_inside_region(const wmEvent *event, const ARegion *region)
-{
- if (wm_event_always_pass(event)) {
- return true;
- }
- return ED_region_contains_xy(region, event->xy);
-}
-
static ScrArea *area_event_inside(bContext *C, const int xy[2])
{
wmWindow *win = CTX_wm_window(C);
@@ -3500,6 +3511,55 @@ static void wm_event_handle_xrevent(bContext *C,
}
#endif /* WITH_XR_OPENXR */
+static int wm_event_do_region_handlers(bContext *C, wmEvent *event, ARegion *region)
+{
+ CTX_wm_region_set(C, region);
+
+ /* Call even on non mouse events, since the */
+ wm_region_mouse_co(C, event);
+
+ const wmWindowManager *wm = CTX_wm_manager(C);
+ if (!BLI_listbase_is_empty(&wm->drags)) {
+ /* Does polls for drop regions and checks #uiButs. */
+ /* Need to be here to make sure region context is true. */
+ if (ELEM(event->type, MOUSEMOVE, EVT_DROP) || ISKEYMODIFIER(event->type)) {
+ wm_drags_check_ops(C, event);
+ }
+ }
+
+ return wm_handlers_do(C, event, &region->handlers);
+}
+
+/**
+ * Send event to region handlers in \a area.
+ *
+ * Two cases:
+ * 1) Always pass events (#wm_event_always_pass()) are sent to all regions.
+ * 2) Event is passed to the region visually under the cursor (#ED_area_find_region_xy_visual()).
+ */
+static int wm_event_do_handlers_area_regions(bContext *C, wmEvent *event, ScrArea *area)
+{
+ /* Case 1. */
+ if (wm_event_always_pass(event)) {
+ int action = WM_HANDLER_CONTINUE;
+
+ LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
+ action |= wm_event_do_region_handlers(C, event, region);
+ }
+
+ wm_event_handler_return_value_check(event, action);
+ return action;
+ }
+
+ /* Case 2. */
+ ARegion *region_hovered = ED_area_find_region_xy_visual(area, RGN_TYPE_ANY, event->xy);
+ if (!region_hovered) {
+ return WM_HANDLER_CONTINUE;
+ }
+
+ return wm_event_do_region_handlers(C, event, region_hovered);
+}
+
void wm_event_do_handlers(bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
@@ -3682,36 +3742,12 @@ void wm_event_do_handlers(bContext *C)
if (wm_event_inside_rect(event, &area->totrct)) {
CTX_wm_area_set(C, area);
- if ((action & WM_HANDLER_BREAK) == 0) {
- LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
- if (wm_event_inside_region(event, region)) {
-
- CTX_wm_region_set(C, region);
-
- /* Call even on non mouse events, since the */
- wm_region_mouse_co(C, event);
-
- if (!BLI_listbase_is_empty(&wm->drags)) {
- /* Does polls for drop regions and checks #uiButs. */
- /* Need to be here to make sure region context is true. */
- if (ELEM(event->type, MOUSEMOVE, EVT_DROP) || ISKEYMODIFIER(event->type)) {
- wm_drags_check_ops(C, event);
- }
- }
-
- action |= wm_handlers_do(C, event, &region->handlers);
+ action |= wm_event_do_handlers_area_regions(C, event, area);
- /* Fileread case (python), T29489. */
- if (CTX_wm_window(C) == NULL) {
- wm_event_free_and_remove_from_queue_if_valid(event);
- return;
- }
-
- if (action & WM_HANDLER_BREAK) {
- break;
- }
- }
- }
+ /* Fileread case (python), T29489. */
+ if (CTX_wm_window(C) == NULL) {
+ wm_event_free_and_remove_from_queue_if_valid(event);
+ return;
}
CTX_wm_region_set(C, NULL);
diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 0609b8fd792..6f23fbe486a 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -685,7 +685,11 @@ if(UNIX AND NOT APPLE)
PATTERN "*.pyo" EXCLUDE # * any cache *
)
# On some platforms requests does have extra dependencies.
- set(_requests_deps "certifi" "chardet" "idna" "urllib3")
+ #
+ # Either 'chardet' or 'charset_normalizer" is used, depending on the
+ # version of Python. The code below silently skips the one that's not
+ # available, so we can just list both here.
+ set(_requests_deps "certifi" "chardet" "charset_normalizer" "idna" "urllib3")
foreach(_requests_dep ${_requests_deps})
if(EXISTS ${PYTHON_REQUESTS_PATH}/${_requests_dep})
install(