diff options
Diffstat (limited to 'source/blender')
39 files changed, 1240 insertions, 187 deletions
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, ¶ms, 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, ®ion->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, ®ion->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); |